///// represents a single document var haste_document = function() { this.locked = false; }; // Escapes HTML tag characters haste_document.prototype.htmlEscape = function(s) { return s .replace(/&/g, '&') .replace(/>/g, '>') .replace(/'+msg+''); $('#messages').prepend(msgBox); setTimeout(function() { msgBox.slideUp('fast', function() { $(this).remove(); }); }, 3000); }; // Show the light key haste.prototype.lightKey = function() { this.configureKey(['new', 'save']); }; // Show the full key haste.prototype.fullKey = function() { this.configureKey(['new', 'duplicate', 'twitter', 'raw']); }; // Set the key up for certain things to be enabled haste.prototype.configureKey = function(enable) { var $this, i = 0; $('#box2 .function').each(function() { $this = $(this); for (i = 0; i < enable.length; i++) { if ($this.hasClass(enable[i])) { $this.addClass('enabled'); return true; } } $this.removeClass('enabled'); }); }; // Remove the current document (if there is one) // and set up for a new one haste.prototype.newDocument = function(hideHistory) { this.$box.hide(); this.doc = new haste_document(); if (!hideHistory) { window.history.pushState(null, this.appName, '/'); } this.setTitle(); this.lightKey(); this.$textarea.val('').show('fast', function() { this.focus(); }); this.removeLineNumbers(); }; // Map of common extensions // Note: this list does not need to include anything that IS its extension, // due to the behavior of lookupTypeByExtension and lookupExtensionByType // Note: optimized for lookupTypeByExtension haste.extensionMap = { rb: 'ruby', py: 'python', pl: 'perl', php: 'php', scala: 'scala', go: 'go', 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', sql: 'sql', sm: 'smalltalk', lisp: 'lisp', ini: 'ini', diff: 'diff', bash: 'bash', sh: 'bash', tex: 'tex', erl: 'erlang', hs: 'haskell', md: 'markdown', txt: '', coffee: 'coffee', json: 'javascript', swift: 'swift' }; // Look up the extension preferred for a type // If not found, return the type itself - which we'll place as the extension haste.prototype.lookupExtensionByType = function(type) { for (var key in haste.extensionMap) { if (haste.extensionMap[key] === type) return key; } return type; }; // Look up the type for a given extension // If not found, return the extension - which we'll attempt to use as the type haste.prototype.lookupTypeByExtension = function(ext) { return haste.extensionMap[ext] || ext; }; // Add line numbers to the document // For the specified number of lines haste.prototype.addLineNumbers = function(lineCount) { var h = ''; for (var i = 0; i < lineCount; i++) { h += (i + 1).toString() + '
'; } $('#linenos').html(h); }; // Remove the line numbers haste.prototype.removeLineNumbers = function() { $('#linenos').html('>'); }; // Load a document and show it haste.prototype.loadDocument = function(key) { // Split the key up var parts = key.split('.', 2); // Ask for what we want var _this = this; _this.doc = new haste_document(); _this.doc.load(parts[0], function(ret) { if (ret) { _this.$code.html(ret.value); _this.setTitle(ret.key); _this.fullKey(); _this.$textarea.val('').hide(); _this.$box.show().focus(); _this.addLineNumbers(ret.lineCount); } else { _this.newDocument(); } }, this.lookupTypeByExtension(parts[1])); }; // Duplicate the current document - only if locked haste.prototype.duplicateDocument = function() { if (this.doc.locked) { var currentData = this.doc.data; this.newDocument(); this.$textarea.val(currentData); } }; // Lock the current document haste.prototype.lockDocument = function() { var _this = this; this.doc.save(this.$textarea.val(), function(err, ret) { if (err) { _this.showMessage(err.message, 'error'); } else if (ret) { _this.$code.html(ret.value); _this.setTitle(ret.key); var file = '/' + ret.key; if (ret.language) { file += '.' + _this.lookupExtensionByType(ret.language); } window.history.pushState(null, _this.appName + '-' + ret.key, file); _this.fullKey(); _this.$textarea.val('').hide(); _this.$box.show().focus(); _this.addLineNumbers(ret.lineCount); } }); }; haste.prototype.configureButtons = function() { var _this = this; this.buttons = [ { $where: $('#box2 .save'), label: 'Save', shortcutDescription: 'control + alt + s', shortcut: function(evt) { return evt.ctrlKey && evt.altKey && (evt.keyCode === 83); }, action: function() { if (_this.$textarea.val().replace(/^\s+|\s+$/g, '') !== '') { _this.lockDocument(); } } }, { $where: $('#box2 .new'), label: 'New', shortcut: function(evt) { return evt.ctrlKey && evt.altKey && evt.keyCode === 78 }, shortcutDescription: 'control + atl + n', action: function() { _this.newDocument(!_this.doc.key); } }, { $where: $('#box2 .duplicate'), label: 'Duplicate & Edit', shortcut: function(evt) { return _this.doc.locked && evt.ctrlKey && evt.altKey && evt.keyCode === 68; }, shortcutDescription: 'control + alt + d', action: function() { _this.duplicateDocument(); } }, { $where: $('#box2 .raw'), label: 'Just Text', shortcut: function(evt) { return evt.ctrlKey && evt.shiftKey && evt.keyCode === 82; }, shortcutDescription: 'control + shift + r', action: function() { window.location.href = '/raw/' + _this.doc.key; } }, { $where: $('#box2 .twitter'), label: 'Twitter', shortcut: function(evt) { return _this.options.twitter && _this.doc.locked && evt.shiftKey && evt.ctrlKey && evt.keyCode == 84; }, shortcutDescription: 'control + shift + t', action: function() { window.open('https://twitter.com/share?url=' + encodeURI(window.location.href)); } } ]; for (var i = 0; i < this.buttons.length; i++) { this.configureButton(this.buttons[i]); } }; haste.prototype.configureButton = function(options) { // Handle the click action options.$where.click(function(evt) { evt.preventDefault(); if (!options.clickDisabled && $(this).hasClass('enabled')) { options.action(); } }); // Show the label options.$where.mouseenter(function(evt) { $('#box3 .label').text(options.label); $('#box3 .shortcut').text(options.shortcutDescription || ''); $('#box3').show(); $(this).append($('#pointer').remove().show()); }); // Hide the label options.$where.mouseleave(function(evt) { $('#box3').hide(); $('#pointer').hide(); }); }; // Configure keyboard shortcuts for the textarea haste.prototype.configureShortcuts = function() { var _this = this; $(document.body).keydown(function(evt) { var button; for (var i = 0 ; i < _this.buttons.length; i++) { button = _this.buttons[i]; if (button.shortcut && button.shortcut(evt)) { evt.preventDefault(); button.action(); return; } } }); }; ///// Tab behavior in the textarea - 2 spaces per tab $(function() { $('textarea').keydown(function(evt) { if (evt.keyCode === 9) { evt.preventDefault(); var myValue = ' '; // http://stackoverflow.com/questions/946534/insert-text-into-textarea-with-jquery // For browsers like Internet Explorer if (document.selection) { this.focus(); sel = document.selection.createRange(); sel.text = myValue; this.focus(); } // Mozilla and Webkit else if (this.selectionStart || this.selectionStart == '0') { var startPos = this.selectionStart; var endPos = this.selectionEnd; var scrollTop = this.scrollTop; this.value = this.value.substring(0, startPos) + myValue + this.value.substring(endPos,this.value.length); this.focus(); this.selectionStart = startPos + myValue.length; this.selectionEnd = startPos + myValue.length; this.scrollTop = scrollTop; } else { this.value += myValue; this.focus(); } } }); });