diff --git a/README.md b/README.md index 2fbd1d64..decada8e 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,34 @@ A terminal written in javascript. Used by [tty.js](https://github.com/chjj/tty.js). +## Example + +Server: + +``` js +var term = require('term.js'); +express.use(term.middleware()); +``` + +Client: + +``` js +window.addEventListener('load', function() { + var term = new Terminal({ + cols: 80, + rows: 24 + }); + + term.open(document.body); + + term.on('data', function(data) { + console.log(data); + }); + + term.write('\x1b[41hello world\x1b[m'); +}, false); +``` + ## License Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) diff --git a/index.js b/index.js index 4aad8798..bf4d18ac 100644 --- a/index.js +++ b/index.js @@ -1 +1 @@ -module.exports = require('./lib/term.js'); +module.exports = require('./lib/index.js'); diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 00000000..f837da1f --- /dev/null +++ b/lib/index.js @@ -0,0 +1,50 @@ +/** + * term.js - an xterm emulator + * Copyright (c) 2013, Christopher Jeffrey (https://github.com/chjj/term.js) + */ + +function term(options) { + return new term.Terminal(options); +} + +term.middleware = function(options) { + var url = require('url'); + return function(req, res, next) { + if (url.parse(req.url).pathname !== '/term.js') { + return next(); + } + + if (+new Date(req.headers['if-modified-since']) === term.last) { + res.statusCode = 304; + res.end(); + return; + } + + res.writeHead(200, { + 'Content-Type': 'application/javascript; charset=utf-8', + 'Content-Length': Buffer.byteLength(term.script), + 'Last-Modified': term.last + }); + + res.end(term.script); + }; +}; + +term.path = __dirname + '/term.js'; + +term.__defineGetter__('script', function() { + if (term._script) return term._script; + term.last = +new Date; + return term._script = require('fs').readFileSync(term.path, 'utf8'); +}); + +term.__defineGetter__('Terminal', function() { + if (term._Terminal) return term._Terminal; + return term._Terminal = require('./term'); +}); + +/** + * Expose + */ + +module.exports = term; diff --git a/lib/term.js b/lib/term.js index 69cd6a54..6cd07ebd 100644 --- a/lib/term.js +++ b/lib/term.js @@ -1,7 +1,7 @@ /** - * tty.js - an xterm emulator + * term.js - an xterm emulator * - * Copyright (c) 2012-2013, Christopher Jeffrey (https://github.com/chjj/tty.js) + * Copyright (c) 2012-2013, Christopher Jeffrey (https://github.com/chjj/term.js) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -131,23 +131,32 @@ var normal = 0 * Terminal */ -function Terminal(cols, rows, handler) { +function Terminal(options) { + if (!(this instanceof Terminal)) { + return new Terminal(arguments[0], arguments[1], arguments[2]); + } + EventEmitter.call(this); - var options; - if (typeof cols === 'object') { - options = cols; - cols = options.cols; - rows = options.rows; - handler = options.handler; + if (arguments.length > 1) { + options = { + cols: arguments[0], + rows: arguments[1], + handler: arguments[2] + }; } + this._options = options || {}; - this.cols = cols || Terminal.geometry[0]; - this.rows = rows || Terminal.geometry[1]; + this.context = options.context || window; + this.document = options.document || document; + this.parent = options.body || options.parent || document.body; - if (handler) { - this.on('data', handler); + this.cols = options.cols || Terminal.geometry[0]; + this.rows = options.rows || Terminal.geometry[1]; + + if (options.handler) { + this.on('data', options.handler); } this.ybase = 0; @@ -331,7 +340,7 @@ Terminal.prototype.focus = function() { * Global Events for key handling */ -Terminal.bindKeys = function() { +Terminal.bindKeys = function(document) { if (Terminal.focus) return; // We could put an "if (Terminal.focus)" check @@ -349,26 +358,29 @@ Terminal.bindKeys = function() { * Open Terminal */ -Terminal.prototype.open = function() { +Terminal.prototype.open = function(parent) { var self = this , i = 0 , div; - this.element = document.createElement('div'); + this.parent = parent || this.parent || document.body; + + this.element = this.document.createElement('div'); this.element.className = 'terminal'; this.children = []; for (; i < this.rows; i++) { - div = document.createElement('div'); + div = this.document.createElement('div'); this.element.appendChild(div); this.children.push(div); } - document.body.appendChild(this.element); + this.parent.appendChild(this.element); this.refresh(0, this.rows - 1); - Terminal.bindKeys(); + Terminal.bindKeys(this.document); + this.focus(); this.startBlink(); @@ -403,8 +415,8 @@ Terminal.prototype.open = function() { on(this.element, 'paste', function(ev) { if (ev.clipboardData) { self.send(ev.clipboardData.getData('text/plain')); - } else if (window.clipboardData) { - self.send(window.clipboardData.getData('Text')); + } else if (self.context.clipboardData) { + self.send(self.context.clipboardData.getData('Text')); } // Not necessary. Do it anyway for good measure. self.element.contentEditable = 'inherit'; @@ -415,7 +427,7 @@ Terminal.prototype.open = function() { // XXX - hack, move this somewhere else. if (Terminal.brokenBold == null) { - Terminal.brokenBold = isBoldBroken(); + Terminal.brokenBold = isBoldBroken(this.document); } // sync default bg/fg colors @@ -438,7 +450,7 @@ Terminal.prototype.bindMouse = function() { , self = this , pressed = 32; - var wheelEvent = 'onmousewheel' in window + var wheelEvent = 'onmousewheel' in this.context ? 'mousewheel' : 'DOMMouseScroll'; @@ -669,7 +681,7 @@ Terminal.prototype.bindMouse = function() { // should probably check offsetParent // but this is more portable - while (el !== document.documentElement) { + while (el !== self.document.documentElement) { x -= el.offsetLeft; y -= el.offsetTop; el = el.parentNode; @@ -719,14 +731,14 @@ Terminal.prototype.bindMouse = function() { } // bind events - if (self.normalMouse) on(document, 'mousemove', sendMove); + if (self.normalMouse) on(self.document, 'mousemove', sendMove); // x10 compatibility mode can't send button releases if (!self.x10Mouse) { - on(document, 'mouseup', function up(ev) { + on(self.document, 'mouseup', function up(ev) { sendButton(ev); - if (self.normalMouse) off(document, 'mousemove', sendMove); - off(document, 'mouseup', up); + if (self.normalMouse) off(self.document, 'mousemove', sendMove); + off(self.document, 'mouseup', up); return cancel(ev); }); } @@ -2247,16 +2259,16 @@ Terminal.prototype.bell = function() { Terminal.prototype.log = function() { if (!Terminal.debug) return; - if (!window.console || !window.console.log) return; + if (!this.context.console || !this.context.console.log) return; var args = Array.prototype.slice.call(arguments); - window.console.log.apply(window.console, args); + this.context.console.log.apply(this.context.console, args); }; Terminal.prototype.error = function() { if (!Terminal.debug) return; - if (!window.console || !window.console.error) return; + if (!this.context.console || !this.context.console.error) return; var args = Array.prototype.slice.call(arguments); - window.console.error.apply(window.console, args); + this.context.console.error.apply(this.context.console, args); }; Terminal.prototype.resize = function(x, y) { @@ -2299,7 +2311,7 @@ Terminal.prototype.resize = function(x, y) { this.lines.push(this.blankLine()); } if (this.children.length < y) { - line = document.createElement('div'); + line = this.document.createElement('div'); el.appendChild(line); this.children.push(line); } @@ -4204,7 +4216,7 @@ var isMac = ~navigator.userAgent.indexOf('Mac'); // if bold is broken, we can't // use it in the terminal. -function isBoldBroken() { +function isBoldBroken(document) { var el = document.createElement('span'); el.innerHTML = 'hello world'; document.body.appendChild(el);