--- /dev/null
+// TODO: refactor
+// TODO: This code is now legacy code, none of it will make its way into Beta
+window.editorsModified = false;
+
+Request.JSON.implement({
+ initialize: function(options) {
+ this.parent(options);
+ this.setHeader('X-CSRFToken', Cookie.read('csrftoken'));
+ }
+});
+
+Track = {
+ ui: function(action){
+ if (typeof(_gaq) !== 'undefined'){
+ _gaq.push(['_trackEvent', 'UI', action || null]);
+ }
+ }
+};
+
+/*
+ * Define actions on the run/save/clean buttons
+ */
+var MooShellActions = new Class({
+ Implements: [Options, Events],
+ options: {
+ // onRun: $empty,
+ // onClean: $empty,
+ formId: 'show-result',
+ saveAndReloadId: 'update',
+ saveAsNewId: 'savenew',
+ runId: 'run',
+ draftId: 'mobile',
+ jslintId: 'jslint',
+ tidyId: 'tidy',
+ showJsId: 'showjscode',
+ shareSelector: '#share_link_dropdown, #share_embedded_dropdown, #share_result_dropdown',
+ favId: 'mark_favourite',
+ entriesSelector: 'textarea',
+ resultLabel: 'result_label',
+ resultInput: 'select_link',
+ collaborateId: 'collaborate',
+ example_id: false,
+ exampleURL: '',
+ exampleSaveURL: '',
+ loadDependenciesURL: '',
+ tidy: {
+ 'javascript': 'js',
+ 'javascript 1.7': 'js',
+ 'html': 'html',
+ 'css': 'css'
+ },
+ jslint: {
+ evil: true,
+ passfail: false,
+ browser: true,
+ newcap: false
+ },
+ jslintLanguages: ['text/javascript', 'javascript', 'javascript 1.7'],
+ showJSLanguages: ['text/coffeescript', 'coffeescript']
+ },
+ /*
+ * Assign actions
+ */
+ initialize: function(options) {
+ this.setOptions(options);
+ var addBound = function(el, callback, bind) {
+ el = $(el)[0]; // jordan added [0] for jquery
+ if (el){
+ el.addEvent('click', callback.bind(bind));
+ }
+ };
+
+ Layout.addEvent('ready', function() {
+ addBound(this.options.saveAndReloadId, this.saveAndReload, this);
+ addBound(this.options.saveAsNewId, this.saveAsNew, this);
+ addBound(this.options.runId, this.run, this);
+ addBound(this.options.draftId, this.run, this);
+ addBound(this.options.jslintId, this.jsLint, this);
+ addBound(this.options.showJsId, this.showJs, this);
+ addBound(this.options.tidyId, this.prepareAndLaunchTidy, this);
+ addBound(this.options.favId, this.makeFavourite, this);
+ // addBound(this.options.collaborateId, this.launchTowtruck, this);
+
+ // show key shortcuts
+ var keyActions = document.getElements('a.keyActions');
+ keyActions.addEvents({
+ click: function(event){
+ this.showShortcutDialog(event);
+ }.bind(this)
+ });
+ document.id(document.body).addEvents({
+ keydown: function(event){
+ if (event.shift && event.key === '/'){
+ var elType = new Element(event.target);
+ if (elType.get('tag') === 'body'){
+ keyActions[0].fireEvent('click');
+ }
+ }
+ }
+ });
+
+ var share = $$(this.options.shareSelector);
+ if (share.length > 0) {
+ share.addEvent('click', function() {
+ this.select();
+ });
+ }
+
+ // actions run if shell loaded
+ this.form = document.id(this.options.formId);
+
+ if (this.options.exampleURL) {
+ // this.run();
+ this.displayExampleURL();
+ }
+ // assign change language in panel
+ $$('.panelChoice').addEvent('change', this.switchLanguageAction.bind(this));
+ this.showHideJsLint();
+ this.showHideTidyUp();
+ this.showHideShowJs();
+ }.bind(this));
+ },
+
+ switchLanguageAction: function(e) {
+ if (!e) return;
+ var sel = $(e.target);
+ this.switchLanguage(sel);
+ },
+
+ /*
+ * Change language in panel
+ */
+ switchLanguage: function(sel) {
+ var panel_name = sel.get('data-panel'),
+ editorClass = Layout.editors[panel_name],
+ // Klass = MooShellEditor[panel_name.toUpperCase()],
+ language = sel.getElement('option:selected').get('text'),
+ type = sel.getElement('option:selected').get('data-mime-type');
+
+ editorClass.editor.setOption('mode', type);
+ // editorClass.updateCode();
+
+ // editor.getWindow().getElement('.CodeMirror-wrapping').destroy();
+ // Layout.editors[panel_name] = editor = false;
+ // new Klass($(sel.get('data-panel_id')), {
+ // language: language.toLowerCase()
+ // });
+
+ Layout.editors[panel_name].setLabelName(language);
+ window['panel_' + panel_name] = language.toLowerCase();
+ this.showHideTidyUp();
+ this.showHideJsLint();
+ this.showHideShowJs();
+
+ Track.ui('Switch language: ' + language);
+ },
+
+ loadAsset: function(check, file, callback, scope) {
+ if (!check()) {
+ if (scope){
+ callback = callback.bind(scope);
+ }
+
+ Asset.javascript(file, {
+ onload: callback
+ });
+ return true;
+ }
+ },
+
+ prepareCoffee: function(callback) {
+ return this.loadAsset(function() {
+ return $defined(window.CoffeeScript);
+ }, '/js/coffeescript/coffeescript.js', callback, this);
+ },
+
+ showJs: function(e) {
+ if (e) e.stop();
+ if (this.prepareCoffee(this.showJs, this)) return;
+ var html = '<div class="modalWrap modal_Coffee">' +
+ '<div class="modalHeading"><h3>JavaScript Code</h3><span class="close">Close window</span></div>'+
+ '<div id="" class="modalBody">',
+ jscode, coffeecode;
+
+ if (panel_js != 'coffeescript') return;
+ coffeecode = Layout.editors.js.editor.getValue();
+ try {
+ jscode = CoffeeScript.compile(coffeecode);
+ html += '<pre>' + jscode + '</pre>';
+ } catch(error) {
+ html += '<p class="error">' + error + '</p>';
+ }
+ html += '</div></div>';
+ new StickyWin({
+ content: html,
+ relativeTo: $(document.body),
+ position: 'center',
+ edge: 'center',
+ closeClassName: 'close',
+ draggable: true,
+ dragHandleSelector: 'h3',
+ closeOnEsc: true,
+ destroyOnClose: true,
+ allowMultiple: false,
+ onDisplay: this.showModalFx
+ }).show();
+
+ Track.ui('Show JS');
+ },
+
+ prepareTidyUp: function(callback, scope) {
+ //return this.loadAsset(function() {
+ // return $defined(window.Beautifier);
+ //}, '/js/beautifier.js', callback, scope);
+ },
+
+ showHideShowJs: function() {
+ var show = false,
+ showjs = $(this.options.showJsId)[0]; // jordan
+
+ if (showjs){
+ show = (Layout.editors.js.editor.getOption('mode').contains('coffeescript'));
+ if (show) {
+ showjs.getParent('li').show();
+ } else {
+ showjs.getParent('li').hide();
+ }
+ }
+ },
+
+ showHideJsLint: function() {
+
+ var hide = true,
+ lint = $(this.options.jslintId)[0]; // jordan added [0] for jquery
+
+ if (!lint) return;
+ Layout.editors.each(function(w){
+ if (this.options.jslintLanguages.contains(w.editor.getOption('mode'))) {
+ hide = false;
+ }
+ }, this);
+ if (hide) {
+ lint.getParent('li').hide();
+ } else {
+ lint.getParent('li').show();
+ }
+ },
+
+ showHideTidyUp: function() {
+/*
+ if (this.prepareTidyUp(this.showHideTidyUp, this)) return;
+ var hide = true,
+ tidy = $(this.options.tidyId);
+
+ if (!tidy) return;
+ Layout.editors.each(function(w){
+ language = this.options.tidy[w.options.language];
+ if (language && Beautifier[language]) {
+ hide = false;
+ }
+ }, this);
+ if (hide) {
+ tidy.getParent('li').hide();
+ } else {
+ tidy.getParent('li').show();
+ }
+*/
+ },
+
+ prepareAndLaunchTidy: function(e) {
+ e.stop();
+ if (this.prepareTidyUp(this.makeTidy.bind(this))) return;
+ this.makeTidy();
+ },
+
+ makeTidy: function(){
+/*
+ Layout.editors.each(function(editorInstance){
+ var code = editorInstance.editor.getValue(), language;
+ if (code) {
+ language = this.options.tidy[editorInstance.options.language];
+ if (language && Beautifier[language]) {
+ var fixed = Beautifier[language](code);
+ if (fixed) editorInstance.editor.setValue(fixed);
+ else editorInstance.editor.reindent();
+ }
+ }
+ }, this);
+*/
+ },
+
+ jsLint: function(e) {
+ e.stop();
+ if (JSHINT){
+ return this.JSLintValidate();
+ Track.ui('Validate JavaScript');
+ }
+ // if (!window.JSLINT) {
+ // // never happens as apparently JSLINT needs to be loaded before MooTools
+ // Asset.javascript('/js/jslint.min.js', {
+ // onload: this.JSLintValidate.bind(this)
+ // });
+ // } else {
+ // return this.JSLintValidate();
+ // }
+ },
+
+ JSLintValidate: function() {
+ var editor = Layout.editors.js.editor;
+ var html = '<div class="modalWrap modal_jslint">' +
+ '<div class="modalHeading"><h3>JSLint {title}</h3><span class="close">Close window</span></div>'+
+ '<div class="modalBody">{content}</div></div>';
+ var sticky = function(subs){
+ return new StickyWin({
+ content: html.substitute(subs),
+ relativeTo: $(document.body),
+ position: 'center',
+ edge: 'center',
+ closeClassName: 'close',
+ draggable: true,
+ dragHandleSelector: 'h3',
+ closeOnEsc: true,
+ destroyOnClose: true,
+ allowMultiple: false,
+ onDisplay: this.showModalFx
+ }).show();
+ };
+
+ if (this.options.jslintLanguages.contains(panel_js)){
+ var editorValue = editor.getValue();
+
+ // clear all markers from the gutter, we'll highlight errors again in the next step
+ for (var line = 0; editor.lineCount() >= line; line++){
+ editor.setGutterMarker(line, 'note-gutter', null);
+ }
+
+ if (editorValue.trim() === ''){
+ sticky.call(this, {
+ title: 'Valid!',
+ content: '<p>Good work! Your JavaScript code is perfectly valid.</p>'
+ });
+ } else {
+ if (JSHINT(editorValue)){
+ sticky.call(this, {
+ title: 'Valid!',
+ content: '<p>Good work! Your JavaScript code is perfectly valid.</p>'
+ });
+ } else {
+ Array.each(JSHINT.errors, function(error, index){
+ errorEl = Element('span', {
+ 'class': 'CodeMirror-line-error',
+ 'data-title': error.reason,
+ 'text': '●'
+ });
+ editor.setGutterMarker(error.line - 1, 'note-gutter', errorEl);
+ }, this);
+ }
+ }
+ } else {
+ sticky.call(this, {
+ title: 'Sorry No JavaScript!',
+ content: '<p>You\'re using ' + panel_js + '</p>'
+ });
+ }
+ },
+
+ // mark shell as favourite
+ makeFavourite: function(e) {
+ e.stop();
+ new Request.JSON({
+ 'url': makefavouritepath,
+ 'data': {shell_id: this.options.example_id},
+ 'onSuccess': function(response) {
+
+ // #TODO: reload page after successful save
+ window.location.href = response.url;
+ //$('mark_favourite').addClass('isFavourite').getElements('span')[0].set('text', 'Base');
+ }
+ }).send();
+ Track.ui('Mark as favourite');
+ },
+
+ launchTowtruck: function(event){
+ if (event){
+ event.stop();
+ }
+ TowTruck(this);
+ Track.ui('Launch TowTruck');
+ },
+
+ // save and create new pastie
+ saveAsNew: function(e) {
+ e.stop();
+
+ // reset change state so the confirmation doesn't appear on saving
+ window.editorsModified = false;
+ Layout.updateFromMirror();
+ $('id_slug').value='';
+ $('id_version').value='0';
+ new Request.JSON({
+ 'url': this.options.exampleSaveUrl,
+ 'onSuccess': function(json) {
+ Layout.decodeEditors();
+ if (!json.error) {
+
+ // reload page after successful save
+ window.location = json.pastie_url_relative;
+ } else {
+ alert('ERROR: ' + json.error);
+ }
+ }
+ }).send(this.form);
+
+ Track.ui('Save as new fiddle');
+ },
+
+ // update existing (create shell with new version)
+ saveAndReload: function(e) {
+ if (e) e.stop();
+
+ // reset change state so the confirmation doesn't appear on updating
+ window.editorsModified = false;
+ Layout.updateFromMirror();
+ new Request.JSON({
+ 'url': this.options.exampleSaveUrl,
+ 'onSuccess': function(json) {
+
+ // reload page after successful save
+ Layout.decodeEditors();
+ window.location = json.pastie_url_relative;
+ }
+ }).send(this.form);
+
+ Track.ui('Update fiddle');
+ },
+
+ // run - submit the form (targets to the iframe)
+ run: function(e) {
+ var draftonly = false;
+ if (e) e.stop();
+ if (e && ($(e.target).getParent().get('id') === 'mobile' || $(e.target).get('id') === 'mobile')) {
+ draftonly = new Element('input', {
+ 'hidden': true,
+ 'name': 'draftonly',
+ 'id': 'draftonly',
+ 'value': true
+ });
+ draftonly.inject(this.form);
+ }
+ Layout.updateFromMirror();
+ this.form.submit();
+ if (draftonly) {
+ draftonly.destroy();
+ }
+ this.fireEvent('run');
+
+ Track.ui('Run fiddle');
+ },
+
+ loadDraft: function(e) {
+ if (e) e.stop();
+ if (username) {
+ window.open('/draft/', 'jsfiddle_draft');
+ } else {
+ window.location = '/user/login/';
+ }
+
+ Track.ui('Load draft');
+ },
+
+ // This is a method to be called by keyboard shortcut
+ toggleSidebar: function(e) {
+ if (e) e.stop();
+ Layout.sidebar.toggle();
+
+ Track.ui('Toggle sidebar');
+ },
+
+ showShortcutDialog: function(e) {
+ if (e) e.stop();
+ var html = '<div class="modalWrap modal_kbd">' +
+ '<div class="modalHeading"><h3>Keyboard shortcuts</h3><span class="close">Close window</span></div>'+
+ '<div id="kbd" class="modalBody">' +
+ '<ul>' +
+ '<li><kbd>CTRL</kbd> + <kbd>Return</kbd> <span>Run fiddle</span></li>' +
+ '<li><kbd>CTRL</kbd> + <kbd>S</kbd> <span>Save fiddle (Save or Update)</span></li>' +
+ '<li><kbd>CTRL</kbd> + <kbd>Shift</kbd> + <kbd>Return</kbd> <span>Load draft</span></li>' +
+ // '<li><kbd>Control</kbd> + <kbd>↑</kbd> or <kbd>Control</kbd> + <kbd>↓</kbd> <span>Switch editor windows</span></li>' +
+ '<li><kbd>CTRL</kbd> + <kbd>Shift</kbd> + <kbd>↑</kbd> <span>Toggle sidebar</span></li>' +
+ '<li><kbd>CTRL</kbd> + <kbd>K</kbd> <span>Collaboration with TowTruck (very Alpha, don\'t rely on it too much)</span></li>' +
+ '</ul>' +
+ '</div></div>';
+
+ new StickyWin({
+ content: html,
+ relativeTo: $(document.body),
+ position: 'center',
+ edge: 'center',
+ closeClassName: 'close',
+ draggable: true,
+ dragHandleSelector: 'h3',
+ closeOnEsc: true,
+ destroyOnClose: true,
+ allowMultiple: false,
+ onDisplay: this.showModalFx
+ }).show();
+
+ Track.ui('Show shortcuts modal');
+ },
+
+ showModalFx: function(){
+ $$('.modalWrap')[0].addClass('show');
+ },
+
+ switchTo: function(index) {
+ Layout.current_editor = Layout.editors_order[index];
+ Layout.editors[Layout.current_editor].editor.focus();
+ },
+
+ switchNext: function() {
+ // find current and switch to the next
+ var index = Layout.editors_order.indexOf(Layout.current_editor);
+ var nextindex = (index + 1) % 3;
+ this.switchTo(nextindex);
+ },
+
+ switchPrev: function() {
+ // find current and switch to previous
+ var index = Layout.editors_order.indexOf(Layout.current_editor);
+ var nextindex = (index - 1) % 3;
+ if (nextindex < 0) nextindex = 2;
+ this.switchTo(nextindex);
+ },
+
+ // rename iframe label to present the current URL
+ displayExampleURL: function() {
+ var resultInput = document.id(this.options.resultInput);
+ if (resultInput) {
+ if (Browser.Engine.gecko) {
+ resultInput.setStyle('padding-top', '4px');
+ }
+ // resultInput.select();
+ }
+ },
+
+ loadLibraryVersions: function(group_id) {
+ if (group_id) {
+ new Request.JSON({
+ url: this.options.loadLibraryVersionsURL.substitute({group_id: group_id}),
+ onSuccess: function(response) {
+ $('js_lib').empty();
+ $('js_dependency').empty();
+ response.libraries.each( function(lib) {
+ new Element('option', {
+ value: lib.id,
+ text: "{group_name} {version}".substitute(lib)
+ }).inject($('js_lib'));
+ if (lib.selected) $('js_lib').set('value',lib.id);
+ });
+ response.dependencies.each(function (dep) {
+ new Element('li', {
+ html: [
+ "<input id='dep_{id}' type='checkbox' name='js_dependency[{id}]' value='{id}'/>",
+ "<label for='dep_{id}'>{name}</label>"
+ ].join('').substitute(dep)
+ }).inject($('js_dependency'));
+ if (dep.selected) $('dep_'+dep.id).set('checked', true);
+ });
+ }
+ }).send();
+ } else {
+ // XXX: would be good to send an error somehow
+ }
+ },
+
+ loadDependencies: function(lib_id) {
+ if (lib_id) {
+ new Request.JSON({
+ url: this.options.loadDependenciesURL.substitute({lib_id: lib_id}),
+ method: 'get',
+ onSuccess: function(response) {
+ $('js_dependency').empty();
+ response.each(function (dep) {
+ new Element('li', {
+ html: [
+ "<input id='dep_{id}' type='checkbox' name='js_dependency[{id}]' value='{id}'/>",
+ "<label for='dep_{id}'>{name}</label>"
+ ].join('').substitute(dep)
+ }).inject($('js_dependency'));
+ if (dep.selected) $('dep_'+dep.id).set('checked', true);
+ });
+ }
+ }).send();
+ } else {
+ // XXX: would be good to send an error somehow
+ }
+ }
+});
+
+/**
+*
+* Base64 encode / decode
+* http://www.webtoolkit.info/
+*
+**/
+
+var Base64 = {
+
+ // private property
+ _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
+
+ // public method for encoding
+ encode : function (input) {
+ var output = "";
+ input = input || "";
+ var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
+ var i = 0;
+
+ input = Base64._utf8_encode(input);
+
+ while (i < input.length) {
+
+ chr1 = input.charCodeAt(i++);
+ chr2 = input.charCodeAt(i++);
+ chr3 = input.charCodeAt(i++);
+
+ 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 +
+ this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
+ this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
+
+ }
+
+ return output;
+ },
+
+ // public method for decoding
+ decode : function (input) {
+ var output = "";
+ var chr1, chr2, chr3;
+ var enc1, enc2, enc3, enc4;
+ var i = 0;
+
+ input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
+
+ while (i < input.length) {
+
+ enc1 = this._keyStr.indexOf(input.charAt(i++));
+ enc2 = this._keyStr.indexOf(input.charAt(i++));
+ enc3 = this._keyStr.indexOf(input.charAt(i++));
+ enc4 = this._keyStr.indexOf(input.charAt(i++));
+
+ chr1 = (enc1 << 2) | (enc2 >> 4);
+ chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
+ chr3 = ((enc3 & 3) << 6) | enc4;
+
+ output = output + String.fromCharCode(chr1);
+
+ if (enc3 != 64) {
+ output = output + String.fromCharCode(chr2);
+ }
+ if (enc4 != 64) {
+ output = output + String.fromCharCode(chr3);
+ }
+
+ }
+
+ output = Base64._utf8_decode(output);
+
+ return output;
+
+ },
+
+ // private method for UTF-8 encoding
+ _utf8_encode : function (string) {
+ var utftext = "";
+ string = string.replace(/\r\n/g,"\n");
+
+ for (var n = 0; n < string.length; n++) {
+
+ var c = string.charCodeAt(n);
+
+ if (c < 128) {
+ utftext += String.fromCharCode(c);
+ }
+ else if((c > 127) && (c < 2048)) {
+ utftext += String.fromCharCode((c >> 6) | 192);
+ utftext += String.fromCharCode((c & 63) | 128);
+ }
+ else {
+ utftext += String.fromCharCode((c >> 12) | 224);
+ utftext += String.fromCharCode(((c >> 6) & 63) | 128);
+ utftext += String.fromCharCode((c & 63) | 128);
+ }
+
+ }
+ return utftext;
+ },
+
+ // private method for UTF-8 decoding
+ _utf8_decode : function (utftext) {
+ var string = "";
+ var i = 0;
+ var c = c1 = c2 = 0;
+
+ while ( i < utftext.length ) {
+
+ c = utftext.charCodeAt(i);
+
+ if (c < 128) {
+ string += String.fromCharCode(c);
+ i++;
+ }
+ else if((c > 191) && (c < 224)) {
+ c2 = utftext.charCodeAt(i+1);
+ string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
+ i += 2;
+ }
+ else {
+ c2 = utftext.charCodeAt(i+1);
+ c3 = utftext.charCodeAt(i+2);
+ string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
+ i += 3;
+ }
+
+ }
+
+ return string;
+ }
+
+};
+
+var Dropdown = new Class({
+
+ initialize: function(){
+ this.dropdown = {
+ cont: document.getElements('.dropdownCont'),
+ trigger: document.getElements('.dropdown a.aiButton')
+ };
+
+ this.setDefaults();
+ },
+
+ setDefaults: function(){
+ this.dropdown.cont.fade('hide');
+ this.dropdown.cont.set('tween', {
+ duration: 200
+ });
+
+ this.dropdown.trigger.each(function(trigger){
+ trigger.addEvents({
+ click: this.toggle.bindWithEvent(trigger, this)
+ });
+ }, this);
+
+ $(document.body).addEvents({
+ click: function(e){
+ if (!$(e.target).getParent('.dropdownCont')){
+ this.hide();
+ // this.dropdown.trigger.getElement('span').removeClass('selected')
+ }
+ }.bind(this)
+ });
+ },
+
+ toggle: function(event, parent){
+ var trigger = Element(event.target);
+ event.stop();
+ parent.dropdown.cont.fade('out');
+ // trigger.removeClass('selected')
+
+ if (this.getNext('.dropdownCont').getStyles('opacity')['opacity'] === 0){
+ this.getNext('.dropdownCont').fade('in');
+ // trigger.addClass('selected');
+ }
+ },
+
+ hide: function(){
+ this.dropdown.cont.fade('out');
+ }
+});
+
+// mootools events don't work with beforeunload for some reason
+window.onbeforeunload = function(){
+ if (window.editorsModified){
+ return "You've modified your fiddle, reloading the page will reset all changes."
+ }
+};