fix vTerm paste from clipboard

This commit is contained in:
lxsang 2020-11-29 23:10:42 +01:00
parent ee3cfe92bd
commit 40ff4b4f05
22 changed files with 2732 additions and 6 deletions

8
VizApp/README.md Normal file
View File

@ -0,0 +1,8 @@
# GraphEditor
Generate graph from text using dot(viz) format
## Change logs
### v0.0.1-a
* First version

13
VizApp/assets/scheme.html Normal file
View File

@ -0,0 +1,13 @@
<afx-app-window data-id="graph_editor_win" apptitle="__(Viz editor)" width="650" height="500">
<afx-hbox>
<div data-id="datarea"></div>
<afx-resizer data-width="5" ></afx-resizer>
<div data-id="preview">
<div data-height="25" data-id="btn-container">
<afx-button data-id="btn-zoomin" iconclass="fa fa-search-plus"></afx-button>
<afx-button data-id="btn-zoomout" iconclass="fa fa-search-minus"></afx-button>
<afx-button data-id="btn-reset" iconclass="fa fa-square-o"></afx-button>
</div>
</div>
</afx-hbox>
</afx-app-window>

View File

@ -0,0 +1,8 @@
# GraphEditor
Generate graph from text using dot(viz) format
## Change logs
### v0.0.1-a
* First version

View File

@ -0,0 +1,24 @@
afx-app-window[data-id="graph_editor_win"] div[data-id="preview"]
{
display: flex;
align-items: center;
justify-content: center;
background-color: white;
}
afx-app-window[data-id="graph_editor_win"] afx-button button
{
border-radius: 0;
padding-top:2px;
padding-bottom: 2px;
}
afx-app-window[data-id="graph_editor_win"] afx-resizer{
background-color: transparent;
}
afx-app-window[data-id="graph_editor_win"] div[data-id="btn-container"]{
background-color: transparent;
position: absolute;
bottom:10px;
right: 10px;
display: inline;
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,14 @@
{
"app":"VizApp",
"name":"Viz editor",
"description":"Create graph with dot language",
"info":{
"author": "Xuan Sang LE",
"email": "xsang.le@gmail.com",
"licences": "GPLv3"
},
"version":"0.0.1-a",
"category":"Office",
"iconclass": "fa fa-sitemap",
"mimes":["text/.*graphviz"]
}

View File

@ -0,0 +1,13 @@
<afx-app-window data-id="graph_editor_win" apptitle="__(Viz editor)" width="650" height="500">
<afx-hbox>
<div data-id="datarea"></div>
<afx-resizer data-width="5" ></afx-resizer>
<div data-id="preview">
<div data-height="25" data-id="btn-container">
<afx-button data-id="btn-zoomin" iconclass="fa fa-search-plus"></afx-button>
<afx-button data-id="btn-zoomout" iconclass="fa fa-search-minus"></afx-button>
<afx-button data-id="btn-reset" iconclass="fa fa-square-o"></afx-button>
</div>
</div>
</afx-hbox>
</afx-app-window>

Binary file not shown.

Binary file not shown.

254
VizApp/coffees/main.coffee Normal file
View File

@ -0,0 +1,254 @@
# Copyright 2020 Xuan Sang LE <xsang.le AT gmail DOT com>
# AnTOS Web desktop is is licensed under the GNU General Public
# License v3.0, see the LICENCE file for more information
# This program is free software: you can redistribute it and/or
# modify it under the terms of the GNU 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
# General Public License for more details.
# You should have received a copy of the GNU General Public License
#along with this program. If not, see https://www.gnu.org/licenses/.
class VizApp extends this.OS.application.BaseApplication
constructor: ( args ) ->
super "VizApp", args
main: () ->
@currfile = if @args and @args.length > 0 then @args[0].path.asFileHandle() else "Untitled".asFileHandle()
@currfile.dirty = false
@datarea = @find "datarea"
@preview = @find "preview"
@btctn = @find "btn-container"
@previewSVG = undefined
$(@preview).append @btctn
ace.config.set("basePath", "#{window.location.pathname}/scripts/ace")
@editor = ace.edit @datarea
@editor.setOptions {
enableBasicAutocompletion: true,
enableLiveAutocompletion: true,
fontSize: "11pt"
}
@editor.getSession().setUseWrapMode true
@editor.session.setMode "ace/mode/dot"
@editor.setTheme "ace/theme/monokai"
@editor.on "input", () =>
if @editormux
@editormux = false
return false
if not @currfile.dirty
@currfile.dirty = true
if not @currfile.basename
@editormux = true
@editor.setValue VizApp.dummydot
@renderSVG false
@editor.container.addEventListener "keydown", (e) =>
@renderSVG true if e.keyCode is 13
, true
@bindKey "CTRL-R", () => @renderSVG false
@bindKey "ALT-G", () => @export "SVG"
@bindKey "ALT-P", () => @export "PNG"
@bindKey "ALT-N", () => @actionFile "#{@name}-New"
@bindKey "ALT-O", () => @actionFile "#{@name}-Open"
@bindKey "CTRL-S", () => @actionFile "#{@name}-Save"
@bindKey "ALT-W", () => @actionFile "#{@name}-Saveas"
@on "hboxchange", () =>
@editor.resize()
@calibrate()
@on "focus", () => @editor.focus()
(@find "btn-zoomin").onbtclick = (e) =>
@pan.zoomIn() if @pan
(@find "btn-zoomout").onbtclick = (e) =>
@pan.zoomOut() if @pan
(@find "btn-reset").onbtclick = (e) =>
@pan.resetZoom() if @pan
@open @currfile
menu: () ->
menu = [{
text: "__(File)",
nodes: [
{ text: "__(New)", dataid: "#{@name}-New", shortcut: "A-N" },
{ text: "__(Open)", dataid: "#{@name}-Open", shortcut: "A-O" },
{ text: "__(Save)", dataid: "#{@name}-Save", shortcut: "C-S" },
{ text: "__(Save as)",dataid: "#{@name}-Saveas" , shortcut: "A-W" },
{ text: "__(Render)", dataid: "#{@name}-Render", shortcut: "C-R" },
{
text: "__(Export as)",
nodes: [
{ text: "SVG", shortcut: "A-G" },
{ text: "PNG", shortcut: "A-P" }
],
onchildselect: (e) => @export e.data.item.data.text
},
],
onchildselect: (e) => @actionFile e.data.item.data.dataid
}]
menu
open: (file) ->
return if file.path is "Untitled"
file.dirty = false
file.read().then (d) =>
@currfile = file
@editormux = true
@currfile.dirty = false
@editor.setValue d
@scheme.apptitle = "#{@currfile.basename}"
@renderSVG false
.catch (e) => @error e.toString(), e
save: (file) ->
file.write "text/plain"
.then (d) =>
file.dirty = false
file.text = file.basename
@scheme.apptitle = "#{@currfile.basename}"
.catch (e) => @error e.toString(), e
actionFile: (e) ->
saveas = () =>
@openDialog "FileDialog",{title: __("Save as"), file: @currfile }
.then (d) =>
@currfile.setPath "#{d.file.path}/#{d.name}"
@save @currfile
.catch (e) => @error e.toString(), e
switch e
when "#{@name}-Open"
@openDialog "FileDialog", { title: __("Open file")}
.then ( d, f ) =>
@open d.file.path.asFileHandle()
.catch (e) => @error e.toString(), e
when "#{@name}-Save"
@currfile.cache = @editor.getValue()
return @save @currfile if @currfile.basename
saveas()
when "#{@name}-Saveas"
@currfile.cache = @editor.getValue()
saveas()
when "#{@name}-Render"
@renderSVG false
when "#{@name}-New"
@currfile = "Untitled".asFileHandle()
@currfile.cache = ""
@currfile.dirty = false
@editormux = true
@editor.setValue("")
export: (t) ->
@openDialog "FileDialog", {title: __("Export as"), file: @currfile }
.then (d) =>
fp = "#{d.file.path}/#{d.name}".asFileHandle()
viz = new Viz()
text = @editor.getValue()
switch t
when "SVG"
viz.renderSVGElement(text)
.then (svg) =>
serializer = new XMLSerializer()
fp.cache = serializer.serializeToString(svg)
fp.write "text/plain"
.then (r) =>
@notify __("File exported to SVG")
.catch (e) => @error __("Cannot export to {0}: {1}", t, e.toString()), e
.catch (error) =>
@error error.toString(), error
when "PNG"
# toDataURL("image/png")
viz.renderImageElement(text)
.then (img) =>
xhr = new XMLHttpRequest()
xhr.responseType = 'blob'
xhr.onload = () =>
blob = xhr.response
reader = new FileReader()
reader.onload = () =>
base64data = reader.result
fp.cache = base64data
fp.write "base64"
.then (r) =>
@notify __("File exported to PNG")
.catch (e) => @error __("Cannot export to {0}: {1}", t, e.toString()), e
reader.readAsDataURL(blob)
xhr.open('GET', img.src)
xhr.send()
.catch (error) =>
@error error.toString(), error
.catch (e) => @error e.toString(), e
renderSVG: (silent) ->
id = Math.floor(Math.random() * 100000) + 1
text = @editor.getValue()
viz = new Viz()
viz.renderSVGElement(text)
.then (element) =>
return if $(element).prop("tagName").toUpperCase() is "HTML"
$(@previewSVG).remove() if @previewSVG
@previewSVG = element
$(@preview).append element
@calibrate()
@pan = svgPanZoom element, {
zoomEnabled: true,
controlIconsEnabled: false,
fit: true,
center: true,
minZoom: 0.1
}
.catch (error) =>
@error error.toString(), error
calibrate: () ->
svg = ($ @preview).children("svg")[0]
if svg
prs = [$(@preview).width(), $(@preview).height()]
$(svg).attr "width", prs[0] + "px"
$(svg).attr "height", prs[1] + "px"
cleanup: (evt) ->
return unless @currfile
return unless @currfile.dirty
evt.preventDefault()
@ask {title: __("Quit"), text: __("Quit without saving ?") }
.then (d) =>
if d
@currfile.dirty = false
@quit()
VizApp.dummydot = """
digraph D {
A [shape=diamond]
B [shape=box]
C [shape=circle]
A -> B [style=dashed, color=grey]
A -> C [color="black:invis:black"]
A -> D [penwidth=5, arrowhead=none]
}
"""
VizApp.dependencies = [
"os://scripts/ace/ace.js"
]
this.OS.register "VizApp", VizApp

23
VizApp/css/main.css Normal file
View File

@ -0,0 +1,23 @@
afx-app-window[data-id="graph_editor_win"] div[data-id="preview"]
{
display: flex;
align-items: center;
justify-content: center;
background-color: white;
}
afx-app-window[data-id="graph_editor_win"] afx-button button
{
border-radius: 0;
padding-top:2px;
padding-bottom: 2px;
}
afx-app-window[data-id="graph_editor_win"] afx-resizer{
background-color: transparent;
}
afx-app-window[data-id="graph_editor_win"] div[data-id="btn-container"]{
background-color: transparent;
position: absolute;
bottom:10px;
right: 10px;
display: inline;
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

333
VizApp/javascripts/viz.js Normal file
View File

@ -0,0 +1,333 @@
/*
Viz.js 2.1.2 (Graphviz 2.40.1, Expat 2.2.5, Emscripten 1.37.36)
Copyright (c) 2014-2018 Michael Daines
Licensed under MIT license
This distribution contains other software in object code form:
Graphviz
Licensed under Eclipse Public License - v 1.0
http://www.graphviz.org
Expat
Copyright (c) 1998, 1999, 2000 Thai Open Source Software Center Ltd and Clark Cooper
Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006 Expat maintainers.
Licensed under MIT license
http://www.libexpat.org
zlib
Copyright (C) 1995-2013 Jean-loup Gailly and Mark Adler
http://www.zlib.net/zlib_license.html
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global.Viz = factory());
}(this, (function () { 'use strict';
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
return typeof obj;
} : function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
var classCallCheck = function (instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
};
var createClass = function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
var _extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
var WorkerWrapper = function () {
function WorkerWrapper(worker) {
var _this = this;
classCallCheck(this, WorkerWrapper);
this.worker = worker;
this.listeners = [];
this.nextId = 0;
this.worker.addEventListener('message', function (event) {
var id = event.data.id;
var error = event.data.error;
var result = event.data.result;
_this.listeners[id](error, result);
delete _this.listeners[id];
});
}
createClass(WorkerWrapper, [{
key: 'render',
value: function render(src, options) {
var _this2 = this;
return new Promise(function (resolve, reject) {
var id = _this2.nextId++;
_this2.listeners[id] = function (error, result) {
if (error) {
reject(new Error(error.message, error.fileName, error.lineNumber));
return;
}
resolve(result);
};
_this2.worker.postMessage({ id: id, src: src, options: options });
});
}
}]);
return WorkerWrapper;
}();
var ModuleWrapper = function ModuleWrapper(module, render) {
classCallCheck(this, ModuleWrapper);
var instance = module();
this.render = function (src, options) {
return new Promise(function (resolve, reject) {
try {
resolve(render(instance, src, options));
} catch (error) {
reject(error);
}
});
};
};
// https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding
function b64EncodeUnicode(str) {
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function (match, p1) {
return String.fromCharCode('0x' + p1);
}));
}
function defaultScale() {
if ('devicePixelRatio' in window && window.devicePixelRatio > 1) {
return window.devicePixelRatio;
} else {
return 1;
}
}
function svgXmlToImageElement(svgXml) {
var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
_ref$scale = _ref.scale,
scale = _ref$scale === undefined ? defaultScale() : _ref$scale,
_ref$mimeType = _ref.mimeType,
mimeType = _ref$mimeType === undefined ? "image/png" : _ref$mimeType,
_ref$quality = _ref.quality,
quality = _ref$quality === undefined ? 1 : _ref$quality;
return new Promise(function (resolve, reject) {
var svgImage = new Image();
svgImage.onload = function () {
var canvas = document.createElement('canvas');
canvas.width = svgImage.width * scale;
canvas.height = svgImage.height * scale;
var context = canvas.getContext("2d");
context.drawImage(svgImage, 0, 0, canvas.width, canvas.height);
canvas.toBlob(function (blob) {
var image = new Image();
image.src = URL.createObjectURL(blob);
image.width = svgImage.width;
image.height = svgImage.height;
resolve(image);
}, mimeType, quality);
};
svgImage.onerror = function (e) {
var error;
if ('error' in e) {
error = e.error;
} else {
error = new Error('Error loading SVG');
}
reject(error);
};
svgImage.src = 'data:image/svg+xml;base64,' + b64EncodeUnicode(svgXml);
});
}
function svgXmlToImageElementFabric(svgXml) {
var _ref2 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
_ref2$scale = _ref2.scale,
scale = _ref2$scale === undefined ? defaultScale() : _ref2$scale,
_ref2$mimeType = _ref2.mimeType,
mimeType = _ref2$mimeType === undefined ? 'image/png' : _ref2$mimeType,
_ref2$quality = _ref2.quality,
quality = _ref2$quality === undefined ? 1 : _ref2$quality;
var multiplier = scale;
var format = void 0;
if (mimeType == 'image/jpeg') {
format = 'jpeg';
} else if (mimeType == 'image/png') {
format = 'png';
}
return new Promise(function (resolve, reject) {
fabric.loadSVGFromString(svgXml, function (objects, options) {
// If there's something wrong with the SVG, Fabric may return an empty array of objects. Graphviz appears to give us at least one <g> element back even given an empty graph, so we will assume an error in this case.
if (objects.length == 0) {
reject(new Error('Error loading SVG with Fabric'));
}
var element = document.createElement("canvas");
element.width = options.width;
element.height = options.height;
var canvas = new fabric.Canvas(element, { enableRetinaScaling: false });
var obj = fabric.util.groupSVGElements(objects, options);
canvas.add(obj).renderAll();
var image = new Image();
image.src = canvas.toDataURL({ format: format, multiplier: multiplier, quality: quality });
image.width = options.width;
image.height = options.height;
resolve(image);
});
});
}
var Viz = function () {
function Viz() {
var _ref3 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
workerURL = _ref3.workerURL,
worker = _ref3.worker,
Module = _ref3.Module,
render = _ref3.render;
classCallCheck(this, Viz);
if (typeof workerURL !== 'undefined') {
this.wrapper = new WorkerWrapper(new Worker(workerURL));
} else if (typeof worker !== 'undefined') {
this.wrapper = new WorkerWrapper(worker);
} else if (typeof Module !== 'undefined' && typeof render !== 'undefined') {
this.wrapper = new ModuleWrapper(Module, render);
} else if (typeof Viz.Module !== 'undefined' && typeof Viz.render !== 'undefined') {
this.wrapper = new ModuleWrapper(Viz.Module, Viz.render);
} else {
throw new Error('Must specify workerURL or worker option, Module and render options, or include one of full.render.js or lite.render.js after viz.js.');
}
}
createClass(Viz, [{
key: 'renderString',
value: function renderString(src) {
var _ref4 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
_ref4$format = _ref4.format,
format = _ref4$format === undefined ? 'svg' : _ref4$format,
_ref4$engine = _ref4.engine,
engine = _ref4$engine === undefined ? 'dot' : _ref4$engine,
_ref4$files = _ref4.files,
files = _ref4$files === undefined ? [] : _ref4$files,
_ref4$images = _ref4.images,
images = _ref4$images === undefined ? [] : _ref4$images,
_ref4$yInvert = _ref4.yInvert,
yInvert = _ref4$yInvert === undefined ? false : _ref4$yInvert,
_ref4$nop = _ref4.nop,
nop = _ref4$nop === undefined ? 0 : _ref4$nop;
for (var i = 0; i < images.length; i++) {
files.push({
path: images[i].path,
data: '<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n<svg width="' + images[i].width + '" height="' + images[i].height + '"></svg>'
});
}
return this.wrapper.render(src, { format: format, engine: engine, files: files, images: images, yInvert: yInvert, nop: nop });
}
}, {
key: 'renderSVGElement',
value: function renderSVGElement(src) {
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
return this.renderString(src, _extends({}, options, { format: 'svg' })).then(function (str) {
var parser = new DOMParser();
return parser.parseFromString(str, 'image/svg+xml').documentElement;
});
}
}, {
key: 'renderImageElement',
value: function renderImageElement(src) {
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var scale = options.scale,
mimeType = options.mimeType,
quality = options.quality;
return this.renderString(src, _extends({}, options, { format: 'svg' })).then(function (str) {
if ((typeof fabric === 'undefined' ? 'undefined' : _typeof(fabric)) === "object" && fabric.loadSVGFromString) {
return svgXmlToImageElementFabric(str, { scale: scale, mimeType: mimeType, quality: quality });
} else {
return svgXmlToImageElement(str, { scale: scale, mimeType: mimeType, quality: quality });
}
});
}
}, {
key: 'renderJSONObject',
value: function renderJSONObject(src) {
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var format = options.format;
if (format !== 'json' || format !== 'json0') {
format = 'json';
}
return this.renderString(src, _extends({}, options, { format: format })).then(function (str) {
return JSON.parse(str);
});
}
}]);
return Viz;
}();
return Viz;
})));

14
VizApp/package.json Normal file
View File

@ -0,0 +1,14 @@
{
"app":"VizApp",
"name":"Viz editor",
"description":"Create graph with dot language",
"info":{
"author": "Xuan Sang LE",
"email": "xsang.le@gmail.com",
"licences": "GPLv3"
},
"version":"0.0.1-a",
"category":"Office",
"iconclass": "fa fa-sitemap",
"mimes":["text/.*graphviz"]
}

11
VizApp/project.json Normal file
View File

@ -0,0 +1,11 @@
{
"name": "VizApp",
"root": "home://workspace/antosdk-apps/Viz",
"css": ["css/main.css"],
"javascripts": [
"javascripts/svg-pan-zoom.js",
"javascripts/viz.js",
"javascripts/lite.render.js"],
"coffees": ["coffees/main.coffee"],
"copies": ["assets/scheme.html", "package.json", "README.md"]
}

View File

@ -161,13 +161,22 @@
"version": "0.0.4-a",
"download": "https://raw.githubusercontent.com/lxsang/antosdk-apps/master/TinyEditor/build/release/TinyEditor.zip"
},
{
"pkgname": "VizApp",
"name": "Viz editor",
"description": "https://raw.githubusercontent.com/lxsang/antosdk-apps/master/VizApp/README.md",
"category": "Office",
"author": "Xuan Sang LE",
"version": "0.0.1-a",
"download": "https://raw.githubusercontent.com/lxsang/antosdk-apps/master/VizApp/build/release/VizApp.zip"
},
{
"pkgname": "vTerm",
"name": "Virtual Terminal",
"description": "https://raw.githubusercontent.com/lxsang/antosdk-apps/master/vTerm/README.md",
"category": "System",
"author": "Xuan Sang LE",
"version": "0.1.4-a",
"version": "0.1.5-a",
"download": "https://raw.githubusercontent.com/lxsang/antosdk-apps/master/vTerm/build/release/vTerm.zip"
},
{

File diff suppressed because one or more lines are too long

View File

@ -6,7 +6,7 @@
"author": "Xuan Sang LE",
"email": "xsang.le@gmail.com"
},
"version":"0.1.4-a",
"version":"0.1.5-a",
"category":"System",
"iconclass":"fa fa-terminal",
"mimes":["none"],

Binary file not shown.

View File

@ -75,11 +75,22 @@ class vTerm extends this.OS.application.BaseApplication
mctxHandle: (data) ->
switch data.id
when "paste"
@_api.getClipboard().then (text) =>
cb = (text) =>
return unless text and text isnt ""
text = text.replace /\n/g, "\r"
@sub.send Antunnel.Msg.DATA, new TextEncoder("utf-8").encode(text) if @sub
@term.focus()
.catch (e) => @error __("Unable to paste"), e
@_api.getClipboard()
.then (text) =>
cb(text)
.catch (e) =>
@error __("Unable to paste"), e
#ask for user to enter the text manually
@openDialog("TextDialog", { title: "Paste text"})
.then (text) =>
cb(text)
.catch (err) => @error err.toString(), err
when "copy"
text = @term.getSelection()
return unless text and text isnt ""

View File

@ -6,7 +6,7 @@
"author": "Xuan Sang LE",
"email": "xsang.le@gmail.com"
},
"version":"0.1.4-a",
"version":"0.1.5-a",
"category":"System",
"iconclass":"fa fa-terminal",
"mimes":["none"],