From 3b9ad98d9a7010f02468c16f2b10da9a7356ac4b Mon Sep 17 00:00:00 2001 From: =?utf8?q?Jordan=20Aug=C3=A9?= Date: Fri, 2 Aug 2013 10:09:33 +0200 Subject: [PATCH] plugins: added debug_platform, code_editor --- debug_platform/__init__.py | 0 debug_platform/urls.py | 31 + debug_platform/views.py | 62 + plugins/code_editor/Actions.js | 798 + plugins/code_editor/EditorCM.js | 345 + plugins/code_editor/LayoutCM.js | 301 + plugins/code_editor/__init__.py | 29 + plugins/code_editor/code_editor.html | 1 + plugins/code_editor/code_editor.js | 433 + plugins/code_editor/img/handle-h.png | Bin 0 -> 202 bytes plugins/code_editor/img/handle-v.png | Bin 0 -> 196 bytes plugins/code_editor/img/initializing.png | Bin 0 -> 20831 bytes plugins/code_editor/moo-clientcide-1.3.js | 15425 +++++++++++++++++++ plugins/debug_platform/__init__.py | 34 + plugins/debug_platform/debug_platform.css | 1017 ++ plugins/debug_platform/debug_platform.html | 49 + plugins/debug_platform/debug_platform.js | 109 + plugins/tabs/tabs.js | 13 + 18 files changed, 18647 insertions(+) create mode 100644 debug_platform/__init__.py create mode 100644 debug_platform/urls.py create mode 100644 debug_platform/views.py create mode 100644 plugins/code_editor/Actions.js create mode 100644 plugins/code_editor/EditorCM.js create mode 100644 plugins/code_editor/LayoutCM.js create mode 100644 plugins/code_editor/__init__.py create mode 100644 plugins/code_editor/code_editor.html create mode 100644 plugins/code_editor/code_editor.js create mode 100644 plugins/code_editor/img/handle-h.png create mode 100644 plugins/code_editor/img/handle-v.png create mode 100644 plugins/code_editor/img/initializing.png create mode 100644 plugins/code_editor/moo-clientcide-1.3.js create mode 100644 plugins/debug_platform/__init__.py create mode 100644 plugins/debug_platform/debug_platform.css create mode 100644 plugins/debug_platform/debug_platform.html create mode 100644 plugins/debug_platform/debug_platform.js create mode 100644 plugins/tabs/tabs.js diff --git a/debug_platform/__init__.py b/debug_platform/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/debug_platform/urls.py b/debug_platform/urls.py new file mode 100644 index 00000000..3e4398cd --- /dev/null +++ b/debug_platform/urls.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# +# debug/urls.py: URL mappings for the debug application +# This file is part of the Manifold project. +# +# Authors: +# Jordan Augé +# Copyright 2013, UPMC Sorbonne Universités / LIP6 +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 3, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program; see the file COPYING. If not, write to the Free Software +# Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +from django.conf.urls import patterns, include, url +from debug_platform.views import PlatformView +from portal.views import DashboardView + +urlpatterns = patterns('', + #(r'platform/(?P[\w\.]+)/?$', 'debug.platform'), + url(r'^platform/?$', PlatformView.as_view(), name='debugplatform'), + #url(r'^platform/(?P[\w\.]+)/?$', PlatformView.as_view(), name='debug_platform'), +) diff --git a/debug_platform/views.py b/debug_platform/views.py new file mode 100644 index 00000000..f56661b3 --- /dev/null +++ b/debug_platform/views.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +# +# portal/views.py: views for the portal application +# This file is part of the Manifold project. +# +# Authors: +# Jordan Augé +# Mohammed Yasin Rahman +# Copyright 2013, UPMC Sorbonne Universités / LIP6 +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 3, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program; see the file COPYING. If not, write to the Free Software +# Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +from django.conf import settings +from django.contrib.sites.models import Site, RequestSite +from django.contrib import messages +from django.views.generic import View +from django.views.generic.base import TemplateView +from django.shortcuts import render +from manifold.core.query import Query +from unfold.page import Page +from myslice.viewutils import topmenu_items, the_user +from django.http import HttpResponseRedirect +from plugins.debug_platform import DebugPlatform + +class PlatformView(TemplateView): + template_name = "view-unfold1.html" + + def get_context_data(self, **kwargs): + + page = Page(self.request) + + debug_platform = DebugPlatform(page = page) + + context = super(PlatformView, self).get_context_data(**kwargs) + + context['ALL_STATIC'] = "all_static" + context['unfold1_main'] = debug_platform.render(self.request) + + # XXX This is repeated in all pages + # more general variables expected in the template + context['title'] = 'Test view that combines various plugins' + # the menu items on the top + context['topmenu_items'] = topmenu_items('Dashboard', self.request) + # so we can sho who is logged + context['username'] = the_user(self.request) + + prelude_env = page.prelude_env() + context.update(prelude_env) + + return context + diff --git a/plugins/code_editor/Actions.js b/plugins/code_editor/Actions.js new file mode 100644 index 00000000..08ea0c1a --- /dev/null +++ b/plugins/code_editor/Actions.js @@ -0,0 +1,798 @@ +// 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 = ''; + 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 = ''; + 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: '

Good work! Your JavaScript code is perfectly valid.

' + }); + } else { + if (JSHINT(editorValue)){ + sticky.call(this, { + title: 'Valid!', + content: '

Good work! Your JavaScript code is perfectly valid.

' + }); + } 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: '

You\'re using ' + panel_js + '

' + }); + } + }, + + // 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 = ''; + + 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: [ + "", + "" + ].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: [ + "", + "" + ].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." + } +}; diff --git a/plugins/code_editor/EditorCM.js b/plugins/code_editor/EditorCM.js new file mode 100644 index 00000000..0c5f98ee --- /dev/null +++ b/plugins/code_editor/EditorCM.js @@ -0,0 +1,345 @@ +/* + Class: MooshellEditor + Editor using CodeMirror + http://codemirror.net + */ + +// jordan +var codemirrorpath = ""; +// end jorda + +var disallowedPlatforms = ['ios', 'android', 'ipod']; +var default_code_mirror_options = { + gutters: ["note-gutter", "CodeMirror-linenumbers"], + tabSize: 4, + indentUnit: 4, + matchBrackets: true, + lineNumbers: true, + lineWrapping: true, + tabMode: 'spaces' // or 'shift' +}; + +var MooShellEditor = new Class({ + Implements: [Options, Events], + + parameter: 'Editor', + + options: { + useCodeMirror: true, + codeMirrorOptions: default_code_mirror_options, + syntaxHighlighting: [] + }, + + window_names: { + 'javascript': 'JavaScript', + 'html': 'HTML', + 'css': 'CSS', + 'scss': 'SCSS', + 'coffeescript': 'CoffeeScript', + 'javascript 1.7': 'JavaScript 1.7' + }, + + initialize: function(el, options) { + this.validationTooltip; + + // switch off CodeMirror for IE + //if (Browser.Engine.trident) options.useCodeMirror = false; + this.element = $(el)[0]; // jordan added [0] for jquery + if (!this.options.syntaxHighlighting.contains(options.language)) { + this.forceDefaultCodeMirrorOptions(); + } + this.setOptions(options); + var is_disallowed = (disallowedPlatforms.contains(Browser.Platform.name)); + if (this.options.useCodeMirror && !is_disallowed) { + // hide textarea + this.element.hide(); + // prepare settings + if (!this.options.codeMirrorOptions.stylesheet && this.options.stylesheet) { + this.options.codeMirrorOptions.stylesheet = this.options.stylesheet.map(function(path) { + return mediapath + path; + }); + } + if (!this.options.codeMirrorOptions.path) { + this.options.codeMirrorOptions.path = codemirrorpath + 'js/'; + } + if (!this.options.codeMirrorOptions.content) { + this.options.codeMirrorOptions.content = this.element.get('value'); + } + + var parentNode = this.element.getParent(); + var options = { value: this.element.value }; + options = Object.append(options, this.options.codeMirrorOptions); + this.editor = CodeMirror(parentNode, options); + + // run this after initialization + if (!this.options.codeMirrorOptions.initCallback){ + if (this.options.codeMirrorOptions.autofocus) { + // set current editor + Layout.current_editor = this.options.name; + } + } + + // set editor options that can only be set once the editor is initialized + var cur = this.editor.getLineHandle(this.editor.getCursor().line); + this.setEditorEvents({ + focus: function(){ + Layout.current_editor = this.options.name; + // this.editor.removeLineClass(cur, 'background', 'activeline'); + // this.editor.hlLine = this.editor.addLineClass(0, "background", "activeline"); + }, + cursorActivity: function(){ + // var cur = this.editor.getLineHandle(this.editor.getCursor().line); + // if (cur != this.editor.hlLine) { + // this.editor.removeLineClass(this.editor.hlLine, 'background', 'activeline'); + // this.editor.hlLine = this.editor.addLineClass(cur, 'background', 'activeline'); + // } + }, + blur: function(){ + // this.editor.removeLineClass(this.editor.hlLine, 'background', 'activeline'); + }, + change: function(){ + this.validateEditorInput.call(this, parentNode); + window.editorsModified = true; + }.bind(this) + }); + + // disable new line insertion when saving fiddle + CodeMirror.keyMap.default['Ctrl-Enter'] = function(){ + return false; + }; + + // don't let Emmet capture this + delete CodeMirror.keyMap.default['Cmd-L']; + } + + this.editorLabelFX = new Fx.Tween(this.getLabel(), { + property: 'opacity', + link: 'cancel' + }); + + this.getWindow().addEvents({ + mouseenter: function() { + this.editorLabelFX.start(0); + }.bind(this), + mouseleave: function() { + this.editorLabelFX.start(0.8); + }.bind(this) + }); + +// jordan commented +// mooshell.addEvents({ +// 'run': this.b64decode.bind(this) +// }); + + Layout.registerEditor(this); + this.setLabelName(this.options.language || this.options.name); + }, + + validateEditorInput: function(parentNode){ + var currentValue = this.getCode(); + var warnings = []; + + // destroy tooltip if it already exists for this editor + if (this.validationTooltip){ + this.validationTooltip.destroy(); + } + + // create the container + this.validationTooltip = Element('ul', { + 'class': 'warningTooltip' + }); + + // collect warnings + Object.each(this.options.disallowed, function(value, key){ + if (currentValue.test(key, 'i')){ + warnings.push('
  • ' + value + '
  • '); + } + }); + + // inject container + this.validationTooltip = this.validationTooltip.inject(parentNode); + + // squash and apply warnings + this.validationTooltip.set({ + html: warnings.join('') + }); + }, + + getEditor: function() { + return this.editor || this.element; + }, + + getWindow: function() { + if (!this.window) { + this.window = this.element.getParent('.window'); + } + return this.window; + }, + + getLabel: function() { + return this.getWindow().getElement('.window_label'); + }, + + b64decode: function() { + this.element.set('value', this.before_decode); + }, + + getCode: function() { + return (this.editor) ? this.editor.getValue() : this.element.get('value'); + }, + + updateFromMirror: function() { + this.before_decode = this.getCode(); + this.element.set('value', Base64.encode(this.before_decode)); + }, + + updateCode: function() { + this.element.set('value', this.getCode()); + }, + + clean: function() { + this.element.set('value', ''); + this.cleanEditor(); + }, + + cleanEditor: function() { + if (this.editor) this.editor.setCode(''); + }, + + hide: function() { + this.getWindow().hide(); + }, + + show: function() { + this.getWindow().show(); + }, + + setEditorOptions: function(options){ + Object.each(options, function(fn, key){ + this.editor.setOption(key, fn.bind(this)); + }, this); + }, + + setEditorEvents: function(e){ + Object.each(e, function(fn, key){ + this.editor.on(key, fn.bind(this)); + }, this); + }, + + setLanguage: function(language) { + // Todo: This is hacky + this.setLabelName(language); + }, + + setLabelName: function(language) { + this.getLabel().set('text', this.window_names[language] || language); + }, + + setStyle: function(key, value) { + if (this.editor) return $(this.editor.frame).setStyle(key, value); + return this.element.setStyle(key, value); + }, + + setStyles: function(options) { + if (this.editor) return $(this.editor.frame).setStyles(options); + return this.element.setStyles(options); + }, + + setWidth: function(width) { + this.getWindow().setStyle('width', width); + }, + + setHeight: function(height) { + this.getWindow().setStyle('height', height); + }, + + getPosition: function() { + if (this.editor) return $(this.editor.frame).getPosition(); + return this.element.getPosition(); + }, + + forceDefaultCodeMirrorOptions: function() { + this.options.codeMirrorOptions = default_code_mirror_options; + } +}); + + +/* + * JS specific settings + */ +MooShellEditor.JS = new Class({ + Extends: MooShellEditor, + + options: { + name: 'js', + language: 'javascript', + useCodeMirror: true, + codeMirrorOptions: { + autofocus: true, + mode: 'javascript' + }, + syntaxHighlighting: ['javascript', 'javascript 1.7'], + disallowed: { + 'document.write is disallowed in JSFiddle envioriment and might break your fiddle." + } + }, + + initialize: function(el, options) { + this.setOptions(options); + this.parent(el, this.options); + } +}); + +/* + * CSS specific settings + */ +MooShellEditor.CSS = new Class({ + Extends: MooShellEditor, + + options: { + name: 'css', + language: 'css', + useCodeMirror: true, + codeMirrorOptions: { + mode: 'css' + }, + syntaxHighlighting: ['css', 'scss'] + }, + + initialize: function(el, options) { + this.setOptions(options); + this.parent(el, this.options); + } +}); + +/* + * HTML specific settings + */ +MooShellEditor.HTML = new Class({ + Extends: MooShellEditor, + + options: { + name: 'html', + language: 'html', + useCodeMirror: true, + codeMirrorOptions: { + mode: 'xml' + }, + syntaxHighlighting: ['html'], + disallowed: { + 'HTML tag, it's already in the output.", + 'META tags.", + '': "No need for the HEAD tag, it's already in the output.", + 'DOCTYPE from the Info panel on the left, instead of adding a tag.", + '