Commit 4e1af813c4647126c917e339c4d879362cefa339
Ace editor for plugins
Integrating ace editor for plugins
One can upload plugins too. Create plugin is not yet implemented.
All plugins have to be moved to static/user_plugins/
| | | | 6 | /lib/ | 6 | /lib/ |
---|
7 | /local/ | 7 | /local/ |
---|
8 | /include/ | 8 | /include/ |
---|
| | 9 | |
---|
| | 10 | #uploads directory |
---|
| | 11 | mouchak/static/uploads/* |
---|
| | 12 | mouchak/static/user_plugins/* |
---|
| | | | 7 | import pymongo | 7 | import pymongo |
---|
8 | import bson | 8 | import bson |
---|
9 | import conf | 9 | import conf |
---|
| | 10 | import os |
---|
| | 11 | from werkzeug import secure_filename |
---|
10 | | 12 | |
---|
| | 13 | PLUGIN_UPLOAD_FOLDER = os.path.join(os.path.dirname(os.path.abspath(__file__)) |
---|
| | 14 | + '/static/user_plugins') |
---|
| | 15 | PLUGIN_ALLOWED_EXTENSIONS = set(['js', 'css']) |
---|
| | 16 | |
---|
| | 17 | FILE_UPLOAD_FOLDER = os.path.join(os.path.dirname(os.path.abspath(__file__)) + |
---|
| | 18 | '/static/uploads') |
---|
| | 19 | |
---|
11 | app = flask.Flask(__name__) | 20 | app = flask.Flask(__name__) |
---|
12 | | 21 | |
---|
13 | | 22 | |
---|
… | | … | |
---|
49 | return {'content': content, 'menu': menu} | 49 | return {'content': content, 'menu': menu} |
---|
50 | | 50 | |
---|
51 | | 51 | |
---|
| | 52 | def allowed_file(filename): |
---|
| | 53 | return '.' in filename and \ |
---|
| | 54 | filename.rsplit('.', 1)[1] in PLUGIN_ALLOWED_EXTENSIONS |
---|
| | 55 | |
---|
| | 56 | |
---|
52 | @app.errorhandler(404) | 57 | @app.errorhandler(404) |
---|
53 | def pageNotFound(e): | 58 | def pageNotFound(e): |
---|
54 | return flask.render_template('404.html'), 404 | 59 | return flask.render_template('404.html'), 404 |
---|
… | | … | |
---|
167 | flask.flash('You were logged out') | 167 | flask.flash('You were logged out') |
---|
168 | return flask.redirect(flask.url_for('login')) | 168 | return flask.redirect(flask.url_for('login')) |
---|
169 | | 169 | |
---|
| | 170 | |
---|
| | 171 | #TODO: refactor these code to classes |
---|
| | 172 | #TODO: find out if this is a good method for saving plugins.. |
---|
| | 173 | @app.route('/static/user_plugins/<filename>', methods=['POST']) |
---|
| | 174 | def savePlugin(filename): |
---|
| | 175 | if flask.request.method == 'POST': |
---|
| | 176 | if filename and allowed_file(filename): |
---|
| | 177 | data = flask.request.form['code'] |
---|
| | 178 | filename = secure_filename(filename) |
---|
| | 179 | fh = open(os.path.join(PLUGIN_UPLOAD_FOLDER + '/' + filename), 'w') |
---|
| | 180 | fh.write(data) |
---|
| | 181 | fh.close() |
---|
| | 182 | return flask.jsonify(saved = True) |
---|
| | 183 | |
---|
| | 184 | #TODO: find out if this is a good method for uploading plugins.. |
---|
| | 185 | @app.route('/upload/plugin', methods=['POST']) |
---|
| | 186 | def uploadPlugin(): |
---|
| | 187 | if flask.request.method == 'POST': |
---|
| | 188 | print flask.request.files |
---|
| | 189 | file = flask.request.files['plugin-file'] |
---|
| | 190 | if file and allowed_file(file.filename): |
---|
| | 191 | filename = secure_filename(file.filename) |
---|
| | 192 | file.save(os.path.join(app.config['PLUGIN_UPLOAD_FOLDER'], |
---|
| | 193 | filename)) |
---|
| | 194 | |
---|
| | 195 | #return flask.redirect(flask.url_for('uploaded_file', |
---|
| | 196 | # filename=filename)) |
---|
| | 197 | return flask.jsonify(uploaded = True, |
---|
| | 198 | path=flask.url_for('static', filename = |
---|
| | 199 | 'user_plugins/'+ filename)) |
---|
| | 200 | |
---|
170 | @app.route('/robots.txt') | 201 | @app.route('/robots.txt') |
---|
171 | @app.route('/crossdomain.xml') | 202 | @app.route('/crossdomain.xml') |
---|
172 | def static_from_root(): | 203 | def static_from_root(): |
---|
… | | … | |
---|
205 | | 205 | |
---|
206 | | 206 | |
---|
207 | app.config.from_object(conf) | 207 | app.config.from_object(conf) |
---|
| | 208 | app.config['PLUGIN_UPLOAD_FOLDER'] = PLUGIN_UPLOAD_FOLDER |
---|
208 | | 209 | |
---|
209 | import logging,os | 210 | import logging,os |
---|
210 | from logging import FileHandler | 211 | from logging import FileHandler |
---|
| | | | 73 | left: 33%; | 73 | left: 33%; |
---|
74 | top: 0px; | 74 | top: 0px; |
---|
75 | } | 75 | } |
---|
76 | #code-edit { | | #code-edit { |
---|
| | 76 | .ace-mouchak { | 77 | position: relative; | 77 | position: relative; |
---|
78 | top: 0; | 78 | top: 0; |
---|
79 | left: 0; | 79 | left: 0; |
---|
| | | | 186 | split('-')[1]; | 186 | split('-')[1]; |
---|
187 | var content = this.model.get('content')[idx]; | 187 | var content = this.model.get('content')[idx]; |
---|
188 | content = new M.types.model[content.type](content); | 188 | content = new M.types.model[content.type](content); |
---|
| | 189 | console.log('model inited ', content); |
---|
189 | this.editing = true; | 190 | this.editing = true; |
---|
190 | this.edit_idx = idx; | 191 | this.edit_idx = idx; |
---|
191 | var contentview = new ContentView({model: content}); | 192 | var contentview = new ContentView({model: content}); |
---|
… | | … | |
---|
300 | 'click #updateContent': 'update', | 300 | 'click #updateContent': 'update', |
---|
301 | 'click #back' : 'back', | 301 | 'click #back' : 'back', |
---|
302 | 'click #edit-type button' : 'editTypeChanged', | 302 | 'click #edit-type button' : 'editTypeChanged', |
---|
303 | 'change .contentview select': 'typeChanged' | | 'change .contentview select': 'typeChanged' |
---|
| | 303 | 'change .contentview select': 'typeChanged', | | | 304 | /* plugin events */ |
---|
| | 305 | 'click #upload-plugin': 'uploadPlugin' |
---|
304 | }, | 306 | }, |
---|
305 | initialize: function() { | 307 | initialize: function() { |
---|
306 | _.bindAll.apply(_, [this].concat(_.functions(this))); | 308 | _.bindAll.apply(_, [this].concat(_.functions(this))); |
---|
… | | … | |
---|
361 | src: this.model.get('src'), | 361 | src: this.model.get('src'), |
---|
362 | callback: this.model.get('callback') | 362 | callback: this.model.get('callback') |
---|
363 | })); | 363 | })); |
---|
| | 364 | if(this.model.get('src')) { |
---|
| | 365 | var plugin_type = this.model.get('plugin_type'); |
---|
| | 366 | plugin_type = (plugin_type === 'js') ? 'javascript': 'css'; |
---|
| | 367 | this.model.getCode(function(data) { |
---|
| | 368 | $('#plugin-edit').html(escapeHtml(data)); |
---|
| | 369 | M.editor.code.init('plugin-edit', plugin_type); |
---|
| | 370 | }); |
---|
| | 371 | } |
---|
364 | } | 372 | } |
---|
365 | else if(type === 'map') { | 373 | else if(type === 'map') { |
---|
366 | var template = _.template($('#map-template').html()); | 374 | var template = _.template($('#map-template').html()); |
---|
… | | … | |
---|
382 | var type = this.$select.val(); | 382 | var type = this.$select.val(); |
---|
383 | //TODO: do validation on type - a list of valid models is in | 383 | //TODO: do validation on type - a list of valid models is in |
---|
384 | //M.types.model | 384 | //M.types.model |
---|
| | 385 | var base_props = _.keys(new M.types.model.base().defaults); |
---|
| | 386 | var props = _.omit(this.model.toJSON(), base_props); |
---|
| | 387 | var new_model = new M.types.model[type](props); |
---|
| | 388 | this.model = new_model; |
---|
385 | this.model.set({'type': type}); | 389 | this.model.set({'type': type}); |
---|
386 | this.render(); | 390 | this.render(); |
---|
387 | }, | 391 | }, |
---|
… | | … | |
---|
425 | new_attrs['data'] = data; | 425 | new_attrs['data'] = data; |
---|
426 | } | 426 | } |
---|
427 | } | 427 | } |
---|
| | 428 | else if(this.$select.val() === 'plugin') { |
---|
| | 429 | var data = M.editor.code.save('plugin-edit'); |
---|
| | 430 | this.model.saveCode(data, function(resp) { |
---|
| | 431 | console.log('plugin saved..'); |
---|
| | 432 | }); |
---|
| | 433 | } |
---|
428 | this.model.set(new_attrs); | 434 | this.model.set(new_attrs); |
---|
429 | M.editor.pageview.updateContent(this.model.toJSON()); | 435 | M.editor.pageview.updateContent(this.model.toJSON()); |
---|
430 | }, | 436 | }, |
---|
… | | … | |
---|
446 | }, | 446 | }, |
---|
447 | back: function() { | 447 | back: function() { |
---|
448 | this.cleanUp(); | 448 | this.cleanUp(); |
---|
| | 449 | }, |
---|
| | 450 | //upload inputed plugin file to server |
---|
| | 451 | uploadPlugin: function(event) { |
---|
| | 452 | var self = this; |
---|
| | 453 | M.editor.showOverlay(); |
---|
| | 454 | var $form = $('#plugin-upload-form')[0]; |
---|
| | 455 | console.log($form); |
---|
| | 456 | var formdata = new FormData($form); |
---|
| | 457 | console.log(formdata); |
---|
| | 458 | $.ajax({ |
---|
| | 459 | type: 'POST', |
---|
| | 460 | url: M.PluginUploadURL(), |
---|
| | 461 | data: formdata, |
---|
| | 462 | processData: false, |
---|
| | 463 | contentType: false, |
---|
| | 464 | success: function(response) { |
---|
| | 465 | self.model.set({'src': response.path}) |
---|
| | 466 | self.render(); |
---|
| | 467 | M.editor.hideOverlay(); |
---|
| | 468 | console.log(self.model.toJSON()); |
---|
| | 469 | } |
---|
| | 470 | }); |
---|
449 | } | 471 | } |
---|
450 | }); | 472 | }); |
---|
451 | | 473 | |
---|
| | | | 80 | data: {}, | 80 | data: {}, |
---|
81 | callback: "" | 81 | callback: "" |
---|
82 | }, BaseType.prototype.defaults), | 82 | }, BaseType.prototype.defaults), |
---|
| | 83 | |
---|
83 | initialize: function() { | 84 | initialize: function() { |
---|
84 | BaseType.prototype.initialize.call(this, arguments); | 85 | BaseType.prototype.initialize.call(this, arguments); |
---|
85 | | | |
---|
86 | if(this.get('src').match(/\.js/)) { | 86 | if(this.get('src').match(/\.js/)) { |
---|
| | 87 | this.set({'plugin_type': 'js'}); |
---|
| | 88 | } |
---|
| | 89 | else if(this.get('src').match(/\.css/)) { |
---|
| | 90 | this.set({'plugin_type': 'css'}); |
---|
| | 91 | } |
---|
| | 92 | }, |
---|
| | 93 | exec: function() { |
---|
| | 94 | console.log('exec called'); |
---|
| | 95 | if(this.get('src').match(/\.js/)) { |
---|
87 | var script = document.createElement('script'); | 96 | var script = document.createElement('script'); |
---|
88 | var callback = this.get('callback'); | 97 | var callback = this.get('callback'); |
---|
89 | script.src = this.get('src'); | 98 | script.src = this.get('src'); |
---|
… | | … | |
---|
111 | link.type = 'text/css'; | 111 | link.type = 'text/css'; |
---|
112 | document.body.appendChild(link); | 112 | document.body.appendChild(link); |
---|
113 | } | 113 | } |
---|
| | 114 | }, |
---|
| | 115 | // get the source code of the plugin from the src path |
---|
| | 116 | getCode: function(cb) { |
---|
| | 117 | var self = this; |
---|
| | 118 | $.ajax({ |
---|
| | 119 | type: 'GET', |
---|
| | 120 | url: self.get('src'), |
---|
| | 121 | cache: false, |
---|
| | 122 | success: function(data) { |
---|
| | 123 | cb(data); |
---|
| | 124 | } |
---|
| | 125 | }); |
---|
| | 126 | }, |
---|
| | 127 | // save the source code of the plugin to the src path |
---|
| | 128 | saveCode: function(data, cb) { |
---|
| | 129 | var self = this; |
---|
| | 130 | $.ajax({ |
---|
| | 131 | type: 'POST', |
---|
| | 132 | url: this.get('src'), |
---|
| | 133 | data: {code: data}, |
---|
| | 134 | success: function(data) { |
---|
| | 135 | cb(data); |
---|
| | 136 | } |
---|
| | 137 | }); |
---|
114 | } | 138 | } |
---|
115 | }); | 139 | }); |
---|
116 | | 140 | |
---|
| | | | 106 | return; | 106 | return; |
---|
107 | }, | 107 | }, |
---|
108 | render: function(el) { | 108 | render: function(el) { |
---|
109 | return; | | return; |
---|
| | 109 | this.model.exec(); | 110 | } | 110 | } |
---|
111 | }); | 111 | }); |
---|
112 | | 112 | |
---|
| | | | 21 | <script> | 21 | <script> |
---|
22 | // initialize editor | 22 | // initialize editor |
---|
23 | window.M = window.M || {}; | 23 | window.M = window.M || {}; |
---|
24 | M.MenuURL = function() { return "{{ url_for('insertMenu') }}"}; | | M.MenuURL = function() { return "{{ url_for('insertMenu') }}"}; |
---|
25 | M.PageURL = function() { return "{{ url_for('insertPage') }}"}; | | M.PageURL = function() { return "{{ url_for('insertPage') }}"}; |
---|
| | 24 | M.MenuURL = function() { return "{{ url_for('insertMenu') }}"; }; | | | 25 | M.PageURL = function() { return "{{ url_for('insertPage') }}"; }; |
---|
| | 26 | M.PluginUploadURL = function() { return "{{ url_for('uploadPlugin') }}"; }; |
---|
26 | M.site_content = {{ content|tojson|safe }}; | 27 | M.site_content = {{ content|tojson|safe }}; |
---|
27 | window.onload = function() { | 28 | window.onload = function() { |
---|
28 | M.editor.init(); | 29 | M.editor.init(); |
---|
… | | … | |
---|
291 | | 291 | |
---|
292 | <script type="text/template" id="plugin-template"> | 292 | <script type="text/template" id="plugin-template"> |
---|
293 | <div class="data"> | 293 | <div class="data"> |
---|
294 | <div class="row"> | | <div class="row"> |
---|
| | 294 | <!--div class="row"> | 295 | <div class="form-group col-lg-6"> | 295 | <div class="form-group col-lg-6"> |
---|
296 | <div class="input-group"> | 296 | <div class="input-group"> |
---|
297 | <span class="input-group-addon"><strong>path to file</strong></span> | 297 | <span class="input-group-addon"><strong>path to file</strong></span> |
---|
… | | … | |
---|
299 | m-data-target="src"> | 299 | m-data-target="src"> |
---|
300 | </div> | 300 | </div> |
---|
301 | </div> | 301 | </div> |
---|
302 | </div> | | </div> |
---|
| | 302 | </div--> | | | 303 | <% if(!src) { %> |
---|
| | 304 | <div class="row"> |
---|
| | 305 | <div class="form-group col-lg-6"> |
---|
| | 306 | <form id="plugin-upload-form" action="" method="post" enctype="multipart/form-data"> |
---|
| | 307 | <input type="file" name="plugin-file" id="select-plugin"> |
---|
| | 308 | <input type="button" id="upload-plugin" class="btn btn-info" value="Upload Plugin"> |
---|
| | 309 | </form> |
---|
| | 310 | </div> |
---|
| | 311 | </div> |
---|
| | 312 | <div class="row"> |
---|
| | 313 | <div class="form-group col-lg-6"> |
---|
| | 314 | <button type="button" class="btn btn-info" id="create-plugin">Create a new plugin</button> |
---|
| | 315 | </div> |
---|
| | 316 | </div> |
---|
| | 317 | <% } %> |
---|
303 | <div class="row"> | 318 | <div class="row"> |
---|
304 | <div class="form-group col-lg-6"> | 319 | <div class="form-group col-lg-6"> |
---|
305 | <div class="input-group"> | 320 | <div class="input-group"> |
---|
… | | … | |
---|
325 | </div> | 325 | </div> |
---|
326 | </div> | 326 | </div> |
---|
327 | </div> | 327 | </div> |
---|
| | 328 | <% if(src) { %> |
---|
| | 329 | <div class="ace-mouchak" id="plugin-edit"></div> |
---|
| | 330 | <% } %> |
---|
328 | <div class="preview"></div> | 331 | <div class="preview"></div> |
---|
329 | </div> | 332 | </div> |
---|
330 | </script> | 333 | </script> |
---|
… | | … | |
---|
358 | <%= data %> | 358 | <%= data %> |
---|
359 | </textarea> | 359 | </textarea> |
---|
360 | <% } else { %> | 360 | <% } else { %> |
---|
361 | <div id="code-edit" m-data-target="data"> | | <div id="code-edit" m-data-target="data"> |
---|
| | 361 | <div id="code-edit" class="ace-mouchak" m-data-target="data"> | 362 | <%= M.escapeHtml(data) %> | 362 | <%= M.escapeHtml(data) %> |
---|
363 | </div> | 363 | </div> |
---|
364 | <% } %> | 364 | <% } %> |
---|