Commit 63c7919ccc9a6695f72c022f3380671cf67b979f
- Diff rendering mode:
- inline
- side by side
.gitignore
(1 / 0)
  | |||
1 | 1 | ||
2 | 2 | *.*~ | |
3 | *.pyc |
README.md
(57 / 31)
  | |||
1 | 1 | Mouchak | |
2 | 2 | ======= | |
3 | 3 | ||
4 | A Javascript framework for building single page websites or web apps. | ||
4 | A Javascript framework for building websites quickly. | ||
5 | 5 | ||
6 | It takes the content of the website as a JSON. The JSON file can contain | ||
7 | text or multimedia content(images, audio, video). | ||
6 | It aims to provide a visual editing interface to create a website and edit its | ||
7 | content, primarily for non-technical users. | ||
8 | 8 | ||
9 | Mouchak can also load external JS/CSS files through the JSON. This gives the website to load | ||
10 | plugins to enhance/customize the website either through JS or CSS. | ||
9 | Under the hood, Mouchak abstracts the content of the website into a JSON | ||
10 | structure and uses Backbone model and views to render them dynamically. | ||
11 | This makes the site entirely run on the client side and syncs with the server | ||
12 | whenever there is a update. | ||
13 | This also makes Mouchak quite extensible. The built-in types to represent HTML | ||
14 | elements and media and such can be used and extended to build more custom | ||
15 | types. | ||
11 | 16 | ||
17 | Mouchak can also load external JS/CSS files. Any magic that can be done using | ||
18 | Javascript and CSS can be easily integrated into Mouchak. Just specify Mouchak | ||
19 | the plugin to load and the callback to execute. | ||
12 | 20 | ||
21 | Additionally, Mouchak can also provide a semantic rich environment. It provides | ||
22 | the user to specify tags or keywords with each associated content. To | ||
23 | complement it, Mouchak provides an API (filterTags) to get all the related | ||
24 | content together. One can easily built a view if this kind of functionality is | ||
25 | necessary. | ||
26 | |||
27 | |||
13 | 28 | How to use it | |
14 | 29 | ============= | |
15 | 30 | ||
31 | Installing | ||
32 | ---------- | ||
16 | 33 | Either download this codebase or git clone the repo. | |
17 | 34 | ||
18 | Once you have downloaded or cloned the repo, load the index.html file in your browser. | ||
19 | This loads the example content from example.json. | ||
35 | **Pre-requisites** | ||
20 | 36 | ||
21 | Use the index.html file as the boilerplate file of your index.html file. | ||
22 | Modify the code in the script tag, which loads the example.json, and change | ||
23 | the URL to point to your JSON file. | ||
24 | Remember the JSON files is loaded in the client side. Hence your JSON file should | ||
25 | be from the same domain where this app is loaded. | ||
26 | See cross-domain policies for details. | ||
37 | You need to have Python, MongoDB, Flask and PyMongo. | ||
38 | To install python and mongodb on your platform, please use search engines to | ||
39 | find instructions. As they are quite popular softwares, getting help online | ||
40 | should not be difficult. | ||
27 | 41 | ||
42 | To install Flask and PyMongo - | ||
43 | > pip install flask pymongo | ||
44 | |||
45 | Configuring | ||
46 | ----------- | ||
47 | Configuration of Mouchak consists of configuring which database to use, | ||
48 | hostname, port no, title of the website etc. | ||
49 | Open up mouchak.conf, edit the file according to your needs, and then save it. | ||
50 | Ideally, you should set up seperate db for every project. | ||
51 | |||
52 | Running | ||
53 | ------- | ||
54 | Once you have installed all the dependencies, go to the directory where the | ||
55 | code is located and type: | ||
56 | > python server.py | ||
57 | |||
58 | This starts the Mouchak server. You can now point your browser to | ||
59 | [http://localhost:5000](http://localhost:5000) | ||
60 | |||
61 | This will load up and display the website. | ||
62 | To edit the website go to [/edit](http://localhost:5000/edit) | ||
63 | |||
64 | |||
28 | 65 | The global object for this framework is exposed as the variable M. This can be | |
29 | 66 | inspected in the console. | |
30 | 67 | ||
… | … | ||
76 | 76 | (in terms of type like text, audio or image) and also provide the content (in case of text | |
77 | 77 | the content is as it is, in case images urls are given). The JSON should also describe the | |
78 | 78 | content semantically by giving it tags. | |
79 | More details about the JSON format in example.json file. | ||
80 | 79 | ||
80 | Mouchak provides a very simple and easy to use editor to edit the JSON. | ||
81 | |||
81 | 82 | The framework provides an easy way to pull up related content by just specifying the tags. | |
82 | 83 | ||
83 | 84 | Backbone models are used to model the content. Different content has different types. | |
… | … | ||
98 | 98 | Mouchak also leverages powerful libraries like Backbone.js and Underscore.js to manage and render | |
99 | 99 | content. This gives more flexibility to the content of the website. | |
100 | 100 | ||
101 | The main code resides in js/mouchak.js. The HTML markup it uses is in index.html. | ||
102 | 101 | ||
103 | Javascript libary files are in js/lib. We use backbone.js, underscore.js and jquery in this | ||
104 | framework. | ||
105 | |||
106 | Boilerplate code/files: | ||
107 | 404.html - error template | ||
108 | crossdomain.xml - cross-domain policies to be obeyed by the client | ||
109 | css/bootstrap.css - boilerplate css | ||
110 | css/normalize.css - boilerplate css | ||
111 | css/main.css - boilerplate css | ||
112 | humans.txt - write your own credits | ||
113 | img/ - directory for images | ||
114 | robots.txt - crawl spider rules | ||
115 | |||
116 | |||
117 | 102 | Support | |
118 | 103 | ======= | |
119 | 104 | ||
120 | Email to rayanon at janastu dot org / arvind at janastu dot org for any kind of feedback. | ||
105 | Email to rayanon at servelots dot com / arvind at servelots dot com for any kind of feedback. | ||
121 | 106 | ||
122 | 107 | ||
123 | 108 | Issues | |
124 | 109 | ====== | |
125 | 110 | ||
126 | Report issues [here](http://bugzilla.pantoto.org/bugzilla3/describecomponents.cgi?product=Mouchak) | ||
111 | Report issues [here](http://trac.pantoto.org/mouchak/) | ||
112 | First, check if your issue is already submitted by anyone else, by clicking on | ||
113 | "View Tickets". | ||
114 | If your issue is not reported, you can report it by clicking on "New Ticket". |
mouchak.conf
(4 / 0)
  | |||
1 | DB=test_mouchak | ||
2 | SITE_TITLE=Testing Mouchak | ||
3 | HOST=0.0.0.0 | ||
4 | PORT=5000 |
readConfig.py
(22 / 0)
  | |||
1 | #!/usr/bin/python | ||
2 | |||
3 | # Read Mouchak Configuration | ||
4 | |||
5 | |||
6 | import re | ||
7 | |||
8 | def 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
(79 / 25)
  | |||
1 | 1 | #!/usr/bin/python | |
2 | |||
2 | 3 | # Mouchak Server - | |
3 | 4 | # A Flask Application (http://flask.pocoo.org/) | |
4 | 5 | ||
5 | 6 | import flask | |
6 | 7 | import pymongo | |
7 | 8 | import bson | |
9 | import readConfig | ||
8 | 10 | ||
9 | 11 | app = flask.Flask(__name__) | |
10 | 12 | ||
13 | |||
14 | config = readConfig.readConfig() | ||
15 | |||
11 | 16 | dbClient = pymongo.MongoClient() | |
12 | db = dbClient['mouchak'] | ||
13 | collection = db['content'] | ||
17 | db = dbClient[config['db']] | ||
18 | siteContent = db['content'] | ||
19 | siteMenu = db['menu'] | ||
20 | if siteMenu.find_one() == None: | ||
21 | siteMenu.insert({'customMenu': False}) | ||
22 | |||
14 | 23 | # handy reference to otherwise long name | |
15 | 24 | bson.ObjId = bson.objectid.ObjectId | |
16 | 25 | ||
26 | |||
17 | 27 | def getContent(): | |
18 | 28 | content = [] | |
19 | for i in collection.find(): | ||
29 | for i in siteContent.find(): | ||
20 | 30 | objId = bson.ObjId(i['_id']) | |
21 | 31 | del(i['_id']) | |
22 | 32 | i['id'] = str(objId) | |
23 | 33 | content.append(i) | |
24 | return content | ||
25 | 34 | ||
35 | menu = siteMenu.find_one() | ||
36 | objId = bson.ObjId(menu['_id']) | ||
37 | del(menu['_id']) | ||
38 | menu['id'] = str(objId) | ||
26 | 39 | ||
40 | return {'content': content, 'menu': menu} | ||
41 | |||
42 | |||
43 | |||
27 | 44 | @app.route('/', methods=['GET']) | |
28 | 45 | def index(): | |
29 | return flask.render_template('index.html', content=getContent()) | ||
46 | return flask.render_template('index.html', content=getContent(), | ||
47 | title=config['site_title']) | ||
30 | 48 | ||
31 | 49 | ||
32 | @app.route('/edit', methods=['GET', 'POST']) | ||
50 | @app.route('/edit', methods=['GET']) | ||
33 | 51 | def 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']) | ||
36 | 54 | ||
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()) | ||
43 | 55 | ||
44 | |||
45 | @app.route('/edit/<_id>', methods=['PUT', 'DELETE']) | ||
46 | def editPage(_id): | ||
56 | @app.route('/page', methods=['POST']) | ||
57 | def 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']) | ||
70 | def updatePage(_id): | ||
47 | 71 | if flask.request.method == 'PUT': | |
48 | 72 | changedPage = flask.request.json | |
49 | 73 | print changedPage | |
50 | res = collection.update({'_id' : bson.ObjId(_id)}, | ||
74 | print '=======' | ||
75 | res = siteContent.update({'_id': bson.ObjId(_id)}, | ||
51 | 76 | changedPage) | |
52 | 77 | 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) | ||
57 | 81 | ||
58 | 82 | elif flask.request.method == 'DELETE': | |
59 | 83 | delPage = flask.request.url | |
60 | 84 | print delPage | |
61 | 85 | print _id | |
62 | res = collection.remove({'_id': bson.ObjId(_id)}) | ||
86 | res = siteContent.remove({'_id': bson.ObjId(_id)}) | ||
63 | 87 | 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') | ||
65 | 92 | ||
66 | 93 | ||
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']) | ||
104 | def 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 | |||
67 | 120 | if __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
(5 / 2)
  | |||
97 | 97 | border: 1px solid #999; | |
98 | 98 | padding: 20px; | |
99 | 99 | width: 400px; | |
100 | height: 400px; | ||
100 | /*height: 400px;*/ | ||
101 | 101 | } | |
102 | 102 | #pages { | |
103 | 103 | position: absolute; | |
… | … | ||
106 | 106 | border: 1px solid black; | |
107 | 107 | padding: 10px; | |
108 | 108 | width: 300px; | |
109 | height: 80%; | ||
109 | /*height: 80%;*/ | ||
110 | 110 | } | |
111 | 111 | #page { | |
112 | 112 | position: absolute; | |
… | … | ||
137 | 137 | #specific-content { | |
138 | 138 | padding: 3px; | |
139 | 139 | margin-bottom: 10px; | |
140 | } | ||
141 | #addPage { | ||
142 | margin: 10px 0 0 200px; | ||
140 | 143 | } | |
141 | 144 | ||
142 | 145 | /* ========================================================================== |
static/css/main.css
(0 / 1)
  | |||
94 | 94 | ========================================================================== */ | |
95 | 95 | ||
96 | 96 | ||
97 | |||
98 | 97 | /* ========================================================================== | |
99 | 98 | Helper classes | |
100 | 99 | ========================================================================== */ |
static/js/editor.js
(73 / 30)
  | |||
8 | 8 | events: { | |
9 | 9 | 'click .pagename .disp': 'showPage', | |
10 | 10 | 'click #addPage': 'addPage', | |
11 | 'click .pagename .remove': 'removePage' | ||
11 | 'click .pagename .remove': 'removePage', | ||
12 | 'click #menu-config': 'showMenu' | ||
12 | 13 | }, | |
13 | 14 | initialize: function() { | |
14 | 15 | _.bindAll(this); | |
… | … | ||
20 | 20 | $('#content-container').append(this.$el); | |
21 | 21 | this.$el.append(this.template()); | |
22 | 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 | 28 | render: function() { | |
25 | 29 | // append the page list | |
… | … | ||
42 | 42 | M.editor.pageview = pageview; | |
43 | 43 | }, | |
44 | 44 | addPage: function() { | |
45 | var newpage = new Page(); | ||
45 | var newpage = new M.types.model.Page(); | ||
46 | 46 | M.pages.add(newpage); | |
47 | 47 | var newpageview = new PageView({model: newpage}); | |
48 | 48 | newpageview.render(); | |
… | … | ||
64 | 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 | }); | |
81 | 72 | ||
82 | 73 | var Pages = Backbone.Collection.extend({ | |
83 | model: Page, | ||
84 | url: '/edit' | ||
74 | model: M.types.model.Page, | ||
75 | url: '/page' | ||
85 | 76 | }); | |
86 | 77 | ||
87 | 78 | /* view to manage each page and their properties - change page properties, | |
… | … | ||
111 | 111 | console.log('name changed', page); | |
112 | 112 | }, | |
113 | 113 | render: function() { | |
114 | console.log(this.$el); | ||
115 | 114 | $('#page').html(''); | |
116 | console.log('content: ', this.model.get('content')); | ||
115 | //console.log('content: ', this.model.get('content')); | ||
117 | 116 | ||
118 | 117 | this.$el.html(this.template({ | |
119 | 118 | name: this.model.get('name'), | |
120 | 119 | title: this.model.get('title'), | |
121 | 120 | children: this.model.get('children'), | |
122 | content: this.listContent() | ||
121 | content: this.listContent(), | ||
122 | checked: this.model.get('showNav') ? 'checked="checked"' : '' | ||
123 | 123 | })); | |
124 | 124 | ||
125 | 125 | //hover effect | |
… | … | ||
128 | 128 | }, function(event) { | |
129 | 129 | $(event.target).closest('.content-item').removeClass('alert-error') | |
130 | 130 | }); | |
131 | console.log('done'); | ||
132 | 131 | }, | |
133 | 132 | listContent: function() { | |
134 | 133 | var content = ''; | |
… | … | ||
182 | 182 | console.log('recvd remove event..about to process..'); | |
183 | 183 | var content = this.model.get('content'); | |
184 | 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 | 186 | console.log('remove content: ', content[idx]); | |
187 | 187 | content.splice(idx, 1); | |
188 | 188 | this.model.set({'content': content}); | |
… | … | ||
197 | 197 | children = (children === '') ? [] : children.split(','); | |
198 | 198 | this.model.set({'name': name, 'title': title, 'children': children}); | |
199 | 199 | ||
200 | if($('#showNav').is(':checked')) { | ||
201 | this.model.set({'showNav': true}); | ||
202 | } | ||
203 | else { | ||
204 | this.model.set({'showNav': false}); | ||
205 | } | ||
206 | |||
200 | 207 | this.model.save({}, { | |
201 | 208 | success: function(model, response) { | |
202 | 209 | console.log('saved', model, response); | |
210 | model.set(response.page); | ||
211 | model.id = response.page.id; | ||
203 | 212 | }, | |
204 | 213 | error: function(model, xhr) { | |
205 | 214 | console.log('failed', model, xhr); | |
… | … | ||
325 | 325 | ||
326 | 326 | /* view to configure custom navigation menu */ | |
327 | 327 | var MenuConfigView = Backbone.View.extend({ | |
328 | el: '#menu-config', | ||
328 | tagName: 'div', | ||
329 | id: 'page', | ||
329 | 330 | events: { | |
331 | 'change #custom-menu': 'customMenuChange', | ||
332 | 'click #updateMenu': 'saveMenu' | ||
330 | 333 | }, | |
331 | 334 | initialize: function() { | |
332 | 335 | _.bindAll(this); | |
333 | 336 | this.template = _.template($('#menu-config-template').html()); | |
334 | 337 | }, | |
335 | 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 | }); | |
341 | 383 | ||
342 | 384 | M.editor = { | |
343 | 385 | init: function() { | |
344 | M.pages = new Pages(M.site_content); | ||
386 | M.pages = new Pages(M.site_content.content); | ||
345 | 387 | var pagelistview = new PageListView(); | |
346 | 388 | pagelistview.render(); | |
347 | 389 | M.pages.on('add', function(page) { | |
348 | 390 | pagelistview.render(); | |
349 | 391 | }); | |
350 | 392 | M.pagelistview = pagelistview; | |
351 | |||
352 | //var menuconfig = new MenuConfigView(); | ||
353 | //menuconfig.render(); | ||
354 | 393 | } | |
355 | 394 | }; | |
356 | 395 | ||
357 | function escapeHtml(string) { | ||
396 | var escapeHtml = function(string) { | ||
358 | 397 | var entityMap = { | |
359 | 398 | "&": "&", | |
360 | 399 | "<": "<", | |
… | … | ||
403 | 403 | "/": '/' | |
404 | 404 | }; | |
405 | 405 | return String(string).replace(/[&<>"'\/]/g, function (s) { | |
406 | return entityMap[s]; | ||
407 | }); | ||
406 | return entityMap[s]; | ||
407 | }); | ||
408 | 408 | } | |
409 | 409 | ||
410 | 410 | })(M); |
static/js/models.js
(17 / 6)
  | |||
86 | 86 | // model for each Page | |
87 | 87 | var Page = Backbone.Model.extend({ | |
88 | 88 | defaults: { | |
89 | name: "index", | ||
89 | name: "", | ||
90 | 90 | title: "", | |
91 | 91 | children: [], | |
92 | content: [] | ||
92 | content: [], | ||
93 | showNav: true | ||
93 | 94 | }, | |
94 | 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 | 97 | this.set({id: this.get('id')}); | |
100 | 98 | } | |
101 | 99 | }); | |
… | … | ||
102 | 102 | model: Page | |
103 | 103 | }); | |
104 | 104 | ||
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 | 117 | //export types to the typemap | |
106 | 118 | M.types = M.types || {}; | |
107 | 119 | M.types.model = { | |
108 | 120 | 'text': Text, | |
109 | 121 | 'image': Image, | |
110 | 122 | 'video': Video, | |
123 | 'menu': Menu, | ||
111 | 124 | 'rss': RSS, | |
112 | 125 | 'table': Table, | |
113 | 126 | 'plugin': Plugin, |
static/js/mouchak.js
(33 / 6)
  | |||
13 | 13 | _.bindAll(this); | |
14 | 14 | }, | |
15 | 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 | 18 | navview.render(); | |
18 | 19 | }, | |
19 | 20 | updateBreadcrumbs: function(event) { | |
… | … | ||
33 | 33 | this.template = _.template($('#nav-bar-template').html()); | |
34 | 34 | }, | |
35 | 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 | 50 | populate: function() { | |
41 | 51 | var item_template = _.template($('#nav-item-template').html()); | |
42 | 52 | _.each(M.pages.models, function(page) { | |
43 | console.log(_.isEmpty(page.get('children'))); | ||
53 | //console.log('no children?', _.isEmpty(page.get('children'))); | ||
44 | 54 | this.$ul.append(item_template({ | |
45 | 55 | cls: (_.isEmpty(page.get('children'))) ? '' : 'dropdown', | |
46 | 56 | page: page.get('name') | |
… | … | ||
85 | 85 | M.rss_view.render(); | |
86 | 86 | } | |
87 | 87 | var id = nameIdMap[page]; | |
88 | if(!id) { | ||
89 | this.render404(); | ||
90 | return; | ||
91 | } | ||
88 | 92 | $('#'+id).show(); | |
89 | 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 | }); | |
92 | 108 | ||
… | … | ||
115 | 115 | M.tags = {}; //global tag cache | |
116 | 116 | ||
117 | 117 | // 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); | ||
119 | 119 | ||
120 | 120 | // iterate through pages to get their content and render them using views and | |
121 | 121 | // models |
templates/editor.html
(43 / 13)
  | |||
6 | 6 | <head> | |
7 | 7 | <meta charset="utf-8"> | |
8 | 8 | <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> | |
9 | <title> Mouchak - ummm!</title> | ||
9 | <title> Editing.. | {{title}} </title> | ||
10 | 10 | <meta name="description" content=""> | |
11 | 11 | <meta name="viewport" content="width=device-width"> | |
12 | 12 | ||
… | … | ||
24 | 24 | ||
25 | 25 | <div class="container" id="container"> | |
26 | 26 | <div id="header"> </div> | |
27 | |||
28 | 27 | <div id="content-container"></div> | |
29 | |||
30 | 28 | <div id="footer"> | |
31 | 29 | </div> | |
32 | 30 | </div> | |
33 | 31 | ||
34 | |||
35 | 32 | <script> | |
36 | 33 | // initialize editor | |
37 | 34 | window.M = window.M || {}; | |
… | … | ||
51 | 51 | ||
52 | 52 | <!-- Underscore templates --> | |
53 | 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 | 79 | </div> | |
59 | 80 | </script> | |
60 | 81 | ||
… | … | ||
83 | 83 | <div id="pagelistview"> | |
84 | 84 | <h4> List of Pages </h4> | |
85 | 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 | 90 | </div> | |
89 | 91 | </script> | |
92 | |||
90 | 93 | <script type="text/template" id="page-list-item-template"> | |
91 | 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 | 96 | <span class="pull-right"> | |
94 | <a href="#" class="remove" for="<%= id %>"> | ||
97 | <a href="javascript:void(0);" class="remove" for="<%= id %>"> | ||
95 | 98 | <i class="icon-trash"></i> | |
96 | 99 | </a> | |
97 | 100 | </span> | |
101 | <span class="clearfix"></span> | ||
98 | 102 | </div> | |
99 | 103 | </script> | |
100 | 104 | ||
… | … | ||
126 | 126 | value="<%= children %>"> | |
127 | 127 | </div> | |
128 | 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 | 135 | <label><strong> Content </strong></label> | |
130 | 136 | <div id="content" class="content well"> | |
131 | 137 | <%= content %> | |
… | … | ||
139 | 139 | <button class="addContent btn btn-mini btn-primary">Add Content</button> | |
140 | 140 | </div> | |
141 | 141 | <button id="updatePage" type="submit" class="btn btn-primary pull-right"> Update </button> | |
142 | <div class="clearfix"></div> | ||
142 | 143 | </form> | |
143 | 144 | </div> | |
144 | 145 | </script> | |
… | … | ||
153 | 153 | </span> | |
154 | 154 | </span> | |
155 | 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 | 157 | </span> | |
158 | <span class="clearfix"></span> | ||
158 | 159 | </div> | |
159 | 160 | </script> | |
160 | 161 |
templates/index.html
(1 / 1)
  | |||
6 | 6 | <head> | |
7 | 7 | <meta charset="utf-8"> | |
8 | 8 | <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> | |
9 | <title> Mouchak - ummm!</title> | ||
9 | <title> {{title}} </title> | ||
10 | 10 | <meta name="description" content=""> | |
11 | 11 | <meta name="viewport" content="width=device-width"> | |
12 | 12 |