diff --git a/.gitignore b/.gitignore index 6bd26a55..9e7313e8 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,8 @@ logs logs/* config.*.json public/js/jsbin*.js -public/js/prod/* .nodemonignore +public/js/prod !config.default.json statsd.js .allow-devtools-edit diff --git a/Gruntfile.js b/Gruntfile.js index b667408b..2b9a5c9a 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,3 +1,4 @@ +'use strict'; /*global module:false*/ module.exports = function (grunt) { var fs = require('fs'), @@ -32,7 +33,7 @@ module.exports = function (grunt) { } } - child = exec(cmd, function (err, stdout, stderr) { + child = exec(cmd, function (err) { if (err) { grunt.log.writeln(err.message); process.exit(err.code); @@ -49,7 +50,7 @@ module.exports = function (grunt) { var distpaths = { script: 'public/js/prod/<%= pkg.name %>-<%= pkg.version %>.js', - map: 'public/js/prod/<%= pkg.name %>.map.json', // don't version this so we overwrite + // map: 'public/js/prod/<%= pkg.name %>.map.json', // don't version this so we overwrite min: 'public/js/prod/<%= pkg.name %>-<%= pkg.version %>.min.js', runner: 'public/js/prod/runner-<%= pkg.version %>.js', runnermin: 'public/js/prod/runner-<%= pkg.version %>.min.js' @@ -96,9 +97,9 @@ module.exports = function (grunt) { }, dist: { src: [ - 'public/js/intro.js', + 'public/js/intro-start.js', '<%= scriptsRelative %>', - 'public/js/outro.js' + 'public/js/outro-start.js' ], dest: distpaths.script }, @@ -119,12 +120,12 @@ module.exports = function (grunt) { options: { banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' + '<%= grunt.template.today("yyyy-mm-dd") %> */\n', - sourceMap: distpaths.map, - sourceMappingURL: '/js/prod/jsbin.map.json', - sourceMapPrefix: 2, - sourceMapRoot: '/js', + // sourceMap: distpaths.map, + // sourceMappingURL: '/js/prod/jsbin.map.json', + // sourceMapPrefix: 2, + // sourceMapRoot: '/js', }, - src: '<%= scriptsRelative %>', + src: distpaths.script, //'<%= scriptsRelative %>', dest: distpaths.min }, runner: { diff --git a/README.markdown b/README.markdown index 401231e9..5a86ca77 100644 --- a/README.markdown +++ b/README.markdown @@ -4,7 +4,7 @@ JS Bin is an open source collaborative web development debugging tool. ## If you use JS Bin locally... -It likely means you're not going to subscribe a pro user - which is how we're sustaining our project, which is cool, but [please consider donating via gittip here](https://www.gittip.com/js_bin/). +It likely means you're not going to subscribe as a pro user - which is how we're sustaining our project, which is cool, but [please consider donating via gittip here](https://www.gittip.com/js_bin/). ## What can JS Bin do? diff --git a/build/full-db-v3.mysql.sql b/build/full-db-v3.mysql.sql index d52b6e3f..9ab7ca49 100644 --- a/build/full-db-v3.mysql.sql +++ b/build/full-db-v3.mysql.sql @@ -1,8 +1,8 @@ --- MySQL dump 10.13 Distrib 5.5.9, for osx10.6 (i386) +-- MySQL dump 10.13 Distrib 5.5.29, for debian-linux-gnu (x86_64) -- -- Host: localhost Database: jsbin -- ------------------------------------------------------ --- Server version 5.5.9 +-- Server version 5.5.29-0ubuntu0.12.04.1 /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; @@ -15,11 +15,31 @@ /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; +-- +-- Table structure for table `customers` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `customers` ( + `stripe_id` char(255) NOT NULL, + `id` int(11) NOT NULL AUTO_INCREMENT, + `user_id` int(11) DEFAULT NULL, + `name` char(255) NOT NULL, + `expiry` datetime DEFAULT NULL, + `active` tinyint(1) DEFAULT '1', + PRIMARY KEY (`id`), + KEY `stripe_id` (`stripe_id`), + KEY `name` (`name`), + KEY `user_id` (`user_id`), + KEY `expired` (`expiry`,`active`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +/*!40101 SET character_set_client = @saved_cs_client */; + -- -- Table structure for table `forgot_tokens` -- -DROP TABLE IF EXISTS `forgot_tokens`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `forgot_tokens` ( @@ -32,16 +52,34 @@ CREATE TABLE `forgot_tokens` ( ) ENGINE=MyISAM DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; +-- +-- Table structure for table `owner_bookmarks` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `owner_bookmarks` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` char(255) NOT NULL, + `url` char(255) NOT NULL, + `revision` int(11) NOT NULL, + `type` char(50) NOT NULL, + `created` datetime NOT NULL, + PRIMARY KEY (`id`), + KEY `name` (`name`,`type`,`created`), + KEY `revision` (`url`(191),`revision`) +) ENGINE=InnoDB CHARSET=utf8; +/*!40101 SET character_set_client = @saved_cs_client */; + -- -- Table structure for table `owners` -- -DROP TABLE IF EXISTS `owners`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `owners` ( `id` int(11) NOT NULL AUTO_INCREMENT, - `name` char(255) NOT NULL, + `name` char(75) NOT NULL, `url` char(255) NOT NULL, `revision` int(11) DEFAULT '1', `last_updated` datetime NOT NULL, @@ -50,41 +88,47 @@ CREATE TABLE `owners` ( `css` tinyint(1) NOT NULL DEFAULT '0', `javascript` tinyint(1) NOT NULL DEFAULT '0', `archive` tinyint(1) NOT NULL DEFAULT '0', - `visibility` ENUM('public', 'unlisted', 'private') DEFAULT 'public' NOT NULL, + `visibility` enum('public','unlisted','private') NOT NULL DEFAULT 'public', PRIMARY KEY (`id`), KEY `name_url` (`name`,`url`,`revision`), - KEY `last_updated` (`name`,`last_updated`) -) ENGINE=InnoDB AUTO_INCREMENT=289 DEFAULT CHARSET=utf8; + KEY `last_updated` (`name`,`last_updated`), + KEY `url` (`url`,`revision`) +) ENGINE=InnoDB CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Table structure for table `ownership` -- -DROP TABLE IF EXISTS `ownership`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `ownership` ( - `name` char(255) NOT NULL, + `name` char(75) NOT NULL, `key` char(255) NOT NULL, `email` varchar(255) NOT NULL DEFAULT '', - `api_key` VARCHAR(255) NULL, - `github_token` VARCHAR(255), - `github_id` int(11), `last_login` datetime NOT NULL, `created` datetime NOT NULL, `updated` datetime NOT NULL, - PRIMARY KEY (`name`), + `api_key` varchar(255) DEFAULT NULL, + `github_token` varchar(255) DEFAULT NULL, + `github_id` int(11) DEFAULT NULL, + `verified` tinyint(1) NOT NULL DEFAULT '0', + `pro` tinyint(1) NOT NULL DEFAULT '0', + `id` int(11) NOT NULL AUTO_INCREMENT, + `settings` text, + `dropbox_token` varchar(255) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`), KEY `name_key` (`name`,`key`), + KEY `created` (`created`), KEY `ownership_api_key` (`api_key`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; +) ENGINE=InnoDB CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Table structure for table `sandbox` -- -DROP TABLE IF EXISTS `sandbox`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `sandbox` ( @@ -110,7 +154,7 @@ CREATE TABLE `sandbox` ( KEY `streaming_key` (`streaming_key`), KEY `spam` (`created`,`last_viewed`), KEY `revision` (`url`(191),`revision`) -) ENGINE=InnoDB AUTO_INCREMENT=1585463 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +) ENGINE=InnoDB CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; /*!40101 SET character_set_client = @saved_cs_client */; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; @@ -122,4 +166,4 @@ CREATE TABLE `sandbox` ( /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; --- Dump completed on 2013-01-11 9:26:38 \ No newline at end of file +-- Dump completed on 2014-06-05 7:45:41 diff --git a/config.default.json b/config.default.json index 6961d66e..2fcf3329 100644 --- a/config.default.json +++ b/config.default.json @@ -43,6 +43,9 @@ "server": { "logger": "default" }, + "security": { + "allowAnonymousPreview": false + }, "client": { "user": true }, @@ -96,6 +99,8 @@ "devices", "download", "downloads", + "edit", + "embed", "faq", "favorites", "favs", @@ -152,8 +157,10 @@ "translate", "trends", "unarchive", + "upgrade", "user", "users", + "watch", "widgets", "tutorials", "video", diff --git a/lib/addons/memcached/index.js b/lib/addons/memcached/index.js index 86a75d3a..3fb25004 100644 --- a/lib/addons/memcached/index.js +++ b/lib/addons/memcached/index.js @@ -75,19 +75,27 @@ module.exports = function(app, connection) { next(); }); } else { - return next(); + next(); } // We're basically yaking the same idea that connects cookieSessions use // from here: http://www.senchalabs.org/connect/cookieSession.html res.on('header', function () { - var oldSessionUser = undefsafe(req, '_sessionBeforeBlacklist.user'); - if (oldSessionUser) { - // FIXME - this is hacky - if (JSON.stringify(oldSessionUser) !== req._userJSONCopy) { - memcached.set(oldSessionUser.name, oldSessionUser); + var sessionUser = undefsafe(req, '_sessionBeforeBlacklist.user'); + // If there's no user on the session, there's nothing to save + if (!sessionUser) { + return; + } + // If we have a copy of the user on the "way in" and it's the + // same as the user on the session now, there's no need to save + if (req._userJSONCopy) { + // FIXME - this is hacky (use a compare/equals function?) + if (JSON.stringify(sessionUser) === req._userJSONCopy) { + return; } } + // For everything else, we save them to memcache + memcached.set(sessionUser.name, sessionUser); }); }); diff --git a/lib/addons/memcached/store.js b/lib/addons/memcached/store.js index c2f44376..783352c9 100644 --- a/lib/addons/memcached/store.js +++ b/lib/addons/memcached/store.js @@ -1,11 +1,20 @@ 'use strict'; -var Memcached = require('memcached'), - replify = require('replify'), - Promise = require('rsvp').Promise; +var net = require('net'); +var Memcached = require('memcached'); +var replify = require('replify'); +var Promise = require('rsvp').Promise; +var testForMemcachedServer = function (connection) { + (new net.Socket()) + .connect(connection.split(':')[1]) + .on('error', console.log.bind(console, 'Couldn\'t connect to memcached server - is it running?')) + .destroy(); +}; module.exports = function(connection) { + testForMemcachedServer(connection); + var memcachedstore = new Memcached(connection); var API = { diff --git a/lib/app.js b/lib/app.js index 9a11b9cf..69cf4807 100644 --- a/lib/app.js +++ b/lib/app.js @@ -218,7 +218,7 @@ app.connect = function (callback) { app.emit('before:cookies', { app: app }); app.use(express.cookieParser(app.get('session secret') || generateSessionSecret())); - app.use(filteredCookieSession(['user.settings', 'passport', 'user.email', 'user.github_id', 'user.github_token', 'user.bincount', 'user.api_key', 'flashCache', ])); + app.use(filteredCookieSession(['user.settings', 'passport', 'user.email', 'user.github_id', 'user.github_token', 'user.bincount', 'user.api_key'])); app.use(express.cookieSession({ key: 'jsbin', cookie: { @@ -259,6 +259,7 @@ app.connect = function (callback) { app.use(middleware.cors()); app.use(middleware.jsonp()); app.use(middleware.protocolCheck); + app.use(middleware.decompressBody); app.use(middleware.notices()); app.use(mount, app.router); diff --git a/lib/blacklist.js b/lib/blacklist.js index 2f8c26b9..25eebe91 100644 --- a/lib/blacklist.js +++ b/lib/blacklist.js @@ -1,19 +1,36 @@ +'use strict'; var blacklist = require('./config').blacklist || {}; module.exports.validate = function (bin) { - var type, keywords, content, index, length; + var okay = true; - for (type in blacklist) { - if (blacklist.hasOwnProperty(type)) { - content = bin[type] || ''; - keywords = blacklist[type] || []; + ['html', 'javascript', 'css'].forEach(function (type) { + var content = bin[type] || ''; + var keywords = blacklist[type] || []; - for (index = 0, length = keywords.length; index < length; index += 1) { - if (content.indexOf(keywords[index]) > -1) { - return false; - } - } + if (okay) { // then keep checking + okay = keywords.filter(function (keyword) { + // return if found + return content.indexOf(keyword) !== -1; + }).length === 0; } + }); + + // now test regexp + if (blacklist.re) { + ['html', 'javascript', 'css'].forEach(function (type) { + var content = bin[type] || ''; + + if (okay) { // then keep checking + okay = blacklist.re.filter(function (re) { + // convert the string regexp to a real reggie expession. + var reg = new RegExp(re); + // return if found + return content.match(reg) !== null; + }).length === 0; + } + }); } - return true; + + return okay; }; diff --git a/lib/data/backers.json b/lib/data/backers.json new file mode 100644 index 00000000..f078bc7f --- /dev/null +++ b/lib/data/backers.json @@ -0,0 +1,7 @@ +[ + { + "name": "Librato", + "url": "http://metrics.librato.com", + "image": "http://data.bigdatastartups.netdna-cdn.com/wp-content/uploads/2013/08/Librato-logo.jpg" + } +] diff --git a/lib/data/features.json b/lib/data/features.json new file mode 100644 index 00000000..2d2db869 --- /dev/null +++ b/lib/data/features.json @@ -0,0 +1,50 @@ +[ + { + "name": "Vanity URLs", + "user": "pro" + }, + { + "name": "Private Bins", + "user": "pro" + }, + { + "name": "Dropbox Sync", + "user": "pro" + }, + { + "name": "Read & write API access", + "user": "pro" + }, + { + "name": "Direct support", + "user": "pro" + }, + { + "name": "Asset hosting (coming soon)", + "user": "pro" + }, + { + "name": "Save bins to Gist", + "user": "free" + }, + { + "name": "Codecasting (unlimited connections)", + "user": "anon" + }, + { + "name": "Live reload (unlimited connections)", + "user": "anon" + }, + { + "name": "Custom starting code", + "user": "anon" + }, + { + "name": "Unlimited external resources", + "user": "anon" + }, + { + "name": "Custom libraries", + "user": "anon" + } +] diff --git a/lib/data/welcome-panel.json b/lib/data/welcome-panel.json new file mode 100644 index 00000000..9fa3aa84 --- /dev/null +++ b/lib/data/welcome-panel.json @@ -0,0 +1,81 @@ +{ + "features": { + "title": "JS Bin features", + "link": "http://jsbin.com/help/features", + "data": [ + { + "title": "Getting started with JS Bin", + "link": "http://jsbin.com/help/getting-started" + }, + { + "title": "Keyboard Shortcuts", + "link": "http://jsbin.com/help/keyboard-shortcuts" + }, + { + "title": "Test out code on other devices", + "link": "http://jsbin.com/help/test-code-on-other-devices" + }, + { + "title": "Exporting/importing gist", + "link": "http://jsbin.com/help/export-gist" + } + ] + }, + "pro-features": { + "title": "Pro features", + "link": "http://jsbin.com/help/features", + "data": [ + { + "title": "Sync with Dropbox", + "link": "http://jsbin.com/help/features" + }, + { + "title": "Private bins", + "link": "http://jsbin.com/help/features" + }, + { + "title": "Vanity URLs", + "link": "http://jsbin.com/help/features" + }, + { + "title": "Read & write API", + "link": "http://jsbin.com/help/features" + } + ] + }, + "blog": { + "title": "Blog", + "link": "http://jsbin.com/blog", + "data": [ + { + "title": "JSHint, line highlighting and more", + "link": "http://jsbin.com/blog/twdtw-10-jshint-highlighting-pro" + } + ] + }, + "help": { + "title": "Help", + "link": "http://jsbin.com/help", + "data": [ + { + "title": "Versions: processors & more", + "link": "http://jsbin.com/help/versions" + }, + { + "title": "Delete a bin", + "link": "http://jsbin.com/help/delete-a-bin" + } + ] + }, + "twitter": { + "title": "Recently on Twitter", + "link": "http://twitter.com/js_bin", + "data": [ + { + "title": "We’re expecting to hit our 10 __millionth__ bin in around a week’s time. Wow.", + "link": "https://twitter.com/js_bin/status/479668198384865281" + } + ] + }, + "quote": "“Everyone should learn how to program a computer because it teaches you how to think” — Steve Jobs" +} \ No newline at end of file diff --git a/lib/dropbox/index.js b/lib/dropbox/index.js index 1eb89c8a..f23759bc 100644 --- a/lib/dropbox/index.js +++ b/lib/dropbox/index.js @@ -17,7 +17,9 @@ module.exports = function (options) { return !!zmq ? { initialize: function () { socket.bind(options.dropbox.port, function(err) { - console.error(err); + if (err) { + console.error(err); + } }); passport.use(new DropboxOAuth2Strategy({ clientID: options.dropbox.id, diff --git a/lib/features.js b/lib/features.js index 265bccbb..0c9ce395 100644 --- a/lib/features.js +++ b/lib/features.js @@ -48,7 +48,7 @@ function team(req) { } function pro(req) { - return flags.alpha(req) || undefsafe(req, 'session.user.pro'); + return alpha(req) || undefsafe(req, 'session.user.pro'); } /* End: user types */ @@ -67,23 +67,22 @@ function percentage(n, req) { var flags = { /* Begin: actual features */ + admin: team, + + pro: pro, github: function () { return options.github && options.github.id; }, // private bins - private: function (req) { - return alpha(req); // pro - }, + private: pro, // live June 16, 2014-05-27 // whether user can delete bins delete: true, // live 25 Feb 2014 // allows for sandbox play in a bin without actually saving - sandbox: function (req) { - return alpha(req); // pro - }, + sandbox: pro, // live June 16, 2014-05-27 // info/hover card with details of bin and streaming info infocard: function (req) { @@ -91,9 +90,7 @@ var flags = { }, // seperate account management pages - accountPages: function (req) { - return alpha(req) || percentage(10, req); - }, + accountPages: true, // live 2014-05-27 // use SSL for sign in sslLogin: true, @@ -101,38 +98,28 @@ var flags = { // using memcache for sessions serverSession: true, - // code-analysis engine for javascript panel - tern: function (req) { - return alpha(req); - }, + vanity: pro, // live June 16, 2014-05-27 - stripe: function (req) { - return team(req); - }, + dropbox: pro, // live June 20, 2014 - vanity: function (req) { - return alpha(req); // return pro(req); - }, - - dropbox: function (req) { - return alpha(req); - }, - - assets: function (req) { - return alpha(req); - }, + assets: false, // disabled June 20, 2014 revisionless: function (req) { return team(req); }, - fileMenuTest: function (req) { - return team(req) || percentage(50, req); + // allows the user to use jsbin entirely through SSL + sslForAll: function (req) { + return alpha(req) && undefsafe(req, 'session.user.settings.ssl'); }, - upgrade: function (req) { - return team(req); // return !pro(req); - }, + fileMenuTest: true, // live 2014-05-27 - #1414 + + // ability to upgrade to a pro user + upgrade: team, + + // top introduction view with help and features of JS Bin + welcomePanel: alpha, }; var features = module.exports = new Features(flags); diff --git a/lib/handlers/bin.js b/lib/handlers/bin.js index 769b3dc0..e79dc71c 100644 --- a/lib/handlers/bin.js +++ b/lib/handlers/bin.js @@ -10,7 +10,9 @@ var async = require('asyncjs'), undefsafe = require('undefsafe'), metrics = require('../metrics'), features = require('../features'), + config = require('../config'), Promise = require('rsvp').Promise, + welcomePanel = require('../welcome-panel'), Observable = utils.Observable; module.exports = Observable.extend({ @@ -76,6 +78,8 @@ module.exports = Observable.extend({ data.settings.processors = processorSettings; } + data.post = true; + this.render(req, res, data); }, getCustom: function (req, res, next) { @@ -146,6 +150,40 @@ module.exports = Observable.extend({ req.embed = true; next(); }, + testPreviewAllowed: function (req, res, next) { + /** + * if the bin does not have a user who create it + * and it was made 2 hours ago + * then redirect to the /edit url + */ + var user = undefsafe(req, 'bin.metadata.name'); + if (config.security.allowAnonymousPreview !== true && user === 'anonymous' || !user) { + var created = req.bin.created; + + // this is hard coded for production jsbin.com - it means that all bins + // created before we released this change won't be affected by the limit + if (config.url.host === 'jsbin.com') { + if (req.bin.id < 10786492) { + return next(); + } + } + + // test the time created, and if it's older than 90 minutes, then redirect + // to the edit view. Details as to why on our blog + if ((Date.now() - created.getTime()) / 1000 / 60 > 90) { + var msg = 'This bin was created anonymously and its free preview time has expired.'; + + if (!req.session.user) { + msg += ' Get a free unrestricted account'; + } else { + msg += ' Clone it create to enable the full preview'; + } + res.flash(req.flash.NOTIFICATION, msg); + return res.redirect(this.helpers.urlForBin(req.bin) + '/edit'); + } + } + next(); + }, getBinPreview: function (req, res, next) { // if we're loading a username/last(-:n)? and no 'n' is present // it breaks quiet, leaving it as undefined, here we manually @@ -185,7 +223,7 @@ module.exports = Observable.extend({ resolve(); } }).then(function () { - this.formatPreview(req.bin, options, function (err, formatted) { + this.formatPreview(req, req.bin, options, function (err, formatted) { if (err) { next(err); } @@ -215,7 +253,7 @@ module.exports = Observable.extend({ output = 'var template = ' + output; } res.send(output); - }); + }.bind(this)); }, getBinSourceFile: function (req, res) { this.protectVisibility(req.session.user, req.bin, function(err, bin){ @@ -520,7 +558,7 @@ module.exports = Observable.extend({ // Notify interested parties (spike) that a new revision was created var oldBin = req.bin; handler.once('created', function (newBin) { - handler.emit('new-revision', oldBin, newBin); + handler.emit('new-revision', bin, newBin); }); handler.completeCreateBin(result, req, res, next); @@ -636,7 +674,7 @@ module.exports = Observable.extend({ var filename = ['jsbin', bin.url, bin.revision, 'html'].join('.'), options = {analytics: false, edit: false, silent: true}; - this.formatPreview(bin, options, function (err, formatted) { + this.formatPreview(req, bin, options, function (err, formatted) { if (err) { next(err); } @@ -703,7 +741,7 @@ module.exports = Observable.extend({ _this.render(req, res, results); } else { var options = {edit: true, silent: true, csrf: req.session._csrf}; - _this.formatPreview(results, options, function (err, formatted) { + _this.formatPreview(req, results, options, function (err, formatted) { if (err) { next(err); } @@ -796,7 +834,9 @@ module.exports = Observable.extend({ helpers = this.helpers, version = helpers.set('version'), created = req.flash('checksum') || {}, - root = helpers.url('', true), + sslForAll = features('sslForAll', req), + ssl = req.embed ? req.secure && sslForAll : sslForAll, + root = helpers.url('', true, ssl), _this = this, production = (req.cookies && req.cookies.debug) ? false : helpers.production, jsbin; @@ -808,6 +848,8 @@ module.exports = Observable.extend({ root = root.replace('://', '://' + req.subdomain + '.'); } + var statik = helpers.urlForStatic(undefined, ssl); + jsbin = this.jsbin(bin, { version: version, token: req.session._csrf, @@ -815,7 +857,7 @@ module.exports = Observable.extend({ shareRoot: features('vanity', req) ? 'http://' + req.session.user.name + '.' + req.app.get('url host') : root, metadata: bin.metadata, runner: helpers.runner, - static: helpers.urlForStatic(), + static: statik, settings: !bin.url ? config && config.settings : {}, // If we've pulled a just created bin out of the flash messages object // then we check to see if the previously created bin is the one we're @@ -873,6 +915,14 @@ module.exports = Observable.extend({ } } + var embedURL = null; + if (req.bin) { + embedURL = helpers.editUrlForBin(req.bin, true).replace(/\/edit/, '/embed'); + if (req.secure) { + embedURL = embedURL.replace(/http:/, 'https:'); + } + } + if (info) {tipType = 'info';} if (notification) {tipType = 'notification';} if (error) {tipType = 'error';} @@ -903,20 +953,25 @@ module.exports = Observable.extend({ isProduction: production, concat: req.cookies && req.cookies.debug ? req.cookies.debug === 'concat' : false, root: root, - static: helpers.urlForStatic(), + static: statik, bincount: user.bincount, url: url, + embedURL: embedURL, vanity: features('vanity', req) ? 'http://' + req.session.user.name + '.' + req.app.get('url host') : root, live: req.live, embed: req.embed, code_id: bin.url, code_id_path: url, - code_id_domain: helpers.urlForBin(bin, true).replace(/^https?:\/\//, '') + code_id_domain: helpers.urlForBin(bin, true).replace(/^https?:\/\//, ''), + welcomePanel: welcomePanel.getData() }); }; if (user) { _this.models.user.getBinCount(user.name, function (err, result) { + if (err || !result) { + return done(err); + } user.bincount = result.total; done(); }); @@ -1038,11 +1093,20 @@ module.exports = Observable.extend({ // this value isn't always present in anonymous metadata options.metadata.last_updated = bin.created; + var statik = options.static || options.root; + var runner = this.helpers.runner; + if (statik.indexOf('https') === 0) { + // then ensure the runner is also https + if (runner.indexOf('https') === -1) { + runner = runner.replace(/http/, 'https'); + } + } + return { root: options.root, shareRoot: options.shareRoot, - runner: this.helpers.runner, - static: options.static || options.root, + runner: runner, + static: statik, version: options.version, state: { token: options.token, @@ -1065,6 +1129,10 @@ module.exports = Observable.extend({ template[panel] = utils.cleanForRender(template[panel] || ''); }); + if (bin.post) { + template.post = bin.post; + } + template.url = this.helpers.jsbinURL(bin); //.permalink; return template; }, @@ -1107,7 +1175,7 @@ module.exports = Observable.extend({ // nothing returned as it updates the bin object }, - formatPreview: function (bin, options, fn) { + formatPreview: function (req, bin, options, fn) { metrics.increment('bin.rendered'); this.applyProcessors(bin); @@ -1135,8 +1203,8 @@ module.exports = Observable.extend({ // Include 'Edit in JS Bin' button if (options.edit) { - var data = {static: helpers.urlForStatic(''), root: helpers.url('/', true), csrf: options.csrf}; - insert.push(''); + var data = {static: helpers.urlForStatic('', req.secure), root: helpers.url('/', true, req.secure), csrf: options.csrf}; + insert.push(''); insert.push(''); } @@ -1145,7 +1213,7 @@ module.exports = Observable.extend({ if (!options.silent && _this.models.bin.isStreaming(bin)) { // jshint ignore:line _this.emit('render-scripts', scripts); insert = insert.concat(scripts.map(function (script) { - script = script.indexOf('http') === 0 ? script : helpers.urlForStatic(script); + script = script.indexOf('http') === 0 ? script : helpers.urlForStatic(script, req.secure); return ''; })); } @@ -1193,14 +1261,26 @@ module.exports = Observable.extend({ context = { domain: helpers.set('url host'), - permalink: helpers.editUrlForBin(bin, true) + permalink: helpers.editUrlForBin(bin, true), + user: undefsafe(bin, 'metadata.name') || false, + year: (new Date()).getYear() + 1900 }; // Append attribution comment to header. helpers.render('comment', context, function (err, comment) { - formatted = formatted.replace(/]*>/, function ($0) { + var done = false; + formatted = formatted.replace(/]*>/, function ($0) { + if ($0) { + done = true; + } return $0 + '\n' + (comment || '').trim(); }); + + if (!done) { + formatted = formatted.replace(/]*>/, function ($0) { + return $0 + '\n' + (comment || '').trim(); + }); + } return fn(err || null, err ? undefined : formatted); }); } @@ -1335,12 +1415,15 @@ module.exports = Observable.extend({ var owner = undefsafe(req, 'bin.metadata.name'); var streamingKey = undefsafe(req, 'bin.streaming_key'); - // if the user doesn't own this bin...and - if (user !== owner) { - // the checksum doesn't match the key in the database - if (req.body.checksum !== streamingKey) { - // then it's not theirs to delete - return res.send(403, {error: 'Not authorised.'}); + // Only check if they're not admin + if (features('admin', req) === false) { + // if the user doesn't own this bin...and + if (user !== owner) { + // the checksum doesn't match the key in the database + if (req.body.checksum !== streamingKey) { + // then it's not theirs to delete + return res.send(403, {error: 'Not authorised.'}); + } } } diff --git a/lib/handlers/session.js b/lib/handlers/session.js index 49ac20df..c1824b81 100644 --- a/lib/handlers/session.js +++ b/lib/handlers/session.js @@ -523,7 +523,7 @@ module.exports = Observable.extend({ // Was a name matching user found if (jsbinUserFromGithubName) { // Uh oh, that user exists already - res.flash(res.flash.ERROR, 'Your GitHub username is already used in JS Bin. If this is you, please sign into JS Bin and "link your GitHub account" otherwise, register, then link your account.'); + res.flash(res.flash.ERROR, 'Your GitHub username is taken on JS Bin. If this is you, please register with JS Bin and link your GitHub account. Read this for more help.'); return res.redirect(redirect); } // No matching user was found, create an account! diff --git a/lib/hbs.js b/lib/hbs.js index 77d7a498..798ca374 100644 --- a/lib/hbs.js +++ b/lib/hbs.js @@ -13,8 +13,21 @@ hbs.registerHelper('feature', function(request, flag, options) { } }); +hbs.registerHelper('equal', function(lvalue, rvalue, options) { + if (arguments.length < 3) { + return false; + } + if (lvalue !== rvalue) { + return options.inverse(this); + } else { + return options.fn(this); + } +}); + hbs.registerHelper('dump', function(obj) { return JSON.stringify(obj, null, 2); }); +hbs.registerPartial('welcome_panel', __dirname + '/../views/partials/welcome-panel.html'); + module.exports = hbs; \ No newline at end of file diff --git a/lib/middleware.js b/lib/middleware.js index c7af1826..2008ed18 100644 --- a/lib/middleware.js +++ b/lib/middleware.js @@ -5,6 +5,8 @@ var utils = require('./utils'), models = require('./models'), config = require('./config'), features = require('./features'), + LZString = require('lz-string'), + undefsafe = require('undefsafe'), parse = require('url').parse; // Custom middleware used by the application. @@ -152,12 +154,17 @@ module.exports = { // Checks for a subdomain in the current url, if found it sets the // req.subdomain property. This supports existing behaviour that allows // subdomains to load custom config files. - subdomain: function (app) { + subdomain: function () { return function (req, res, next) { - var apphost = app.set('url host').split(':')[0], + var apphost = config.url.host, + outputHost = undefsafe(config, 'security.preview'), host = req.header('Host', ''), offset = host.indexOf(apphost); + if (host === outputHost) { + offset = host.indexOf(outputHost); + } + if (offset) { // Slice the host from the subdomain and subtract 1 for // trailing . on the subdomain. @@ -276,20 +283,34 @@ module.exports = { if (!features('sslLogin', req)) { return next(); } - var pathname = parse(req.url).pathname; + var pathShouldBeSecure = pathIsSSL(parse(req.url).pathname); + var forceSSL = features('sslForAll', req); + + // var useSSL = pathShouldBeSecure || forceSSL; if (!config.url.ssl) { if (req.secure) { + // if the request is https and we don't have a https host available in our config return res.redirect('http://' + req.headers.host.replace(/:.*/, '') + req.url); } } else { - if (!req.secure) { - if (pathIsSSL(pathname)) { - return res.redirect('https://' + req.headers.host.replace(/:.*/, '') + req.url); + // we do have https host in our config + if (req.secure) { + // we are using SSL... + if (forceSSL && pathShouldBeSecure === false) { + return next(); + } + + if (pathShouldBeSecure === false) { + req.shouldNotBeSecure = true; } } else { - if (!pathIsSSL(pathname)) { - return res.redirect('http://' + req.headers.host.replace(/:.*/, '') + req.url); + // if (forceSSL) { + // return next(); + // } + if (pathShouldBeSecure) { + // if the request is not https and _should be_, redirect to https + return res.redirect('https://' + req.headers.host.replace(/:.*/, '') + req.url); } } } @@ -332,10 +353,18 @@ module.exports = { next(); }; }, + decompressBody: function (req, res, next) { + if (req.body && req.body.compressed) { + req.body.compressed.split(',').forEach(function (key) { + req.body[key] = LZString.decompressFromUTF16(req.body[key]); + }); + } + next(); + } }; function pathIsSSL (route) { - return config.url.ssl.paths.reduce(function (bool, path) { + return (undefsafe(config, 'url.ssl.paths') || []).reduce(function (bool, path) { if (route.indexOf(path) === 0) { bool = true; } diff --git a/lib/models/bin.js b/lib/models/bin.js index d75a61ca..04d6a0ef 100644 --- a/lib/models/bin.js +++ b/lib/models/bin.js @@ -6,6 +6,7 @@ var Observable = require('../utils').Observable, crypto = require('crypto'), dropbox = require('../dropbox'), processors = require('../processors'), + blacklist = require('../blacklist'), readFileSync = require('fs').readFileSync, compileTemplate = require('handlebars').compile, binFileTemplate = compileTemplate(readFileSync(__dirname + '/../dropbox/bin-template.hbs').toString()), @@ -19,25 +20,7 @@ var model = { this.store = store; }, load: function (params, fn) { - this.store.getBin(params, function loadedGetBin(err, bin) { - if (err || !bin) { - return fn(err); - } - - this.getBinMetadata(bin, function loadBinMetadata(err, metadata) { - if (err) { - return fn(err); - } - - bin.metadata = metadata; - - if (this.isVisible(bin, params.username)) { - fn(null, bin); - } else { - fn(401); - } - }.bind(this)); - }.bind(this)); + this.store.getBin(params, this.binLoadCallback(params, fn)); }, isVisible: function (bin, username) { if (!username) { @@ -68,13 +51,21 @@ var model = { return false; }, latest: function (params, fn) { - this.store.getLatestBin(params, function loadedGetLatestBin(err, bin) { + this.store.getLatestBin(params, this.binLoadCallback(params, fn)); + }, + binLoadCallback: function (params, fn) { + + return function (err, bin) { if (err || !bin) { return fn(err, null); } bin.latest = true; + if (blacklist.validate(bin) === false) { + return fn(410); + } + this.getBinMetadata(bin, function loadedGetMetadata(err, metadata) { bin.metadata = metadata; @@ -85,8 +76,8 @@ var model = { fn(401); } }.bind(this)); + }.bind(this); - }.bind(this)); }, // Create a new bin. create: function (data, fn) { @@ -200,7 +191,7 @@ Object.keys(model).forEach(function (key) { }; args.push(fn); - method.apply(this, args); + return method.apply(this, args); }; }); diff --git a/lib/routes.js b/lib/routes.js index 6ce068b6..03b13bc7 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -1,3 +1,4 @@ +'use strict'; var express = require('express'), handlers = require('./handlers'), models = require('./models'), @@ -8,7 +9,9 @@ var express = require('express'), features = require('./features'), metrics = require('./metrics'), scripts = require('../scripts.json'), - Promise = require('rsvp').Promise, + undefsafe = require('undefsafe'), + Promise = require('rsvp').Promise, // jshint ignore:line + config = require('./config'), reBin = null; // created when the module is imported function tag(label) { @@ -19,7 +22,6 @@ function tag(label) { } function time(label) { - 'use strict'; return function (req, res, next) { res.on('header', function () { metrics.timing(label, Date.now() - req.start); @@ -39,17 +41,28 @@ module.exports = function (app) { models: models, mailer: app.mailer, helpers: helpers.createHelpers(app) - }, binHandler, sessionHandler, errorHandler, userHandler; + }; + + // Create handlers for accepting incoming requests. + var binHandler = new handlers.BinHandler(sandbox); + var sessionHandler = new handlers.SessionHandler(sandbox); + var errorHandler = new handlers.ErrorHandler(sandbox); + var userHandler = new handlers.UserHandler(sandbox); var root = app.get('url full'); - reBin = new RegExp(root + '/(.*?)/(\\d+)/?'); + reBin = new RegExp(root.replace(/^http.?:/, '') + '/(.*?)/(\\d+)/?'); function binParamFromReferer(req, res, next) { reBin.lastIndex = 0; // reset position + var r = root; + if (features('sslForAll', req)) { + r = r.replace(/http:/, 'https:'); + } + // only allow cloning via url if it came from jsbin - if (req.headers.referer && req.headers.referer.indexOf(root) === 0) { + if (req.headers.referer && req.headers.referer.indexOf(r) === 0) { var match = req.headers.referer.match(reBin) || []; if (match.length) { req.params.bin = match[1]; @@ -68,11 +81,20 @@ module.exports = function (app) { }; } - // Create handlers for accepting incoming requests. - binHandler = new handlers.BinHandler(sandbox); - sessionHandler = new handlers.SessionHandler(sandbox); - errorHandler = new handlers.ErrorHandler(sandbox); - userHandler = new handlers.UserHandler(sandbox); + function shouldNotBeSecure(req, res, next) { + // otherwise redirect to the http version + if (req.shouldNotBeSecure) { + return res.redirect('http://' + req.headers.host.replace(/:.*/, '') + req.url); + } + + // if the flag isn't present, then skip on + next(); + } + + function noframes(req, res, next) { + res.setHeader('X-Frame-Options', 'DENY'); + next(); + } // Redirects @@ -140,9 +162,31 @@ module.exports = function (app) { }); // Set up the routes. + app.get(/(?:.*\/(edit|watch|download|source)|^\/$)$/, function (req, res, next) { + var ssl = features('sslForAll', req); + + if ( (!req.secure && ssl) || // a) request *should* be secure + (req.secure && !ssl) ) { // b) request is secure and *should not* be + var url = sandbox.helpers.url(req.url, true, ssl); + return res.redirect(url); + } + + next('route'); + }); + + // secure the following paths from being iframed, note that it's also applied + // to full bin output + app.get('/auth/*', noframes, function (req, res, next) { + next('route'); + }); + app.get('/account/*', noframes, function (req, res, next) { + next('route'); + }); + + app.get('/', time('request.root'), userHandler.loadVanityURL, binHandler.loadBin, binHandler.getBinPreview); app.get('/', binHandler.getDefault); - app.get('/gist/*', binHandler.getDefault); + app.get('/gist/*', shouldNotBeSecure, binHandler.getDefault); app.post('/', binHandler.getFromPost); // sandbox @@ -150,9 +194,10 @@ module.exports = function (app) { // Runner app.get('/runner', function (req, res) { + var statik = sandbox.helpers.urlForStatic(undefined, req.secure && features('sslForAll', req)); res.render('runner', { scripts: app.get('is_production') ? false : scripts.runner, - static: sandbox.helpers.urlForStatic() + static: statik }); }); @@ -160,13 +205,41 @@ module.exports = function (app) { app.post('/api/save', binHandler.apiCreateBin); app.post('/api/:bin/save', binHandler.apiCreateRevision); + app.get('/account/upgrade', features.route('upgrade'), function (req, res) { + var featureList = require('./data/features.json'); + var backersList = require('./data/backers.json'); + var root = sandbox.helpers.url('', true, req.secure); + var statik = sandbox.helpers.urlForStatic('', req.secure); + var referrer = req.get('referer'); + var stripeKey = undefsafe(config, 'payment.stripe.public'); + var stripeProMonthURL = undefsafe(config, 'payment.stripe.urls.month'); + var showCoupon = false; + if (req.query.coupon === 'true') { + showCoupon = true; + } + + res.render('upgrade', { + layout: 'sub/layout', + root: root, + static: statik, + referrer: referrer, + featureList: featureList, + backersList: backersList, + cachebust: app.set('is_production') ? '?' + app.set('version') : '', + stripeKey: stripeKey, + stripeProMonthURL: stripeProMonthURL, + showCoupon: showCoupon + }); + + }); + // Account settings var renderAccountSettings = (function(){ var pages = ['editor', 'profile', 'delete', 'preferences']; return function renderAccountSettings (req, res) { var root = sandbox.helpers.url('', true, req.secure); - var static = sandbox.helpers.urlForStatic('', req.secure); + var statik = sandbox.helpers.urlForStatic('', req.secure); var referrer = req.get('referer'); var page = pages.indexOf(req.param('page')) === -1 ? false : req.param('page') + '.html'; @@ -180,6 +253,16 @@ module.exports = function (app) { } } + var info = req.flash(req.flash.INFO), + error = req.flash(req.flash.ERROR), + notification = req.flash(req.flash.NOTIFICATION); + + var flash = error || notification || info; + var flashType = ''; + if (info) {flashType = 'info';} + if (notification) {flashType = 'notification';} + if (error) {flashType = 'error';} + if (!page) { return res.redirect('back'); } @@ -188,12 +271,14 @@ module.exports = function (app) { } res.render('account/' + page, { + flash_tip: flash, // jshint ignore:line + flash_tip_type: flashType, // jshint ignore:line token: req.session._csrf, layout: 'sub/layout.html', referrer: referrer, httproot: root.replace('https', 'http'), root: root, - static: static, + static: statik, user: req.session.user, request: req, addons: app.get('is_production') ? false : addons, @@ -202,7 +287,7 @@ module.exports = function (app) { }; }()); - app.get('/account/:page', features.route('accountPages'), renderAccountSettings); + app.get('/account/:page', shouldNotBeSecure, features.route('accountPages'), renderAccountSettings); app.get('/account', function(req, res) { res.redirect('/account/editor'); }); @@ -303,14 +388,14 @@ module.exports = function (app) { app.get('/logout', function (req, res) { if (req.session.user) { var root = sandbox.helpers.url('', true, req.secure); - var static = sandbox.helpers.urlForStatic('', req.secure); + var statik = sandbox.helpers.urlForStatic('', req.secure); res.render('account/logout', { token: req.session._csrf, learn: 'http://learn.jsbin.com/', layout: 'sub/layout.html', root: root, - static: static, + static: statik, user: req.session.user }); } else { @@ -364,7 +449,7 @@ module.exports = function (app) { // FIXME the assetUrl url lookup from username should go via memcache, // because doing a mysql query for every image that appears in these bins // will start to get silly expensive - app.get('/:username/assets/*', features.route('assets'), function (req, res, next) { + app.get('/:username/assets/*', features.route('assets'), function (req, res) { if (req.user.settings && req.user.settings.assetUrl) { res.redirect(req.user.settings.assetUrl + req.params[0]); } else { @@ -397,8 +482,7 @@ module.exports = function (app) { }); }); - - app.get('/:username/last(-:n)?/:quiet(quiet)?', tag('keepLatest'), binHandler.getLatestForUser, spike.getStream, binHandler.getBinPreview); + // username shortcut routes app.get('/:username/last(-:n)?/edit', binHandler.getLatestForUser, binHandler.getBin); app.get('/:username/last(-:n)?/watch', binHandler.getLatestForUser, binHandler.live, binHandler.getBin); @@ -438,31 +522,62 @@ module.exports = function (app) { app.get('/download', binParamFromReferer, binHandler.loadBin, binHandler.downloadBin); app.get('/:bin/:rev?/download', binHandler.downloadBin); + + /** + * Full output routes + */ + var secureOutput = function (req, res, next) { + // 1. check request is supposed to be on a vanity url + // 2. if not, then check if the req.headers.host matches security.preview + // 3. if not, redirect + var metadata = undefsafe(req, 'bin.metadata'); + var settings = {}; + var ssl = false; + var url; + + // apply x-frame-options, but pass a dummy `next` + noframes(req, res, function () {}); + + if (settings) { + try { + settings = JSON.parse(metadata.settings); + ssl = features('sslForAll', { session: { user: { name: metadata.name, pro: metadata.pro, settings: { ssl: settings.ssl }}}}); + } catch (e) {} + } + + if (features('sslForAll', req)) { + return next(); + } + + url = sandbox.helpers.url(req.url, true, ssl); + + if ( (!req.secure && ssl) || // a) request *should* be secure + (req.secure && !ssl) ) { // b) request is secure and *should not* be + return res.redirect(url); + } + + next(); + }; + // Source - app.get('/:bin/:rev?/source', time('request.source'), binHandler.getBinSource); - app.get('/:bin/:rev?.:format(js|json|css|html|md|markdown|stylus|less|coffee|jade|svg)', time('request.source'), binHandler.getBinSourceFile); - app.get('/:bin/:rev?/:format(js)', function (req, res) { + app.get('/:bin/:rev?/source', secureOutput, time('request.source'), binHandler.getBinSource); + app.get('/:bin/:rev?.:format(js|json|css|html|md|markdown|stylus|less|coffee|jade|svg)', secureOutput, time('request.source'), binHandler.getBinSourceFile); + app.get('/:bin/:rev?/:format(js)', secureOutput, function (req, res) { // Redirect legacy /js suffix to the new .js extension. res.redirect(301, req.path.replace(/\/js$/, '.js')); }); - // Log - not actually implemented - app.get('/:bin/:rev/log', spike.getLog); - app.post('/:bin/:rev/log', spike.postLog); - // Preview - app.get('/:bin/:quiet(quiet)?', spike.getStream, binHandler.getBinPreview); - app.get('/:bin/:rev?/:quiet(quiet)?', spike.getStream, binHandler.getBinPreview); - app.get('/:bin/:rev?/stats', tag('stats'), spike.getStream); + app.get('/:username/last(-:n)?/:quiet(quiet)?', secureOutput, tag('keepLatest'), binHandler.getLatestForUser, spike.getStream, binHandler.getBinPreview); + app.get('/:bin/:quiet(quiet)?', secureOutput, binHandler.testPreviewAllowed, spike.getStream, binHandler.getBinPreview); + app.get('/:bin/:rev?/:quiet(quiet)?', secureOutput, binHandler.testPreviewAllowed, spike.getStream, binHandler.getBinPreview); + app.get('/:bin/:rev?/stats', tag('stats'), secureOutput, spike.getStream); + // used for simple testing app.get('/test/error/:num', function (req, res, next) { next(req.params.num * 1); }); - app.get('/test/bin/:bin', function (req, res, next) { - res.send(req.bin); - }); - // Handle failed auth requests. app.use(sessionHandler.githubCallbackWithError); diff --git a/lib/spike/index.js b/lib/spike/index.js index 23964962..058de471 100644 --- a/lib/spike/index.js +++ b/lib/spike/index.js @@ -35,7 +35,7 @@ replify('spike', { setInterval(function () { Object.keys(sessions).forEach(function (id) { sessions[id].res.forEach(function (res) { - if (!res.connection.writable) { + if (!res.connection || !res.connection.writable) { utils.removeConnection(id, res); } }); diff --git a/lib/stripe/handlers/index.js b/lib/stripe/handlers/index.js index 0e601de7..1278c644 100644 --- a/lib/stripe/handlers/index.js +++ b/lib/stripe/handlers/index.js @@ -65,6 +65,10 @@ module.exports = function (config) { plan: req.params.plan, card: req.body.stripeToken }; + var couponCode = req.url.indexOf('?') !== -1 ? req.url.split('?').pop() : null; + if (couponCode) { + customerOptions.coupon = couponCode; + } if (!user) { res.flash('error', 'Please login before attempting to signup'); diff --git a/lib/stripe/routes.js b/lib/stripe/routes.js index 1db95fdc..e53c5ee7 100644 --- a/lib/stripe/routes.js +++ b/lib/stripe/routes.js @@ -1,15 +1,11 @@ 'use strict'; -var features = require('../features.js'); module.exports = function routes(app, config) { // pass handlers the app object so they have access to the stripe API key var stripeHandler = require('./handlers')(config); + // following POSTs come directly from stripe app.post('/hooks/stripe', stripeHandler.authEvent, stripeHandler.handleTransaction); - app.post('/subscribe/:plan', stripeHandler.handleSubscription); - - app.get('/signup', features.route('stripe'), stripeHandler.renderSignUp); - }; diff --git a/lib/utils.js b/lib/utils.js index bd4c65f6..86d314d6 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -156,7 +156,7 @@ module.exports = { gravatar: function (email, size) { email = (email || '').trim().toLowerCase(); var hash = crypto.createHash('md5').update(email).digest('hex'); - return 'http://www.gravatar.com/avatar/' + hash + '?s=' + (size || 29); // 26 + return '//www.gravatar.com/avatar/' + hash + '?s=' + (size || 29); // 26 }, re: { flatten: /\s+/g, diff --git a/lib/welcome-panel.js b/lib/welcome-panel.js new file mode 100644 index 00000000..3a983e54 --- /dev/null +++ b/lib/welcome-panel.js @@ -0,0 +1,48 @@ +'use strict'; + +var fs = require('fs'); + +var info = []; +var reg = /^https?\:\/\/([^\/?#]+)(?:[\/?#]|$)/i; +var root = require('./config').url.host || ''; +var m; +var extUrls = function(obj) { + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + if (key == 'link') { + m = obj[key].match(reg); + if (m && m[1] !== root) { + obj.ext = true; + } + } else if (typeof obj[key] === 'object') { + extUrls(obj[key]); + } + } + } +}; + +function load() { + fs.readFile(__dirname + '/data/welcome-panel.json', 'utf8', function (error, data) { + if (error) { + console.error('failed to load welcome-panel.json'); + return; + } + + try { + info = JSON.parse(data); + extUrls(info); + } catch (e) { + console.error('failed to parse welcome-panel.json'); + } + }); +} + +load(); + +process.on('SIGHUP', load); + +module.exports = { + getData: function () { + return info; + } +}; diff --git a/package.json b/package.json index 9d260968..76a1e945 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "name": "jsbin", "description": "Collaborative JavaScript Debugging App", "main": "./lib/app", - "version": "3.13.24", + "version": "3.15.7", "preferGlobal": "true", "homepage": "http://jsbin.com", "bin": "./bin/jsbin", @@ -29,13 +29,12 @@ "sqlite3": "~2.2.0", "nodemailer": "0.3.20", "commander": "1.0.0", - "less": "~1.4.2", + "less": "~1.7.3", "jade": "0.26.3", - "stylus": "0.28.2", "flatten.js": "0.1.0", "soak": "0.3.0", "async": "~0.1.22", - "lynx": "0.0.11", + "lynx": "~0.1.1", "file-db": "0.0.2", "underscore": "~1.4.4", "passport": "~0.1.17", @@ -54,7 +53,9 @@ "express-cookie-blacklist": "~2.0.0", "passport-dropbox-oauth2": "~0.1.5", "handlebars": "~2.0.0-alpha.2", - "dropbox": "~0.10.2" + "dropbox": "~0.10.2", + "lz-string": "~1.3.3", + "stylus": "^0.47.1" }, "devDependencies": { "grunt": "~0.4.1", diff --git a/public/css/account.css b/public/css/account.css index 7afd99d0..903543c6 100644 --- a/public/css/account.css +++ b/public/css/account.css @@ -75,10 +75,16 @@ form input[type="submit"] { form input[type="submit"]:hover { /*background: linear-gradient(0deg, #e8e8e8, #f8f8f8);*/ } -form input { +form input, +form textarea { font-size: 16px; +} +form input { position: relative; } +form textarea { + font-family: inherit; +} input[type="number"] { width: 50px; @@ -143,6 +149,30 @@ form label.mini-desc a { font-weight: normal; margin-right: 0; } +span.mini-desc { + color: #979A99; + display: block; + font-weight: normal; + font-size: 14px; +} +span.mini-desc a { + color: #979A99; +} +.mini-desc-error { + border-radius: 4px; + display: block; + font-weight: normal; + background: #FF4136; + color: #fff; + padding: 5px 10px; + opacity: 0; + -webkit-transition: opacity 100ms ease-out; + -ms-transition: opacity 100ms ease-out; + transition: opacity 100ms ease-out; +} +.mini-desc-error.show { + opacity: 1; +} .loginFeedback { display: none; @@ -154,4 +184,39 @@ form label.mini-desc a { white-space: normal; line-height: 18px; margin-bottom: 1em; +} + +.hintOptions { + font-family: SourceCodeProRegular, Menlo, Monaco, consolas, monospace !important; + font-size: 16px; + line-height: 1.2em; +} + + +.hintOptWrapper { + display: inline; + width: 400px; + margin-left: 1em; +} +.hintOptWrapper summary { + width: 100px; + display: inline; + outline: none; + color: #979A99; + cursor: pointer; +} +.hintOptWrapper summary + div { + margin-top: 0.5em; +} +#jshint, #csshint, #htmlhint, #coffeescripthint { + vertical-align: top; + margin-top: 6px; +} +form label.hintOptions { + width: 400px; + display: block; + float: none; + text-align: left; + margin-top: -8px; + margin-bottom: 1em; } \ No newline at end of file diff --git a/public/css/help.css b/public/css/help.css index f9b2a793..a8034953 100644 --- a/public/css/help.css +++ b/public/css/help.css @@ -200,13 +200,14 @@ header nav a { header nav a.selected:before { position: absolute; - top: 42px; + top: 45px; /* 42px */ left: 35%; /* border-left: solid 1px #ccc; */ /* border-top: solid 1px #ccc; */ width: 1.1em; height: 1.1em; -webkit-transform: rotate(45deg); + transform: rotate(45deg); background: #fff; content: ''; } @@ -253,6 +254,8 @@ article p { nav { float: right; + width: 50%; + text-align: right; } .button.action { @@ -735,4 +738,100 @@ span.status { span.status.show { opacity: 1; +} + +/* Tip flash messages */ +/* Slightly changed from the one in style.css */ +#tip { + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: -moz-none; /* if this is "none" you can't copy text */ + -ms-user-select: none; + user-select: none; +} +#tip { + color: #000; + z-index: 100; + display: none; + border-top: 1px solid #ccc; + position: absolute; + bottom: 0; + font-size: 14px; + line-height: 22px; + background: #fdfece; + left: 0; + right: 0; + padding: 2px 10px 2px 10px; + -webkit-animation: tip-flash 100ms linear 4 alternate; + -moz-animation: tip-flash 100ms linear 4 alternate; + -ms-animation: tip-flash 100ms linear 4 alternate; + -o-animation: tip-flash 100ms linear 4 alternate; + animation: tip-flash 100ms linear 4 alternate; + + -webkit-transition: bottom 100ms linear; + transition: bottom 100ms linear; +} + +@-webkit-keyframes tip-flash { + to { + background: white; + } +} +@-moz-keyframes tip-flash { + to { + background: white; + } +} +@-ms-keyframes tip-flash { + to { + background: white; + } +} +@-o-keyframes tip-flash { + to { + background: white; + } +} +@keyframes tip-flash { + to { + background: white; + } +} + +#tip.error { + background: #FF4136; + color: #fff; +} + +.error a { + color: #fff; + text-shadow: none; +} + +#tip.notification, +#tip.error { + bottom: auto; + top: 60px; + line-height: 28px; + box-shadow: 0 2px 4px rgba(0,0,0,.2); +} + +#tip p { + margin: 5px 25px 5px 0; + padding: 0 65px 0 0; + line-height: 1.3; +} + +#tip a.dismiss { + line-height: 28px; + position: absolute; + right: 20px; + top: 2px; + text-decoration: none; + text-shadow: none; + color: inherit; +} + +.showtip #tip { + display: block; } \ No newline at end of file diff --git a/public/css/ie.css b/public/css/ie.css index ddedce74..d1dbf465 100644 --- a/public/css/ie.css +++ b/public/css/ie.css @@ -19,3 +19,25 @@ div { .fake-dropdown:after { content: url(/images/down-arrow.png); } + +/* toppanel */ +a.toppanel-logo { + position: relative; + left: -10px; + top: 237px; + width: 20px; +} +.toppanel a.toppanel-logo { + position: relative; + left: 65px; + top: 0; + width: 100px; +} +.toppanel-button-dropdown:after { + text-align: center; +} +.toppanel-button-disabled { + opacity: 0.5; + filter: alpha(opacity=50); + -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=50)"; +} diff --git a/public/css/style.css b/public/css/style.css index e726c102..98ba6ccd 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -1452,6 +1452,13 @@ THE SOFTWARE. /* ============================================================================= JS Bin ========================================================================== */ + +/* Common */ +.cm-s-solarized .CodeMirror-linenumber { + color: rgba(88, 110, 117, 0.3); +} + + html { height: 100%; } @@ -1590,7 +1597,7 @@ textarea:focus { } -#control, .label, #tip { +#control, .label, #tip, #toppanel { -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: -moz-none; /* if this is "none" you can't copy text */ @@ -2898,6 +2905,20 @@ input { line-height: 18px; } +/*@media (max-height: 770px) {*/ +.hideheader.ready #control { + border-bottom: 6px solid #AAA; + -webkit-transition: top ease-out 100ms, border-bottom ease-out 100ms; + -moz-transition: top ease-out 100ms, border-bottom ease-out 100ms; + -o-transition: top ease-out 100ms, border-bottom ease-out 100ms; + transition: top ease-out 100ms, border-bottom ease-out 100ms; + -webkit-transition-delay: 1s; + -moz-transition-delay: 1s; + -o-transition-delay: 1s; + transition-delay: 1s; + top: -35px; +} + /*#loginFeedback:empty { display: none; } @@ -4197,8 +4218,11 @@ pre .highlight:last-of-type { .embed #live .options { overflow: hidden; background: #fff; - background: rgba(255, 255, 255, 0.8); - opacity: 0; + background: rgba(255, 255, 255, 0.3); + opacity: 0.3; + position: absolute; + bottom: 10px; + right: 10px; -webkit-transition: opacity ease-in 50ms; -moz-transition: opacity ease-in 50ms; -o-transition: opacity ease-in 50ms; @@ -4209,6 +4233,18 @@ pre .highlight:last-of-type { opacity: 1; } +.embed #live .label { + position: static; +} + +.embed .blog { + display: none; +} + +.embed .options a { + display: none; +} + .embed #live iframe { padding-top: 0; } @@ -4356,7 +4392,8 @@ html * { background: transparent; } -.CodeMirror-activeline-background:before { +.CodeMirror-activeline-background:before, +.line-highlight:before { content: ""; position: absolute; background: inherit; @@ -4419,6 +4456,10 @@ html * { #panels { display: none; } + + .embed #panels { + display: block; + } } /* hover card styles */ @@ -4969,4 +5010,488 @@ tbody:hover .snapshot1 .url a span:before { #share .direct-links a { margin-right: 5px; +} + +.CodeMirror-highlight-line, +.CodeMirror-highlight-line .CodeMirror-linenumber, +.CodeMirror-highlight-line-background { + background: rgba(255, 255, 204, 0.6); +} + +/* toppanel status */ +#toppanel { + height: 200px; + background: #ecf2fa; + position: relative; + z-index: 100000; + border-bottom: rgb(191, 191, 191) solid 1px; + margin-top: -200px; +} +.toppanel #toppanel { + margin-top: 0; +} + +.toppanel #bin, +.toppanel #history, +.toppanel #history .preview { + top: 236px; +} + +#toppanel ~ #control .brand { + margin-left: 34px; +} +#toppanel ~ #control .brand img { + display: none; +} + +body.toppanel { + background-position: center calc((100% - 250px) / 2 + 256px); +} +body.ready.toppanel { + background: white; +} + +.toppanel #tip.notification, +.toppanel #tip.error { + top: 234px; +} + +/* toppanel */ +.toppanel-wrapper { + display: block !important; + color: #232323; + font-size: 0; + padding: 10px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +.toppanel-column { + font-size: 14px; + display: inline-block; + padding: 0 4px; + vertical-align: top; + max-width: 220px; + width: 16.66%; + height: 179px; + overflow: hidden; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +@media (max-width: 1223px) { /* 5 */ + .toppanel-column { + width: calc((100% - 217px) / 4); + max-width: 100%; + } + .toppanel-column:nth-child(n+6) { + display: none; + } +} +@media (max-width: 1023px) { /* 4 */ + .toppanel-column { + width: calc((100% - 217px) / 3); + } + .toppanel-column:nth-child(n+5) { + display: none; + } +} +@media (max-width: 816px) { /* 3 */ + .toppanel-column { + width: calc((100% - 217px) / 2); + } + .toppanel-column:nth-child(n+4) { + display: none; + } +} +@media (max-width: 616px) { /* 2 */ + .toppanel-column { + width: calc(100% - 217px); + } + .toppanel-column:nth-child(n+3) { + display: none; + } +} +@media (max-width: 416px) { /* 1 */ + .toppanel-column:nth-child(n+2) { + display: none; + } +} +.toppanel-column-first { + padding-right: 10px; + overflow: visible; + width: 197px; +} +.toppanel-section + .toppanel-section { + margin-top: 13px; +} +.toppanel-actions { + position: relative; + top: -12px; + z-index: 0; +} +.toppanel-actions-alone { + top: 0; +} +.toppanel-title { + font-size: 14px; + font-weight: 600; + margin: 0; +} +.toppanel-title a:after { + content: " »"; + font-size: 1.1em; +} +.toppanel-list { + list-style: none; + margin: 0; + padding: 0; +} +.toppanel-wrapper a { + color: #6293D3; + display: block; + padding: 6px 10px 6px 1px; + text-decoration: none; + text-shadow: none; +} +.toppanel-wrapper a:hover { + background: #CCC; + background: rgba(0, 0, 0, 0.05); +} +a.toppanel-twitter-link { + display: inline; +} +.toppanel-bin-name { + width: 5em; + display: inline-block; +} +.toppanel-bin-date { + color: #888; +} +.toppanel-quote { + line-height: 1.4; +} +.toppanel-quote-author { + color: #888; + display: block; + font-weight: 500; +} +div:first-child > .toppanel-button { + position: static; +} +.toppanel-button { + border: 1px solid #6293D3; + line-height: 25px; + margin: 0 auto; + text-align: center; + width: 150px; + position: relative; + top: -1px; +} +.toppanel-button-dropdown { + position: relative; +} +.toppanel-button-dropdown:after { + border-left: 1px solid #6293D3; + content: "▾"; + font-size: 18px; + line-height: 40px; + position: absolute; + width: 28px; + right: 0; + top: 0; + bottom: 0; +} +.toppanel-button-disabled { + opacity: 0.5; +} +a.toppanel-hide { + position: absolute; + left: 5px; + top: 5px; + padding: 0 5px 7px; + font-size: 35px; + line-height: 0.7; + color: #6293D3; + color: rgba(98, 147, 211, 0.5); + font-weight: 200; +} +.toppanel-actions .btn-github { + font-size: 70%; + width: 163px; + margin: 0 auto; + color: #232323; +} +.toppanel-actions .btn-github:hover { + background: linear-gradient(0deg, #e8e8e8, #f8f8f8); +} + +a.toppanel-logo { + display: inline-block; + width: 100px; + padding: 4px 6px; + text-align: center; + position: relative; + z-index: 1; + -webkit-transform: translate(-94px, 112px) scale(0.25); + -moz-transform: translate(-94px, 112px) scale(0.25); + -ms-transform: translate(-94px, 112px) scale(0.25); + -o-transform: translate(-94px, 112px) scale(0.25); + transform: translate(-94px, 112px) scale(0.25); + -webkit-transform-origin: 100% 100%; + -moz-transform-origin: 100% 100%; + -ms-transform-origin: 100% 100%; + -o-transform-origin: 100% 100%; + transform-origin: 100% 100%; +} +a.toppanel-logo:hover { + background: transparent; + border: 0 none; +} +.toppanel-logo img { + border: 0 none; + width: 100%; +} +.toppanel .toppanel-logo { + cursor: default; + -webkit-transform: translate(35px, -8px) scale(1); + -moz-transform: translate(35px, -8px) scale(1); + -ms-transform: translate(35px, -8px) scale(1); + -o-transform: translate(35px, -8px) scale(1); + transform: translate(35px, -8px) scale(1); +} + +/* toppanel animation */ +#bin { + -webkit-transition: top ease-in-out 100ms; + -moz-transition: top ease-in-out 100ms; + -o-transition: top ease-in-out 100ms; + transition: top ease-in-out 100ms; +} +#toppanel { + -webkit-transition: margin-top ease-in-out 100ms; + -moz-transition: margin-top ease-in-out 100ms; + -o-transition: margin-top ease-in-out 100ms; + transition: margin-top ease-in-out 100ms; +} +a.toppanel-logo { + -webkit-transition: -webkit-transform linear 120ms 120ms; + -moz-transition: -moz-transform linear 120ms 120ms; + -o-transition: -o-transform linear 120ms 120ms; + transition: transform linear 120ms 120ms; +} +/* slow to remove */ +.toppanel-slow #bin, +.toppanel-slow #toppanel, +.toppanel-slow a.toppanel-logo { + -webkit-transition-duration: 5s; + -moz-transition-duration: 5s; + -o-transition-duration: 5s; + transition-duration: 5s; +} + +.toppanel-close #bin, +.toppanel-close #toppanel { + -webkit-transition-delay: 120ms; + -moz-transition-delay: 120ms; + -o-transition-delay: 120ms; + transition-delay: 120ms; + -webkit-transition-duration: 100ms; + -moz-transition-duration: 100ms; + -o-transition-duration: 100ms; + transition-duration: 100ms; +} +.toppanel-close a.toppanel-logo { + -webkit-transition-delay: 0ms; + -moz-transition-delay: 0ms; + -o-transition-delay: 0ms; + transition-delay: 0ms; + -webkit-transition-duration: 120ms; + -moz-transition-duration: 120ms; + -o-transition-duration: 120ms; + transition-duration: 120ms; +} + + +/* CodeMirror lint */ +/* The lint marker gutter */ +.CodeMirror-lint-markers { + width: 16px; +} + +.CodeMirror-lint-tooltip { + background-color: infobackground; + border: 1px solid black; + border-radius: 4px 4px 4px 4px; + color: infotext; + font-family: monospace; + font-size: 10pt; + overflow: hidden; + padding: 2px 5px; + position: fixed; + white-space: pre; + white-space: pre-wrap; + z-index: 100; + max-width: 600px; + opacity: 0; + transition: opacity .4s; + -moz-transition: opacity .4s; + -webkit-transition: opacity .4s; + -o-transition: opacity .4s; + -ms-transition: opacity .4s; +} + +.CodeMirror-lint-mark-error, .CodeMirror-lint-mark-warning { + background-position: left bottom; + background-repeat: repeat-x; +} + +.CodeMirror-lint-mark-error { + background-image: + url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJDw4cOCW1/KIAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAHElEQVQI12NggIL/DAz/GdA5/xkY/qPKMDAwAADLZwf5rvm+LQAAAABJRU5ErkJggg==") + ; +} + +.CodeMirror-lint-mark-warning { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJFhQXEbhTg7YAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAMklEQVQI12NkgIIvJ3QXMjAwdDN+OaEbysDA4MPAwNDNwMCwiOHLCd1zX07o6kBVGQEAKBANtobskNMAAAAASUVORK5CYII="); +} + +.lint-icon-warning:before, +.lint-icon-error:before, +.CodeMirror-lint-marker-error, .CodeMirror-lint-marker-warning { + background-position: center center; + background-repeat: no-repeat; + cursor: pointer; + display: inline-block; + height: 16px; + width: 16px; + vertical-align: middle; + position: relative; +} + +.CodeMirror-lint-message-error, .CodeMirror-lint-message-warning { + padding-left: 18px; + background-position: top left; + background-repeat: no-repeat; +} + +.lint-icon-error:before, +.CodeMirror-lint-marker-error, .CodeMirror-lint-message-error { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAHlBMVEW7AAC7AACxAAC7AAC7AAAAAAC4AAC5AAD///+7AAAUdclpAAAABnRSTlMXnORSiwCK0ZKSAAAATUlEQVR42mWPOQ7AQAgDuQLx/z8csYRmPRIFIwRGnosRrpamvkKi0FTIiMASR3hhKW+hAN6/tIWhu9PDWiTGNEkTtIOucA5Oyr9ckPgAWm0GPBog6v4AAAAASUVORK5CYII="); +} + +.lint-icon-warning:before, +.CodeMirror-lint-marker-warning, .CodeMirror-lint-message-warning { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAANlBMVEX/uwDvrwD/uwD/uwD/uwD/uwD/uwD/uwD/uwD6twD/uwAAAADurwD2tQD7uAD+ugAAAAD/uwDhmeTRAAAADHRSTlMJ8mN1EYcbmiixgACm7WbuAAAAVklEQVR42n3PUQqAIBBFUU1LLc3u/jdbOJoW1P08DA9Gba8+YWJ6gNJoNYIBzAA2chBth5kLmG9YUoG0NHAUwFXwO9LuBQL1giCQb8gC9Oro2vp5rncCIY8L8uEx5ZkAAAAASUVORK5CYII="); +} + +.CodeMirror-lint-marker-multiple { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAMAAADzjKfhAAAACVBMVEUAAAAAAAC/v7914kyHAAAAAXRSTlMAQObYZgAAACNJREFUeNo1ioEJAAAIwmz/H90iFFSGJgFMe3gaLZ0od+9/AQZ0ADosbYraAAAAAElFTkSuQmCC"); + background-repeat: no-repeat; + background-position: right bottom; + width: 100%; height: 100%; +} + + +.lint-icon-warning:before, +.lint-icon-error:before { + content: ''; +} +.lint-icon-warning.dis:before, +.lint-icon-error.dis:before { + filter: url("data:image/svg+xml;utf8,#grayscale"); /* Firefox 10+, Firefox on Android */ + filter: gray; /* IE6-9 */ + -webkit-filter: grayscale(100%); +} +.lint-icon-error.dis:before { + -webkit-filter: grayscale(100%) invert(85%); +} +.lint-error { + background: #FFA; + font-size: 80%; + color: #A00; + padding: 2px 5px 3px; +} +.console-wrapper { + background: #FFF; + color: #333; +} +.console-log-head { + background: #ecf2fa; + border: 1px solid #CCC; + border-top: 0 none; + color: #6293d3; + cursor: pointer; + float: none; + font-size: 13px; + height: auto; + line-height: 19px; + margin: 0; + padding: 0.2em 0.2em 0.2em 1em; + width: auto; +} +.console-log { + border: 1px solid #CCC; + border-top: 0 none; + font-size: 13px; + height: 10.5em; + overflow: auto; +} +.console-log-line { + border-top: 1px solid #CCC; + cursor: pointer; + padding: 0.2em; +} +.console-log-line:first-child { + border-top: 0 none; +} +.console-log-line:before { + display: inline-block; + min-width: 1em; + padding-right: 0.5em; + text-align: center; +} +#console-log-line-selected { + background: #AFF; +} + +/* embed tweaks */ +.embed #control { + height: 28px; +} + +.embed #runwithalerts { + float: right; +} + +.embed #bin { + top: 28px; +} + +.embed .dropdownmenu a, +.embed .button { + height: 18px; + line-height: 20px; +} + +.embed #panels { + padding-top: 0; + padding-bottom: 0; +} + +.embed #panels .button { + border-radius: 0; + border-top: 0; + border-bottom: 0; + height: 18px; + line-height: 20px; +} + +.embed .menu .brand { + margin-left: 0; + padding-left: 5px; +} + +.embed .menu .brand img { + margin-top: 2px; } \ No newline at end of file diff --git a/public/css/upgrade.css b/public/css/upgrade.css new file mode 100644 index 00000000..26da2354 --- /dev/null +++ b/public/css/upgrade.css @@ -0,0 +1,104 @@ +body { + font-family: 'helvetica neue', sans-serif; + font-weight:200!important; +} + +h1, h2 { + text-align:center; +} + +h1 { + font-weight:300; +} + +h2 { + font-weight:400; +} + +table { + border: 1; + width: 80%; + border-collapse: collapse; + margin-left:10%; +} + +tbody tr { + border-top: 1px solid rgb(240, 240, 240); +} + +tr.headings th { + vertical-align: bottom; + padding-bottom: 20px; +} + +td { + text-align:center; + line-height:30px; + font-size:16px; + font-weight:300; + width:160px; +} + +td.feature { + text-align:left; + width: auto; + min-width:230px; +} + +.coupon-btn { + font-size:14px; +} + +#coupon_code { + font-size:14px; +} + +.backers { + width:720px; + margin:0 auto; +} +.backers a { + +} +#body .backers .backer-img { + width:200px; + margin:20px auto; + border:none; +} +/* +button.stripe-button-el { + font-size: 1.4em; + font-weight: 200; + background: #f0f0f0; + background: linear-gradient(0deg, #f0f0f0, #fefefe); + box-shadow: 0px 1px 1px #eee; + border: 1px solid #aaa; + border-radius: 2px; + padding: 10px 12px; + cursor: pointer; + display: inline; + -moz-box-sizing: border-box; + box-sizing: border-box; + text-align: center; + text-decoration: none; + border-bottom: 2px solid #aaa; +} +button.stripe-button-el:hover { + background: linear-gradient(0deg, #e8e8e8, #f8f8f8); +} +*/ + +th small { + color: rgb(165, 164, 164); + font-weight: normal; +} + +/*tr.anon td.anon:after, tr.anon td.free:after, tr.anon td.pro:after { + content: "\2713"; +} +tr.free td.free:after, tr.free td.pro:after { + content: "\2713"; +} +tr.pro td.pro:after { + content: "\2713"; +}*/ diff --git a/public/custom/emberjs/default.html b/public/custom/emberjs/default.html index 9009d3b6..b2907a88 100644 --- a/public/custom/emberjs/default.html +++ b/public/custom/emberjs/default.html @@ -6,7 +6,7 @@ - + diff --git a/public/images/dave.svg b/public/images/dave.svg new file mode 100644 index 00000000..ddc4f821 --- /dev/null +++ b/public/images/dave.svg @@ -0,0 +1,43 @@ + + + Layer 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/js/account/editor-settings.js b/public/js/account/editor-settings.js index 4ae36880..e9889d6c 100644 --- a/public/js/account/editor-settings.js +++ b/public/js/account/editor-settings.js @@ -26,6 +26,14 @@ html: null }; + // default CodeMirror settings + var cmDefaultSettings = { + lineWrapping: true, + theme: 'jsbin', + indentUnit: 2, + tabSize: 2 + }; + // needed for the keymaps $.browser = {}; // work out the browser platform @@ -74,12 +82,19 @@ 'vim', 'emacs', 'sublime', - 'tern' + 'tern', + 'matchbrackets' ]; var $addons = {}; + var $saveStatus = $('span.status'); + var saveTimer = null; + // setup variables; + var $saveStatus = $('span.status'); + var saveTimer = null; + var $textarea = $('textarea'); var currentSettings = getCurrentSettings(); if (currentSettings.editor === undefined) { @@ -88,6 +103,7 @@ if (currentSettings.addons === undefined) { currentSettings.addons = {}; } + currentSettings.editor = $.extend({}, cmDefaultSettings, currentSettings.editor); jsbin.settings = $.extend({}, currentSettings); var editor = window.editor = CodeMirror.fromTextArea($textarea[0], $.extend({ @@ -137,7 +153,8 @@ editor.setOption('lineWrapping', $lineWrapping.prop('checked')); editor.setOption('lineNumbers', $lineNumbers.prop('checked')); editor.setOption('indentWithTabs', $indentWithTabs.prop('checked')); - editor.setOption('tabSize', $tabSize.val()); + editor.setOption('tabSize', parseInt($tabSize.val(), 10)); + editor.setOption('indentUnit', parseInt($tabSize.val(), 10)); editor.setOption('theme', $theme.val()); $CodeMirror.css('font-size', $fontsize.val()+'px'); editor.refresh(); @@ -146,7 +163,7 @@ var localStorageSettings = JSON.parse(localStorage.settings || '{}'); var codemirrorSettings = pick(editor.options, settingsKeys); var newSettingsEditor = $.extend(localStorageSettings.editor, codemirrorSettings); - + var addonsSettings = {}; for (var i = 0; i < addonsKeys.length; i++) { addonsSettings[ addonsKeys[i] ] = $addons[ addonsKeys[i] ].prop('checked'); @@ -179,6 +196,9 @@ } reloadAddons(tempAddonsKeys); + clearTimeout(saveTimer); + $saveStatus.addClass('show'); + // Save on server $.ajax({ type: 'POST', @@ -188,6 +208,13 @@ _csrf: $csrf.val() }, success: function() { + clearTimeout(saveTimer); + saveTimer = setTimeout(function () { + $saveStatus.addClass('show'); + saveTimer = setTimeout(function () { + $saveStatus.removeClass('show'); + }, 3000); + }, 1000); if (console && console.log) { console.log('Success on saving settings'); } @@ -196,6 +223,11 @@ if (console && console.log) { console.log('Error: ' + status); } + }, + complete: function () { + saveTimer = setTimeout(function () { + $saveStatus.removeClass('show'); + }, 1000); } }); diff --git a/public/js/account/preferences-settings.js b/public/js/account/preferences-settings.js index bdbd8432..ca388a71 100644 --- a/public/js/account/preferences-settings.js +++ b/public/js/account/preferences-settings.js @@ -24,12 +24,27 @@ // Setup variables var $csrf = $('#_csrf'); + var hintShow = { + console: true, + line: false, + // under: false, + // tooltip: true, + gutter: false + }; var currentSettings = { panels: [], includejs: true, focusedPanel: 'html', - jshint: true, assetUrl: '', + hintShow: hintShow, + jshint: true, + jshintOptions: '', + csshint: false, + csshintOptions: '', + htmlhint: false, + htmlhintOptions: '', + coffeescripthint: false, + coffeescripthintOptions: '', }; var $saveStatus = $('span.status'); var saveTimer = null; @@ -39,20 +54,26 @@ var $includejs = $('#includejs').prop('checked', currentSettings.includejs); var $focusedPanel = $('#focused-panel').val(currentSettings.focusedPanel); var $assetUrl = $('#asset-url').val(currentSettings.assetUrl); - var hints = ['js']; + var hints = ['js', 'css', 'html', 'coffeescript']; + var $ssl = $('#ssl'); // .prop('checked', currentSettings.ssl); // checking happens server side var $hints = {}; + var $hintsOptions = {}; + var $hintsOptWrapper = {}; + var hintsOptionsVal = {}; + var $hintsOptError = {}; + var $hintShow = {}; - var jshints = { - 'forin': 'About unsafe for..in', - 'eqnull': 'About == null', - 'noempty': 'About empty blocks', - 'eqeqeq': 'About unsafe comparisons', - 'boss': 'About assignments inside if/for/...', - 'undef': 'When variable is undefined', - 'unused': 'When variable is defined but not used', - 'curly': 'When blocks omit {}' - }; - var source = ''; + // var jshints = { + // 'forin': 'About unsafe for..in', + // 'eqnull': 'About == null', + // 'noempty': 'About empty blocks', + // 'eqeqeq': 'About unsafe comparisons', + // 'boss': 'About assignments inside if/for/...', + // 'undef': 'When variable is undefined', + // 'unused': 'When variable is defined but not used', + // 'curly': 'When blocks omit {}' + // }; + // var source = ''; for (var i = 0; i < panels.length; i++) { $panels[panels[i]] = $('#panel-' + panels[i]) @@ -60,22 +81,43 @@ } for (var m = 0; m < hints.length; m++) { + $hintsOptWrapper[hints[m]] = $('.' + hints[m] + 'hintOptWrapper') + .toggle(currentSettings[ hints[m] + 'hint' ]); $hints[hints[m]] = $('#' + hints[m] + 'hint') - .prop('checked', currentSettings[ hints[m] + 'hint' ]); - } + .prop('checked', currentSettings[ hints[m] + 'hint' ]) + .on('click', { el: $hintsOptWrapper[hints[m]] }, function(event) { + event.data.el.toggle(this.checked); + }); - for (var prop in jshints) { - if (jshints.hasOwnProperty(prop)) { - source += '
' + - '' + - '
'; + hintsOptionsVal[hints[m]] = JSON.stringify(currentSettings[ hints[m] + 'hintOptions'], undefined, 2); + + if (hintsOptionsVal[hints[m]] === '{}' || !currentSettings[ hints[m] + 'hintOptions']) { + hintsOptionsVal[hints[m]] = ''; + } + $hintsOptions[hints[m]] = $('#' + hints[m] + 'hintOptions') + .val(hintsOptionsVal[hints[m]]); + $hintsOptError[hints[m]] = $('#' + hints[m] + 'hintOptError'); + } + $hintShow = {}; + for (var key in hintShow) { + if (hintShow.hasOwnProperty(key)) { + $hintShow[key] = $('#hintShow-' + key).prop('checked', currentSettings.hintShow[key]); } } + // for (var prop in jshints) { + // if (jshints.hasOwnProperty(prop)) { + // source += '
' + + // '' + + // '
'; + // } + // } + // Listeners $(':checkbox').on('change', saveSettings); $('select').on('change', saveSettings); - $('input').on('blur', saveSettings); + $('input:not([type=checkbox])').on('blur', saveSettings); + $('textarea').on('blur', saveSettings); function saveSettings() { // Merge all our settings together @@ -90,20 +132,36 @@ for (var m = 0; m < hints.length; m++) { localStorageSettings[ hints[m] + 'hint' ] = $hints[hints[m]].prop('checked'); + $hintsOptError[ hints[m] ].html('').removeClass('show'); + try { + localStorageSettings[ hints[m] + 'hintOptions' ] = JSON.parse($hintsOptions[ hints[m] ].val() || '{}'); + } catch (e) { + $hintsOptError[ hints[m] ].html(e).addClass('show'); + } + } + localStorageSettings.hintShow = {}; + for (var key in hintShow) { + if (hintShow.hasOwnProperty(key)) { + localStorageSettings.hintShow[key] = $hintShow[key].prop('checked'); + } } localStorageSettings.includejs = $includejs.prop('checked'); localStorageSettings.focusedPanel = $focusedPanel.val(); localStorageSettings.assetUrl = $assetUrl.val(); + localStorageSettings.ssl = $ssl.prop('checked'); localStorage.settings = JSON.stringify(localStorageSettings); - console.log(localStorageSettings); clearTimeout(saveTimer); $saveStatus.addClass('show'); // Save on server $.ajax({ + beforeSend: function () { + clearTimeout(saveTimer); + $saveStatus.addClass('show'); + }, url: 'editor', type: 'POST', dataType: 'json', @@ -124,7 +182,7 @@ complete: function () { saveTimer = setTimeout(function () { $saveStatus.removeClass('show'); - }, 1000) + }, 1000); } }); } diff --git a/public/js/account/upgrade-page.js b/public/js/account/upgrade-page.js new file mode 100644 index 00000000..ce7c29dc --- /dev/null +++ b/public/js/account/upgrade-page.js @@ -0,0 +1,24 @@ +'use strict'; +/* globals $, backers */ + +var $backers = $('.backers'); + +backers.forEach(function (backer) { + var $a = $(''); + $a.attr('href', backer.url); + var $img = $(''); + $img.attr('src', backer.image).addClass('backer-img'); + $a.html($img); + $backers.append($a); +}); + +var $backers = $('.backers'); + +backers.forEach(function (backer) { + var $a = $(''); + $a.attr('href', backer.url); + var $img = $(''); + $img.attr('src', backer.image).addClass('backer-img'); + $a.html($img); + $backers.append($a); +}); diff --git a/public/js/account/upgrade.js b/public/js/account/upgrade.js new file mode 100644 index 00000000..8fa879bf --- /dev/null +++ b/public/js/account/upgrade.js @@ -0,0 +1,13 @@ +/* globals $ */ +(function () { + + var $input = $('#coupon_code'); + var $form = $('#stripe_pro_month'); + var originalAction = $form.attr('action'); + + $input.on('change', function () { + 'use strict'; + $form.attr('action', originalAction + '?' + $input.val()); + }); + +}()); diff --git a/public/js/chrome/analytics.js b/public/js/chrome/analytics.js index 19a66d96..87df080f 100644 --- a/public/js/chrome/analytics.js +++ b/public/js/chrome/analytics.js @@ -119,6 +119,16 @@ var analytics = { }, runconsole: function (from) { analytics.track(from || 'button', 'run console'); + }, + welcomePanelState: function (state) { + var s = 'close'; + if (state) { + s = 'open'; + } + analytics.track('state', 'welcome-panel', s); + }, + welcomePanelLink: function (url) { + analytics.track('welcome-panel-link', url); } }; diff --git a/public/js/chrome/app.js b/public/js/chrome/app.js index bbf051f2..e907627f 100644 --- a/public/js/chrome/app.js +++ b/public/js/chrome/app.js @@ -1,10 +1,8 @@ // if a gist has been requested, lazy load the gist library and plug it in -if (/gist(\/.*)?\/\d+/.test(window.location.pathname) && (!sessionStorage.getItem('javascript') && !sessionStorage.getItem('html'))) { +if (/gist\/.*/.test(window.location.pathname)) { window.editors = editors; // needs to be global when the callback triggers to set the content loadGist = function () { - $.getScript(jsbin.static + '/js/chrome/gist.js', function () { - window.gist = new Gist(window.location.pathname.replace(/.*?(\d+).*/, "$1")); - }); + window.gist = new Gist(window.location.pathname.replace(/.*\/([^/]+)$/, "$1")); }; if (editors.ready) { @@ -43,4 +41,4 @@ document.getElementsByTagName('head')[0].appendChild(link); if (jsbin.embed) { analytics.embed(); -} +} \ No newline at end of file diff --git a/public/js/chrome/archive.js b/public/js/chrome/archive.js new file mode 100644 index 00000000..2f414eb1 --- /dev/null +++ b/public/js/chrome/archive.js @@ -0,0 +1,52 @@ +function archive(unarchive) { + /*global jsbin, $, $document, analytics*/ + 'use strict'; + var type = unarchive === false ? 'unarchive' : 'archive'; + var text = unarchive === false ? 'restore from archive' : 'archiving'; + analytics[type](jsbin.getURL()); + if (!jsbin.user.name) { + $document.trigger('tip', { + type: 'notication', + content: 'You must be logged in and the owner of the bin to archive.' + }); + } else if (jsbin.owner()) { + $.ajax({ + type: 'POST', + url: jsbin.getURL() + '/' + type, + error: function () { + $document.trigger('tip', { + type: 'error', + content: 'The ' + text + ' failed. If this continues, please can you file an issue?' + }); + }, + success: function () { + jsbin.state.metadata.archive = unarchive !== false; + updateArchiveMenu(); + $document.trigger('tip', { + type: 'notication', + autohide: 5000, + content: 'This bin is now ' + (unarchive === false ? 'restored from the archive.' : 'archived.') + }); + } + }); + } else { + $document.trigger('tip', { + type: 'notication', + content: 'The ' + text + ' failed. You can only archive bins that you own.' + }); + } +} + +function updateArchiveMenu() { + if (jsbin.state.metadata && jsbin.state.metadata.archive) { + $('a.archivebin').hide(); + $('a.unarchivebin').show(); + } else { + $('a.archivebin').show(); + $('a.unarchivebin').hide(); + } +} + +updateArchiveMenu(); + +var unarchive = archive.bind(null, false); \ No newline at end of file diff --git a/public/js/chrome/errors.js b/public/js/chrome/errors.js index d2905ca3..251724c1 100644 --- a/public/js/chrome/errors.js +++ b/public/js/chrome/errors.js @@ -2,7 +2,14 @@ var jshint = function () { var source = editors.javascript.editor.getCode(); - var ok = JSHINT(source); + + // default jshint options + var options = { + 'eqnull': true + }; + + $.extend(options, jsbin.settings.jshintOptions || {}); + var ok = JSHINT(source, options); return ok ? true : JSHINT.data(); }; diff --git a/public/js/chrome/gist.js b/public/js/chrome/gist.js index 3e73161d..68517e3c 100644 --- a/public/js/chrome/gist.js +++ b/public/js/chrome/gist.js @@ -21,7 +21,6 @@ var Gist = (function () { // jshint ignore:line $.get('https://api.github.com/gists/' + id + token, function (data) { if (!data) {return;} $.each(data.files, function (fileName, fileData) { - console.log.apply(console, [].slice.call(arguments)); var ext = fileName.split('.').slice(-1).join(''); gist.code[ext] = fileData.content; }); @@ -38,7 +37,9 @@ var Gist = (function () { // jshint ignore:line panel = jsbin.panels.panels[target]; if (!panel) {return;} processors.set(target, processorInit.id); + jsbin.saveDisabled = true; panel.setCode(data); + jsbin.saveDisabled = false; }); }; diff --git a/public/js/chrome/infocard.js b/public/js/chrome/infocard.js index 9ffebbf1..4cbf6077 100644 --- a/public/js/chrome/infocard.js +++ b/public/js/chrome/infocard.js @@ -76,6 +76,7 @@ if (jsbin.state.streaming) { if (window.EventSource && owner) { listenStats(); + handleVisibility(); var url = jsbin.getURL(); $document.on('saved', function () { var newurl = window.location.toString(); @@ -91,6 +92,28 @@ } } + function handleVisibility() { + var hiddenProperty = 'hidden' in document ? 'hidden' : + 'webkitHidden' in document ? 'webkitHidden' : + 'mozHidden' in document ? 'mozHidden' : + null; + var visibilityStateProperty = 'visibilityState' in document ? 'visibilityState' : + 'webkitVisibilityState' in document ? 'webkitVisibilityState' : + 'mozVisibilityState' in document ? 'mozVisibilityState' : + null; + + if (visibilityStateProperty) { + var visibilityChangeEvent = hiddenProperty.replace(/hidden/i, 'visibilitychange'); + document.addEventListener(visibilityChangeEvent, function visibilityChangeEvent() { + if (document[hiddenProperty]) { // hidden + es.close(); + } else { + listenStats(); + } + }); + } + } + function updateStats(event, _data) { var data = _data ? JSON.parse(_data) : JSON.parse(event.data); @@ -117,8 +140,11 @@ } function listenStats() { - es = new EventSource(jsbin.getURL() + '/stats?checksum=' + jsbin.state.checksum); - es.addEventListener('stats', throttle(updateStats, 1000)); + if (window.EventSource && owner) { + // TODO use pagevisibility api to close connection + es = new EventSource(jsbin.getURL() + '/stats?checksum=' + jsbin.state.checksum); + es.addEventListener('stats', throttle(updateStats, 1000)); + } } } diff --git a/public/js/chrome/last-bin.js b/public/js/chrome/last-bin.js new file mode 100644 index 00000000..8a2099f3 --- /dev/null +++ b/public/js/chrome/last-bin.js @@ -0,0 +1,49 @@ +(function () { + 'use strict'; + + function getExpires() { + var d = new Date(); + + // expires in 1 hour from now + d.setTime(+d + 1000 * 60 * 60); + return d.toUTCString(); + } + + function save() { + var url = jsbin.getURL(true) + '/edit'; + if (url) { + document.cookie = 'last=' + encodeURIComponent(url) + '; expires=' + getExpires() + '; path=/'; + } else { + // expire cookie + document.cookie = 'last=""; expires=-1; path=/'; + } + } + + function readCookie(name) { + var nameEQ = name + '='; + var ca = document.cookie.split(';'); + for(var i=0;i < ca.length;i++) { + var c = ca[i]; + while (c.charAt(0)==' ') c = c.substring(1,c.length); + if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length); + } + return null; + } + + function updateBackButton() { + var el = document.getElementById('back'); + var back = readCookie('last'); + + if (el && back !== null && back !== '%2Fedit') { + el.href = decodeURIComponent(back); + } + } + + // save the bin url when the bin is saved, changed and when we load first time + if (jsbin && jsbin.getURL) { + $document.on('saved', save); + save(); + } else { + updateBackButton(); + } +})(); \ No newline at end of file diff --git a/public/js/chrome/navigation.js b/public/js/chrome/navigation.js index 10ae0275..feddb0ee 100644 --- a/public/js/chrome/navigation.js +++ b/public/js/chrome/navigation.js @@ -230,6 +230,11 @@ $('#runconsole').click(function () { return false; }); +$('#clearconsole').click(function () { + jsconsole.clear(); + return false; +}); + $('#showhelp').click(function () { $body.toggleClass('keyboardHelp'); keyboardHelpVisible = $body.is('.keyboardHelp'); @@ -512,6 +517,14 @@ $('a.deletebin').on('click', function (e) { } }); +$('a.archivebin').on('click', function (e) { + e.preventDefault(); + archive(); +}); +$('a.unarchivebin').on('click', function (e) { + e.preventDefault(); + archive(false); +}); }()); diff --git a/public/js/chrome/save.js b/public/js/chrome/save.js index 4a8b936f..f75dabf3 100644 --- a/public/js/chrome/save.js +++ b/public/js/chrome/save.js @@ -56,6 +56,7 @@ var split = $('#sharemenu').find('.share-split').length; // TODO candidate for removal function updateSavedState() { + 'use strict'; if (split) { return; } @@ -76,12 +77,21 @@ function updateSavedState() { $shareLinks.each(function () { var url = jsbin.getURL({ revision: withRevision }) + this.getAttribute('data-path') + (query && this.id !== 'livepreview' ? '?' + query : ''), nodeName = this.nodeName; + var hash = panels.getHighlightLines(); + + if (hash) { + hash = '#' + hash; + } + if (nodeName === 'A') { this.href = url; } else if (nodeName === 'INPUT') { this.value = url; + if (path === '/edit') { + this.value += hash; + } } else if (nodeName === 'TEXTAREA') { - this.value = ('' + documentTitle + '<' + 'script src="' + jsbin.static + '/js/embed.js"><' + '/script>').replace(/<>"&/g, function (m) { + this.value = ('' + documentTitle + '<' + 'script src="' + jsbin.static + '/js/embed.js"><' + '/script>').replace(/<>"&/g, function (m) { return { '<': '<', '>': '>', @@ -239,6 +249,13 @@ if (!jsbin.saveDisabled) { }); } +function compressKeys(keys, obj) { + obj.compressed = keys; + keys.split(',').forEach(function (key) { + obj[key] = LZString.compressToUTF16(obj[key]); + }); +} + function updateCode(panelId, callback) { var panelSettings = {}; @@ -246,17 +263,23 @@ function updateCode(panelId, callback) { panelSettings.processors = jsbin.state.processors; } + var data = { + code: jsbin.state.code, + revision: jsbin.state.revision, + method: 'update', + panel: panelId, + content: editors[panelId].getCode(), + checksum: saveChecksum, + settings: JSON.stringify(panelSettings), + }; + + if (jsbin.settings.useCompression) { + compressKeys('content', data); + } + $.ajax({ url: jsbin.getURL() + '/save', - data: { - code: jsbin.state.code, - revision: jsbin.state.revision, - method: 'update', - panel: panelId, - content: editors[panelId].getCode(), - checksum: saveChecksum, - settings: JSON.stringify(panelSettings) - }, + data: data, type: 'post', dataType: 'json', headers: {'Accept': 'application/json'}, @@ -345,15 +368,23 @@ function saveCode(method, ajax, ajaxCallback) { jsbin.panels.save(); jsbin.panels.saveOnExit = true; + var data = $form.serializeArray().reduce(function(obj, data) { + obj[data.name] = data.value; + return obj; + }, {}); + + if (jsbin.settings.useCompression) { + compressKeys('html,css,javascript', data); + } + if (ajax) { $.ajax({ url: $form.attr('action'), - data: $form.serialize(), + data: data, dataType: 'json', type: 'post', headers: {'Accept': 'application/json'}, success: function (data) { - $form.attr('action', data.url + '/save'); if (ajaxCallback) { ajaxCallback(data); } @@ -365,10 +396,14 @@ function saveCode(method, ajax, ajaxCallback) { jsbin.state.code = data.code; jsbin.state.revision = data.revision; jsbin.state.latest = true; // this is never not true...end of conversation! + jsbin.state.metadata = { name: jsbin.user.name }; + $form.attr('action', jsbin.getURL() + '/save'); if (window.history && window.history.pushState) { // updateURL(edit); - window.history.pushState(null, '', jsbin.getURL() + '/edit'); + var hash = panels.getHighlightLines(); + if (hash) {hash = '#' + hash;} + window.history.pushState(null, '', jsbin.getURL() + '/edit' + hash); sessionStorage.setItem('url', jsbin.getURL()); } else { window.location.hash = data.edit; diff --git a/public/js/chrome/settings.js b/public/js/chrome/settings.js new file mode 100644 index 00000000..0eaa6b75 --- /dev/null +++ b/public/js/chrome/settings.js @@ -0,0 +1,27 @@ +/*global jsbin, $*/ + +var settings = { + save: function () { + localStorage.setItem('settings', JSON.stringify(jsbin.settings)); + + $.ajax({ + url: '/account/editor', + type: 'POST', + dataType: 'json', + data: { + settings: localStorage.settings, + _csrf: jsbin.state.token + }, + success: function() { + if (console && console.log) { + console.log('Success on saving settings'); + } + }, + error: function(xhr, status) { + if (console && console.log) { + console.log('Error: ' + status); + } + } + }); + } +}; \ No newline at end of file diff --git a/public/js/chrome/welcome-panel.js b/public/js/chrome/welcome-panel.js new file mode 100644 index 00000000..9d7fa2f9 --- /dev/null +++ b/public/js/chrome/welcome-panel.js @@ -0,0 +1,70 @@ +(function () { + /*global jsbin, $, $body, $document, analytics, settings*/ + 'use strict'; + + if (!$('#toppanel').length) { + return; + } + + if (jsbin.settings.gui === undefined) { + jsbin.settings.gui = {}; + } + if (jsbin.settings.gui.toppanel === undefined) { + jsbin.settings.gui.toppanel = true; + localStorage.setItem('settings', JSON.stringify(jsbin.settings)); + } + + if ($body.hasClass('toppanel') && jsbin.settings.gui.toppanel === false) { + $body.addClass('toppanel-close'); + $body.removeClass('toppanel'); + } + + // analytics for panel state + analytics.welcomePanelState(jsbin.settings.gui.toppanel); + + var removeToppanel = function() { + jsbin.settings.gui.toppanel = false; + settings.save(); + $body.addClass('toppanel-close'); + $body.removeClass('toppanel'); + }; + + var showToppanel = function() { + jsbin.settings.gui.toppanel = true; + settings.save(); + $body.removeClass('toppanel-close'); + $body.addClass('toppanel'); + }; + + // to remove + var goSlow = function(e) { + $body.removeClass('toppanel-slow'); + if (e.shiftKey) { + $body.addClass('toppanel-slow'); + } + }; + + $('.toppanel-hide').click(function(event) { + event.preventDefault(); + goSlow(event); + removeToppanel(); + }); + $('.toppanel-logo').click(function(event) { + event.preventDefault(); + goSlow(event); + showToppanel(); + }); + $document.keydown(function (event) { + if (event.which === 27) { + if ($body.hasClass('toppanel')) { + removeToppanel(); + } + } + }); + + // analytics for links + $('#toppanel').find('.toppanel-link').mousedown(function() { + analytics.welcomePanelLink(this.href); + }); + +}()); \ No newline at end of file diff --git a/public/js/editors/addons.js b/public/js/editors/addons.js index a723645e..73044545 100644 --- a/public/js/editors/addons.js +++ b/public/js/editors/addons.js @@ -11,13 +11,39 @@ fold: false, sublime: false, tern: false, - activeline: true + activeline: true, + matchbrackets: false }; if (!jsbin.settings.addons) { jsbin.settings.addons = defaults; } + var detailsSupport = 'open' in document.createElement('details'); + + var settingsHints = {}; + var settingsHintShow = {}; + var hintShow = { + console: true, + line: false, + under: false, + gutter: false + }; + // css must go last for the moment due to CSSLint creating the + // global variable 'exports' + ['js', 'html', 'coffeescript', 'css'].forEach(function (val) { + var h = val + 'hint'; + var d = false; + if (val === 'js') { + d = true; + } + settingsHints[h] = (jsbin.settings[h] !== undefined) ? jsbin.settings[h] : d; + }); + + settingsHintShow = $.extend({}, hintShow, jsbin.settings.hintShow); + settingsHintShow.tooltip = settingsHintShow.gutter; + var settingsAddons = $.extend({}, jsbin.settings.addons, settingsHints); + var addons = { closebrackets: { url: '/js/vendor/codemirror4/addon/edit/closebrackets.js', @@ -102,7 +128,10 @@ cm.foldCode(cm.getCursor()); }}); setOption(cm, 'foldGutter', true); - setOption(cm, 'gutters', ['CodeMirror-linenumbers', 'CodeMirror-foldgutter']); + var gutters = cm.getOption('gutters'); + gutters.push('CodeMirror-linenumbers'); + gutters.push('CodeMirror-foldgutter'); + setOption(cm, 'gutters', gutters); } }, sublime: { @@ -143,7 +172,7 @@ ], test: function () { return jsbin.panels.panels.javascript.editor.openDialog && - (typeof window.ternBasicDefs !== undefined) && + (typeof window.ternBasicDefs !== 'undefined') && CodeMirror.showHint && CodeMirror.TernServer && CodeMirror.startTern; @@ -157,11 +186,83 @@ '/js/vendor/codemirror4/addon/selection/active-line.js' ], test: function() { - return CodeMirror.defaults.styleActiveLine !== undefined; + return (typeof CodeMirror.defaults.styleActiveLine !== 'undefined'); }, done: function(cm) { setOption(cm, 'styleActiveLine', true); } + }, + matchbrackets: { + url: [], + test: function() { + return (typeof CodeMirror.defaults.matchBrackets !== 'undefined'); + }, + done: function(cm) { + setOption(cm, 'matchBrackets', true); + } + }, + csshint: { + url: [ + '/js/vendor/csslint/csslint.min.js', + '/js/vendor/cm_addons/lint/css-lint.js' + ], + test: function() { + return hintingTest('css') && + (typeof CSSLint !== 'undefined'); + }, + done: function(cm) { + if (cm.getOption('mode') !== 'css') { + return; + } + hintingDone(cm); + } + }, + jshint: { + url: [], + test: function() { + return hintingTest('javascript') && + (typeof JSHINT !== 'undefined'); + }, + done: function(cm) { + if (cm.getOption('mode') !== 'javascript') { + return; + } + hintingDone(cm, { + 'eqnull': true + }); + } + }, + htmlhint: { + url: [ + '/js/vendor/htmlhint/htmlhint.js', + '/js/vendor/cm_addons/lint/html-lint.js' + ], + test: function() { + return hintingTest('htmlmixed') && + (typeof HTMLHint !== 'undefined'); + }, + done: function(cm) { + if (cm.getOption('mode') !== 'htmlmixed') { + return; + } + hintingDone(cm); + } + }, + coffeescripthint: { + url: [ + '/js/vendor/coffeelint/coffeelint.min.js', + '/js/vendor/cm_addons/lint/coffeescript-lint.js' + ], + test: function() { + return hintingTest('coffeescript') && + (typeof coffeelint !== 'undefined'); + }, + done: function(cm) { + if (cm.getOption('mode') !== 'coffeescript') { + return; + } + hintingDone(cm); + } } }; @@ -215,15 +316,61 @@ function defaultTest(prop) { return function () { - return CodeMirror.optionHandlers[prop] !== undefined; + return (typeof CodeMirror.optionHandlers[prop] !== 'undefined'); }; } - var options = Object.keys(jsbin.settings.addons); + function hintingTest(mode) { + return (typeof CodeMirror.defaults.lint !== 'undefined') && + CodeMirror.helpers.lint && + CodeMirror.helpers.lint[mode] && + CodeMirror.optionHandlers.lint; + } + + window.hintingDone = function(cm, defhintOptions) { + var mode = cm.getOption('mode'); + if (mode === 'javascript') { + mode = 'js'; + } + if (mode === 'htmlmixed') { + mode = 'html'; + } + var opt = $.extend({}, settingsHintShow); + opt.consoleParent = cm.getWrapperElement().parentNode.parentNode; + setOption(cm, 'lintOpt', opt); + setOption(cm, 'lintRules', $.extend({}, defhintOptions, jsbin.settings[mode + 'hintOptions'])); + if (opt.gutter) { + var gutters = cm.getOption('gutters'); + if (gutters.indexOf('CodeMirror-lint-markers') === -1) { + gutters.push('CodeMirror-lint-markers'); + setOption(cm, 'gutters', gutters); + } + setOption(cm, 'lint', true); + var ln = cm.getOption('lineNumbers'); + setOption(cm, 'lineNumbers', !ln); + setOption(cm, 'lineNumbers', ln); + } else { + setOption(cm, 'lint', true); + } + if (opt.console) { + $document.trigger('sizeeditors'); + $(cm.consolelint.head).on('click', function() { + if (!detailsSupport) { + $(this).nextAll().toggle(); + } + // trigger a resize after the click has completed and the details is close + setTimeout(function () { + $document.trigger('sizeeditors'); + }, 10); + }); + } + } + + var options = Object.keys(settingsAddons); function loadAddon(key) { var addon = addons[key]; - if (addon && jsbin.settings.addons[key]) { + if (addon && settingsAddons[key]) { if (typeof addon.url === 'string') { addon.url = [addon.url]; } @@ -254,4 +401,12 @@ } }; + // External method to realod the selected addon + // may be useful in the future + // window.reloadSelectedAddon = function(addon) { + // if (options.indexOf(addon) !== -1) { + // loadAddon(addon); + // } + // }; + })(); \ No newline at end of file diff --git a/public/js/editors/editors.js b/public/js/editors/editors.js index f052bbcd..b652d958 100644 --- a/public/js/editors/editors.js +++ b/public/js/editors/editors.js @@ -110,12 +110,12 @@ panels.restore = function () { width = $window.width(), deferredCodeInsert = '', focused = !!sessionStorage.getItem('panel'), - validPanels = 'live javascript html css console'.split(' '); + validPanels = 'live javascript html css console'.split(' '), + cachedHash = ''; - // TODO document why this happens... - // if (history.replaceState && (location.pathname.indexOf('/edit') !== -1) || ((location.origin + location.pathname) === jsbin.getURL() + '/')) { - // history.replaceState(null, '', jsbin.getURL() + (jsbin.getURL() === jsbin.root ? '' : '/edit')); - // } + if (history.replaceState && (location.pathname.indexOf('/edit') !== -1) || ((location.origin + location.pathname) === jsbin.getURL() + '/')) { + history.replaceState(null, '', jsbin.getURL() + (jsbin.getURL() === jsbin.root ? '' : '/edit') + (hash ? '#' + hash : '')); + } if (search || hash) { var query = (search || hash); @@ -306,6 +306,23 @@ panels.savecontent = function () { } }; +panels.getHighlightLines = function () { + 'use strict'; + var hash = []; + var lines = ''; + var panel; + for (name in panels.panels) { + panel = panels.panels[name]; + if (panel.editor) { + lines = panel.editor.highlightLines().string; + if (lines) { + hash.push(name.substr(0, 1).toUpperCase() + ':L' + lines); + } + } + } + return hash.join(','); +}; + panels.focus = function (panel) { this.focused = panel; if (panel) { @@ -579,16 +596,31 @@ var editorsReady = setInterval(function () { var ready = true, resizeTimer = null, panel, - panelId; + panelId, + hash = window.location.hash.substring(1); + for (panelId in panels.panels) { panel = panels.panels[panelId]; - if (panel.visible && !panel.ready) ready = false; + if (panel.visible && !panel.ready) { + ready = false; + break; + } } panels.ready = ready; if (ready) { + panels.allEditors(function (panel) { + var key = panel.id.substr(0, 1).toUpperCase() + ':L'; + if (hash.indexOf(key) !== -1) { + var lines = hash.match(new RegExp(key + '(\\d+(?:-\\d+)?)')); + if (lines !== null) { + panel.editor.highlightLines(lines[1]); + } + } + }); + clearInterval(editorsReady); // panels.ready = true; // if (typeof editors.onReady == 'function') editors.onReady(); diff --git a/public/js/editors/keycontrol.js b/public/js/editors/keycontrol.js index b0fded06..13372c59 100644 --- a/public/js/editors/keycontrol.js +++ b/public/js/editors/keycontrol.js @@ -47,20 +47,18 @@ if (!customKeys.disabled) { if (event.ctrlKey) { event.metaKey = true; } + if (event.metaKey && event.which === 89) { + archive(!event.shiftKey); + return event.preventDefault(); + } + if (event.metaKey && event.which === 79) { // open $('a.homebtn').trigger('click', 'keyboard'); event.preventDefault(); } else if (event.metaKey && event.shiftKey && event.which === 8) { // cmd+shift+backspace $('a.deletebin:first').trigger('click', 'keyboard'); event.preventDefault(); - // } else if (event.altKey && event.which === 83) { // open share menu - // var $sharemenu = $('#sharemenu'); - // if ($sharemenu.hasClass('open')) { - - // } - // $('#sharemenu a').trigger('mousedown'); - // event.preventDefault(); - } else if (event.metaKey && event.which === 83) { // save + } else if (!jsbin.embed && event.metaKey && event.which === 83) { // save if (event.shiftKey === false) { if (saveChecksum) { saveChecksum = false; diff --git a/public/js/editors/libraries.js b/public/js/editors/libraries.js index 530cc022..20e33bcd 100644 --- a/public/js/editors/libraries.js +++ b/public/js/editors/libraries.js @@ -33,6 +33,15 @@ var libraries = [ 'label': 'jQuery UI WIP (via git)', 'group': 'jQuery UI' }, + { + 'url': [ + 'http://code.jquery.com/ui/1.11.0/themes/smoothness/jquery-ui.min.css', + 'http://code.jquery.com/jquery-1.11.0.min.js', + 'http://code.jquery.com/ui/1.11.0/jquery-ui.min.js' + ], + 'label': 'jQuery UI 1.11.0', + 'group': 'jQuery UI' + }, { 'url': [ 'http://code.jquery.com/ui/1.10.4/themes/smoothness/jquery-ui.min.css', @@ -99,8 +108,8 @@ var libraries = [ { 'url': [ 'http://code.jquery.com/jquery.min.js', - 'http://getbootstrap.com/dist/css/bootstrap.css', - 'http://getbootstrap.com/dist/js/bootstrap.js' + 'http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css', + 'http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js' ], 'label': 'Bootstrap Latest', 'group': 'Bootstrap' @@ -108,9 +117,8 @@ var libraries = [ { 'url': [ 'http://code.jquery.com/jquery.min.js', - 'http://getbootstrap.com/2.3.2/assets/css/bootstrap.css', - 'http://getbootstrap.com/2.3.2/assets/css/bootstrap-responsive.css', - 'http://getbootstrap.com/2.3.2/assets/js/bootstrap.js' + 'http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/css/bootstrap-combined.min.css', + 'http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/js/bootstrap.min.js' ], 'label': 'Bootstrap 2.3.2', 'group': 'Bootstrap' @@ -157,8 +165,13 @@ var libraries = [ 'group': 'YUI' }, { - 'url': 'http://ajax.googleapis.com/ajax/libs/mootools/1/mootools-yui-compressed.js', - 'label': 'MooTools latest', + 'url': 'http://ajax.googleapis.com/ajax/libs/mootools/1.5.0/mootools-yui-compressed.js', + 'label': 'MooTools 1.5.0', + 'group': 'MooTools' + }, + { + 'url': 'http://ajax.googleapis.com/ajax/libs/mootools/1.5.0/mootools-nocompat-yui-compressed.js', + 'label': 'MooTools 1.5.0 (without 1.2+ compatibility layer)', 'group': 'MooTools' }, { @@ -205,6 +218,30 @@ var libraries = [ 'label': 'Dijit 1.7.4 (Claro)', 'group': 'Dojo' }, + { + 'url': [ + 'http://cdn.kendostatic.com/2014.1.528/styles/kendo.common.min.css', + 'http://cdn.kendostatic.com/2014.1.528/styles/kendo.default.min.css', + 'http://code.jquery.com/jquery-1.9.1.min.js', + 'http://cdn.kendostatic.com/2014.1.528/js/kendo.ui.core.min.js' + ], + 'label': 'Kendo UI Core Q1 SP2', + 'group': 'Kendo UI' + }, + { + 'url': [ + 'http://cdn.kendostatic.com/2014.1.318/styles/kendo.common.min.css', + 'http://cdn.kendostatic.com/2014.1.318/styles/kendo.rtl.min.css', + 'http://cdn.kendostatic.com/2014.1.318/styles/kendo.default.min.css', + 'http://cdn.kendostatic.com/2014.1.318/styles/kendo.dataviz.min.css', + 'http://cdn.kendostatic.com/2014.1.318/styles/kendo.dataviz.default.min.css', + 'http://cdn.kendostatic.com/2014.1.318/styles/kendo.mobile.all.min.css', + 'http://code.jquery.com/jquery-1.9.1.min.js', + 'http://cdn.kendostatic.com/2014.1.318/js/kendo.all.min.js' + ], + 'label': 'Kendo UI Q1 2014', + 'group': 'Kendo UI' + }, { 'url': [ 'http://cdn.kendostatic.com/2013.3.1119/styles/kendo.common.min.css', @@ -219,20 +256,6 @@ var libraries = [ 'label': 'Kendo UI Q3 2013', 'group': 'Kendo UI' }, - { - 'url': [ - 'http://cdn.kendostatic.com/2013.2.716/styles/kendo.common.min.css', - 'http://cdn.kendostatic.com/2013.2.716/styles/kendo.rtl.min.css', - 'http://cdn.kendostatic.com/2013.2.716/styles/kendo.default.min.css', - 'http://cdn.kendostatic.com/2013.2.716/styles/kendo.dataviz.min.css', - 'http://cdn.kendostatic.com/2013.2.716/styles/kendo.dataviz.default.min.css', - 'http://cdn.kendostatic.com/2013.2.716/styles/kendo.mobile.all.min.css', - 'http://code.jquery.com/jquery-1.9.1.min.js', - 'http://cdn.kendostatic.com/2013.2.716/js/kendo.all.min.js' - ], - 'label': 'Kendo UI Q2 2013', - 'group': 'Kendo UI' - }, { 'url' : [ 'http://code.jquery.com/qunit/qunit-git.css', @@ -261,6 +284,11 @@ var libraries = [ 'label': 'Angular 1.2.14 Stable', 'group': 'Angular' }, + { + 'url': 'https://ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular.js', + 'label': 'Angular 1.2.14 Uncompressed', + 'group': 'Angular' + }, { 'url':'https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js', 'label': 'Angular 1.0.7 Stable', @@ -300,6 +328,26 @@ var libraries = [ 'label': 'Enyo 2.2.0', 'group': 'Enyo' }, + { + 'url': '//cdnjs.cloudflare.com/ajax/libs/bluebird/1.2.2/bluebird.js', + 'label': 'Bluebird 1.2.2', + 'group': 'Promises' + }, + { + 'url': 'https://www.promisejs.org/polyfills/promise-4.0.0.js', + 'label': 'Promise 4.0.0', + 'group': 'Promises' + }, + { + 'url': '//cdnjs.cloudflare.com/ajax/libs/q.js/1.0.1/q.js', + 'label': 'Q 1.0.1', + 'group': 'Promises' + }, + { + 'url': '//cdn.jsdelivr.net/rsvp/3.0.6/rsvp.js', + 'label': 'RSVP 3.0.6', + 'group': 'Promises' + }, { 'url': [ 'https://rawgithub.com/ai/autoprefixer-rails/master/vendor/autoprefixer.js' @@ -375,8 +423,15 @@ var libraries = [ 'label': 'Lo-Dash 2.4.1' }, { - 'url': 'http://cdnjs.cloudflare.com/ajax/libs/modernizr/2.6.2/modernizr.min.js', - 'label': 'Modernizr 2.6.2' + 'url': 'http://modernizr.com/downloads/modernizr-latest.js', + 'label': 'Modernizr Development latest' + }, + { + 'url': [ + 'http://cdnjs.cloudflare.com/ajax/libs/modernizr/2.6.2/modernizr.min.js', + 'http://cdnjs.cloudflare.com/ajax/libs/detectizr/1.5.0/detectizr.min.js' + ], + 'label': 'Detectizr 1.5.0' }, { 'url': 'http://cdnjs.cloudflare.com/ajax/libs/prefixfree/1.0.7/prefixfree.min.js', @@ -390,6 +445,10 @@ var libraries = [ 'url': 'http://d3js.org/d3.v3.min.js', 'label': 'D3 3.x' }, + { + 'url': '//code.highcharts.com/highcharts.js', + 'label': 'Highcharts latest' + }, { 'url': 'http://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js', 'label': 'Raphaël 2.1.0' @@ -450,10 +509,10 @@ var libraries = [ }, { 'url': [ - '//cdnjs.cloudflare.com/ajax/libs/polymer/0.2.3/platform.js', - '//cdnjs.cloudflare.com/ajax/libs/polymer/0.2.3/polymer.js' + '//cdnjs.cloudflare.com/ajax/libs/polymer/0.3.3/platform.js', + '//cdnjs.cloudflare.com/ajax/libs/polymer/0.3.3/polymer.js' ], - 'label': 'Polymer 0.2.3' + 'label': 'Polymer 0.3.3' }, { 'url': '//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css', @@ -475,6 +534,10 @@ var libraries = [ { 'url': 'http://cdnjs.cloudflare.com/ajax/libs/gsap/1.11.7/TweenMax.min.js', 'label': 'GSAP 1.11.7' + }, + { + 'url': '//cdnjs.cloudflare.com/ajax/libs/phaser/2.0.5/phaser.min.js', + 'label': 'Phaser 2.0.5' } ]; diff --git a/public/js/editors/panel.js b/public/js/editors/panel.js index 882563d5..c9ab349b 100644 --- a/public/js/editors/panel.js +++ b/public/js/editors/panel.js @@ -42,6 +42,7 @@ CodeMirror.commands.snippets = function (cm) { }; var Panel = function (name, settings) { + 'use strict'; var panel = this, showPanelButton = true, $panel = null, @@ -74,17 +75,18 @@ var Panel = function (name, settings) { } // this is nasty and wrong, but I'm going to put here anyway .i.. - if (this.id === 'javascript') { - this.on('processor', function (e, preprocessor) { - if (preprocessor === 'none') { - jshintEnabled = true; - checkForErrors(); - } else { - jshintEnabled = false; - $error.hide(); - } - }); - } + // removed as we have a different way to check for errors + // if (this.id === 'javascript') { + // this.on('processor', function (e, preprocessor) { + // if (preprocessor === 'none') { + // jshintEnabled = true; + // checkForErrors(); + // } else { + // jshintEnabled = false; + // $error.hide(); + // } + // }); + // } if (settings.editor) { cmSettings = { @@ -93,7 +95,9 @@ var Panel = function (name, settings) { dragDrop: false, // we handle it ourselves mode: editorModes[panelLanguage], lineWrapping: true, - theme: jsbin.settings.theme || 'jsbin' + // gutters: ['line-highlight'], + theme: jsbin.settings.theme || 'jsbin', + highlighLine: true }; $.extend(cmSettings, jsbin.settings.editor || {}); @@ -114,11 +118,27 @@ var Panel = function (name, settings) { profile: name /* define Zen Coding output profile */ }); + // make sure tabSize and indentUnit are numbers + if (typeof cmSettings.tabSize === 'string') { + cmSettings.tabSize = parseInt(cmSettings.tabSize, 10) || 2; + } + if (typeof cmSettings.indentUnit === 'string') { + cmSettings.indentUnit = parseInt(cmSettings.indentUnit, 10) || 2; + } + panel.editor = CodeMirror.fromTextArea(panel.el, cmSettings); + panel.editor.on('highlightLines', function () { + window.location.hash = panels.getHighlightLines(); + }); + // Bind events using CM3 syntax panel.editor.on('change', function codeChange(cm, changeObj) { - $document.trigger('codeChange', [{ panelId: panel.id, revert: true, origin: changeObj.origin }]); + if (jsbin.saveDisabled) { + $document.trigger('codeChange.live', [{ panelId: panel.id, revert: true, origin: changeObj.origin }]); + } else { + $document.trigger('codeChange', [{ panelId: panel.id, revert: true, origin: changeObj.origin }]); + } return true; }); @@ -433,14 +453,8 @@ Panel.prototype = { var height = panel.editor.scroller.closest('.panel').outerHeight(), offset = 0; // offset = panel.$el.find('> .label').outerHeight(); - - // special case for the javascript panel - if (panel.name === 'javascript') { - if ($error === null) { // it wasn't there right away, so we populate - $error = panel.$el.find('details'); - } - offset += ($error.filter(':visible').height() || 0); - } + $error = panel.$el.find('details'); + offset += ($error.filter(':visible').height() || 0); if (!jsbin.lameEditor) { editor.scroller.height(height - offset); @@ -509,7 +523,7 @@ function populateEditor(editor, panel) { if (!editor.codeSet) { // populate - should eventually use: session, saved data, local storage var cached = sessionStorage.getItem('jsbin.content.' + panel), // session code - saved = localStorage.getItem('saved-' + panel), // user template + saved = jsbin.embed ? null : localStorage.getItem('saved-' + panel), // user template sessionURL = sessionStorage.getItem('url'), changed = false; @@ -528,7 +542,7 @@ function populateEditor(editor, panel) { // tell the document that it's currently being edited, but check that it doesn't match the saved template // because sessionStorage gets set on a reload changed = cached != saved && cached != template[panel]; - } else if (saved !== null && !/edit/.test(window.location) && !window.location.search) { // then their saved preference + } else if (!template.post && saved !== null && !/(edit|embed)$/.test(window.location) && !window.location.search) { // then their saved preference editor.setCode(saved); } else { // otherwise fall back on the JS Bin default editor.setCode(template[panel]); diff --git a/public/js/editors/tern.js b/public/js/editors/tern.js index cbac223c..68d88de6 100644 --- a/public/js/editors/tern.js +++ b/public/js/editors/tern.js @@ -2,12 +2,16 @@ 'use strict'; /*globals $, jsbin, CodeMirror, template, ternDefinitions, ternBasicDefs */ + if (!jsbin.settings.addons.tern) { + return; + } var ternServer; var ternLoaded = {}; var initTern = function(editor, defs){ var keyMap = { + 'Ctrl-Q': function(cm) { ternServer.selectName(cm); }, 'Ctrl-I': function(cm) { ternServer.showType(cm); }, 'Ctrl-Space': function(cm) { ternServer.complete(cm); } }; @@ -90,7 +94,7 @@ if (cm.options.indentWithTabs) { indent = '\t'; } else { - indent = new Array(cm.options.indentUnit + 1).join(' '); + indent = new Array(cm.options.indentUnit * 1 + 1).join(' '); } if (tok.string === ';') { diff --git a/public/js/intro-start.js b/public/js/intro-start.js new file mode 100644 index 00000000..cac7c20e --- /dev/null +++ b/public/js/intro-start.js @@ -0,0 +1 @@ +function start(template, jsbin, window, document, undefined) { \ No newline at end of file diff --git a/public/js/jsbin.js b/public/js/jsbin.js index 756adb78..3dbe7a2d 100644 --- a/public/js/jsbin.js +++ b/public/js/jsbin.js @@ -1,11 +1,13 @@ try { - console.log('init'); + console.log('Dave is ready.'); } catch (e) { - var console = { + window.console = { log: function () { // alert([].slice.call(arguments).join('\n')); }, - warn: function () {} + warn: function () {}, + trace: function () {}, + error: function () {} }; } @@ -57,20 +59,47 @@ function dedupe(array) { return results; } +function exposeSettings() { + 'use strict'; + if (window.jsbin instanceof Node || !window.jsbin) { // because...STUPIDITY!!! + window.jsbin = { + 'static': jsbin['static'] + }; // create the holding object + + if (jsbin.state.metadata && jsbin.user && jsbin.state.metadata.name === jsbin.user.name && jsbin.user.name) { + window.jsbin.settings = jsbin.settings; + return; + } + + var key = 'o' + (Math.random() * 1).toString(32).slice(2); + Object.defineProperty(window, key, { + get:function () { + window.jsbin.settings = jsbin.settings; + console.log('jsbin.settings can how be modified on the console'); + } + }); + if (!jsbin.embed) { + console.log('To edit settings, type this string into the console: ' + key); + } + } +} -window['jsbin'] || (window.jsbin = {}); -// dodgy? var storedSettings = localStorage.getItem('settings'); if (storedSettings === "undefined") { // yes, equals the *string* "undefined", then something went wrong storedSettings = null; } -window.jsbin.settings = $.extend(JSON.parse(storedSettings || '{}'), jsbin.settings); +// In all cases localStorage takes precedence over user settings so users can +// configure it from the console and overwrite the server delivered settings +jsbin.settings = $.extend({}, jsbin.settings, JSON.parse(storedSettings || '{}')); if (jsbin.user) { - $.extend(window.jsbin.settings, jsbin.user.settings); + jsbin.settings = $.extend({}, jsbin.user.settings, jsbin.settings); } + +exposeSettings(); + // if the above code isn't dodgy, this for hellz bells is: jsbin.mobile = /WebKit.*Mobile.*|Android/.test(navigator.userAgent); jsbin.tablet = /iPad/i.test(navigator.userAgent); // sue me. @@ -91,10 +120,6 @@ jsbin.ie = (function(){ if (!storedSettings && (location.origin + location.pathname) === jsbin.root + '/') { // first timer - let's welcome them shall we, Dave? localStorage.setItem('settings', '{}'); - if (!jsbin.custom) { - window.location = jsbin.root + '/welcome/1/edit?html,live' - + (location.search.indexOf('api=') !== -1 ? ',&' + location.search.substring(1) : ''); - } } if (!jsbin.settings.editor) { @@ -120,6 +145,10 @@ jQuery.ajaxPrefilter(function (options, original, xhr) { } }); +jsbin.owner = function () { + return jsbin.user && jsbin.user.name && jsbin.state.metadata && jsbin.state.metadata.name === jsbin.user.name; +}; + jsbin.getURL = function (options) { if (!options) { options = {}; } diff --git a/public/js/outro-start.js b/public/js/outro-start.js new file mode 100644 index 00000000..ff30235f --- /dev/null +++ b/public/js/outro-start.js @@ -0,0 +1 @@ +} \ No newline at end of file diff --git a/public/js/outro.js b/public/js/outro.js index 08a0186d..4b3cc37b 100644 --- a/public/js/outro.js +++ b/public/js/outro.js @@ -1 +1 @@ -})(this, document); \ No newline at end of file +})(window, document); \ No newline at end of file diff --git a/public/js/processors/processor.js b/public/js/processors/processor.js index c9b06d83..d602dc0b 100644 --- a/public/js/processors/processor.js +++ b/public/js/processors/processor.js @@ -279,7 +279,7 @@ var processors = jsbin.processors = (function () { id: 'less', target: 'css', extensions: ['less'], - url: jsbin.static + '/js/vendor/less-1.4.2.min.js', + url: jsbin.static + '/js/vendor/less-1.7.3.min.js', init: function (ready) { // In CodeMirror 4, less is now included in the css mode, so no files to load ready(); @@ -449,6 +449,26 @@ var processors = jsbin.processors = (function () { delete jsbin.state.processors[panelId]; delete panel.type; } + + // linting + mmMode = cmMode; + if (cmMode === 'javascript') { + mmMode = 'js'; + } + if (cmMode === 'htmlmixed') { + mmMode = 'html'; + } + var isHint = panel.editor.getOption('lint'); + if (isHint) { + panel.editor.lintStop(); + } + if (jsbin.settings[mmMode + 'hint']) { + panel.editor.setOption('mode', cmMode); + if (typeof hintingDone !== 'undefined') { + panel.editor.setOption('mode', cmMode); + hintingDone(panel.editor); + } + } }; processors.reset = function (panelId) { diff --git a/public/js/prod/addon-tern-3.15.3.min.js b/public/js/prod/addon-tern-3.15.3.min.js new file mode 100644 index 00000000..e1d3698b --- /dev/null +++ b/public/js/prod/addon-tern-3.15.3.min.js @@ -0,0 +1,5 @@ +!function(a){"object"==typeof exports&&"object"==typeof module?a(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],a):a(CodeMirror)}(function(a){"use strict";function b(a,b,c){var d=a.docs[b];d?c(D(a,d)):a.options.getFile?a.options.getFile(b,c):c(null)}function c(a,b,c){for(var d in a.docs){var e=a.docs[d];if(e.doc==b)return e}if(!c)for(var f=0;;++f)if(d="[doc"+(f||"")+"]",!a.docs[d]){c=d;break}return a.addDoc(c,b)}function d(a,b,d){var f=c(a,b),g=a.cachedArgHints;g&&g.doc==b&&J(g.start,d.to)<=0&&(a.cachedArgHints=null);var h=f.changed;null==h&&(f.changed=h={from:d.from.line,to:d.from.line});var i=d.from.line+(d.text.length-1);d.from.line=h.to&&(h.to=i+1),h.from>d.from.line&&(h.from=d.from.line),b.lineCount()>H&&d.to-h.from>100&&setTimeout(function(){f.changed&&f.changed.to-f.changed.from>100&&e(a,f)},200)}function e(a,b){a.server.request({files:[{type:"full",name:b.name,text:D(a,b)}]},function(a){a?window.console.error(a):b.changed=null})}function f(b,c,d){b.request(c,{type:"completions",types:!0,docs:!0,urls:!0},function(e,f){if(e)return B(b,c,e);var h=[],i="",j=f.start,k=f.end;'["'==c.getRange(F(j.line,j.ch-2),j)&&'"]'!=c.getRange(k,F(k.line,k.ch+2))&&(i='"]');for(var l=0;l=m;--l){for(var o=c.getLine(l),p=0,q=0;;){var r=o.indexOf(" ",q);if(-1==r)break;p+=i-(r+p)%i-1,q=r+1}if(g=f.column-p,"("==o.charAt(g)){n=!0;break}}if(n){var s=F(l,g),t=b.cachedArgHints;return t&&t.doc==c.getDoc()&&0==J(s,t.start)?j(b,c,h):(b.request(c,{type:"type",preferFunction:!0,end:s},function(a,d){!a&&d.type&&/^fn\(/.test(d.type)&&(b.cachedArgHints={start:q,type:k(d.type),name:d.exprName||d.name||"fn",guess:d.guess,doc:c.getDoc()},j(b,c,h))}),void 0)}}}}}function j(a,b,c){C(a);for(var d=a.cachedArgHints,e=d.type,f=v("span",d.guess?G+"fhint-guess":null,v("span",G+"fname",d.name),"("),g=0;g ":")")),e.rettype&&f.appendChild(v("span",G+"type",e.rettype));var i=b.cursorCoords(null,"page");a.activeArgHints=y(i.right+1,i.bottom,f)}function k(a){function b(b){for(var c=0,e=d;;){var f=a.charAt(d);if(b.test(f)&&!c)return a.slice(e,d);/[{\[\(]/.test(f)?++c:/[}\]\)]/.test(f)&&--c,++d}}var c=[],d=3;if(")"!=a.charAt(d))for(;;){var e=a.slice(d).match(/^([^, \(\[\{]+): /);if(e&&(d+=e[0].length,e=e[1]),c.push({name:e,type:b(/[\),]/)}),")"==a.charAt(d))break;d+=2}var f=a.slice(d).match(/^\) -> (.*)$/);return{args:c,rettype:f&&f[1]}}function l(a,b){function d(d){var e={type:"definition",variable:d||null},f=c(a,b.getDoc());a.server.request(t(a,f,e),function(c,d){if(c)return B(a,b,c);if(!d.file&&d.url)return window.open(d.url),void 0;if(d.file){var e,g=a.docs[d.file];if(g&&(e=o(g.doc,d)))return a.jumpStack.push({file:f.name,start:b.getCursor("from"),end:b.getCursor("to")}),n(a,f,g,e.start,e.end),void 0}B(a,b,"Could not find a definition.")})}p(b)?d():w(b,"Jump to variable",function(a){a&&d(a)})}function m(a,b){var d=a.jumpStack.pop(),e=d&&a.docs[d.file];e&&n(a,c(a,b.getDoc()),e,d.start,d.end)}function n(a,b,c,d,e){c.doc.setSelection(e,d),b!=c&&a.options.switchToDoc&&(C(a),a.options.switchToDoc(c.name))}function o(a,b){for(var c=b.context.slice(0,b.contextOffset).split("\n"),d=b.start.line-(c.length-1),e=F(d,(1==c.length?b.start.ch:a.getLine(d).length)-c[0].length),f=a.getLine(d).slice(e.ch),g=d+1;gl&&(h=k,j=l)}if(!h)return null;if(1==c.length?h.ch+=c[0].length:h=F(h.line+(c.length-1),c[c.length-1].length),b.start.line==b.end.line)var m=F(h.line,h.ch+(b.end.ch-b.start.ch));else var m=F(h.line+(b.end.line-b.start.line),b.end.ch);return{start:h,end:m}}function p(a){var b=a.getCursor("end"),c=a.getTokenAt(b);return c.start=0&&J(g,i.end)<=0&&(g=e.length-1))}b.setSelections(e,g)})}function s(a,b){for(var c=Object.create(null),d=0;dH&&g!==!1&&b.changed.to-b.changed.from<100&&b.changed.from<=h.line&&b.changed.to>c.end.line){e.push(u(b,h,c.end)),c.file="#0";var f=e[0].offsetLines;null!=c.start&&(c.start=F(c.start.line- -f,c.start.ch)),c.end=F(c.end.line-f,c.end.ch)}else e.push({type:"full",name:b.name,text:D(a,b)}),c.file=b.name,b.changed=null;else c.file=b.name;for(var i in a.docs){var j=a.docs[i];j.changed&&j!=b&&(e.push({type:"full",name:j.name,text:D(a,j)}),j.changed=null)}return{query:c,files:e}}function u(b,c,d){for(var e,f=b.doc,g=null,h=null,i=4,j=c.line-1,k=Math.max(0,j-50);j>=k;--j){var l=f.getLine(j),m=l.search(/\bfunction\b/);if(!(0>m)){var n=a.countColumn(l,null,i);null!=g&&n>=g||(g=n,h=j)}}null==h&&(h=k);var o=Math.min(f.lastLine(),d.line+20);if(null==g||g==a.countColumn(f.getLine(c.line),null,i))e=o;else for(e=d.line+1;o>e;++e){var n=a.countColumn(f.getLine(e),null,i);if(g>=n)break}var p=F(h,0);return{type:"part",name:b.name,offsetLines:p.line,text:f.getRange(p,F(e,0))}}function v(a,b){var c=document.createElement(a);b&&(c.className=b);for(var d=2;d",c):c(prompt(b,""))}function x(a,b){function c(){e.parentNode&&(a.off("cursorActivity",c),A(e))}var d=a.cursorCoords(),e=y(d.right+1,d.bottom,b);a.on("cursorActivity",c),a.on("blur",c),a.on("keydown",c)}function y(a,b,c){var d=v("div",G+"tooltip",c);return d.style.left=a+"px",d.style.top=b+"px",document.body.appendChild(d),d}function z(a){var b=a&&a.parentNode;b&&b.removeChild(a)}function A(a){a.style.opacity="0",setTimeout(function(){z(a)},1100)}function B(a,b,c){a.options.showError?a.options.showError(b,c):x(b,String(c))}function C(a){a.activeArgHints&&(z(a.activeArgHints),a.activeArgHints=null)}function D(a,b){var c=b.doc.getValue();return a.options.fileFilter&&(c=a.options.fileFilter(c,b.name,b.doc)),c}function E(a){function c(a,b){b&&(a.id=++e,f[e]=b),d.postMessage(a)}var d=new Worker(a.options.workerScript);d.postMessage({type:"init",defs:a.options.defs,plugins:a.options.plugins,scripts:a.options.workerDeps});var e=0,f={};d.onmessage=function(d){var e=d.data;"getFile"==e.type?b(a,e.name,function(a,b){c({type:"getFile",err:String(a),text:b,id:e.id})}):"debug"==e.type?window.console.log(e.message):e.id&&f[e.id]&&(f[e.id](e.err,e.body),delete f[e.id])},d.onerror=function(a){for(var b in f)f[b](a);f={}},this.addFile=function(a,b){c({type:"add",name:a,text:b})},this.delFile=function(a){c({type:"del",name:a})},this.request=function(a,b){c({type:"req",body:a},b)}}a.TernServer=function(a){var c=this;this.options=a||{};var e=this.options.plugins||(this.options.plugins={});e.doc_comment||(e.doc_comment=!0),this.server=this.options.useWorker?new E(this):new tern.Server({getFile:function(a,d){return b(c,a,d)},async:!0,defs:this.options.defs||[],plugins:e}),this.docs=Object.create(null),this.trackChange=function(a,b){d(c,a,b)},this.cachedArgHints=null,this.activeArgHints=null,this.jumpStack=[]},a.TernServer.prototype={addDoc:function(b,c){var d={doc:c,name:b,changed:null};return this.server.addFile(b,D(this,d)),a.on(c,"change",this.trackChange),this.docs[b]=d},delDoc:function(b){var c=this.docs[b];c&&(a.off(c.doc,"change",this.trackChange),delete this.docs[b],this.server.delFile(b))},hideDoc:function(a){C(this);var b=this.docs[a];b&&b.changed&&e(this,b)},complete:function(b){var c=this;a.showHint(b,function(a,b){return f(c,a,b)},{async:!0})},getHint:function(a,b){return f(this,a,b)},showType:function(a,b){h(this,a,b)},updateArgHints:function(a){i(this,a)},jumpToDef:function(a){l(this,a)},jumpBack:function(a){m(this,a)},rename:function(a){q(this,a)},selectName:function(a){r(this,a)},request:function(a,b,d,e){var f=this,g=c(this,a.getDoc()),h=t(this,g,b,e);this.server.request(h,function(a,c){!a&&f.options.responseFilter&&(c=f.options.responseFilter(g,b,h,a,c)),d(a,c)})}};var F=a.Pos,G="CodeMirror-Tern-",H=250,I=0,J=a.cmpPos}),function(a,b){return"object"==typeof exports&&"object"==typeof module?b(exports):"function"==typeof define&&define.amd?define(["exports"],b):(b(a.acorn||(a.acorn={})),void 0)}(this,function(a){"use strict";function b(a){lb=a||{};for(var b in pb)Object.prototype.hasOwnProperty.call(lb,b)||(lb[b]=pb[b]);ob=lb.sourceFile||null}function c(a,b){var c=qb(mb,a);b+=" ("+c.line+":"+c.column+")";var d=new SyntaxError(b);throw d.pos=a,d.loc=c,d.raisedAt=rb,d}function d(a){function b(a){if(1==a.length)return c+="return str === "+JSON.stringify(a[0])+";";c+="switch(str){";for(var b=0;b3){d.sort(function(a,b){return b.length-a.length}),c+="switch(str.length){";for(var e=0;erb&&10!==c&&13!==c&&8232!==c&&8233!==c;)++rb,c=mb.charCodeAt(rb);lb.onComment&&lb.onComment(!1,mb.slice(a+2,rb),a,rb,b,lb.locations&&new e)}function j(){for(;nb>rb;){var a=mb.charCodeAt(rb);if(32===a)++rb;else if(13===a){++rb;var b=mb.charCodeAt(rb);10===b&&++rb,lb.locations&&(++zb,Ab=rb)}else if(10===a||8232===a||8233===a)++rb,lb.locations&&(++zb,Ab=rb);else if(a>8&&14>a)++rb;else if(47===a){var b=mb.charCodeAt(rb+1);if(42===b)h();else{if(47!==b)break;i()}}else if(160===a)++rb;else{if(!(a>=5760&&Sc.test(String.fromCharCode(a))))break;++rb}}}function k(){var a=mb.charCodeAt(rb+1);return a>=48&&57>=a?y(!0):(++rb,g(uc))}function l(){var a=mb.charCodeAt(rb+1);return yb?(++rb,v()):61===a?u(yc,2):u(wc,1)}function m(){var a=mb.charCodeAt(rb+1);return 61===a?u(yc,2):u(Kc,1)}function n(a){var b=mb.charCodeAt(rb+1);return b===a?u(124===a?Bc:Cc,2):61===b?u(yc,2):u(124===a?Dc:Fc,1)}function o(){var a=mb.charCodeAt(rb+1);return 61===a?u(yc,2):u(Ec,1)}function p(a){var b=mb.charCodeAt(rb+1);return b===a?45==b&&62==mb.charCodeAt(rb+2)&&Xc.test(mb.slice(Cb,rb))?(rb+=3,i(),j(),t()):u(zc,2):61===b?u(yc,2):u(Jc,1)}function q(a){var b=mb.charCodeAt(rb+1),c=1;return b===a?(c=62===a&&62===mb.charCodeAt(rb+2)?3:2,61===mb.charCodeAt(rb+c)?u(yc,c+1):u(Ic,c)):33==b&&60==a&&45==mb.charCodeAt(rb+2)&&45==mb.charCodeAt(rb+3)?(rb+=4,i(),j(),t()):(61===b&&(c=61===mb.charCodeAt(rb+2)?3:2),u(Hc,c))}function r(a){var b=mb.charCodeAt(rb+1);return 61===b?u(Gc,61===mb.charCodeAt(rb+2)?3:2):u(61===a?xc:Ac,1)}function s(a){switch(a){case 46:return k();case 40:return++rb,g(pc);case 41:return++rb,g(qc);case 59:return++rb,g(sc);case 44:return++rb,g(rc);case 91:return++rb,g(lc);case 93:return++rb,g(mc);case 123:return++rb,g(nc);case 125:return++rb,g(oc);case 58:return++rb,g(tc);case 63:return++rb,g(vc);case 48:var b=mb.charCodeAt(rb+1);if(120===b||88===b)return x();case 49:case 50:case 51:case 52:case 53:case 54:case 55:case 56:case 57:return y(!1);case 34:case 39:return z(a);case 47:return l(a);case 37:case 42:return m();case 124:case 38:return n(a);case 94:return o();case 43:case 45:return p(a);case 60:case 62:return q(a);case 61:case 33:return r(a);case 126:return u(Ac,1)}return!1}function t(a){if(a?rb=sb+1:sb=rb,lb.locations&&(ub=new e),a)return v();if(rb>=nb)return g(Mb);var b=mb.charCodeAt(rb);if(Zc(b)||92===b)return C();var d=s(b);if(d===!1){var f=String.fromCharCode(b);if("\\"===f||Vc.test(f))return C();c(rb,"Unexpected character '"+f+"'")}return d}function u(a,b){var c=mb.slice(rb,rb+b);rb+=b,g(a,c)}function v(){for(var a,b,d="",e=rb;;){rb>=nb&&c(e,"Unterminated regular expression");var f=mb.charAt(rb);if(Xc.test(f)&&c(e,"Unterminated regular expression"),a)a=!1;else{if("["===f)b=!0;else if("]"===f&&b)b=!1;else if("/"===f&&!b)break;a="\\"===f}++rb}var d=mb.slice(e,rb);++rb;var h=B();h&&!/^[gmsiy]*$/.test(h)&&c(e,"Invalid regexp flag");try{var i=new RegExp(d,h)}catch(j){j instanceof SyntaxError&&c(e,j.message),c(j)}return g(Jb,i)}function w(a,b){for(var c=rb,d=0,e=0,f=null==b?1/0:b;f>e;++e){var g,h=mb.charCodeAt(rb);if(g=h>=97?h-97+10:h>=65?h-65+10:h>=48&&57>=h?h-48:1/0,g>=a)break;++rb,d=d*a+g}return rb===c||null!=b&&rb-c!==b?null:d}function x(){rb+=2;var a=w(16);return null==a&&c(sb+2,"Expected hexadecimal number"),Zc(mb.charCodeAt(rb))&&c(rb,"Identifier directly after number"),g(Ib,a)}function y(a){var b=rb,d=!1,e=48===mb.charCodeAt(rb);a||null!==w(10)||c(b,"Invalid number"),46===mb.charCodeAt(rb)&&(++rb,w(10),d=!0);var f=mb.charCodeAt(rb);(69===f||101===f)&&(f=mb.charCodeAt(++rb),(43===f||45===f)&&++rb,null===w(10)&&c(b,"Invalid number"),d=!0),Zc(mb.charCodeAt(rb))&&c(rb,"Identifier directly after number");var h,i=mb.slice(b,rb);return d?h=parseFloat(i):e&&1!==i.length?/[89]/.test(i)||Gb?c(b,"Invalid number"):h=parseInt(i,8):h=parseInt(i,10),g(Ib,h)}function z(a){rb++;for(var b="";;){rb>=nb&&c(sb,"Unterminated string constant");var d=mb.charCodeAt(rb);if(d===a)return++rb,g(Kb,b);if(92===d){d=mb.charCodeAt(++rb);var e=/^[0-7]+/.exec(mb.slice(rb,rb+3));for(e&&(e=e[0]);e&&parseInt(e,8)>255;)e=e.slice(0,-1);if("0"===e&&(e=null),++rb,e)Gb&&c(rb-2,"Octal literal in strict mode"),b+=String.fromCharCode(parseInt(e,8)),rb+=e.length-1;else switch(d){case 110:b+="\n";break;case 114:b+="\r";break;case 120:b+=String.fromCharCode(A(2));break;case 117:b+=String.fromCharCode(A(4));break;case 85:b+=String.fromCharCode(A(8));break;case 116:b+=" ";break;case 98:b+="\b";break;case 118:b+=" ";break;case 102:b+="\f";break;case 48:b+="\0";break;case 13:10===mb.charCodeAt(rb)&&++rb;case 10:lb.locations&&(Ab=rb,++zb);break;default:b+=String.fromCharCode(d)}}else(13===d||10===d||8232===d||8233===d)&&c(sb,"Unterminated string constant"),b+=String.fromCharCode(d),++rb}}function A(a){var b=w(16,a);return null===b&&c(sb,"Bad character escape sequence"),b}function B(){Mc=!1;for(var a,b=!0,d=rb;;){var e=mb.charCodeAt(rb);if($c(e))Mc&&(a+=mb.charAt(rb)),++rb;else{if(92!==e)break;Mc||(a=mb.slice(d,rb)),Mc=!0,117!=mb.charCodeAt(++rb)&&c(rb,"Expecting Unicode escape sequence \\uXXXX"),++rb;var f=A(4),g=String.fromCharCode(f);g||c(rb-1,"Invalid Unicode escape"),(b?Zc(f):$c(f))||c(rb-4,"Invalid Unicode escape"),a+=g}b=!1}return Mc?a:mb.slice(d,rb)}function C(){var a=B(),b=Lb;return!Mc&&Rc(a)&&(b=kc[a]),g(b,a)}function D(){Bb=sb,Cb=tb,Db=vb,t()}function E(a){if(Gb=a,rb=sb,lb.locations)for(;Ab>rb;)Ab=mb.lastIndexOf("\n",Ab-2)+1,--zb;j(),t()}function F(){this.type=null,this.start=sb,this.end=null}function G(){this.start=ub,this.end=null,null!==ob&&(this.source=ob)}function H(){var a=new F;return lb.locations&&(a.loc=new G),lb.directSourceFile&&(a.sourceFile=lb.directSourceFile),lb.ranges&&(a.range=[sb,0]),a}function I(a){var b=new F;return b.start=a.start,lb.locations&&(b.loc=new G,b.loc.start=a.loc.start),lb.ranges&&(b.range=[a.range[0],0]),b}function J(a,b){return a.type=b,a.end=Cb,lb.locations&&(a.loc.end=Db),lb.ranges&&(a.range[1]=Cb),a}function K(a){return lb.ecmaVersion>=5&&"ExpressionStatement"===a.type&&"Literal"===a.expression.type&&"use strict"===a.expression.value}function L(a){return wb===a?(D(),!0):void 0}function M(){return!lb.strictSemicolons&&(wb===Mb||wb===oc||Xc.test(mb.slice(Cb,sb)))}function N(){L(sc)||M()||P()}function O(a){wb===a?D():P()}function P(){c(sb,"Unexpected token")}function Q(a){"Identifier"!==a.type&&"MemberExpression"!==a.type&&c(a.start,"Assigning to rvalue"),Gb&&"Identifier"===a.type&&Qc(a.name)&&c(a.start,"Assigning to "+a.name+" in strict mode")}function R(a){Bb=Cb=rb,lb.locations&&(Db=new e),Eb=Gb=null,Fb=[],t();var b=a||H(),c=!0;for(a||(b.body=[]);wb!==Mb;){var d=S();b.body.push(d),c&&K(d)&&E(!0),c=!1}return J(b,"Program")}function S(){(wb===wc||wb===yc&&"/="==xb)&&t(!0);var a=wb,b=H();switch(a){case Nb:case Qb:D();var d=a===Nb;L(sc)||M()?b.label=null:wb!==Lb?P():(b.label=kb(),N());for(var e=0;eb){var e=I(a);e.left=a,e.operator=xb;var f=wb;D(),e.right=ab(bb(),d,c);var g=J(e,f===Bc||f===Cc?"LogicalExpression":"BinaryExpression");return ab(g,b,c)}return a}function bb(){if(wb.prefix){var a=H(),b=wb.isUpdate;return a.operator=xb,a.prefix=!0,yb=!0,D(),a.argument=bb(),b?Q(a.argument):Gb&&"delete"===a.operator&&"Identifier"===a.argument.type&&c(a.start,"Deleting local variable in strict mode"),J(a,b?"UpdateExpression":"UnaryExpression")}for(var d=cb();wb.postfix&&!M();){var a=I(d);a.operator=xb,a.prefix=!1,a.argument=d,Q(d),D(),d=J(a,"UpdateExpression")}return d}function cb(){return db(eb())}function db(a,b){if(L(uc)){var c=I(a);return c.object=a,c.property=kb(!0),c.computed=!1,db(J(c,"MemberExpression"),b)}if(L(lc)){var c=I(a);return c.object=a,c.property=Y(),c.computed=!0,O(mc),db(J(c,"MemberExpression"),b)}if(!b&&L(pc)){var c=I(a);return c.callee=a,c.arguments=jb(qc,!1),db(J(c,"CallExpression"),b)}return a}function eb(){switch(wb){case fc:var a=H();return D(),J(a,"ThisExpression");case Lb:return kb();case Ib:case Kb:case Jb:var a=H();return a.value=xb,a.raw=mb.slice(sb,tb),D(),J(a,"Literal");case gc:case hc:case ic:var a=H();return a.value=wb.atomValue,a.raw=wb.keyword,D(),J(a,"Literal");case pc:var b=ub,c=sb;D();var d=Y();return d.start=c,d.end=tb,lb.locations&&(d.loc.start=b,d.loc.end=vb),lb.ranges&&(d.range=[c,tb]),O(qc),d;case lc:var a=H();return D(),a.elements=jb(mc,!0,!0),J(a,"ArrayExpression");case nc:return gb();case Xb:var a=H();return D(),ib(a,!1);case ec:return fb();default:P()}}function fb(){var a=H();return D(),a.callee=db(eb(),!0),a.arguments=L(pc)?jb(qc,!1):Hb,J(a,"NewExpression")}function gb(){var a=H(),b=!0,d=!1;for(a.properties=[],D();!L(oc);){if(b)b=!1;else if(O(rc),lb.allowTrailingCommas&&L(oc))break;var e,f={key:hb()},g=!1;if(L(tc)?(f.value=Y(!0),e=f.kind="init"):lb.ecmaVersion>=5&&"Identifier"===f.key.type&&("get"===f.key.name||"set"===f.key.name)?(g=d=!0,e=f.kind=f.key.name,f.key=hb(),wb!==pc&&P(),f.value=ib(H(),!1)):P(),"Identifier"===f.key.type&&(Gb||d))for(var h=0;hg?a.id:a.params[g];if((Pc(h.name)||Qc(h.name))&&c(h.start,"Defining '"+h.name+"' in strict mode"),g>=0)for(var i=0;g>i;++i)h.name===a.params[i].name&&c(h.start,"Argument name clash in strict mode")}return J(a,b?"FunctionDeclaration":"FunctionExpression")}function jb(a,b,c){for(var d=[],e=!0;!L(a);){if(e)e=!1;else if(O(rc),b&&lb.allowTrailingCommas&&L(a))break;c&&wb===rc?d.push(null):d.push(Y(!0))}return d}function kb(a){var b=H();return a&&"everywhere"==lb.forbidReserved&&(a=!1),wb===Lb?(!a&&(lb.forbidReserved&&(3===lb.ecmaVersion?Nc:Oc)(xb)||Gb&&Pc(xb))&&-1==mb.slice(sb,tb).indexOf("\\")&&c(sb,"The keyword '"+xb+"' is reserved"),b.name=xb):a&&wb.keyword?b.name=wb.keyword:P(),yb=!1,D(),J(b,"Identifier")}a.version="0.4.1";var lb,mb,nb,ob;a.parse=function(a,c){return mb=String(a),nb=mb.length,b(c),f(),R(lb.program)};var pb=a.defaultOptions={ecmaVersion:5,strictSemicolons:!1,allowTrailingCommas:!0,forbidReserved:!1,allowReturnOutsideFunction:!1,locations:!1,onComment:null,ranges:!1,program:null,sourceFile:null,directSourceFile:null},qb=a.getLineInfo=function(a,b){for(var c=1,d=0;;){Yc.lastIndex=d;var e=Yc.exec(a);if(!(e&&e.indexa?36===a:91>a?!0:97>a?95===a:123>a?!0:a>=170&&Vc.test(String.fromCharCode(a))},$c=a.isIdentifierChar=function(a){return 48>a?36===a:58>a?!0:65>a?!1:91>a?!0:97>a?95===a:123>a?!0:a>=170&&Wc.test(String.fromCharCode(a))},_c={kind:"loop"},ad={kind:"switch"}}),function(a,b){return"object"==typeof exports&&"object"==typeof module?b(exports,require("./acorn")):"function"==typeof define&&define.amd?define(["exports","./acorn"],b):(b(a.acorn||(a.acorn={}),a.acorn),void 0)}(this,function(a,b){"use strict";function c(){if(bb=hb.end,Y.locations&&(fb=hb.endLoc),hb=ib.length?ib.shift():d(),hb.start>=db){for(;hb.start>=db;)cb=db,db=l(cb)+1; +eb=m(cb)}}function d(){for(;;)try{return $()}catch(a){if(!(a instanceof SyntaxError))throw a;var c=a.message,d=a.raisedAt,f=!0;if(/unterminated/i.test(c))if(d=l(a.pos),/string/.test(c))f={start:a.pos,end:d,type:ab.string,value:Z.slice(a.pos+1,d)};else if(/regular expr/i.test(c)){var g=Z.slice(a.pos,d);try{g=new RegExp(g)}catch(a){}f={start:a.pos,end:d,type:ab.regexp,value:g}}else f=!1;else if(/invalid (unicode|regexp|number)|expecting unicode|octal literal|is reserved|directly after number/i.test(c))for(;d]/.test(b)||/[enwfd]/.test(b)&&/\b(keywords|case|else|return|throw|new|in|(instance|type)of|delete|void)$/.test(Z.slice(a-10,a));$.jumpTo(a,c)}function f(a){var b={start:a.start,end:a.end,type:a.type,value:a.value};return Y.locations&&(b.startLoc=a.startLoc,b.endLoc=a.endLoc),b}function g(a){for(ib.length||(hb=f(hb));a>ib.length;)ib.push(f(d()));return ib[a-1]}function h(a){return 10===a||13===a||8232===a||8329===a}function i(a){return 14>a&&a>8||32===a||160===a||h(a)}function j(){_.push(eb)}function k(){eb=_.pop()}function l(a){for(;aeb&&o()&&(!d||db>=Z.length||m(db)=cb;--a){var b=Z.charCodeAt(a);if(9!==b&&32!==b)return!1}return!0}function p(a){this.type=null,this.start=a,this.end=null}function q(a){this.start=a||hb.startLoc||{line:1,column:0},this.end=null,null!==gb&&(this.source=gb)}function r(){var a=new p(hb.start);return Y.locations&&(a.loc=new q),Y.directSourceFile&&(a.sourceFile=Y.directSourceFile),a}function s(a){var b=new p(a.start);return Y.locations&&(b.loc=new q(a.loc.start)),b}function t(a,b){return a.type=b,a.end=bb,Y.locations&&(a.loc.end=fb),a}function u(){if(Y.locations){var a=new q;return a.end=a.start,a}}function v(){var a=new p(hb.start);return a.type="Identifier",a.end=hb.start,a.name="✖",a.loc=u(),a}function w(a){return"✖"==a.name}function x(a){return hb.type===a?(c(),!0):void 0}function y(){return hb.type===ab.eof||hb.type===ab.braceR||jb.test(Z.slice(bb,hb.start))}function z(){x(ab.semi)}function A(a){return x(a)?!0:g(1).type==a?(c(),c(),!0):g(2).type==a?(c(),c(),c(),!0):void 0}function B(a){return"Identifier"===a.type||"MemberExpression"===a.type?a:v()}function C(){var a=r();for(a.body=[];hb.type!==ab.eof;)a.body.push(D());return t(a,"Program")}function D(){var a=hb.type,b=r();switch(a){case ab._break:case ab._continue:c();var d=a===ab._break;return b.label=hb.type===ab.name?V():null,z(),t(b,d?"BreakStatement":"ContinueStatement");case ab._debugger:return c(),z(),t(b,"DebuggerStatement");case ab._do:return c(),b.body=D(),b.test=x(ab._while)?J():v(),z(),t(b,"DoWhileStatement");case ab._for:if(c(),j(),A(ab.parenL),hb.type===ab.semi)return F(b,null);if(hb.type===ab._var){var e=r();return c(),H(e,!0),1===e.declarations.length&&x(ab._in)?G(b,e):F(b,e)}var e=I(!1,!0);return x(ab._in)?G(b,B(e)):F(b,e);case ab._function:return c(),W(b,!0);case ab._if:return c(),b.test=J(),b.consequent=D(),b.alternate=x(ab._else)?D():null,t(b,"IfStatement");case ab._return:return c(),x(ab.semi)||y()?b.argument=null:(b.argument=I(),z()),t(b,"ReturnStatement");case ab._switch:var f=eb,g=cb;c(),b.discriminant=J(),b.cases=[],j(),A(ab.braceL);for(var h;!n(ab.braceR,f,g,!0);)if(hb.type===ab._case||hb.type===ab._default){var i=hb.type===ab._case;h&&t(h,"SwitchCase"),b.cases.push(h=r()),h.consequent=[],c(),h.test=i?I():null,A(ab.colon)}else h||(b.cases.push(h=r()),h.consequent=[],h.test=null),h.consequent.push(D());return h&&t(h,"SwitchCase"),k(),x(ab.braceR),t(b,"SwitchStatement");case ab._throw:return c(),b.argument=I(),z(),t(b,"ThrowStatement");case ab._try:if(c(),b.block=E(),b.handler=null,hb.type===ab._catch){var l=r();c(),A(ab.parenL),l.param=V(),A(ab.parenR),l.guard=null,l.body=E(),b.handler=t(l,"CatchClause")}return b.finalizer=x(ab._finally)?E():null,b.handler||b.finalizer?t(b,"TryStatement"):b.block;case ab._var:return c(),b=H(b),z(),b;case ab._while:return c(),b.test=J(),b.body=D(),t(b,"WhileStatement");case ab._with:return c(),b.object=J(),b.body=D(),t(b,"WithStatement");case ab.braceL:return E();case ab.semi:return c(),t(b,"EmptyStatement");default:var m=I();return w(m)?(c(),hb.type===ab.eof?t(b,"EmptyStatement"):D()):a===ab.name&&"Identifier"===m.type&&x(ab.colon)?(b.body=D(),b.label=m,t(b,"LabeledStatement")):(b.expression=m,z(),t(b,"ExpressionStatement"))}}function E(){var a=r();j(),A(ab.braceL);var b=eb,c=cb;for(a.body=[];!n(ab.braceR,b,c,!0);)a.body.push(D());return k(),x(ab.braceR),t(a,"BlockStatement")}function F(a,b){return a.init=b,a.test=a.update=null,x(ab.semi)&&hb.type!==ab.semi&&(a.test=I()),x(ab.semi)&&hb.type!==ab.parenR&&(a.update=I()),k(),A(ab.parenR),a.body=D(),t(a,"ForStatement")}function G(a,b){return a.left=b,a.right=I(),k(),A(ab.parenR),a.body=D(),t(a,"ForInStatement")}function H(a,b){for(a.declarations=[],a.kind="var";hb.type===ab.name;){var c=r();if(c.id=V(),c.init=x(ab.eq)?I(!0,b):null,a.declarations.push(t(c,"VariableDeclarator")),!x(ab.comma))break}if(!a.declarations.length){var c=r();c.id=v(),a.declarations.push(t(c,"VariableDeclarator"))}return t(a,"VariableDeclaration")}function I(a,b){var c=K(b);if(!a&&hb.type===ab.comma){var d=s(c);for(d.expressions=[c];x(ab.comma);)d.expressions.push(K(b));return t(d,"SequenceExpression")}return c}function J(){j(),A(ab.parenL);var a=I();return k(),A(ab.parenR),a}function K(a){var b=L(a);if(hb.type.isAssign){var d=s(b);return d.operator=hb.value,d.left=B(b),c(),d.right=K(a),t(d,"AssignmentExpression")}return b}function L(a){var b=M(a);if(x(ab.question)){var c=s(b);return c.test=b,c.consequent=I(!0),c.alternate=A(ab.colon)?I(!0,a):v(),t(c,"ConditionalExpression")}return b}function M(a){var b=eb,c=cb;return N(O(a),-1,a,b,c)}function N(a,b,d,e,f){if(cb!=f&&e>eb&&o())return a;var g=hb.type.binop;if(null!=g&&(!d||hb.type!==ab._in)&&g>b){var h=s(a);h.left=a,h.operator=hb.value,c(),h.right=cb!=f&&e>eb&&o()?v():N(O(d),g,d,e,f);var h=t(h,/&&|\|\|/.test(h.operator)?"LogicalExpression":"BinaryExpression");return N(h,b,d,e,f)}return a}function O(a){if(hb.type.prefix){var b=r(),d=hb.type.isUpdate;return b.operator=hb.value,b.prefix=!0,c(),b.argument=O(a),d&&(b.argument=B(b.argument)),t(b,d?"UpdateExpression":"UnaryExpression")}for(var e=P();hb.type.postfix&&!y();){var b=s(e);b.operator=hb.value,b.prefix=!1,b.argument=B(e),c(),e=t(b,"UpdateExpression")}return e}function P(){return Q(R(),!1,eb,cb)}function Q(a,b,d,e){for(;;){if(cb!=e&&d>=eb&&o()){if(hb.type!=ab.dot||eb!=d)return a;--d}if(x(ab.dot)){var f=s(a);f.object=a,f.property=cb!=e&&d>=eb&&o()?v():U()||v(),f.computed=!1,a=t(f,"MemberExpression")}else if(hb.type==ab.bracketL){j(),c();var f=s(a);f.object=a,f.property=I(),f.computed=!0,k(),A(ab.bracketR),a=t(f,"MemberExpression")}else{if(b||hb.type!=ab.parenL)return a;j();var f=s(a);f.callee=a,f.arguments=X(ab.parenR),a=t(f,"CallExpression")}}}function R(){switch(hb.type){case ab._this:var a=r();return c(),t(a,"ThisExpression");case ab.name:return V();case ab.num:case ab.string:case ab.regexp:var a=r();return a.value=hb.value,a.raw=Z.slice(hb.start,hb.end),c(),t(a,"Literal");case ab._null:case ab._true:case ab._false:var a=r();return a.value=hb.type.atomValue,a.raw=hb.type.keyword,c(),t(a,"Literal");case ab.parenL:var b=hb.start;c();var d=I();return d.start=b,d.end=hb.end,A(ab.parenR),d;case ab.bracketL:var a=r();return j(),a.elements=X(ab.bracketR),t(a,"ArrayExpression");case ab.braceL:return T();case ab._function:var a=r();return c(),W(a,!1);case ab._new:return S();default:return v()}}function S(){var a=r(),b=eb,d=cb;return c(),a.callee=Q(R(),!0,b,d),hb.type==ab.parenL?(j(),a.arguments=X(ab.parenR)):a.arguments=[],t(a,"NewExpression")}function T(){var a=r();a.properties=[],j(),c();for(var b=eb,d=cb;!n(ab.braceR,b,d);){var e=U();if(e){var f,g={key:e},h=!1;if(x(ab.colon))g.value=I(!0),f=g.kind="init";else{if(!(Y.ecmaVersion>=5&&"Identifier"===g.key.type)||"get"!==g.key.name&&"set"!==g.key.name){c(),x(ab.comma);continue}h=!0,f=g.kind=g.key.name,g.key=U()||v(),g.value=W(r(),!1)}a.properties.push(g),x(ab.comma)}else w(I(!0))&&c(),x(ab.comma)}return k(),x(ab.braceR),t(a,"ObjectExpression")}function U(){return hb.type===ab.num||hb.type===ab.string?R():hb.type===ab.name||hb.type.keyword?V():void 0}function V(){var a=r();return a.name=hb.type===ab.name?hb.value:hb.type.keyword,c(),t(a,"Identifier")}function W(a,b){for(a.id=hb.type===ab.name?V():b?v():null,a.params=[],j(),A(ab.parenL);hb.type==ab.name;)a.params.push(V()),x(ab.comma);return k(),x(ab.parenR),a.body=E(),t(a,b?"FunctionDeclaration":"FunctionExpression")}function X(a){var b=eb,d=cb,e=[],f=db;for(c(),cb>f&&(f=cb);!n(a,b+(f>=cb?1:0),d);){var g=I(!0);if(w(g)){if(n(a,b,d))break;c()}else e.push(g);for(;x(ab.comma););}return k(),x(a),e}var Y,Z,$,_,ab=b.tokTypes;a.parse_dammit=function(a,d){return d||(d={}),Z=String(a),Y=d,d.tabSize||(d.tabSize=4),$=b.tokenize(a,d),gb=Y.sourceFile||null,_=[],db=0,ib.length=0,c(),C()};var bb,cb,db,eb,fb,gb,hb={start:0,end:0},ib=[],jb=/[\n\r\u2028\u2029]/}),function(a){return"object"==typeof exports&&"object"==typeof module?a(exports):"function"==typeof define&&define.amd?define(["exports"],a):(a((this.acorn||(this.acorn={})).walk={}),void 0)}(function(a){"use strict";function b(a){return"string"==typeof a?function(b){return b==a}:a?a:function(){return!0}}function c(a,b){this.node=a,this.state=b}function d(a,b,c){c(a,b)}function e(){}function f(a,b){return{vars:Object.create(null),prev:a,isCatch:b}}function g(a){for(;a.isCatch;)a=a.prev;return a}a.simple=function(b,c,d,e){function f(a,b,e){var g=e||a.type,h=c[g];d[g](a,b,f),h&&h(a,b)}d||(d=a.base),f(b,e)},a.recursive=function(b,c,d,e){function f(a,b,c){g[c||a.type](a,b,f)}var g=d?a.make(d,e):e;f(b,c)},a.findNodeAt=function(d,e,f,g,h,i){g=b(g);try{h||(h=a.base);var j=function(a,b,d){var i=d||a.type;if((null==e||a.start<=e)&&(null==f||a.end>=f)&&h[i](a,b,j),g(i,a)&&(null==e||a.start==e)&&(null==f||a.end==f))throw new c(a,b)};j(d,i)}catch(k){if(k instanceof c)return k;throw k}},a.findNodeAround=function(d,e,f,g,h){f=b(f);try{g||(g=a.base);var i=function(a,b,d){var h=d||a.type;if(!(a.start>e||a.end=e&&f(h,a))throw new c(a,b);g[h](a,b,i)}};i(d,h)}catch(j){if(j instanceof c)return j;throw j}},a.findNodeBefore=function(d,e,f,g,h){f=b(f),g||(g=a.base);var i,j=function(a,b,d){if(!(a.start>e)){var h=d||a.type;a.end<=e&&(!i||i.node.endb?a:a.slice(0,b)}function p(a,b,c){var d=Math.max(0,c-500),e=null;if(!/^\s*$/.test(a))for(;;){var f=b.indexOf(a,d);if(0>f||f>c+500)break;(null==e||Math.abs(e-c)>Math.abs(f-c))&&(e=f),d=f+a.length}return e}function q(a){for(var b=0;a;++b,a=a.prev);return b}function r(a){var b=new Error(a);return b.name="TernError",b}function s(a,c,d){var f=d.match(/^#(\d+)$/);if(!f)return n(a.files,d);var g=c[f[1]];if(!g)throw r("Reference to unknown file "+d);if("full"==g.type)return n(a.files,g.name);var h=g.backing=n(a.files,g.name),i=g.offset;g.offsetLines&&(i={line:g.offsetLines,ch:0}),g.offset=i=w(h,null==g.offsetLines?g.offset:{line:g.offsetLines,ch:0},!0);var j=o(g.text),k=p(j,h.text,i),l=null==k?Math.max(0,h.text.lastIndexOf("\n",i)):k;return b.withContext(a.cx,function(){b.purgeTypes(g.name,l,l+g.text.length);var c,d=g.text;if(c=d.match(/(?:"([^"]*)"|([\w$]+))\s*:\s*function\b/)){var f=e.findNodeAround(g.backing.ast,l,"ObjectExpression");if(f&&f.node.objType)var i={type:f.node.objType,prop:c[2]||c[1]}}if(k&&(c=j.match(/^(.*?)\bfunction\b/))){for(var m=c[1].length,n="",o=0;m>o;++o)n+=" ";d=n+d.slice(m);var p=!0}var r=b.scopeAt(h.ast,l,h.scope),s=b.scopeAt(h.ast,l+d.length,h.scope),t=g.scope=q(r)o;++o)x.args[o].propagate(y.args[o]);x.self.propagate(y.self),y.retval.propagate(x.retval)}}}),g}function t(a){return"number"==typeof a||"object"==typeof a&&"number"==typeof a.line&&"number"==typeof a.ch}function u(a){if(a.query){if("string"!=typeof a.query.type)return".query.type must be a string";if(a.query.start&&!t(a.query.start))return".query.start must be a position";if(a.query.end&&!t(a.query.end))return".query.end must be a position"}if(a.files){if(!Array.isArray(a.files))return"Files property must be an array";for(var b=0;bf;){if(++f,e=c.indexOf("\n",e)+1,0==e)return null;0==f%S&&d.push(e)}return e}function w(a,b,c){if("number"!=typeof b){var d=v(a,b.line);if(null==d){if(!c)throw r("File doesn't contain a line "+b.line);b=a.text.length}else b=d+b.ch}if(b>a.text.length){if(!c)throw r("Position "+b+" is outside of file.");b=a.text.length}return b}function x(a,b){if(!a)return{line:0,ch:0};for(var c,d,e=a.lineOffsets||(a.lineOffsets=[0]),f=a.text,g=e.length-1;g>=0;--g)e[g]<=b&&(c=g*S,d=e[g]);for(;;){var h=f.indexOf("\n",d);if(h>=b||0>h)break;d=h+1,++c}return{line:c,ch:b-d}}function y(a,b,c){if(a.lineCharPositions){var d=x(b,c);return"part"==b.type&&(d.line+=null!=b.offsetLines?b.offsetLines:x(b.backing,b.offset).line),d}return c+("part"==b.type?b.offset:0)}function z(a){for(var b in a)null==a[b]&&delete a[b];return a}function A(a,b,c){null!=c&&(a[b]=c)}function B(a,b){"string"!=typeof a&&(a=a.name,b=b.name);var c=/^[A-Z]/.test(a),d=/^[A-Z]/.test(b);return c==d?b>a?-1:a==b?0:1:c?1:-1}function C(a,b,c){return"Literal"==a.type&&"string"==typeof a.value&&a.start==b-1&&a.end<=c+1}function D(a,c,e){function f(d,e,f){if(!(c.omitObjectPrototype!==!1&&e==a.cx.protos.Object&&!j||c.filter!==!1&&j&&0!=(c.caseInsensitive?d.toLowerCase():d).indexOf(j))){for(var g=0;g=2&&c.guess!==!1)for(var n in a.cx.props)f(n,a.cx.props[n][0],0)}else b.forAllLocalsAt(e.ast,g,e.scope,f);return c.sort!==!1&&k.sort(B),{start:y(c,e,g),end:y(c,e,h),completions:k}}function E(a,b){var c=b.prefix,d=[];for(var e in a.cx.props)""==e||c&&0!=e.indexOf(c)||d.push(e);return b.sort!==!1&&d.sort(B),{completions:d}}function F(a,c,d){var e=T(d,c);b.resetGuessing();var f=b.expressionType(e);if(f=c.preferFunction?f.getFunctionType()||f.getType():f.getType(),"Identifier"==e.node.type)var g=e.node.name;else if("MemberExpression"==e.node.type&&!e.node.computed)var g=e.node.property.name;if(null!=c.depth&&"number"!=typeof c.depth)throw r(".query.depth must be a number");var h={guess:b.didGuess(),type:b.toString(f,c.depth),name:f&&f.name,exprName:g};return f&&H(f,h),z(h)}function G(a,c,d){var e=T(d,c),f=b.expressionType(e),g={url:f.url,doc:f.doc},h=f.getType();return h&&H(h,g),z(g)}function H(a,c){c.url||(c.url=a.url),c.doc||(c.doc=a.doc),c.origin||(c.origin=a.origin);var d,e=b.cx().protos;!c.url&&!c.doc&&a.proto&&(d=a.proto.hasCtor)&&a.proto!=e.Object&&a.proto!=e.Function&&a.proto!=e.Array&&(c.url=d.url,c.doc=d.doc)}function I(a,c,d){var e=T(d,c);b.resetGuessing();var f=b.expressionType(e);if(b.didGuess())return{};var g=U(f),h={url:f.url,doc:f.doc,origin:f.origin};if(f.types)for(var i=f.types.length-1;i>=0;--i){var j=f.types[i];H(j,h),g||(g=U(j))}if(g&&g.node){var k=g.node.sourceFile||n(a.files,g.origin),l=y(c,k,g.node.start),m=y(c,k,g.node.end);h.start=l,h.end=m,h.file=g.origin;var o=Math.max(0,g.node.start-50);h.contextOffset=g.node.start-o,h.context=k.text.slice(o,o+50)}else g&&(h.file=g.origin,V(a,c,g,h));return z(h)}function J(a,c,d,e,f){function g(a){return function(b,d){if(f)for(var e=d;e!=i;e=e.prev){var g=e.hasProp(f);if(g)throw r("Renaming `"+h+"` to `"+f+"` would make a variable at line "+(x(a,b.start).line+1)+" point to the definition at line "+(x(a,g.name.start).line+1))}k.push({file:a.name,start:y(c,a,b.start),end:y(c,a,b.end)})}}for(var h=e.node.name,i=e.state;i&&!(h in i.props);i=i.prev);if(!i)throw r("Could not find a definition for "+h+" "+!!a.cx.topScope.props.x);var j,k=[];if(i.node){if(j="local",f){for(var l=i.prev;l&&!(f in l.props);l=l.prev);l&&b.findRefs(i.node,i,f,l,function(a){throw r("Renaming `"+h+"` to `"+f+"` would shadow the definition used at line "+(x(d,a.start).line+1))})}b.findRefs(i.node,i,h,i,g(d))}else{j="global";for(var m=0;m=f)return K(a,b,d,h)}throw r("Not at a variable or property name.")}function M(a,b,c){if("string"!=typeof b.newName)throw r(".query.newName should be a string");var d=T(c,b);if(!d||"Identifier"!=d.node.type)throw r("Not at a variable.");var e=J(a,b,c,d,b.newName),f=e.refs;delete e.refs,e.files=a.files.map(function(a){return a.name});for(var g=e.changes=[],h=0;h40&&(d.reset(),m(d,function(){}))})},findFile:function(a){return n(this.files,a)},flush:function(a){var c=this.cx;m(this,function(d){return d?a(d):(b.withContext(c,a),void 0)})},startAsyncAction:function(){++this.pending},finishAsyncAction:function(a){a&&(this.asyncError=a),0==--this.pending&&this.signal("everythingFetched")}});var S=25,T=a.findQueryExpr=function(a,c,d){if(null==c.end)throw r("missing .query.end field");if(c.variable){var e=b.scopeAt(a.ast,w(a,c.end),a.scope);return{node:{type:"Identifier",name:c.variable,start:c.end,end:c.end+1},state:e}}var f=c.start&&w(a,c.start),g=w(a,c.end),h=b.findExpressionAt(a.ast,f,g,a.scope);if(h)return h;if(h=b.findExpressionAround(a.ast,f,g,a.scope),h&&(d||(null==f?g:f)-h.node.start<20||h.node.end-g<20))return h;throw r("No expression at the given position.")},U=a.getSpan=function(a){if(a.origin){if(a.originNode){var b=a.originNode;return/^Function/.test(b.type)&&b.id&&(b=b.id),{origin:a.origin,node:b}}return a.span?{origin:a.origin,span:a.span}:void 0}},V=a.storeSpan=function(a,b,c,d){if(d.origin=c.origin,c.span){var e=/^(\d+)\[(\d+):(\d+)\]-(\d+)\[(\d+):(\d+)\]$/.exec(c.span);d.start=b.lineCharPositions?{line:Number(e[2]),ch:Number(e[3])}:Number(e[1]),d.end=b.lineCharPositions?{line:Number(e[5]),ch:Number(e[6])}:Number(e[4])}else{var f=n(a.files,c.origin);d.start=y(b,f,c.node.start),d.end=y(b,f,c.node.end)}};a.version="0.5.1"}),function(a){return"object"==typeof exports&&"object"==typeof module?exports.init=a:"function"==typeof define&&define.amd?define({init:a}):(tern.def={init:a},void 0)}(function(a,b){"use strict";function c(a,b){return Object.prototype.hasOwnProperty.call(a,b)}function d(a,c,d,f){var g=new m(a,null,d,f).parseType(c,!0);if(/^fn\(/.test(a))for(var h=0;h ")?c&&this.spec.indexOf("!",this.pos)>-1?(i=b.ANull,k=this.pos,j=this.parseRetType()):i=this.parseType():i=b.ANull,c&&(l=this.base)?b.Fn.call(this.base,a,b.ANull,d,e,i):l=new b.Fn(a,b.ANull,d,e,i),j&&(l.computeRet=j),null!=k&&(l.computeRetSource=this.spec.slice(k,this.pos)),l},parseType:function(a,c){if(this.eat("fn("))return this.parseFnType(a,c);if(this.eat("[")){var d=this.parseType();if(d==b.ANull&&"[b.]"==this.spec){var e=p("b");console.log(e.props[""].types.length)}return this.eat("]")||this.error(),c&&this.base?(b.Arr.call(this.base,d),this.base):new b.Arr(d)}if(this.eat("+")){var f=this.word(/[\w$<>\.!]/),g=p(f+".prototype");return g instanceof b.Obj||(g=p(f)),g instanceof b.Obj?c&&this.forceNew?new b.Obj(g):b.getInstance(g):g}return this.eat("?")?b.ANull:this.fromWord(this.word(/[\w$<>\.!`]/))},fromWord:function(a){var c=b.cx();switch(a){case"number":return c.num;case"string":return c.str;case"bool":return c.bool;case"":return c.topScope}return c.localDefs&&a in c.localDefs?c.localDefs[a]:p(a)},parseBaseRetType:function(){if(this.eat("[")){var a=this.parseRetType();return this.eat("]")||this.error(),function(c,d){return new b.Arr(a(c,d))}}if(this.eat("+")){var c=this.parseRetType();return function(a,d){return b.getInstance(c(a,d))}}if(this.eat("!")){var d=this.word(/\d/);if(d)return d=Number(d),function(a,c){return c[d]||b.ANull};if(this.eat("this"))return function(a){return a};if(this.eat("custom:")){var e=this.word(/[\w$]/);return q[e]||function(){return b.ANull}}return this.fromWord("!"+d+this.word(/[\w$<>\.!]/))}var f=this.parseType();return function(){return f}},extendRetType:function(a){var c=this.word(/[\w<>$!]/)||this.error();return"!ret"==c?function(c,d){var e=a(c,d);if(e.retval)return e.retval;var f=new b.AVal;return e.propagate(new b.IsCallee(b.ANull,[],null,f)),f}:function(b,d){return a(b,d).getProp(c)}},parseRetType:function(){for(var a=this.parseBaseRetType();this.eat(".");)a=this.extendRetType(a);return a}};var n,o=a.parseEffect=function(a,c){var d;if(0==a.indexOf("propagate ")){var f=new m(a,10),g=f.parseRetType();f.eat(" ")||f.error();var h=f.parseRetType();e(c,function(a,b){g(a,b).propagate(h(a,b))})}else if(0==a.indexOf("call ")){var i=5==a.indexOf("and return ",5),f=new m(a,i?16:5),j=f.parseRetType(),k=null,l=[];for(f.eat(" this=")&&(k=f.parseRetType());f.eat(" ");)l.push(f.parseRetType());e(c,function(a,c){for(var d=j(a,c),e=k?k(a,c):b.ANull,f=[],g=0;g"!=a&&e.propagate(new b.PropHasSubset(a,c))})})}},p=a.parsePath=function(a){var c=b.cx(),d=c.paths[a],e=a;if(null!=d)return d;c.paths[a]=b.ANull;var f=n||c.topScope;if(c.localDefs)for(var g in c.localDefs)if(0==a.indexOf(g)){if(a==g)return c.paths[a]=c.localDefs[a];if("."==a.charAt(g.length)){f=c.localDefs[g],a=a.slice(g.length+1);break}}for(var h=a.split("."),i=0;i"),f=0;fa&&a>8||32===a||160===a}function c(a,c){for(;c>0;--c){var d=a.charCodeAt(c-1);if(10==d)break;if(!b(d))return!1}return!0}a.commentsBefore=function(a,d){var e,f=null,g=0;a:for(;d>0;){var h=a.charCodeAt(d-1);if(10==h)for(var i=--d,j=!1;i>0;--i){if(h=a.charCodeAt(i-1),47==h&&47==a.charCodeAt(i-2)){if(!c(a,i-2))break a;var k=a.slice(i,d);!g&&e?f[0]=k+"\n"+f[0]:(f||(f=[])).unshift(k),e=!0,g=0,d=i-2;break}if(10==h){if(!j&&++g>1)break a;break}j||b(h)||(j=!0)}else if(47==h&&42==a.charCodeAt(d-2)){for(var i=d-2;i>1;--i)if(42==a.charCodeAt(i-1)&&47==a.charCodeAt(i-2)){if(!c(a,i-2))break a;(f||(f=[])).unshift(a.slice(i,d-2)),e=!1,g=0;break}d=i-2}else{if(!b(h))break;--d}}return f},a.commentAfter=function(a,c){for(;ce?a.length:e)}b(d)&&++c}},a.ensureCommentsBefore=function(b,c){return c.hasOwnProperty("commentsBefore")?c.commentsBefore:c.commentsBefore=a.commentsBefore(b,c.start)}}),function(a){return"object"==typeof exports&&"object"==typeof module?a(exports,require("acorn/acorn"),require("acorn/acorn_loose"),require("acorn/util/walk"),require("./def"),require("./signal")):"function"==typeof define&&define.amd?define(["exports","acorn/acorn","acorn/acorn_loose","acorn/util/walk","./def","./signal"],a):(a(self.tern||(self.tern={}),acorn,acorn,acorn.walk,tern.def,tern.signal),void 0)}(function(a,b,c,d,e,f){"use strict";function g(a,b){var c=Object.create(a);if(b)for(var d in b)c[d]=b[d];return c}function h(a){for(var b=0,c=0,d=0,e=null,f=0;f1)return null;if(e)return e;for(var i=0,j=null,f=0;f").isEmpty()?1:2;else if(c){k=1;for(var l=0;l=i&&(i=k,j=g)}return j}function i(){}function j(a,b){fb.disabledComputing={fn:a,prev:fb.disabledComputing};try{return b()}finally{fb.disabledComputing=fb.disabledComputing.prev}}function k(a,b){var c=fb.props[a]||(fb.props[a]=[]);c.push(b)}function l(a){return fb.props[a]}function m(a){if(fb.workList)return a(fb.workList);var b=[],c=0,d=fb.workList=function(a,d,e){c3)&&a.forward)for(var f=0;f"));for(var h=b(c.self,"!this",0),i=0;!h&&i"):d.name}function u(a){switch(a){case"+":case"-":case"~":return fb.num;case"!":return fb.bool;case"typeof":return fb.str;case"void":case"delete":return F}}function v(a){switch(a){case"==":case"!=":case"===":case"!==":case"<":case">":case">=":case"<=":case"in":case"instanceof":return!0}}function w(a){switch(typeof a){case"boolean":return fb.bool;case"number":return fb.num;case"string":return fb.str;case"object":case"function":return a?V(fb.protos.RegExp):F}}function x(a){return function(b,c,d,e,f){var g=a(b,c,d,f);return e&&g.propagate(e),g}}function y(a){return function(b,c,d,e,f){return e||(e=new N),a(b,c,d,e,f),e}}function z(a,b,c,d,e){return lb[a.type](a,b,c,d,e)}function A(a,b){var c=a&&a[b],d=Array.prototype.slice.call(arguments,2);if(c)for(var e=0;e-1}:function(d,e){return e&&e.start>=b&&e.end<=c&&a.indexOf(d.origin)>-1}:null==c?function(b){return b.origin==a}:function(d,e){return e&&e.start>=b&&e.end<=c&&d.origin==a}}function C(a){qb=!0;var b=l(a);if(b)for(var c=0;cb||this.types.indexOf(a)>-1)return;this.signal("addType",a),this.types.push(a);var c=this.forward;c&&m(function(d){for(var e=0;eb&&(a=new _(a,b)),(this.forward||(this.forward=[])).push(a);var c=this.types;c.length&&m(function(d){for(var e=0;e-1},isEmpty:function(){return 0==this.types.length},getFunctionType:function(){for(var a=this.types.length-1;a>=0;--a)if(this.types[a]instanceof db)return this.types[a]},getType:function(a){return 0==this.types.length&&a!==!1?this.makeupType():1==this.types.length?this.types[0]:h(this.types)},computedPropType:function(){if(!this.propertyOf||!this.propertyOf.hasProp(""))return null;var a=this.propertyOf.getProp("");return a==this?null:a.getType()},makeupType:function(){var a=this.computedPropType();if(a)return a;if(!this.forward)return null;for(var b=this.forward.length-1;b>=0;--b){var c=this.forward[b].typeHint();if(c&&!c.isEmpty())return qb=!0,c}for(var d=Object.create(null),e=null,b=0;b"!=f&&"✖"!=f&&(d[f]=!0,e=f)}if(!e)return null;var g=l(e);if(g){var i=[];a:for(var b=0;b8||(a==fb.protos.Array?this.target.addType(new eb):this.target.addType(V(a,this.ctor))))}}),X=O("fn",{addType:function(a){if(a instanceof cb&&!a.hasCtor){a.hasCtor=this.fn;var b=new $(a,this.fn);b.addType(this.fn),a.forAllProps(function(a,c,d){d&&c.propagate(b)})}}}),Y=O("other, target",{addType:function(a,b){a==fb.str?this.target.addType(fb.str,b):a==fb.num&&this.other.hasType(fb.num)&&this.target.addType(fb.num,b)},typeHint:function(){return this.other}}),Z=a.IfObj=O("target",{addType:function(a,b){a instanceof cb&&this.target.addType(a,b)},propagatesTo:function(){return this.target}}),$=O("obj, ctor",{addType:function(a){a instanceof db&&a.self&&a.self.isEmpty()&&a.self.addType(V(this.obj,this.ctor),M)}}),_=O("inner, weight",{addType:function(a,b){this.inner.addType(a,Math.min(b,this.weight))},propagatesTo:function(){return this.inner.propagatesTo()},typeHint:function(){return this.inner.typeHint()},propHint:function(){return this.inner.propHint()}}),ab=a.Type=function(){};ab.prototype=g(F,{constructor:ab,propagate:function(a,b){a.addType(this,b)},hasType:function(a){return a==this},isEmpty:function(){return!1},typeHint:function(){return this},getType:function(){return this}});var bb=a.Prim=function(a,b){this.name=b,this.proto=a};bb.prototype=g(ab.prototype,{constructor:bb,toString:function(){return this.name},getProp:function(a){return this.proto.hasProp(a)||F},gatherProperties:function(a,b){this.proto&&this.proto.gatherProperties(a,b)}});var cb=a.Obj=function(a,b){if(this.props||(this.props=Object.create(null)),this.proto=a===!0?fb.protos.Object:a,a&&!b&&a.name&&!(this instanceof db)){var c=/^(.*)\.prototype$/.exec(this.proto.name);c&&(b=c[1])}this.name=b,this.maybeProps=null,this.origin=fb.curOrigin};cb.prototype=g(ab.prototype,{constructor:cb,toString:function(a){if(!a&&this.name)return this.name;var b=[],c=!1;for(var d in this.props)if(""!=d){if(b.length>5){c=!0;break}a?b.push(d+": "+E(this.props[d].getType(),a-1)):b.push(d)}return b.sort(),c&&b.push("..."),"{"+b.join(", ")+"}"},hasProp:function(a,b){var c=this.props[a];if(b!==!1)for(var d=this.proto;d&&!c;d=d.proto)c=d.props[a];return c},defProp:function(a,b){var c=this.hasProp(a,!1);if(c)return b&&!c.originNode&&(c.originNode=b),c;if("__proto__"==a||"✖"==a)return F;var d=this.maybeProps&&this.maybeProps[a];return d?(delete this.maybeProps[a],this.maybeUnregProtoPropHandler()):(d=new N,d.propertyOf=this),this.props[a]=d,d.originNode=b,d.origin=fb.curOrigin,this.broadcastProp(a,d,!0),d},getProp:function(a){var b=this.hasProp(a,!0)||this.maybeProps&&this.maybeProps[a];if(b)return b;if("__proto__"==a||"✖"==a)return F;var c=this.ensureMaybeProps()[a]=new N;return c.propertyOf=this,c},broadcastProp:function(a,b,c){if(c&&(this.signal("addProp",a,b),this instanceof ib||k(a,this)),this.onNewProp)for(var d=0;d"!=c&&a(c,this,b);this.proto&&this.proto.gatherProperties(a,b+1)}});var db=a.Fn=function(a,b,c,d,e){cb.call(this,fb.protos.Function,a),this.self=b,this.args=c,this.argNames=d,this.retval=e};db.prototype=g(cb.prototype,{constructor:db,toString:function(a){a&&a--;for(var b="fn(",c=0;c");a&&a.propagate(b)};eb.prototype=g(cb.prototype,{constructor:eb,toString:function(a){return"["+E(this.getProp("").getType(),a,this)+"]"}}),a.Context=function(b,c){this.parent=c,this.props=Object.create(null),this.protos=Object.create(null),this.origins=[],this.curOrigin="ecma5",this.paths=Object.create(null),this.definitions=Object.create(null),this.purgeGen=0,this.workList=null,this.disabledComputing=null,a.withContext(this,function(){if(fb.protos.Object=new cb(null,"Object.prototype"),fb.topScope=new ib,fb.topScope.name="",fb.protos.Array=new cb(!0,"Array.prototype"),fb.protos.Function=new cb(!0,"Function.prototype"),fb.protos.RegExp=new cb(!0,"RegExp.prototype"),fb.protos.String=new cb(!0,"String.prototype"),fb.protos.Number=new cb(!0,"Number.prototype"),fb.protos.Boolean=new cb(!0,"Boolean.prototype"),fb.str=new bb(fb.protos.String,"string"),fb.bool=new bb(fb.protos.Boolean,"bool"),fb.num=new bb(fb.protos.Number,"number"),fb.curOrigin=null,b)for(var a=0;ad;++d)z(a.expressions[d],b,c,F);return z(a.expressions[e],b,c)}),UnaryExpression:x(function(a,b,c){return z(a.argument,b,c,F),u(a.operator)}),UpdateExpression:x(function(a,b,c){return z(a.argument,b,c,F),fb.num}),BinaryExpression:x(function(a,b,c){if("+"==a.operator){var d=z(a.left,b,c),e=z(a.right,b,c);if(d.hasType(fb.str)||e.hasType(fb.str))return fb.str;if(d.hasType(fb.num)&&e.hasType(fb.num))return fb.num;var f=new N;return d.propagate(new Y(e,f)),e.propagate(new Y(d,f)),f}return z(a.left,b,c,F),z(a.right,b,c,F),v(a.operator)?fb.bool:fb.num}),AssignmentExpression:x(function(a,b,c){var d,e,f;if("MemberExpression"==a.left.type?(f=t(a.left,b,c),"Identifier"==a.left.object.type&&(e=a.left.object.name+"."+f)):e=a.left.name,"="!=a.operator&&"+="!=a.operator?(z(a.right,b,c,F),d=fb.num):d=z(a.right,b,c,null,e),"MemberExpression"==a.left.type){var g=z(a.left.object,b,c);if("prototype"==f&&n(b,20),""==f){var h=a.left.property.name,i=b.props[h],j=i&&i.iteratesOver;if(j){n(b,20);var k="MemberExpression"==a.right.type&&a.right.computed&&a.right.property.name==h;return j.forAllProps(function(a,b,c){c&&"prototype"!=a&&""!=a&&g.propagate(new Q(a,k?b:F))}),d}}g.propagate(new Q(f,d,a.left.property))}else{var h=b.defVar(a.left.name,a.left);h.maybePurge&&(h.maybePurge=!1),d.propagate(h)}return d}),LogicalExpression:y(function(a,b,c,d){z(a.left,b,c,d),z(a.right,b,c,d)}),ConditionalExpression:y(function(a,b,c,d){z(a.test,b,c,F),z(a.consequent,b,c,d),z(a.alternate,b,c,d)}),NewExpression:y(function(a,b,c,d,e){"Identifier"==a.callee.type&&a.callee.name in b.props&&n(b,20);for(var f=0,g=[];f-1&&n(b,30),g.propagate(new T(h,f,a.arguments,d))}else{var i=z(a.callee,b,c);b.fnType&&b.fnType.args.indexOf(i)>-1&&n(b,30);var j=i.getFunctionType();j&&j.instantiateScore&&b.fnType&&n(b,j.instantiateScore/5),i.propagate(new S(fb.topScope,f,a.arguments,d))}}),MemberExpression:y(function(a,b,c,d){var e=t(a,b),f=z(a.object,b,c),g=f.getProp(e);if(""==e){var h=z(a.property,b,c);if(!h.hasType(fb.num))return g.propagate(d,J)}g.propagate(d)}),Identifier:x(function(a,b){return"arguments"!=a.name||!b.fnType||a.name in b.props||b.defProp(a.name,b.fnType.originNode).addType(new eb(b.fnType.arguments=new N)),b.getProp(a.name)}),ThisExpression:x(function(a,b){return b.fnType?b.fnType.self:fb.topScope}),Literal:x(function(a){return w(a.value)})},mb=d.make({Expression:function(a,b,c){z(a,b,c,F)},FunctionDeclaration:function(a,b,c){var d=a.body.scope,e=d.fnType;c(a.body,b,"ScopeBody"),p(a,d)||r(d);var f=b.getProp(a.id.name);f.addType(e)},VariableDeclaration:function(a,b,c){for(var d=0;d"==c?F:C(c)},Identifier:function(a,b){return b.hasProp(a.name)||F},ThisExpression:function(a,b){return b.fnType?b.fnType.self:fb.topScope +},Literal:function(a){return w(a.value)}},pb=a.searchVisitor=d.make({Function:function(a,b,c){var d=a.body.scope;a.id&&c(a.id,d);for(var e=0;eb?!1:ob.hasOwnProperty(c.type)};return d.findNodeAround(a,c,g,pb,e||fb.topScope)},a.expressionType=function(a){return D(a.node,a.state)};var qb=!1;a.resetGuessing=function(a){qb=a},a.didGuess=function(){return qb},a.forAllPropertiesOf=function(a,b){a.gatherProperties(b,0)};var rb=d.make({},pb);a.findRefs=function(a,b,c,e,f){rb.Identifier=function(a,b){if(a.name==c)for(var d=b;d;d=d.prev)if(d==e&&f(a,b),c in d.props)return},d.recursive(a,b,null,rb)};var sb=d.make({Function:function(a,b,c){c(a.body,a.body.scope,"ScopeBody")}});a.findPropRefs=function(a,b,c,e,f){d.simple(a,{MemberExpression:function(a,b){a.computed||a.property.name!=e||D(a.object,b).getType()==c&&f(a.property)},ObjectExpression:function(a,b){if(D(a,b).getType()==c)for(var d=0;di)return null;var k=b.slice(c,i);if(!/^[\w$]+$/.test(k))return null;e.push(k),c=i+1;var l=j(a,b,c);if(!l)return null;c=l.end,f.push(l.type),c=h(b,c);var m=b.charAt(c);if(++c,m==d)break;if(","!=m)return null}return{labels:e,types:f,end:c}}function j(b,c,d){d=h(c,d);var e;if(c.indexOf("function(",d)==d){var f=i(b,c,d+9,")"),g=a.ANull;if(!f)return null;if(d=h(c,f.end),":"==c.charAt(d)){++d;var k=j(b,c,d+1);if(!k)return null;d=k.end,g=k.type}e=new a.Fn(null,a.ANull,f.types,f.labels,g)}else if("["==c.charAt(d)){var l=j(b,c,d+1);if(!l)return null;if(d=h(c,l.end),"]"!=c.charAt(d))return null;++d,e=new a.Arr(l.type)}else if("{"==c.charAt(d)){var m=i(b,c,d+1,"}");if(!m)return null;e=new a.Obj(!0);for(var n=0;n"!=c.charAt(d++))return null;l=r.type}e=new a.Arr(l)}else if(/^object$/i.test(q)){if(e=new a.Obj(!0),"."==c.charAt(d)&&"<"==c.charAt(d+1)){var s=j(b,c,d+2);if(!s)return null;if(d=h(c,s.end),","!=c.charAt(d++))return null;var t=j(b,c,d);if(!t)return null;if(d=h(c,t.end),">"!=c.charAt(d++))return null;t.type.propagate(e.defProp(""))}}else{var u=b.hasProp(q);if(u&&(u=u.getType()),u)if(u instanceof a.Fn&&/^[A-Z]/.test(q)){var v=u.getProp("prototype").getType();e=v instanceof a.Obj?a.getInstance(v):u}else e=u;else e=a.ANull}}var w=!1;return"="==c.charAt(d)&&(++d,w=!0),{type:e,end:d,isOptional:w}}function k(a,b,c){if(c=h(b,c||0),"{"!=b.charAt(c))return null;var d=j(a,b,c+1);return d&&"}"==b.charAt(d.end)?(++d.end,d):null}function l(a,b,c,d){for(var e,f,g,h,i=0;i ?","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/getPrototypeOf","!doc":"Returns the prototype (i.e. the internal prototype) of the specified object."},create:{"!type":"fn(proto: ?) -> !custom:Object_create","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/create","!doc":"Creates a new object with the specified prototype object and properties."},defineProperty:{"!type":"fn(obj: ?, prop: string, desc: ?)","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/defineProperty","!doc":"Defines a new property directly on an object, or modifies an existing property on an object, and returns the object. If you want to see how to use the Object.defineProperty method with a binary-flags-like syntax, see this article."},defineProperties:{"!type":"fn(obj: ?, props: ?)","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/defineProperty","!doc":"Defines a new property directly on an object, or modifies an existing property on an object, and returns the object. If you want to see how to use the Object.defineProperty method with a binary-flags-like syntax, see this article."},getOwnPropertyDescriptor:{"!type":"fn(obj: ?, prop: string) -> ?","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor","!doc":"Returns a property descriptor for an own property (that is, one directly present on an object, not present by dint of being along an object's prototype chain) of a given object."},keys:{"!type":"fn(obj: ?) -> [string]","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/keys","!doc":"Returns an array of a given object's own enumerable properties, in the same order as that provided by a for-in loop (the difference being that a for-in loop enumerates properties in the prototype chain as well)."},getOwnPropertyNames:{"!type":"fn(obj: ?) -> [string]","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames","!doc":"Returns an array of all properties (enumerable or not) found directly upon a given object."},seal:{"!type":"fn(obj: ?)","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/seal","!doc":"Seals an object, preventing new properties from being added to it and marking all existing properties as non-configurable. Values of present properties can still be changed as long as they are writable."},isSealed:{"!type":"fn(obj: ?) -> bool","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/isSealed","!doc":"Determine if an object is sealed."},freeze:{"!type":"fn(obj: ?)","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/freeze","!doc":"Freezes an object: that is, prevents new properties from being added to it; prevents existing properties from being removed; and prevents existing properties, or their enumerability, configurability, or writability, from being changed. In essence the object is made effectively immutable. The method returns the object being frozen."},isFrozen:{"!type":"fn(obj: ?) -> bool","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/isFrozen","!doc":"Determine if an object is frozen."},prototype:{"!stdProto":"Object",toString:{"!type":"fn() -> string","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/toString","!doc":"Returns a string representing the object."},toLocaleString:{"!type":"fn() -> string","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/toLocaleString","!doc":"Returns a string representing the object. This method is meant to be overriden by derived objects for locale-specific purposes."},valueOf:{"!type":"fn() -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/valueOf","!doc":"Returns the primitive value of the specified object"},hasOwnProperty:{"!type":"fn(prop: string) -> bool","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/hasOwnProperty","!doc":"Returns a boolean indicating whether the object has the specified property."},propertyIsEnumerable:{"!type":"fn(prop: string) -> bool","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/propertyIsEnumerable","!doc":"Returns a Boolean indicating whether the specified property is enumerable."}},"!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object","!doc":"Creates an object wrapper."},Function:{"!type":"fn(body: string) -> fn()",prototype:{"!stdProto":"Function",apply:{"!type":"fn(this: ?, args: [?])","!effects":["call and return !this this=!0 !1. !1. !1."],"!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/apply","!doc":"Calls a function with a given this value and arguments provided as an array (or an array like object)."},call:{"!type":"fn(this: ?, args?: ?) -> !this.!ret","!effects":["call and return !this this=!0 !1 !2 !3 !4"],"!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/call","!doc":"Calls a function with a given this value and arguments provided individually."},bind:{"!type":"fn(this: ?, args?: ?) -> !custom:Function_bind","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind","!doc":"Creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function was called."},prototype:"?"},"!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function","!doc":"Every function in JavaScript is actually a Function object."},Array:{"!type":"fn(size: number) -> !custom:Array_ctor",isArray:{"!type":"fn(value: ?) -> bool","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/isArray","!doc":"Returns true if an object is an array, false if it is not."},prototype:{"!stdProto":"Array",length:{"!type":"number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/length","!doc":"An unsigned, 32-bit integer that specifies the number of elements in an array."},concat:{"!type":"fn(other: [?]) -> !this","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/concat","!doc":"Returns a new array comprised of this array joined with other array(s) and/or value(s)."},join:{"!type":"fn(separator?: string) -> string","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/join","!doc":"Joins all elements of an array into a string."},splice:{"!type":"fn(pos: number, amount: number)","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/splice","!doc":"Changes the content of an array, adding new elements while removing old elements."},pop:{"!type":"fn() -> !this.","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/pop","!doc":"Removes the last element from an array and returns that element."},push:{"!type":"fn(newelt: ?) -> number","!effects":["propagate !0 !this."],"!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/push","!doc":"Mutates an array by appending the given elements and returning the new length of the array."},shift:{"!type":"fn() -> !this.","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/shift","!doc":"Removes the first element from an array and returns that element. This method changes the length of the array."},unshift:{"!type":"fn(newelt: ?) -> number","!effects":["propagate !0 !this."],"!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/unshift","!doc":"Adds one or more elements to the beginning of an array and returns the new length of the array."},slice:{"!type":"fn(from: number, to?: number) -> !this","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/slice","!doc":"Returns a shallow copy of a portion of an array."},reverse:{"!type":"fn()","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/reverse","!doc":"Reverses an array in place. The first array element becomes the last and the last becomes the first."},sort:{"!type":"fn(compare?: fn(a: ?, b: ?) -> number)","!effects":["call !0 !this. !this."],"!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/sort","!doc":"Sorts the elements of an array in place and returns the array."},indexOf:{"!type":"fn(elt: ?, from?: number) -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/indexOf","!doc":"Returns the first index at which a given element can be found in the array, or -1 if it is not present."},lastIndexOf:{"!type":"fn(elt: ?, from?: number) -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/lastIndexOf","!doc":"Returns the last index at which a given element can be found in the array, or -1 if it is not present. The array is searched backwards, starting at fromIndex."},every:{"!type":"fn(test: fn(elt: ?, i: number) -> bool, context?: ?) -> bool","!effects":["call !0 this=!1 !this. number"],"!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/every","!doc":"Tests whether all elements in the array pass the test implemented by the provided function."},some:{"!type":"fn(test: fn(elt: ?, i: number) -> bool, context?: ?) -> bool","!effects":["call !0 this=!1 !this. number"],"!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/some","!doc":"Tests whether some element in the array passes the test implemented by the provided function."},filter:{"!type":"fn(test: fn(elt: ?, i: number) -> bool, context?: ?) -> !this","!effects":["call !0 this=!1 !this. number"],"!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/filter","!doc":"Creates a new array with all elements that pass the test implemented by the provided function."},forEach:{"!type":"fn(f: fn(elt: ?, i: number), context?: ?)","!effects":["call !0 this=!1 !this. number"],"!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/forEach","!doc":"Executes a provided function once per array element."},map:{"!type":"fn(f: fn(elt: ?, i: number) -> ?, context?: ?) -> [!0.!ret]","!effects":["call !0 this=!1 !this. number"],"!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/map","!doc":"Creates a new array with the results of calling a provided function on every element in this array."},reduce:{"!type":"fn(combine: fn(sum: ?, elt: ?, i: number) -> ?, init?: ?) -> !0.!ret","!effects":["call !0 !1 !this. number"],"!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/Reduce","!doc":"Apply a function against an accumulator and each value of the array (from left-to-right) as to reduce it to a single value."},reduceRight:{"!type":"fn(combine: fn(sum: ?, elt: ?, i: number) -> ?, init?: ?) -> !0.!ret","!effects":["call !0 !1 !this. number"],"!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/ReduceRight","!doc":"Apply a function simultaneously against two values of the array (from right-to-left) as to reduce it to a single value."}},"!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array","!doc":"The JavaScript Array global object is a constructor for arrays, which are high-level, list-like objects."},String:{"!type":"fn(value: ?) -> string",fromCharCode:{"!type":"fn(code: number) -> string","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/fromCharCode","!doc":"Returns a string created by using the specified sequence of Unicode values."},prototype:{"!stdProto":"String",length:{"!type":"number","!url":"https://developer.mozilla.org/en/docs/JavaScript/Reference/Global_Objects/String/length","!doc":"Represents the length of a string."},"":"string",charAt:{"!type":"fn(i: number) -> string","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/charAt","!doc":"Returns the specified character from a string."},charCodeAt:{"!type":"fn(i: number) -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/charCodeAt","!doc":"Returns the numeric Unicode value of the character at the given index (except for unicode codepoints > 0x10000)."},indexOf:{"!type":"fn(char: string, from?: number) -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/indexOf","!doc":"Returns the index within the calling String object of the first occurrence of the specified value, starting the search at fromIndex,\nreturns -1 if the value is not found."},lastIndexOf:{"!type":"fn(char: string, from?: number) -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/lastIndexOf","!doc":"Returns the index within the calling String object of the last occurrence of the specified value, or -1 if not found. The calling string is searched backward, starting at fromIndex."},substring:{"!type":"fn(from: number, to?: number) -> string","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/substring","!doc":"Returns a subset of a string between one index and another, or through the end of the string."},substr:{"!type":"fn(from: number, length?: number) -> string","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/substr","!doc":"Returns the characters in a string beginning at the specified location through the specified number of characters."},slice:{"!type":"fn(from: number, to?: number) -> string","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/slice","!doc":"Extracts a section of a string and returns a new string."},trim:{"!type":"fn() -> string","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/Trim","!doc":"Removes whitespace from both ends of the string."},trimLeft:{"!type":"fn() -> string","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/TrimLeft","!doc":"Removes whitespace from the left end of the string."},trimRight:{"!type":"fn() -> string","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/TrimRight","!doc":"Removes whitespace from the right end of the string."},toUpperCase:{"!type":"fn() -> string","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/toUpperCase","!doc":"Returns the calling string value converted to uppercase."},toLowerCase:{"!type":"fn() -> string","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/toLowerCase","!doc":"Returns the calling string value converted to lowercase."},toLocaleUpperCase:{"!type":"fn() -> string","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/toLocaleUpperCase","!doc":"Returns the calling string value converted to upper case, according to any locale-specific case mappings."},toLocaleLowerCase:{"!type":"fn() -> string","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/toLocaleLowerCase","!doc":"Returns the calling string value converted to lower case, according to any locale-specific case mappings."},split:{"!type":"fn(pattern: string) -> [string]","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/split","!doc":"Splits a String object into an array of strings by separating the string into substrings."},concat:{"!type":"fn(other: string) -> string","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/concat","!doc":"Combines the text of two or more strings and returns a new string."},localeCompare:{"!type":"fn(other: string) -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/localeCompare","!doc":"Returns a number indicating whether a reference string comes before or after or is the same as the given string in sort order."},match:{"!type":"fn(pattern: +RegExp) -> [string]","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/match","!doc":"Used to retrieve the matches when matching a string against a regular expression."},replace:{"!type":"fn(pattern: +RegExp, replacement: string) -> string","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/replace","!doc":"Returns a new string with some or all matches of a pattern replaced by a replacement. The pattern can be a string or a RegExp, and the replacement can be a string or a function to be called for each match."},search:{"!type":"fn(pattern: +RegExp) -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/search","!doc":"Executes the search for a match between a regular expression and this String object."}},"!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String","!doc":"The String global object is a constructor for strings, or a sequence of characters."},Number:{"!type":"fn(value: ?) -> number",MAX_VALUE:{"!type":"number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Number/MAX_VALUE","!doc":"The maximum numeric value representable in JavaScript."},MIN_VALUE:{"!type":"number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Number/MIN_VALUE","!doc":"The smallest positive numeric value representable in JavaScript."},POSITIVE_INFINITY:{"!type":"number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Number/POSITIVE_INFINITY","!doc":"A value representing the positive Infinity value."},NEGATIVE_INFINITY:{"!type":"number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Number/NEGATIVE_INFINITY","!doc":"A value representing the negative Infinity value."},prototype:{"!stdProto":"Number",toString:{"!type":"fn(radix?: number) -> string","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Number/toString","!doc":"Returns a string representing the specified Number object"},toFixed:{"!type":"fn(digits: number) -> string","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Number/toFixed","!doc":"Formats a number using fixed-point notation"},toExponential:{"!type":"fn(digits: number) -> string","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Number/toExponential","!doc":"Returns a string representing the Number object in exponential notation"}},"!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Number","!doc":"The Number JavaScript object is a wrapper object allowing you to work with numerical values. A Number object is created using the Number() constructor."},Boolean:{"!type":"fn(value: ?) -> bool",prototype:{"!stdProto":"Boolean"},"!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Boolean","!doc":"The Boolean object is an object wrapper for a boolean value."},RegExp:{"!type":"fn(source: string, flags?: string)",prototype:{"!stdProto":"RegExp",exec:{"!type":"fn(input: string) -> [string]","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/RegExp/exec","!doc":"Executes a search for a match in a specified string. Returns a result array, or null."},compile:{"!type":"fn(source: string, flags?: string)","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/RegExp","!doc":"Creates a regular expression object for matching text with a pattern."},test:{"!type":"fn(input: string) -> bool","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/RegExp/test","!doc":"Executes the search for a match between a regular expression and a specified string. Returns true or false."},global:{"!type":"bool","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/RegExp","!doc":"Creates a regular expression object for matching text with a pattern."},ignoreCase:{"!type":"bool","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/RegExp","!doc":"Creates a regular expression object for matching text with a pattern."},multiline:{"!type":"bool","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/RegExp/multiline","!doc":"Reflects whether or not to search in strings across multiple lines.\n"},source:{"!type":"string","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/RegExp/source","!doc":"A read-only property that contains the text of the pattern, excluding the forward slashes.\n"},lastIndex:{"!type":"number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/RegExp/lastIndex","!doc":"A read/write integer property that specifies the index at which to start the next match."}},"!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/RegExp","!doc":"Creates a regular expression object for matching text with a pattern."},Date:{"!type":"fn(ms: number)",parse:{"!type":"fn(source: string) -> +Date","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/parse","!doc":"Parses a string representation of a date, and returns the number of milliseconds since January 1, 1970, 00:00:00 UTC."},UTC:{"!type":"fn(year: number, month: number, date: number, hour?: number, min?: number, sec?: number, ms?: number) -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/UTC","!doc":"Accepts the same parameters as the longest form of the constructor, and returns the number of milliseconds in a Date object since January 1, 1970, 00:00:00, universal time."},now:{"!type":"fn() -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/now","!doc":"Returns the number of milliseconds elapsed since 1 January 1970 00:00:00 UTC."},prototype:{toUTCString:{"!type":"fn() -> string","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/toUTCString","!doc":"Converts a date to a string, using the universal time convention."},toISOString:{"!type":"fn() -> string","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/toISOString","!doc":"JavaScript provides a direct way to convert a date object into a string in ISO format, the ISO 8601 Extended Format."},toDateString:{"!type":"fn() -> string","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/toDateString","!doc":"Returns the date portion of a Date object in human readable form in American English."},toTimeString:{"!type":"fn() -> string","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/toTimeString","!doc":"Returns the time portion of a Date object in human readable form in American English."},toLocaleDateString:{"!type":"fn() -> string","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/toLocaleDateString","!doc":"Converts a date to a string, returning the \"date\" portion using the operating system's locale's conventions.\n"},toLocaleTimeString:{"!type":"fn() -> string","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/toLocaleTimeString","!doc":'Converts a date to a string, returning the "time" portion using the current locale\'s conventions.'},getTime:{"!type":"fn() -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getTime","!doc":"Returns the numeric value corresponding to the time for the specified date according to universal time."},getFullYear:{"!type":"fn() -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getFullYear","!doc":"Returns the year of the specified date according to local time."},getYear:{"!type":"fn() -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getYear","!doc":"Returns the year in the specified date according to local time."},getMonth:{"!type":"fn() -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getMonth","!doc":"Returns the month in the specified date according to local time."},getUTCMonth:{"!type":"fn() -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getUTCMonth","!doc":"Returns the month of the specified date according to universal time.\n"},getDate:{"!type":"fn() -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getDate","!doc":"Returns the day of the month for the specified date according to local time."},getUTCDate:{"!type":"fn() -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getUTCDate","!doc":"Returns the day (date) of the month in the specified date according to universal time.\n"},getDay:{"!type":"fn() -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getDay","!doc":"Returns the day of the week for the specified date according to local time."},getUTCDay:{"!type":"fn() -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getUTCDay","!doc":"Returns the day of the week in the specified date according to universal time.\n"},getHours:{"!type":"fn() -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getHours","!doc":"Returns the hour for the specified date according to local time."},getUTCHours:{"!type":"fn() -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getUTCHours","!doc":"Returns the hours in the specified date according to universal time.\n"},getMinutes:{"!type":"fn() -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getMinutes","!doc":"Returns the minutes in the specified date according to local time."},getUTCMinutes:{"!type":"fn() -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date","!doc":"Creates JavaScript Date instances which let you work with dates and times."},getSeconds:{"!type":"fn() -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getSeconds","!doc":"Returns the seconds in the specified date according to local time."},getUTCSeconds:{"!type":"fn() -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getUTCSeconds","!doc":"Returns the seconds in the specified date according to universal time.\n"},getMilliseconds:{"!type":"fn() -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getMilliseconds","!doc":"Returns the milliseconds in the specified date according to local time."},getUTCMilliseconds:{"!type":"fn() -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getUTCMilliseconds","!doc":"Returns the milliseconds in the specified date according to universal time.\n"},getTimezoneOffset:{"!type":"fn() -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/getTimezoneOffset","!doc":"Returns the time-zone offset from UTC, in minutes, for the current locale."},setTime:{"!type":"fn(date: +Date) -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setTime","!doc":"Sets the Date object to the time represented by a number of milliseconds since January 1, 1970, 00:00:00 UTC.\n"},setFullYear:{"!type":"fn(year: number) -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setFullYear","!doc":"Sets the full year for a specified date according to local time.\n"},setUTCFullYear:{"!type":"fn(year: number) -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setUTCFullYear","!doc":"Sets the full year for a specified date according to universal time.\n"},setMonth:{"!type":"fn(month: number) -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setMonth","!doc":"Set the month for a specified date according to local time."},setUTCMonth:{"!type":"fn(month: number) -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setUTCMonth","!doc":"Sets the month for a specified date according to universal time.\n"},setDate:{"!type":"fn(day: number) -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setDate","!doc":"Sets the day of the month for a specified date according to local time."},setUTCDate:{"!type":"fn(day: number) -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setUTCDate","!doc":"Sets the day of the month for a specified date according to universal time.\n"},setHours:{"!type":"fn(hour: number) -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setHours","!doc":"Sets the hours for a specified date according to local time, and returns the number of milliseconds since 1 January 1970 00:00:00 UTC until the time represented by the updated Date instance."},setUTCHours:{"!type":"fn(hour: number) -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setUTCHours","!doc":"Sets the hour for a specified date according to universal time.\n"},setMinutes:{"!type":"fn(min: number) -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setMinutes","!doc":"Sets the minutes for a specified date according to local time."},setUTCMinutes:{"!type":"fn(min: number) -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setUTCMinutes","!doc":"Sets the minutes for a specified date according to universal time.\n"},setSeconds:{"!type":"fn(sec: number) -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setSeconds","!doc":"Sets the seconds for a specified date according to local time."},setUTCSeconds:{"!type":"fn(sec: number) -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setUTCSeconds","!doc":"Sets the seconds for a specified date according to universal time.\n"},setMilliseconds:{"!type":"fn(ms: number) -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setMilliseconds","!doc":"Sets the milliseconds for a specified date according to local time.\n"},setUTCMilliseconds:{"!type":"fn(ms: number) -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/setUTCMilliseconds","!doc":"Sets the milliseconds for a specified date according to universal time.\n"}},"!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date","!doc":"Creates JavaScript Date instances which let you work with dates and times."},Error:{"!type":"fn(message: string)",prototype:{name:{"!type":"string","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Error/name","!doc":"A name for the type of error."},message:{"!type":"string","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Error/message","!doc":"A human-readable description of the error."}},"!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Error","!doc":"Creates an error object."},SyntaxError:{"!type":"fn(message: string)",prototype:"Error.prototype","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/SyntaxError","!doc":"Represents an error when trying to interpret syntactically invalid code."},ReferenceError:{"!type":"fn(message: string)",prototype:"Error.prototype","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/ReferenceError","!doc":"Represents an error when a non-existent variable is referenced."},URIError:{"!type":"fn(message: string)",prototype:"Error.prototype","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/URIError","!doc":"Represents an error when a malformed URI is encountered."},EvalError:{"!type":"fn(message: string)",prototype:"Error.prototype","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/EvalError","!doc":"Represents an error regarding the eval function."},RangeError:{"!type":"fn(message: string)",prototype:"Error.prototype","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/RangeError","!doc":"Represents an error when a number is not within the correct range allowed."},parseInt:{"!type":"fn(string: string, radix?: number) -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/parseInt","!doc":"Parses a string argument and returns an integer of the specified radix or base."},parseFloat:{"!type":"fn(string: string) -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/parseFloat","!doc":"Parses a string argument and returns a floating point number."},isNaN:{"!type":"fn(value: number) -> bool","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/isNaN","!doc":"Determines whether a value is NaN or not. Be careful, this function is broken. You may be interested in ECMAScript 6 Number.isNaN."},eval:{"!type":"fn(code: string) -> ?","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/eval","!doc":"Evaluates JavaScript code represented as a string."},encodeURI:{"!type":"fn(uri: string) -> string","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/encodeURI","!doc":'Encodes a Uniform Resource Identifier (URI) by replacing each instance of certain characters by one, two, three, or four escape sequences representing the UTF-8 encoding of the character (will only be four escape sequences for characters composed of two "surrogate" characters).'},encodeURIComponent:{"!type":"fn(uri: string) -> string","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/encodeURIComponent","!doc":'Encodes a Uniform Resource Identifier (URI) component by replacing each instance of certain characters by one, two, three, or four escape sequences representing the UTF-8 encoding of the character (will only be four escape sequences for characters composed of two "surrogate" characters).'},decodeURI:{"!type":"fn(uri: string) -> string","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/decodeURI","!doc":"Decodes a Uniform Resource Identifier (URI) previously created by encodeURI or by a similar routine."},decodeURIComponent:{"!type":"fn(uri: string) -> string","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/decodeURIComponent","!doc":"Decodes a Uniform Resource Identifier (URI) component previously created by encodeURIComponent or by a similar routine."},Math:{E:{"!type":"number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/E","!doc":"The base of natural logarithms, e, approximately 2.718."},LN2:{"!type":"number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/LN2","!doc":"The natural logarithm of 2, approximately 0.693."},LN10:{"!type":"number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/LN10","!doc":"The natural logarithm of 10, approximately 2.302."},LOG2E:{"!type":"number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/LOG2E","!doc":"The base 2 logarithm of E (approximately 1.442)."},LOG10E:{"!type":"number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/LOG10E","!doc":"The base 10 logarithm of E (approximately 0.434)."},SQRT1_2:{"!type":"number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/SQRT1_2","!doc":"The square root of 1/2; equivalently, 1 over the square root of 2, approximately 0.707."},SQRT2:{"!type":"number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/SQRT2","!doc":"The square root of 2, approximately 1.414."},PI:{"!type":"number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/PI","!doc":"The ratio of the circumference of a circle to its diameter, approximately 3.14159."},abs:{"!type":"fn(number) -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/abs","!doc":"Returns the absolute value of a number."},cos:{"!type":"fn(number) -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/cos","!doc":"Returns the cosine of a number."},sin:{"!type":"fn(number) -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/sin","!doc":"Returns the sine of a number."},tan:{"!type":"fn(number) -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/tan","!doc":"Returns the tangent of a number."},acos:{"!type":"fn(number) -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/acos","!doc":"Returns the arccosine (in radians) of a number."},asin:{"!type":"fn(number) -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/asin","!doc":"Returns the arcsine (in radians) of a number."},atan:{"!type":"fn(number) -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/atan","!doc":"Returns the arctangent (in radians) of a number."},atan2:{"!type":"fn(number, number) -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/atan2","!doc":"Returns the arctangent of the quotient of its arguments."},ceil:{"!type":"fn(number) -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/ceil","!doc":"Returns the smallest integer greater than or equal to a number."},floor:{"!type":"fn(number) -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/floor","!doc":"Returns the largest integer less than or equal to a number."},round:{"!type":"fn(number) -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/round","!doc":"Returns the value of a number rounded to the nearest integer."},exp:{"!type":"fn(number) -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/exp","!doc":"Returns Ex, where x is the argument, and E is Euler's constant, the base of the natural logarithms."},log:{"!type":"fn(number) -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/log","!doc":"Returns the natural logarithm (base E) of a number."},sqrt:{"!type":"fn(number) -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/sqrt","!doc":"Returns the square root of a number."},pow:{"!type":"fn(number, number) -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/pow","!doc":"Returns base to the exponent power, that is, baseexponent."},max:{"!type":"fn(number, number) -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/max","!doc":"Returns the largest of zero or more numbers."},min:{"!type":"fn(number, number) -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/min","!doc":"Returns the smallest of zero or more numbers."},random:{"!type":"fn() -> number","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/random","!doc":"Returns a floating-point, pseudo-random number in the range [0, 1) that is, from 0 (inclusive) up to but not including 1 (exclusive), which you can then scale to your desired range."},"!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math","!doc":"A built-in object that has properties and methods for mathematical constants and functions."},JSON:{parse:{"!type":"fn(json: string) -> ?","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/JSON/parse","!doc":"Parse a string as JSON, optionally transforming the value produced by parsing."},stringify:{"!type":"fn(value: ?) -> string","!url":"https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/JSON/stringify","!doc":"Convert a value to JSON, optionally replacing values if a replacer function is specified, or optionally including only the specified properties if a replacer array is specified."},"!url":"https://developer.mozilla.org/en-US/docs/JSON","!doc":"JSON (JavaScript Object Notation) is a data-interchange format. It closely resembles a subset of JavaScript syntax, although it is not a strict subset. (See JSON in the JavaScript Reference for full details.) It is useful when writing any kind of JavaScript-based application, including websites and browser extensions. For example, you might store user information in JSON format in a cookie, or you might store extension preferences in JSON in a string-valued browser preference."}},ternBasicDefs[1]={"!name":"browser",location:{assign:{"!type":"fn(url: string)","!url":"https://developer.mozilla.org/en/docs/DOM/window.location","!doc":"Load the document at the provided URL."},replace:{"!type":"fn(url: string)","!url":"https://developer.mozilla.org/en/docs/DOM/window.location","!doc":"Replace the current document with the one at the provided URL. The difference from the assign() method is that after using replace() the current page will not be saved in session history, meaning the user won't be able to use the Back button to navigate to it."},reload:{"!type":"fn()","!url":"https://developer.mozilla.org/en/docs/DOM/window.location","!doc":"Reload the document from the current URL. forceget is a boolean, which, when it is true, causes the page to always be reloaded from the server. If it is false or not specified, the browser may reload the page from its cache."},origin:{"!type":"string","!url":"https://developer.mozilla.org/en/docs/DOM/window.location","!doc":"The origin of the URL."},hash:{"!type":"string","!url":"https://developer.mozilla.org/en/docs/DOM/window.location","!doc":"The part of the URL that follows the # symbol, including the # symbol."},search:{"!type":"string","!url":"https://developer.mozilla.org/en/docs/DOM/window.location","!doc":"The part of the URL that follows the ? symbol, including the ? symbol."},pathname:{"!type":"string","!url":"https://developer.mozilla.org/en/docs/DOM/window.location","!doc":"The path (relative to the host)."},port:{"!type":"string","!url":"https://developer.mozilla.org/en/docs/DOM/window.location","!doc":"The port number of the URL."},hostname:{"!type":"string","!url":"https://developer.mozilla.org/en/docs/DOM/window.location","!doc":"The host name (without the port number or square brackets)."},host:{"!type":"string","!url":"https://developer.mozilla.org/en/docs/DOM/window.location","!doc":"The host name and port number."},protocol:{"!type":"string","!url":"https://developer.mozilla.org/en/docs/DOM/window.location","!doc":"The protocol of the URL."},href:{"!type":"string","!url":"https://developer.mozilla.org/en/docs/DOM/window.location","!doc":"The entire URL."},"!url":"https://developer.mozilla.org/en/docs/DOM/window.location","!doc":"Returns a location object with information about the current location of the document. Assigning to the location property changes the current page to the new address."},Node:{"!type":"fn()",prototype:{parentElement:{"!type":"+Element","!url":"https://developer.mozilla.org/en/docs/DOM/Node.parentElement","!doc":"Returns the DOM node's parent Element, or null if the node either has no parent, or its parent isn't a DOM Element."},textContent:{"!type":"string","!url":"https://developer.mozilla.org/en/docs/DOM/Node.textContent","!doc":"Gets or sets the text content of a node and its descendants."},baseURI:{"!type":"string","!url":"https://developer.mozilla.org/en/docs/DOM/Node.baseURI","!doc":"The absolute base URI of a node or null if unable to obtain an absolute URI."},localName:{"!type":"string","!url":"https://developer.mozilla.org/en/docs/DOM/Node.localName","!doc":"Returns the local part of the qualified name of this node."},prefix:{"!type":"string","!url":"https://developer.mozilla.org/en/docs/DOM/Node.prefix","!doc":"Returns the namespace prefix of the specified node, or null if no prefix is specified. This property is read only."},namespaceURI:{"!type":"string","!url":"https://developer.mozilla.org/en/docs/DOM/Node.namespaceURI","!doc":"The namespace URI of the node, or null if the node is not in a namespace (read-only). When the node is a document, it returns the XML namespace for the current document."},ownerDocument:{"!type":"+Document","!url":"https://developer.mozilla.org/en/docs/DOM/Node.ownerDocument","!doc":"The ownerDocument property returns the top-level document object for this node."},attributes:{"!type":"+NamedNodeMap","!url":"https://developer.mozilla.org/en/docs/DOM/Node.attributes","!doc":"A collection of all attribute nodes registered to the specified node. It is a NamedNodeMap,not an Array, so it has no Array methods and the Attr nodes' indexes may differ among browsers."},nextSibling:{"!type":"+Element","!url":"https://developer.mozilla.org/en/docs/DOM/Node.nextSibling","!doc":"Returns the node immediately following the specified one in its parent's childNodes list, or null if the specified node is the last node in that list."},previousSibling:{"!type":"+Element","!url":"https://developer.mozilla.org/en/docs/DOM/Node.previousSibling","!doc":"Returns the node immediately preceding the specified one in its parent's childNodes list, null if the specified node is the first in that list."},lastChild:{"!type":"+Element","!url":"https://developer.mozilla.org/en/docs/DOM/Node.lastChild","!doc":"Returns the last child of a node."},firstChild:{"!type":"+Element","!url":"https://developer.mozilla.org/en/docs/DOM/Node.firstChild","!doc":"Returns the node's first child in the tree, or null if the node is childless. If the node is a Document, it returns the first node in the list of its direct children."},childNodes:{"!type":"+NodeList","!url":"https://developer.mozilla.org/en/docs/DOM/Node.childNodes","!doc":"Returns a collection of child nodes of the given element."},parentNode:{"!type":"+Element","!url":"https://developer.mozilla.org/en/docs/DOM/Node.parentNode","!doc":"Returns the parent of the specified node in the DOM tree."},nodeType:{"!type":"number","!url":"https://developer.mozilla.org/en/docs/DOM/Node.nodeType","!doc":"Returns an integer code representing the type of the node."},nodeValue:{"!type":"string","!url":"https://developer.mozilla.org/en/docs/DOM/Node.nodeValue","!doc":"Returns or sets the value of the current node."},nodeName:{"!type":"string","!url":"https://developer.mozilla.org/en/docs/DOM/Node.nodeName","!doc":"Returns the name of the current node as a string."},tagName:{"!type":"string","!url":"https://developer.mozilla.org/en/docs/DOM/Node.nodeName","!doc":"Returns the name of the current node as a string."},insertBefore:{"!type":"fn(newElt: +Element, before: +Element) -> +Element","!url":"https://developer.mozilla.org/en/docs/DOM/Node.insertBefore","!doc":"Inserts the specified node before a reference element as a child of the current node."},replaceChild:{"!type":"fn(newElt: +Element, oldElt: +Element) -> +Element","!url":"https://developer.mozilla.org/en/docs/DOM/Node.replaceChild","!doc":"Replaces one child node of the specified element with another."},removeChild:{"!type":"fn(oldElt: +Element) -> +Element","!url":"https://developer.mozilla.org/en/docs/DOM/Node.removeChild","!doc":"Removes a child node from the DOM. Returns removed node."},appendChild:{"!type":"fn(newElt: +Element) -> +Element","!url":"https://developer.mozilla.org/en/docs/DOM/Node.appendChild","!doc":"Adds a node to the end of the list of children of a specified parent node. If the node already exists it is removed from current parent node, then added to new parent node."},hasChildNodes:{"!type":"fn() -> bool","!url":"https://developer.mozilla.org/en/docs/DOM/Node.hasChildNodes","!doc":"Returns a Boolean value indicating whether the current Node has child nodes or not."},cloneNode:{"!type":"fn(deep: bool) -> +Element","!url":"https://developer.mozilla.org/en/docs/DOM/Node.cloneNode","!doc":"Returns a duplicate of the node on which this method was called."},normalize:{"!type":"fn()","!url":"https://developer.mozilla.org/en/docs/DOM/Node.normalize","!doc":'Puts the specified node and all of its subtree into a "normalized" form. In a normalized subtree, no text nodes in the subtree are empty and there are no adjacent text nodes.'},isSupported:{"!type":"fn(features: string, version: number) -> bool","!url":"https://developer.mozilla.org/en/docs/DOM/Node.isSupported","!doc":"Tests whether the DOM implementation implements a specific feature and that feature is supported by this node."},hasAttributes:{"!type":"fn() -> bool","!url":"https://developer.mozilla.org/en/docs/DOM/Node.hasAttributes","!doc":"Returns a boolean value of true or false, indicating if the current element has any attributes or not."},lookupPrefix:{"!type":"fn(uri: string) -> string","!url":"https://developer.mozilla.org/en/docs/DOM/Node.lookupPrefix","!doc":"Returns the prefix for a given namespaceURI if present, and null if not. When multiple prefixes are possible, the result is implementation-dependent."},isDefaultNamespace:{"!type":"fn(uri: string) -> bool","!url":"https://developer.mozilla.org/en/docs/DOM/Node.isDefaultNamespace","!doc":"Accepts a namespace URI as an argument and returns true if the namespace is the default namespace on the given node or false if not."},lookupNamespaceURI:{"!type":"fn(uri: string) -> string","!url":"https://developer.mozilla.org/en/docs/DOM/Node.lookupNamespaceURI","!doc":"Takes a prefix and returns the namespaceURI associated with it on the given node if found (and null if not). Supplying null for the prefix will return the default namespace."},addEventListener:{"!type":"fn(type: string, listener: fn(e: +Event), capture: bool)","!url":"https://developer.mozilla.org/en/docs/DOM/EventTarget.addEventListener","!doc":"Registers a single event listener on a single target. The event target may be a single element in a document, the document itself, a window, or an XMLHttpRequest."},removeEventListener:{"!type":"fn(type: string, listener: fn(), capture: bool)","!url":"https://developer.mozilla.org/en/docs/DOM/EventTarget.removeEventListener","!doc":"Allows the removal of event listeners from the event target."},isSameNode:{"!type":"fn(other: +Node) -> bool","!url":"https://developer.mozilla.org/en/docs/DOM/Node.isSameNode","!doc":"Tests whether two nodes are the same, that is they reference the same object."},isEqualNode:{"!type":"fn(other: +Node) -> bool","!url":"https://developer.mozilla.org/en/docs/DOM/Node.isEqualNode","!doc":"Tests whether two nodes are equal."},compareDocumentPosition:{"!type":"fn(other: +Node) -> number","!url":"https://developer.mozilla.org/en/docs/DOM/Node.compareDocumentPosition","!doc":"Compares the position of the current node against another node in any other document."},contains:{"!type":"fn(other: +Node) -> bool","!url":"https://developer.mozilla.org/en/docs/DOM/Node.contains","!doc":"Indicates whether a node is a descendent of a given node."},dispatchEvent:{"!type":"fn(event: +Event) -> bool","!url":"https://developer.mozilla.org/en/docs/DOM/EventTarget.dispatchEvent","!doc":"Dispatches an event into the event system. The event is subject to the same capturing and bubbling behavior as directly dispatched events."},ELEMENT_NODE:"number",ATTRIBUTE_NODE:"number",TEXT_NODE:"number",CDATA_SECTION_NODE:"number",ENTITY_REFERENCE_NODE:"number",ENTITY_NODE:"number",PROCESSING_INSTRUCTION_NODE:"number",COMMENT_NODE:"number",DOCUMENT_NODE:"number",DOCUMENT_TYPE_NODE:"number",DOCUMENT_FRAGMENT_NODE:"number",NOTATION_NODE:"number",DOCUMENT_POSITION_DISCONNECTED:"number",DOCUMENT_POSITION_PRECEDING:"number",DOCUMENT_POSITION_FOLLOWING:"number",DOCUMENT_POSITION_CONTAINS:"number",DOCUMENT_POSITION_CONTAINED_BY:"number",DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC:"number"},"!url":"https://developer.mozilla.org/en/docs/DOM/Node","!doc":"A Node is an interface from which a number of DOM types inherit, and allows these various types to be treated (or tested) similarly."},Element:{"!type":"fn()",prototype:{"!proto":"Node.prototype",getAttribute:{"!type":"fn(name: string) -> string","!url":"https://developer.mozilla.org/en/docs/DOM/element.getAttribute","!doc":'Returns the value of the named attribute on the specified element. If the named attribute does not exist, the value returned will either be null or "" (the empty string).'},setAttribute:{"!type":"fn(name: string, value: string)","!url":"https://developer.mozilla.org/en/docs/DOM/element.setAttribute","!doc":"Adds a new attribute or changes the value of an existing attribute on the specified element."},removeAttribute:{"!type":"fn(name: string)","!url":"https://developer.mozilla.org/en/docs/DOM/element.removeAttribute","!doc":"Removes an attribute from the specified element."},getAttributeNode:{"!type":"fn(name: string) -> +Attr","!url":"https://developer.mozilla.org/en/docs/DOM/element.getAttributeNode","!doc":"Returns the specified attribute of the specified element, as an Attr node."},getElementsByTagName:{"!type":"fn(tagName: string) -> +NodeList","!url":"https://developer.mozilla.org/en/docs/DOM/element.getElementsByTagName","!doc":"Returns a list of elements with the given tag name. The subtree underneath the specified element is searched, excluding the element itself. The returned list is live, meaning that it updates itself with the DOM tree automatically. Consequently, there is no need to call several times element.getElementsByTagName with the same element and arguments."},getElementsByTagNameNS:{"!type":"fn(ns: string, tagName: string) -> +NodeList","!url":"https://developer.mozilla.org/en/docs/DOM/element.getElementsByTagNameNS","!doc":"Returns a list of elements with the given tag name belonging to the given namespace."},getAttributeNS:{"!type":"fn(ns: string, name: string) -> string","!url":"https://developer.mozilla.org/en/docs/DOM/element.getAttributeNS","!doc":'Returns the string value of the attribute with the specified namespace and name. If the named attribute does not exist, the value returned will either be null or "" (the empty string).'},setAttributeNS:{"!type":"fn(ns: string, name: string, value: string)","!url":"https://developer.mozilla.org/en/docs/DOM/element.setAttributeNS","!doc":"Adds a new attribute or changes the value of an attribute with the given namespace and name."},removeAttributeNS:{"!type":"fn(ns: string, name: string)","!url":"https://developer.mozilla.org/en/docs/DOM/element.removeAttributeNS","!doc":"removeAttributeNS removes the specified attribute from an element."},getAttributeNodeNS:{"!type":"fn(ns: string, name: string) -> +Attr","!url":"https://developer.mozilla.org/en/docs/DOM/element.getAttributeNodeNS","!doc":"Returns the Attr node for the attribute with the given namespace and name."},hasAttribute:{"!type":"fn(name: string) -> bool","!url":"https://developer.mozilla.org/en/docs/DOM/element.hasAttribute","!doc":"hasAttribute returns a boolean value indicating whether the specified element has the specified attribute or not."},hasAttributeNS:{"!type":"fn(ns: string, name: string) -> bool","!url":"https://developer.mozilla.org/en/docs/DOM/element.hasAttributeNS","!doc":"hasAttributeNS returns a boolean value indicating whether the current element has the specified attribute."},focus:{"!type":"fn()","!url":"https://developer.mozilla.org/en/docs/DOM/element.focus","!doc":"Sets focus on the specified element, if it can be focused."},blur:{"!type":"fn()","!url":"https://developer.mozilla.org/en/docs/DOM/element.blur","!doc":"The blur method removes keyboard focus from the current element."},scrollIntoView:{"!type":"fn(top: bool)","!url":"https://developer.mozilla.org/en/docs/DOM/element.scrollIntoView","!doc":"The scrollIntoView() method scrolls the element into view."},scrollByLines:{"!type":"fn(lines: number)","!url":"https://developer.mozilla.org/en/docs/DOM/window.scrollByLines","!doc":"Scrolls the document by the given number of lines."},scrollByPages:{"!type":"fn(pages: number)","!url":"https://developer.mozilla.org/en/docs/DOM/window.scrollByPages","!doc":"Scrolls the current document by the specified number of pages."},getElementsByClassName:{"!type":"fn(name: string) -> +NodeList","!url":"https://developer.mozilla.org/en/docs/DOM/document.getElementsByClassName","!doc":"Returns a set of elements which have all the given class names. When called on the document object, the complete document is searched, including the root node. You may also call getElementsByClassName on any element; it will return only elements which are descendants of the specified root element with the given class names."},querySelector:{"!type":"fn(selectors: string) -> +Node","!url":"https://developer.mozilla.org/en/docs/DOM/Element.querySelector","!doc":"Returns the first element that is a descendent of the element on which it is invoked that matches the specified group of selectors."},querySelectorAll:{"!type":"fn(selectors: string) -> +NodeList","!url":"https://developer.mozilla.org/en/docs/DOM/Element.querySelectorAll","!doc":"Returns a non-live NodeList of all elements descended from the element on which it is invoked that match the specified group of CSS selectors."},getClientRects:{"!type":"fn() -> [+ClientRect]","!url":"https://developer.mozilla.org/en/docs/DOM/element.getClientRects","!doc":"Returns a collection of rectangles that indicate the bounding rectangles for each box in a client."},getBoundingClientRect:{"!type":"fn() -> +ClientRect","!url":"https://developer.mozilla.org/en/docs/DOM/element.getBoundingClientRect","!doc":"Returns a text rectangle object that encloses a group of text rectangles."},setAttributeNode:{"!type":"fn(attr: +Attr) -> +Attr","!url":"https://developer.mozilla.org/en/docs/DOM/element.setAttributeNode","!doc":"Adds a new Attr node to the specified element."},removeAttributeNode:{"!type":"fn(attr: +Attr) -> +Attr","!url":"https://developer.mozilla.org/en/docs/DOM/element.removeAttributeNode","!doc":"Removes the specified attribute from the current element."},setAttributeNodeNS:{"!type":"fn(attr: +Attr) -> +Attr","!url":"https://developer.mozilla.org/en/docs/DOM/element.setAttributeNodeNS","!doc":"Adds a new namespaced attribute node to an element."},insertAdjacentHTML:{"!type":"fn(position: string, text: string)","!url":"https://developer.mozilla.org/en/docs/DOM/element.insertAdjacentHTML","!doc":"Parses the specified text as HTML or XML and inserts the resulting nodes into the DOM tree at a specified position. It does not reparse the element it is being used on and thus it does not corrupt the existing elements inside the element. This, and avoiding the extra step of serialization make it much faster than direct innerHTML manipulation."},children:{"!type":"+HTMLCollection","!url":"https://developer.mozilla.org/en/docs/DOM/Element.children","!doc":"Returns a collection of child elements of the given element."},childElementCount:{"!type":"number","!url":"https://developer.mozilla.org/en/docs/DOM/Element.childElementCount","!doc":"Returns the number of child elements of the given element."},className:{"!type":"string","!url":"https://developer.mozilla.org/en/docs/DOM/element.className","!doc":"Gets and sets the value of the class attribute of the specified element."},style:{cssText:"string",alignmentBaseline:"string",background:"string",backgroundAttachment:"string",backgroundClip:"string",backgroundColor:"string",backgroundImage:"string",backgroundOrigin:"string",backgroundPosition:"string",backgroundPositionX:"string",backgroundPositionY:"string",backgroundRepeat:"string",backgroundRepeatX:"string",backgroundRepeatY:"string",backgroundSize:"string",baselineShift:"string",border:"string",borderBottom:"string",borderBottomColor:"string",borderBottomLeftRadius:"string",borderBottomRightRadius:"string",borderBottomStyle:"string",borderBottomWidth:"string",borderCollapse:"string",borderColor:"string",borderImage:"string",borderImageOutset:"string",borderImageRepeat:"string",borderImageSlice:"string",borderImageSource:"string",borderImageWidth:"string",borderLeft:"string",borderLeftColor:"string",borderLeftStyle:"string",borderLeftWidth:"string",borderRadius:"string",borderRight:"string",borderRightColor:"string",borderRightStyle:"string",borderRightWidth:"string",borderSpacing:"string",borderStyle:"string",borderTop:"string",borderTopColor:"string",borderTopLeftRadius:"string",borderTopRightRadius:"string",borderTopStyle:"string",borderTopWidth:"string",borderWidth:"string",bottom:"string",boxShadow:"string",boxSizing:"string",captionSide:"string",clear:"string",clip:"string",clipPath:"string",clipRule:"string",color:"string",colorInterpolation:"string",colorInterpolationFilters:"string",colorProfile:"string",colorRendering:"string",content:"string",counterIncrement:"string",counterReset:"string",cursor:"string",direction:"string",display:"string",dominantBaseline:"string",emptyCells:"string",enableBackground:"string",fill:"string",fillOpacity:"string",fillRule:"string",filter:"string","float":"string",floodColor:"string",floodOpacity:"string",font:"string",fontFamily:"string",fontSize:"string",fontStretch:"string",fontStyle:"string",fontVariant:"string",fontWeight:"string",glyphOrientationHorizontal:"string",glyphOrientationVertical:"string",height:"string",imageRendering:"string",kerning:"string",left:"string",letterSpacing:"string",lightingColor:"string",lineHeight:"string",listStyle:"string",listStyleImage:"string",listStylePosition:"string",listStyleType:"string",margin:"string",marginBottom:"string",marginLeft:"string",marginRight:"string",marginTop:"string",marker:"string",markerEnd:"string",markerMid:"string",markerStart:"string",mask:"string",maxHeight:"string",maxWidth:"string",minHeight:"string",minWidth:"string",opacity:"string",orphans:"string",outline:"string",outlineColor:"string",outlineOffset:"string",outlineStyle:"string",outlineWidth:"string",overflow:"string",overflowWrap:"string",overflowX:"string",overflowY:"string",padding:"string",paddingBottom:"string",paddingLeft:"string",paddingRight:"string",paddingTop:"string",page:"string",pageBreakAfter:"string",pageBreakBefore:"string",pageBreakInside:"string",pointerEvents:"string",position:"string",quotes:"string",resize:"string",right:"string",shapeRendering:"string",size:"string",speak:"string",src:"string",stopColor:"string",stopOpacity:"string",stroke:"string",strokeDasharray:"string",strokeDashoffset:"string",strokeLinecap:"string",strokeLinejoin:"string",strokeMiterlimit:"string",strokeOpacity:"string",strokeWidth:"string",tabSize:"string",tableLayout:"string",textAlign:"string",textAnchor:"string",textDecoration:"string",textIndent:"string",textLineThrough:"string",textLineThroughColor:"string",textLineThroughMode:"string",textLineThroughStyle:"string",textLineThroughWidth:"string",textOverflow:"string",textOverline:"string",textOverlineColor:"string",textOverlineMode:"string",textOverlineStyle:"string",textOverlineWidth:"string",textRendering:"string",textShadow:"string",textTransform:"string",textUnderline:"string",textUnderlineColor:"string",textUnderlineMode:"string",textUnderlineStyle:"string",textUnderlineWidth:"string",top:"string",unicodeBidi:"string",unicodeRange:"string",vectorEffect:"string",verticalAlign:"string",visibility:"string",whiteSpace:"string",width:"string",wordBreak:"string",wordSpacing:"string",wordWrap:"string",writingMode:"string",zIndex:"string",zoom:"string","!url":"https://developer.mozilla.org/en/docs/DOM/element.style","!doc":"Returns an object that represents the element's style attribute."},classList:{"!type":"+DOMTokenList","!url":"https://developer.mozilla.org/en/docs/DOM/element.classList","!doc":"Returns a token list of the class attribute of the element."},contentEditable:{"!type":"bool","!url":"https://developer.mozilla.org/en/docs/DOM/Element.contentEditable","!doc":"Indicates whether or not the element is editable."},firstElementChild:{"!type":"+Element","!url":"https://developer.mozilla.org/en/docs/DOM/Element.firstElementChild","!doc":"Returns the element's first child element or null if there are no child elements."},lastElementChild:{"!type":"+Element","!url":"https://developer.mozilla.org/en/docs/DOM/Element.lastElementChild","!doc":"Returns the element's last child element or null if there are no child elements."},nextElementSibling:{"!type":"+Element","!url":"https://developer.mozilla.org/en/docs/DOM/Element.nextElementSibling","!doc":"Returns the element immediately following the specified one in its parent's children list, or null if the specified element is the last one in the list."},previousElementSibling:{"!type":"+Element","!url":"https://developer.mozilla.org/en/docs/DOM/Element.previousElementSibling","!doc":"Returns the element immediately prior to the specified one in its parent's children list, or null if the specified element is the first one in the list."},tabIndex:{"!type":"number","!url":"https://developer.mozilla.org/en/docs/DOM/element.tabIndex","!doc":"Gets/sets the tab order of the current element."},title:{"!type":"string","!url":"https://developer.mozilla.org/en/docs/DOM/element.title","!doc":"Establishes the text to be displayed in a 'tool tip' popup when the mouse is over the displayed node."},width:{"!type":"number","!url":"https://developer.mozilla.org/en/docs/DOM/element.offsetWidth","!doc":"Returns the layout width of an element."},height:{"!type":"number","!url":"https://developer.mozilla.org/en/docs/DOM/element.offsetHeight","!doc":"Height of an element relative to the element's offsetParent."},getContext:{"!type":"fn(id: string) -> CanvasRenderingContext2D","!url":"https://developer.mozilla.org/en/docs/DOM/HTMLCanvasElement","!doc":"DOM canvas elements expose the HTMLCanvasElement interface, which provides properties and methods for manipulating the layout and presentation of canvas elements. The HTMLCanvasElement interface inherits the properties and methods of the element object interface."},supportsContext:"fn(id: string) -> bool",oncopy:{"!type":"?","!url":"https://developer.mozilla.org/en/docs/DOM/element.oncopy","!doc":"The oncopy property returns the onCopy event handler code on the current element."},oncut:{"!type":"?","!url":"https://developer.mozilla.org/en/docs/DOM/element.oncut","!doc":"The oncut property returns the onCut event handler code on the current element."},onpaste:{"!type":"?","!url":"https://developer.mozilla.org/en/docs/DOM/element.onpaste","!doc":"The onpaste property returns the onPaste event handler code on the current element."},onbeforeunload:{"!type":"?","!url":"https://developer.mozilla.org/en/docs/HTML/Element/body","!doc":"The HTML element represents the main content of an HTML document. There is only one element in a document."},onfocus:{"!type":"?","!url":"https://developer.mozilla.org/en/docs/DOM/element.onfocus","!doc":"The onfocus property returns the onFocus event handler code on the current element."},onblur:{"!type":"?","!url":"https://developer.mozilla.org/en/docs/DOM/element.onblur","!doc":"The onblur property returns the onBlur event handler code, if any, that exists on the current element."},onchange:{"!type":"?","!url":"https://developer.mozilla.org/en/docs/DOM/element.onchange","!doc":"The onchange property sets and returns the onChange event handler code for the current element."},onclick:{"!type":"?","!url":"https://developer.mozilla.org/en/docs/DOM/element.onclick","!doc":"The onclick property returns the onClick event handler code on the current element."},ondblclick:{"!type":"?","!url":"https://developer.mozilla.org/en/docs/DOM/element.ondblclick","!doc":"The ondblclick property returns the onDblClick event handler code on the current element."},onmousedown:{"!type":"?","!url":"https://developer.mozilla.org/en/docs/DOM/element.onmousedown","!doc":"The onmousedown property returns the onMouseDown event handler code on the current element."},onmouseup:{"!type":"?","!url":"https://developer.mozilla.org/en/docs/DOM/element.onmouseup","!doc":"The onmouseup property returns the onMouseUp event handler code on the current element."},onmousewheel:{"!type":"?","!url":"https://developer.mozilla.org/en/docs/DOM/Mozilla_event_reference/wheel","!doc":"The wheel event is fired when a wheel button of a pointing device (usually a mouse) is rotated. This event deprecates the legacy mousewheel event."},onmouseover:{"!type":"?","!url":"https://developer.mozilla.org/en/docs/DOM/element.onmouseover","!doc":"The onmouseover property returns the onMouseOver event handler code on the current element."},onmouseout:{"!type":"?","!url":"https://developer.mozilla.org/en/docs/DOM/element.onmouseout","!doc":"The onmouseout property returns the onMouseOut event handler code on the current element."},onmousemove:{"!type":"?","!url":"https://developer.mozilla.org/en/docs/DOM/element.onmousemove","!doc":"The onmousemove property returns the mousemove event handler code on the current element."},oncontextmenu:{"!type":"?","!url":"https://developer.mozilla.org/en/docs/DOM/window.oncontextmenu","!doc":'An event handler property for right-click events on the window. Unless the default behavior is prevented, the browser context menu will activate. Note that this event will occur with any non-disabled right-click event and does not depend on an element possessing the "contextmenu" attribute.'},onkeydown:{"!type":"?","!url":"https://developer.mozilla.org/en/docs/DOM/element.onkeydown","!doc":"The onkeydown property returns the onKeyDown event handler code on the current element."},onkeyup:{"!type":"?","!url":"https://developer.mozilla.org/en/docs/DOM/element.onkeyup","!doc":"The onkeyup property returns the onKeyUp event handler code for the current element."},onkeypress:{"!type":"?","!url":"https://developer.mozilla.org/en/docs/DOM/element.onkeypress","!doc":"The onkeypress property sets and returns the onKeyPress event handler code for the current element."},onresize:{"!type":"?","!url":"https://developer.mozilla.org/en/docs/DOM/element.onresize","!doc":"onresize returns the element's onresize event handler code. It can also be used to set the code to be executed when the resize event occurs."},onscroll:{"!type":"?","!url":"https://developer.mozilla.org/en/docs/DOM/element.onscroll","!doc":"The onscroll property returns the onScroll event handler code on the current element."},ondragstart:{"!type":"?","!url":"https://developer.mozilla.org/en/docs/DragDrop/Drag_Operations","!doc":"The following describes the steps that occur during a drag and drop operation."},ondragover:{"!type":"?","!url":"https://developer.mozilla.org/en/docs/DOM/Mozilla_event_reference/dragover","!doc":"The dragover event is fired when an element or text selection is being dragged over a valid drop target (every few hundred milliseconds)."},ondragleave:{"!type":"?","!url":"https://developer.mozilla.org/en/docs/DOM/Mozilla_event_reference/dragleave","!doc":"The dragleave event is fired when a dragged element or text selection leaves a valid drop target."},ondragenter:{"!type":"?","!url":"https://developer.mozilla.org/en/docs/DOM/Mozilla_event_reference/dragenter","!doc":"The dragenter event is fired when a dragged element or text selection enters a valid drop target."},ondragend:{"!type":"?","!url":"https://developer.mozilla.org/en/docs/DOM/Mozilla_event_reference/dragend","!doc":"The dragend event is fired when a drag operation is being ended (by releasing a mouse button or hitting the escape key)."},ondrag:{"!type":"?","!url":"https://developer.mozilla.org/en/docs/DOM/Mozilla_event_reference/drag","!doc":"The drag event is fired when an element or text selection is being dragged (every few hundred milliseconds)."},offsetTop:{"!type":"number","!url":"https://developer.mozilla.org/en/docs/DOM/element.offsetTop","!doc":"Returns the distance of the current element relative to the top of the offsetParent node."},offsetLeft:{"!type":"number","!url":"https://developer.mozilla.org/en/docs/DOM/element.offsetLeft","!doc":"Returns the number of pixels that the upper left corner of the current element is offset to the left within the offsetParent node."},offsetHeight:{"!type":"number","!url":"https://developer.mozilla.org/en/docs/DOM/element.offsetHeight","!doc":"Height of an element relative to the element's offsetParent."},offsetWidth:{"!type":"number","!url":"https://developer.mozilla.org/en/docs/DOM/element.offsetWidth","!doc":"Returns the layout width of an element."},scrollTop:{"!type":"number","!url":"https://developer.mozilla.org/en/docs/DOM/element.scrollTop","!doc":"Gets or sets the number of pixels that the content of an element is scrolled upward."},scrollLeft:{"!type":"number","!url":"https://developer.mozilla.org/en/docs/DOM/element.scrollLeft","!doc":"Gets or sets the number of pixels that an element's content is scrolled to the left."},scrollHeight:{"!type":"number","!url":"https://developer.mozilla.org/en/docs/DOM/element.scrollHeight","!doc":"Height of the scroll view of an element; it includes the element padding but not its margin."},scrollWidth:{"!type":"number","!url":"https://developer.mozilla.org/en/docs/DOM/element.scrollWidth","!doc":"Read-only property that returns either the width in pixels of the content of an element or the width of the element itself, whichever is greater."},clientTop:{"!type":"number","!url":"https://developer.mozilla.org/en/docs/DOM/element.clientTop","!doc":"The width of the top border of an element in pixels. It does not include the top margin or padding. clientTop is read-only."},clientLeft:{"!type":"number","!url":"https://developer.mozilla.org/en/docs/DOM/element.clientLeft","!doc":"The width of the left border of an element in pixels. It includes the width of the vertical scrollbar if the text direction of the element is right-to-left and if there is an overflow causing a left vertical scrollbar to be rendered. clientLeft does not include the left margin or the left padding. clientLeft is read-only."},clientHeight:{"!type":"number","!url":"https://developer.mozilla.org/en/docs/DOM/element.clientHeight","!doc":"Returns the inner height of an element in pixels, including padding but not the horizontal scrollbar height, border, or margin."},clientWidth:{"!type":"number","!url":"https://developer.mozilla.org/en/docs/DOM/element.clientWidth","!doc":"The inner width of an element in pixels. It includes padding but not the vertical scrollbar (if present, if rendered), border or margin."},innerHTML:{"!type":"string","!url":"https://developer.mozilla.org/en/docs/DOM/element.innerHTML","!doc":"Sets or gets the HTML syntax describing the element's descendants."},createdCallback:{"!type":"fn()","!url":"http://w3c.github.io/webcomponents/spec/custom/index.html#dfn-created-callback","!doc":"This callback is invoked after custom element instance is created and its definition is registered. The actual timing of this callback is defined further in this specification."},attachedCallback:{"!type":"fn()","!url":"http://w3c.github.io/webcomponents/spec/custom/index.html#dfn-entered-view-callback","!doc":"Unless specified otherwise, this callback must be enqueued whenever custom element is inserted into a document and this document has a browsing context."},detachedCallback:{"!type":"fn()","!url":"http://w3c.github.io/webcomponents/spec/custom/index.html#dfn-left-view-callback","!doc":"Unless specified otherwise, this callback must be enqueued whenever custom element is removed from the document and this document has a browsing context."},attributeChangedCallback:{"!type":"fn()","!url":"http://w3c.github.io/webcomponents/spec/custom/index.html#dfn-attribute-changed-callback","!doc":"Unless specified otherwise, this callback must be enqueued whenever custom element's attribute is added, changed or removed."}},"!url":"https://developer.mozilla.org/en/docs/DOM/Element","!doc":"Represents an element in an HTML or XML document."},Text:{"!type":"fn()",prototype:{"!proto":"Node.prototype",wholeText:{"!type":"string","!url":"https://developer.mozilla.org/en/docs/DOM/Text.wholeText","!doc":"Returns all text of all Text nodes logically adjacent to the node. The text is concatenated in document order. This allows you to specify any text node and obtain all adjacent text as a single string."},splitText:{"!type":"fn(offset: number) -> +Text","!url":"https://developer.mozilla.org/en/docs/DOM/Text.splitText","!doc":"Breaks the Text node into two nodes at the specified offset, keeping both nodes in the tree as siblings."}},"!url":"https://developer.mozilla.org/en/docs/DOM/Text","!doc":"In the DOM, the Text interface represents the textual content of an Element or Attr. If an element has no markup within its content, it has a single child implementing Text that contains the element's text. However, if the element contains markup, it is parsed into information items and Text nodes that form its children."},Document:{"!type":"fn()",prototype:{"!proto":"Node.prototype",activeElement:{"!type":"+Element","!url":"https://developer.mozilla.org/en/docs/DOM/document.activeElement","!doc":"Returns the currently focused element, that is, the element that will get keystroke events if the user types any. This attribute is read only."},compatMode:{"!type":"string","!url":"https://developer.mozilla.org/en/docs/DOM/document.compatMode","!doc":"Indicates whether the document is rendered in Quirks mode or Strict mode."},designMode:{"!type":"string","!url":"https://developer.mozilla.org/en/docs/DOM/document.designMode","!doc":"Can be used to make any document editable, for example in a