diff --git a/frame.js b/frame.js index 2aaecce5..8c490301 100644 --- a/frame.js +++ b/frame.js @@ -334,17 +334,17 @@ Volcanos(chat.ONAPPEND, {_init: function(can, meta, list, cb, target, field) { }) }); return res }, figure: function(can, meta, target, cb) { if (meta.action == ice.AUTO || can.base.isIn(meta.type, html.BUTTON)) { return } - var input = meta.action||mdb.KEY; can.require([chat.PLUGIN_INPUT+input+nfs._JS], function(can) { + var input = meta.action||mdb.KEY, path = chat.PLUGIN_INPUT+input+nfs._JS; can.require([path], function(can) { function _cb(sub, value, old) { if (value == old) { return } can.base.isFunc(cb)? cb(sub, value, old): target.value = value||"", can.onmotion.delay(can, function() { can.onmotion.focus(can, target) }) } can.core.ItemCB(can.onfigure[input], function(key, on) { var last = target[key]||function(){}; target[key] = function(event) { target._can && can.onlayout.figure(event, can, target._can._target) can.core.CallFunc(on, {event: event, can: can, meta: meta, cb: _cb, target: target, sub: target._can, last: last, cbs: function(cb) { if (target._can) { return can.onmotion.toggle(can, target._can._target, true), can.base.isFunc(cb) && cb(target._can, _cb) } - can.onappend._init(can, {type: html.INPUT, name: input, pos: chat.FLOAT, mode: meta.mode}, [chat.PLUGIN_INPUT+input+nfs._JS], function(sub) { sub.Conf(meta) + can.onappend._init(can, {type: html.INPUT, name: input, pos: chat.FLOAT, mode: meta.mode}, [path], function(sub) { sub.Conf(meta) sub.run = function(event, cmds, cb) { var msg = sub.request(event) if (meta.range) { for (var i = meta.range[0]; i < meta.range[1]; i += meta.range[2]||1) { msg.Push(mdb.VALUE, i) } cb(msg); return } (meta.run||can.run)(sub.request(event, can.Option()), cmds, cb, true) - }, target._can = sub, can.base.Copy(sub, can.onfigure[input], true) + }, target._can = sub, can.base.Copy(sub, can.onfigure[input], true), sub._name = sub._path = path sub.hidden = function() { return sub._target.style.display == html.NONE } sub.close = function() { can.page.Remove(can, sub._target), delete(target._can) } can.onmotion.delay(can, function() { can.onlayout.figure({target: target}, can, sub._target), can.page.style(sub, sub._target, meta.style) @@ -598,7 +598,7 @@ Volcanos(chat.ONKEYMAP, {_init: function(can, target) { }, insertText: function(target, text) { var start = target.selectionStart var left = target.value.slice(0, target.selectionStart), rest = target.value.slice(target.selectionEnd) - return target.value = left+text+rest, target.setSelectionRange(start, start+text.length) + return target.value = left+text+rest, target.setSelectionRange(start+text.length, start+text.length) }, deleteText: function(target, start, count) { var end = count? start+count: target.value.length, cut = target.value.slice(start, end) @@ -609,8 +609,8 @@ Volcanos(chat.ONKEYMAP, {_init: function(can, target) { selectCtrlN: function(event, can, target, key, cb) { if (!event.ctrlKey || event.key < "0" || event.key > "9") { return } return can.page.Select(can, target, key, function(target, index) { if (index+1 == event.key) { return cb(target) } })[0] }, - selectInputs: function(event, can, cb, target) { - if (can.page.ismodkey(event)) { return } + selectInputs: function(event, can, cb, target) { if (can.page.ismodkey(event)) { return } + if (event.key == lang.ESCAPE) { return target.blur() } if (event.ctrlKey || event.key == lang.TAB) { if (can.base.isUndefined(target._index)) { target._index = -1, target._value = target.value } function select(order) { if (order == -1) { target.value = target._value } var index = 0; return can.page.Select(can, can._output, [html.TBODY, html.TR], function(tr) { @@ -620,8 +620,7 @@ Volcanos(chat.ONKEYMAP, {_init: function(can, target) { } return tr }).length } - var total = select(target._index); switch (event.key) { - case lang.TAB: can.onkeymap.prevent(event) + var total = select(target._index), key = event.key; if (event.key == lang.TAB) { key = event.shiftKey? "p": "n" } switch (key) { case "n": select(target._index = (target._index+2) % (total+1) - 1); break case "p": select(target._index = (target._index+total+1) % (total+1) - 1); break default: return diff --git a/lib/misc.js b/lib/misc.js index 9e1e2ccd..3b2d8939 100644 --- a/lib/misc.js +++ b/lib/misc.js @@ -163,11 +163,12 @@ Volcanos("misc", {Message: function(event, can) { var msg = {} objs.topic = can.misc.Search(can, chat.TOPIC) return can.misc.MergeURL(can, objs, true) }, - MergeURL: function(can, objs, clear) { + MergeURL: function(can, objs, clear) { var pod = "" var path = location.pathname; objs._path && (path = objs._path), delete(objs._path) objs.pod && (path = can.base.Path("/chat/pod/", objs.pod)), delete(objs.pod) - objs.cmd && (path = can.base.Path(path.indexOf("/chat/") == 0? path: "/chat", ice.CMD, objs.cmd)), delete(objs.cmd) - objs.website && (path = can.base.Path(path.indexOf("/chat/") == 0? path: "/chat", web.WEBSITE, objs.website)), delete(objs.website) + var ls = path.split("/"); ls[1] == "chat" && ls[2] == "pod" && (pod = ls[3]) + objs.cmd && (path = can.base.Path("/chat", pod? "pod/"+pod: "", ice.CMD, objs.cmd)), delete(objs.cmd) + objs.website && (path = can.base.Path("/chat", pod? "pod/"+pod: "", web.WEBSITE, objs.website)), delete(objs.website) return can.base.MergeURL(location.origin+path+(clear?"":location.search), objs) }, SearchOrConf: function(can, key, def) { return can.base.getValid(can.misc.Search(can, key), can.Conf(key), def) }, diff --git a/plugin/input/img.js b/plugin/input/img.js index 293b7195..4de7996f 100644 --- a/plugin/input/img.js +++ b/plugin/input/img.js @@ -1,6 +1,6 @@ Volcanos(chat.ONFIGURE, {img: { _init: function(can, meta, target) { var images = can.core.Split(target.value); can.onmotion.hidden(can, target) - var count = parseInt(meta.value||"1"), width = target.parentNode.offsetWidth-12; for (var n = 1; n < 10; n++) { if (n * n >= count) { width = width/n; break } } width -= 1 + var count = parseInt(meta.value||"1"), width = target.parentNode.offsetWidth-12; for (var n = 1; n < 10; n++) { if (n*n >= count) { width = width/n; break } } width -= 1 function add(target, hash) { target._hash = hash, can.page.Appends(can, target, [{img: can.misc.MergeURL(can, {_path: web.SHARE_CACHE+hash}, true), height: width, width: width}]) } function set() { target.value = can.page.SelectChild(can, target.parentNode, html.DIV, function(target) { return target._hash }).join(ice.FS) } for (var i = 0; i < count; i++) { diff --git a/plugin/input/key.js b/plugin/input/key.js index 23d77976..915f1d8c 100644 --- a/plugin/input/key.js +++ b/plugin/input/key.js @@ -5,6 +5,7 @@ Volcanos(chat.ONFIGURE, {key: { can._delay_hidden = false, cb(can, value, target.value), msg.Option(ice.MSG_PROCESS) == ice.PROCESS_AGAIN && can.onmotion.delay(can, function() { can._load(event, can, cb, target, name, value) }) }} }), can.onappend._status(can, [mdb.TOTAL, mdb.INDEX]), can.Status(mdb.TOTAL, msg.Length()) + msg.append.length == 1 && can.page.ClassList.add(can, can._target, chat.SIMPLE) }, _load: function(event, can, cb, target, name, value) { can.runAction(event, mdb.INPUTS, [name, value||""], function(msg) { name == ctx.INDEX && can.core.Item(can.onengine.plugin.meta, function(key) { msg.Push(ctx.INDEX, can.core.Keys(ice.CAN, key)) }) @@ -15,13 +16,9 @@ Volcanos(chat.ONFIGURE, {key: { meta.msg && meta.msg.Length() > 0? sub._show(sub, meta.msg, cb, target, meta.name): sub._load(event, sub, cb, target, meta.name, target.value) }) }, onblur: function(event, can, sub) { can.onmotion.delay(can, function() { sub._delay_hidden || can.onmotion.hidden(can, sub._target), sub._delay_hidden = false }, 300) }, - onkeydown: function(event, can, meta, cb, target, sub, last) { if (sub.hidden()) { return } switch (event.key) { - case "n": - case "p": - case lang.ESCAPE: target.blur(); break - case lang.TAB: can.onkeymap.selectInputs(event, sub, function() { sub._load(event, sub, cb, target, meta.name) }, target); break - case lang.ENTER: if (meta._enter && (!can.page.tagis(event.target, html.TEXTAREA) || event.ctrlKey) && meta._enter(event)) { break } - default: can.onkeymap.selectCtrlN(event, can, sub._output, "tr:not(.hidden)>td:first-child", function(td) { return cb(sub, td.innerText, target.value), td }) || last(event) - target.value == "" && sub._load(event, sub, cb, target, meta.name) - } }, + onkeydown: function(event, can, meta, cb, target, sub, last) { if (sub.hidden()) { return } + if (event.key == lang.ENTER && meta._enter && (!can.page.tagis(event.target, html.TEXTAREA) || event.ctrlKey) && meta._enter(event)) { return } + can.onkeymap.selectCtrlN(event, can, sub._output, "tr:not(.hidden)>td:first-child", function(td) { return cb(sub, td.innerText, target.value), td }) + || can.onkeymap.selectInputs(event, sub, function() { sub._load(event, sub, cb, target, meta.name) }, target) + }, }}) diff --git a/plugin/input/keyboard.css b/plugin/input/keyboard.css index 7e795bed..a0442960 100644 --- a/plugin/input/keyboard.css +++ b/plugin/input/keyboard.css @@ -1,4 +1,3 @@ -fieldset.keyboard>div.output { min-width:750px; } fieldset.keyboard>div.output>br { clear:both; } fieldset.keyboard>div.output>div.key { font-size:24px; text-align:center; @@ -15,7 +14,7 @@ fieldset.keyboard>div.output>div.key.tail { width:70px; } fieldset.keyboard>div.output>div.key.Tab { width:60px; } fieldset.keyboard>div.output>div.key.Ctrl { width:70px; } fieldset.keyboard>div.output>div.key.Shift { width:90px; } -fieldset.keyboard>div.output>div.key.Win { width:60px; } +fieldset.keyboard>div.output>div.key.Cmd { width:60px; } fieldset.keyboard>div.output>div.key.Alt { width:60px; } fieldset.keyboard>div.output>div.key.Space { width:300px; } fieldset.keyboard>div.output>div.key.Shift.tail { width:140px; } diff --git a/plugin/input/keyboard.js b/plugin/input/keyboard.js index 86c51e1f..3148e96b 100644 --- a/plugin/input/keyboard.js +++ b/plugin/input/keyboard.js @@ -1,22 +1,26 @@ Volcanos(chat.ONFIGURE, {keyboard: { - onclick: function(can, cbs, target) { cbs(function(sub) { var msg = can.request(); sub._normal(can, msg), can.onfigure.keyboard._show(sub, msg, target) }) }, - _show: function(can, msg, target) { can.require(["/plugin/input/keyboard.css"]) - msg.Table(function(item) { item.type == "head" && can.page.Append(can, can._output, html.BR) + _init: function(can, meta, target) { can.onfigure.keyboard[target.value] && (target.value = "") }, + onclick: function(can, meta, target, cbs) { cbs(function(sub) { var msg = can.request() + can.page.style(can, can._output, html.MIN_WIDTH, sub[meta.value||"_normal"](sub, msg)) + can.onfigure.keyboard._show(sub, msg, target) + }) }, + _show: function(can, msg, target) { can.require(["/plugin/input/keyboard.css"]), can.onmotion.clear(can, can._output) + msg.Table(function(item) { item.type == html.HEAD && can.page.Append(can, can._output, html.BR) function add(value) { target.value += value, target.focus(), can.user.toast(can, value||item.name) } function hold() { can.page.ClassList.add(can, div, "hold") } var div = can.page.Append(can, can._output, [{view: item.type+ice.SP+item.name+(item.name.indexOf(ice.NL)>-1? " double": item.name.length>1? " special": ""), list: [{text: [item.name]}], onclick: function(event) { switch (item.name) { - case "clear": target.value = "", target.focus(); break - case "close": can.close(); break - case "Esc": can.close(); break - case "Ctrl": can._ctrl = !can._ctrl, hold(); break - case "Shift": can._shift = !can._shift, hold(); break - case "Backspace": target.value = target.value.slice(0, -1), add(""); break - case "Enter": break + case cli.CLEAR: target.value = "", target.focus(); break + case cli.CLOSE: can.close(); break + case lang.ESC: can.close(); break + case lang.CTRL: can._ctrl = !can._ctrl, hold(); break + case lang.SHIFT: can._shift = !can._shift, hold(); break + case lang.BACKSPACE: target.value = target.value.slice(0, -1), add(""); break + case lang.ENTER: break default: can._shift = can._shift||event.shiftKey if (item.name == lang.TAB) { add(ice.TB) - } else if (item.name == "Space") { + } else if (item.name == lang.SPACE) { add(ice.SP) } else if (item.name.indexOf(ice.NL) > -1) { var ls = can.core.Split(item.name, ice.NL, ice.NL, ice.NL) add(can._shift? ls[0]: ls[1]) @@ -27,15 +31,24 @@ Volcanos(chat.ONFIGURE, {keyboard: { } }]).first }) }, - _normal: function(can, msg) { - can.core.List([["Esc", "close", "clear"], - ["~\n`", "!\n1", "@\n2", "#\n3", "$\n4", "%\n5", "^\n6", "&\n7", "*\n8", "(\n9", ")\n0", "_\n-", "+\n=", "Backspace"], - ["Tab", "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "{\n[", "}\n]", "|\n\\"], - ["Ctrl", "a", "s", "d", "f", "g", "h", "j", "k", "l", ":\n;", "\"\n'", "Enter"], - ["Shift", "z", "x", "c", "v", "b", "n", "m", "<\n,", ">\n.", "?\n/", "Shift"], - ["Ctrl", "Win", "Alt", "Space", "Alt", "Win", "Ctrl"], + _number: function(can, msg) { + can.core.List([ + ["1", "2", "3"], + ["4", "5", "6"], + ["7", "8", "9"], ], function(list) { can.core.List(list, function(item, index) { - msg.Push(can.base.isObject(item)? item: {type: "key"+(index == 0? " head": index == list.length-1? " tail": ""), name: item}) - }) }) + msg.Push(can.base.isObject(item)? item: {type: [mdb.KEY, (index == 0? html.HEAD: "")].join(ice.SP), name: item}) + }) }); return 150 + }, + _normal: function(can, msg) { + can.core.List([[lang.ESC, "close", "clear"], + ["~\n`", "!\n1", "@\n2", "#\n3", "$\n4", "%\n5", "^\n6", "&\n7", "*\n8", "(\n9", ")\n0", "_\n-", "+\n=", lang.BACKSPACE], + [lang.TAB, "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "{\n[", "}\n]", "|\n\\"], + [lang.CTRL, "a", "s", "d", "f", "g", "h", "j", "k", "l", ":\n;", "\"\n'", lang.ENTER], + [lang.SHIFT, "z", "x", "c", "v", "b", "n", "m", "<\n,", ">\n.", "?\n/", lang.SHIFT], + [lang.CTRL, lang.CMD, lang.ALT, lang.SPACE, lang.ALT, lang.CMD, lang.CTRL], + ], function(list) { can.core.List(list, function(item, index) { + msg.Push(can.base.isObject(item)? item: {type: [mdb.KEY, index == 0? html.HEAD: index == list.length-1? "tail": ""].join(ice.SP), name: item}) + }) }); return 750 } }}) diff --git a/plugin/input/province.js b/plugin/input/province.js index d42b01d6..6d8af2cf 100644 --- a/plugin/input/province.js +++ b/plugin/input/province.js @@ -1,9 +1,8 @@ Volcanos(chat.ONFIGURE, {province: { onclick: function(event, can, meta, cbs, target) { cbs(function(can, cb) { can.require(["/require/shylinux.com/x/echarts/echarts.js", "/require/shylinux.com/x/echarts/china.js"], function() { - var chart = echarts.init(can.page.Append(can, can._output, [{type: html.DIV, style: {width: 600, height: 400}}]).first) + var chart = echarts.init(can.page.Append(can, can._output, [{type: html.DIV, style: {width: can.page.width()/2, height: can.page.height()/2}}]).first) chart.setOption({geo: {map: 'china'}}), chart.on(html.CLICK, function(params) { target.value = params.name, can.close() }) - can.Status(mdb.TOTAL, 34) }) }) } -}}) +}}) \ No newline at end of file diff --git a/plugin/local/code/inner/syntax.js b/plugin/local/code/inner/syntax.js index 3918bf5d..f8d37ee6 100644 --- a/plugin/local/code/inner/syntax.js +++ b/plugin/local/code/inner/syntax.js @@ -270,6 +270,7 @@ Volcanos(chat.ONSYNTAX, {help: "语法高亮", // "can.*": code.FUNCTION, }, keyword: { + "const": code.KEYWORD, "var": code.KEYWORD, "new": code.KEYWORD, "typeof": code.KEYWORD, @@ -419,6 +420,7 @@ Volcanos(chat.ONSYNTAX, {help: "语法高亮", "both": code.CONSTANT, "auto": code.CONSTANT, + "pointer": code.CONSTANT, "center": code.CONSTANT, "relative": code.CONSTANT, "absolute": code.CONSTANT, diff --git a/plugin/local/code/vimer.css b/plugin/local/code/vimer.css index b7598d1e..e5e94d90 100644 --- a/plugin/local/code/vimer.css +++ b/plugin/local/code/vimer.css @@ -43,5 +43,6 @@ body.white fieldset.vimer>div.output input.current.insert { caret-color:black; } body.white fieldset.vimer>div.output input.current.normal { caret-color:lightgray; } div.project div.zone.create>div.list div.item { padding:2px; float:left; clear:none; } +div.project div.zone.create>div.list div.item input { font-family:monospace; } body.webview div.project div.zone.create>div.list div.item { padding:2px; } div.project div.zone.create>div.action { display:none; } diff --git a/plugin/local/code/vimer.js b/plugin/local/code/vimer.js index 7200affe..f3be3c2b 100644 --- a/plugin/local/code/vimer.js +++ b/plugin/local/code/vimer.js @@ -28,7 +28,8 @@ Volcanos(chat.ONIMPORT, {help: "导入数据", _init: function(can, msg, cb, tar Volcanos(chat.ONFIGURE, {help: "索引导航", create: function(can, target, zone, path) { can.isCmdMode()? can.onappend._action(can, can.base.Obj(can._msg.Option(ice.MSG_ACTION)).concat( - window.webview? ["查找", "录屏", "git", "vim", "日志", "编辑器", "浏览器", "首页", "百度"]: ["查找"]), target): can.onmotion.hidden(can, target.parentNode) + ["查找", "首页", "百度", "plan", "git"], window.webview? ["录屏", "日志", "编辑器", "浏览器"]: [], + ), target): can.onmotion.hidden(can, target.parentNode) }, recent: function(can, target, zone, path) { var total = 0 function show(msg, cb) { @@ -210,16 +211,16 @@ Volcanos(chat.ONKEYMAP, {help: "键盘交互", y: shy("向上滚屏", function(can) { can.current.scroll(-1) }), }, insert: { - Escape: shy("退出编辑", function(event, can) { event.key == "Escape" && can.onkeymap._normal(event, can) }), - Tab: shy("缩进", function(event, can) { if (event.key != "Tab") { return } + Escape: shy("退出编辑", function(event, can) { event.key == lang.ESCAPE && can.onkeymap._normal(event, can) }), + Tab: shy("缩进", function(event, can) { if (event.key != lang.TAB) { return } can.onkeymap.insertText(can.ui.current, ice.TB), can.onkeymap.prevent(event) }), - Backspace: shy("删除", function(event, can, target) { if (event.key != "Backspace") { return } + Backspace: shy("删除", function(event, can, target) { if (event.key != lang.BACKSPACE) { return } if (target.selectionStart > 0 || !can.current.prev()) { return } can.onkeymap.prevent(event) var rest = can.current.text(); can.onaction.selectLine(can, can.current.prev()), can.onaction.deleteLine(can, can.current.next()) var text = can.current.text(); can.ui.current.value = text+rest, can.onkeymap.cursorMove(target, 0, text.length) }), - Enter: shy("换行", function(can, target) { if (event.key != "Enter") { return } + Enter: shy("换行", function(can, target) { if (event.key != lang.ENTER) { return } var rest = can.onkeymap.deleteText(target, target.selectionEnd), text = can.ui.current.value var left = text.substr(0, text.indexOf(text.trimLeft()))||(text.trimRight() == ""? text: "") text && can.core.List(["{}", "[]", "()"], function(item) { if (can.base.endWith(text, item[0])) { @@ -325,6 +326,7 @@ Volcanos(chat.ONACTION, {help: "控件交互", can.onimport.exts(can, list[0]) }) }, + "plan": function(event, can) { can.onimport.tabview(can, can.Option(nfs.PATH), "web.team.plan", ctx.INDEX) }, "git": function(event, can) { can.onimport.tabview(can, can.Option(nfs.PATH), "web.code.git.status", ctx.INDEX) }, "vim": function(event, can) { can.onaction._run(can.request(event, can.Option()), can, code.XTERM, [mdb.TYPE, "vim +"+can.Option(nfs.LINE)+" "+can.Option(nfs.PATH)+can.Option(nfs.FILE)], function(msg) { @@ -336,8 +338,8 @@ Volcanos(chat.ONACTION, {help: "控件交互", "日志": function(event, can) { window.opencmd("cd ~/contexts; tail -f var/log/bench.log") }, "编辑器": function(event, can) { window.opencmd("cd ~/contexts; vim +"+can.Option(nfs.LINE)+" "+can.Option(nfs.PATH)+can.Option(nfs.FILE)) }, "浏览器": function(event, can) { window.openurl(location.href) }, - "首页": function(event, can) { window.openurl(location.protocol+"//"+location.host) }, - "百度": function(event, can) { window.openurl("https://baidu.com") }, + "首页": function(event, can) { can.user.isWebview? window.openurl(location.protocol+"//"+location.host): window.open(location.protocol+"//"+location.host) }, + "百度": function(event, can) { can.user.isWebview? window.openurl("https://baidu.com"): can.user.open("https://baidu.com") }, "查找": function(event, can) { var ui = can.page.Append(can, can._output, [{view: "vimer find float", list: [html.ACTION, html.OUTPUT], style: {position: "absolute", left: can.ui.project.offsetWidth+can.ui.content.offsetWidth/2, top: can.base.Max(can.base.Min(can.current.line.offsetTop-can.ui.content.scrollTop, 100), can.ConfHeight()/2)+57+28}}]) diff --git a/plugin/local/mall/goods.js b/plugin/local/mall/goods.js index 1ca47c6d..57a9c3fc 100644 --- a/plugin/local/mall/goods.js +++ b/plugin/local/mall/goods.js @@ -26,6 +26,17 @@ Volcanos(chat.ONIMPORT, { can.isCmdMode() && can.page.styleHeight(can, can._output, can.ConfHeight()) }, }, [""]) +Volcanos(chat.ONACTION, {list: ["music"], + "play": function(event, can, button) { + can._audio = can._audio||can.page.Append(can, can._output, [{type:"audio", src: "https://m701.music.126.net/20221029062844/f7593e1bb844dc4e35003543494314a2/jdyyaac/obj/w5rDlsOJwrLDjj7CmsOj/9879113394/5ffc/73bd/1a47/b11e9469bf4f6744db6e88c527a678df.m4a", _init: function(target) { + }}])._target + can._audio.play() + }, + "stop": function(event, can, button) { + }, + "music": function(event, can, button) { + }, +}) Volcanos(chat.ONEXPORT, { width: function(can) { if (can.ConfWidth() < 343) { return 343 } for (var i = 2; i < 10; i++) { if (can.ConfWidth() < 343*i) { return can.ConfWidth()/(i-1) } } }, }) diff --git a/proto.js b/proto.js index 83a9796d..5eeac5ef 100644 --- a/proto.js +++ b/proto.js @@ -229,6 +229,7 @@ var html = {PLUGIN_MARGIN: 10, ACTION_HEIGHT: 31, ACTION_MARGIN: 200, var lang = { UNDEFINED: "undefined", STRING: "string", NUMBER: "number", BOOLEAN: "boolean", OBJECT: "object", FUNCTION: "function", META: "Meta", ALT: "Alt", CONTROL: "Control", SHIFT: "Shift", TAB: "Tab", ENTER: "Enter", ESCAPE: "Escape", PS: "/", + ESC: "Esc", CTRL: "Ctrl", CMD: "Cmd", SPACE: "Space", BACKSPACE: "Backspace", } function shy(help, meta, list, cb) { var args = arguments, i = 0