Update Antos editor: Antedit now have it own extension manager

This commit is contained in:
lxsang 2021-06-13 11:06:49 +02:00
parent 8d4191b7ec
commit e86139e202
17 changed files with 480 additions and 126 deletions

View File

@ -6,6 +6,7 @@ The editor functionality can be extended by its extension mechanism.
Extension can be developed/released/isntalled by the editor itself.
### Change logs
- 0.1.10-b: Antedit now has it own extension manager
- 0.1.9-a: Allow output text selection
- 0.1.8-a: Allow to change language mode
- 0.1.7-a: Add keyboard shortcut support to extension actions

View File

@ -1,11 +1,27 @@
<afx-app-window apptitle="Antos Editor" width="600" height="400" data-id="antedit">
<afx-vbox>
<afx-hbox data-id="wrapper">
<afx-vbox data-width = "155" min-width="155" data-id = "sidebar">
<div data-height="10"></div>
<afx-vbox data-width = "200" min-width="200" data-id = "sidebar">
<afx-tab-container data-id="sidebar-tab-container" dir="row" tabbarwidth="30">
<!--File tab-->
<afx-hbox data-height="100%" iconclass="bi bi-files" >
<afx-vbox>
<div data-height="5"></div>
<afx-file-view chdir="false" data-id = "fileview" view="tree" status = "false">
</afx-file-view>
</afx-vbox>
</afx-hbox>
<!--extension tab-->
<afx-hbox data-height="100%" iconclass="bi bi-puzzle" >
<afx-vbox>
<input data-id="txt_ext_search" type="text" data-height="23">
<afx-list-view data-id="extension-list"></afx-list-view>
</afx-vbox>
</afx-hbox>
</afx-tab-container>
</afx-vbox>
<afx-resizer data-width = "3" ></afx-resizer>
<afx-vbox>
<afx-hbox>

View File

@ -1,13 +1,31 @@
{
"name": "Antedit",
"targets": {
"build": {
"require":["ts"],
"copy": {
"jobs": [
{
"name": "vfs-mkdir",
"data": ["build","build/debug","build/release"]
},
{
"name": "vfs-cp",
"data": {
"src": [
"extensions",
"assets/scheme.html",
"package.json",
"README.md",
"css/main.css"
],
"dest":"build/debug"
}
}
]
},
"build": {
"depend": ["copy"],
"require":["ts"],
"jobs": [
{
"name": "ts-import",
"data": ["sdk://core/ts/core.d.ts", "sdk://core/ts/jquery.d.ts","sdk://core/ts/antos.d.ts"]
@ -24,19 +42,6 @@
],
"dest": "build/debug/main.js"
}
},
{
"name": "vfs-cp",
"data": {
"src": [
"extensions",
"assets/scheme.html",
"package.json",
"README.md",
"css/main.css"
],
"dest":"build/debug"
}
}
]
},
@ -75,6 +80,14 @@
}
}
]
},
"run": {
"jobs": [
{
"name": "sdk-run-app",
"data": "build/debug"
}
]
}
}
}

View File

@ -6,6 +6,7 @@ The editor functionality can be extended by its extension mechanism.
Extension can be developed/released/isntalled by the editor itself.
### Change logs
- 0.1.10-b: Antedit now has it own extension manager
- 0.1.9-a: Allow output text selection
- 0.1.8-a: Allow to change language mode
- 0.1.7-a: Add keyboard shortcut support to extension actions

View File

@ -21,11 +21,11 @@
"name": "release"
},
{
"text": "__(Install extension from file)",
"text": "__(Install from file)",
"name": "install"
},
{
"text": "__(Install extension from URL)",
"text": "__(Install from URL)",
"name": "installFromURL"
}
]

View File

@ -35,7 +35,7 @@ afx-app-window[data-id = "antedit"] afx-tab-bar> afx-list-view > div.list-contai
border-radius: 0;
}
afx-app-window[data-id = "antedit"] afx-tab-bar> afx-list-view afx-list-view i.closable:before {
color:afafaf;
color:#afafaf;
}
afx-app-window[data-id = "antedit"] afx-tab-bar> afx-list-view ul afx-list-item:nth-child(even) li,
afx-app-window[data-id = "antedit"] afx-tab-bar> afx-list-view > div.list-container > ul li{
@ -48,6 +48,15 @@ afx-app-window[data-id = "antedit"] afx-tab-bar> afx-list-view > div.list-conta
padding-right: 20px;
border-right: 1px solid #272822;
}
afx-app-window[data-id = "antedit"] afx-tab-container[data-id="sidebar-tab-container"] afx-tab-bar> afx-list-view > div.list-container {
background-color: #333333;
}
afx-app-window[data-id = "antedit"] afx-tab-container[data-id="sidebar-tab-container"] afx-tab-bar> afx-list-view > div.list-container > ul li{
float: none;
font-size: 20px;
}
afx-app-window[data-id = "antedit"] .afx-window-wrapper afx-vbox[data-id = "sidebar"]{
background-color:#272822;
}
@ -91,49 +100,6 @@ afx-app-window[data-id = "antedit"] .afx-window-wrapper div[data-id="statctn"] a
padding-left: 10px;
}
afx-app-window[data-id = "cmd-win"] .afx-window-wrapper{
border-radius: 0px;
border: 0;
/*border: 1px solid #37373d;*/
background-color: transparent;
box-shadow: 0px 3px 6px 0px rgba(0,0,0,0.65);
}
afx-app-window[data-id = "cmd-win"] .afx-window-wrapper afx-list-view ul afx-list-item:nth-child(even) li
{
background-color: transparent;
}
afx-app-window[data-id = "cmd-win"] .afx-window-wrapper afx-list-view afx-list-item li{
background-color: transparent;
color:#afafaf;
}
afx-app-window[data-id = "cmd-win"] .afx-window-wrapper div.list-container > ul li:hover{
background-color: #37373d;
}
afx-app-window[data-id = "cmd-win"] .afx-window-wrapper afx-list-view ul afx-list-item:nth-child(even) li.selected,
afx-app-window[data-id = "cmd-win"] .afx-window-wrappe dafx-list-viewafx-list-view ul li.selected
{
background-color: #116cd6;
color:white;
}
afx-app-window[data-id = "cmd-win"] .afx-window-top{
height: 0;
border:0;
}
afx-app-window[data-id = "cmd-win"] input{
border: 1px solid #007acc;
border-radius: 0;
font-size: 12px;
color:#afafaf;
background-color:#272822;
padding-left: 5px;
margin: 3px;
}
afx-app-window[data-id = "cmd-win"] .afx-window-content{
background-color:#272822;
}
afx-app-window[data-id = "antedit"] div[data-id="output-tab"] {
overflow-y: auto;
overflow-x: hidden;
@ -166,3 +132,40 @@ afx-app-window[data-id = "antedit"] afx-button[ data-id="logger-clear" ] button{
border: 0;
background: transparent;
}
afx-app-window[data-id = "antedit"] afx-antedit-ext-list-item {
color: white !important;
}
afx-app-window[data-id = "antedit"] afx-antedit-ext-list-item afx-label i.label-text{
font-weight: bold !important;
}
afx-app-window[data-id = "antedit"] afx-antedit-ext-list-item p {
margin: 0;
padding: 0;
padding-left:15px;
font-size: 11px;
}
afx-app-window[data-id = "antedit"] afx-antedit-ext-list-item p[data-id="ext-list-item-b-p"] {
text-align: right;
font-size: 11px;
}
afx-app-window[data-id = "antedit"] afx-antedit-ext-list-item > li {
background-color: transparent !important;
padding-right: 5px !important;
}
afx-app-window[data-id = "antedit"] afx-antedit-ext-list-item > li.selected {
background-color: #116cd6 !important;
}
afx-app-window[data-id = "antedit"] afx-antedit-ext-list-item button {
height: 22px;
width: 24px;
padding: 0 !important;
}
afx-app-window[data-id = "antedit"] input[data-id="txt_ext_search"] {
background-color: transparent;
border-radius: 0;
border-color: #333;
color: white;
}

File diff suppressed because one or more lines are too long

View File

@ -7,7 +7,7 @@
"author": "Xuan Sang LE",
"email": "mrsang@iohub.dev"
},
"version": "0.1.9-a",
"version": "0.1.10-b",
"category": "Development",
"iconclass": "bi bi-journal-code",
"mimes": [

View File

@ -1,11 +1,27 @@
<afx-app-window apptitle="Antos Editor" width="600" height="400" data-id="antedit">
<afx-vbox>
<afx-hbox data-id="wrapper">
<afx-vbox data-width = "155" min-width="155" data-id = "sidebar">
<div data-height="10"></div>
<afx-vbox data-width = "200" min-width="200" data-id = "sidebar">
<afx-tab-container data-id="sidebar-tab-container" dir="row" tabbarwidth="30">
<!--File tab-->
<afx-hbox data-height="100%" iconclass="bi bi-files" >
<afx-vbox>
<div data-height="5"></div>
<afx-file-view chdir="false" data-id = "fileview" view="tree" status = "false">
</afx-file-view>
</afx-vbox>
</afx-hbox>
<!--extension tab-->
<afx-hbox data-height="100%" iconclass="bi bi-puzzle" >
<afx-vbox>
<input data-id="txt_ext_search" type="text" data-height="23">
<afx-list-view data-id="extension-list"></afx-list-view>
</afx-vbox>
</afx-hbox>
</afx-tab-container>
</afx-vbox>
<afx-resizer data-width = "3" ></afx-resizer>
<afx-vbox>
<afx-hbox>

Binary file not shown.

View File

@ -35,7 +35,7 @@ afx-app-window[data-id = "antedit"] afx-tab-bar> afx-list-view > div.list-contai
border-radius: 0;
}
afx-app-window[data-id = "antedit"] afx-tab-bar> afx-list-view afx-list-view i.closable:before {
color:afafaf;
color:#afafaf;
}
afx-app-window[data-id = "antedit"] afx-tab-bar> afx-list-view ul afx-list-item:nth-child(even) li,
afx-app-window[data-id = "antedit"] afx-tab-bar> afx-list-view > div.list-container > ul li{
@ -48,6 +48,15 @@ afx-app-window[data-id = "antedit"] afx-tab-bar> afx-list-view > div.list-conta
padding-right: 20px;
border-right: 1px solid #272822;
}
afx-app-window[data-id = "antedit"] afx-tab-container[data-id="sidebar-tab-container"] afx-tab-bar> afx-list-view > div.list-container {
background-color: #333333;
}
afx-app-window[data-id = "antedit"] afx-tab-container[data-id="sidebar-tab-container"] afx-tab-bar> afx-list-view > div.list-container > ul li{
float: none;
font-size: 20px;
}
afx-app-window[data-id = "antedit"] .afx-window-wrapper afx-vbox[data-id = "sidebar"]{
background-color:#272822;
}
@ -91,49 +100,6 @@ afx-app-window[data-id = "antedit"] .afx-window-wrapper div[data-id="statctn"] a
padding-left: 10px;
}
afx-app-window[data-id = "cmd-win"] .afx-window-wrapper{
border-radius: 0px;
border: 0;
/*border: 1px solid #37373d;*/
background-color: transparent;
box-shadow: 0px 3px 6px 0px rgba(0,0,0,0.65);
}
afx-app-window[data-id = "cmd-win"] .afx-window-wrapper afx-list-view ul afx-list-item:nth-child(even) li
{
background-color: transparent;
}
afx-app-window[data-id = "cmd-win"] .afx-window-wrapper afx-list-view afx-list-item li{
background-color: transparent;
color:#afafaf;
}
afx-app-window[data-id = "cmd-win"] .afx-window-wrapper div.list-container > ul li:hover{
background-color: #37373d;
}
afx-app-window[data-id = "cmd-win"] .afx-window-wrapper afx-list-view ul afx-list-item:nth-child(even) li.selected,
afx-app-window[data-id = "cmd-win"] .afx-window-wrappe dafx-list-viewafx-list-view ul li.selected
{
background-color: #116cd6;
color:white;
}
afx-app-window[data-id = "cmd-win"] .afx-window-top{
height: 0;
border:0;
}
afx-app-window[data-id = "cmd-win"] input{
border: 1px solid #007acc;
border-radius: 0;
font-size: 12px;
color:#afafaf;
background-color:#272822;
padding-left: 5px;
margin: 3px;
}
afx-app-window[data-id = "cmd-win"] .afx-window-content{
background-color:#272822;
}
afx-app-window[data-id = "antedit"] div[data-id="output-tab"] {
overflow-y: auto;
overflow-x: hidden;
@ -166,3 +132,40 @@ afx-app-window[data-id = "antedit"] afx-button[ data-id="logger-clear" ] button{
border: 0;
background: transparent;
}
afx-app-window[data-id = "antedit"] afx-antedit-ext-list-item {
color: white !important;
}
afx-app-window[data-id = "antedit"] afx-antedit-ext-list-item afx-label i.label-text{
font-weight: bold !important;
}
afx-app-window[data-id = "antedit"] afx-antedit-ext-list-item p {
margin: 0;
padding: 0;
padding-left:15px;
font-size: 11px;
}
afx-app-window[data-id = "antedit"] afx-antedit-ext-list-item p[data-id="ext-list-item-b-p"] {
text-align: right;
font-size: 11px;
}
afx-app-window[data-id = "antedit"] afx-antedit-ext-list-item > li {
background-color: transparent !important;
padding-right: 5px !important;
}
afx-app-window[data-id = "antedit"] afx-antedit-ext-list-item > li.selected {
background-color: #116cd6 !important;
}
afx-app-window[data-id = "antedit"] afx-antedit-ext-list-item button {
height: 22px;
width: 24px;
padding: 0 !important;
}
afx-app-window[data-id = "antedit"] input[data-id="txt_ext_search"] {
background-color: transparent;
border-radius: 0;
border-color: #333;
color: white;
}

View File

@ -21,11 +21,11 @@
"name": "release"
},
{
"text": "__(Install extension from file)",
"text": "__(Install from file)",
"name": "install"
},
{
"text": "__(Install extension from URL)",
"text": "__(Install from URL)",
"name": "installFromURL"
}
]

View File

@ -7,7 +7,7 @@
"author": "Xuan Sang LE",
"email": "mrsang@iohub.dev"
},
"version": "0.1.9-a",
"version": "0.1.10-b",
"category": "Development",
"iconclass": "bi bi-journal-code",
"mimes": [

View File

@ -321,16 +321,57 @@ namespace OS {
);
}
/**
*
*
* @param {string} name extension name
* @returns {Promise<any>}
* @memberof EditorExtensionMaker
*/
uninstall(name: string): Promise<void>
{
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);
}
});
}
/**
*
*
* @private
* @param {string} path
* @returns {Promise<any>}
* @memberof EditorExtensionMaker
*/
private installZip(path: string): Promise<void> {
installZip(path: string): Promise<void> {
return new Promise(async (resolve, reject) => {
try{
await API.requires("os://scripts/jszip.min.js");
@ -338,6 +379,8 @@ namespace OS {
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 = [];

View File

@ -1,9 +1,110 @@
namespace OS {
declare var $: any;
export namespace GUI {
export namespace tag {
class AntEditExtensionListItem extends ListViewItemTag
{
protected itemlayout(): TagLayoutType {
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"
}
]
}
]
};
}
protected ondatachange(): void {
const v = this.data;
if (!v) {
return;
}
const label = this.refs.label as LabelTag;
label.iconclass = "bi bi-puzzle";
label.text = `${v.text} - v${v.version}`;
// add description
const p_desc = this.refs.desc as HTMLParagraphElement;
$(p_desc).text(v.description);
// button install
const btn_install = this.refs.btn_install as ButtonTag;
// button remove
const btn_remove = this.refs.btn_remove as ButtonTag;
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(" ");
}
}
protected init(): void {
this.closable = false;
this.data = {};
// button install
const btn_install = this.refs.btn_install as ButtonTag;
// button remove
const btn_remove = this.refs.btn_remove as ButtonTag;
btn_install.onbtclick = (e) => {
if(!this.data.download || !this.data.install_action)
{
return;
}
this.data.install_action(this.data.download, (v: string) =>{
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);
});
};
}
protected reload(d?: any): void {
this.data = this.data;
}
}
define("afx-antedit-ext-list-item", AntEditExtensionListItem)
}
}
export namespace application {
declare var require: any;
export type AnteditLogger = typeof Logger;
const DEFAULT_REPO = "https://raw.githubusercontent.com/lxsang/antos-antedit-extensions/master/extensions.json"
/**
* A simple yet powerful code/text editor.
*
@ -44,6 +145,13 @@ namespace OS {
*/
extensions: GenericObject<any>;
/**
* Variable stores all available extension meta-data
* @type {GenericObject<any>[]}
* @meberof Antedit
*/
private extension_meta_data: GenericObject<any>[];
/**
* Reference to the sidebar file view UI
*
@ -62,6 +170,24 @@ namespace OS {
*/
private sidebar: GUI.tag.VBoxTag;
/**
* Reference to the sidebar tab container
*
* @private
* @type {GUI.tag.TabContainerTag}
* @memberof Antedit
*/
private sidebar_container: GUI.tag.TabContainerTag;
/**
* Reference to the extension list UI
*
* @private
* @type {GUI.tag.ListViewTag}
* @memberof Antedit
*/
private extension_list_view: GUI.tag.ListViewTag;
/**
* Reference to the bottom bar
*
@ -157,10 +283,12 @@ namespace OS {
this.eum = new EditorModelManager();
this.fileview = this.find("fileview") as GUI.tag.FileViewTag;
this.sidebar = this.find("sidebar") as GUI.tag.VBoxTag;
this.sidebar_container = this.find("sidebar-tab-container") as GUI.tag.TabContainerTag;
this.bottombar = this.find("bottombar") as GUI.tag.TabContainerTag;
this.langstat = this.find("langstat") as GUI.tag.LabelTag;
this.editorstat = this.find("editorstat") as GUI.tag.LabelTag;
this.filestat = this.find("current-file-lbl") as GUI.tag.LabelTag;
this.extension_list_view = this.find("extension-list") as GUI.tag.ListViewTag;
this.logger = new Logger(this.find("output-tab"));
this.split_mode = true;
@ -196,6 +324,8 @@ namespace OS {
}
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');
@ -226,6 +356,8 @@ namespace OS {
* @memberof Antedit
*/
private setup(): void {
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;
@ -313,7 +445,6 @@ namespace OS {
if (this.setting.showBottomBar === undefined) {
this.setting.showBottomBar = false;
}
//TODO: support change editor model languages
const extension = {
name: "Editor",
text: __("Editor")
@ -336,12 +467,126 @@ namespace OS {
}
});
$(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
*/
private extension_search(e: JQuery.KeyUpEvent): void
{
let k: string;
const search_box = this.find("txt_ext_search") as HTMLInputElement;
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
*/
private refreshExtensionRepositories(): void {
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: string,callback: (arg0: string) => void) => {
(new Antedit.extensions["EditorExtensionMaker"](this))
.installZip(url)
.then(() => {
this.loadExtensionMetaData();
if(callback)
{
callback(ext.version);
}
this.notify(__("Extension '{0}' installed", ext.text));
})
.catch((error: Error) => {
this.error(__("Unable to install '{0}': {1}", ext.text , error.toString()), error);
});
};
ext.uninstall_action = (name: string, callback: () => void) => {
(new Antedit.extensions["EditorExtensionMaker"](this))
.uninstall(name)
.then(() => {
this.loadExtensionMetaData();
if(callback)
{
callback();
}
this.notify(__("Extension '{0}' uninstalled", name));
})
.catch((error: 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
*
@ -374,6 +619,7 @@ namespace OS {
if (this.currdir) {
$(this.sidebar).show();
this.fileview.path = this.currdir.path;
this.refreshExtensionRepositories();
} else {
$(this.sidebar).hide();
}
@ -426,6 +672,11 @@ namespace OS {
this.showBottomBar(!this.setting.showBottomBar);
}
/**
* Toogle split mode
*
* #memberof Antedit
**/
private toggleSplitMode(): void {
const right_pannel = this.find("right-panel");
const right_editor = this.eum.editors[1];
@ -968,6 +1219,14 @@ namespace OS {
return this;
}
/**
* Add an action to the editor
*
* @param {GenericObject<any>} extension
* @param {GenericObject<any>} action
* @param callback
* @memberof EditorModelManager
*/
addAction(extension: GenericObject<any>, action: GenericObject<any>, callback): void {
const ed_action = {
id: `${extension.name}:${action.name}`,

View File

@ -1,5 +1,4 @@
namespace OS {
declare var $: any;
export namespace application {
class Logger {

View File

@ -45,7 +45,7 @@
"description": "https://raw.githubusercontent.com/lxsang/antosdk-apps/master/Antedit/README.md",
"category": "Development",
"author": "Xuan Sang LE",
"version": "0.1.9-a",
"version": "0.1.10-b",
"dependencies": ["MonacoCore@0.23.0-r"],
"download": "https://raw.githubusercontent.com/lxsang/antosdk-apps/master/Antedit/build/release/Antedit.zip"
},