Commit 63c7919ccc9a6695f72c022f3380671cf67b979f

Add new menu config and Fix many bugs

  - Add a configuration system for writing customized navigation menus. The
user has total flexibility to incorporate any kind of menus. Write any HTML
and CSS you want.
  - Fix many bugs in adding new pages and rendering them.
  - Fix the backend API. Its more meaningful now.
  • Diff rendering mode:
  • inline
  • side by side

.gitignore

11
2*.*~2*.*~
3*.pyc

README.md

1Mouchak1Mouchak
2=======2=======
33
4A Javascript framework for building single page websites or web apps.
4A Javascript framework for building websites quickly.
55
6It takes the content of the website as a JSON. The JSON file can contain
7text or multimedia content(images, audio, video).
6It aims to provide a visual editing interface to create a website and edit its
7content, primarily for non-technical users.
88
9Mouchak can also load external JS/CSS files through the JSON. This gives the website to load
10plugins to enhance/customize the website either through JS or CSS.
9Under the hood, Mouchak abstracts the content of the website into a JSON
10structure and uses Backbone model and views to render them dynamically.
11This makes the site entirely run on the client side and syncs with the server
12whenever there is a update.
13This also makes Mouchak quite extensible. The built-in types to represent HTML
14elements and media and such can be used and extended to build more custom
15types.
1116
17Mouchak can also load external JS/CSS files. Any magic that can be done using
18Javascript and CSS can be easily integrated into Mouchak. Just specify Mouchak
19the plugin to load and the callback to execute.
1220
21Additionally, Mouchak can also provide a semantic rich environment. It provides
22the user to specify tags or keywords with each associated content. To
23complement it, Mouchak provides an API (filterTags) to get all the related
24content together. One can easily built a view if this kind of functionality is
25necessary.
26
27
13How to use it28How to use it
14=============29=============
1530
31Installing
32----------
16Either download this codebase or git clone the repo.33Either download this codebase or git clone the repo.
1734
18Once you have downloaded or cloned the repo, load the index.html file in your browser.
19This loads the example content from example.json.
35**Pre-requisites**
2036
21Use the index.html file as the boilerplate file of your index.html file.
22Modify the code in the script tag, which loads the example.json, and change
23the URL to point to your JSON file.
24Remember the JSON files is loaded in the client side. Hence your JSON file should
25be from the same domain where this app is loaded.
26See cross-domain policies for details.
37You need to have Python, MongoDB, Flask and PyMongo.
38To install python and mongodb on your platform, please use search engines to
39find instructions. As they are quite popular softwares, getting help online
40should not be difficult.
2741
42To install Flask and PyMongo -
43> pip install flask pymongo
44
45Configuring
46-----------
47Configuration of Mouchak consists of configuring which database to use,
48hostname, port no, title of the website etc.
49Open up mouchak.conf, edit the file according to your needs, and then save it.
50Ideally, you should set up seperate db for every project.
51
52Running
53-------
54Once you have installed all the dependencies, go to the directory where the
55code is located and type:
56> python server.py
57
58This starts the Mouchak server. You can now point your browser to
59[http://localhost:5000](http://localhost:5000)
60
61This will load up and display the website.
62To edit the website go to [/edit](http://localhost:5000/edit)
63
64
28The global object for this framework is exposed as the variable M. This can be65The global object for this framework is exposed as the variable M. This can be
29inspected in the console.66inspected in the console.
3067
76(in terms of type like text, audio or image) and also provide the content (in case of text76(in terms of type like text, audio or image) and also provide the content (in case of text
77the content is as it is, in case images urls are given). The JSON should also describe the77the content is as it is, in case images urls are given). The JSON should also describe the
78content semantically by giving it tags.78content semantically by giving it tags.
79More details about the JSON format in example.json file.
8079
80Mouchak provides a very simple and easy to use editor to edit the JSON.
81
81The framework provides an easy way to pull up related content by just specifying the tags.82The framework provides an easy way to pull up related content by just specifying the tags.
8283
83Backbone models are used to model the content. Different content has different types.84Backbone models are used to model the content. Different content has different types.
98Mouchak also leverages powerful libraries like Backbone.js and Underscore.js to manage and render98Mouchak also leverages powerful libraries like Backbone.js and Underscore.js to manage and render
99content. This gives more flexibility to the content of the website.99content. This gives more flexibility to the content of the website.
100100
101The main code resides in js/mouchak.js. The HTML markup it uses is in index.html.
102101
103Javascript libary files are in js/lib. We use backbone.js, underscore.js and jquery in this
104framework.
105
106Boilerplate code/files:
107404.html - error template
108crossdomain.xml - cross-domain policies to be obeyed by the client
109css/bootstrap.css - boilerplate css
110css/normalize.css - boilerplate css
111css/main.css - boilerplate css
112humans.txt - write your own credits
113img/ - directory for images
114robots.txt - crawl spider rules
115
116
117Support102Support
118=======103=======
119104
120Email to rayanon at janastu dot org / arvind at janastu dot org for any kind of feedback.
105Email to rayanon at servelots dot com / arvind at servelots dot com for any kind of feedback.
121106
122107
123Issues108Issues
124======109======
125110
126Report issues [here](http://bugzilla.pantoto.org/bugzilla3/describecomponents.cgi?product=Mouchak)
111Report issues [here](http://trac.pantoto.org/mouchak/)
112First, check if your issue is already submitted by anyone else, by clicking on
113"View Tickets".
114If your issue is not reported, you can report it by clicking on "New Ticket".

mouchak.conf

1DB=test_mouchak
2SITE_TITLE=Testing Mouchak
3HOST=0.0.0.0
4PORT=5000

readConfig.py

1#!/usr/bin/python
2
3# Read Mouchak Configuration
4
5
6import re
7
8def readConfig():
9 confFile = 'mouchak.conf'
10 fh = open(confFile, 'r')
11 contents = fh.read()
12
13 match = re.search('DB=(.*)', contents)
14 dbName = match.group(1)
15 match = re.search('SITE_TITLE=(.*)', contents)
16 title = match.group(1)
17 match = re.search('HOST=(.*)', contents)
18 host = match.group(1)
19 match = re.search('PORT=(.*)', contents)
20 port = match.group(1)
21
22 return {'db': dbName, 'site_title': title, 'host': host, 'port': int(port)}

server.py

1#!/usr/bin/python1#!/usr/bin/python
2
2# Mouchak Server -3# Mouchak Server -
3# A Flask Application (http://flask.pocoo.org/)4# A Flask Application (http://flask.pocoo.org/)
45
5import flask6import flask
6import pymongo7import pymongo
7import bson8import bson
9import readConfig
810
9app = flask.Flask(__name__)11app = flask.Flask(__name__)
1012
13
14config = readConfig.readConfig()
15
11dbClient = pymongo.MongoClient()16dbClient = pymongo.MongoClient()
12db = dbClient['mouchak']
13collection = db['content']
17db = dbClient[config['db']]
18siteContent = db['content']
19siteMenu = db['menu']
20if siteMenu.find_one() == None:
21 siteMenu.insert({'customMenu': False})
22
14# handy reference to otherwise long name23# handy reference to otherwise long name
15bson.ObjId = bson.objectid.ObjectId24bson.ObjId = bson.objectid.ObjectId
1625
26
17def getContent():27def getContent():
18 content = []28 content = []
19 for i in collection.find():
29 for i in siteContent.find():
20 objId = bson.ObjId(i['_id'])30 objId = bson.ObjId(i['_id'])
21 del(i['_id'])31 del(i['_id'])
22 i['id'] = str(objId)32 i['id'] = str(objId)
23 content.append(i)33 content.append(i)
24 return content
2534
35 menu = siteMenu.find_one()
36 objId = bson.ObjId(menu['_id'])
37 del(menu['_id'])
38 menu['id'] = str(objId)
2639
40 return {'content': content, 'menu': menu}
41
42
43
27@app.route('/', methods=['GET'])44@app.route('/', methods=['GET'])
28def index():45def index():
29 return flask.render_template('index.html', content=getContent())
46 return flask.render_template('index.html', content=getContent(),
47 title=config['site_title'])
3048
3149
32@app.route('/edit', methods=['GET', 'POST'])
50@app.route('/edit', methods=['GET'])
33def edit():51def edit():
34 if flask.request.method == 'GET':
35 return flask.render_template('editor.html', content=getContent())
52 return flask.render_template('editor.html', content=getContent(),
53 title=config['site_title'])
3654
37 elif flask.request.method == 'POST':
38 newpage = flask.request.json
39 print newpage
40 res = collection.insert(newpage)
41 print res
42 return flask.jsonify(status='success')#, content=getContent())
4355
44
45@app.route('/edit/<_id>', methods=['PUT', 'DELETE'])
46def editPage(_id):
56@app.route('/page', methods=['POST'])
57def insertPage():
58 newpage = flask.request.json
59 print newpage
60 res = siteContent.insert(newpage)
61 _id = bson.ObjId(res)
62 newpage['id'] = str(_id)
63 del(newpage['_id'])
64 print newpage
65 # FIXME: handle errors
66 return flask.jsonify(status='ok', page=newpage)
67
68
69@app.route('/page/<_id>', methods=['PUT', 'DELETE'])
70def updatePage(_id):
47 if flask.request.method == 'PUT':71 if flask.request.method == 'PUT':
48 changedPage = flask.request.json72 changedPage = flask.request.json
49 print changedPage73 print changedPage
50 res = collection.update({'_id' : bson.ObjId(_id)},
74 print '======='
75 res = siteContent.update({'_id': bson.ObjId(_id)},
51 changedPage)76 changedPage)
52 print res77 print res
53 #print collection.find({'name': changed['name']})
54 #for i in collection.find({'name': changed['name']}):
55 #print i
56 return flask.jsonify(status='success')#, content=getContent())
78 if res['err'] == None:
79 print changedPage
80 return flask.jsonify(status='ok', page=changedPage)
5781
58 elif flask.request.method == 'DELETE':82 elif flask.request.method == 'DELETE':
59 delPage = flask.request.url83 delPage = flask.request.url
60 print delPage84 print delPage
61 print _id85 print _id
62 res = collection.remove({'_id': bson.ObjId(_id)})
86 res = siteContent.remove({'_id': bson.ObjId(_id)})
63 print res87 print res
64 return flask.jsonify(status='success', msg='removed')
88 if res['err'] == None:
89 return flask.jsonify(status='ok')
90 else:
91 return flask.jsonify(error=res['err'], status='error')
6592
6693
94#@app.route('/menu', methods=['POST'])
95#def insertMenu():
96# newmenu = flask.request.json
97# print newmenu
98# res = siteMenu.insert(newmenu)
99# print res
100# return flask.jsonify(status='success')#, content=getContent())
101#
102
103@app.route('/menu/<_id>', methods=['PUT'])
104def updateMenu(_id):
105 if flask.request.method == 'PUT':
106 changedMenu = flask.request.json
107 print changedMenu
108 res = siteMenu.update({'_id': bson.ObjId(_id)}, changedMenu)
109 print res
110 return flask.jsonify(status='ok',menu=changedMenu)
111
112 #elif flask.request.method == 'DELETE':
113 # delMenu = flask.request.url
114 # print delMenu
115 # print _id
116 # res = siteMenu.remove({'_id': bson.ObjId(_id)})
117 # return flask.jsonify(status='deleted')
118
119
67if __name__ == "__main__":120if __name__ == "__main__":
68 app.run(debug=True, host='0.0.0.0')
121 print config
122 app.run(debug=True, host=config['host'], port=config['port'])

static/css/editor.css

97 border: 1px solid #999;97 border: 1px solid #999;
98 padding: 20px;98 padding: 20px;
99 width: 400px;99 width: 400px;
100 height: 400px;
100 /*height: 400px;*/
101}101}
102#pages {102#pages {
103 position: absolute;103 position: absolute;
106 border: 1px solid black;106 border: 1px solid black;
107 padding: 10px;107 padding: 10px;
108 width: 300px;108 width: 300px;
109 height: 80%;
109 /*height: 80%;*/
110}110}
111#page {111#page {
112 position: absolute;112 position: absolute;
137#specific-content {137#specific-content {
138 padding: 3px;138 padding: 3px;
139 margin-bottom: 10px;139 margin-bottom: 10px;
140}
141#addPage {
142 margin: 10px 0 0 200px;
140}143}
141144
142/* ==========================================================================145/* ==========================================================================

static/css/main.css

94 ========================================================================== */94 ========================================================================== */
9595
9696
97
98/* ==========================================================================97/* ==========================================================================
99 Helper classes98 Helper classes
100 ========================================================================== */99 ========================================================================== */

static/js/editor.js

8 events: {8 events: {
9 'click .pagename .disp': 'showPage',9 'click .pagename .disp': 'showPage',
10 'click #addPage': 'addPage',10 'click #addPage': 'addPage',
11 'click .pagename .remove': 'removePage'
11 'click .pagename .remove': 'removePage',
12 'click #menu-config': 'showMenu'
12 },13 },
13 initialize: function() {14 initialize: function() {
14 _.bindAll(this);15 _.bindAll(this);
20 $('#content-container').append(this.$el);20 $('#content-container').append(this.$el);
21 this.$el.append(this.template());21 this.$el.append(this.template());
22 this.$pagelist = $('#pagelist');22 this.$pagelist = $('#pagelist');
23 var menu = M.site_content.menu;
24 //console.log(menu);
25 this.menuconfig = new M.types.model.menu(menu);
26 this.menuconfigview = new MenuConfigView({model: this.menuconfig});
23 },27 },
24 render: function() {28 render: function() {
25 // append the page list29 // append the page list
42 M.editor.pageview = pageview;42 M.editor.pageview = pageview;
43 },43 },
44 addPage: function() {44 addPage: function() {
45 var newpage = new Page();
45 var newpage = new M.types.model.Page();
46 M.pages.add(newpage);46 M.pages.add(newpage);
47 var newpageview = new PageView({model: newpage});47 var newpageview = new PageView({model: newpage});
48 newpageview.render();48 newpageview.render();
64 console.log('failed', model, xhr);64 console.log('failed', model, xhr);
65 }65 }
66 });66 });
67 }
68 });
69
70 var Page = Backbone.Model.extend({
71 defaults: {
72 name: '',
73 title: '',
74 children: [],
75 content: []
76 },67 },
77 initialize: function() {
78 this.id = this.get('id');
68 showMenu: function(event) {
69 this.menuconfigview.render();
79 }70 }
80 });71 });
8172
82 var Pages = Backbone.Collection.extend({73 var Pages = Backbone.Collection.extend({
83 model: Page,
84 url: '/edit'
74 model: M.types.model.Page,
75 url: '/page'
85 });76 });
8677
87 /* view to manage each page and their properties - change page properties,78 /* view to manage each page and their properties - change page properties,
111 console.log('name changed', page);111 console.log('name changed', page);
112 },112 },
113 render: function() {113 render: function() {
114 console.log(this.$el);
115 $('#page').html('');114 $('#page').html('');
116 console.log('content: ', this.model.get('content'));
115 //console.log('content: ', this.model.get('content'));
117116
118 this.$el.html(this.template({117 this.$el.html(this.template({
119 name: this.model.get('name'),118 name: this.model.get('name'),
120 title: this.model.get('title'),119 title: this.model.get('title'),
121 children: this.model.get('children'),120 children: this.model.get('children'),
122 content: this.listContent()
121 content: this.listContent(),
122 checked: this.model.get('showNav') ? 'checked="checked"' : ''
123 }));123 }));
124124
125 //hover effect125 //hover effect
128 }, function(event) {128 }, function(event) {
129 $(event.target).closest('.content-item').removeClass('alert-error')129 $(event.target).closest('.content-item').removeClass('alert-error')
130 });130 });
131 console.log('done');
132 },131 },
133 listContent: function() {132 listContent: function() {
134 var content = '';133 var content = '';
182 console.log('recvd remove event..about to process..');182 console.log('recvd remove event..about to process..');
183 var content = this.model.get('content');183 var content = this.model.get('content');
184 var idx = $(event.target).parent().attr('for');184 var idx = $(event.target).parent().attr('for');
185 idx = Number(idx);
185 idx = Number(idx); //is this a correct way of doing it?
186 console.log('remove content: ', content[idx]);186 console.log('remove content: ', content[idx]);
187 content.splice(idx, 1);187 content.splice(idx, 1);
188 this.model.set({'content': content});188 this.model.set({'content': content});
197 children = (children === '') ? [] : children.split(',');197 children = (children === '') ? [] : children.split(',');
198 this.model.set({'name': name, 'title': title, 'children': children});198 this.model.set({'name': name, 'title': title, 'children': children});
199199
200 if($('#showNav').is(':checked')) {
201 this.model.set({'showNav': true});
202 }
203 else {
204 this.model.set({'showNav': false});
205 }
206
200 this.model.save({}, {207 this.model.save({}, {
201 success: function(model, response) {208 success: function(model, response) {
202 console.log('saved', model, response);209 console.log('saved', model, response);
210 model.set(response.page);
211 model.id = response.page.id;
203 },212 },
204 error: function(model, xhr) {213 error: function(model, xhr) {
205 console.log('failed', model, xhr);214 console.log('failed', model, xhr);
325325
326 /* view to configure custom navigation menu */326 /* view to configure custom navigation menu */
327 var MenuConfigView = Backbone.View.extend({327 var MenuConfigView = Backbone.View.extend({
328 el: '#menu-config',
328 tagName: 'div',
329 id: 'page',
329 events: {330 events: {
331 'change #custom-menu': 'customMenuChange',
332 'click #updateMenu': 'saveMenu'
330 },333 },
331 initialize: function() {334 initialize: function() {
332 _.bindAll(this);335 _.bindAll(this);
333 this.template = _.template($('#menu-config-template').html());336 this.template = _.template($('#menu-config-template').html());
334 },337 },
335 render: function() {338 render: function() {
336 $('#content-container').append(this.template({
337 menu: 'foo'
339 $('#page').remove();
340 $('#content-container').append(this.$el);
341 console.log('rendering..', this.$el);
342 this.$el.html(this.template({
343 pos: this.model.get('pos'),
344 menu: this.model.get('html')
338 }));345 }));
346 this.$menuOptions = $('.menu-options');
347
348 if(this.model.get('customMenu') === true) {
349 $('#custom-menu').attr('checked', true);
350 this.$menuOptions.show();
351 }
352 },
353 showMenuOptions: function(bool) {
354 if(bool === true) {
355 this.$menuOptions.show();
356 }
357 else {
358 this.$menuOptions.hide();
359 }
360 },
361 customMenuChange: function(event) {
362 this.$menuOptions = $('.menu-options');
363 if($('#custom-menu').is(':checked')) {
364 this.model.set({'customMenu': true});
365 }
366 else {
367 this.model.set({'customMenu': false});
368 }
369 this.showMenuOptions(this.model.get('customMenu'));
370 },
371 saveMenu: function() {
372 var menuHTML = $('#menu').val().trim();
373 this.model.set({'html': menuHTML});
374 console.log(this.model.toJSON());
375 this.model.save({}, {
376 success: function(model, response) {
377 },
378 error: function(xhr, response) {
379 }
380 });
339 }381 }
340 });382 });
341383
342 M.editor = {384 M.editor = {
343 init: function() {385 init: function() {
344 M.pages = new Pages(M.site_content);
386 M.pages = new Pages(M.site_content.content);
345 var pagelistview = new PageListView();387 var pagelistview = new PageListView();
346 pagelistview.render();388 pagelistview.render();
347 M.pages.on('add', function(page) {389 M.pages.on('add', function(page) {
348 pagelistview.render();390 pagelistview.render();
349 });391 });
350 M.pagelistview = pagelistview;392 M.pagelistview = pagelistview;
351
352 //var menuconfig = new MenuConfigView();
353 //menuconfig.render();
354 }393 }
355 };394 };
356395
357 function escapeHtml(string) {
396 var escapeHtml = function(string) {
358 var entityMap = {397 var entityMap = {
359 "&": "&amp;",398 "&": "&amp;",
360 "<": "&lt;",399 "<": "&lt;",
403 "/": '&#x2F;'403 "/": '&#x2F;'
404 };404 };
405 return String(string).replace(/[&<>"'\/]/g, function (s) {405 return String(string).replace(/[&<>"'\/]/g, function (s) {
406 return entityMap[s];
407 });
406 return entityMap[s];
407 });
408 }408 }
409409
410})(M);410})(M);

static/js/models.js

86 // model for each Page86 // model for each Page
87 var Page = Backbone.Model.extend({87 var Page = Backbone.Model.extend({
88 defaults: {88 defaults: {
89 name: "index",
89 name: "",
90 title: "",90 title: "",
91 children: [],91 children: [],
92 content: []
92 content: [],
93 showNav: true
93 },94 },
94 initialize: function() {95 initialize: function() {
95 // adding the name of the model as its id.
96 // look up of this model through the collection
97 // is faster this way.
98 //this.set({id: M.sanitize(this.get('name'))});
96 this.id = this.get('id');
99 this.set({id: this.get('id')});97 this.set({id: this.get('id')});
100 }98 }
101 });99 });
102 model: Page102 model: Page
103 });103 });
104104
105 var Menu = Backbone.Model.extend({
106 defaults: {
107 customMenu: false
108 },
109 url: function() {
110 return '/menu/' + this.id;
111 },
112 initialize: function() {
113 this.id = this.get('id');
114 },
115 });
116
105 //export types to the typemap117 //export types to the typemap
106 M.types = M.types || {};118 M.types = M.types || {};
107 M.types.model = {119 M.types.model = {
108 'text': Text,120 'text': Text,
109 'image': Image,121 'image': Image,
110 'video': Video,122 'video': Video,
123 'menu': Menu,
111 'rss': RSS,124 'rss': RSS,
112 'table': Table,125 'table': Table,
113 'plugin': Plugin,126 'plugin': Plugin,

static/js/mouchak.js

13 _.bindAll(this);13 _.bindAll(this);
14 },14 },
15 render: function() {15 render: function() {
16 var navview = new NavigationView();
16 var menu = new M.types.model.menu(M.site_content.menu);
17 var navview = new NavigationView({model: menu});
17 navview.render();18 navview.render();
18 },19 },
19 updateBreadcrumbs: function(event) {20 updateBreadcrumbs: function(event) {
33 this.template = _.template($('#nav-bar-template').html());33 this.template = _.template($('#nav-bar-template').html());
34 },34 },
35 render: function() {35 render: function() {
36 this.$el.append(this.template({}));
37 this.$ul = $('.nav');
38 this.populate();
36 // if custom menu is not defined, render a default menu
37 console.log(this.model.toJSON());
38 if(this.model.get('customMenu') === false) {
39 console.log('generating default menu..');
40 this.$el.append(this.template({}));
41 this.$ul = $('.nav');
42 this.populate();
43 }
44 // else render the custom menu
45 else {
46 console.log('rendering custom menu..');
47 this.$el.append(this.model.get('html'));
48 }
39 },49 },
40 populate: function() {50 populate: function() {
41 var item_template = _.template($('#nav-item-template').html());51 var item_template = _.template($('#nav-item-template').html());
42 _.each(M.pages.models, function(page) {52 _.each(M.pages.models, function(page) {
43 console.log(_.isEmpty(page.get('children')));
53 //console.log('no children?', _.isEmpty(page.get('children')));
44 this.$ul.append(item_template({54 this.$ul.append(item_template({
45 cls: (_.isEmpty(page.get('children'))) ? '' : 'dropdown',55 cls: (_.isEmpty(page.get('children'))) ? '' : 'dropdown',
46 page: page.get('name')56 page: page.get('name')
85 M.rss_view.render();85 M.rss_view.render();
86 }86 }
87 var id = nameIdMap[page];87 var id = nameIdMap[page];
88 if(!id) {
89 this.render404();
90 return;
91 }
88 $('#'+id).show();92 $('#'+id).show();
89 $('.'+page).show();93 $('.'+page).show();
94 if(M.pages.get(id).get('showNav') === false) {
95 $('#navigation').hide();
96 }
97 else {
98 $('#navigation').show();
99 }
100 },
101 render404: function() {
102 $('.pageview').hide();
103 var notFound = "Sorry, a page corresponding to your URL was not found.\n" +
104 "Maybe you have typed the URL wrong, or this page is no longer available.";
105 alert(notFound);
90 }106 }
91});107});
92108
115 M.tags = {}; //global tag cache115 M.tags = {}; //global tag cache
116116
117 // global collection of pages117 // global collection of pages
118 M.pages = new types.model.Pages(M.site_content);
118 M.pages = new types.model.Pages(M.site_content.content);
119119
120 // iterate through pages to get their content and render them using views and120 // iterate through pages to get their content and render them using views and
121 // models121 // models

templates/editor.html

6 <head>6 <head>
7 <meta charset="utf-8">7 <meta charset="utf-8">
8 <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">8 <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
9 <title> Mouchak - ummm!</title>
9 <title> Editing.. | {{title}} </title>
10 <meta name="description" content="">10 <meta name="description" content="">
11 <meta name="viewport" content="width=device-width">11 <meta name="viewport" content="width=device-width">
1212
2424
25 <div class="container" id="container">25 <div class="container" id="container">
26 <div id="header"> </div>26 <div id="header"> </div>
27
28 <div id="content-container"></div>27 <div id="content-container"></div>
29
30 <div id="footer">28 <div id="footer">
31 </div>29 </div>
32 </div>30 </div>
33 31
34
35 <script>32 <script>
36 // initialize editor33 // initialize editor
37 window.M = window.M || {};34 window.M = window.M || {};
5151
52 <!-- Underscore templates -->52 <!-- Underscore templates -->
53 <script type="text/template" id="menu-config-template">53 <script type="text/template" id="menu-config-template">
54 <div id="menu-config">
55 <textarea id="menu">
56 <%= menu %>
57 </textarea>
54 <div class="page">
55 <div class="menu-config">
56 <h4> Menu Config </h4>
57 <form class="form-horizontal">
58 <div class="control-group">
59 <input id="custom-menu" type="checkbox">
60 <span class=""><strong> Custom Menu </strong></span>
61 </div>
62 <button id="updateMenu" type="submit" class="btn btn-primary pull-right"> Update </button>
63 <div class="clearfix"></div>
64 <div class="menu-options" style="display: none;">
65 <div class="control-group">
66 <div class="input-prepend">
67 <span class="add-on"> <strong> Position </strong></span>
68 <input id="pos" type="text" placeholder="[left, top]"
69 value="<%= pos %>">
70 </div>
71 </div>
72 <label><strong> HTML </strong></label>
73 <textarea id="menu">
74 <%= menu %>
75 </textarea>
76 </div>
77 </form>
78 </div>
58 </div>79 </div>
59 </script>80 </script>
6081
83 <div id="pagelistview">83 <div id="pagelistview">
84 <h4> List of Pages </h4>84 <h4> List of Pages </h4>
85 <div id="pagelist"></div>85 <div id="pagelist"></div>
86 <button class="btn btn-primary pull-right" id="addPage">Add Page</button>
87 <p><a href="/" class="btn"> Go to site </a></p>
86 <button class="btn btn-primary" id="addPage">Add Page</button>
87 <hr>
88 <p> <a href="javascript:void(0);" id="menu-config"> Site Menu </a> </p>
89 <p><a href="/"> Go to site </a></p>
88 </div>90 </div>
89 </script>91 </script>
92
90 <script type="text/template" id="page-list-item-template">93 <script type="text/template" id="page-list-item-template">
91 <div class="pagename">94 <div class="pagename">
92 <a class="disp" id="<%= id %>" href="#"><%= name %></a>
95 <a class="disp" id="<%= id %>" href="javascript:void(0);"><%= name %></a>
93 <span class="pull-right">96 <span class="pull-right">
94 <a href="#" class="remove" for="<%= id %>">
97 <a href="javascript:void(0);" class="remove" for="<%= id %>">
95 <i class="icon-trash"></i>98 <i class="icon-trash"></i>
96 </a>99 </a>
97 </span>100 </span>
101 <span class="clearfix"></span>
98 </div>102 </div>
99 </script>103 </script>
100104
126 value="<%= children %>">126 value="<%= children %>">
127 </div> 127 </div>
128 </div>128 </div>
129 <div class="control-group">
130 <div class="input-prepend">
131 <span class="add-on"><strong> Show Navigation </strong></span>
132 <input id="showNav" type="checkbox" <%=checked%>>
133 </div>
134 </div>
129 <label><strong> Content </strong></label>135 <label><strong> Content </strong></label>
130 <div id="content" class="content well">136 <div id="content" class="content well">
131 <%= content %>137 <%= content %>
139 <button class="addContent btn btn-mini btn-primary">Add Content</button>139 <button class="addContent btn btn-mini btn-primary">Add Content</button>
140 </div>140 </div>
141 <button id="updatePage" type="submit" class="btn btn-primary pull-right"> Update </button>141 <button id="updatePage" type="submit" class="btn btn-primary pull-right"> Update </button>
142 <div class="clearfix"></div>
142 </form>143 </form>
143 </div>144 </div>
144 </script>145 </script>
153 </span>153 </span>
154 </span>154 </span>
155 <span class="pull-right">155 <span class="pull-right">
156 <a href="#" class="remove" for="<%=no%>"><i class="icon-trash"></i></a>
156 <a href="javascript:void(0);" class="remove" for="<%=no%>"><i class="icon-trash"></i></a>
157 </span>157 </span>
158 <span class="clearfix"></span>
158 </div>159 </div>
159 </script>160 </script>
160161

templates/index.html

6 <head>6 <head>
7 <meta charset="utf-8">7 <meta charset="utf-8">
8 <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">8 <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
9 <title> Mouchak - ummm!</title>
9 <title> {{title}} </title>
10 <meta name="description" content="">10 <meta name="description" content="">
11 <meta name="viewport" content="width=device-width">11 <meta name="viewport" content="width=device-width">
1212