Commit 9fa0b565383754d62fd01d080312f65326fa96a5

Add Header and Footer config in the editor

  Now header and footer can be added from the Mouchak editor directly (via HTML)
  • Diff rendering mode:
  • inline
  • side by side

mouchak/server.py

50if siteMenu.find_one() == None:50if siteMenu.find_one() == None:
51 siteMenu.insert({'customMenu': False, 'menuOrder': [], 'html': ''})51 siteMenu.insert({'customMenu': False, 'menuOrder': [], 'html': ''})
5252
53siteFooter = db['footer']
54if siteFooter.find_one() == None:
55 siteFooter.insert({'html': ''})
5356
57siteHeader = db['header']
58if siteHeader.find_one() == None:
59 siteHeader.insert({'html': ''})
60
61
54# handy reference to otherwise long name62# handy reference to otherwise long name
55bson.ObjId = bson.objectid.ObjectId63bson.ObjId = bson.objectid.ObjectId
5664
76 del(menu['_id'])76 del(menu['_id'])
77 menu['id'] = str(objId)77 menu['id'] = str(objId)
7878
79 return {'content': content, 'menu': menu}
79 footer = siteFooter.find_one()
80 objId = bson.ObjId(footer['_id'])
81 del(footer['_id'])
82 footer['id'] = str(objId)
8083
84 header = siteHeader.find_one()
85 objId = bson.ObjId(header['_id'])
86 del(header['_id'])
87 header['id'] = str(objId)
8188
89 return {'content': content, 'menu': menu, 'footer': footer, 'header':
90 header}
91
92
82def allowed_file(filename):93def allowed_file(filename):
83 return '.' in filename and \94 return '.' in filename and \
84 filename.rsplit('.', 1)[1] in PLUGIN_ALLOWED_EXTENSIONS95 filename.rsplit('.', 1)[1] in PLUGIN_ALLOWED_EXTENSIONS
103@app.route('/', methods=['GET'])103@app.route('/', methods=['GET'])
104def index():104def index():
105 return flask.render_template('index.html', content=getContent(),105 return flask.render_template('index.html', content=getContent(),
106 title=conf.SITE_TITLE, footer=conf.SITE_FOOTER)
106 title=conf.SITE_TITLE)
107107
108108
109@app.route('/edit', methods=['GET'])109@app.route('/edit', methods=['GET'])
110def edit():110def edit():
111 if "logged_in" in flask.session:111 if "logged_in" in flask.session:
112 flask.session['key'] = conf.SECRET_KEY112 flask.session['key'] = conf.SECRET_KEY
113 #print getContent()
113 return flask.render_template('editor.html', content=getContent(),114 return flask.render_template('editor.html', content=getContent(),
114 title=conf.SITE_TITLE)115 title=conf.SITE_TITLE)
115 else:116 else:
131 for page in siteContent.find().sort('_id', 1)[offset:offset+limit]:131 for page in siteContent.find().sort('_id', 1)[offset:offset+limit]:
132 del(page['_id'])132 del(page['_id'])
133 content.append(page)133 content.append(page)
134 print len(content)
134 #print len(content)
135 return flask.make_response(json.dumps(content), '200 OK',135 return flask.make_response(json.dumps(content), '200 OK',
136 {'Content-Type': 'application/json'})136 {'Content-Type': 'application/json'})
137 else:137 else:
144 try:144 try:
145 page = siteContent.find_one({'_id': bson.ObjId(_id)})145 page = siteContent.find_one({'_id': bson.ObjId(_id)})
146 del(page['_id'])146 del(page['_id'])
147 print page
147 #print page
148 return flask.jsonify(page)148 return flask.jsonify(page)
149 except:149 except:
150 return flask.abort(404)150 return flask.abort(404)
153@app.route('/page', methods=['POST'])153@app.route('/page', methods=['POST'])
154def insertPage():154def insertPage():
155 newpage = flask.request.json155 newpage = flask.request.json
156 print newpage
156 #print newpage
157 res = siteContent.insert(newpage)157 res = siteContent.insert(newpage)
158 _id = bson.ObjId(res)158 _id = bson.ObjId(res)
159 newpage['id'] = str(_id)159 newpage['id'] = str(_id)
160 del(newpage['_id'])160 del(newpage['_id'])
161 print newpage
161 #print newpage
162 # FIXME: handle errors162 # FIXME: handle errors
163 #return flask.jsonify(status='ok', page=newpage)163 #return flask.jsonify(status='ok', page=newpage)
164 return flask.jsonify(newpage)164 return flask.jsonify(newpage)
188 return flask.jsonify(status='ok')188 return flask.jsonify(status='ok')
189 else:189 else:
190 return flask.jsonify(error=res['err'], status='error')190 return flask.jsonify(error=res['err'], status='error')
191
192
193@app.route('/footer', methods=['POST'])
194def insertFooter():
195 return '200 OK'
196
197@app.route('/footer/<_id>', methods=['PUT'])
198def updateFooter(_id):
199 if flask.request.method == 'PUT':
200 changedFooter = flask.request.json
201 print "changed footer:"
202 print changedFooter
203 res = siteFooter.update({'_id': bson.ObjId(_id)}, changedFooter)
204 print res
205 return flask.jsonify(changedFooter)
206
207@app.route('/header', methods=['POST'])
208def insertHeader():
209 return '200 OK'
210
211@app.route('/header/<_id>', methods=['PUT'])
212def updateHeader(_id):
213 if flask.request.method == 'PUT':
214 changedHeader = flask.request.json
215 print "changed header:"
216 print changedHeader
217 res = siteHeader.update({'_id': bson.ObjId(_id)}, changedHeader)
218 print res
219 return flask.jsonify(changedHeader)
191220
192221
193@app.route('/menu', methods=['POST'])222@app.route('/menu', methods=['POST'])

mouchak/static/css/editor.css

83 width: 100%;83 width: 100%;
84 height: 300px;84 height: 300px;
85}85}
86#updateMenu {
86.update-btn {
87 margin-top: 10px;87 margin-top: 10px;
88}88}
89#editor-overlay {89#editor-overlay {

mouchak/static/js/editor.js

10 'click #addPage': 'addPage',10 'click #addPage': 'addPage',
11 'click .pagename .remove': 'removePage',11 'click .pagename .remove': 'removePage',
12 'click #menu-config': 'showMenu',12 'click #menu-config': 'showMenu',
13 'click #footer-config': 'showFooterConfig'
13 'click #footer-config': 'showFooterConfig',
14 'click #header-config': 'showHeaderConfig'
14 },15 },
15 initialize: function() {16 initialize: function() {
16 _.bindAll.apply(_, [this].concat(_.functions(this)));17 _.bindAll.apply(_, [this].concat(_.functions(this)));
26 //console.log(menu);26 //console.log(menu);
27 this.menuconfig = new M.types.model.menu(menu);27 this.menuconfig = new M.types.model.menu(menu);
28 this.menuconfigview = new MenuConfigView({model: this.menuconfig});28 this.menuconfigview = new MenuConfigView({model: this.menuconfig});
29 this.footerconfig = new M.types.model.footer(M.site_content.footer);
30 this.footerconfigview = new FooterConfigView({model: this.footerconfig});
31 this.headerconfig = new M.types.model.header(M.site_content.header);
32 this.headerconfigview = new HeaderConfigView({model: this.headerconfig});
29 },33 },
30 render: function() {34 render: function() {
31 // append the page list35 // append the page list
94 },94 },
95 showFooterConfig: function(event) {95 showFooterConfig: function(event) {
96 event.preventDefault();96 event.preventDefault();
97 //this.footerconfigview.render();
97 this.footerconfigview.render();
98 return false;98 return false;
99 },99 },
100 showHeaderConfig: function(event) {
101 event.preventDefault();
102 this.headerconfigview.render();
103 return false;
104 },
100 // validate the page list with menu order list105 // validate the page list with menu order list
101 validate: function() {106 validate: function() {
102 //TODO: validate if the menu order list matches with the list of pages107 //TODO: validate if the menu order list matches with the list of pages
561561
562 /* Footer Config View */562 /* Footer Config View */
563 var FooterConfigView = Backbone.View.extend({563 var FooterConfigView = Backbone.View.extend({
564 tagName: 'div',
565 className: 'prettybox-lg',
566 id: 'page',
567 events: {
568 'click #updateFooter': 'saveFooter'
569 },
570 initialize: function() {
571 _.bindAll.apply(_, [this].concat(_.functions(this)));
572 this.template = _.template($('#footer-config-template').html());
573 },
574 render: function() {
575 $('#page').remove();
576 $('#content-container').append(this.$el);
577 //console.log('rendering..', this.$el);
578 this.$el.html(this.template({
579 footer: this.model.get('html')
580 }));
581 M.editor.code.init('footer-input', 'html');
582 },
583 saveFooter: function() {
584 var html = M.editor.code.save('footer-input');
585 this.model.set({html: html});
586 this.model.save({}, {
587 success: function(model, response) {
588 //console.log(model, response);
589 M.editor.hideOverlay();
590 M.editor.notifs.show('success', 'Saved', '');
591
592 },
593 error: function(xhr, response) {
594 M.editor.hideOverlay();
595 var msg = 'Something went wrong, and the page could not be updated';
596 M.editor.notifs.show('fail', 'Error!', msg);
597 }
598 });
599 M.editor.showOverlay();
600 }
601 });
602
603 /* Header Config View */
604 var HeaderConfigView = Backbone.View.extend({
605 tagName: 'div',
606 className: 'prettybox-lg',
607 id: 'page',
608 events: {
609 'click #updateHeader': 'saveHeader'
610 },
611 initialize: function() {
612 _.bindAll.apply(_, [this].concat(_.functions(this)));
613 this.template = _.template($('#header-config-template').html());
614 },
615 render: function() {
616 $('#page').remove();
617 $('#content-container').append(this.$el);
618 //console.log('rendering..', this.$el);
619 this.$el.html(this.template({
620 header: this.model.get('html')
621 }));
622 M.editor.code.init('header-input', 'html');
623 },
624 saveHeader: function() {
625 var html = M.editor.code.save('header-input');
626 this.model.set({html: html});
627 this.model.save({}, {
628 success: function(model, response) {
629 //console.log(model, response);
630 M.editor.hideOverlay();
631 M.editor.notifs.show('success', 'Saved', '');
632
633 },
634 error: function(xhr, response) {
635 M.editor.hideOverlay();
636 var msg = 'Something went wrong, and the page could not be updated';
637 M.editor.notifs.show('fail', 'Error!', msg);
638 }
639 });
640 M.editor.showOverlay();
641 }
564 });642 });
565643
566 /* Notification view */644 /* Notification view */

mouchak/static/js/models.js

169 },169 },
170 });170 });
171171
172 var Footer = Backbone.Model.extend({
173 defaults: {
174 html: ''
175 },
176 url: function() {
177 return M.FooterURL() + '/' + this.id;
178 },
179 initialize: function() {
180 this.id = this.get('id');
181 }
182 });
183
184 var Header = Backbone.Model.extend({
185 defaults: {
186 html: ''
187 },
188 url: function() {
189 return M.HeaderURL() + '/' + this.id;
190 },
191 initialize: function() {
192 this.id = this.get('id');
193 }
194 });
195
172 //export types to the typemap196 //export types to the typemap
173 M.types = M.types || {};197 M.types = M.types || {};
174 M.types.model = {198 M.types.model = {
201 'image': Image,201 'image': Image,
202 'video': Video,202 'video': Video,
203 'menu': Menu,203 'menu': Menu,
204 'footer': Footer,
205 'header': Header,
204 'rss': RSS,206 'rss': RSS,
205 'table': Table,207 'table': Table,
206 'plugin': Plugin,208 'plugin': Plugin,

mouchak/static/js/mouchak.js

38 },38 },
39 render: function() {39 render: function() {
40 // if custom menu is not defined, render a default menu40 // if custom menu is not defined, render a default menu
41 console.log(this.model.toJSON());
41 //console.log(this.model.toJSON());
42 if(this.model.get('customMenu') === false) {42 if(this.model.get('customMenu') === false) {
43 console.log('generating default menu..');
43 //console.log('generating default menu..');
44 var startpage = M.site_content.menu.menuOrder[0];44 var startpage = M.site_content.menu.menuOrder[0];
45 this.$el.append(this.template({45 this.$el.append(this.template({
46 brand: document.title,//brand name,46 brand: document.title,//brand name,
51 }51 }
52 // else render the custom menu52 // else render the custom menu
53 else {53 else {
54 console.log('rendering custom menu..');
54 //console.log('rendering custom menu..');
55 this.$el.append(this.model.get('html'));55 this.$el.append(this.model.get('html'));
56 }56 }
57 this.$links = $('.nav li');57 this.$links = $('.nav li');
86 if(!event) {86 if(!event) {
87 var fragment = location.hash.split('/')[1];87 var fragment = location.hash.split('/')[1];
88 //var pos = _.indexOf(M.pages.models, M.pages.where({'name': fragment})[0]);88 //var pos = _.indexOf(M.pages.models, M.pages.where({'name': fragment})[0]);
89 //console.log(fragment);
89 var pos = _.indexOf(this.model.get('menuOrder'), fragment);90 var pos = _.indexOf(this.model.get('menuOrder'), fragment);
91 //console.log(pos);
90 if(!fragment) {92 if(!fragment) {
91 pos = 0;93 pos = 0;
92 }94 }
93 $(this.$links[pos]).addClass('active');95 $(this.$links[pos]).addClass('active');
94 }96 }
95 else {97 else {
98 //console.log();
96 $(event.currentTarget).parent().addClass('active');99 $(event.currentTarget).parent().addClass('active');
97 }100 }
98 }101 }
125 else {125 else {
126 $('#navigation').show();126 $('#navigation').show();
127 }127 }
128 M.appView.navView.trigger('navclicked');
128 //console.log('navclicked');
129 M.appView.navView.trigger('navclicked');
129 },130 },
130 render404: function() {131 render404: function() {
131 $('.pageview').hide();132 $('.pageview').hide();
184 Backbone.history.start();184 Backbone.history.start();
185185
186 if(!window.location.hash) {186 if(!window.location.hash) {
187 var startpage = M.site_content.menu.menuOrder[0];
187 var startpage = '#/' + M.site_content.menu.menuOrder[0];
188 //console.log(startpage);
188 app_router.navigate(startpage, {trigger: true});189 app_router.navigate(startpage, {trigger: true});
189 }190 }
191 M.app_router = app_router;
190192
191 //M.simHeir();193 //M.simHeir();
192};194};

mouchak/templates/editor.html

22 // initialize editor22 // initialize editor
23 window.M = window.M || {};23 window.M = window.M || {};
24 M.MenuURL = function() { return "{{ url_for('insertMenu') }}"; };24 M.MenuURL = function() { return "{{ url_for('insertMenu') }}"; };
25 M.FooterURL = function() { return "{{ url_for('insertFooter') }}"; };
26 M.HeaderURL = function() { return "{{ url_for('insertHeader') }}"; };
25 M.PageURL = function() { return "{{ url_for('insertPage') }}"; };27 M.PageURL = function() { return "{{ url_for('insertPage') }}"; };
26 M.PluginUploadURL = function() { return "{{ url_for('uploadPlugin') }}"; };28 M.PluginUploadURL = function() { return "{{ url_for('uploadPlugin') }}"; };
27 M.site_content = {{ content|tojson|safe }};29 M.site_content = {{ content|tojson|safe }};
102 </div>102 </div>
103 <div class="clearfix"></div>103 <div class="clearfix"></div>
104 <div class="row">104 <div class="row">
105 <button id="updateMenu" class="btn btn-primary pull-right"> Update </button>
105 <button id="updateMenu" class="btn btn-primary pull-right update-btn"> Update </button>
106 </div>106 </div>
107 <div class="clearfix"></div>107 <div class="clearfix"></div>
108 </div>108 </div>
109 </script>109 </script>
110110
111 <script type="text/template" id="header-config-template">
112 <div class="page">
113 <h4> Configure your header here </h4>
114 <div id="header-input" class="ace-mouchak">
115 <%= M.escapeHtml(header) %>
116 </div>
117 <div class="row">
118 <button id="updateHeader" class="btn btn-primary pull-right update-btn"> Update </button>
119 </div>
120 <div class="clearfix"></div>
121 </div>
122 </script>
123
124 <script type="text/template" id="footer-config-template">
125 <div class="page">
126 <h4> Configure your footer here </h4>
127 <div id="footer-input" class="ace-mouchak">
128 <%= M.escapeHtml(footer) %>
129 </div>
130 <div class="row">
131 <button id="updateFooter" class="btn btn-primary pull-right update-btn"> Update </button>
132 </div>
133 <div class="clearfix"></div>
134 </div>
135 </script>
136
111 <script type="text/template" id="page-list-template">137 <script type="text/template" id="page-list-template">
112 <div id="pagelistview">138 <div id="pagelistview">
113 <h4> List of Pages </h4>139 <h4> List of Pages </h4>
141 <button class="btn btn-primary" id="addPage">Add Page</button>141 <button class="btn btn-primary" id="addPage">Add Page</button>
142 <hr>142 <hr>
143 <p> <a href="#" id="menu-config"> Site Menu </a> </p>143 <p> <a href="#" id="menu-config"> Site Menu </a> </p>
144 <p> <a href="#" id="header-config"> Header Config </a> </p>
144 <p> <a href="#" id="footer-config"> Footer Config </a> </p>145 <p> <a href="#" id="footer-config"> Footer Config </a> </p>
145 <p><a href="{{ url_for('index') }}"> Go to site </a></p>146 <p><a href="{{ url_for('index') }}"> Go to site </a></p>
146 </div>147 </div>

mouchak/templates/index.html

7{% block body %}7{% block body %}
8 <div class="container" id="container">8 <div class="container" id="container">
9 <div id="header">9 <div id="header">
10 <div id="header-banner">
11 {{ content['header']['html']|safe }}
12 </div>
10 <div id="navigation"></div>13 <div id="navigation"></div>
11 </div>14 </div>
12 <div id="content-container"></div>15 <div id="content-container"></div>
13 <div id="footer"> {{ footer|safe }} </div>
16 <div id="footer"> {{ content['footer']['html']|safe }} </div>
14 </div>17 </div>
1518
16 <script>19 <script>

setup.py

10 'pymongo'10 'pymongo'
11 ]11 ]
1212
13setup(name='mouchak',
13setup(name='Mouchak',
14 version='0.1',14 version='0.1',
15 description='Web framework',15 description='Web framework',
16 license='BSD',16 license='BSD',
23 "Programming Language :: JavaScript",23 "Programming Language :: JavaScript",
24 "Programming Language :: Python",24 "Programming Language :: Python",
25 "Programming Language :: Python :: 2.7",25 "Programming Language :: Python :: 2.7",
26 "Programming Language :: JavaScript",
27 "Topic :: Internet",26 "Topic :: Internet",
28 "Topic :: Internet :: WWW/HTTP :: Site Management",27 "Topic :: Internet :: WWW/HTTP :: Site Management",
29 ],28 ],
30 author='Anon Ray, Arvind',29 author='Anon Ray, Arvind',
30 author_email='rayanon@riseup.net',
31 url='https://git.pantoto.org/mouchak/mouchak.git',31 url='https://git.pantoto.org/mouchak/mouchak.git',
32 keywords='',32 keywords='',
33 packages=find_packages(),33 packages=find_packages(),