2 // TODO: This code is now legacy code, none of it will make its way into Beta
3 window.editorsModified = false;
5 Request.JSON.implement({
6 initialize: function(options) {
8 this.setHeader('X-CSRFToken', Cookie.read('csrftoken'));
14 if (typeof(_gaq) !== 'undefined'){
15 _gaq.push(['_trackEvent', 'UI', action || null]);
21 * Define actions on the run/save/clean buttons
23 var MooShellActions = new Class({
24 Implements: [Options, Events],
28 formId: 'show-result',
29 saveAndReloadId: 'update',
30 saveAsNewId: 'savenew',
35 showJsId: 'showjscode',
36 shareSelector: '#share_link_dropdown, #share_embedded_dropdown, #share_result_dropdown',
37 favId: 'mark_favourite',
38 entriesSelector: 'textarea',
39 resultLabel: 'result_label',
40 resultInput: 'select_link',
41 collaborateId: 'collaborate',
45 loadDependenciesURL: '',
48 'javascript 1.7': 'js',
58 jslintLanguages: ['text/javascript', 'javascript', 'javascript 1.7'],
59 showJSLanguages: ['text/coffeescript', 'coffeescript']
64 initialize: function(options) {
65 this.setOptions(options);
66 var addBound = function(el, callback, bind) {
67 el = $(el)[0]; // jordan added [0] for jquery
69 el.addEvent('click', callback.bind(bind));
73 Layout.addEvent('ready', function() {
74 addBound(this.options.saveAndReloadId, this.saveAndReload, this);
75 addBound(this.options.saveAsNewId, this.saveAsNew, this);
76 addBound(this.options.runId, this.run, this);
77 addBound(this.options.draftId, this.run, this);
78 addBound(this.options.jslintId, this.jsLint, this);
79 addBound(this.options.showJsId, this.showJs, this);
80 addBound(this.options.tidyId, this.prepareAndLaunchTidy, this);
81 addBound(this.options.favId, this.makeFavourite, this);
82 // addBound(this.options.collaborateId, this.launchTowtruck, this);
85 var keyActions = document.getElements('a.keyActions');
86 keyActions.addEvents({
87 click: function(event){
88 this.showShortcutDialog(event);
91 document.id(document.body).addEvents({
92 keydown: function(event){
93 if (event.shift && event.key === '/'){
94 var elType = new Element(event.target);
95 if (elType.get('tag') === 'body'){
96 keyActions[0].fireEvent('click');
102 var share = $$(this.options.shareSelector);
103 if (share.length > 0) {
104 share.addEvent('click', function() {
109 // actions run if shell loaded
110 this.form = document.id(this.options.formId);
112 if (this.options.exampleURL) {
114 this.displayExampleURL();
116 // assign change language in panel
117 $$('.panelChoice').addEvent('change', this.switchLanguageAction.bind(this));
118 this.showHideJsLint();
119 this.showHideTidyUp();
120 this.showHideShowJs();
124 switchLanguageAction: function(e) {
126 var sel = $(e.target);
127 this.switchLanguage(sel);
131 * Change language in panel
133 switchLanguage: function(sel) {
134 var panel_name = sel.get('data-panel'),
135 editorClass = Layout.editors[panel_name],
136 // Klass = MooShellEditor[panel_name.toUpperCase()],
137 language = sel.getElement('option:selected').get('text'),
138 type = sel.getElement('option:selected').get('data-mime-type');
140 editorClass.editor.setOption('mode', type);
141 // editorClass.updateCode();
143 // editor.getWindow().getElement('.CodeMirror-wrapping').destroy();
144 // Layout.editors[panel_name] = editor = false;
145 // new Klass($(sel.get('data-panel_id')), {
146 // language: language.toLowerCase()
149 Layout.editors[panel_name].setLabelName(language);
150 window['panel_' + panel_name] = language.toLowerCase();
151 this.showHideTidyUp();
152 this.showHideJsLint();
153 this.showHideShowJs();
155 Track.ui('Switch language: ' + language);
158 loadAsset: function(check, file, callback, scope) {
161 callback = callback.bind(scope);
164 Asset.javascript(file, {
171 prepareCoffee: function(callback) {
172 return this.loadAsset(function() {
173 return $defined(window.CoffeeScript);
174 }, '/js/coffeescript/coffeescript.js', callback, this);
177 showJs: function(e) {
179 if (this.prepareCoffee(this.showJs, this)) return;
180 var html = '<div class="modalWrap modal_Coffee">' +
181 '<div class="modalHeading"><h3>JavaScript Code</h3><span class="close">Close window</span></div>'+
182 '<div id="" class="modalBody">',
185 if (panel_js != 'coffeescript') return;
186 coffeecode = Layout.editors.js.editor.getValue();
188 jscode = CoffeeScript.compile(coffeecode);
189 html += '<pre>' + jscode + '</pre>';
191 html += '<p class="error">' + error + '</p>';
193 html += '</div></div>';
196 relativeTo: $(document.body),
199 closeClassName: 'close',
201 dragHandleSelector: 'h3',
203 destroyOnClose: true,
204 allowMultiple: false,
205 onDisplay: this.showModalFx
211 prepareTidyUp: function(callback, scope) {
212 //return this.loadAsset(function() {
213 // return $defined(window.Beautifier);
214 //}, '/js/beautifier.js', callback, scope);
217 showHideShowJs: function() {
219 showjs = $(this.options.showJsId)[0]; // jordan
222 show = (Layout.editors.js.editor.getOption('mode').contains('coffeescript'));
224 showjs.getParent('li').show();
226 showjs.getParent('li').hide();
231 showHideJsLint: function() {
234 lint = $(this.options.jslintId)[0]; // jordan added [0] for jquery
237 Layout.editors.each(function(w){
238 if (this.options.jslintLanguages.contains(w.editor.getOption('mode'))) {
243 lint.getParent('li').hide();
245 lint.getParent('li').show();
249 showHideTidyUp: function() {
251 if (this.prepareTidyUp(this.showHideTidyUp, this)) return;
253 tidy = $(this.options.tidyId);
256 Layout.editors.each(function(w){
257 language = this.options.tidy[w.options.language];
258 if (language && Beautifier[language]) {
263 tidy.getParent('li').hide();
265 tidy.getParent('li').show();
270 prepareAndLaunchTidy: function(e) {
272 if (this.prepareTidyUp(this.makeTidy.bind(this))) return;
276 makeTidy: function(){
278 Layout.editors.each(function(editorInstance){
279 var code = editorInstance.editor.getValue(), language;
281 language = this.options.tidy[editorInstance.options.language];
282 if (language && Beautifier[language]) {
283 var fixed = Beautifier[language](code);
284 if (fixed) editorInstance.editor.setValue(fixed);
285 else editorInstance.editor.reindent();
292 jsLint: function(e) {
295 return this.JSLintValidate();
296 Track.ui('Validate JavaScript');
298 // if (!window.JSLINT) {
299 // // never happens as apparently JSLINT needs to be loaded before MooTools
300 // Asset.javascript('/js/jslint.min.js', {
301 // onload: this.JSLintValidate.bind(this)
304 // return this.JSLintValidate();
308 JSLintValidate: function() {
309 var editor = Layout.editors.js.editor;
310 var html = '<div class="modalWrap modal_jslint">' +
311 '<div class="modalHeading"><h3>JSLint {title}</h3><span class="close">Close window</span></div>'+
312 '<div class="modalBody">{content}</div></div>';
313 var sticky = function(subs){
314 return new StickyWin({
315 content: html.substitute(subs),
316 relativeTo: $(document.body),
319 closeClassName: 'close',
321 dragHandleSelector: 'h3',
323 destroyOnClose: true,
324 allowMultiple: false,
325 onDisplay: this.showModalFx
329 if (this.options.jslintLanguages.contains(panel_js)){
330 var editorValue = editor.getValue();
332 // clear all markers from the gutter, we'll highlight errors again in the next step
333 for (var line = 0; editor.lineCount() >= line; line++){
334 editor.setGutterMarker(line, 'note-gutter', null);
337 if (editorValue.trim() === ''){
340 content: '<p>Good work! Your JavaScript code is perfectly valid.</p>'
343 if (JSHINT(editorValue)){
346 content: '<p>Good work! Your JavaScript code is perfectly valid.</p>'
349 Array.each(JSHINT.errors, function(error, index){
350 errorEl = Element('span', {
351 'class': 'CodeMirror-line-error',
352 'data-title': error.reason,
355 editor.setGutterMarker(error.line - 1, 'note-gutter', errorEl);
361 title: 'Sorry No JavaScript!',
362 content: '<p>You\'re using ' + panel_js + '</p>'
367 // mark shell as favourite
368 makeFavourite: function(e) {
371 'url': makefavouritepath,
372 'data': {shell_id: this.options.example_id},
373 'onSuccess': function(response) {
375 // #TODO: reload page after successful save
376 window.location.href = response.url;
377 //$('mark_favourite').addClass('isFavourite').getElements('span')[0].set('text', 'Base');
380 Track.ui('Mark as favourite');
383 launchTowtruck: function(event){
388 Track.ui('Launch TowTruck');
391 // save and create new pastie
392 saveAsNew: function(e) {
395 // reset change state so the confirmation doesn't appear on saving
396 window.editorsModified = false;
397 Layout.updateFromMirror();
398 $('id_slug').value='';
399 $('id_version').value='0';
401 'url': this.options.exampleSaveUrl,
402 'onSuccess': function(json) {
403 Layout.decodeEditors();
406 // reload page after successful save
407 window.location = json.pastie_url_relative;
409 alert('ERROR: ' + json.error);
414 Track.ui('Save as new fiddle');
417 // update existing (create shell with new version)
418 saveAndReload: function(e) {
421 // reset change state so the confirmation doesn't appear on updating
422 window.editorsModified = false;
423 Layout.updateFromMirror();
425 'url': this.options.exampleSaveUrl,
426 'onSuccess': function(json) {
428 // reload page after successful save
429 Layout.decodeEditors();
430 window.location = json.pastie_url_relative;
434 Track.ui('Update fiddle');
437 // run - submit the form (targets to the iframe)
439 var draftonly = false;
441 if (e && ($(e.target).getParent().get('id') === 'mobile' || $(e.target).get('id') === 'mobile')) {
442 draftonly = new Element('input', {
448 draftonly.inject(this.form);
450 Layout.updateFromMirror();
455 this.fireEvent('run');
457 Track.ui('Run fiddle');
460 loadDraft: function(e) {
463 window.open('/draft/', 'jsfiddle_draft');
465 window.location = '/user/login/';
468 Track.ui('Load draft');
471 // This is a method to be called by keyboard shortcut
472 toggleSidebar: function(e) {
474 Layout.sidebar.toggle();
476 Track.ui('Toggle sidebar');
479 showShortcutDialog: function(e) {
481 var html = '<div class="modalWrap modal_kbd">' +
482 '<div class="modalHeading"><h3>Keyboard shortcuts</h3><span class="close">Close window</span></div>'+
483 '<div id="kbd" class="modalBody">' +
485 '<li><kbd>CTRL</kbd> + <kbd>Return</kbd> <span>Run fiddle</span></li>' +
486 '<li><kbd>CTRL</kbd> + <kbd>S</kbd> <span>Save fiddle (Save or Update)</span></li>' +
487 '<li><kbd>CTRL</kbd> + <kbd>Shift</kbd> + <kbd>Return</kbd> <span>Load draft</span></li>' +
488 // '<li><kbd>Control</kbd> + <kbd>↑</kbd> or <kbd>Control</kbd> + <kbd>↓</kbd> <span>Switch editor windows</span></li>' +
489 '<li><kbd>CTRL</kbd> + <kbd>Shift</kbd> + <kbd>↑</kbd> <span>Toggle sidebar</span></li>' +
490 '<li><kbd>CTRL</kbd> + <kbd>K</kbd> <span>Collaboration with TowTruck (very Alpha, don\'t rely on it too much)</span></li>' +
496 relativeTo: $(document.body),
499 closeClassName: 'close',
501 dragHandleSelector: 'h3',
503 destroyOnClose: true,
504 allowMultiple: false,
505 onDisplay: this.showModalFx
508 Track.ui('Show shortcuts modal');
511 showModalFx: function(){
512 $$('.modalWrap')[0].addClass('show');
515 switchTo: function(index) {
516 Layout.current_editor = Layout.editors_order[index];
517 Layout.editors[Layout.current_editor].editor.focus();
520 switchNext: function() {
521 // find current and switch to the next
522 var index = Layout.editors_order.indexOf(Layout.current_editor);
523 var nextindex = (index + 1) % 3;
524 this.switchTo(nextindex);
527 switchPrev: function() {
528 // find current and switch to previous
529 var index = Layout.editors_order.indexOf(Layout.current_editor);
530 var nextindex = (index - 1) % 3;
531 if (nextindex < 0) nextindex = 2;
532 this.switchTo(nextindex);
535 // rename iframe label to present the current URL
536 displayExampleURL: function() {
537 var resultInput = document.id(this.options.resultInput);
539 if (Browser.Engine.gecko) {
540 resultInput.setStyle('padding-top', '4px');
542 // resultInput.select();
546 loadLibraryVersions: function(group_id) {
549 url: this.options.loadLibraryVersionsURL.substitute({group_id: group_id}),
550 onSuccess: function(response) {
552 $('js_dependency').empty();
553 response.libraries.each( function(lib) {
554 new Element('option', {
556 text: "{group_name} {version}".substitute(lib)
557 }).inject($('js_lib'));
558 if (lib.selected) $('js_lib').set('value',lib.id);
560 response.dependencies.each(function (dep) {
563 "<input id='dep_{id}' type='checkbox' name='js_dependency[{id}]' value='{id}'/>",
564 "<label for='dep_{id}'>{name}</label>"
565 ].join('').substitute(dep)
566 }).inject($('js_dependency'));
567 if (dep.selected) $('dep_'+dep.id).set('checked', true);
572 // XXX: would be good to send an error somehow
576 loadDependencies: function(lib_id) {
579 url: this.options.loadDependenciesURL.substitute({lib_id: lib_id}),
581 onSuccess: function(response) {
582 $('js_dependency').empty();
583 response.each(function (dep) {
586 "<input id='dep_{id}' type='checkbox' name='js_dependency[{id}]' value='{id}'/>",
587 "<label for='dep_{id}'>{name}</label>"
588 ].join('').substitute(dep)
589 }).inject($('js_dependency'));
590 if (dep.selected) $('dep_'+dep.id).set('checked', true);
595 // XXX: would be good to send an error somehow
602 * Base64 encode / decode
603 * http://www.webtoolkit.info/
610 _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
612 // public method for encoding
613 encode : function (input) {
616 var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
619 input = Base64._utf8_encode(input);
621 while (i < input.length) {
623 chr1 = input.charCodeAt(i++);
624 chr2 = input.charCodeAt(i++);
625 chr3 = input.charCodeAt(i++);
628 enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
629 enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
634 } else if (isNaN(chr3)) {
639 this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
640 this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
647 // public method for decoding
648 decode : function (input) {
650 var chr1, chr2, chr3;
651 var enc1, enc2, enc3, enc4;
654 input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
656 while (i < input.length) {
658 enc1 = this._keyStr.indexOf(input.charAt(i++));
659 enc2 = this._keyStr.indexOf(input.charAt(i++));
660 enc3 = this._keyStr.indexOf(input.charAt(i++));
661 enc4 = this._keyStr.indexOf(input.charAt(i++));
663 chr1 = (enc1 << 2) | (enc2 >> 4);
664 chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
665 chr3 = ((enc3 & 3) << 6) | enc4;
667 output = output + String.fromCharCode(chr1);
670 output = output + String.fromCharCode(chr2);
673 output = output + String.fromCharCode(chr3);
678 output = Base64._utf8_decode(output);
684 // private method for UTF-8 encoding
685 _utf8_encode : function (string) {
687 string = string.replace(/\r\n/g,"\n");
689 for (var n = 0; n < string.length; n++) {
691 var c = string.charCodeAt(n);
694 utftext += String.fromCharCode(c);
696 else if((c > 127) && (c < 2048)) {
697 utftext += String.fromCharCode((c >> 6) | 192);
698 utftext += String.fromCharCode((c & 63) | 128);
701 utftext += String.fromCharCode((c >> 12) | 224);
702 utftext += String.fromCharCode(((c >> 6) & 63) | 128);
703 utftext += String.fromCharCode((c & 63) | 128);
710 // private method for UTF-8 decoding
711 _utf8_decode : function (utftext) {
716 while ( i < utftext.length ) {
718 c = utftext.charCodeAt(i);
721 string += String.fromCharCode(c);
724 else if((c > 191) && (c < 224)) {
725 c2 = utftext.charCodeAt(i+1);
726 string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
730 c2 = utftext.charCodeAt(i+1);
731 c3 = utftext.charCodeAt(i+2);
732 string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
743 var Dropdown = new Class({
745 initialize: function(){
747 cont: document.getElements('.dropdownCont'),
748 trigger: document.getElements('.dropdown a.aiButton')
754 setDefaults: function(){
755 this.dropdown.cont.fade('hide');
756 this.dropdown.cont.set('tween', {
760 this.dropdown.trigger.each(function(trigger){
762 click: this.toggle.bindWithEvent(trigger, this)
766 $(document.body).addEvents({
768 if (!$(e.target).getParent('.dropdownCont')){
770 // this.dropdown.trigger.getElement('span').removeClass('selected')
776 toggle: function(event, parent){
777 var trigger = Element(event.target);
779 parent.dropdown.cont.fade('out');
780 // trigger.removeClass('selected')
782 if (this.getNext('.dropdownCont').getStyles('opacity')['opacity'] === 0){
783 this.getNext('.dropdownCont').fade('in');
784 // trigger.addClass('selected');
789 this.dropdown.cont.fade('out');
793 // mootools events don't work with beforeunload for some reason
794 window.onbeforeunload = function(){
795 if (window.editorsModified){
796 return "You've modified your fiddle, reloading the page will reset all changes."