Merge branch 'master' of codeplane.com:seejohnrun/haste-server

Conflicts:
	TODO.md
This commit is contained in:
John Crepezzi 2011-11-28 09:48:28 -05:00
commit f651c30981
19 changed files with 477 additions and 183 deletions

17
TODO.md
View file

@ -1,21 +1,10 @@
# TODO # TODO for OSS
* cache headers for static assets (and on document GET)
* tests * tests
* fix that chrome bug where it loads the doc twice
* expand extension map
* kick expiration back by increment on each view
* Add file extensions ourselves to push state * Add file extensions ourselves to push state
* Proper markdown highlighting
* Better about page text
* Collapse CSS rules to get rid of the #key .box1 nonsense * Collapse CSS rules to get rid of the #key .box1 nonsense
* add feedback for errors to UI - esp. too long
* make sure file store still functions appropriately
# shared version only # shared version only
* some way to do announcements easily (and use for ads) * some way to do announcements easily (and use for ads)
* start using CDNs for most assets (optional) * start using CDNs for most assets (optional)
# with brian's design
* add feedback for errors to UI - esp. too long
* add link to about page
* copy URL to clipboard button

View file

@ -1,10 +1,15 @@
# Haste # Haste
<b>Sharing code is a good thing</b>, and it should be _really_ easy to do it. Sharing code is a good thing, and it should be _really_ easy to do it.
A lot of times, I want to show you something I'm seeing - and that's where we use pastebins. A lot of times, I want to show you something I'm seeing - and that's where we use pastebins.
Haste is the prettiest, easist to use pastebin ever made. Haste is the prettiest, easist to use pastebin ever made.
## Basic Usage
Type what you want me to see, click "Save", and then copy the URL. Send that URL
to someone and they'll see what you see.
## From the Console ## From the Console
Most of the time I want to show you some text, its coming from my current console session. Most of the time I want to show you some text, its coming from my current console session.
@ -21,6 +26,17 @@ been conveniently copied to your clipboard.
That's all there is to that, and you can install it with `gem install haste` right now. That's all there is to that, and you can install it with `gem install haste` right now.
## Duration
Pastes will stay for 30 days from their last view.
## Privacy
While the contents of hastebin.com are not directly crawled by any search robot that
obeys "robots.txt", there should be no great expectation of privacy. Post things at your
own risk. Not responsible for any loss of data or removed pastes.
## Author ## Author
John Crepezzi <john.crepezzi@gmail.com> Code by John Crepezzi <john.crepezzi@gmail.com>
Key Design by Brian Dawson

View file

@ -7,7 +7,9 @@
"maxLength": 400000, "maxLength": 400000,
"cacheStaticAssets": false, "staticMaxAge": 86400,
"recompressStaticAssets": true,
"logging": [ "logging": [
{ {

View file

@ -11,7 +11,7 @@ var DocumentHandler = function(options) {
}; };
// Handle retrieving a document // Handle retrieving a document
DocumentHandler.prototype.handleGet = function(key, response) { DocumentHandler.prototype.handleGet = function(key, response, skipExpire) {
this.store.get(key, function(ret) { this.store.get(key, function(ret) {
if (ret) { if (ret) {
winston.verbose('retrieved document', { key: key }); winston.verbose('retrieved document', { key: key });
@ -23,7 +23,7 @@ DocumentHandler.prototype.handleGet = function(key, response) {
response.writeHead(404, { 'content-type': 'application/json' }); response.writeHead(404, { 'content-type': 'application/json' });
response.end(JSON.stringify({ message: 'document not found' })); response.end(JSON.stringify({ message: 'document not found' }));
} }
}); }, skipExpire);
}; };
// Handle adding a new Document // Handle adding a new Document

View file

@ -9,10 +9,11 @@ var hashlib = require('hashlib');
var FileDocumentStore = function(options) { var FileDocumentStore = function(options) {
this.basePath = options.path || './data'; this.basePath = options.path || './data';
this.expire = options.expire;
}; };
// Save data in a file, key as md5 - since we don't know what we could be passed here // Save data in a file, key as md5 - since we don't know what we could be passed here
FileDocumentStore.prototype.set = function(key, data, callback) { FileDocumentStore.prototype.set = function(key, data, callback, skipExpire) {
try { try {
var _this = this; var _this = this;
fs.mkdir(this.basePath, '700', function() { fs.mkdir(this.basePath, '700', function() {
@ -22,6 +23,9 @@ FileDocumentStore.prototype.set = function(key, data, callback) {
} }
else { else {
callback(true); callback(true);
if (_this.expire && !skipExpire) {
winston.warn('file store cannot set expirations on keys');
}
} }
}); });
}); });
@ -31,13 +35,17 @@ FileDocumentStore.prototype.set = function(key, data, callback) {
}; };
// Get data from a file from key // Get data from a file from key
FileDocumentStore.prototype.get = function(key, callback) { FileDocumentStore.prototype.get = function(key, callback, skipExpire) {
var _this = this;
fs.readFile(this.basePath + '/' + hashlib.md5(key), 'utf8', function(err, data) { fs.readFile(this.basePath + '/' + hashlib.md5(key), 'utf8', function(err, data) {
if (err) { if (err) {
callback(false); callback(false);
} }
else { else {
callback(data); callback(data);
if (_this.expire && !skipExpire) {
winston.warn('file store cannot set expirations on keys');
}
} }
}); });
}; };

View file

@ -53,7 +53,7 @@ RedisDocumentStore.prototype.set = function(key, data, callback, skipExpire) {
RedisDocumentStore.prototype.setExpiration = function(key) { RedisDocumentStore.prototype.setExpiration = function(key) {
if (this.expire) { if (this.expire) {
RedisDocumentStore.client.expire(key, this.expire, function(err, reply) { RedisDocumentStore.client.expire(key, this.expire, function(err, reply) {
if (err || !reply) { if (err) {
winston.error('failed to set expiry on key: ' + key); winston.error('failed to set expiry on key: ' + key);
} }
}); });
@ -61,8 +61,12 @@ RedisDocumentStore.prototype.setExpiration = function(key) {
}; };
// Get a file from a key // Get a file from a key
RedisDocumentStore.prototype.get = function(key, callback) { RedisDocumentStore.prototype.get = function(key, callback, skipExpire) {
var _this = this;
RedisDocumentStore.client.get(key, function(err, reply) { RedisDocumentStore.client.get(key, function(err, reply) {
if (!err && !skipExpire) {
_this.setExpiration(key);
}
callback(err ? false : reply); callback(err ? false : reply);
}); });
}; };

View file

@ -1,102 +0,0 @@
var path = require('path');
var fs = require('fs');
var winston = require('winston');
var DocumentHandler = require('./document_handler');
// For serving static assets
var StaticHandler = function(path, cacheAssets) {
this.basePath = path;
this.defaultPath = '/index.html';
this.cacheAssets = cacheAssets;
// Grab the list of available files - and move into hash for quick lookup
var available = fs.readdirSync(this.basePath);
this.availablePaths = {};
for (var i = 0; i < available.length; i++) {
this.availablePaths['/' + available[i]] = true;
}
};
StaticHandler.cache = {};
// Determine the content type for a given extension
StaticHandler.contentTypeFor = function(ext) {
if (ext == '.js') return 'text/javascript';
else if (ext == '.css') return 'text/css';
else if (ext == '.html') return 'text/html';
else if (ext == '.ico') return 'image/ico';
else if (ext == '.txt') return 'text/plain';
else if (ext == '.png') return 'image/png';
else {
winston.error('unable to determine content type for static asset with extension: ' + ext);
return 'text/plain';
}
};
// Handle a request, and serve back the asset if it exists
StaticHandler.prototype.handle = function(incPath, response) {
// If this is a potential key, show the index - otherwise
// bust out a 404
if (!this.availablePaths[incPath]) {
if (incPath === '/' || DocumentHandler.potentialKey(incPath.substring(1))) {
incPath = this.defaultPath;
}
else {
winston.warn('failed to find static asset', { path: incPath });
response.writeHead(404, { 'content-type': 'application/json' });
response.end(JSON.stringify({ message: 'no such file' }));
return;
}
}
// And then stream the file back - either from the cache or from source
var filePath = this.basePath + (incPath == '/' ? this.defaultPath : incPath);
var cached = this.cacheAssets && this.isCached(filePath);
// Go get'er
var _this = this;
var method = cached ? this.serveCached : this.retrieve;
method(filePath, function(error, content) {
// detect errors
if (error) {
winston.error('unable to read file', { path: filePath, error: error });
response.writeHead(500, { 'content-type': 'application/json' });
response.end(JSON.stringify({ message: 'IO: Unable to read file' }));
// If it was cached, bust the cache
if (cached) {
StaticHandler.cache[filePath] = null;
}
}
// Get the content
else {
var contentType = StaticHandler.contentTypeFor(path.extname(filePath));
response.writeHead(200, { 'content-type': contentType });
response.end(content, 'utf-8');
// Stick it in the cache if its not in there
if (!cached && _this.cacheAssets) {
StaticHandler.cache[filePath] = content;
}
}
});
};
// Retrieve from the file
StaticHandler.prototype.retrieve = function(filePath, callback) {
var _this = this;
winston.verbose('loading static asset', { path: filePath });
fs.readFile(filePath, function(error, content) {
callback(error, content);
});
};
// Retrieve from memory cache
StaticHandler.prototype.serveCached = function(filePath, callback) {
callback(undefined, StaticHandler.cache[filePath]);
};
// Determine if a given filePath is cached or not
StaticHandler.prototype.isCached = function(filePath) {
return !!StaticHandler.cache[filePath];
};
module.exports = StaticHandler;

View file

@ -19,7 +19,9 @@
"dependencies": { "dependencies": {
"winston": "*", "winston": "*",
"hashlib": "*" "hashlib": "*",
"connect": "*",
"uglify-js": "*"
}, },
"devDependencies": { "devDependencies": {

View file

@ -3,8 +3,8 @@ var url = require('url');
var fs = require('fs'); var fs = require('fs');
var winston = require('winston'); var winston = require('winston');
var connect = require('connect');
var StaticHandler = require('./lib/static_handler');
var DocumentHandler = require('./lib/document_handler'); var DocumentHandler = require('./lib/document_handler');
// Load the configuration and set some defaults // Load the configuration and set some defaults
@ -37,6 +37,26 @@ if (!config.storage.type) {
var Store = require('./lib/' + config.storage.type + '_document_store'); var Store = require('./lib/' + config.storage.type + '_document_store');
var preferredStore = new Store(config.storage); var preferredStore = new Store(config.storage);
// Compress the static javascript assets
if (config.recompressStaticAssets) {
var jsp = require("uglify-js").parser;
var pro = require("uglify-js").uglify;
var list = fs.readdirSync('./static');
for (var i = 0; i < list.length; i++) {
var item = list[i];
var orig_code, ast;
if ((item.indexOf('.js') === item.length - 3) && (item.indexOf('.min.js') === -1)) {
dest = item.substring(0, item.length - 3) + '.min' + item.substring(item.length - 3);
orig_code = fs.readFileSync('./static/' + item, 'utf8');
ast = jsp.parse(orig_code);
ast = pro.ast_mangle(ast);
ast = pro.ast_squeeze(ast);
fs.writeFileSync('./static/' + dest, pro.gen_code(ast), 'utf8');
winston.info('compressed ' + item + ' into ' + dest);
}
}
}
// Send the static documents into the preferred store, skipping expirations // Send the static documents into the preferred store, skipping expirations
for (var name in config.documents) { for (var name in config.documents) {
var path = config.documents[name]; var path = config.documents[name];
@ -52,9 +72,6 @@ for (var name in config.documents) {
}); });
} }
// Configure a static handler for the static files
var staticHandler = new StaticHandler('./static', !!config.cacheStaticAssets);
// Configure the document handler // Configure the document handler
var documentHandler = new DocumentHandler({ var documentHandler = new DocumentHandler({
store: preferredStore, store: preferredStore,
@ -62,21 +79,32 @@ var documentHandler = new DocumentHandler({
keyLength: config.keyLength keyLength: config.keyLength
}); });
// Set the server up and listen forever // Set the server up with a static cache
http.createServer(function(request, response) { connect.createServer(
var incoming = url.parse(request.url, false); // First look for api calls
var handler = null; connect.router(function(app) {
// Looking to add a new doc // add documents
if (incoming.pathname.match(/^\/documents$/) && request.method == 'POST') { app.post('/documents', function(request, response, next) {
return documentHandler.handlePost(request, response); return documentHandler.handlePost(request, response);
} });
// Looking up a doc // get documents
var match = incoming.pathname.match(/^\/documents\/([A-Za-z0-9]+)$/); app.get('/documents/:id', function(request, response, next) {
if (request.method == 'GET' && match) { var skipExpire = !!config.documents[request.params.id];
return documentHandler.handleGet(match[1], response); return documentHandler.handleGet(request.params.id, response, skipExpire);
} });
// Otherwise, look for static file }),
staticHandler.handle(incoming.pathname, response); // Otherwise, static
}).listen(config.port, config.host); connect.staticCache(),
connect.static(__dirname + '/static', { maxAge: config.staticMaxAge }),
// Then we can loop back - and everything else should be a token,
// so route it back to /index.html
connect.router(function(app) {
app.get('/:id', function(request, response, next) {
request.url = request.originalUrl = '/index.html';
next();
});
}),
connect.static(__dirname + '/static', { maxAge: config.staticMaxAge })
).listen(config.port, config.host);
winston.info('listening on ' + config.host + ':' + config.port); winston.info('listening on ' + config.host + ':' + config.port);

311
static/ZeroClipboard.js Executable file
View file

@ -0,0 +1,311 @@
// Simple Set Clipboard System
// Author: Joseph Huckaby
var ZeroClipboard = {
version: "1.0.7",
clients: {}, // registered upload clients on page, indexed by id
moviePath: 'ZeroClipboard.swf', // URL to movie
nextId: 1, // ID of next movie
$: function(thingy) {
// simple DOM lookup utility function
if (typeof(thingy) == 'string') thingy = document.getElementById(thingy);
if (!thingy.addClass) {
// extend element with a few useful methods
thingy.hide = function() { this.style.display = 'none'; };
thingy.show = function() { this.style.display = ''; };
thingy.addClass = function(name) { this.removeClass(name); this.className += ' ' + name; };
thingy.removeClass = function(name) {
var classes = this.className.split(/\s+/);
var idx = -1;
for (var k = 0; k < classes.length; k++) {
if (classes[k] == name) { idx = k; k = classes.length; }
}
if (idx > -1) {
classes.splice( idx, 1 );
this.className = classes.join(' ');
}
return this;
};
thingy.hasClass = function(name) {
return !!this.className.match( new RegExp("\\s*" + name + "\\s*") );
};
}
return thingy;
},
setMoviePath: function(path) {
// set path to ZeroClipboard.swf
this.moviePath = path;
},
dispatch: function(id, eventName, args) {
// receive event from flash movie, send to client
var client = this.clients[id];
if (client) {
client.receiveEvent(eventName, args);
}
},
register: function(id, client) {
// register new client to receive events
this.clients[id] = client;
},
getDOMObjectPosition: function(obj, stopObj) {
// get absolute coordinates for dom element
var info = {
left: 0,
top: 0,
width: obj.width ? obj.width : obj.offsetWidth,
height: obj.height ? obj.height : obj.offsetHeight
};
while (obj && (obj != stopObj)) {
info.left += obj.offsetLeft;
info.top += obj.offsetTop;
obj = obj.offsetParent;
}
return info;
},
Client: function(elem) {
// constructor for new simple upload client
this.handlers = {};
// unique ID
this.id = ZeroClipboard.nextId++;
this.movieId = 'ZeroClipboardMovie_' + this.id;
// register client with singleton to receive flash events
ZeroClipboard.register(this.id, this);
// create movie
if (elem) this.glue(elem);
}
};
ZeroClipboard.Client.prototype = {
id: 0, // unique ID for us
ready: false, // whether movie is ready to receive events or not
movie: null, // reference to movie object
clipText: '', // text to copy to clipboard
handCursorEnabled: true, // whether to show hand cursor, or default pointer cursor
cssEffects: true, // enable CSS mouse effects on dom container
handlers: null, // user event handlers
glue: function(elem, appendElem, stylesToAdd) {
// glue to DOM element
// elem can be ID or actual DOM element object
this.domElement = ZeroClipboard.$(elem);
// float just above object, or zIndex 99 if dom element isn't set
var zIndex = 99;
if (this.domElement.style.zIndex) {
zIndex = parseInt(this.domElement.style.zIndex, 10) + 1;
}
if (typeof(appendElem) == 'string') {
appendElem = ZeroClipboard.$(appendElem);
}
else if (typeof(appendElem) == 'undefined') {
appendElem = document.getElementsByTagName('body')[0];
}
// find X/Y position of domElement
var box = ZeroClipboard.getDOMObjectPosition(this.domElement, appendElem);
// create floating DIV above element
this.div = document.createElement('div');
var style = this.div.style;
style.position = 'absolute';
style.left = '' + box.left + 'px';
style.top = '' + box.top + 'px';
style.width = '' + box.width + 'px';
style.height = '' + box.height + 'px';
style.zIndex = zIndex;
if (typeof(stylesToAdd) == 'object') {
for (addedStyle in stylesToAdd) {
style[addedStyle] = stylesToAdd[addedStyle];
}
}
// style.backgroundColor = '#f00'; // debug
appendElem.appendChild(this.div);
this.div.innerHTML = this.getHTML( box.width, box.height );
},
getHTML: function(width, height) {
// return HTML for movie
var html = '';
var flashvars = 'id=' + this.id +
'&width=' + width +
'&height=' + height;
if (navigator.userAgent.match(/MSIE/)) {
// IE gets an OBJECT tag
var protocol = location.href.match(/^https/i) ? 'https://' : 'http://';
html += '<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="'+protocol+'download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,0,0" width="'+width+'" height="'+height+'" id="'+this.movieId+'" align="middle"><param name="allowScriptAccess" value="always" /><param name="allowFullScreen" value="false" /><param name="movie" value="'+ZeroClipboard.moviePath+'" /><param name="loop" value="false" /><param name="menu" value="false" /><param name="quality" value="best" /><param name="bgcolor" value="#ffffff" /><param name="flashvars" value="'+flashvars+'"/><param name="wmode" value="transparent"/></object>';
}
else {
// all other browsers get an EMBED tag
html += '<embed id="'+this.movieId+'" src="'+ZeroClipboard.moviePath+'" loop="false" menu="false" quality="best" bgcolor="#ffffff" width="'+width+'" height="'+height+'" name="'+this.movieId+'" align="middle" allowScriptAccess="always" allowFullScreen="false" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" flashvars="'+flashvars+'" wmode="transparent" />';
}
return html;
},
hide: function() {
// temporarily hide floater offscreen
if (this.div) {
this.div.style.left = '-2000px';
}
},
show: function() {
// show ourselves after a call to hide()
this.reposition();
},
destroy: function() {
// destroy control and floater
if (this.domElement && this.div) {
this.hide();
this.div.innerHTML = '';
var body = document.getElementsByTagName('body')[0];
try { body.removeChild( this.div ); } catch(e) {;}
this.domElement = null;
this.div = null;
}
},
reposition: function(elem) {
// reposition our floating div, optionally to new container
// warning: container CANNOT change size, only position
if (elem) {
this.domElement = ZeroClipboard.$(elem);
if (!this.domElement) this.hide();
}
if (this.domElement && this.div) {
var box = ZeroClipboard.getDOMObjectPosition(this.domElement);
var style = this.div.style;
style.left = '' + box.left + 'px';
style.top = '' + box.top + 'px';
}
},
setText: function(newText) {
// set text to be copied to clipboard
this.clipText = newText;
if (this.ready) this.movie.setText(newText);
},
addEventListener: function(eventName, func) {
// add user event listener for event
// event types: load, queueStart, fileStart, fileComplete, queueComplete, progress, error, cancel
eventName = eventName.toString().toLowerCase().replace(/^on/, '');
if (!this.handlers[eventName]) this.handlers[eventName] = [];
this.handlers[eventName].push(func);
},
setHandCursor: function(enabled) {
// enable hand cursor (true), or default arrow cursor (false)
this.handCursorEnabled = enabled;
if (this.ready) this.movie.setHandCursor(enabled);
},
setCSSEffects: function(enabled) {
// enable or disable CSS effects on DOM container
this.cssEffects = !!enabled;
},
receiveEvent: function(eventName, args) {
// receive event from flash
eventName = eventName.toString().toLowerCase().replace(/^on/, '');
// special behavior for certain events
switch (eventName) {
case 'load':
// movie claims it is ready, but in IE this isn't always the case...
// bug fix: Cannot extend EMBED DOM elements in Firefox, must use traditional function
this.movie = document.getElementById(this.movieId);
if (!this.movie) {
var self = this;
setTimeout( function() { self.receiveEvent('load', null); }, 1 );
return;
}
// firefox on pc needs a "kick" in order to set these in certain cases
if (!this.ready && navigator.userAgent.match(/Firefox/) && navigator.userAgent.match(/Windows/)) {
var self = this;
setTimeout( function() { self.receiveEvent('load', null); }, 100 );
this.ready = true;
return;
}
this.ready = true;
this.movie.setText( this.clipText );
this.movie.setHandCursor( this.handCursorEnabled );
break;
case 'mouseover':
if (this.domElement && this.cssEffects) {
this.domElement.addClass('hover');
if (this.recoverActive) this.domElement.addClass('active');
}
break;
case 'mouseout':
if (this.domElement && this.cssEffects) {
this.recoverActive = false;
if (this.domElement.hasClass('active')) {
this.domElement.removeClass('active');
this.recoverActive = true;
}
this.domElement.removeClass('hover');
}
break;
case 'mousedown':
if (this.domElement && this.cssEffects) {
this.domElement.addClass('active');
}
break;
case 'mouseup':
if (this.domElement && this.cssEffects) {
this.domElement.removeClass('active');
this.recoverActive = false;
}
break;
} // switch eventName
if (this.handlers[eventName]) {
for (var idx = 0, len = this.handlers[eventName].length; idx < len; idx++) {
var func = this.handlers[eventName][idx];
if (typeof(func) == 'function') {
// actual function reference
func(this, args);
}
else if ((typeof(func) == 'object') && (func.length == 2)) {
// PHP style object + method, i.e. [myObject, 'myMethod']
func[0][ func[1] ](this, args);
}
else if (typeof(func) == 'string') {
// name of function
window[func](this, args);
}
} // foreach event handler defined
} // user defined handler for event
}
};

1
static/ZeroClipboard.min.js vendored Normal file

File diff suppressed because one or more lines are too long

BIN
static/ZeroClipboard.swf Executable file

Binary file not shown.

View file

@ -69,6 +69,10 @@ textarea {
position: relative; position: relative;
} }
#key .box2 .link embed {
vertical-align: bottom; /* fix for zeroClipboard style */
}
#key .box2 .function.enabled:hover { #key .box2 .function.enabled:hover {
cursor: hand; cursor: hand;
cursor: pointer; cursor: pointer;

View file

@ -14,11 +14,16 @@ haste_document.prototype.load = function(key, callback, lang) {
_this.locked = true; _this.locked = true;
_this.key = key; _this.key = key;
_this.data = res.data; _this.data = res.data;
try {
var high = lang ? hljs.highlight(lang, res.data) : hljs.highlightAuto(res.data); var high = lang ? hljs.highlight(lang, res.data) : hljs.highlightAuto(res.data);
} catch(err) {
// failed highlight, fall back on auto
high = hljs.highlightAuto(res.data);
}
callback({ callback({
value: high.value, value: high.value,
key: key, key: key,
language: lang || high.language language: high.language || lang
}); });
}, },
error: function(err) { error: function(err) {
@ -77,11 +82,13 @@ haste.prototype.setTitle = function(ext) {
// Show the light key // Show the light key
haste.prototype.lightKey = function() { haste.prototype.lightKey = function() {
this.configureKey(['new', 'save']); this.configureKey(['new', 'save']);
this.removeClip();
}; };
// Show the full key // Show the full key
haste.prototype.fullKey = function() { haste.prototype.fullKey = function() {
this.configureKey(['new', 'duplicate', 'twitter', 'link']); this.configureKey(['new', 'duplicate', 'twitter', 'link']);
this.configureClip();
}; };
// Set the key up for certain things to be enabled // Set the key up for certain things to be enabled
@ -116,8 +123,12 @@ haste.prototype.newDocument = function(hideHistory) {
// Map of common extensions // Map of common extensions
haste.extensionMap = { haste.extensionMap = {
'rb': 'ruby', rb: 'ruby', py: 'python', pl: 'perl', php: 'php', scala: 'scala', go: 'go',
'py': 'python' xml: 'xml', html: 'xml', htm: 'xml', css: 'css', js: 'javascript', vbs: 'vbscript',
lua: 'lua', pas: 'delphi', java: 'java', cpp: 'cpp', cc: 'cpp', m: 'objectivec',
vala: 'vala', cs: 'cs', sql: 'sql', sm: 'smalltalk', lisp: 'lisp', ini: 'ini',
diff: 'diff', bash: 'bash', sh: 'bash', tex: 'tex', erl: 'erlang', hs: 'haskell',
md: 'markdown'
}; };
// Map an extension to a language // Map an extension to a language
@ -171,14 +182,32 @@ haste.prototype.lockDocument = function() {
title += ' - ' + ret.language; title += ' - ' + ret.language;
} }
_this.setTitle(title); _this.setTitle(title);
_this.fullKey();
window.history.pushState(null, _this.appName + '-' + ret.key, '/' + ret.key); window.history.pushState(null, _this.appName + '-' + ret.key, '/' + ret.key);
_this.fullKey();
_this.$textarea.val('').hide(); _this.$textarea.val('').hide();
_this.$box.show().focus(); _this.$box.show().focus();
} }
}); });
}; };
// set up a clip that will copy the current url
haste.prototype.configureClip = function() {
var clip = this.clipper = new ZeroClipboard.Client();
this.clipper.setHandCursor(true);
this.clipper.setCSSEffects(false);
// and then set and show
this.clipper.setText(window.location.href);
$('#key .box2 .link').html(this.clipper.getHTML(32, 37));
};
// hide the current clip, if it exists
haste.prototype.removeClip = function() {
if (this.clipper) {
this.clipper.destroy();
}
$('#key .box2 .link').html('');
};
haste.prototype.configureButtons = function() { haste.prototype.configureButtons = function() {
var _this = this; var _this = this;
this.buttons = [ this.buttons = [
@ -231,9 +260,8 @@ haste.prototype.configureButtons = function() {
{ {
$where: $('#key .box2 .link'), $where: $('#key .box2 .link'),
label: 'Copy URL', label: 'Copy URL',
action: function() { letBubble: true,
alert('not yet implemented'); action: function() { }
}
} }
]; ];
for (var i = 0; i < this.buttons.length; i++) { for (var i = 0; i < this.buttons.length; i++) {
@ -271,7 +299,9 @@ haste.prototype.configureShortcuts = function() {
for (var i = 0 ; i < _this.buttons.length; i++) { for (var i = 0 ; i < _this.buttons.length; i++) {
button = _this.buttons[i]; button = _this.buttons[i];
if (button.shortcut && button.shortcut(evt)) { if (button.shortcut && button.shortcut(evt)) {
if (!button.letBubble) {
evt.preventDefault(); evt.preventDefault();
}
button.action(); button.action();
return; return;
} }

1
static/application.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -2,14 +2,15 @@
<head> <head>
<title>haste</title> <title>hastebin</title>
<link rel="stylesheet" type="text/css" href="solarized_dark.css"/> <link rel="stylesheet" type="text/css" href="solarized_dark.css"/>
<link rel="stylesheet" type="text/css" href="application.css"/> <link rel="stylesheet" type="text/css" href="application.css"/>
<script type="text/javascript" src="jquery-1.7.min.js"></script> <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js"></script>
<script type="text/javascript" src="highlight.min.js"></script> <script type="text/javascript" src="highlight.min.js"></script>
<script type="text/javascript" src="application.js"></script> <script type="text/javascript" src="application.min.js"></script>
<script type="text/javascript" src="ZeroClipboard.min.js"></script>
<meta name="robots" content="noindex,nofollow"/> <meta name="robots" content="noindex,nofollow"/>
@ -18,24 +19,20 @@
// Handle pops // Handle pops
var handlePop = function(evt) { var handlePop = function(evt) {
var path = evt.target.location.pathname; var path = evt.target.location.pathname;
if (path === '/') { if (path === '/') { app.newDocument(true); }
app.newDocument(true); else { app.loadDocument(path.substring(1, path.length)); }
}
else {
app.loadDocument(path.substring(1, path.length));
}
}; };
// If pop before loading jquery, delay load // Set up the pop state to handle loads, skipping the first load
// to make chrome behave like others:
// http://code.google.com/p/chromium/issues/detail?id=63040
setTimeout(function() {
window.onpopstate = function(evt) { window.onpopstate = function(evt) {
try { try { handlePop(evt); } catch(err) { /* not loaded yet */ }
handlePop(evt);
} catch(err) {
// not loaded yet
}
}; };
// Construct app and load if not loaded }, 1000);
// Construct app and load initial path
$(function() { $(function() {
app = new haste('haste', { twitter: true }); app = new haste('hastebin', { twitter: true });
handlePop({ target: window }); handlePop({ target: window });
}); });
</script> </script>
@ -47,14 +44,14 @@
<div id="key"> <div id="key">
<div id="pointer" style="display:none;"></div> <div id="pointer" style="display:none;"></div>
<div class="box1"> <div class="box1">
<a href="/about" class="logo"></a> <a href="/about.md" class="logo"></a>
</div> </div>
<div class="box2"> <div class="box2">
<a href="#" class="save function"></a> <div class="save function"></div>
<a href="#" class="new function"></a> <div class="new function"></div>
<a href="#" class="duplicate function"></a> <div class="duplicate function"></div>
<a href="#" class="link function"></a> <div class="link function"></div>
<a href="#" class="twitter function"></a> <div class="twitter function"></div>
</div> </div>
<div class="box3" style="display:none;"> <div class="box3" style="display:none;">
<div class="label"></div> <div class="label"></div>

View file

@ -1,2 +1,3 @@
User-agent: * User-agent: *
Allow: /about
Disallow: / Disallow: /

View file

@ -51,7 +51,8 @@ pre .built_in,
pre .lisp .title, pre .lisp .title,
pre .identifier, pre .identifier,
pre .title .keymethods, pre .title .keymethods,
pre .id { pre .id,
pre .header {
color: #268bd2; color: #268bd2;
} }
@ -91,6 +92,7 @@ pre .deletion {
color: #dc322f; color: #dc322f;
} }
pre .tex .formula { pre .tex .formula,
pre .code {
background: #073642; background: #073642;
} }