Merge branch 'master' into feature/url-revisionless

Conflicts:
	lib/features.js
	lib/models/bin.js
	public/css/style.css
	public/js/chrome/save.js
	public/js/editors/editors.js
	public/js/editors/keycontrol.js
	public/js/jsbin.js
	scripts.json
	views/index.html
This commit is contained in:
Remy Sharp 2014-07-13 17:00:48 +01:00
commit 56222dd394
105 changed files with 273056 additions and 14497 deletions

2
.gitignore vendored
View File

@ -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

View File

@ -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: {

View File

@ -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?

View File

@ -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
-- Dump completed on 2014-06-05 7:45:41

View File

@ -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",

View File

@ -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);
});
});

View File

@ -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 = {

View File

@ -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);

View File

@ -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;
};

7
lib/data/backers.json Normal file
View File

@ -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"
}
]

50
lib/data/features.json Normal file
View File

@ -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"
}
]

View File

@ -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": "Were expecting to hit our 10 __millionth__ bin in around a weeks time. Wow.",
"link": "https://twitter.com/js_bin/status/479668198384865281"
}
]
},
"quote": "&ldquo;Everyone should learn how to program a computer because it teaches you how to think&rdquo; &mdash; <span class=\"toppanel-quote-author\">Steve Jobs</span>"
}

View File

@ -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,

View File

@ -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);

View File

@ -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 += ' <a href="/register">Get a free unrestricted account</a>';
} else {
msg += ' <a href="/clone">Clone it create to enable the full preview</a>';
}
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('<script src="' + helpers.urlForStatic('js/render/edit.js?' + helpers.set('version')) + '"></script>');
var data = {static: helpers.urlForStatic('', req.secure), root: helpers.url('/', true, req.secure), csrf: options.csrf};
insert.push('<script src="' + helpers.urlForStatic('js/render/edit.js?' + helpers.set('version'), req.secure) + '"></script>');
insert.push('<script>jsbinShowEdit(' + JSON.stringify(data) + ');</script>');
}
@ -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 '<script src="' + script + '"></script>';
}));
}
@ -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(/<html[^>]*>/, function ($0) {
var done = false;
formatted = formatted.replace(/<meta.*?charset.*?[^>]*>/, function ($0) {
if ($0) {
done = true;
}
return $0 + '\n' + (comment || '').trim();
});
if (!done) {
formatted = formatted.replace(/<html[^>]*>/, 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.'});
}
}
}

View File

@ -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. <a target="_blank" href="http://jsbin.com/help/github-username">Read this for more help</a>.');
return res.redirect(redirect);
}
// No matching user was found, create an account!

View File

@ -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;

View File

@ -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;
}

View File

@ -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);
};
});

View File

@ -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);

View File

@ -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);
}
});

View File

@ -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');

View File

@ -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);
};

View File

@ -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,

48
lib/welcome-panel.js Normal file
View File

@ -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;
}
};

View File

@ -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",

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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)";
}

View File

@ -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,<svg xmlns=\'http://www.w3.org/2000/svg\'><filter id=\'grayscale\'><feColorMatrix type=\'matrix\' values=\'0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0 0 0 1 0\'/></filter></svg>#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;
}

104
public/css/upgrade.css Normal file
View File

@ -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";
}*/

View File

@ -6,7 +6,7 @@
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/normalize/2.1.0/normalize.css">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script src="http://builds.handlebarsjs.com.s3.amazonaws.com/handlebars-v1.3.0.js"></script>
<script src="http://builds.emberjs.com/tags/v1.5.1/ember.js"></script>
<script src="http://builds.emberjs.com/tags/v1.6.0/ember.js"></script>
</head>
<body>

43
public/images/dave.svg Normal file
View File

@ -0,0 +1,43 @@
<svg width="400" height="400" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<g>
<title>Layer 1</title>
<g id="svg_1" transform="translate(72, 20)">
<path id="svg_2" d="m8.787,159.65099l0,147.51601c0,28.61899 53.37401,51.82199 119.21401,51.82199c65.841,0 119.214,-23.203 119.214,-51.82199l0,-147.51601c0,0 -4.95901,50.373 -119.214,50.373c-114.25701,0.00101 -119.21401,-50.373 -119.21401,-50.373z" fill="#9EAAB2"/>
<ellipse id="svg_3" ry="51.818" rx="119.214" cy="159.651" cx="128.001" fill="#8E979A"/>
<path id="svg_4" d="m236.838,156.95399c0,4.564 -1.96799,8.67799 -5.599,12.77899c-7.185,8.11801 -54.739,26.93901 -102.5,26.97902c-48.25101,0.04199 -96.71501,-18.77901 -103.97701,-26.97902c-3.63799,-4.10399 -5.598,-8.209 -5.598,-12.77899c0,-22.71901 48.729,-41.13599 108.83701,-41.13599c60.10802,0.00099 108.83701,18.41698 108.83701,41.13599z" fill="#1A2B2F"/>
<g id="svg_5" class="hide">
<path id="svg_6" d="m24.761,107.832c17.174,11.981 60.859,23.16399 103.23899,23.16399c42.38001,0 88.46203,-11.58199 103.23803,-23.16399l0,-7.18901c0,0 -32.38202,20.72 -103.23803,20.72c-70.85799,0 -103.23899,-20.72 -103.23899,-20.72l0,7.18901z" fill="#1B3134"/>
<path id="svg_7" d="m128.00101,119.25401c-68.99001,0 -125.22301,-21.459 -127.89101,-48.32401c-0.066,0.662 -0.109,1.328 -0.109,1.996c0,27.793 57.308,50.321 128.00001,50.321c70.692,0 127.99997,-22.528 127.99997,-50.321c0,-0.668 -0.04298,-1.334 -0.10997,-1.996c-2.66699,26.86501 -58.89999,48.32401 -127.89,48.32401z" fill="#8E979A"/>
<path id="svg_8" d="m231.239,165.562c0,0 -20.98901,29.55299 -103.23801,29.55299c-82.251,0 -103.23901,-29.55299 -103.23901,-29.55299l0,-64.16699c0,0 29.719,20.23601 100.80901,20.23601c71.08901,0 105.66901,-20.23601 105.66901,-20.23601l0,64.16699l-0.00101,0z" fill="#213F48"/>
<path id="svg_9" d="m128.00101,0c-70.69201,0 -128.00001,22.53 -128.00001,50.322c0,7.642 0,14.82301 0,21.96601c0,27.78999 57.308,50.31999 128.00001,50.31999c70.692,0 127.99997,-22.53 127.99997,-50.31999c0,-7.87601 0,-14.37901 0,-21.96601c0,-27.792 -57.30797,-50.322 -127.99997,-50.322z" fill="#9EAAB2"/>
<path id="svg_10" d="m127.738,16.975c18.91702,0 36.344,2.599 49.566,6.879l18.944,-6.879l4.58701,19.51801l-57.217,0l-0.729,-0.148l20.342,-7.383c-9.35699,-3.127 -21.84,-5.039 -35.44001,-5.039c-10.89999,0 -21.11299,1.23801 -29.55699,3.355l-18.79401,-3.816c13.183,-4.106 30.194,-6.487 48.298,-6.487zm-73.982,17.971c0.635,-2.843 7.603,-5.11 15.575,-5.11c7.97,0 14.132,2.268 13.758,5.11c-0.38,2.894 -7.37,5.278 -15.604,5.278c-8.233,0.001 -14.376,-2.384 -13.729,-5.278zm74.3,36.05701c-22.30102,0 -42.05502,-3.54601 -55.67001,-9.10801l-25.09,9.10801l5.203,-23.77501l59.869,0l0.272,0.05901l-23.11501,8.389c9.561,3.757 23.19501,6.13499 38.459,6.13499c12.54401,0 23.90801,-1.591 32.80102,-4.22l21.90099,4.69c-13.29999,5.27001 -32.50098,8.72201 -54.62999,8.72201zm62.16101,-16.366c-8.599,0 -15.94701,-2.602 -16.405,-5.753c-0.448,-3.096 5.933,-5.562 14.241,-5.562c8.312,0 15.63701,2.466 16.37001,5.562c0.74698,3.151 -5.60802,5.753 -14.20601,5.753z" fill="#DBE1E3" clip-rule="evenodd" fill-rule="evenodd"/>
<path id="svg_11" d="m196.15601,155.806c0,9.787 -7.16,17.72301 -16.979,17.72301c-9.81799,0 -16.978,-7.936 -16.978,-17.72301c0,-9.789 7.16,-17.722 16.978,-17.722c9.819,0 16.979,7.933 16.979,17.722z" fill="#1B3134" clip-rule="evenodd" fill-rule="evenodd"/>
<circle id="svg_12" r="15.597" cy="152.28" cx="179.178" fill="#51C3F1" clip-rule="evenodd" fill-rule="evenodd"/>
<g id="svg_13">
<path id="svg_14" d="m93.398,155.806c0,9.787 -7.16,17.72301 -16.979,17.72301c-9.817,0 -16.979,-7.936 -16.979,-17.72301c0,-9.789 7.161,-17.722 16.979,-17.722c9.819,0 16.979,7.933 16.979,17.722z" fill="#1B3134" clip-rule="evenodd" fill-rule="evenodd"/>
<circle id="svg_15" r="15.596" cy="152.28" cx="76.575" fill="#51C3F1" clip-rule="evenodd" fill-rule="evenodd"/>
</g>
<path id="svg_16" d="m216.86101,108.50201l0,-24.414c0,0 34.646,-10.649 39.13997,-33.766c0,7.642 0,14.82301 0,21.96601c0,14.22098 -15.01097,27.06299 -39.13997,36.21399z" fill="#8E979A"/>
<path id="svg_17" d="m39.139,108.50201l0,-24.681c-27.257,-8.519 -37.908,-23.96301 -39.139,-33.499c0,7.642 0,14.82301 0,21.96601c0.001,14.22098 15.011,27.06299 39.139,36.21399z" fill="#8E979A"/>
<path id="svg_18" d="m128.00101,96.65001c-68.99001,0 -125.22301,-21.459 -127.89101,-48.32401c-0.066,0.663 -0.109,1.327 -0.109,1.997c0,27.79102 57.308,50.32001 128.00001,50.32001c70.692,0 127.99997,-22.52899 127.99997,-50.32001c0,-0.67 -0.04298,-1.334 -0.10997,-1.997c-2.66699,26.86501 -58.89999,48.32401 -127.89,48.32401z" fill="#DBE1E3"/>
<circle id="svg_19" r="5.088" cy="149.872" cx="179.177" fill="#FFFFFF" clip-rule="evenodd" fill-rule="evenodd"/>
<circle id="svg_20" r="5.088" cy="149.872" cx="76.42" fill="#FFFFFF" clip-rule="evenodd" fill-rule="evenodd"/>
</g>
<path id="svg_21" d="m8.787,159.65099l0,147.51601c0,28.61899 53.37401,51.82199 119.21401,51.82199c65.841,0 119.214,-23.203 119.214,-51.82199l0,-147.51601c0,0 -4.95901,50.373 -119.214,50.373c-114.25701,0.00101 -119.21401,-50.373 -119.21401,-50.373z" fill="#9EAAB2"/>
<path id="svg_22" d="m127.96101,191.95599c-51.834,-0.008 -94.681,-15.004 -103.199,-32.30501l0,10.082c0,0 20.977,28.338 103.158,28.35802l0,0c0.01601,0 0.028,0 0.041,0c0.01401,0 0.02501,0 0.04002,0l0,0c82.18399,-0.02002 103.23801,-28.35802 103.23801,-28.35802l0,-10.082c-8.51804,17.30101 -51.44402,32.29701 -103.27802,32.30501z" fill="#1B3134"/>
<path id="svg_23" d="m231.27101,184.228c3.36398,-2.575 6.64499,-5.15201 9.12399,-8.63901c1.423,-2.006 2.595,-4.14099 3.69601,-6.31599c1.21899,-2.119 2.26498,-4.311 3.12299,-6.57001l0,144.46501c0,13.26999 -11.48001,25.375 -30.353,34.54199l0,-148.78c5.09,-2.39801 9.94699,-5.28799 14.41,-8.702z" fill="#8E979A"/>
<path id="svg_24" d="m24.731,184.228c-3.366,-2.575 -6.645,-5.15201 -9.124,-8.63901c-1.426,-2.006 -2.597,-4.14099 -3.69701,-6.31599c-1.219,-2.119 -2.264,-4.311 -3.123,-6.57001l0,144.46501c0,13.26999 11.48,25.375 30.352,34.54199l0,-148.78c-5.088,-2.39801 -9.946,-5.28799 -14.408,-8.702z" fill="#8E979A"/>
<path id="svg_25" d="m128.00101,355.116c-64.21601,0 -116.55601,-21.45898 -119.03801,-48.32401c-0.062,0.66199 -0.103,1.328 -0.103,1.99399c0,27.793 53.341,50.32202 119.14101,50.32202c65.8,0 119.14101,-22.52902 119.14101,-50.32202c0,-0.66599 -0.03799,-1.332 -0.103,-1.99399c-2.483,26.86499 -54.82201,48.32401 -119.03801,48.32401z" fill="#8E979A"/>
<path id="svg_26" d="m128.00101,207.974c-64.21601,0 -116.55601,-21.457 -119.03801,-48.32199c-0.062,0.66199 -0.103,1.32599 -0.103,1.99599c0,27.79201 53.341,50.32002 119.14101,50.32002c65.8,0 119.14101,-22.52802 119.14101,-50.32002c0,-0.67 -0.03799,-1.334 -0.103,-1.99599c-2.483,26.86499 -54.82201,48.32199 -119.03801,48.32199z" fill="#DBE1E3"/>
<path id="svg_27" d="m128.00101,338.60699c-64.21601,0 -116.55601,-21.457 -119.03801,-48.32199c-0.062,0.664 -0.103,1.32599 -0.103,1.996c0,27.79199 53.341,50.31998 119.14101,50.31998c65.8,0 119.14101,-22.52798 119.14101,-50.31998c0,-0.67001 -0.03799,-1.332 -0.103,-1.996c-2.483,26.86499 -54.82201,48.32199 -119.03801,48.32199z" fill="#DBE1E3"/>
<g id="svg_28">
<path id="svg_29" d="m128.74001,327.302c-5.31901,0 -10.66801,-0.27899 -19.08201,-0.992c-3.94299,-0.33798 -7.153,-3.86301 -7.153,-7.867l0,-79.97299c0,-3.728 2.82401,-6.54099 6.57001,-6.54099c0.207,0 0.42099,0.00998 0.633,0.02798c8.382,0.71201 13.71899,0.991 19.03201,0.991c5.312,0 10.64799,-0.27899 19.034,-0.992c0.21001,-0.01801 0.423,-0.02698 0.63,-0.02698c3.744,0 6.57001,2.81198 6.57001,6.54099l0,79.97299c0,4.004 -3.21101,7.52902 -7.15401,7.867c-8.416,0.71298 -13.76401,0.992 -19.08,0.992zm-19.665,-91.379c-1.54199,0 -2.57599,1.022 -2.57599,2.547l0,79.97299c0,1.91202 1.63399,3.728 3.498,3.88702c8.29099,0.70297 13.54599,0.97897 18.743,0.97897c5.19398,0 10.44899,-0.27499 18.74199,-0.97897c1.86301,-0.15802 3.498,-1.97501 3.498,-3.88702l0,-79.97299c0,-1.617 -1.20299,-2.68399 -2.864,-2.53299c-8.511,0.72198 -13.942,1.004 -19.37599,1.004c-5.43501,0 -10.86701,-0.28201 -19.37501,-1.004c-0.09801,-0.01001 -0.19701,-0.01401 -0.29001,-0.01401z" fill="#DBE1E3"/>
</g>
<g id="svg_30">
<path id="svg_31" d="m226.244,232.49298c-9.48099,6.14499 -27.129,17.582 -62.338,25.51599c5.05701,26.83801 5.05701,26.83801 11.70599,62.01401c12.18102,-3.625 37.52901,-11.17401 58.06499,-28.104c-2.34299,-19.66599 -4.22198,-34.961 -7.43298,-59.42599" fill="#F5DD3E"/>
</g>
<path id="svg_32" d="m203.304,303.173c-6.93901,3.828 -11.68201,-1.73401 -11.68201,-1.73401l4.097,-3.89301c0,0 2.285,2.94702 6.06,0.43402c1.76799,-1.17599 0.61501,-7.06601 0.61501,-7.06601l-3.394,-16.19501l5.34099,-2.25198c0,0 3.23801,18.70099 3.57602,20.57199c0.63199,3.50299 1.15298,6.95099 -4.61301,10.134z" fill="#3B3735"/>
<path id="svg_33" d="m217.68901,294.991c-3.90799,0.59 -6.117,-2.32401 -6.117,-2.32401l3.56299,-4.36099c0,0 1.34302,1.48999 3.20801,1.211c1.582,-0.242 3.439,-1.29501 4.08701,-3.28302c1.104,-3.40399 0.08099,-4.56998 -3.38599,-4.18399c-3.46301,0.38699 -7.819,0.89502 -8.59003,-5.936c-0.68399,-6.05899 4.61301,-10.694 8.19901,-11.32401c3.55801,-0.62299 5.543,1.88901 5.543,1.88901l-3.548,4.30701c0,0 -2.35397,-1.07001 -3.76898,0.19699c-1.28702,1.155 -1.78201,2.66202 -1.29199,3.80701c0.42899,1.00299 1.72498,1.44501 3.806,1.29102c2.082,-0.15002 5.50998,-1.36102 7.62,2.88498c2.37299,4.76901 -0.099,14.435 -9.32401,15.82501z" fill="#3B3735"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@ -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);
}
});

View File

@ -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 <code>for..in</code>',
'eqnull': 'About <code>== null</code>',
'noempty': 'About empty blocks',
'eqeqeq': 'About unsafe comparisons',
'boss': 'About assignments inside <code>if/for/...</code>',
'undef': 'When variable is undefined',
'unused': 'When variable is defined but not used',
'curly': 'When blocks omit <code>{}</code>'
};
var source = '';
// var jshints = {
// 'forin': 'About unsafe <code>for..in</code>',
// 'eqnull': 'About <code>== null</code>',
// 'noempty': 'About empty blocks',
// 'eqeqeq': 'About unsafe comparisons',
// 'boss': 'About assignments inside <code>if/for/...</code>',
// 'undef': 'When variable is undefined',
// 'unused': 'When variable is defined but not used',
// 'curly': 'When blocks omit <code>{}</code>'
// };
// 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 += '<div><label for="' + prop + '">' + jshints[prop] + '</label>' +
'<input id="' + prop + '" type="checkbox">' +
'</div>';
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 += '<div><label for="' + prop + '">' + jshints[prop] + '</label>' +
// '<input id="' + prop + '" type="checkbox">' +
// '</div>';
// }
// }
// 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);
}
});
}

View File

@ -0,0 +1,24 @@
'use strict';
/* globals $, backers */
var $backers = $('.backers');
backers.forEach(function (backer) {
var $a = $('<a>');
$a.attr('href', backer.url);
var $img = $('<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>');
$a.attr('href', backer.url);
var $img = $('<img>');
$img.attr('src', backer.image).addClass('backer-img');
$a.html($img);
$backers.append($a);
});

View File

@ -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());
});
}());

View File

@ -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);
}
};

View File

@ -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();
}
}

View File

@ -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);

View File

@ -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();
};

View File

@ -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;
});
};

View File

@ -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));
}
}
}

View File

@ -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();
}
})();

View File

@ -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);
});
}());

View File

@ -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 = ('<a class="jsbin-embed" href="' + url + '">' + documentTitle + '</a><' + 'script src="' + jsbin.static + '/js/embed.js"><' + '/script>').replace(/<>"&/g, function (m) {
this.value = ('<a class="jsbin-embed" href="' + url + hash + '">' + documentTitle + '</a><' + 'script src="' + jsbin.static + '/js/embed.js"><' + '/script>').replace(/<>"&/g, function (m) {
return {
'<': '&lt;',
'>': '&gt;',
@ -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;

View File

@ -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);
}
}
});
}
};

View File

@ -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);
});
}());

View File

@ -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);
// }
// };
})();

View File

@ -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();

View File

@ -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;

View File

@ -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&euml;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'
}
];

View File

@ -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]);

View File

@ -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 === ';') {

1
public/js/intro-start.js Normal file
View File

@ -0,0 +1 @@
function start(template, jsbin, window, document, undefined) {

View File

@ -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 = {}; }

1
public/js/outro-start.js Normal file
View File

@ -0,0 +1 @@
}

View File

@ -1 +1 @@
})(this, document);
})(window, document);

View File

@ -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) {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

94476
public/js/prod/jsbin-3.15.3.js Normal file

File diff suppressed because it is too large Load Diff

20
public/js/prod/jsbin-3.15.3.min.js vendored Normal file

File diff suppressed because one or more lines are too long

94476
public/js/prod/jsbin-3.15.4.js Normal file

File diff suppressed because it is too large Load Diff

20
public/js/prod/jsbin-3.15.4.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

1
public/js/prod/runner-3.15.3.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

1
public/js/prod/runner-3.15.4.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -583,8 +583,10 @@ var msgType = '';
jsconsole.init(document.getElementById('output'));
function upgradeConsolePanel(console) {
console.$el.click(function () {
jsconsole.focus();
console.$el.click(function (event) {
if (!$(event.target).closest('#output').length) {
jsconsole.focus();
}
});
console.reset = function () {
jsconsole.reset();

View File

@ -192,7 +192,7 @@ var renderer = (function () {
size.show().html(data.width + 'px');
hide();
}
if (jsbin.embed && embedResizeDone === false) {
if (jsbin.embed && self !== top && embedResizeDone === false) {
embedResizeDone = true;
// Inform the outer page of a size change
var height = ($body.outerHeight(true) - $(renderer.runner.iframe).height()) + data.offsetHeight;

View File

@ -144,9 +144,9 @@
$('a[pubdate]', $history).attr('pubdate', function (i, val) {
return val.replace('Z', '+0000');
}).prettyDate();
setInterval(function(){
$created.prettyDate();
}, 30 * 1000);
// setInterval(function(){
// $created.prettyDate();
// }, 30 * 1000);
// Update the layout straign away
setTimeout(function () {

View File

@ -4,16 +4,16 @@
* ========================================================================== */
var proxyConsole = (function () {
'use strict';
var supportsConsole = true;
try { window.console.log('runner'); } catch (e) { supportsConsole = false; }
try { window.console.log('d[ o_0 ]b'); } catch (e) { supportsConsole = false; }
var proxyConsole = {};
var proxyConsole = function() {};
/**
* Stringify all of the console objects from an array for proxying
*/
proxyConsole.stringifyArgs = function (args) {
var stringifyArgs = function (args) {
var newArgs = [];
// TODO this was forEach but when the array is [undefined] it wouldn't
// iterate over them
@ -31,7 +31,7 @@ var proxyConsole = (function () {
// Create each of these methods on the proxy, and postMessage up to JS Bin
// when one is called.
var methods = [
var methods = proxyConsole.prototype.methods = [
'debug', 'clear', 'error', 'info', 'log', 'warn', 'dir', 'props', '_raw',
'group', 'groupEnd', 'dirxml', 'table', 'trace', 'assert', 'count',
'markTimeline', 'profile', 'profileEnd', 'time', 'timeEnd', 'timeStamp',
@ -39,10 +39,10 @@ var proxyConsole = (function () {
];
methods.forEach(function (method) {
// Create console method
proxyConsole[method] = function () {
proxyConsole.prototype[method] = function () {
// Replace args that can't be sent through postMessage
var originalArgs = [].slice.call(arguments),
args = proxyConsole.stringifyArgs(originalArgs);
args = stringifyArgs(originalArgs);
// Post up with method and the arguments
runner.postMessage('console', {
@ -59,6 +59,6 @@ var proxyConsole = (function () {
};
});
return proxyConsole;
return new proxyConsole();
}());

View File

@ -4,7 +4,7 @@
* ========================================================================== */
var runner = (function () {
'use strict';
var runner = {};
/**
@ -18,7 +18,7 @@ var runner = (function () {
*/
runner.error = function () {
var args = ['Runner:'].concat([].slice.call(arguments));
if (!('console' in window)) return alert(args.join(' '));
if (!('console' in window)) {return alert(args.join(' '));}
window.console.error.apply(console, args);
};
@ -26,7 +26,7 @@ var runner = (function () {
* Handle all incoming postMessages to the runner
*/
runner.handleMessage = function (event) {
if (!event.origin) return;
if (!event.origin) {return;}
var data = event.data;
try {
data = JSON.parse(event.data);
@ -67,6 +67,12 @@ var runner = (function () {
childWindow = getIframeWindow(iframe);
if (!childDoc) childDoc = childWindow.document;
// Reset the console to the prototype state
proxyConsole.methods.forEach(function (method) {
delete proxyConsole[method];
});
// Process the source according to the options passed in
var source = processor.render(data.source, data.options);

View File

@ -226,8 +226,7 @@ var id = location.pathname.replace(/\/(preview|edit|watch).*$/, ''),
useSS = false,
es = null;
// Wait for a bit, then set up the EventSource stream
setTimeout(function () {
function startStream() {
es = new EventSource(id + '?' + Math.random());
if (codecasting) {
codecastStream();
@ -240,6 +239,37 @@ setTimeout(function () {
$document.trigger('stats', [event.data]);
});
}
return es;
}
function handleVisibility(es, listen) {
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 {
es = listen();
}
});
}
}
// Wait for a bit, then set up the EventSource stream
setTimeout(function() {
es = startStream();
handleVisibility(es, startStream);
}, 500);
// If this is the render stream, restore data from before the last reload if

View File

@ -0,0 +1,172 @@
// Remy Sharp / MIT
//
// Because sometimes you want line numbers to be highlighted
//
// Adds an option 'highlightedLine' which, when enabled, gives the
// active line's wrapping <div> the CSS class 'CodeMirror-highlight-line',
// and gives its background <div> the class 'CodeMirror-highlight-line-background'.
// *and* emits an event called "highlight-lines" against the CM instance with
// the first argument being the line numbers now highlighted.
(function(mod) {
/* globals define, CodeMirror */
'use strict';
if (typeof exports === 'object' && typeof module === 'object') { // CommonJS
mod(require('../../lib/codemirror'));
} else if (typeof define === 'function' && define.amd) { // AMD
define(['../../lib/codemirror'], mod);
} else { // Plain browser env
mod(CodeMirror);
}
})(function(CodeMirror) {
'use strict';
var WRAP_CLASS = 'CodeMirror-highlight-line';
var BACK_CLASS = 'CodeMirror-highlight-line-background';
CodeMirror.defineOption('highlighLine', false, function(cm, val, old) {
var prev = old && old !== CodeMirror.Init;
if (val && !prev) {
cm.state.highlightedLines = [];
if (typeof val !== 'boolean') {
updateHighlightedLines(cm, parseLinesToArray(val));
}
cm.on('gutterClick', gutterClick);
} else if (!val && prev) {
cm.off('gutterClick', gutterClick);
clearHighlightedLines(cm);
delete cm.state.highlightedLines;
}
});
CodeMirror.defineExtension('highlightLines', function (lines) {
if (lines) {
clearHighlightedLines(this);
updateHighlightedLines(this, parseLinesToArray(lines));
} else {
var active = [].slice.call(this.state.highlightedLines);
return {
lines: active,
string: parseArrayToString(active)
};
}
});
function parseLinesToArray(str) {
var active = [];
if (({}).toString.call(str) === '[object Array]') {
// wat...oh you gave me an array
return str;
}
if (str.indexOf('-') !== -1) {
// range
var range = str.split('-');
var i = parseInt(range[0], 10);
var length = parseInt(range[1], 10);
for (; i <= length; i++) {
active.push(i-1);
}
} else {
active = [parseInt(str, 10) - 1];
}
return active;
}
function parseArrayToString(active) {
if (active.length === 1) {
return (active[0] + 1) + '';
} else if (active.length === 0) {
return '';
} else {
return (active[0] + 1) + '-' + (active.slice(-1)[0] + 1);
}
}
function clearHighlightedLines(cm) {
for (var i = 0; i < cm.state.highlightedLines.length; i++) {
cm.removeLineClass(cm.state.highlightedLines[i], 'wrap', WRAP_CLASS);
cm.removeLineClass(cm.state.highlightedLines[i], 'background', BACK_CLASS);
}
cm.state.highlightedLines = [];
}
function sameArray(a, b) {
if (a.length !== b.length) {
return false;
}
for (var i = 0; i < a.length; i++) {
if (a[i] !== b[i]) {
return false;
}
}
return true;
}
function highlightLines(cm, lineNumber, event) {
// copy the array (to avoid creating a reference)
var active = [].slice.call(cm.state.highlightedLines, 0);
// shiftKey down gives multi-line highlight support
if (active.length && event.shiftKey) {
var i = active[0];
active = [];
// then highlight *to* this line
if (lineNumber < i) {
// highlight *up* to this new number
// reduce highlight to this point
for (; i >= lineNumber; i--) {
active.push(i);
}
} else {
// reduce highlight to this point
for (; i <= lineNumber; i++) {
active.push(i);
}
}
} else if (active.indexOf(lineNumber) === -1) {
active = [lineNumber]; // only select one line
}
// sort the line numbers so when the user gets them in the event, it's vaguely sane.
active = active.sort(function (a, b) {
return a - b;
});
if (sameArray(cm.state.highlightedLines, active)) {
clearHighlightedLines(cm);
if (event) {
// only signal if it came from a user action
signal(cm, active);
}
return;
}
updateHighlightedLines(cm, active, event);
}
function updateHighlightedLines(cm, active, event) {
cm.operation(function() {
clearHighlightedLines(cm);
for (var i = 0; i < active.length; i++) {
cm.addLineClass(active[i], 'wrap', WRAP_CLASS);
cm.addLineClass(active[i], 'background', BACK_CLASS);
}
cm.state.highlightedLines = active;
if (event) {
// only signal if it came from a user action
signal(cm, active);
}
});
}
function signal(cm, active) {
CodeMirror.signal(cm, 'highlightLines', cm, active, parseArrayToString(active));
}
function gutterClick(cm, lineNumber, gutter, event) {
highlightLines(cm, lineNumber, event);
}
});

View File

@ -0,0 +1,38 @@
// Depends on coffeelint.js from http://www.coffeelint.org/js/coffeelint.js
// declare global: coffeelint
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.registerHelper("lint", "coffeescript", function(text, options) {
var found = [];
var parseError = function(err) {
var loc = err.lineNumber;
found.push({from: CodeMirror.Pos(loc-1, 0),
to: CodeMirror.Pos(loc, 0),
severity: err.level,
message: err.message});
};
try {
var res = coffeelint.lint(text, options);
for(var i = 0; i < res.length; i++) {
parseError(res[i]);
}
} catch(e) {
found.push({from: CodeMirror.Pos(e.location.first_line, 0),
to: CodeMirror.Pos(e.location.last_line, e.location.last_column),
severity: 'error',
message: e.message});
}
return found;
});
});

View File

@ -0,0 +1,35 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
// Depends on csslint.js from https://github.com/stubbornella/csslint
// declare global: CSSLint
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.registerHelper("lint", "css", function(text, options) {
var found = [];
if (!window.CSSLint) return found;
var results = CSSLint.verify(text, options), messages = results.messages, message = null;
for ( var i = 0; i < messages.length; i++) {
message = messages[i];
var startLine = message.line -1, endLine = message.line -1, startCol = message.col -1, endCol = message.col;
found.push({
from: CodeMirror.Pos(startLine, startCol),
to: CodeMirror.Pos(endLine, endCol),
message: message.message,
severity : message.type
});
}
return found;
});
});

View File

@ -0,0 +1,32 @@
// Depends on htmlhint.js from https://github.com/yaniswang/HTMLHint
// declare global: HTMLHint
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.registerHelper("lint", "htmlmixed", function(text, options) {
var found = [];
if (!window.HTMLHint) return found;
var results = HTMLHint.verify(text, options), messages = results, message = null;
for ( var i = 0; i < messages.length; i++) {
message = messages[i];
var startLine = message.line -1, endLine = message.line -1, startCol = message.col -1, endCol = message.col;
found.push({
from: CodeMirror.Pos(startLine, startCol),
to: CodeMirror.Pos(endLine, endCol),
message: message.message,
severity : message.type
});
}
return found;
});
});

View File

@ -0,0 +1,132 @@
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
// declare global: JSHINT
var bogus = [ "Dangerous comment" ];
var warnings = [ [ "Expected '{'",
"Statement body should be inside '{ }' braces." ] ];
var errors = [ "Missing semicolon", "Extra comma", "Missing property name",
"Unmatched ", " and instead saw", " is not defined",
"Unclosed string", "Stopping, unable to continue" ];
function validator(text, options) {
JSHINT(text, options);
var errors = JSHINT.data().errors, result = [];
if (errors) parseErrors(errors, result);
return result;
}
CodeMirror.registerHelper("lint", "javascript", validator);
function cleanup(error) {
// All problems are warnings by default
fixWith(error, warnings, "warning", true);
fixWith(error, errors, "error");
return isBogus(error) ? null : error;
}
function fixWith(error, fixes, severity, force) {
var description, fix, find, replace, found;
description = error.description;
for ( var i = 0; i < fixes.length; i++) {
fix = fixes[i];
find = (typeof fix === "string" ? fix : fix[0]);
replace = (typeof fix === "string" ? null : fix[1]);
found = description.indexOf(find) !== -1;
if (force || found) {
error.severity = severity;
}
if (found && replace) {
error.description = replace;
}
}
}
function isBogus(error) {
var description = error.description;
for ( var i = 0; i < bogus.length; i++) {
if (description.indexOf(bogus[i]) !== -1) {
return true;
}
}
return false;
}
function parseErrors(errors, output) {
for ( var i = 0; i < errors.length; i++) {
var error = errors[i];
if (error) {
var linetabpositions, index;
linetabpositions = [];
// This next block is to fix a problem in jshint. Jshint
// replaces
// all tabs with spaces then performs some checks. The error
// positions (character/space) are then reported incorrectly,
// not taking the replacement step into account. Here we look
// at the evidence line and try to adjust the character position
// to the correct value.
if (error.evidence) {
// Tab positions are computed once per line and cached
var tabpositions = linetabpositions[error.line];
if (!tabpositions) {
var evidence = error.evidence;
tabpositions = [];
// ugggh phantomjs does not like this
// forEachChar(evidence, function(item, index) {
Array.prototype.forEach.call(evidence, function(item,
index) {
if (item === '\t') {
// First col is 1 (not 0) to match error
// positions
tabpositions.push(index + 1);
}
});
linetabpositions[error.line] = tabpositions;
}
if (tabpositions.length > 0) {
var pos = error.character;
tabpositions.forEach(function(tabposition) {
if (pos > tabposition) pos -= 1;
});
error.character = pos;
}
}
var start = error.character - 1, end = start + 1;
if (error.evidence) {
index = error.evidence.substring(start).search(/.\b/);
if (index > -1) {
end += index;
}
}
// Convert to format expected by validation service
error.description = error.reason;// + "(jshint)";
error.start = error.character;
error.end = end;
error = cleanup(error);
if (error)
output.push({message: error.description,
severity: error.severity,
from: CodeMirror.Pos(error.line - 1, start),
to: CodeMirror.Pos(error.line - 1, end)});
}
}
}
});

140
public/js/vendor/cm_addons/lint/lint.css vendored Normal file
View File

@ -0,0 +1,140 @@
/* 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,<svg xmlns=\'http://www.w3.org/2000/svg\'><filter id=\'grayscale\'><feColorMatrix type=\'matrix\' values=\'0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0 0 0 1 0\'/></filter></svg>#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;
}

425
public/js/vendor/cm_addons/lint/lint.js vendored Normal file
View File

@ -0,0 +1,425 @@
(function(mod) {
if (typeof exports == 'object' && typeof module == 'object') // CommonJS
mod(require('../../lib/codemirror'));
else if (typeof define == 'function' && define.amd) // AMD
define(['../../lib/codemirror'], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
'use strict';
var GUTTER_ID = 'CodeMirror-lint-markers';
var SEVERITIES = /^(?:error|warning)$/;
function showTooltip(e, content) {
var tt = document.createElement('div');
tt.className = 'CodeMirror-lint-tooltip';
tt.appendChild(content.cloneNode(true));
document.body.appendChild(tt);
function position(e) {
if (!tt.parentNode) return CodeMirror.off(document, 'mousemove', position);
tt.style.top = Math.max(0, e.clientY - tt.offsetHeight - 5) + 'px';
tt.style.left = (e.clientX + 5) + 'px';
}
CodeMirror.on(document, 'mousemove', position);
position(e);
if (tt.style.opacity != null) tt.style.opacity = 1;
return tt;
}
function rm(elt) {
if (elt.parentNode) elt.parentNode.removeChild(elt);
}
function hideTooltip(tt) {
if (!tt.parentNode) return;
if (tt.style.opacity == null) rm(tt);
tt.style.opacity = 0;
setTimeout(function() { rm(tt); }, 600);
}
function showTooltipFor(e, content, node) {
var tooltip = showTooltip(e, content);
function hide() {
CodeMirror.off(node, 'mouseout', hide);
if (tooltip) { hideTooltip(tooltip); tooltip = null; }
}
var poll = setInterval(function() {
if (tooltip) for (var n = node;; n = n.parentNode) {
if (n == document.body) return;
if (!n) { hide(); break; }
}
if (!tooltip) return clearInterval(poll);
}, 400);
CodeMirror.on(node, 'mouseout', hide);
}
function LintState(cm, options, hasGutter) {
this.lineWidgets = [];
this.marked = [];
this.options = options;
this.timeout = null;
this.hasGutter = hasGutter;
this.onMouseOver = function(e) { onMouseOver(cm, e); };
}
function parseOptions(cm, options) {
if (options instanceof Function) return {getAnnotations: options};
if (!options || options === true) options = {};
if (!options.getAnnotations) options.getAnnotations = cm.getHelper(CodeMirror.Pos(0, 0), 'lint');
if (!options.getAnnotations && cm.getOption('mode') === 'htmlmixed') {
options.getAnnotations = CodeMirror.helpers.lint.htmlmixed;
}
if (!options.getAnnotations && cm.getOption('mode') === 'coffeescript') {
options.getAnnotations = CodeMirror.helpers.lint.coffeescript;
}
if (!options.getAnnotations) throw new Error('Required option "getAnnotations" missing (lint addon)');
return options;
}
function underClear(cm) {
var state = cm.state.lint;
for (var i = 0; i < state.marked.length; ++i)
state.marked[i].clear();
state.marked.length = 0;
}
function gutterMarkerDraw(labels, severity, multiple, tooltip) {
var marker = document.createElement('div'), inner = marker;
marker.className = 'CodeMirror-lint-marker-' + severity;
if (multiple) {
inner = marker.appendChild(document.createElement('div'));
inner.className = 'CodeMirror-lint-marker-multiple';
}
if (tooltip) {
CodeMirror.on(inner, 'mouseover', function(e) {
showTooltipFor(e, labels, inner);
});
}
return marker;
}
function getMaxSeverity(a, b) {
if (a == 'error') return a;
else return b;
}
function groupByLine(annotations) {
var lines = [];
for (var i = 0; i < annotations.length; ++i) {
var ann = annotations[i], line = ann.from.line;
(lines[line] || (lines[line] = [])).push(ann);
}
return lines;
}
function annotationTooltip(ann, severity) {
var tip = document.createElement('div');
tip.className = 'CodeMirror-lint-message-' + severity;
tip.appendChild(document.createTextNode(ann.message));
return tip;
}
function gutterDraw(line, tipLabel, maxSeverity, anns, state, cm) {
cm.setGutterMarker(line, GUTTER_ID, gutterMarkerDraw(tipLabel, maxSeverity, anns.length > 1, cm.options.lintOpt.tooltip));
}
function gutterReset(cm) {
cm.clearGutter(GUTTER_ID);
}
function underDraw(ann, severity, state, cm) {
state.marked.push(cm.markText(ann.from, ann.to, {
className: 'CodeMirror-lint-mark-' + severity,
__annotation: ann
}));
}
function consoleInit(cm) {
var wrapper = document.createElement('details');
var head = document.createElement('summary');
var logs = document.createElement('div');
wrapper.className = 'console-wrapper';
head.className = 'console-log-head';
logs.className = 'console-log';
wrapper.appendChild(head);
wrapper.appendChild(logs);
cm.consolelint = {
wrapper: wrapper,
logs: logs,
head: head,
error: 0,
warning: 0
};
cm.options.lintOpt.consoleParent.appendChild(wrapper);
CodeMirror.on(logs, 'click', function(event) {
consoleClick(event, cm);
});
}
function consoleLine(ann, severity, cm) {
cm.consolelint[severity]++;
var line = document.createElement('div');
line.className = 'console-log-line lint-icon-' + severity;
line.setAttribute('data-ch', ann.from.ch);
line.setAttribute('data-line', ann.from.line);
line.setAttribute('data-reason', ann.message);
line.appendChild(document.createTextNode('Line ' + (ann.from.line + 1) + ': ' + ann.message));
return line;
}
function consoleReset(cm) {
cm.consolelint.error = 0;
cm.consolelint.warning = 0;
cm.consolelint.logs.innerHTML = '';
}
function consoleHeadUpdate(cm) {
var counterEclass = '';
var counterWclass = '';
var es = 's';
var ws = 's';
if (cm.consolelint.error === 0) counterEclass = ' dis';
if (cm.consolelint.warning === 0) counterWclass = ' dis';
if (cm.consolelint.error === 1) es = '';
if (cm.consolelint.warning === 1) ws = '';
cm.consolelint.head.innerHTML = '';
if (counterEclass && counterWclass) {
cm.consolelint.head.style.display = 'none';
cm.consolelint.logs.style.display = 'none';
$document.trigger('sizeeditors');
return;
}
cm.consolelint.head.style.display = '';
cm.consolelint.logs.style.display = '';
if (!counterEclass) {
cm.consolelint.head.innerHTML += '<i class="lint-icon-error' + counterEclass + '"></i> ' +
cm.consolelint.error + ' error' + es + ' ';
}
if (!counterWclass) {
cm.consolelint.head.innerHTML += '<i class="lint-icon-warning' + counterWclass + '"></i> ' +
cm.consolelint.warning + ' warning' + ws;
}
$document.trigger('sizeeditors');
}
function consoleClick(event, cm) {
var target = event.target;
if (target.className.indexOf('console-log-line') !== -1) {
var ch = target.getAttribute('data-ch') * 1;
var line = target.getAttribute('data-line') * 1;
var reason = target.getAttribute('data-reason');
var lineHeight = cm.defaultTextHeight();
cm.setCursor({ line: line, ch: ch });
cm.scrollIntoView(null, lineHeight * 3);
cm.focus();
var old = document.getElementById('console-log-line-selected');
if (old) {
old.id = '';
}
target.id = 'console-log-line-selected';
if (cm.options.lintOpt.line) {
lineUpdate({ reason: reason, line: line }, cm);
}
}
}
function lineUpdate(ann, cm) {
lineReset(cm);
var msg = document.createElement('div');
msg.appendChild(document.createTextNode(ann.reason));
msg.className = 'lint-error';
cm.state.lint.lineWidgets.push(cm.addLineWidget(ann.line * 1, msg, { coverGutter: false, noHScroll: true }));
}
function lineReset(cm) {
var lineWidgets = cm.state.lint.lineWidgets;
for (var i = 0; i < lineWidgets.length; ++i) {
cm.removeLineWidget(lineWidgets[i]);
}
lineWidgets.length = 0;
}
function startLinting(cm) {
var state = cm.state.lint, options = state.options;
if (options.async)
options.getAnnotations(cm, updateLinting, cm.options.lintRules);
else
updateLinting(cm, options.getAnnotations(cm.getValue(), cm.options.lintRules));
}
function updateLinting(cm, annotationsNotSorted) {
var state = cm.state.lint, options = state.options;
if (cm.options.lintOpt.under) {
underClear(cm);
}
if (state.hasGutter) {
gutterReset(cm);
}
var annotations = groupByLine(annotationsNotSorted);
if (cm.options.lintOpt.console) {
consoleReset(cm);
if (cm.options.lintOpt.line) {
lineReset(cm);
}
}
for (var line = 0; line < annotations.length; ++line) {
var anns = annotations[line];
if (!anns) continue;
var maxSeverity = null;
var tipLabel = state.hasGutter && document.createDocumentFragment();
for (var i = 0; i < anns.length; ++i) {
var ann = anns[i];
var severity = ann.severity;
if (!SEVERITIES.test(severity)) severity = 'error';
maxSeverity = getMaxSeverity(maxSeverity, severity);
if (options.formatAnnotation) ann = options.formatAnnotation(ann);
if (state.hasGutter) {
tipLabel.appendChild(annotationTooltip(ann, severity));
}
if (cm.options.lintOpt.console) {
cm.consolelint.logs.appendChild(consoleLine(ann, severity, cm));
}
if (cm.options.lintOpt.under) {
if (ann.to) {
underDraw(ann, severity, state, cm);
}
}
}
if (state.hasGutter) {
gutterDraw(line, tipLabel, maxSeverity, anns, state, cm);
}
}
if (options.onUpdateLinting) options.onUpdateLinting(annotationsNotSorted, annotations, cm);
if (cm.options.lintOpt.console) {
consoleHeadUpdate(cm);
}
}
function onChange(cm) {
var state = cm.state.lint;
clearTimeout(state.timeout);
state.timeout = setTimeout(function(){startLinting(cm);}, state.options.delay || 500);
}
function popupSpanTooltip(ann, e) {
var target = e.target || e.srcElement;
showTooltipFor(e, annotationTooltip(ann), target);
}
// When the mouseover fires, the cursor might not actually be over
// the character itself yet. These pairs of x,y offsets are used to
// probe a few nearby points when no suitable marked range is found.
var nearby = [0, 0, 0, 5, 0, -5, 5, 0, -5, 0];
function onMouseOver(cm, e) {
if (!/\bCodeMirror-lint-mark-/.test((e.target || e.srcElement).className)) return;
for (var i = 0; i < nearby.length; i += 2) {
var spans = cm.findMarksAt(cm.coordsChar({left: e.clientX + nearby[i],
top: e.clientY + nearby[i + 1]}, 'client'));
for (var j = 0; j < spans.length; ++j) {
var span = spans[j], ann = span.__annotation;
if (ann) return popupSpanTooltip(ann, e);
}
}
}
function lintStop(cm) {
lineReset(cm);
cm.setOption('lint', false);
var opt = cm.getOption('lintOpt');
if (opt.gutter) {
var gutters = cm.getOption('gutters');
var pos = gutters.indexOf('CodeMirror-lint-markers');
if (pos !== -1) {
gutters.splice(pos, 1);
cm.setOption('gutters', gutters);
var ln = cm.getOption('lineNumbers');
cm.setOption('lineNumbers', !ln);
cm.setOption('lineNumbers', ln);
}
}
if (opt.console) {
opt.consoleParent.removeChild(cm.consolelint.wrapper);
$document.trigger('sizeeditors');
}
cm.refresh();
}
CodeMirror.defineOption('lint', false, function(cm, val, old) {
var defaults = {
console: true,
consoleParent: cm.getWrapperElement().parentNode,
line: true,
under: true,
tooltip: true
};
if (!cm.options.lintOpt) {
cm.options.lintOpt = {};
}
for (var key in defaults) {
if (defaults.hasOwnProperty(key)) {
cm.options.lintOpt[key] = (cm.options.lintOpt[key] !== undefined) ? cm.options.lintOpt[key] : defaults[key];
}
}
if (!cm.options.lintRules) {
cm.options.lintRules = {};
}
if (old && old != CodeMirror.Init) {
if (cm.options.lintOpt.under) {
underClear(cm);
}
if (cm.state.lint.hasGutter) {
gutterReset(cm);
}
cm.off('change', onChange);
CodeMirror.off(cm.getWrapperElement(), 'mouseover', cm.state.lint.onMouseOver);
delete cm.state.lint;
}
if (val) {
var gutters = cm.getOption('gutters'), hasLintGutter = false;
for (var i = 0; i < gutters.length; ++i) if (gutters[i] == GUTTER_ID) hasLintGutter = true;
var state = cm.state.lint = new LintState(cm, parseOptions(cm, val), hasLintGutter);
cm.on('change', onChange);
if (cm.options.lintOpt.console) {
consoleInit(cm);
}
if (cm.options.lintOpt.tooltip)
CodeMirror.on(cm.getWrapperElement(), 'mouseover', state.onMouseOver);
startLinting(cm);
cm.lintStop = function() {
return lintStop(this);
};
}
// probably to improve according to real case scenarios
// cm.updateLinting = function(annotationsNotSorted) {
// updateLinting(cm, annotationsNotSorted);
// console.log('ciao');
// };
});
});

2324
public/js/vendor/coffeelint/coffeelint.js vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

9259
public/js/vendor/csslint/csslint.js vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

8
public/js/vendor/htmlhint/htmlhint.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

1
public/js/vendor/jshint/jshint.min.js vendored Normal file

File diff suppressed because one or more lines are too long

4748
public/js/vendor/jshint/jshint.old.js vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

16
public/js/vendor/less-1.7.3.min.js vendored Normal file

File diff suppressed because one or more lines are too long

665
public/js/vendor/lz-string-1.3.3.js vendored Normal file
View File

@ -0,0 +1,665 @@
// Copyright (c) 2013 Pieroxy <pieroxy@pieroxy.net>
// This work is free. You can redistribute it and/or modify it
// under the terms of the WTFPL, Version 2
// For more information see LICENSE.txt or http://www.wtfpl.net/
//
// For more information, the home page:
// http://pieroxy.net/blog/pages/lz-string/testing.html
//
// LZ-based compression algorithm, version 1.3.3
var LZString = {
// private property
_keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
_f : String.fromCharCode,
compressToBase64 : function (input) {
if (input == null) return "";
var output = "";
var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
var i = 0;
input = LZString.compress(input);
while (i < input.length*2) {
if (i%2==0) {
chr1 = input.charCodeAt(i/2) >> 8;
chr2 = input.charCodeAt(i/2) & 255;
if (i/2+1 < input.length)
chr3 = input.charCodeAt(i/2+1) >> 8;
else
chr3 = NaN;
} else {
chr1 = input.charCodeAt((i-1)/2) & 255;
if ((i+1)/2 < input.length) {
chr2 = input.charCodeAt((i+1)/2) >> 8;
chr3 = input.charCodeAt((i+1)/2) & 255;
} else
chr2=chr3=NaN;
}
i+=3;
enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
enc4 = chr3 & 63;
if (isNaN(chr2)) {
enc3 = enc4 = 64;
} else if (isNaN(chr3)) {
enc4 = 64;
}
output = output +
LZString._keyStr.charAt(enc1) + LZString._keyStr.charAt(enc2) +
LZString._keyStr.charAt(enc3) + LZString._keyStr.charAt(enc4);
}
return output;
},
decompressFromBase64 : function (input) {
if (input == null) return "";
var output = "",
ol = 0,
output_,
chr1, chr2, chr3,
enc1, enc2, enc3, enc4,
i = 0, f=LZString._f;
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
while (i < input.length) {
enc1 = LZString._keyStr.indexOf(input.charAt(i++));
enc2 = LZString._keyStr.indexOf(input.charAt(i++));
enc3 = LZString._keyStr.indexOf(input.charAt(i++));
enc4 = LZString._keyStr.indexOf(input.charAt(i++));
chr1 = (enc1 << 2) | (enc2 >> 4);
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
chr3 = ((enc3 & 3) << 6) | enc4;
if (ol%2==0) {
output_ = chr1 << 8;
if (enc3 != 64) {
output += f(output_ | chr2);
}
if (enc4 != 64) {
output_ = chr3 << 8;
}
} else {
output = output + f(output_ | chr1);
if (enc3 != 64) {
output_ = chr2 << 8;
}
if (enc4 != 64) {
output += f(output_ | chr3);
}
}
ol+=3;
}
return LZString.decompress(output);
},
compressToUTF16 : function (input) {
if (input == null) return "";
var output = "",
i,c,
current,
status = 0,
f = LZString._f;
input = LZString.compress(input);
for (i=0 ; i<input.length ; i++) {
c = input.charCodeAt(i);
switch (status++) {
case 0:
output += f((c >> 1)+32);
current = (c & 1) << 14;
break;
case 1:
output += f((current + (c >> 2))+32);
current = (c & 3) << 13;
break;
case 2:
output += f((current + (c >> 3))+32);
current = (c & 7) << 12;
break;
case 3:
output += f((current + (c >> 4))+32);
current = (c & 15) << 11;
break;
case 4:
output += f((current + (c >> 5))+32);
current = (c & 31) << 10;
break;
case 5:
output += f((current + (c >> 6))+32);
current = (c & 63) << 9;
break;
case 6:
output += f((current + (c >> 7))+32);
current = (c & 127) << 8;
break;
case 7:
output += f((current + (c >> 8))+32);
current = (c & 255) << 7;
break;
case 8:
output += f((current + (c >> 9))+32);
current = (c & 511) << 6;
break;
case 9:
output += f((current + (c >> 10))+32);
current = (c & 1023) << 5;
break;
case 10:
output += f((current + (c >> 11))+32);
current = (c & 2047) << 4;
break;
case 11:
output += f((current + (c >> 12))+32);
current = (c & 4095) << 3;
break;
case 12:
output += f((current + (c >> 13))+32);
current = (c & 8191) << 2;
break;
case 13:
output += f((current + (c >> 14))+32);
current = (c & 16383) << 1;
break;
case 14:
output += f((current + (c >> 15))+32, (c & 32767)+32);
status = 0;
break;
}
}
return output + f(current + 32);
},
decompressFromUTF16 : function (input) {
if (input == null) return "";
var output = "",
current,c,
status=0,
i = 0,
f = LZString._f;
while (i < input.length) {
c = input.charCodeAt(i) - 32;
switch (status++) {
case 0:
current = c << 1;
break;
case 1:
output += f(current | (c >> 14));
current = (c&16383) << 2;
break;
case 2:
output += f(current | (c >> 13));
current = (c&8191) << 3;
break;
case 3:
output += f(current | (c >> 12));
current = (c&4095) << 4;
break;
case 4:
output += f(current | (c >> 11));
current = (c&2047) << 5;
break;
case 5:
output += f(current | (c >> 10));
current = (c&1023) << 6;
break;
case 6:
output += f(current | (c >> 9));
current = (c&511) << 7;
break;
case 7:
output += f(current | (c >> 8));
current = (c&255) << 8;
break;
case 8:
output += f(current | (c >> 7));
current = (c&127) << 9;
break;
case 9:
output += f(current | (c >> 6));
current = (c&63) << 10;
break;
case 10:
output += f(current | (c >> 5));
current = (c&31) << 11;
break;
case 11:
output += f(current | (c >> 4));
current = (c&15) << 12;
break;
case 12:
output += f(current | (c >> 3));
current = (c&7) << 13;
break;
case 13:
output += f(current | (c >> 2));
current = (c&3) << 14;
break;
case 14:
output += f(current | (c >> 1));
current = (c&1) << 15;
break;
case 15:
output += f(current | c);
status=0;
break;
}
i++;
}
return LZString.decompress(output);
//return output;
},
compress: function (uncompressed) {
if (uncompressed == null) return "";
var i, value,
context_dictionary= {},
context_dictionaryToCreate= {},
context_c="",
context_wc="",
context_w="",
context_enlargeIn= 2, // Compensate for the first entry which should not count
context_dictSize= 3,
context_numBits= 2,
context_data_string="",
context_data_val=0,
context_data_position=0,
ii,
f=LZString._f;
for (ii = 0; ii < uncompressed.length; ii += 1) {
context_c = uncompressed.charAt(ii);
if (!Object.prototype.hasOwnProperty.call(context_dictionary,context_c)) {
context_dictionary[context_c] = context_dictSize++;
context_dictionaryToCreate[context_c] = true;
}
context_wc = context_w + context_c;
if (Object.prototype.hasOwnProperty.call(context_dictionary,context_wc)) {
context_w = context_wc;
} else {
if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate,context_w)) {
if (context_w.charCodeAt(0)<256) {
for (i=0 ; i<context_numBits ; i++) {
context_data_val = (context_data_val << 1);
if (context_data_position == 15) {
context_data_position = 0;
context_data_string += f(context_data_val);
context_data_val = 0;
} else {
context_data_position++;
}
}
value = context_w.charCodeAt(0);
for (i=0 ; i<8 ; i++) {
context_data_val = (context_data_val << 1) | (value&1);
if (context_data_position == 15) {
context_data_position = 0;
context_data_string += f(context_data_val);
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
} else {
value = 1;
for (i=0 ; i<context_numBits ; i++) {
context_data_val = (context_data_val << 1) | value;
if (context_data_position == 15) {
context_data_position = 0;
context_data_string += f(context_data_val);
context_data_val = 0;
} else {
context_data_position++;
}
value = 0;
}
value = context_w.charCodeAt(0);
for (i=0 ; i<16 ; i++) {
context_data_val = (context_data_val << 1) | (value&1);
if (context_data_position == 15) {
context_data_position = 0;
context_data_string += f(context_data_val);
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
}
context_enlargeIn--;
if (context_enlargeIn == 0) {
context_enlargeIn = Math.pow(2, context_numBits);
context_numBits++;
}
delete context_dictionaryToCreate[context_w];
} else {
value = context_dictionary[context_w];
for (i=0 ; i<context_numBits ; i++) {
context_data_val = (context_data_val << 1) | (value&1);
if (context_data_position == 15) {
context_data_position = 0;
context_data_string += f(context_data_val);
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
}
context_enlargeIn--;
if (context_enlargeIn == 0) {
context_enlargeIn = Math.pow(2, context_numBits);
context_numBits++;
}
// Add wc to the dictionary.
context_dictionary[context_wc] = context_dictSize++;
context_w = String(context_c);
}
}
// Output the code for w.
if (context_w !== "") {
if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate,context_w)) {
if (context_w.charCodeAt(0)<256) {
for (i=0 ; i<context_numBits ; i++) {
context_data_val = (context_data_val << 1);
if (context_data_position == 15) {
context_data_position = 0;
context_data_string += f(context_data_val);
context_data_val = 0;
} else {
context_data_position++;
}
}
value = context_w.charCodeAt(0);
for (i=0 ; i<8 ; i++) {
context_data_val = (context_data_val << 1) | (value&1);
if (context_data_position == 15) {
context_data_position = 0;
context_data_string += f(context_data_val);
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
} else {
value = 1;
for (i=0 ; i<context_numBits ; i++) {
context_data_val = (context_data_val << 1) | value;
if (context_data_position == 15) {
context_data_position = 0;
context_data_string += f(context_data_val);
context_data_val = 0;
} else {
context_data_position++;
}
value = 0;
}
value = context_w.charCodeAt(0);
for (i=0 ; i<16 ; i++) {
context_data_val = (context_data_val << 1) | (value&1);
if (context_data_position == 15) {
context_data_position = 0;
context_data_string += f(context_data_val);
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
}
context_enlargeIn--;
if (context_enlargeIn == 0) {
context_enlargeIn = Math.pow(2, context_numBits);
context_numBits++;
}
delete context_dictionaryToCreate[context_w];
} else {
value = context_dictionary[context_w];
for (i=0 ; i<context_numBits ; i++) {
context_data_val = (context_data_val << 1) | (value&1);
if (context_data_position == 15) {
context_data_position = 0;
context_data_string += f(context_data_val);
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
}
context_enlargeIn--;
if (context_enlargeIn == 0) {
context_enlargeIn = Math.pow(2, context_numBits);
context_numBits++;
}
}
// Mark the end of the stream
value = 2;
for (i=0 ; i<context_numBits ; i++) {
context_data_val = (context_data_val << 1) | (value&1);
if (context_data_position == 15) {
context_data_position = 0;
context_data_string += f(context_data_val);
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
// Flush the last char
while (true) {
context_data_val = (context_data_val << 1);
if (context_data_position == 15) {
context_data_string += f(context_data_val);
break;
}
else context_data_position++;
}
return context_data_string;
},
decompress: function (compressed) {
if (compressed == null) return "";
if (compressed == "") return null;
var dictionary = [],
next,
enlargeIn = 4,
dictSize = 4,
numBits = 3,
entry = "",
result = "",
i,
w,
bits, resb, maxpower, power,
c,
f = LZString._f,
data = {string:compressed, val:compressed.charCodeAt(0), position:32768, index:1};
for (i = 0; i < 3; i += 1) {
dictionary[i] = i;
}
bits = 0;
maxpower = Math.pow(2,2);
power=1;
while (power!=maxpower) {
resb = data.val & data.position;
data.position >>= 1;
if (data.position == 0) {
data.position = 32768;
data.val = data.string.charCodeAt(data.index++);
}
bits |= (resb>0 ? 1 : 0) * power;
power <<= 1;
}
switch (next = bits) {
case 0:
bits = 0;
maxpower = Math.pow(2,8);
power=1;
while (power!=maxpower) {
resb = data.val & data.position;
data.position >>= 1;
if (data.position == 0) {
data.position = 32768;
data.val = data.string.charCodeAt(data.index++);
}
bits |= (resb>0 ? 1 : 0) * power;
power <<= 1;
}
c = f(bits);
break;
case 1:
bits = 0;
maxpower = Math.pow(2,16);
power=1;
while (power!=maxpower) {
resb = data.val & data.position;
data.position >>= 1;
if (data.position == 0) {
data.position = 32768;
data.val = data.string.charCodeAt(data.index++);
}
bits |= (resb>0 ? 1 : 0) * power;
power <<= 1;
}
c = f(bits);
break;
case 2:
return "";
}
dictionary[3] = c;
w = result = c;
while (true) {
if (data.index > data.string.length) {
return "";
}
bits = 0;
maxpower = Math.pow(2,numBits);
power=1;
while (power!=maxpower) {
resb = data.val & data.position;
data.position >>= 1;
if (data.position == 0) {
data.position = 32768;
data.val = data.string.charCodeAt(data.index++);
}
bits |= (resb>0 ? 1 : 0) * power;
power <<= 1;
}
switch (c = bits) {
case 0:
bits = 0;
maxpower = Math.pow(2,8);
power=1;
while (power!=maxpower) {
resb = data.val & data.position;
data.position >>= 1;
if (data.position == 0) {
data.position = 32768;
data.val = data.string.charCodeAt(data.index++);
}
bits |= (resb>0 ? 1 : 0) * power;
power <<= 1;
}
dictionary[dictSize++] = f(bits);
c = dictSize-1;
enlargeIn--;
break;
case 1:
bits = 0;
maxpower = Math.pow(2,16);
power=1;
while (power!=maxpower) {
resb = data.val & data.position;
data.position >>= 1;
if (data.position == 0) {
data.position = 32768;
data.val = data.string.charCodeAt(data.index++);
}
bits |= (resb>0 ? 1 : 0) * power;
power <<= 1;
}
dictionary[dictSize++] = f(bits);
c = dictSize-1;
enlargeIn--;
break;
case 2:
return result;
}
if (enlargeIn == 0) {
enlargeIn = Math.pow(2, numBits);
numBits++;
}
if (dictionary[c]) {
entry = dictionary[c];
} else {
if (c === dictSize) {
entry = w + w.charAt(0);
} else {
return null;
}
}
result += entry;
// Add w+entry[0] to the dictionary.
dictionary[dictSize++] = w + entry.charAt(0);
enlargeIn--;
w = entry;
if (enlargeIn == 0) {
enlargeIn = Math.pow(2, numBits);
numBits++;
}
}
}
};
if( typeof module !== 'undefined' && module != null ) {
module.exports = LZString
}

File diff suppressed because one or more lines are too long

View File

@ -16,6 +16,10 @@
"/js/vendor/cm_addons/javascript-hint.js",
"/js/vendor/codemirror4/addon/edit/matchbrackets.js",
"/js/vendor/codemirror4/addon/comment/comment.js",
"/js/vendor/cm_addons/lint/javascript-lint.js",
"/js/vendor/cm_addons/lint/lint.js",
"/js/vendor/lz-string-1.3.3.js",
"/js/vendor/cm_addons/cm-highlight-line.js",
"/js/editors/snippets.cm.js",
"/js/vendor/json2.js",
"/js/vendor/prettyprint.js",
@ -25,13 +29,13 @@
"/js/editors/mobileCodeMirror.js",
"/js/chrome/splitter.js",
"/js/chrome/analytics.js",
"/js/chrome/settings.js",
"/js/chrome/font.js",
"/js/render/title.js",
"/js/render/render.js",
"/js/render/live.js",
"/js/editors/keycontrol.js",
"/js/render/console.js",
"/js/chrome/errors.js",
"/js/processors/processor.js",
"/js/editors/panel.js",
"/js/editors/editors.js",
@ -39,6 +43,7 @@
"/js/editors/library.js",
"/js/editors/addons.js",
"/js/editors/snapshot.js",
"/js/editors/tern.js",
"/js/render/saved-history-preview.js",
"/js/chrome/share.js",
"/js/chrome/esc.js",
@ -53,6 +58,9 @@
"/js/chrome/gist.js",
"/js/chrome/spinner.js",
"/js/chrome/infocard.js",
"/js/chrome/last-bin.js",
"/js/chrome/archive.js",
"/js/chrome/welcome-panel.js",
"/js/chrome/app.js"
],
"runner": [
@ -79,8 +87,7 @@
"/js/vendor/tern/lib/infer.js",
"/js/vendor/tern/plugin/doc_comment.js",
"/js/editors/defs.js",
"/js/editors/definitions.js",
"/js/editors/tern.js"
"/js/editors/definitions.js"
]
}
}
}

View File

@ -35,8 +35,8 @@
<h1>Customise your JS Bin editor</h1>
<section id="content">
<p>As soon as you make any changes, they will be automatically saved.</p>
<p>Tweak JS Bin's editor to your liking and manage the addons loaded.</p>
<form id="editor-settings">
<h3>Settings</h3>
<div>
@ -47,7 +47,7 @@
<label for="theme">Theme</label>
<select id="theme" name="theme">
<option selected>jsbin</option>
<option value="default">CodeMirror default</option>
<option value="default">codemirror</option>
<option>3024-day</option>
<option>3024-night</option>
<option>ambiance</option>
@ -118,6 +118,12 @@
<label for="closebrackets" class="mini-desc">Auto-close brackets and quotes</label>
</div>
<div>
<label for="matchbrackets">Match brackets</label>
<input id="matchbrackets" name="matchbrackets" type="checkbox">
<label for="matchbrackets" class="mini-desc">Highlights matching brackets whenever the cursor is next to them</label>
</div>
<div>
<label for="highlight">Highlight</label>
<input id="highlight" name="highlight" type="checkbox">

View File

@ -3,12 +3,22 @@
<h1>Default Preferences</h1>
<section id="content">
<p>Configure how JS Bin is set up when you create a new bin<!-- , such as your default
HTML content, processors selected, and more -->. <br>
As soon as you make any changes, they will be automatically saved.</p>
<p>Configure how JS Bin is set up when you create a new bin, and how errors are reported.</p>
<form>
<form id="editor-settings">
{{#feature request "pro"}}
<h3>Security</h3>
<div>
<label for="ssl">Always use SSL</label>
<input type="checkbox" name="ssl" id="ssl" {{#feature request "sslForAll"}}checked{{/feature}}>
</div>
<p><small>Note that by enabling SSL for all of JS Bin, including non-https assets in your bins will cause the iframe <em>not</em> to render. This is the correct behaviour for https frames containing non-secure content. <a href="http://jsbin.com/help/ssl">Read more</a>.</small></p>
{{/feature}}
{{#feature request "assets"}}
<h3>Custom content</h3>
<p>If you want to link to images or additional assets, we redirect the <a href="#">/{{user.name}}/assets/</a> relative URL to <em>your asset URL</em>, so you can link to <a href="#">/{{user.name}}/assets/images/logo.png</a> for example.</p>
@ -44,10 +54,65 @@
<input type="checkbox" checked id="includejs">
</div>
<h3>Linting</h3>
<div>
<span class="label">Show errors in</span>
<label><input type="checkbox" name="hintShow-console" id="hintShow-console"> Footer</label>
<label><input type="checkbox" name="hintShow-gutter" id="hintShow-gutter"> Gutter</label>
<label><input type="checkbox" name="hintShow-line" id="hintShow-line"> Intraline</label>
</div>
<div>
<label for="jshint">JSHint</label>
<input type="checkbox" checked id="jshint">
<details class="jshintOptWrapper hintOptWrapper">
<summary>settings</summary>
<div>
<textarea name="jshintOptions" id="jshintOptions" class="hintOptions" cols="40" rows="5" placeholder='{ "//": "your custom JSHint settings" }'></textarea>
<label for="jshintOptions" class="hintOptions"><span class="mini-desc">JSON format, for more info read the <a href="http://jshint.com/docs/options/" target="_blank">documentation</a></span> <span id="jshintOptError" class="mini-desc-error"></span></label>
</div>
</details>
</div>
<div>
<label for="coffeescripthint">CoffeeLint</label>
<input type="checkbox" checked id="coffeescripthint">
<details class="coffeescripthintOptWrapper hintOptWrapper">
<summary>settings</summary>
<div>
<textarea name="coffeescripthintOptions" id="coffeescripthintOptions" class="hintOptions" cols="40" rows="5" placeholder='{ "//": "your custom CoffeeLint settings" }'></textarea>
<label for="coffeescripthintOptions" class="hintOptions"><span class="mini-desc">JSON format, for more info read the <a href="http://www.coffeelint.org/#options" target="_blank">documentation</a></span> <span id="coffeescripthintOptError" class="mini-desc-error"></span></label>
</div>
</details>
</div>
<div>
<label for="csshint">CSSLint</label>
<input type="checkbox" checked id="csshint">
<details class="csshintOptWrapper hintOptWrapper">
<summary>settings</summary>
<div>
<textarea name="csshintOptions" id="csshintOptions" class="hintOptions" cols="40" rows="5" placeholder='{ "//": "your custom CSSLint settings" }'></textarea>
<label for="csshintOptions" class="hintOptions"><span class="mini-desc">JSON format, for more info read the <a href="https://github.com/CSSLint/csslint/wiki/Rules" target="_blank">documentation</a></span> <span id="csshintOptError" class="mini-desc-error"></span></label>
</div>
</details>
</div>
<div>
<label for="htmlhint">HTMLHint</label>
<input type="checkbox" checked id="htmlhint">
<details class="htmlhintOptWrapper hintOptWrapper">
<summary>settings</summary>
<div>
<textarea name="htmlhintOptions" id="htmlhintOptions" class="hintOptions" cols="40" rows="5" placeholder='{ "//": "your custom HTMLHint settings" }'></textarea>
<label for="htmlhintOptions" class="hintOptions"><span class="mini-desc">JSON format, for more info read the <a href="https://github.com/yaniswang/HTMLHint/wiki/Rules" target="_blank">documentation</a></span> <span id="htmlhintOptError" class="mini-desc-error"></span></label>
</div>
</details>
</div>
<input type="hidden" id="_csrf" name="_csrf" value="{{token}}">
</form>
</section>

View File

@ -1,4 +1,8 @@
<!--
Created using {{domain}}
Source can be edited via {{permalink}}
-->
Created using JS Bin
http://{{domain}}
Copyright (c) {{year}} {{#user}}by {{.}} {{/user}}({{permalink}})
Released under the MIT license: http://jsbin.mit-license.org
-->

View File

@ -15,7 +15,17 @@
<!--[if lt IE 7]> <body class="source ie ie6"> <![endif]-->
<!--[if IE 7]> <body class="source ie ie7"> <![endif]-->
<!--[if gt IE 7]> <body class="source ie"> <![endif]-->
<!--[if !IE]><!--> <body class="source {{#if private}}private{{else}}public{{/if}}"> <!--<![endif]-->
<!--[if !IE]><!--> <body class="source {{#if private}}private{{else}}public{{/if}}"> <!--<![endif]-->
{{#unless embed}}
<script>
if(top != self) {
window.location = '{{embedURL}}';
document.write('<!--');
}
</script>
{{#feature request "welcomePanel"}}
{{> welcome_panel}}
{{/feature}}{{/unless}}
<div id="control">
<div class="control">
<div id="menuinfo"><p></p></div>
@ -23,7 +33,7 @@
{{#if embed}}
<span class="menu">
<a target="_blank" href="{{code_id_path}}/edit" class="brand button group"><img src="{{static}}/images/jsbin_16.png">
<h1>JS Bin</h1></a><a href="{{code_id_path}}/save" target="_blank" class="button save">Save</a>
<h1>JS Bin</h1></a><a href="/clone" target="_blank" class="button">Save</a>
</span>
{{else}}
<div class="menu">
@ -42,6 +52,11 @@
<a class="deletebin button group" data-desc="Delete bin" title="Delete this bin" href="{{root}}/delete" data-shortcut="ctrl+shift+del">Delete</a>
<a class="deleteallbins button group" data-desc="Delete all bin snapshots" title="Delete all bin snapshots" href="{{root}}/delete-all">Delete all snapshots</a>
{{/feature}}
<a class="archivebin button group" data-desc="Archive bin" title="Archive this bin" href="{{code_id_path}}/archive" data-shortcut="ctrl+y">Archive</a>
<a class="unarchivebin button group" data-desc="Unrchive bin" title="Restore this bin from the archive" href="{{code_id_path}}/unarchive" data-shortcut="ctrl+shift+y">Unarchive</a>
{{#if home}}
<a href="#" data-desc="Browse your previous created bins" data-shortcut="ctrl+o" class="button group homebtn" data-label="open">My Bins{{#if bincount}} <span class="meta">({{bincount}})</span>{{/if}}</a>
{{/if}}
<hr data-desc="">
<a id="addmeta" data-desc="Insert a description shown in My Bins" title="Add meta data to bin" class="button group" href="#add-description">Add description</a>
<a title="Save snapshot" data-desc="Save current work, and begin new revision on next change" data-shortcut="ctrl+s" class="button save group" data-label="save" href="{{root}}/save">Save snapshot</a>
@ -53,12 +68,9 @@
<a data-desc="Export individual panels to Github's gist{{#unless user.github_token}} as an anonymous user{{/unless}}" class="export-as-gist button group" title="Create a new {{#unless user.github_token}}anonymous {{/unless}}GitHub Gist from this bin" href="#export-to-gist">Export as gist</a>
<a data-desc="Download a complete html file for this bin" id="download" title="Save to local drive" class="button download group" href="{{root}}/download" data-label="download">Download</a>
<a data-desc="Use content from this bin when creating new bins" class="startingpoint button group" title="Set as starting code" href="{{root}}/save" data-label="save-as-template">Save as template</a>
<!-- <a data-desc="Reset to the original JS Bin starting point" id="cleartemplate" title="Reset to the original JS Bin starting template" class="button group" href="#clear-template" data-label="clear-template">Clear template</a> -->
<!-- <a data-desc="How to embed a bin" target="_blank" title="How to embed a bin" data-label="how-to-embed" class="button group" href="http://jsbin.com/help/how-can-i-embed-jsbin">How to embed</a> -->
</div>
</div>
</div><!-- spacer (or not) DO NOT TOUCH ERE BE DRAGONS etc wat?
--><div class="menu">
</div><div class="menu">
<span class="button group">Add library
<select id="library"></select>
</span>
@ -139,7 +151,6 @@
<li><label><input type="checkbox" data-panel="live">Output</label></li>
</ul>
</div>
<!-- <div id="removelock"><input type="button" title="Unlocks the revision that you own so that you can continue to write to this specific bin" class="unlocklockrevision" value="Unlock revision #{{revision}}"> <small>to allow changes.</small></div> -->
<div data-desc="The url to this bin with the JS Bin editor">
<a class="link heading" data-path="/edit" target="_blank" href="{{root}}{{code_id_path}}/edit">Link</a><br><input data-path="/edit" class="link" value="{{root}}{{code_id_path}}/edit" type="text">
</div>
@ -156,7 +167,7 @@
</div>
</div>
</div><!-- intentional space -->
</div>
<div id="start-saving" class="menu">
<a href="{{code_id_path}}/save" class="save button group">Start saving your work</a>
</div>
@ -322,7 +333,7 @@
{{/unless}}
{{#if settings.[ui showblog]}}
<div class="menu">
<div class="menu blog">
<a href="http://jsbin.com/blog" class="button">Blog</a>
</div>
{{/if}}
@ -387,7 +398,6 @@
<div class="dropdownmenu processorSelector" data-type="css">
<a href="#css">CSS</a>
<a href="#less">LESS</a>
<!-- <a href="#stylus">Stylus</a> --><!-- Removed because of a crash bug in stylus https://twitter.com/phuunet/status/377735455670030336 -->
<a href="#convert">Convert to CSS</a>
</div>
</div>
@ -401,6 +411,7 @@
<span class="name"><strong>Console</strong></span>
<span class="options">
<button id="runconsole" title="ctrl + enter">Run</button>
<button id="clearconsole" title="ctrl + l">Clear</button>
</span>
</div>
<div id="console" class="stretch"><ul id="output"></ul><form>
@ -420,10 +431,12 @@ Include alerts, prompts &amp; confirm boxes">Run with JS</button>
</div>
</div>
</div>
<form {{#if embed}}target="_blank"{{/if}} id="saveform" method="post" action="{{code_id_path}}/save">
{{#unless embed}}
<form id="saveform" method="post" action="{{code_id_path}}/save">
<input type="hidden" name="method">
<input type="hidden" name="_csrf" value="{{token}}">
</form>
{{/unless}}
</div>
<div id="tip" class="{{#if flash_tip_type}}{{flash_tip_type}}{{/if}}{{#unless flash_tip_type}} notification{{/unless}}">
<p>
@ -471,10 +484,6 @@ Include alerts, prompts &amp; confirm boxes">Run with JS</button>
<td>ctrl + /</td>
<td>Toggle comment on selected lines</td>
</tr>
<!-- <tr>
<td>ctrl + alt + .</td>
<td>Close current HTML element</td>
</tr> -->
<tr>
<td>ctrl + [</td>
<td>Indents selected lines</td>
@ -485,7 +494,7 @@ Include alerts, prompts &amp; confirm boxes">Run with JS</button>
</tr>
<tr>
<td>tab</td>
<td>Code complete (JavaScript only) &amp; Emmet expand</td>
<td>Code complete &amp; <a href="http://docs.emmet.io/" target="_blank">Emmet</a> expand</td>
</tr>
<tr>
<td>ctrl + s</td>
@ -495,22 +504,11 @@ Include alerts, prompts &amp; confirm boxes">Run with JS</button>
<td>ctrl + shift + s</td>
<td>Open the share options</td>
</tr>
<!-- too confusing, let's throw it away -->
<!--
<tr>
<td>ctrl + \</td>
<td>Hide navigation bar</td>
<td>ctrl + y</td>
<td>Archive Bin</td>
</tr>
<tr>
<td>esc, ctrl + [num]</td>
<td>JS Bin ignores this sequence, and returns control to browser shortcuts</td>
</tr>
<tr>
<td>ctrl + §<br>(or `)</td>
<td>Hide focused panel</td>
</tr>-->
<tr><td colspan="2"><small><br>JS Bin also supports <a href="http://docs.emmet.io/" target="_blank">Emmet/Zen Coding</a> shortcuts</small></td></tr>
<tr><td colspan="2"><small><br><a href="/help/keyboard-shortcuts" target="_blank">Complete list of JS Bin shortcuts</a></small></td></tr>
</tbody>
</table>
</div>
@ -588,19 +586,10 @@ Include alerts, prompts &amp; confirm boxes">Run with JS</button>
</header>
<div class="body">
<ul>
{{#feature request "private"}}<!-- <li>
<select>
<option>Public</option>
<option>Private to me</option>
</option>
</select>
</li> -->
{{/feature}}
<li><a href="/clone">Clone</a> </li>
<li><a class="startingpoint" href="/save-as-template">Save as template</a> </li>
<li><a class="export-as-gist" href="#export-gist">Export gist</a> </li>
<li class="owner"><a href="/download">Download</a> </li>
<!-- <li class="owner"><a href="/archive">Archive</a> </li> -->
{{#feature request "delete"}}
<li class="owner"><a class="deletebin" href="/delete">Delete</a></li>
{{/feature}}
@ -609,27 +598,41 @@ Include alerts, prompts &amp; confirm boxes">Run with JS</button>
</div>
</div>
{{/feature}}
<script>
var template = {{{json_template}}};
var jsbin = {{{jsbin}}}; tips = {{tips}};
</script>
{{#if isProduction}}
<script src="{{static}}/js/vendor/jquery-1.11.0.min.js"></script>
<script src="{{static}}/js/prod/jsbin-{{version}}.min.js"></script>
<!--[if lte IE 8]>
<script src="{{static}}/js/vendor/jshint/jshint.old.min.js"></script>
<![endif]-->
{{#unless embed}}
<script>if(top != self) document.write('<!--');</script>
{{/unless}}
{{#if live}}
<script src="{{static}}/js/vendor/eventsource.js"></script>
<script src="{{static}}/js/spike.js{{cacheBust}}"></script>
{{else}}
{{#if concat}}
<script src="{{static}}/js/vendor/jquery-1.11.0.js"></script>
<script src="{{static}}/js/prod/jsbin-{{version}}.js"></script>
<script src="{{static}}/js/vendor/jquery-1.11.0.min.js"></script>
{{#if isProduction}}
<script src="{{static}}/js/prod/jsbin-{{version}}.min.js"></script>
<script>
start({{{json_template}}}, {{{jsbin}}}, this, document);
</script>
{{else}}
<script src="{{static}}/js/vendor/jquery-1.11.0.js"></script>
{{#scripts}}<script src="{{../static}}{{.}}"></script>{{/scripts}}
<script>
window.template = {{{json_template}}};
window.jsbin = {{{jsbin}}};
</script>
{{#if concat}}
<script src="{{static}}/js/prod/jsbin-{{version}}.js"></script>
{{else}}
{{#scripts}}<script src="{{../static}}{{.}}"></script>{{/scripts}}
{{/if}}
{{#addons}}<script src="{{../static}}{{.}}"></script>{{/addons}}
{{/if}}
{{#addons}}<script src="{{../static}}{{.}}"></script>{{/addons}}
{{/if}}
{{#if live}}
<script src="{{static}}/js/vendor/eventsource.js"></script>
<script src="{{static}}/js/spike.js{{cacheBust}}"></script>
{{/if}}
<script>
</script>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show More