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/
  • Diff rendering mode:
  • inline
  • side by side

.gitignore

6/lib/6/lib/
7/local/7/local/
8/include/8/include/
9
10#uploads directory
11mouchak/static/uploads/*
12mouchak/static/user_plugins/*

mouchak/server.py

7import pymongo7import pymongo
8import bson8import bson
9import conf9import conf
10import os
11from werkzeug import secure_filename
1012
13PLUGIN_UPLOAD_FOLDER = os.path.join(os.path.dirname(os.path.abspath(__file__))
14 + '/static/user_plugins')
15PLUGIN_ALLOWED_EXTENSIONS = set(['js', 'css'])
16
17FILE_UPLOAD_FOLDER = os.path.join(os.path.dirname(os.path.abspath(__file__)) +
18 '/static/uploads')
19
11app = flask.Flask(__name__)20app = flask.Flask(__name__)
1221
1322
49 return {'content': content, 'menu': menu}49 return {'content': content, 'menu': menu}
5050
5151
52def 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)
53def pageNotFound(e):58def pageNotFound(e):
54 return flask.render_template('404.html'), 40459 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'))
169169
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'])
174def 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'])
186def 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')
172def static_from_root():203def static_from_root():
205205
206206
207app.config.from_object(conf)207app.config.from_object(conf)
208app.config['PLUGIN_UPLOAD_FOLDER'] = PLUGIN_UPLOAD_FOLDER
208209
209import logging,os210import logging,os
210from logging import FileHandler211from logging import FileHandler

mouchak/static/css/editor.css

73 left: 33%;73 left: 33%;
74 top: 0px;74 top: 0px;
75}75}
76#code-edit {
76.ace-mouchak {
77 position: relative;77 position: relative;
78 top: 0;78 top: 0;
79 left: 0;79 left: 0;

mouchak/static/js/editor.js

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'
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 in383 //TODO: do validation on type - a list of valid models is in
384 //M.types.model384 //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 });
451473

mouchak/static/js/models.js

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 });
116140

mouchak/static/js/views.js

106 return;106 return;
107 },107 },
108 render: function(el) {108 render: function(el) {
109 return;
109 this.model.exec();
110 }110 }
111 });111 });
112112

mouchak/templates/editor.html

21 <script>21 <script>
22 // initialize editor22 // initialize editor
23 window.M = window.M || {};23 window.M = window.M || {};
24 M.MenuURL = function() { return "{{ url_for('insertMenu') }}"};
25 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();
291291
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">
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>
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">
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 <% } %>