- Add alarm mode [alt+A] for now

- isolate ext modules
- remove now useless scss
- update node deps
This commit is contained in:
Florian Mounier
2014-07-18 16:03:40 +02:00
parent 0a23565302
commit 488e03246c
24 changed files with 828 additions and 586 deletions

View File

@@ -12,11 +12,15 @@ module.exports = (grunt) ->
butterfly:
files:
'butterfly/static/main.min.js': 'butterfly/static/main.js'
'butterfly/static/ext.min.js': 'butterfly/static/ext.js'
sass:
options:
includePaths: ['butterfly/sass/']
butterfly:
expand: true
cwd: 'butterfly/sass'
cwd: 'butterfly/sass/'
src: '*.sass'
dest: 'butterfly/static/'
ext: '.css'
@@ -27,12 +31,8 @@ module.exports = (grunt) ->
butterfly:
files:
'butterfly/static/main.js': [
'coffees/term.coffee'
'coffees/selection.coffee'
'coffees/virtual_input.coffee'
'coffees/main.coffee'
]
'butterfly/static/main.js': 'coffees/*.coffee'
'butterfly/static/ext.js': 'coffees/ext/*.coffee'
coffeelint:
butterfly:
@@ -43,6 +43,7 @@ module.exports = (grunt) ->
livereload: true
coffee:
files: [
'coffees/ext/*.coffee'
'coffees/*.coffee'
'Gruntfile.coffee'
]

View File

@@ -14,7 +14,7 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
__version__ = '1.5.4'
__version__ = '1.5.5'
import os

View File

@@ -25,15 +25,23 @@ $shadow-alpha: .5 !default
&.bell
-webkit-filter: blur(2px)
filter: blur(2px)
&.skip
-webkit-filter: sepia(1)
filter: sepia(1)
&.selection
-webkit-filter: unquote("saturate(2)")
filter: unquote("saturate(2)")
&.alarm
-webkit-filter: hue-rotate(150deg)
filter: hue-rotate(150deg)
&.dead
-webkit-filter: unquote("grayscale(1)")
filter: unquote("grayscale(1)")
&:after
content: "CLOSED"

View File

@@ -15,11 +15,11 @@
/* You should have received a copy of the GNU General Public License */
/* along with this program. If not, see <http://www.gnu.org/licenses/>. */
@import 'font'
@import 'layout'
@import 'fx'
@import 'colors'
@import '16_colors'
@import '256_colors'
@import 'cursor'
@import 'term_styles'
@import font
@import layout
@import fx
@import colors
@import 16_colors
@import 256_colors
@import cursor
@import term_styles

445
butterfly/static/ext.js Normal file
View File

@@ -0,0 +1,445 @@
(function() {
var Selection, alt, cancel, ctrl, first, next_leaf, previous_leaf, selection, set_alarm, virtual_input,
__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; };
set_alarm = function(notification) {
var alarm;
alarm = function(data) {
var note;
butterfly.element.classList.remove('alarm');
note = "New activity on butterfly terminal [" + butterfly.title + "]";
if (notification) {
new Notification(note, {
body: data.data,
icon: '/static/images/favicon.png'
});
} else {
alert(note + '\n' + data.data);
}
return butterfly.ws.removeEventListener('message', alarm);
};
butterfly.ws.addEventListener('message', alarm);
return butterfly.element.classList.add('alarm');
};
cancel = function(ev) {
if (ev.preventDefault) {
ev.preventDefault();
}
if (ev.stopPropagation) {
ev.stopPropagation();
}
ev.cancelBubble = true;
return false;
};
document.addEventListener('keydown', function(e) {
if (!(e.altKey && e.keyCode === 65)) {
return true;
}
if (Notification && Notification.permission === 'default') {
Notification.requestPermission(function() {
return set_alarm(Notification.permission === 'granted');
});
} else {
set_alarm(Notification.permission === 'granted');
}
return cancel(e);
});
selection = null;
cancel = function(ev) {
if (ev.preventDefault) {
ev.preventDefault();
}
if (ev.stopPropagation) {
ev.stopPropagation();
}
ev.cancelBubble = true;
return false;
};
previous_leaf = function(node) {
var previous;
previous = node.previousSibling;
if (!previous) {
previous = node.parentNode.previousSibling;
}
if (!previous) {
previous = node.parentNode.parentNode.previousSibling;
}
while (previous.lastChild) {
previous = previous.lastChild;
}
return previous;
};
next_leaf = function(node) {
var next;
next = node.nextSibling;
if (!next) {
next = node.parentNode.nextSibling;
}
if (!next) {
next = node.parentNode.parentNode.nextSibling;
}
while (next.firstChild) {
next = next.firstChild;
}
return next;
};
Selection = (function() {
function Selection() {
butterfly.element.classList.add('selection');
this.selection = getSelection();
}
Selection.prototype.reset = function() {
var fake_range, _ref, _results;
this.selection = getSelection();
fake_range = document.createRange();
fake_range.setStart(this.selection.anchorNode, this.selection.anchorOffset);
fake_range.setEnd(this.selection.focusNode, this.selection.focusOffset);
this.start = {
node: this.selection.anchorNode,
offset: this.selection.anchorOffset
};
this.end = {
node: this.selection.focusNode,
offset: this.selection.focusOffset
};
if (fake_range.collapsed) {
_ref = [this.end, this.start], this.start = _ref[0], this.end = _ref[1];
}
this.start_line = this.start.node;
while (!this.start_line.classList || __indexOf.call(this.start_line.classList, 'line') < 0) {
this.start_line = this.start_line.parentNode;
}
this.end_line = this.end.node;
_results = [];
while (!this.end_line.classList || __indexOf.call(this.end_line.classList, 'line') < 0) {
_results.push(this.end_line = this.end_line.parentNode);
}
return _results;
};
Selection.prototype.clear = function() {
return this.selection.removeAllRanges();
};
Selection.prototype.destroy = function() {
butterfly.element.classList.remove('selection');
return this.clear();
};
Selection.prototype.text = function() {
return this.selection.toString();
};
Selection.prototype.up = function() {
return this.go(-1);
};
Selection.prototype.down = function() {
return this.go(+1);
};
Selection.prototype.go = function(n) {
var index;
index = butterfly.children.indexOf(this.start_line) + n;
if (!((0 <= index && index < butterfly.children.length))) {
return;
}
while (!butterfly.children[index].textContent.match(/\S/)) {
index += n;
if (!((0 <= index && index < butterfly.children.length))) {
return;
}
}
return this.select_line(index);
};
Selection.prototype.apply = function() {
var range;
this.clear();
range = document.createRange();
range.setStart(this.start.node, this.start.offset);
range.setEnd(this.end.node, this.end.offset);
return this.selection.addRange(range);
};
Selection.prototype.select_line = function(index) {
var line, line_end, line_start;
line = butterfly.children[index];
line_start = {
node: line.firstChild,
offset: 0
};
line_end = {
node: line.lastChild,
offset: line.lastChild.textContent.length
};
this.start = this.walk(line_start, /\S/);
return this.end = this.walk(line_end, /\S/, true);
};
Selection.prototype.collapsed = function(start, end) {
var fake_range;
fake_range = document.createRange();
fake_range.setStart(start.node, start.offset);
fake_range.setEnd(end.node, end.offset);
return fake_range.collapsed;
};
Selection.prototype.shrink_right = function() {
var end, node;
node = this.walk(this.end, /\s/, true);
end = this.walk(node, /\S/, true);
if (!this.collapsed(this.start, end)) {
return this.end = end;
}
};
Selection.prototype.shrink_left = function() {
var node, start;
node = this.walk(this.start, /\s/);
start = this.walk(node, /\S/);
if (!this.collapsed(start, this.end)) {
return this.start = start;
}
};
Selection.prototype.expand_right = function() {
var node;
node = this.walk(this.end, /\S/);
return this.end = this.walk(node, /\s/);
};
Selection.prototype.expand_left = function() {
var node;
node = this.walk(this.start, /\S/, true);
return this.start = this.walk(node, /\s/, true);
};
Selection.prototype.walk = function(needle, til, backward) {
var i, node, text;
if (backward == null) {
backward = false;
}
if (needle.node.firstChild) {
node = needle.node.firstChild;
} else {
node = needle.node;
}
text = node.textContent;
i = needle.offset;
if (backward) {
while (node) {
while (i > 0) {
if (text[--i].match(til)) {
return {
node: node,
offset: i + 1
};
}
}
node = previous_leaf(node);
text = node.textContent;
i = text.length;
}
} else {
while (node) {
while (i < text.length) {
if (text[i++].match(til)) {
return {
node: node,
offset: i - 1
};
}
}
node = next_leaf(node);
text = node.textContent;
i = 0;
}
}
return needle;
};
return Selection;
})();
document.addEventListener('keydown', function(e) {
var _ref, _ref1;
if (_ref = e.keyCode, __indexOf.call([16, 17, 18, 19], _ref) >= 0) {
return true;
}
if (e.shiftKey && e.keyCode === 13 && !selection && !getSelection().isCollapsed) {
butterfly.handler(getSelection().toString());
getSelection().removeAllRanges();
return cancel(e);
}
if (selection) {
selection.reset();
if (!e.ctrlKey && e.shiftKey && (37 <= (_ref1 = e.keyCode) && _ref1 <= 40)) {
return true;
}
if (e.shiftKey && e.ctrlKey) {
if (e.keyCode === 38) {
selection.up();
} else if (e.keyCode === 40) {
selection.down();
}
} else if (e.keyCode === 39) {
selection.shrink_left();
} else if (e.keyCode === 38) {
selection.expand_left();
} else if (e.keyCode === 37) {
selection.shrink_right();
} else if (e.keyCode === 40) {
selection.expand_right();
} else {
return cancel(e);
}
if (selection != null) {
selection.apply();
}
return cancel(e);
}
if (!selection && e.ctrlKey && e.shiftKey && e.keyCode === 38) {
selection = new Selection();
selection.select_line(butterfly.y - 1);
selection.apply();
return cancel(e);
}
return true;
});
document.addEventListener('keyup', function(e) {
var _ref, _ref1;
if (_ref = e.keyCode, __indexOf.call([16, 17, 18, 19], _ref) >= 0) {
return true;
}
if (selection) {
if (e.keyCode === 13) {
butterfly.handler(selection.text());
selection.destroy();
selection = null;
return cancel(e);
}
if (_ref1 = e.keyCode, __indexOf.call([37, 38, 39, 40], _ref1) < 0) {
selection.destroy();
selection = null;
return true;
}
}
return true;
});
document.addEventListener('dblclick', function(e) {
var anchorNode, anchorOffset, new_range, range, sel;
if (e.ctrlKey || e.altkey) {
return;
}
sel = getSelection();
if (sel.isCollapsed || sel.toString().match(/\s/)) {
return;
}
range = document.createRange();
range.setStart(sel.anchorNode, sel.anchorOffset);
range.setEnd(sel.focusNode, sel.focusOffset);
if (range.collapsed) {
sel.removeAllRanges();
new_range = document.createRange();
new_range.setStart(sel.focusNode, sel.focusOffset);
new_range.setEnd(sel.anchorNode, sel.anchorOffset);
sel.addRange(new_range);
}
range.detach();
while (!(sel.toString().match(/\s/) || !sel.toString())) {
sel.modify('extend', 'forward', 'character');
}
sel.modify('extend', 'backward', 'character');
anchorNode = sel.anchorNode;
anchorOffset = sel.anchorOffset;
sel.collapseToEnd();
sel.extend(anchorNode, anchorOffset);
while (!(sel.toString().match(/\s/) || !sel.toString())) {
sel.modify('extend', 'backward', 'character');
}
return sel.modify('extend', 'forward', 'character');
});
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
ctrl = false;
alt = false;
first = true;
virtual_input = document.createElement('input');
virtual_input.type = 'password';
virtual_input.style.position = 'fixed';
virtual_input.style.top = 0;
virtual_input.style.left = 0;
virtual_input.style.border = 'none';
virtual_input.style.outline = 'none';
virtual_input.style.opacity = 0;
virtual_input.value = '0';
document.body.appendChild(virtual_input);
virtual_input.addEventListener('blur', function() {
return setTimeout(((function(_this) {
return function() {
return _this.focus();
};
})(this)), 10);
});
addEventListener('click', function() {
return virtual_input.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;
}
});
virtual_input.addEventListener('keydown', function(e) {
butterfly.keyDown(e);
return true;
});
virtual_input.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

4
butterfly/static/ext.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,29 @@
/* *-* coding: utf-8 *-* */
/* This file is part of butterfly */
/* butterfly Copyright (C) 2014 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/>. */
/* *-* coding: utf-8 *-* */
/* This file is part of butterfly */
/* butterfly Copyright (C) 2014 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/>. */
@font-face {
font-family: "SourceCodePro";
src: url("/static/fonts/SourceCodePro-ExtraLight.otf") format("woff");
@@ -37,6 +63,19 @@ body {
font-family: "SourceCodePro";
line-height: 1.2; }
/* *-* coding: utf-8 *-* */
/* This file is part of butterfly */
/* butterfly Copyright (C) 2014 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/>. */
html, body {
height: 100%;
margin: 0;
@@ -51,17 +90,37 @@ html, body {
.terminal {
outline: none; }
/* *-* coding: utf-8 *-* */
/* This file is part of butterfly */
/* butterfly Copyright (C) 2014 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/>. */
.terminal {
text-shadow: 0 0 6px rgba(255, 255, 255, 0.5);
transition: 200ms; }
.terminal.bell {
-webkit-filter: blur(2px); }
-webkit-filter: blur(2px);
filter: blur(2px); }
.terminal.skip {
-webkit-filter: sepia(1); }
-webkit-filter: sepia(1);
filter: sepia(1); }
.terminal.selection {
-webkit-filter: saturate(2); }
-webkit-filter: saturate(2);
filter: saturate(2); }
.terminal.alarm {
-webkit-filter: hue-rotate(150deg);
filter: hue-rotate(150deg); }
.terminal.dead {
-webkit-filter: grayscale(1); }
-webkit-filter: grayscale(1);
filter: grayscale(1); }
.terminal.dead:after {
content: "CLOSED";
font-size: 15em;
@@ -77,6 +136,19 @@ html, body {
opacity: 0.2;
font-weight: 900; }
/* *-* coding: utf-8 *-* */
/* This file is part of butterfly */
/* butterfly Copyright (C) 2014 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/>. */
#wrapper {
background-color: #110f13; }
@@ -84,6 +156,20 @@ html, body {
background-color: #110f13;
color: #f4ead5; }
/* *-* coding: utf-8 *-* */
/* This file is part of butterfly */
/* butterfly Copyright (C) 2014 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/>. */
/* Here are the 16 "normal" colors for theming */
.bg-color-0 {
background-color: #2e3436; }
.bg-color-0.reverse-video {
@@ -260,6 +346,21 @@ html, body {
.fg-color-15.reverse-video {
background-color: #eeeeec !important; }
/* *-* coding: utf-8 *-* */
/* This file is part of butterfly */
/* butterfly Copyright (C) 2014 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/>. */
/* Here are the 240 xterm colors */
/* See http://upload.wikimedia.org/wikipedia/en/1/15/Xterm_256color_chart.svg */
.bg-color-16 {
background-color: black; }
.bg-color-16.reverse-video {
@@ -2922,12 +3023,38 @@ html, body {
.fg-color-257.reverse-video {
background-color: #f4ead5 !important; }
/* *-* coding: utf-8 *-* */
/* This file is part of butterfly */
/* butterfly Copyright (C) 2014 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/>. */
.focus .cursor {
transition: 300ms; }
.cursor.reverse-video {
box-shadow: 0 0 0.5 #f4ead5; }
/* *-* coding: utf-8 *-* */
/* This file is part of butterfly */
/* butterfly Copyright (C) 2014 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/>. */
.bold {
font-weight: bold; }

View File

@@ -1,7 +1,97 @@
(function() {
var $, Selection, State, Terminal, alt, bench, cancel, cbench, cols, ctl, ctrl, first, next_leaf, open_ts, previous_leaf, quit, rows, s, selection, send, term, virtual_input, ws, ws_url,
__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; },
__slice = [].slice;
var $, State, Terminal, cancel, cols, open_ts, quit, rows, s,
__slice = [].slice,
__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;
quit = false;
open_ts = (new Date()).getTime();
$ = document.querySelectorAll.bind(document);
document.addEventListener('DOMContentLoaded', function() {
var bench, cbench, ctl, send, term, ws, ws_url;
send = function(data) {
return ws.send('S' + data);
};
ctl = function() {
var args, params, type;
type = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
params = args.join(',');
if (type === 'Resize') {
return ws.send('R' + params);
}
};
if (location.protocol === 'https:') {
ws_url = 'wss://';
} else {
ws_url = 'ws://';
}
ws_url += document.location.host + '/ws' + location.pathname;
ws = new WebSocket(ws_url);
ws.addEventListener('open', function() {
console.log("WebSocket open", arguments);
ws.send('R' + term.cols + ',' + term.rows);
return open_ts = (new Date()).getTime();
});
ws.addEventListener('error', function() {
return console.log("WebSocket error", arguments);
});
ws.addEventListener('message', function(e) {
return setTimeout(function() {
return term.write(e.data);
}, 1);
});
ws.addEventListener('close', function() {
console.log("WebSocket closed", arguments);
setTimeout(function() {
term.write('Closed');
term.skipNextKey = true;
return term.element.classList.add('dead');
}, 1);
quit = true;
if ((new Date()).getTime() - open_ts > 60 * 1000) {
return open('', '_self').close();
}
});
term = new Terminal($('#wrapper')[0], send, ctl);
addEventListener('beforeunload', function() {
if (!quit) {
return 'This will exit the terminal session';
}
});
bench = function(n) {
var rnd, t0;
if (n == null) {
n = 100000000;
}
rnd = '';
while (rnd.length < n) {
rnd += Math.random().toString(36).substring(2);
}
t0 = (new Date()).getTime();
term.write(rnd);
return console.log("" + n + " chars in " + ((new Date()).getTime() - t0) + " ms");
};
cbench = function(n) {
var rnd, t0;
if (n == null) {
n = 100000000;
}
rnd = '';
while (rnd.length < n) {
rnd += "\x1b[" + (30 + parseInt(Math.random() * 20)) + "m";
rnd += Math.random().toString(36).substring(2);
}
t0 = (new Date()).getTime();
term.write(rnd);
return console.log("" + n + " chars + colors in " + ((new Date()).getTime() - t0) + " ms");
};
term.ws = ws;
return window.butterfly = term;
});
cancel = function(ev) {
if (ev.preventDefault) {
@@ -39,6 +129,7 @@
this.element.className = 'terminal focus';
this.element.style.outline = 'none';
this.element.setAttribute('tabindex', 0);
this.element.setAttribute('spellcheck', 'false');
this.parent.appendChild(this.element);
div = this.document.createElement('div');
div.className = 'line';
@@ -2405,486 +2496,7 @@
})();
selection = null;
previous_leaf = function(node) {
var previous;
previous = node.previousSibling;
if (!previous) {
previous = node.parentNode.previousSibling;
}
if (!previous) {
previous = node.parentNode.parentNode.previousSibling;
}
while (previous.lastChild) {
previous = previous.lastChild;
}
return previous;
};
next_leaf = function(node) {
var next;
next = node.nextSibling;
if (!next) {
next = node.parentNode.nextSibling;
}
if (!next) {
next = node.parentNode.parentNode.nextSibling;
}
while (next.firstChild) {
next = next.firstChild;
}
return next;
};
Selection = (function() {
function Selection() {
term.element.classList.add('selection');
this.selection = getSelection();
}
Selection.prototype.reset = function() {
var fake_range, _ref, _results;
this.selection = getSelection();
fake_range = document.createRange();
fake_range.setStart(this.selection.anchorNode, this.selection.anchorOffset);
fake_range.setEnd(this.selection.focusNode, this.selection.focusOffset);
this.start = {
node: this.selection.anchorNode,
offset: this.selection.anchorOffset
};
this.end = {
node: this.selection.focusNode,
offset: this.selection.focusOffset
};
if (fake_range.collapsed) {
_ref = [this.end, this.start], this.start = _ref[0], this.end = _ref[1];
}
this.start_line = this.start.node;
while (!this.start_line.classList || __indexOf.call(this.start_line.classList, 'line') < 0) {
this.start_line = this.start_line.parentNode;
}
this.end_line = this.end.node;
_results = [];
while (!this.end_line.classList || __indexOf.call(this.end_line.classList, 'line') < 0) {
_results.push(this.end_line = this.end_line.parentNode);
}
return _results;
};
Selection.prototype.clear = function() {
return this.selection.removeAllRanges();
};
Selection.prototype.destroy = function() {
term.element.classList.remove('selection');
return this.clear();
};
Selection.prototype.text = function() {
return this.selection.toString();
};
Selection.prototype.up = function() {
return this.go(-1);
};
Selection.prototype.down = function() {
return this.go(+1);
};
Selection.prototype.go = function(n) {
var index;
index = term.children.indexOf(this.start_line) + n;
if (!((0 <= index && index < term.children.length))) {
return;
}
while (!term.children[index].textContent.match(/\S/)) {
index += n;
if (!((0 <= index && index < term.children.length))) {
return;
}
}
return this.select_line(index);
};
Selection.prototype.apply = function() {
var range;
this.clear();
range = document.createRange();
range.setStart(this.start.node, this.start.offset);
range.setEnd(this.end.node, this.end.offset);
return this.selection.addRange(range);
};
Selection.prototype.select_line = function(index) {
var line, line_end, line_start;
line = term.children[index];
line_start = {
node: line.firstChild,
offset: 0
};
line_end = {
node: line.lastChild,
offset: line.lastChild.textContent.length
};
this.start = this.walk(line_start, /\S/);
return this.end = this.walk(line_end, /\S/, true);
};
Selection.prototype.collapsed = function(start, end) {
var fake_range;
fake_range = document.createRange();
fake_range.setStart(start.node, start.offset);
fake_range.setEnd(end.node, end.offset);
return fake_range.collapsed;
};
Selection.prototype.shrink_right = function() {
var end, node;
node = this.walk(this.end, /\s/, true);
end = this.walk(node, /\S/, true);
if (!this.collapsed(this.start, end)) {
return this.end = end;
}
};
Selection.prototype.shrink_left = function() {
var node, start;
node = this.walk(this.start, /\s/);
start = this.walk(node, /\S/);
if (!this.collapsed(start, this.end)) {
return this.start = start;
}
};
Selection.prototype.expand_right = function() {
var node;
node = this.walk(this.end, /\S/);
return this.end = this.walk(node, /\s/);
};
Selection.prototype.expand_left = function() {
var node;
node = this.walk(this.start, /\S/, true);
return this.start = this.walk(node, /\s/, true);
};
Selection.prototype.walk = function(needle, til, backward) {
var i, node, text;
if (backward == null) {
backward = false;
}
if (needle.node.firstChild) {
node = needle.node.firstChild;
} else {
node = needle.node;
}
text = node.textContent;
i = needle.offset;
if (backward) {
while (node) {
while (i > 0) {
if (text[--i].match(til)) {
return {
node: node,
offset: i + 1
};
}
}
node = previous_leaf(node);
text = node.textContent;
i = text.length;
}
} else {
while (node) {
while (i < text.length) {
if (text[i++].match(til)) {
return {
node: node,
offset: i - 1
};
}
}
node = next_leaf(node);
text = node.textContent;
i = 0;
}
}
return needle;
};
return Selection;
})();
document.addEventListener('keydown', function(e) {
var _ref, _ref1;
if (_ref = e.keyCode, __indexOf.call([16, 17, 18, 19], _ref) >= 0) {
return true;
}
if (e.shiftKey && e.keyCode === 13 && !selection && !getSelection().isCollapsed) {
term.handler(getSelection().toString());
getSelection().removeAllRanges();
return cancel(e);
}
if (selection) {
selection.reset();
if (!e.ctrlKey && e.shiftKey && (37 <= (_ref1 = e.keyCode) && _ref1 <= 40)) {
return true;
}
if (e.shiftKey && e.ctrlKey) {
if (e.keyCode === 38) {
selection.up();
} else if (e.keyCode === 40) {
selection.down();
}
} else if (e.keyCode === 39) {
selection.shrink_left();
} else if (e.keyCode === 38) {
selection.expand_left();
} else if (e.keyCode === 37) {
selection.shrink_right();
} else if (e.keyCode === 40) {
selection.expand_right();
} else {
return cancel(e);
}
if (selection != null) {
selection.apply();
}
return cancel(e);
}
if (!selection && e.ctrlKey && e.shiftKey && e.keyCode === 38) {
selection = new Selection();
selection.select_line(term.y - 1);
selection.apply();
return cancel(e);
}
return true;
});
document.addEventListener('keyup', function(e) {
var _ref, _ref1;
if (_ref = e.keyCode, __indexOf.call([16, 17, 18, 19], _ref) >= 0) {
return true;
}
if (selection) {
if (e.keyCode === 13) {
term.handler(selection.text());
selection.destroy();
selection = null;
return cancel(e);
}
if (_ref1 = e.keyCode, __indexOf.call([37, 38, 39, 40], _ref1) < 0) {
selection.destroy();
selection = null;
return true;
}
}
return true;
});
document.addEventListener('dblclick', function(e) {
var anchorNode, anchorOffset, new_range, range, sel;
if (e.ctrlKey || e.altkey) {
return;
}
sel = getSelection();
if (sel.isCollapsed || sel.toString().match(/\s/)) {
return;
}
range = document.createRange();
range.setStart(sel.anchorNode, sel.anchorOffset);
range.setEnd(sel.focusNode, sel.focusOffset);
if (range.collapsed) {
sel.removeAllRanges();
new_range = document.createRange();
new_range.setStart(sel.focusNode, sel.focusOffset);
new_range.setEnd(sel.anchorNode, sel.anchorOffset);
sel.addRange(new_range);
}
range.detach();
while (!(sel.toString().match(/\s/) || !sel.toString())) {
sel.modify('extend', 'forward', 'character');
}
sel.modify('extend', 'backward', 'character');
anchorNode = sel.anchorNode;
anchorOffset = sel.anchorOffset;
sel.collapseToEnd();
sel.extend(anchorNode, anchorOffset);
while (!(sel.toString().match(/\s/) || !sel.toString())) {
sel.modify('extend', 'backward', 'character');
}
return sel.modify('extend', 'forward', 'character');
});
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
ctrl = false;
alt = false;
first = true;
virtual_input = document.createElement('input');
virtual_input.type = 'password';
virtual_input.style.position = 'fixed';
virtual_input.style.top = 0;
virtual_input.style.left = 0;
virtual_input.style.border = 'none';
virtual_input.style.outline = 'none';
virtual_input.style.opacity = 0;
virtual_input.value = '0';
document.body.appendChild(virtual_input);
virtual_input.addEventListener('blur', function() {
return setTimeout(((function(_this) {
return function() {
return _this.focus();
};
})(this)), 10);
});
addEventListener('click', function() {
return virtual_input.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;
}
});
virtual_input.addEventListener('keydown', function(e) {
term.keyDown(e);
return true;
});
virtual_input.addEventListener('input', function(e) {
var len;
len = this.value.length;
if (len === 0) {
e.keyCode = 8;
term.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;
}
term.keyDown(e);
this.value = '0';
ctrl = alt = false;
return true;
}
term.keyPress(e);
first = false;
this.value = '0';
return true;
});
}
cols = rows = null;
quit = false;
open_ts = (new Date()).getTime();
$ = document.querySelectorAll.bind(document);
send = function(data) {
return ws.send('S' + data);
};
ctl = function() {
var args, params, type;
type = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
params = args.join(',');
if (type === 'Resize') {
return ws.send('R' + params);
}
};
if (location.protocol === 'https:') {
ws_url = 'wss://';
} else {
ws_url = 'ws://';
}
ws_url += document.location.host + '/ws' + location.pathname;
ws = new WebSocket(ws_url);
ws.addEventListener('open', function() {
console.log("WebSocket open", arguments);
ws.send('R' + term.cols + ',' + term.rows);
return open_ts = (new Date()).getTime();
});
ws.addEventListener('error', function() {
return console.log("WebSocket error", arguments);
});
ws.addEventListener('message', function(e) {
return setTimeout(function() {
return term.write(e.data);
}, 1);
});
ws.addEventListener('close', function() {
console.log("WebSocket closed", arguments);
setTimeout(function() {
term.write('Closed');
term.skipNextKey = true;
return term.element.classList.add('dead');
}, 1);
quit = true;
if ((new Date()).getTime() - open_ts > 60 * 1000) {
return open('', '_self').close();
}
});
term = new Terminal($('#wrapper')[0], send, ctl);
addEventListener('beforeunload', function() {
if (!quit) {
return 'This will exit the terminal session';
}
});
bench = function(n) {
var rnd, t0;
if (n == null) {
n = 100000000;
}
rnd = '';
while (rnd.length < n) {
rnd += Math.random().toString(36).substring(2);
}
t0 = (new Date()).getTime();
term.write(rnd);
return console.log("" + n + " chars in " + ((new Date()).getTime() - t0) + " ms");
};
cbench = function(n) {
var rnd, t0;
if (n == null) {
n = 100000000;
}
rnd = '';
while (rnd.length < n) {
rnd += "\x1b[" + (30 + parseInt(Math.random() * 20)) + "m";
rnd += Math.random().toString(36).substring(2);
}
t0 = (new Date()).getTime();
term.write(rnd);
return console.log("" + n + " chars + colors in " + ((new Date()).getTime() - t0) + " ms");
};
window.butterfly = term;
window.Terminal = Terminal;
}).call(this);

File diff suppressed because one or more lines are too long

View File

@@ -17,5 +17,7 @@
<main id="wrapper"> </main>
<script src="{{ static_url('main.%sjs' % (
'' if options.unminified else 'min.')) }}"></script>
<script src="{{ static_url('ext.%sjs' % (
'' if options.unminified else 'min.')) }}"></script>
</body>
</html>

View File

@@ -37,15 +37,9 @@ def get_style():
if style is None:
return
if style.endswith('.sass'):
log.error('SASS syntax is not yet supported (see: '
'https://github.com/hcatlin/libsass/issues/16'
') please use SCSS')
return
if style.endswith('.scss'):
scss_path = os.path.join(
os.path.dirname(__file__), 'scss')
if style.endswith('.scss') or style.endswith('.sass'):
sass_path = os.path.join(
os.path.dirname(__file__), 'sass')
try:
import sass
except:
@@ -54,9 +48,11 @@ def get_style():
return
try:
return sass.compile(filename=style, include_paths=[scss_path])
return sass.compile(filename=style, include_paths=[sass_path])
except sass.CompileError:
log.error('Unable to compile style.scss', exc_info=True)
log.error(
'Unable to compile style.scss (filename: %s, paths: %r) ' % (
style, [sass_path]), exc_info=True)
return
with open(style) as s:

36
coffees/ext/alarm.coffee Normal file
View File

@@ -0,0 +1,36 @@
set_alarm = (notification) ->
alarm = (data) ->
butterfly.element.classList.remove 'alarm'
note = "New activity on butterfly terminal [#{ butterfly.title }]"
if notification
new Notification(
note,
body: data.data,
icon: '/static/images/favicon.png')
else
alert(note + '\n' + data.data)
butterfly.ws.removeEventListener 'message', alarm
butterfly.ws.addEventListener 'message', alarm
butterfly.element.classList.add 'alarm'
cancel = (ev) ->
ev.preventDefault() if ev.preventDefault
ev.stopPropagation() if ev.stopPropagation
ev.cancelBubble = true
false
document.addEventListener 'keydown', (e) ->
return true unless e.altKey and e.keyCode is 65
if Notification and Notification.permission is 'default'
Notification.requestPermission ->
set_alarm(Notification.permission is 'granted')
else
set_alarm(Notification.permission is 'granted')
cancel(e)

View File

@@ -16,6 +16,12 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
selection = null
cancel = (ev) ->
ev.preventDefault() if ev.preventDefault
ev.stopPropagation() if ev.stopPropagation
ev.cancelBubble = true
false
previous_leaf = (node) ->
previous = node.previousSibling
if not previous
@@ -38,7 +44,7 @@ next_leaf = (node) ->
class Selection
constructor: ->
term.element.classList.add('selection')
butterfly.element.classList.add('selection')
@selection = getSelection()
reset: ->
@@ -68,7 +74,7 @@ class Selection
@selection.removeAllRanges()
destroy: ->
term.element.classList.remove('selection')
butterfly.element.classList.remove('selection')
@clear()
text: ->
@@ -81,12 +87,12 @@ class Selection
@go +1
go: (n) ->
index = term.children.indexOf(@start_line) + n
return unless 0 <= index < term.children.length
index = butterfly.children.indexOf(@start_line) + n
return unless 0 <= index < butterfly.children.length
until term.children[index].textContent.match /\S/
until butterfly.children[index].textContent.match /\S/
index += n
return unless 0 <= index < term.children.length
return unless 0 <= index < butterfly.children.length
@select_line index
@@ -98,7 +104,7 @@ class Selection
@selection.addRange range
select_line: (index) ->
line = term.children[index]
line = butterfly.children[index]
line_start =
node: line.firstChild
offset: 0
@@ -170,7 +176,7 @@ document.addEventListener 'keydown', (e) ->
# Paste natural selection too if shiftkey
if e.shiftKey and e.keyCode is 13 and
not selection and not getSelection().isCollapsed
term.handler getSelection().toString()
butterfly.handler getSelection().toString()
getSelection().removeAllRanges()
return cancel e
@@ -200,7 +206,7 @@ document.addEventListener 'keydown', (e) ->
# Start selection mode with shift up
if not selection and e.ctrlKey and e.shiftKey and e.keyCode == 38
selection = new Selection()
selection.select_line term.y - 1
selection.select_line butterfly.y - 1
selection.apply()
return cancel e
true
@@ -210,7 +216,7 @@ document.addEventListener 'keyup', (e) ->
if selection
if e.keyCode == 13
term.handler selection.text()
butterfly.handler selection.text()
selection.destroy()
selection = null
return cancel e

View File

@@ -49,7 +49,7 @@ if /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i
alt = true
virtual_input.addEventListener 'keydown', (e) ->
term.keyDown(e)
butterfly.keyDown(e)
return true
virtual_input.addEventListener 'input', (e) ->
@@ -57,7 +57,7 @@ if /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i
if len == 0
e.keyCode = 8
term.keyDown e
butterfly.keyDown e
@value = '0'
return true
@@ -69,12 +69,12 @@ if /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i
e.altKey = alt
if e.keyCode >= 97 && e.keyCode <= 122
e.keyCode -= 32
term.keyDown e
butterfly.keyDown e
@value = '0'
ctrl = alt = false
return true
term.keyPress e
butterfly.keyPress e
first = false
@value = '0'
true

View File

@@ -21,6 +21,8 @@ open_ts = (new Date()).getTime()
$ = document.querySelectorAll.bind(document)
document.addEventListener 'DOMContentLoaded', ->
send = (data) ->
ws.send 'S' + data
@@ -88,5 +90,5 @@ cbench = (n=100000000) ->
term.write rnd
console.log "#{n} chars + colors in #{(new Date()).getTime() - t0} ms"
term.ws = ws
window.butterfly = term

View File

@@ -58,6 +58,7 @@ class Terminal
@element.className = 'terminal focus'
@element.style.outline = 'none'
@element.setAttribute 'tabindex', 0
@element.setAttribute 'spellcheck', 'false'
@parent.appendChild(@element)
@@ -3011,3 +3012,5 @@ class Terminal
Swedish: null # (H or (7
Swiss: null # (=
ISOLatin: null # /A
window.Terminal = Terminal

View File

@@ -27,7 +27,7 @@ options = dict(
install_requires=["tornado>=3.2", "pyOpenSSL", 'tornado_systemd'],
package_data={
'butterfly': [
'scss/*.scss',
'sass/*.sass',
'static/fonts/*',
'static/images/favicon.png',
'static/main.css',