const Haste = function() { this.appName = "unknownBIN"; this.$textarea = $('textarea'); this.$box = $('#code'); this.$code = $('#code code'); this.configureShortcuts(); this.configureButtons(); }; // set title (browser window) Haste.prototype.setTitle = function(ext) { const title = ext ? `${this.appName} - ${ext}` : this.appName; document.title = title; }; // show the light key Haste.prototype.lightKey = function() { this.configureKey(['new', 'save']); }; // show the full key Haste.prototype.fullKey = function() { this.configureKey(['new', 'duplicate', 'raw']); }; // enable certain keys Haste.prototype.configureKey = function(enable) { $('#tools .function').each(function() { const $this = $(this); for (let i = 0; i < enable.length; i++) { if ($this.hasClass(enable[i])) { $this.addClass('enabled'); return true; } } $this.removeClass('enabled'); }); }; // setup a new, blank document Haste.prototype.newDocument = function(hideHistory) { this.$box.hide(); this.doc = new HasteDocument(); if (!hideHistory) { window.history.pushState(null, this.appName, '/'); } this.setTitle(); this.lightKey(); this.$textarea.val('').show('fast', function() { this.focus(); }); }; // load an existing document Haste.prototype.loadDocument = function(key) { // Check if data is pre-rendered by the server const $preloaded = $('#preloaded-data'); if ($preloaded.length > 0 && $preloaded.text().trim().length > 0) { this.doc = new HasteDocument(); this.doc.locked = true; this.doc.key = key; this.doc.data = $preloaded.text(); const high = hljs.highlightAuto(this.doc.data).value; const ret = { value: high.replace(/.+/g, "$&").replace(/^\s*[\r\n]/gm, "\n"), key: key, }; this.$code.html(ret.value); this.setTitle(ret.key); this.fullKey(); this.$textarea.val('').hide(); this.$box.show(); } else { // Data is not pre-rendered, fetch from API const _this = this; _this.doc = new HasteDocument(); _this.doc.load(key, function(ret) { if (ret) { _this.$code.html(ret.value); _this.setTitle(ret.key); window.history.pushState(null, `${_this.appName}-${ret.key}`, `/${ret.key}`); _this.fullKey(); _this.$textarea.val('').hide(); _this.$box.show(); } else { _this.newDocument(); } }); } }; // duplicate the current document Haste.prototype.duplicateDocument = function() { if (this.doc.locked) { const currentData = this.doc.data; this.newDocument(); this.$textarea.val(currentData); } }; // lock the current document Haste.prototype.lockDocument = function() { const _this = this; this.doc.save(this.$textarea.val(), function(err, ret) { if (!err && ret) { // Manually highlight and display after saving const high = hljs.highlightAuto(ret.data).value; const formatted = high.replace(/.+/g, "$&").replace(/^\s*[\r\n]/gm, "\n"); _this.$code.html(formatted); _this.setTitle(ret.key); window.history.pushState(null, `${_this.appName}-${ret.key}`, `/${ret.key}`); _this.fullKey(); _this.$textarea.val('').hide(); _this.$box.show(); } }); }; // configure buttons and their shortcuts Haste.prototype.configureButtons = function() { const _this = this; this.buttons = [ { $where: $('#tools .save'), shortcut: function(evt) { return evt.ctrlKey && evt.keyCode === 83; }, action: function() { if (_this.$textarea.val().replace(/^\s+|\s+$/g, '') !== '') { _this.lockDocument(); } } }, { $where: $('#tools .new'), shortcut: function(evt) { return evt.ctrlKey && evt.keyCode === 32; }, action: function() { window.location.href = '/'; } }, { $where: $('#tools .duplicate'), shortcut: function(evt) { return _this.doc.locked && evt.ctrlKey && evt.keyCode === 68; }, action: function() { _this.duplicateDocument(); } }, { $where: $('#tools .raw'), shortcut: function(evt) { return evt.ctrlKey && evt.shiftKey && evt.keyCode === 82; }, action: function() { window.location.href = `/raw/${_this.doc.key}`; } } ]; for (let i = 0; i < this.buttons.length; i++) { this.configureButton(this.buttons[i]); } }; // handles the button-click Haste.prototype.configureButton = function(options) { options.$where.click(function(evt) { evt.preventDefault(); if (!options.clickDisabled && $(this).hasClass('enabled')) { options.action(); } }); }; // enables the configured shortcuts Haste.prototype.configureShortcuts = function() { const _this = this; $(document.body).keydown(function(evt) { let button; for (let i = 0; i < _this.buttons.length; i++) { button = _this.buttons[i]; if (button.shortcut && button.shortcut(evt)) { evt.preventDefault(); button.action(); return; } } }); }; // represents a single document const HasteDocument = function() { this.locked = false; }; // load a document from the server HasteDocument.prototype.load = function(key, callback) { const _this = this; $.ajax(`/documents/${key}`, { type: 'get', dataType: 'json', success: function(res) { _this.locked = true; _this.key = key; _this.data = res.data; const high = hljs.highlightAuto(res.data).value; callback({ value: high.replace(/.+/g, "$&").replace(/^\s*[\r\n]/gm, "\n"), key: key, }); }, error: function() { callback(false); } }); }; // sends the document to the server HasteDocument.prototype.save = function(data, callback) { if (this.locked) return false; const _this = this; _this.data = data; $.ajax('/documents', { type: 'post', data: data, dataType: 'json', contentType: 'text/plain; charset=utf-8', success: function(res) { _this.locked = true; _this.key = res.key; // Pass the saved data back to the callback callback(null, { key: res.key, data: _this.data }); }, error: function(res) { try { callback($.parseJSON(res.responseText)); } catch (e) { callback({message: 'Something went wrong!'}); } } }); }; // after page is loaded $(function() { $('textarea').keydown(function(evt) { // allow usage of tabs if (evt.keyCode === 9) { evt.preventDefault(); const myValue = ' '; if (document.selection) { this.focus(); let sel = document.selection.createRange(); sel.text = myValue; this.focus(); } else if (this.selectionStart || this.selectionStart == '0') { const startPos = this.selectionStart; const endPos = this.selectionEnd; const 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(); } } }); const app = new Haste(); const path = window.location.pathname; if (path === '/' || path === '') { app.newDocument(true); } else { app.loadDocument(path.substring(1)); } }); // No need for hljs.initHighlightingOnLoad() as we do it manually