24 Commits
3.2.2 ... 3.2.4

Author SHA1 Message Date
Florian Mounier
039c730409 Bump 3.2.4 2018-09-03 14:41:39 +02:00
Florian Mounier
82676862ca Fix one-shot auto-open url when uri-root-path is used. 2018-09-03 11:54:38 +02:00
Mounier Florian
5b6b61286d Merge pull request #173 from GrahamDumpleton/uri-root-path
Fix up --uri-root-path so behaves as one would expect for this.
2018-09-03 11:42:36 +02:00
Mounier Florian
f32cb4d358 Merge pull request #172 from ZoomerAnalytics/fix-keepalive-ping
added missing keepalive_timer.start()
2018-09-03 11:27:55 +02:00
Graham Dumpleton
ad155f1f17 Only create default conf file after options are parsed. 2018-08-30 12:30:22 +10:00
Felix Zumstein
9e1045de9b added missing keepalive_timer.start() 2018-08-26 22:54:53 +02:00
Graham Dumpleton
db3d37f6fe Fix up generation of URLs with prefix. 2018-08-23 13:26:16 +10:00
Graham Dumpleton
611f2e30d6 Add uri root path before all routes. 2018-08-23 11:53:38 +10:00
Florian Mounier
1984e4b869 Fix compare tags in Changelog 2018-06-04 11:20:09 +02:00
Florian Mounier
f58ea904b3 Merge branch 'master' of github.com:paradoxxxzero/butterfly 2018-06-04 11:15:47 +02:00
Florian Mounier
af0f4d20fe Update Changelog 2018-06-04 11:15:24 +02:00
Mounier Florian
10b5ce3bcc Merge pull request #161 from k4pu77/master
Updated docker baseimage
2018-06-04 11:12:33 +02:00
Florian Mounier
a0287946d9 Bump 3.2.3 2018-06-04 11:05:07 +02:00
Florian Mounier
fbd71d55ef Fix lint 2018-06-04 11:03:06 +02:00
Peter Cai
0ac8437387 term: fix password input on Chrome for Android
1. Also force focus on inputHelper on keyup on Android
2. Clear the inputHelper immediately upon receiving input
2018-06-03 20:58:39 +08:00
Peter Cai
866b56b682 term: bring back touch simulation of special keys on mobile
and also fixed it. The original version did not work because it tried
to change read-only fields of the event, which is not allowed.

The last commit removed support of touch simulation of Ctrl and Alt
by removing the `virtual_input.coffee` file. This commit brings it back
with a better implementation.
2018-06-03 20:10:24 +08:00
Peter Cai
4d87059872 remove unneeded virtual_input
We have already introduced a virtual textarea for every platform.
This one seems redundant.

However, some features may still not work perfectly on a mobile browser
2018-06-03 18:03:23 +08:00
Peter Cai
5bbe456496 term: remove redundant events of inputHelper and redundant contentEditable
We do not need to listen for keydown and keypress for inputHelper because these events will propagate through the parent.
Listening them will be redundant and will cause some shortcut key combinations to stop working.

Since we now have a hidden `textarea`, there is no longer need to set anything to contentEditable
2018-06-03 17:51:16 +08:00
Peter Cai
5b9cc257a8 term: do not re-focus on keyup when on mobile
Doing this will mess up the mobile browsers.
Although we still don't support mobile browsers very well, this can at least make it usable on them.
2018-06-03 12:19:43 +08:00
Peter Cai
34b6287e0c term: complete support for IME & CJK rendering
this fixes #75 and #47, two bugs originated long long ago.

1. Added support for IME events `compositionstart` `compositionupdate` and `compositionend`.
2. Refactored some code to receive input events from a hidden textarea just as how `xterm.js` now does. This removes the need to set `contentEditable` on the body in order to receive IME compistion events, and also guides the IME input box correctly following the cursor.
3. Fixed CJK rendering. Forces "forceWidth" mode with double width on those known CJK ranges in Unicode. Corrected the placeholder logic of the force width mode. Note that some rare halfwidth CJK characters will still not render correctly without `force-unicode-width` enabled. If you see any issue, please enable the `--force-unicode-width` option.
4. Miscallaneous fixes for some problems after introducing the above change

Tested on Firefox Nightly 62 on Linux and Chromium 67 on Linux, with `fcitx` as input method.
2018-06-03 10:27:49 +08:00
Peter Cai
41ee5fb843 update grunt-sass
the old grunt-sass no longer works with newer node.js
2018-06-03 10:12:19 +08:00
Christoph Christen
ae6b36fa89 Updated docker baseimage 2018-01-02 21:05:49 +01:00
Mounier Florian
cfda54a724 Merge pull request #158 from brentley/master
updating setuptools
2017-12-19 18:02:48 +01:00
Brent Langston
033169ab08 updating setuptools 2017-12-19 10:56:53 -06:00
25 changed files with 656 additions and 263 deletions

1
.gitignore vendored
View File

@@ -10,3 +10,4 @@ sass/scss
build/
.cache/
.env*
.pytest_cache

2
.isort.cfg Normal file
View File

@@ -0,0 +1,2 @@
[settings]
multi_line_output=4

View File

@@ -1,3 +1,14 @@
[3.2.4](https://github.com/paradoxxxzero/butterfly/compare/3.2.3...3.2.4)
=====
* Fix up --uri-root-path so behaves as one would expect for this. Fix #155 (PR #173 thanks @GrahamDumpleton)
* Fix websocket keepalive. Fix #167 (PR #172 thanks @fzumstein)
[3.2.3](https://github.com/paradoxxxzero/butterfly/compare/3.2.2...3.2.3)
=====
* Complete support for IME & CJK rendering (#168 thanks @PeterCxy)
3.2.2
=====

View File

@@ -1,4 +1,4 @@
FROM ubuntu:14.04
FROM ubuntu:16.04
RUN apt-get update \
&& apt-get install -y -q --no-install-recommends \
@@ -7,6 +7,9 @@ RUN apt-get update \
libssl-dev \
python-dev \
python-setuptools \
ca-certificates \
&& easy_install pip \
&& pip install --upgrade setuptools \
&& apt-get clean \
&& rm -r /var/lib/apt/lists/*

View File

@@ -5,7 +5,7 @@ all: install lint check-outdated run-debug
install:
test -d $(VENV) || virtualenv $(VENV) -p $(PYTHON_VERSION)
$(PIP) install --upgrade --no-cache pip setuptools -e .[lint] devcore
$(PIP) install --upgrade --no-cache pip setuptools -e .[lint,themes] devcore
$(NPM) install
clean:
@@ -22,7 +22,6 @@ check-outdated:
ARGS ?= --port=1212 --unsecure --debug
run-debug:
sleep 0.5 && $(BROWSER) http://localhost:1212&
$(PYTHON) ./butterfly.server.py $(ARGS)
build-coffee:

View File

@@ -98,17 +98,6 @@ butterfly_dir = os.path.join(ev, 'butterfly')
conf_file = os.path.join(butterfly_dir, 'butterfly.conf')
ssl_dir = os.path.join(butterfly_dir, 'ssl')
if not os.path.exists(conf_file):
try:
import butterfly
shutil.copy(
os.path.join(
os.path.abspath(os.path.dirname(butterfly.__file__)),
'butterfly.conf.default'), conf_file)
print('butterfly.conf installed in %s' % conf_file)
except:
pass
tornado.options.define("conf", default=conf_file,
help="Butterfly configuration file. "
"Contains the same options as command line.")
@@ -125,6 +114,21 @@ if os.path.exists(tornado.options.options.conf):
# Do it again to overwrite conf with args
tornado.options.parse_command_line()
# For next time, create them a conf file from template.
# Need to do this after parsing options so we do not trigger
# code import for butterfly module, in case that code is
# dependent on the set of parsed options.
if not os.path.exists(conf_file):
try:
import butterfly
shutil.copy(
os.path.join(
os.path.abspath(os.path.dirname(butterfly.__file__)),
'butterfly.conf.default'), conf_file)
print('butterfly.conf installed in %s' % conf_file)
except:
pass
options = tornado.options.options
for logger in ('tornado.access', 'tornado.application',
@@ -371,8 +375,10 @@ ioloop = tornado.ioloop.IOLoop.instance()
if port == 0:
port = list(http_server._sockets.values())[0].getsockname()[1]
url = "http%s://%s:%d/" % (
"s" if not options.unsecure else "", host, port)
url = "http%s://%s:%d/%s" % (
"s" if not options.unsecure else "", host, port,
(options.uri_root_path.strip('/') + '/') if options.uri_root_path else ''
)
if not options.one_shot or not webbrowser.open(url):
log.warn('Butterfly is ready, open your browser to: %s' % url)

View File

@@ -1,5 +1,5 @@
__title__ = "butterfly"
__version__ = "3.2.2"
__version__ = "3.2.4"
__summary__ = "A sleek web based terminal emulator"
__uri__ = "https://github.com/paradoxxxzero/butterfly"

View File

@@ -31,10 +31,15 @@ class url(object):
self.url = url
def __call__(self, cls):
if tornado.options.options.uri_root_path:
url = '/' + tornado.options.options.uri_root_path.strip('/') + self.url
else:
url = self.url
application.add_handlers(
r'.*$',
(tornado.web.url(self.url, cls, name=cls.__name__),)
(tornado.web.url(url, cls, name=cls.__name__),)
)
return cls
@@ -73,7 +78,7 @@ if hasattr(tornado.options.options, 'debug'):
template_path=os.path.join(os.path.dirname(__file__), "templates"),
debug=tornado.options.options.debug,
static_url_prefix='%s/static/' % (
'/%s' % tornado.options.options.uri_root_path
'/%s' % tornado.options.options.uri_root_path.strip('/')
if tornado.options.options.uri_root_path else '')
)

View File

@@ -1,8 +1,8 @@
import sys
import termios
import tty
from contextlib import contextmanager
import termios
from butterfly.utils import ansi_colors as colors # noqa: F401
@@ -61,7 +61,7 @@ def geolocation():
rv = sys.stdin.read(1)
if rv != 'R':
loc += rv
except:
except Exception:
return
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)

View File

@@ -30,6 +30,7 @@ import tornado.options
import tornado.process
import tornado.web
import tornado.websocket
from butterfly import Route, url, utils
from butterfly.terminal import Terminal
@@ -138,6 +139,7 @@ class KeptAliveWebSocketHandler(tornado.websocket.WebSocketHandler):
def open(self, *args, **kwargs):
self.keepalive_timer = tornado.ioloop.PeriodicCallback(
self.send_ping, tornado.options.options.keepalive_interval * 1000)
self.keepalive_timer.start()
def send_ping(self):
t = int(time.time())

View File

@@ -93,5 +93,20 @@ body
padding: .5em
font-size: .75em
#input-view
position: fixed
z-index: 100
padding: 0
margin: 0
text-decoration: underline
#input-helper
position: fixed
z-index: -100
opacity: 0
white-space: nowrap
overflow: hidden
resize: none
.terminal
outline: none

View File

@@ -1,5 +1,5 @@
(function() {
var Popup, Selection, _set_theme_href, _theme, alt, cancel, clean_ansi, copy, ctrl, escape, first, histSize, linkify, maybePack, nextLeaf, packSize, popup, previousLeaf, selection, setAlarm, tags, tid, virtualInput, walk,
var Popup, Selection, _set_theme_href, _theme, alt, cancel, clean_ansi, copy, ctrl, escape, histSize, linkify, maybePack, nextLeaf, packSize, popup, previousLeaf, selection, setAlarm, tags, tid, walk,
indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
clean_ansi = function(data) {
@@ -121,13 +121,14 @@
});
addEventListener('copy', copy = function(e) {
var data, end, j, len1, line, ref, sel;
var data, end, j, len, line, ref, sel;
document.getElementsByTagName('body')[0].contentEditable = false;
butterfly.bell("copied");
e.clipboardData.clearData();
sel = getSelection().toString().replace(/\u00A0/g, ' ').replace(/\u2007/g, ' ');
data = '';
ref = sel.split('\n');
for (j = 0, len1 = ref.length; j < len1; j++) {
for (j = 0, len = ref.length; j < len; j++) {
line = ref[j];
if (line.slice(-1) === '\u23CE') {
end = '';
@@ -143,6 +144,7 @@
addEventListener('paste', function(e) {
var data, send, size;
document.getElementsByTagName('body')[0].contentEditable = false;
butterfly.bell("pasted");
data = e.clipboardData.getData('text/plain');
data = data.replace(/\r\n/g, '\n').replace(/\n/g, '\r');
@@ -183,10 +185,10 @@
});
walk = function(node, callback) {
var child, j, len1, ref, results;
var child, j, len, ref, results;
ref = node.childNodes;
results = [];
for (j = 0, len1 = ref.length; j < len1; j++) {
for (j = 0, len = ref.length; j < len; j++) {
child = ref[j];
callback.call(child);
results.push(walk(child, callback));
@@ -230,6 +232,46 @@
});
});
ctrl = false;
alt = false;
addEventListener('touchstart', function(e) {
if (e.touches.length === 2) {
return ctrl = true;
} else if (e.touches.length === 3) {
ctrl = false;
return alt = true;
} else if (e.touches.length === 4) {
ctrl = true;
return alt = true;
}
});
window.mobileKeydown = function(e) {
var _altKey, _ctrlKey, _keyCode;
if (ctrl || alt) {
_ctrlKey = ctrl;
_altKey = alt;
_keyCode = e.keyCode;
if (e.keyCode >= 97 && e.keyCode <= 122) {
_keyCode -= 32;
}
e = new KeyboardEvent('keydown', {
ctrlKey: _ctrlKey,
altKey: _altKey,
keyCode: _keyCode
});
ctrl = alt = false;
setTimeout(function() {
return window.dispatchEvent(e);
}, 0);
return true;
} else {
return false;
}
};
document.addEventListener('keydown', function(e) {
if (!(e.altKey && e.keyCode === 79)) {
return true;
@@ -289,9 +331,6 @@
Popup.prototype.open = function(html) {
this.el.innerHTML = html;
this.el.classList.remove('hidden');
if (typeof InstallTrigger !== "undefined") {
document.body.contentEditable = 'false';
}
addEventListener('click', this.bound_click_maybe_close);
return addEventListener('keydown', this.bound_key_maybe_close);
};
@@ -299,9 +338,6 @@
Popup.prototype.close = function() {
removeEventListener('click', this.bound_click_maybe_close);
removeEventListener('keydown', this.bound_key_maybe_close);
if (typeof InstallTrigger !== "undefined") {
document.body.contentEditable = 'true';
}
this.el.classList.add('hidden');
return this.el.innerHTML = '';
};
@@ -665,7 +701,7 @@
}
oReq = new XMLHttpRequest();
oReq.addEventListener('load', function() {
var j, len1, out, ref, response, session;
var j, len, out, ref, response, session;
response = JSON.parse(this.responseText);
out = '<div>';
out += '<h2>Session list</h2>';
@@ -674,7 +710,7 @@
} else {
out += '<ul>';
ref = response.sessions;
for (j = 0, len1 = ref.length; j < len1; j++) {
for (j = 0, len = ref.length; j < len; j++) {
session = ref[j];
out += "<li><a href=\"/session/" + session + "\">" + session + "</a></li>";
}
@@ -729,7 +765,7 @@
}
oReq = new XMLHttpRequest();
oReq.addEventListener('load', function() {
var builtin_themes, inner, j, k, len1, len2, option, response, theme, theme_list, themes, url;
var builtin_themes, inner, j, k, len, len1, option, response, theme, theme_list, themes, url;
response = JSON.parse(this.responseText);
builtin_themes = response.builtin_themes;
themes = response.themes;
@@ -746,7 +782,7 @@
option("/static/main.css", 'default');
if (themes.length) {
inner += '<optgroup label="Local themes">';
for (j = 0, len1 = themes.length; j < len1; j++) {
for (j = 0, len = themes.length; j < len; j++) {
theme = themes[j];
url = "/theme/" + theme + "/style.css";
option(url, theme);
@@ -754,7 +790,7 @@
inner += '</optgroup>';
}
inner += '<optgroup label="Built-in themes">';
for (k = 0, len2 = builtin_themes.length; k < len2; k++) {
for (k = 0, len1 = builtin_themes.length; k < len1; k++) {
theme = builtin_themes[k];
url = "/theme/" + theme + "/style.css";
option(url, theme.slice('built-in-'.length));
@@ -772,74 +808,6 @@
return cancel(e);
});
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
ctrl = false;
alt = false;
first = true;
virtualInput = document.createElement('input');
virtualInput.type = 'password';
virtualInput.style.position = 'fixed';
virtualInput.style.top = 0;
virtualInput.style.left = 0;
virtualInput.style.border = 'none';
virtualInput.style.outline = 'none';
virtualInput.style.opacity = 0;
virtualInput.value = '0';
document.body.appendChild(virtualInput);
virtualInput.addEventListener('blur', function() {
return setTimeout(((function(_this) {
return function() {
return _this.focus();
};
})(this)), 10);
});
addEventListener('click', function() {
return virtualInput.focus();
});
addEventListener('touchstart', function(e) {
if (e.touches.length === 2) {
return ctrl = true;
} else if (e.touches.length === 3) {
ctrl = false;
return alt = true;
} else if (e.touches.length === 4) {
ctrl = true;
return alt = true;
}
});
virtualInput.addEventListener('keydown', function(e) {
butterfly.keyDown(e);
return true;
});
virtualInput.addEventListener('input', function(e) {
var len;
len = this.value.length;
if (len === 0) {
e.keyCode = 8;
butterfly.keyDown(e);
this.value = '0';
return true;
}
e.keyCode = this.value.charAt(1).charCodeAt(0);
if ((ctrl || alt) && !first) {
e.keyCode = this.value.charAt(1).charCodeAt(0);
e.ctrlKey = ctrl;
e.altKey = alt;
if (e.keyCode >= 97 && e.keyCode <= 122) {
e.keyCode -= 32;
}
butterfly.keyDown(e);
this.value = '0';
ctrl = alt = false;
return true;
}
butterfly.keyPress(e);
first = false;
this.value = '0';
return true;
});
}
}).call(this);
//# sourceMappingURL=ext.js.map

File diff suppressed because one or more lines are too long

View File

@@ -2861,6 +2861,19 @@ body {
display: block;
padding: .5em;
font-size: .75em; }
body #input-view {
position: fixed;
z-index: 100;
padding: 0;
margin: 0;
text-decoration: underline; }
body #input-helper {
position: fixed;
z-index: -100;
opacity: 0;
white-space: nowrap;
overflow: hidden;
resize: none; }
.terminal {
outline: none; }

View File

@@ -1,5 +1,5 @@
(function() {
var $, State, Terminal, cancel, cols, openTs, quit, rows, s, ws,
var $, State, Terminal, cancel, cols, isMobile, openTs, quit, rows, s, ws,
indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
cols = rows = null;
@@ -131,6 +131,10 @@
return false;
};
isMobile = function() {
return /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
};
s = 0;
State = {
@@ -170,10 +174,14 @@
this.body = this.document.getElementsByTagName('body')[0];
this.term = this.document.getElementById('term');
this.forceWidth = this.body.getAttribute('data-force-unicode-width') === 'yes';
this.inputHelper = this.document.getElementById('input-helper');
this.inputView = this.document.getElementById('input-view');
this.body.className = 'terminal focus';
this.body.style.outline = 'none';
this.body.setAttribute('tabindex', 0);
this.body.setAttribute('spellcheck', 'false');
this.inputHelper.setAttribute('tabindex', 0);
this.inputHelper.setAttribute('spellcheck', 'false');
div = this.document.createElement('div');
div.className = 'line';
this.term.appendChild(div);
@@ -185,11 +193,28 @@
this.termName = 'xterm';
this.cursorBlink = true;
this.cursorState = 0;
this.inComposition = false;
this.compositionText = "";
this.resetVars();
this.focus();
this.startBlink();
this.inputHelper.addEventListener('compositionstart', this.compositionStart.bind(this));
this.inputHelper.addEventListener('compositionupdate', this.compositionUpdate.bind(this));
this.inputHelper.addEventListener('compositionend', this.compositionEnd.bind(this));
addEventListener('keydown', this.keyDown.bind(this));
addEventListener('keypress', this.keyPress.bind(this));
addEventListener('keyup', (function(_this) {
return function() {
return _this.inputHelper.focus();
};
})(this));
if (isMobile()) {
addEventListener('click', (function(_this) {
return function() {
return _this.inputHelper.focus();
};
})(this));
}
addEventListener('focus', this.focus.bind(this));
addEventListener('blur', this.blur.bind(this));
addEventListener('resize', (function(_this) {
@@ -202,9 +227,6 @@
return _this.nativeScrollTo();
};
})(this), true);
if (typeof InstallTrigger !== "undefined") {
this.body.contentEditable = 'true';
}
this.initmouse();
addEventListener('load', (function(_this) {
return function() {
@@ -248,7 +270,8 @@
invisible: a.invisible,
italic: a.italic,
faint: a.faint,
crossed: a.crossed
crossed: a.crossed,
placeholder: false
};
};
@@ -256,12 +279,18 @@
return a.bg === b.bg && a.fg === b.fg && a.bold === b.bold && a.underline === b.underline && a.blink === b.blink && a.inverse === b.inverse && a.invisible === b.invisible && a.italic === b.italic && a.faint === b.faint && a.crossed === b.crossed;
};
Terminal.prototype.putChar = function(c) {
Terminal.prototype.putChar = function(c, placeholder) {
var newChar;
if (placeholder == null) {
placeholder = false;
}
newChar = this.cloneAttr(this.curAttr, c);
newChar.placeholder = placeholder;
if (this.insertMode) {
this.screen[this.y + this.shift].chars.splice(this.x, 0, this.cloneAttr(this.curAttr, c));
this.screen[this.y + this.shift].chars.splice(this.x, 0, newChar);
this.screen[this.y + this.shift].chars.pop();
} else {
this.screen[this.y + this.shift].chars[this.x] = this.cloneAttr(this.curAttr, c);
this.screen[this.y + this.shift].chars[this.x] = newChar;
}
return this.screen[this.y + this.shift].dirty = true;
};
@@ -297,7 +326,8 @@
invisible: false,
italic: false,
faint: false,
crossed: false
crossed: false,
placeholder: false
};
this.curAttr = this.cloneAttr(this.defAttr);
this.params = [];
@@ -342,6 +372,7 @@
this.showCursor();
this.body.classList.add('focus');
this.body.classList.remove('blur');
this.inputHelper.focus();
this.resize();
return this.scrollLock = old_sl;
};
@@ -581,8 +612,15 @@
return [classes, styles];
};
Terminal.prototype.isCJK = function(ch) {
return ("\u4e00" <= ch && ch <= "\u9fff") || ("\u3040" <= ch && ch <= "\u30ff") || ("\u31f0" <= ch && ch <= "\u31ff") || ("\u3190" <= ch && ch <= "\u319f") || ("\u3301" <= ch && ch <= "\u3356") || ("\uac00" <= ch && ch <= "\ud7ff") || ("\u3000" <= ch && ch <= "\u303f") || ("\uff00" <= ch && ch <= "\uff60") || ("\uffe0" <= ch && ch <= "\uffe6");
};
Terminal.prototype.charToDom = function(data, attr, cursor) {
var ch, char, classes, ref, styles;
if (data.placeholder) {
return;
}
if (data.html) {
return data.html;
}
@@ -621,12 +659,12 @@
default:
if (ch <= " ") {
char += "&nbsp;";
} else if (!this.forceWidth) {
} else if (!(this.forceWidth || this.isCJK(ch))) {
char += ch;
} else {
if (ch <= "~") {
char += ch;
} else if (("\uff00" < ch && ch < "\uffef")) {
} else if (this.isCJK(ch)) {
char += "<span style=\"display: inline-block; width: " + (2 * this.charSize.width) + "px\">" + ch + "</span>";
} else {
char += "<span style=\"display: inline-block; width: " + this.charSize.width + "px\">" + ch + "</span>";
@@ -733,6 +771,7 @@
dom = this.screenToDom(force);
this.writeDom(dom);
this.nativeScrollTo();
this.updateInputViews();
return this.emit('refresh');
};
@@ -912,12 +951,8 @@
}
this.putChar(ch);
this.x++;
if (this.forceWidth && ("\uff00" < ch && ch < "\uffef")) {
if (this.cols < 2 || this.x >= this.cols) {
this.putChar(" ");
break;
}
this.putChar(" ");
if (this.isCJK(ch)) {
this.putChar(" ", true);
this.x++;
}
}
@@ -1409,11 +1444,93 @@
return this.write(data + "\r\n");
};
Terminal.prototype.updateInputViews = function() {
var cursorPos;
cursorPos = this.cursor.getBoundingClientRect();
this.inputView.style['left'] = cursorPos.left + "px";
this.inputView.style['top'] = cursorPos.top + "px";
this.inputHelper.style['left'] = cursorPos.left + "px";
this.inputHelper.style['top'] = cursorPos.top + "px";
return this.inputHelper.value = "";
};
Terminal.prototype.compositionStart = function(ev) {
ev.preventDefault();
ev.stopPropagation();
this.updateInputViews();
this.inputView.className = "";
this.inputView.innerText = "";
this.cursor.style['visibility'] = "hidden";
this.inComposition = true;
this.compositionText = "";
return false;
};
Terminal.prototype.compositionUpdate = function(ev) {
ev.preventDefault();
ev.stopPropagation();
this.compositionText = ev.data;
this.inputView.innerText = this.compositionText;
return false;
};
Terminal.prototype.compositionEnd = function(ev) {
ev.preventDefault();
ev.stopPropagation();
this.finishComposition();
return false;
};
Terminal.prototype.finishComposition = function() {
this.inComposition = false;
this.showCursor();
this.inputHelper.value = "";
this.inputView.className = "hidden";
this.send(this.compositionText);
this.compositionText = "";
return this.inputHelper.focus();
};
Terminal.prototype.keyDown = function(ev) {
var key, ref;
if (this.inComposition) {
if (ev.keyCode === 229) {
return false;
} else if (ev.keyCode === 16 || ev.keyCode === 17 || ev.keyCode === 18) {
return false;
}
this.finishComposition();
}
if (ev.keyCode === 229) {
ev.preventDefault();
ev.stopPropagation();
setTimeout((function(_this) {
return function() {
var char, e, val;
if (!(_this.inComposition || _this.inputHelper.value.length > 1)) {
val = _this.inputHelper.value;
_this.inputHelper.value = "";
char = val.toUpperCase().charCodeAt(0);
if ((65 <= char && char <= 90)) {
e = new KeyboardEvent('keydown', {
keyCode: char
});
if (window.mobileKeydown(e)) {
return;
}
}
return _this.send(val);
}
};
})(this), 0);
return false;
}
if (ev.keyCode > 15 && ev.keyCode < 19) {
return true;
}
if (window.mobileKeydown(ev)) {
return true;
}
if (ev.keyCode === 19) {
this.body.classList.add('stopped');
this.out('\x03');
@@ -1424,6 +1541,7 @@
return true;
}
if ((ev.shiftKey && ev.ctrlKey) && ((ref = ev.keyCode) === 67 || ref === 86)) {
this.body.contentEditable = true;
return true;
}
if (ev.altKey && ev.keyCode === 90 && !this.skipNextKey) {

File diff suppressed because one or more lines are too long

View File

@@ -18,6 +18,10 @@
data-force-unicode-width="{{ 'yes' if options.force_unicode_width else 'no' }}"
data-root-path="{{ options.uri_root_path }}"
data-session-token={{ session }}>
<textarea id="input-helper">
</textarea>
<div id="input-view" class="hidden">
</div>
<div id="popup" class="hidden">
</div>
<script src="{{ static_url('html-sanitizer.js') }}"></script>

View File

@@ -24,6 +24,7 @@ import signal
import string
import struct
import sys
import termios
from logging import getLogger
import tornado.ioloop
@@ -32,7 +33,6 @@ import tornado.process
import tornado.web
import tornado.websocket
import termios
from butterfly import __version__, utils
log = getLogger('butterfly')

View File

@@ -16,6 +16,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
addEventListener 'copy', copy = (e) ->
document.getElementsByTagName('body')[0].contentEditable = false
butterfly.bell "copied"
e.clipboardData.clearData()
sel = getSelection().toString().replace(
@@ -35,6 +36,7 @@ addEventListener 'copy', copy = (e) ->
addEventListener 'paste', (e) ->
document.getElementsByTagName('body')[0].contentEditable = false
butterfly.bell "pasted"
data = e.clipboardData.getData 'text/plain'
data = data.replace(/\r\n/g, '\n').replace(/\n/g, '\r')

52
coffees/ext/mobile.coffee Normal file
View File

@@ -0,0 +1,52 @@
# *-* coding: utf-8 *-*
# This file is part of butterfly
#
# butterfly Copyright(C) 2015-2017 Florian Mounier
# 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 <http://www.gnu.org/licenses/>.
ctrl = false
alt = false
addEventListener 'touchstart', (e) ->
if e.touches.length == 2
ctrl = true
else if e.touches.length == 3
ctrl = false
alt = true
else if e.touches.length == 4
ctrl = true
alt = true
# Dispatch a new event if the current event need to
# be modified with ctrlKey and altKey from touch events
# If so, this function will return true and dispatch the new event.
# The caller should return immediately upon receiving true.
window.mobileKeydown = (e) ->
if ctrl or alt
_ctrlKey = ctrl
_altKey = alt
_keyCode = e.keyCode
if e.keyCode >= 97 && e.keyCode <= 122
_keyCode -= 32
e = new KeyboardEvent 'keydown',
ctrlKey: _ctrlKey,
altKey: _altKey,
keyCode: _keyCode
ctrl = alt = false
setTimeout ->
window.dispatchEvent e
, 0
return true
else
return false

View File

@@ -9,10 +9,6 @@ class Popup
@el.innerHTML = html
@el.classList.remove 'hidden'
# ff glorious hack
if typeof InstallTrigger isnt "undefined"
document.body.contentEditable = 'false'
addEventListener 'click', @bound_click_maybe_close
addEventListener 'keydown', @bound_key_maybe_close
@@ -20,10 +16,6 @@ class Popup
removeEventListener 'click', @bound_click_maybe_close
removeEventListener 'keydown', @bound_key_maybe_close
# ff glorious hack
if typeof InstallTrigger isnt "undefined"
document.body.contentEditable = 'true'
@el.classList.add 'hidden'
@el.innerHTML = ''

View File

@@ -1,80 +0,0 @@
# *-* coding: utf-8 *-*
# This file is part of butterfly
#
# butterfly Copyright(C) 2015-2017 Florian Mounier
# 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 <http://www.gnu.org/licenses/>.
if /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i
.test navigator.userAgent
ctrl = false
alt = false
first = true
virtualInput = document.createElement 'input'
virtualInput.type = 'password'
virtualInput.style.position = 'fixed'
virtualInput.style.top = 0
virtualInput.style.left = 0
virtualInput.style.border = 'none'
virtualInput.style.outline = 'none'
virtualInput.style.opacity = 0
virtualInput.value = '0'
document.body.appendChild virtualInput
virtualInput.addEventListener 'blur', ->
setTimeout((=> @focus()), 10)
addEventListener 'click', ->
virtualInput.focus()
addEventListener 'touchstart', (e) ->
if e.touches.length == 2
ctrl = true
else if e.touches.length == 3
ctrl = false
alt = true
else if e.touches.length == 4
ctrl = true
alt = true
virtualInput.addEventListener 'keydown', (e) ->
butterfly.keyDown(e)
return true
virtualInput.addEventListener 'input', (e) ->
len = @value.length
if len == 0
e.keyCode = 8
butterfly.keyDown e
@value = '0'
return true
e.keyCode = @value.charAt(1).charCodeAt(0)
if (ctrl or alt) and not first
e.keyCode = @value.charAt(1).charCodeAt(0)
e.ctrlKey = ctrl
e.altKey = alt
if e.keyCode >= 97 && e.keyCode <= 122
e.keyCode -= 32
butterfly.keyDown e
@value = '0'
ctrl = alt = false
return true
butterfly.keyPress e
first = false
@value = '0'
true

View File

@@ -34,6 +34,9 @@ cancel = (ev) ->
ev.cancelBubble = true
false
isMobile = ->
/iPhone|iPad|iPod|Android/i.test navigator.userAgent
s = 0
State =
normal: s++
@@ -67,11 +70,23 @@ class Terminal
@forceWidth = @body.getAttribute(
'data-force-unicode-width') is 'yes'
# A hidden textarea to capture all input events
# This allows the body to receive IME composition events
# without being `contentEditable`, which will mess up
# the layout
@inputHelper = @document.getElementById('input-helper')
# A simple div to take place of the IME input preview
# which is now hidden due to the textarea
@inputView = @document.getElementById('input-view')
# Main terminal element
@body.className = 'terminal focus'
@body.style.outline = 'none'
@body.setAttribute 'tabindex', 0
@body.setAttribute 'spellcheck', 'false'
@inputHelper.setAttribute 'tabindex', 0
@inputHelper.setAttribute 'spellcheck', 'false'
# Adding one line to compute char size
div = @document.createElement('div')
@@ -87,14 +102,29 @@ class Terminal
@termName = 'xterm'
@cursorBlink = true
@cursorState = 0
@inComposition = false
@compositionText = ""
@resetVars()
@focus()
@startBlink()
# IME Events should be registered to the textarea
# The textarea helps guiding the IME to pop up
# at the correct position
@inputHelper.addEventListener 'compositionstart',
@compositionStart.bind(@)
@inputHelper.addEventListener 'compositionupdate',
@compositionUpdate.bind(@)
@inputHelper.addEventListener 'compositionend',
@compositionEnd.bind(@)
addEventListener 'keydown', @keyDown.bind(@)
addEventListener 'keypress', @keyPress.bind(@)
# Always focus on the inputHelper textarea
addEventListener 'keyup', => @inputHelper.focus()
if isMobile()
addEventListener 'click', => @inputHelper.focus()
addEventListener 'focus', @focus.bind(@)
addEventListener 'blur', @blur.bind(@)
addEventListener 'resize', => @resize()
@@ -102,10 +132,6 @@ class Terminal
@nativeScrollTo()
, true
# # Horrible Firefox paste workaround
if typeof InstallTrigger isnt "undefined"
@body.contentEditable = 'true'
@initmouse()
addEventListener 'load', => @resize()
@emit 'load'
@@ -131,6 +157,7 @@ class Terminal
italic: a.italic
faint: a.faint
crossed: a.crossed
placeholder: false
equalAttr: (a, b) ->
# Not testing char
@@ -140,12 +167,14 @@ class Terminal
a.italic is b.italic and a.faint is b.faint and
a.crossed is b.crossed)
putChar: (c) ->
putChar: (c, placeholder = false) ->
newChar = @cloneAttr @curAttr, c
newChar.placeholder = placeholder
if @insertMode
@screen[@y + @shift].chars.splice(@x, 0, @cloneAttr @curAttr, c)
@screen[@y + @shift].chars.splice(@x, 0, newChar)
@screen[@y + @shift].chars.pop()
else
@screen[@y + @shift].chars[@x] = @cloneAttr @curAttr, c
@screen[@y + @shift].chars[@x] = newChar
@screen[@y + @shift].dirty = true
@@ -187,6 +216,7 @@ class Terminal
italic: false
faint: false
crossed: false
placeholder: false
@curAttr = @cloneAttr @defAttr
@params = []
@@ -222,6 +252,7 @@ class Terminal
@showCursor()
@body.classList.add('focus')
@body.classList.remove('blur')
@inputHelper.focus() # Always focus on the textarea
@resize()
@scrollLock = old_sl
@@ -450,7 +481,21 @@ class Terminal
[classes, styles]
# Fullwidth (CJK) character ranges
isCJK: (ch) ->
"\u4e00" <= ch <= "\u9fff" or # CJK Unified Ideographs
"\u3040" <= ch <= "\u30ff" or # Japanese Hiragana and Katakana
"\u31f0" <= ch <= "\u31ff" or # Japanese Katakana Phonetic Extensions
"\u3190" <= ch <= "\u319f" or # Japanese Kanbun symbols
"\u3301" <= ch <= "\u3356" or # Japanese compound characters Kumimoji (組文字)
"\uac00" <= ch <= "\ud7ff" or # Hangul precomposed syllables
"\u3000" <= ch <= "\u303f" or # CJK Punctuations and Symbols
"\uff00" <= ch <= "\uff60" or # Fullwidth forms
"\uffe0" <= ch <= "\uffe6" # Fullwidth forms
charToDom: (data, attr, cursor) ->
# Just do not render if we see any placeholder characters
return if data.placeholder
return data.html if data.html
attr = attr or @cloneAttr @defAttr
ch = data.ch
@@ -478,12 +523,13 @@ class Terminal
else
if ch <= " "
char += "&nbsp;"
else unless @forceWidth
# CJK characters should always be forced to be fullwidth
else unless @forceWidth or @isCJK ch
char += ch
else
if ch <= "~" # Ascii chars
char += ch
else if "\uff00" < ch < "\uffef"
else if @isCJK ch # CJK always fullwidth
char += "<span style=\"display: inline-block; width: #{
2 * @charSize.width}px\">#{ch}</span>"
else
@@ -544,6 +590,7 @@ class Terminal
dom = @screenToDom(force)
@writeDom dom
@nativeScrollTo()
@updateInputViews()
@emit 'refresh'
_cursorBlink: ->
@@ -692,12 +739,15 @@ class Terminal
@x = 0
@putChar ch
@x++
if @forceWidth and "\uff00" < ch < "\uffef"
if @cols < 2 or @x >= @cols
@putChar " "
break
@putChar " "
if @isCJK ch
# Add a dummy, placeholder character
# for double-width, CJK characters
# In order to fix counting of characters
# when calculating for remaining cols
# They are always considered to be
# @forceWidth because otherwise they
# do not render properly at all
@putChar " ", true
@x++
when State.escaped
@@ -1251,11 +1301,96 @@ class Terminal
writeln: (data) ->
@write "#{data}\r\n"
updateInputViews: ->
# Re-position the textarea and the preview box
# to the current position of the cursor
cursorPos = @cursor.getBoundingClientRect()
@inputView.style['left'] = cursorPos.left + "px"
@inputView.style['top'] = cursorPos.top + "px"
@inputHelper.style['left'] = cursorPos.left + "px"
@inputHelper.style['top'] = cursorPos.top + "px"
# Clear the textarea as often as possible
@inputHelper.value = ""
compositionStart: (ev) ->
ev.preventDefault()
ev.stopPropagation()
@updateInputViews()
# Show the preview box
@inputView.className = ""
@inputView.innerText = ""
# Hide the blinking cursor
@cursor.style['visibility'] = "hidden"
@inComposition = true
@compositionText = ""
return false
compositionUpdate: (ev) ->
ev.preventDefault()
ev.stopPropagation()
# Update the composition text
@compositionText = ev.data
@inputView.innerText = @compositionText
return false
compositionEnd: (ev) ->
ev.preventDefault()
ev.stopPropagation()
@finishComposition()
return false
finishComposition: ->
@inComposition = false
@showCursor()
@inputHelper.value = ""
@inputView.className = "hidden"
@send @compositionText
@compositionText = ""
# Force focus on the inputHelper
@inputHelper.focus()
keyDown: (ev) ->
if @inComposition
# Continue IME composition if the character is
# composition key or modifier key
if ev.keyCode is 229
return false
else if ev.keyCode is 16 || ev.keyCode is 17 || ev.keyCode is 18
return false
# Otherwise, if we receive a keyDown, abort the composition
@finishComposition()
if ev.keyCode is 229
ev.preventDefault()
ev.stopPropagation()
# If the composition key is sent while IME not active
# it means that some character have been input while IME
# enabled, i.e. punctuations
# in which case we just fetch it from the text area
setTimeout =>
unless @inComposition || @inputHelper.value.length > 1
val = @inputHelper.value
@inputHelper.value = "" # Clear the value immediately
char = val.toUpperCase().charCodeAt(0)
if 65 <= char <= 90
# If the character sent here is a letter
# allow it to be overridden on mobile
# Combinations like "Ctrl+C" won't work without this
# on Chrome for Android
e = new KeyboardEvent 'keydown', keyCode: char
return if window.mobileKeydown e
@send val
, 0
return false
# Key Resources:
# https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent
# Don't handle modifiers alone
return true if ev.keyCode > 15 and ev.keyCode < 19
return true if window.mobileKeydown ev
if ev.keyCode is 19 # Pause break
@body.classList.add 'stopped'
@@ -1268,7 +1403,11 @@ class Terminal
return true if (ev.shiftKey or ev.ctrlKey) and ev.keyCode is 45
# Let the ctrl+shift+c, ctrl+shift+v go through to handle native copy paste
return true if (ev.shiftKey and ev.ctrlKey) and ev.keyCode in [67, 86]
if (ev.shiftKey and ev.ctrlKey) and ev.keyCode in [67, 86]
# Make the content temporarily ediatble, to allow the paste event
# to propagate (this does not work for the textarea if not set like this)
@body.contentEditable = true
return true
# Alt-z works as an escape to relay the following keys to the browser.
# usefull to trigger browser shortcuts, i.e.: Alt+Z F5 to reload

View File

@@ -20,6 +20,6 @@
"grunt-contrib-cssmin": "^1.0.1",
"grunt-contrib-uglify": "^1.0.1",
"grunt-contrib-watch": "^1.0.0",
"grunt-sass": "^1.2.0"
"grunt-sass": "^2.1.0"
}
}

201
yarn.lock
View File

@@ -179,6 +179,10 @@ camelcase@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a"
caseless@~0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7"
caseless@~0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
@@ -283,6 +287,10 @@ commander@2.8.x:
dependencies:
graceful-readlink ">= 1.0.0"
commander@^2.9.0:
version "2.15.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f"
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
@@ -478,6 +486,16 @@ gaze@^1.0.0:
dependencies:
globule "^1.0.0"
generate-function@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.0.0.tgz#6858fe7c0969b7d4e9093337647ac79f60dfbe74"
generate-object-property@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0"
dependencies:
is-property "^1.0.0"
get-caller-file@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5"
@@ -496,6 +514,16 @@ getpass@^0.1.1:
dependencies:
assert-plus "^1.0.0"
glob@^6.0.4:
version "6.0.4"
resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22"
dependencies:
inflight "^1.0.4"
inherits "2"
minimatch "2 || 3"
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@^7.0.0, glob@^7.0.3, glob@^7.0.6, glob@~7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8"
@@ -629,12 +657,12 @@ grunt-legacy-util@~1.0.0:
underscore.string "~3.2.3"
which "~1.2.1"
grunt-sass@^1.2.0:
version "1.2.1"
resolved "https://registry.yarnpkg.com/grunt-sass/-/grunt-sass-1.2.1.tgz#fb87b6caac46fb32d45177fd2e4b6ff7468c1919"
grunt-sass@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/grunt-sass/-/grunt-sass-2.1.0.tgz#b7ba1d85ef4c2d9b7d8195fe65f664ac7554efa1"
dependencies:
each-async "^1.0.0"
node-sass "^3.7.0"
node-sass "^4.7.2"
object-assign "^4.0.1"
grunt@^1.0.1:
@@ -669,6 +697,15 @@ har-schema@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e"
har-validator@~2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-2.0.6.tgz#cdcbc08188265ad119b6a5a7c8ab70eecfb5d27d"
dependencies:
chalk "^1.1.1"
commander "^2.9.0"
is-my-json-valid "^2.12.4"
pinkie-promise "^2.0.0"
har-validator@~4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a"
@@ -758,7 +795,7 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
inherits@2, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1:
inherits@2, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
@@ -792,6 +829,24 @@ is-fullwidth-code-point@^1.0.0:
dependencies:
number-is-nan "^1.0.0"
is-my-ip-valid@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz#7b351b8e8edd4d3995d4d066680e664d94696824"
is-my-json-valid@^2.12.4:
version "2.17.2"
resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.17.2.tgz#6b2103a288e94ef3de5cf15d29dd85fc4b78d65c"
dependencies:
generate-function "^2.0.0"
generate-object-property "^1.1.0"
is-my-ip-valid "^1.0.0"
jsonpointer "^4.0.0"
xtend "^4.0.0"
is-property@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84"
is-typedarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
@@ -851,6 +906,10 @@ jsonify@~0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
jsonpointer@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9"
jsprim@^1.2.2:
version "1.4.0"
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.0.tgz#a3b87e40298d8c380552d8cc7628a0bb95a22918"
@@ -898,6 +957,10 @@ lodash.clonedeep@^4.3.2:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
lodash.mergewith@^4.6.0:
version "4.6.1"
resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz#639057e726c3afbdb3e7d42741caa8d6e4335927"
lodash@^3.10.1, lodash@~3.10.1:
version "3.10.1"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
@@ -998,9 +1061,9 @@ ms@0.7.1:
version "0.7.1"
resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098"
nan@^2.3.2:
version "2.6.2"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.6.2.tgz#e4ff34e6c95fdfb5aecc08de6596f43605a7db45"
nan@^2.10.0:
version "2.10.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f"
node-gyp@^3.3.1:
version "3.6.1"
@@ -1020,9 +1083,9 @@ node-gyp@^3.3.1:
tar "^2.0.0"
which "1"
node-sass@^3.7.0:
version "3.13.1"
resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-3.13.1.tgz#7240fbbff2396304b4223527ed3020589c004fc2"
node-sass@^4.7.2:
version "4.9.0"
resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.9.0.tgz#d1b8aa855d98ed684d6848db929a20771cc2ae52"
dependencies:
async-foreach "^0.1.3"
chalk "^1.1.1"
@@ -1033,13 +1096,16 @@ node-sass@^3.7.0:
in-publish "^2.0.0"
lodash.assign "^4.2.0"
lodash.clonedeep "^4.3.2"
lodash.mergewith "^4.6.0"
meow "^3.7.0"
mkdirp "^0.5.1"
nan "^2.3.2"
nan "^2.10.0"
node-gyp "^3.3.1"
npmlog "^4.0.0"
request "^2.61.0"
sass-graph "^2.1.1"
request "~2.79.0"
sass-graph "^2.2.4"
stdout-stream "^1.4.0"
"true-case-path" "^1.0.2"
"nopt@2 || 3", nopt@~3.0.6:
version "3.0.6"
@@ -1182,6 +1248,10 @@ process-nextick-args@~1.0.6:
version "1.0.7"
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3"
process-nextick-args@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
pseudomap@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
@@ -1198,6 +1268,10 @@ qs@~5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-5.1.0.tgz#4d932e5c7ea411cca76a312d39a606200fd50cd9"
qs@~6.3.0:
version "6.3.2"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.2.tgz#e75bd5f6e268122a2a0e0bda630b2550c166502c"
qs@~6.4.0:
version "6.4.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"
@@ -1225,6 +1299,18 @@ read-pkg@^1.0.0:
normalize-package-data "^2.3.2"
path-type "^1.0.0"
readable-stream@^2.0.1:
version "2.3.6"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
dependencies:
core-util-is "~1.0.0"
inherits "~2.0.3"
isarray "~1.0.0"
process-nextick-args "~2.0.0"
safe-buffer "~5.1.1"
string_decoder "~1.1.1"
util-deprecate "~1.0.1"
readable-stream@^2.0.6, readable-stream@^2.2.2:
version "2.2.9"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.9.tgz#cf78ec6f4a6d1eb43d26488cac97f042e74b7fc8"
@@ -1254,7 +1340,7 @@ repeating@^2.0.0:
dependencies:
is-finite "^1.0.0"
request@2, request@^2.61.0:
request@2:
version "2.81.0"
resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0"
dependencies:
@@ -1281,6 +1367,31 @@ request@2, request@^2.61.0:
tunnel-agent "^0.6.0"
uuid "^3.0.0"
request@~2.79.0:
version "2.79.0"
resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de"
dependencies:
aws-sign2 "~0.6.0"
aws4 "^1.2.1"
caseless "~0.11.0"
combined-stream "~1.0.5"
extend "~3.0.0"
forever-agent "~0.6.1"
form-data "~2.1.1"
har-validator "~2.0.6"
hawk "~3.1.3"
http-signature "~1.1.0"
is-typedarray "~1.0.0"
isstream "~0.1.2"
json-stringify-safe "~5.0.1"
mime-types "~2.1.7"
oauth-sign "~0.8.1"
qs "~6.3.0"
stringstream "~0.0.4"
tough-cookie "~2.3.0"
tunnel-agent "~0.4.1"
uuid "^3.0.0"
require-directory@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
@@ -1311,18 +1422,22 @@ safe-buffer@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7"
sass-graph@^2.1.1:
version "2.2.2"
resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.2.2.tgz#f4d6c95b546ea2a09d14176d0fc1a07ee2b48354"
safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
sass-graph@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.2.4.tgz#13fbd63cd1caf0908b9fd93476ad43a51d1e0b49"
dependencies:
glob "^7.0.0"
lodash "^4.0.0"
scss-tokenizer "^0.2.1"
yargs "^6.6.0"
scss-tokenizer "^0.2.3"
yargs "^7.0.0"
scss-tokenizer@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.1.tgz#07c0cc577bb7ab4d08fd900185adbf4bc844141d"
scss-tokenizer@^0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1"
dependencies:
js-base64 "^2.1.8"
source-map "^0.4.2"
@@ -1396,6 +1511,12 @@ statuses@1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e"
stdout-stream@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/stdout-stream/-/stdout-stream-1.4.0.tgz#a2c7c8587e54d9427ea9edb3ac3f2cd522df378b"
dependencies:
readable-stream "^2.0.1"
string-width@^1.0.1, string-width@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
@@ -1410,6 +1531,12 @@ string_decoder@~1.0.0:
dependencies:
buffer-shims "~1.0.0"
string_decoder@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
dependencies:
safe-buffer "~5.1.0"
stringstream@~0.0.4:
version "0.0.5"
resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878"
@@ -1483,12 +1610,22 @@ trim-newlines@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613"
"true-case-path@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-1.0.2.tgz#7ec91130924766c7f573be3020c34f8fdfd00d62"
dependencies:
glob "^6.0.4"
tunnel-agent@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
dependencies:
safe-buffer "^5.0.1"
tunnel-agent@~0.4.1:
version "0.4.3"
resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb"
tweetnacl@^0.14.3, tweetnacl@~0.14.0:
version "0.14.5"
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
@@ -1599,6 +1736,10 @@ wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
xtend@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
y18n@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
@@ -1607,15 +1748,15 @@ yallist@^2.0.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
yargs-parser@^4.2.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-4.2.1.tgz#29cceac0dc4f03c6c87b4a9f217dd18c9f74871c"
yargs-parser@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-5.0.0.tgz#275ecf0d7ffe05c77e64e7c86e4cd94bf0e1228a"
dependencies:
camelcase "^3.0.0"
yargs@^6.6.0:
version "6.6.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-6.6.0.tgz#782ec21ef403345f830a808ca3d513af56065208"
yargs@^7.0.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8"
dependencies:
camelcase "^3.0.0"
cliui "^3.2.0"
@@ -1629,7 +1770,7 @@ yargs@^6.6.0:
string-width "^1.0.2"
which-module "^1.0.0"
y18n "^3.2.1"
yargs-parser "^4.2.0"
yargs-parser "^5.0.0"
yargs@~3.10.0:
version "3.10.0"