├── .gitattributes ├── app ├── robots.txt ├── favicon.ico ├── scripts │ ├── collection │ │ └── LibraryCollection.js │ ├── component │ │ ├── AboutComponent.react.js │ │ ├── IndexComponent.react.js │ │ ├── ProjectComponent.react.js │ │ ├── ProductComponent.react.js │ │ ├── MenuComponent.react.js │ │ └── LibraryComponent.react.js │ ├── model │ │ ├── UserModel.js │ │ └── LibraryModel.js │ ├── data │ │ ├── navigation.js │ │ └── libraries.js │ ├── main.js │ ├── app.react.js │ ├── vendor │ │ ├── jquery.touchwipe.min.js │ │ ├── mdown.js │ │ ├── json.js │ │ ├── jsx.js │ │ ├── jquery.sidr.min.js │ │ ├── react.backbone.js │ │ ├── text.js │ │ ├── lodash.min.js │ │ └── Markdown.Converter.js │ └── router.react.js └── styles │ ├── grids-responsive-old-ie-min.css │ ├── main.css │ └── pure-min.css ├── .bowerrc ├── test ├── .bowerrc ├── bower.json ├── spec │ └── test.js └── index.html ├── .gitignore ├── .yo-rc.json ├── .editorconfig ├── bower.json ├── README.md ├── .jshintrc ├── package.json ├── index.html └── Gruntfile.js /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /app/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org 2 | 3 | User-agent: * 4 | -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "app/bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /test/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phodal/backbone-react/gh-pages/app/favicon.ico -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | test/temp 4 | .sass-cache 5 | app/bower_components 6 | .tmp 7 | test/bower_components/ 8 | .idea/ 9 | -------------------------------------------------------------------------------- /test/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backbone-react", 3 | "private": true, 4 | "dependencies": { 5 | "chai": "~1.8.0", 6 | "mocha": "~1.14.0" 7 | }, 8 | "devDependencies": {} 9 | } 10 | -------------------------------------------------------------------------------- /app/scripts/collection/LibraryCollection.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(['backbone', '../model/LibraryModel'], function(Backbone, LibraryModel) { 4 | var LibraryCollection = Backbone.Collection.extend({ 5 | model: LibraryModel 6 | }); 7 | 8 | return LibraryCollection; 9 | }); 10 | -------------------------------------------------------------------------------- /.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-backbone": { 3 | "appPath": "app", 4 | "appName": "BackboneReact", 5 | "coffee": false, 6 | "testFramework": "mocha", 7 | "templateFramework": "lodash", 8 | "sassBootstrap": false, 9 | "includeRequireJS": true 10 | }, 11 | "generator-mocha": {} 12 | } -------------------------------------------------------------------------------- /app/scripts/component/AboutComponent.react.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define([ 4 | 'react' 5 | ],function(React){ 6 | return React.createClass({ 7 | render: function () { 8 | return
9 | About Component 10 |
; 11 | } 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /test/spec/test.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | (function () { 4 | 'use strict'; 5 | 6 | describe('Give it some context', function () { 7 | describe('maybe a bit more context here', function () { 8 | it('should run here few assertions', function () { 9 | 10 | }); 11 | }); 12 | }); 13 | })(); 14 | -------------------------------------------------------------------------------- /app/scripts/model/UserModel.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(['backbone'], function(Backbone) { 4 | var UserModel = Backbone.Model.extend({ 5 | initialize : function(name) { 6 | this.name = name; 7 | }, 8 | defaults:{ 9 | name:null 10 | } 11 | }); 12 | 13 | return UserModel; 14 | }); 15 | -------------------------------------------------------------------------------- /app/scripts/component/IndexComponent.react.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define([ 4 | 'react', 5 | 'mdown!../../../README.md' 6 | ], function (React, about) { 7 | return React.createClass({ 8 | render: function () { 9 | this.info = about; 10 | return (
); 11 | } 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /app/scripts/model/LibraryModel.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(['backbone'], function(Backbone) { 4 | var LibraryModel = Backbone.Model.extend({ 5 | initialize : function(name, url) { 6 | this.name = name; 7 | this.url = url; 8 | }, 9 | defaults:{ 10 | name:null, 11 | url: null 12 | } 13 | }); 14 | 15 | return LibraryModel; 16 | }); 17 | -------------------------------------------------------------------------------- /app/scripts/component/ProjectComponent.react.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define([ 4 | 'react', 5 | 'react.backbone' 6 | ],function(React){ 7 | return React.createBackboneClass({ 8 | changeOptions: 'change:name', 9 | render: function () { 10 | return ( 11 |
12 |

{this.getModel().get('name')}

13 | Project 14 |
); 15 | } 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 4 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /app/scripts/component/ProductComponent.react.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define([ 4 | 'react', 5 | 'jsx!../component/AboutComponent.react', 6 | 'jsx!../component/IndexComponent.react' 7 | ],function(React, AboutComponent, IndexComponent){ 8 | return React.createClass({ 9 | render: function () { 10 | return ( 11 |
12 | 13 | 14 |
); 15 | } 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backbonereact", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "jquery": "~2.1.0", 6 | "lodash": "~2.4.1", 7 | "backbone": "~1.1.0", 8 | "requirejs": "~2.1.10", 9 | "requirejs-text": "~2.0.10", 10 | "modernizr": "~2.7.1", 11 | "react.backbone": "~0.6.0" 12 | }, 13 | "devDependencies": { 14 | "react": "~0.13.1", 15 | "requirejs-plugins": "~1.0.3", 16 | "jsx-requirejs-plugin": "~0.6.0", 17 | "jquery": "~2.1.3", 18 | "pure": "~0.6.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/scripts/data/navigation.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(function () { 4 | return [ 5 | { 6 | name: 'home', 7 | aliasName: 'Home' 8 | }, 9 | { 10 | name: 'about', 11 | aliasName: 'About' 12 | }, 13 | { 14 | name: 'product', 15 | aliasName: '产品' 16 | }, 17 | { 18 | name: 'library', 19 | aliasName: 'Library' 20 | }, 21 | { 22 | name: 'project', 23 | aliasName: 'Project' 24 | } 25 | ]; 26 | }); 27 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Mocha Spec Runner 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ##Learning React with Backbone 2 | 3 | A Rewrite of [http://cms.moqi.mobi](http://cms.moqi.mobi) in React.js 4 | 5 | **Github**: [https://github.com/phodal/backbone-react](https://github.com/phodal/backbone-react) 6 | 7 | ###DEPENDENCIES 8 | 9 | - jQuery 10 | - Lodash 11 | - Backbone 12 | - Requirejs 13 | - Requirejs-Text 14 | - Modernizr 15 | - React.Backbone 16 | 17 | ###博客 18 | 19 | - [Backbone React Requirejs 应用实战(一)——RequireJS管理React依赖](http://www.phodal.com/blog/requirejs-react-backbone-build-application/) 20 | - [Backbone React Requirejs 应用实战(二)——使用Backbone Model](http://www.phodal.com/blog/requirejs-react-backbone-build-application-use-backbone-model/) 21 | - [Backbone React Requirejs 应用实战(三)——创建MenuComponent与SideMenu](http://www.phodal.com/blog/requirejs-react-backbone-build-application-add-sidemenu/) -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "camelcase": true, 7 | "curly": true, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 4, 11 | "latedef": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "undef": true, 15 | "unused": true, 16 | "strict": true, 17 | "jquery": true, 18 | "globals": { 19 | "BackboneReact": true, 20 | "backbonereact": true, 21 | "_": false, 22 | "Backbone": false, 23 | "JST": false, 24 | "beforeEach": false, 25 | "describe": false, 26 | "it": false, 27 | "assert": true, 28 | "expect": true, 29 | "should": true, 30 | "require": false, 31 | "define": false 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/scripts/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require.config({ 4 | paths: { 5 | jquery: 'vendor/jquery.min', 6 | jquerySidr: 'vendor/jquery.sidr.min', 7 | touchwipe: 'vendor/jquery.touchwipe.min', 8 | 9 | react: 'vendor/react-with-addons.min', 10 | "JSXTransformer": 'vendor/JSXTransformer', 11 | jsx: 'vendor/jsx', 12 | 'react.backbone': 'vendor/react.backbone', 13 | 14 | backbone: 'vendor/backbone', 15 | underscore: 'vendor/lodash.min', 16 | 17 | text: 'vendor/text', 18 | json: 'vendor/json', 19 | mdown: 'vendor/mdown', 20 | markdownConverter: 'vendor/Markdown.Converter' 21 | }, 22 | shim: { 23 | jquerySidr:['jquery'], 24 | touchwipe: ['jquery'], 25 | underscore: { 26 | exports: '_' 27 | } 28 | } 29 | }); 30 | 31 | require([ 32 | 'backbone', 'jsx!app.react' 33 | ], function (Backbone, App) { 34 | App.initialize(); 35 | }); 36 | -------------------------------------------------------------------------------- /app/scripts/app.react.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define([ 4 | 'backbone', 'react', 'jsx!router.react', 'react.backbone', 'jquery', 'jquerySidr', 'touchwipe', 5 | 'jsx!component/MenuComponent.react', 6 | 'data/navigation' 7 | ], function (Backbone, React, Router, ReactBackbone, $, jquerySidr, touchwipe, MenuComponent, navigation) { 8 | 9 | var initialize = function () { 10 | $(window).touchwipe({ 11 | wipeLeft: function() { 12 | $.sidr('close'); 13 | }, 14 | wipeRight: function() { 15 | $.sidr('open'); 16 | }, 17 | preventDefaultEvents: false 18 | }); 19 | $(document).ready(function() { 20 | $('#sidr').show(); 21 | $('#menu').sidr(); 22 | }); 23 | 24 | React.render(, document.getElementById('sidr')); 25 | new Router(); 26 | }; 27 | 28 | return { 29 | initialize: initialize 30 | }; 31 | }); 32 | -------------------------------------------------------------------------------- /app/scripts/data/libraries.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(function () { 4 | return [ 5 | { name: 'Backbone.js', url: 'http://documentcloud.github.io/backbone/'}, 6 | { name: 'AngularJS', url: 'https://angularjs.org/'}, 7 | { name: 'jQuery', url: 'http://jquery.com/'}, 8 | { name: 'Prototype', url: 'http://www.prototypejs.org/'}, 9 | { name: 'React', url: 'http://facebook.github.io/react/'}, 10 | { name: 'Ember', url: 'http://emberjs.com/'}, 11 | { name: 'Knockout.js', url: 'http://knockoutjs.com/'}, 12 | { name: 'Dojo', url: 'http://dojotoolkit.org/'}, 13 | { name: 'Mootools', url: 'http://mootools.net/'}, 14 | { name: 'Underscore', url: 'http://documentcloud.github.io/underscore/'}, 15 | { name: 'Lodash', url: 'http://lodash.com/'}, 16 | { name: 'Moment', url: 'http://momentjs.com/'}, 17 | { name: 'Express', url: 'http://expressjs.com/'}, 18 | { name: 'Koa', url: 'http://koajs.com/'} 19 | ]; 20 | }); 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backbonereact", 3 | "version": "0.0.0", 4 | "dependencies": {}, 5 | "devDependencies": { 6 | "apache-server-configs": "^2.8.0", 7 | "connect-livereload": "^0.4.0", 8 | "grunt": "^0.4.5", 9 | "grunt-bower-requirejs": "^1.1.0", 10 | "grunt-contrib-clean": "^0.6.0", 11 | "grunt-contrib-concat": "^0.5.0", 12 | "grunt-contrib-connect": "^0.8.0", 13 | "grunt-contrib-copy": "^0.5.0", 14 | "grunt-contrib-cssmin": "^0.10.0", 15 | "grunt-contrib-htmlmin": "^0.3.0", 16 | "grunt-contrib-imagemin": "^0.9.2", 17 | "grunt-contrib-jshint": "^0.10.0", 18 | "grunt-contrib-jst": "^0.6.0", 19 | "grunt-contrib-uglify": "^0.6.0", 20 | "grunt-contrib-watch": "^0.6.1", 21 | "grunt-jsxhint": "^0.5.0", 22 | "grunt-mocha": "^0.4.11", 23 | "grunt-open": "^0.2.3", 24 | "grunt-requirejs": "^0.4.2", 25 | "grunt-rev": "^0.1.0", 26 | "grunt-usemin": "^2.4.0", 27 | "jshint-stylish": "^1.0.0", 28 | "load-grunt-tasks": "^0.6.0", 29 | "react-tools": "^0.13.1", 30 | "time-grunt": "^1.0.0" 31 | }, 32 | "engines": { 33 | "node": ">=0.10.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/scripts/component/MenuComponent.react.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define([ 4 | 'react', 5 | 'jquery' 6 | ],function(React, $){ 7 | return React.createClass({ 8 | getInitialState: function () { 9 | return {focused: 0}; 10 | }, 11 | 12 | clicked: function (index) { 13 | this.setState({focused: index}); 14 | $.sidr('close'); 15 | }, 16 | 17 | render: function () { 18 | var self = this; 19 | return ( 20 |
21 | 34 |
35 | ); 36 | 37 | } 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Backbone React in Practice 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 19 |
20 | 21 | 26 |
27 |
28 | 29 |
30 |
31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/scripts/component/LibraryComponent.react.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define([ 4 | 'react' 5 | ], function (React) { 6 | return React.createClass({ 7 | getInitialState: function () { 8 | return {searchString: ''}; 9 | }, 10 | 11 | handleChange: function (e) { 12 | this.setState({searchString: e.target.value}); 13 | }, 14 | 15 | render: function () { 16 | 17 | var libraries = this.props.items, 18 | searchString = this.state.searchString.trim().toLowerCase(); 19 | 20 | if (searchString.length > 0) { 21 | libraries = libraries.filter(function (l) { 22 | return l.name.toLowerCase().match(searchString); 23 | }); 24 | } 25 | 26 | return
27 | 29 | 30 | 39 | 40 |
; 41 | 42 | } 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /app/scripts/vendor/jquery.touchwipe.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jQuery Plugin to obtain touch gestures from iPhone, iPod Touch and iPad, should also work with Android mobile phones (not tested yet!) 3 | * Common usage: wipe images (left and right to show the previous or next image) 4 | * 5 | * @author Andreas Waltl, netCU Internetagentur (http://www.netcu.de) 6 | * @version 1.1.1 (9th December 2010) - fix bug (older IE's had problems) 7 | * @version 1.1 (1st September 2010) - support wipe up and wipe down 8 | * @version 1.0 (15th July 2010) 9 | */ 10 | (function($){$.fn.touchwipe=function(settings){var config={min_move_x:20,min_move_y:20,wipeLeft:function(){},wipeRight:function(){},wipeUp:function(){},wipeDown:function(){},preventDefaultEvents:true};if(settings)$.extend(config,settings);this.each(function(){var startX;var startY;var isMoving=false;function cancelTouch(){this.removeEventListener('touchmove',onTouchMove);startX=null;isMoving=false}function onTouchMove(e){if(config.preventDefaultEvents){e.preventDefault()}if(isMoving){var x=e.touches[0].pageX;var y=e.touches[0].pageY;var dx=startX-x;var dy=startY-y;if(Math.abs(dx)>=config.min_move_x){cancelTouch();if(dx>0){config.wipeLeft()}else{config.wipeRight()}}else if(Math.abs(dy)>=config.min_move_y){cancelTouch();if(dy>0){config.wipeDown()}else{config.wipeUp()}}}}function onTouchStart(e){if(e.touches.length==1){startX=e.touches[0].pageX;startY=e.touches[0].pageY;isMoving=true;this.addEventListener('touchmove',onTouchMove,false)}}if('ontouchstart'in document.documentElement){this.addEventListener('touchstart',onTouchStart,false)}});return this}})(jQuery); -------------------------------------------------------------------------------- /app/scripts/router.react.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define([ 4 | 'underscore', 5 | 'backbone', 6 | 'react', 7 | 'jsx!component/IndexComponent.react', 8 | 'jsx!component/AboutComponent.react', 9 | 'jsx!component/ProductComponent.react', 10 | 'jsx!component/ProjectComponent.react', 11 | 'jsx!component/LibraryComponent.react', 12 | 'model/UserModel', 13 | 'data/libraries' 14 | ],function(_, Backbone, React, IndexComponent, AboutComponent, ProductComponent, ProjectComponent, LibraryComponent, UserModel, libraries){ 15 | var AppRouter = Backbone.Router.extend({ 16 | index: function(){ 17 | React.render( , document.getElementById('main_content')); 18 | }, 19 | about: function(){ 20 | React.render( , document.getElementById('main_content')); 21 | }, 22 | product: function(){ 23 | React.render( , document.getElementById('main_content')); 24 | }, 25 | library: function(){ 26 | React.render( , document.getElementById('main_content')); 27 | }, 28 | project: function(){ 29 | var user = new UserModel({name: 'phodal'}); 30 | var UserView = React.createFactory(ProjectComponent); 31 | var userView = new UserView({model: user}); 32 | React.render(userView, document.getElementById('main_content')); 33 | }, 34 | initialize: function() { 35 | var self = this, 36 | routes = [ 37 | [ /^.*$/, 'index' ], 38 | [ 'about', 'about' ], 39 | [ 'product', 'product' ], 40 | [ 'project', 'project' ], 41 | [ 'library', 'library' ] 42 | ]; 43 | 44 | _.each(routes, function(route) { 45 | self.route.apply(self, route); 46 | }); 47 | Backbone.history.start(); 48 | } 49 | }); 50 | 51 | return AppRouter; 52 | }); 53 | -------------------------------------------------------------------------------- /app/scripts/vendor/mdown.js: -------------------------------------------------------------------------------- 1 | /** @license 2 | * RequireJS plugin for loading Markdown files and converting them into HTML. 3 | * Author: Miller Medeiros 4 | * Version: 0.1.1 (2012/02/17) 5 | * Released under the MIT license 6 | */ 7 | 8 | // NOTE :: if you don't need to load markdown files in production outside of 9 | // the build, precompile them into modules and set 10 | // `pragmasOnSave.excludeMdown=true` 11 | 12 | define( 13 | [ 14 | //>>excludeStart('excludeMdown', pragmas.excludeMdown) 15 | 'text', 16 | 'markdownConverter' 17 | //>>excludeEnd('excludeMdown') 18 | ], 19 | function ( 20 | //>>excludeStart('excludeMdown', pragmas.excludeMdown) 21 | text, markdownConverter 22 | //>>excludeEnd('excludeMdown') 23 | ) { 24 | 25 | //>>excludeStart('excludeMdown', pragmas.excludeMdown) 26 | var buildMap = {}; 27 | //>>excludeEnd('excludeMdown') 28 | 29 | //API 30 | return { 31 | 32 | load : function(name, req, onLoad, config) { 33 | //>>excludeStart('excludeMdown', pragmas.excludeMdown) 34 | text.get(req.toUrl(name), function(data){ 35 | data = markdownConverter.makeHtml(data); 36 | if (config.isBuild) { 37 | buildMap[name] = data; 38 | onLoad(data); 39 | } else { 40 | onLoad(data); 41 | } 42 | }); 43 | }, 44 | 45 | //write method based on RequireJS official text plugin by James Burke 46 | //https://github.com/jrburke/requirejs/blob/master/text.js 47 | write : function(pluginName, moduleName, write){ 48 | if(moduleName in buildMap){ 49 | var content = text.jsEscape(buildMap[moduleName]); 50 | write.asModule(pluginName + "!" + moduleName, 51 | "define(function () { return '" + 52 | content + 53 | "';});\n"); 54 | } 55 | //>>excludeEnd('excludeMdown') 56 | } 57 | 58 | }; 59 | }); 60 | -------------------------------------------------------------------------------- /app/scripts/vendor/json.js: -------------------------------------------------------------------------------- 1 | /** @license 2 | * RequireJS plugin for loading JSON files 3 | * - depends on Text plugin and it was HEAVILY "inspired" by it as well. 4 | * Author: Miller Medeiros 5 | * Version: 0.4.0 (2014/04/10) 6 | * Released under the MIT license 7 | */ 8 | define(['text'], function(text){ 9 | 10 | var CACHE_BUST_QUERY_PARAM = 'bust', 11 | CACHE_BUST_FLAG = '!bust', 12 | jsonParse = (typeof JSON !== 'undefined' && typeof JSON.parse === 'function')? JSON.parse : function(val){ 13 | return eval('('+ val +')'); //quick and dirty 14 | }, 15 | buildMap = {}; 16 | 17 | function cacheBust(url){ 18 | url = url.replace(CACHE_BUST_FLAG, ''); 19 | url += (url.indexOf('?') < 0)? '?' : '&'; 20 | return url + CACHE_BUST_QUERY_PARAM +'='+ Math.round(2147483647 * Math.random()); 21 | } 22 | 23 | //API 24 | return { 25 | 26 | load : function(name, req, onLoad, config) { 27 | if (( config.isBuild && (config.inlineJSON === false || name.indexOf(CACHE_BUST_QUERY_PARAM +'=') !== -1)) || (req.toUrl(name).indexOf('empty:') === 0)) { 28 | //avoid inlining cache busted JSON or if inlineJSON:false 29 | //and don't inline files marked as empty! 30 | onLoad(null); 31 | } else { 32 | text.get(req.toUrl(name), function(data){ 33 | if (config.isBuild) { 34 | buildMap[name] = data; 35 | onLoad(data); 36 | } else { 37 | onLoad(jsonParse(data)); 38 | } 39 | }, 40 | onLoad.error, { 41 | accept: 'application/json' 42 | } 43 | ); 44 | } 45 | }, 46 | 47 | normalize : function (name, normalize) { 48 | // used normalize to avoid caching references to a "cache busted" request 49 | if (name.indexOf(CACHE_BUST_FLAG) !== -1) { 50 | name = cacheBust(name); 51 | } 52 | // resolve any relative paths 53 | return normalize(name); 54 | }, 55 | 56 | //write method based on RequireJS official text plugin by James Burke 57 | //https://github.com/jrburke/requirejs/blob/master/text.js 58 | write : function(pluginName, moduleName, write){ 59 | if(moduleName in buildMap){ 60 | var content = buildMap[moduleName]; 61 | write('define("'+ pluginName +'!'+ moduleName +'", function(){ return '+ content +';});\n'); 62 | } 63 | } 64 | 65 | }; 66 | }); 67 | -------------------------------------------------------------------------------- /app/scripts/vendor/jsx.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Felipe O. Carvalho 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | define(['JSXTransformer', 'text'], function (JSXTransformer, text) { 25 | 26 | 'use strict'; 27 | 28 | var buildMap = {}; 29 | 30 | var jsx = { 31 | version: '0.6.0', 32 | 33 | load: function (name, req, onLoadNative, config) { 34 | var jsxOptions = config.jsx || {}; 35 | var fileExtension = jsxOptions.fileExtension || '.js'; 36 | 37 | var transformOptions = { 38 | harmony: !!jsxOptions.harmony, 39 | stripTypes: !!jsxOptions.stripTypes 40 | }; 41 | 42 | var onLoad = function(content) { 43 | try { 44 | content = JSXTransformer.transform(content, transformOptions).code; 45 | } catch (err) { 46 | onLoadNative.error(err); 47 | } 48 | 49 | if (config.isBuild) { 50 | buildMap[name] = content; 51 | } else if (typeof location !== 'undefined') { // Do not create sourcemap when loaded in Node 52 | content += '\n//# sourceURL=' + location.protocol + '//' + location.hostname + 53 | config.baseUrl + name + fileExtension; 54 | } 55 | 56 | onLoadNative.fromText(content); 57 | }; 58 | 59 | onLoad.error = function(err) { 60 | onLoadNative.error(err); 61 | }; 62 | 63 | text.load(name + fileExtension, req, onLoad, config); 64 | }, 65 | 66 | write: function (pluginName, moduleName, write) { 67 | if (buildMap.hasOwnProperty(moduleName)) { 68 | var content = buildMap[moduleName]; 69 | write.asModule(pluginName + '!' + moduleName, content); 70 | } 71 | } 72 | }; 73 | 74 | return jsx; 75 | }); 76 | -------------------------------------------------------------------------------- /app/scripts/vendor/jquery.sidr.min.js: -------------------------------------------------------------------------------- 1 | /*! Sidr - v1.2.1 - 2013-11-06 2 | * https://github.com/artberri/sidr 3 | * Copyright (c) 2013 Alberto Varela; Licensed MIT */ 4 | (function(e){var t=!1,i=!1,n={isUrl:function(e){var t=RegExp("^(https?:\\/\\/)?((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|((\\d{1,3}\\.){3}\\d{1,3}))(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*(\\?[;&a-z\\d%_.~+=-]*)?(\\#[-a-z\\d_]*)?$","i");return t.test(e)?!0:!1},loadContent:function(e,t){e.html(t)},addPrefix:function(e){var t=e.attr("id"),i=e.attr("class");"string"==typeof t&&""!==t&&e.attr("id",t.replace(/([A-Za-z0-9_.\-]+)/g,"sidr-id-$1")),"string"==typeof i&&""!==i&&"sidr-inner"!==i&&e.attr("class",i.replace(/([A-Za-z0-9_.\-]+)/g,"sidr-class-$1")),e.removeAttr("style")},execute:function(n,s,a){"function"==typeof s?(a=s,s="sidr"):s||(s="sidr");var r,d,l,c=e("#"+s),u=e(c.data("body")),f=e("html"),p=c.outerWidth(!0),g=c.data("speed"),h=c.data("side"),m=c.data("displace"),v=c.data("onOpen"),y=c.data("onClose"),x="sidr"===s?"sidr-open":"sidr-open "+s+"-open";if("open"===n||"toggle"===n&&!c.is(":visible")){if(c.is(":visible")||t)return;if(i!==!1)return o.close(i,function(){o.open(s)}),void 0;t=!0,"left"===h?(r={left:p+"px"},d={left:"0px"}):(r={right:p+"px"},d={right:"0px"}),u.is("body")&&(l=f.scrollTop(),f.css("overflow-x","hidden").scrollTop(l)),m?u.addClass("sidr-animating").css({width:u.width(),position:"absolute"}).animate(r,g,function(){e(this).addClass(x)}):setTimeout(function(){e(this).addClass(x)},g),c.css("display","block").animate(d,g,function(){t=!1,i=s,"function"==typeof a&&a(s),u.removeClass("sidr-animating")}),v()}else{if(!c.is(":visible")||t)return;t=!0,"left"===h?(r={left:0},d={left:"-"+p+"px"}):(r={right:0},d={right:"-"+p+"px"}),u.is("body")&&(l=f.scrollTop(),f.removeAttr("style").scrollTop(l)),u.addClass("sidr-animating").animate(r,g).removeClass(x),c.animate(d,g,function(){c.removeAttr("style").hide(),u.removeAttr("style"),e("html").removeAttr("style"),t=!1,i=!1,"function"==typeof a&&a(s),u.removeClass("sidr-animating")}),y()}}},o={open:function(e,t){n.execute("open",e,t)},close:function(e,t){n.execute("close",e,t)},toggle:function(e,t){n.execute("toggle",e,t)},toogle:function(e,t){n.execute("toggle",e,t)}};e.sidr=function(t){return o[t]?o[t].apply(this,Array.prototype.slice.call(arguments,1)):"function"!=typeof t&&"string"!=typeof t&&t?(e.error("Method "+t+" does not exist on jQuery.sidr"),void 0):o.toggle.apply(this,arguments)},e.fn.sidr=function(t){var i=e.extend({name:"sidr",speed:200,side:"left",source:null,renaming:!0,body:"body",displace:!0,onOpen:function(){},onClose:function(){}},t),s=i.name,a=e("#"+s);if(0===a.length&&(a=e("
").attr("id",s).appendTo(e("body"))),a.addClass("sidr").addClass(i.side).data({speed:i.speed,side:i.side,body:i.body,displace:i.displace,onOpen:i.onOpen,onClose:i.onClose}),"function"==typeof i.source){var r=i.source(s);n.loadContent(a,r)}else if("string"==typeof i.source&&n.isUrl(i.source))e.get(i.source,function(e){n.loadContent(a,e)});else if("string"==typeof i.source){var d="",l=i.source.split(",");if(e.each(l,function(t,i){d+='
'+e(i).html()+"
"}),i.renaming){var c=e("
").html(d);c.find("*").each(function(t,i){var o=e(i);n.addPrefix(o)}),d=c.html()}n.loadContent(a,d)}else null!==i.source&&e.error("Invalid Sidr Source");return this.each(function(){var t=e(this),i=t.data("sidr");i||(t.data("sidr",s),"ontouchstart"in document.documentElement?(t.bind("touchstart",function(e){e.originalEvent.touches[0],this.touched=e.timeStamp}),t.bind("touchend",function(e){var t=Math.abs(e.timeStamp-this.touched);200>t&&(e.preventDefault(),o.toggle(s))})):t.click(function(e){e.preventDefault(),o.toggle(s)}))})}})(jQuery); -------------------------------------------------------------------------------- /app/scripts/vendor/react.backbone.js: -------------------------------------------------------------------------------- 1 | (function(root, factory) { 2 | if (typeof exports === 'object') { 3 | // CommonJS 4 | module.exports = factory(require('backbone'), require('react'), require('underscore')); 5 | } else if (typeof define === 'function' && define.amd) { 6 | // AMD. Register as an anonymous module. 7 | define(['backbone', 'react', 'underscore'], factory); 8 | } else { 9 | // Browser globals 10 | root.amdWeb = factory(root.Backbone, root.React, root._); 11 | } 12 | }(this, function(Backbone, React, _) { 13 | 14 | 'use strict'; 15 | 16 | var collectionBehavior = { 17 | changeOptions: 'add remove reset sort', 18 | updateScheduler: function(func) { return _.debounce(func, 0); } 19 | }; 20 | 21 | var modelBehavior = { 22 | changeOptions: 'change', 23 | //note: if we debounce models too we can no longer use model attributes 24 | //as properties to react controlled components due to https://github.com/facebook/react/issues/955 25 | updateScheduler: _.identity 26 | }; 27 | 28 | var subscribe = function(component, modelOrCollection, customChangeOptions) { 29 | if (!modelOrCollection) { 30 | return; 31 | } 32 | 33 | var behavior = React.BackboneMixin.ConsiderAsCollection(modelOrCollection) ? collectionBehavior : modelBehavior; 34 | 35 | var triggerUpdate = behavior.updateScheduler(function() { 36 | if (component.isMounted()) { 37 | (component.onModelChange || component.forceUpdate).call(component); 38 | } 39 | }); 40 | 41 | var changeOptions = customChangeOptions || component.changeOptions || behavior.changeOptions; 42 | modelOrCollection.on(changeOptions, triggerUpdate, component); 43 | }; 44 | 45 | var unsubscribe = function(component, modelOrCollection) { 46 | if (!modelOrCollection) { 47 | return; 48 | } 49 | 50 | modelOrCollection.off(null, null, component); 51 | }; 52 | 53 | React.BackboneMixin = function(optionsOrPropName, customChangeOptions) { 54 | var propName, modelOrCollection; 55 | if (typeof optionsOrPropName === "object") { 56 | customChangeOptions = optionsOrPropName.renderOn; 57 | propName = optionsOrPropName.propName; 58 | modelOrCollection = optionsOrPropName.modelOrCollection; 59 | } else { 60 | propName = optionsOrPropName; 61 | } 62 | 63 | if (!modelOrCollection) { 64 | modelOrCollection = function(props) { 65 | return props[propName]; 66 | } 67 | } 68 | 69 | return { 70 | componentDidMount: function() { 71 | // Whenever there may be a change in the Backbone data, trigger a reconcile. 72 | subscribe(this, modelOrCollection(this.props), customChangeOptions); 73 | }, 74 | 75 | componentWillReceiveProps: function(nextProps) { 76 | if (modelOrCollection(this.props) === modelOrCollection(nextProps)) { 77 | return; 78 | } 79 | 80 | unsubscribe(this, modelOrCollection(this.props)); 81 | subscribe(this, modelOrCollection(nextProps), customChangeOptions); 82 | 83 | if (typeof this.componentWillChangeModel === 'function') { 84 | this.componentWillChangeModel(); 85 | } 86 | }, 87 | 88 | componentDidUpdate: function(prevProps, prevState) { 89 | if (modelOrCollection(this.props) === modelOrCollection(prevProps)) { 90 | return; 91 | } 92 | 93 | if (typeof this.componentDidChangeModel === 'function') { 94 | this.componentDidChangeModel(); 95 | } 96 | }, 97 | 98 | componentWillUnmount: function() { 99 | // Ensure that we clean up any dangling references when the component is destroyed. 100 | unsubscribe(this, modelOrCollection(this.props)); 101 | } 102 | }; 103 | }; 104 | 105 | React.BackboneMixin.ConsiderAsCollection = function (modelOrCollection) { 106 | return modelOrCollection instanceof Backbone.Collection; 107 | }; 108 | 109 | React.BackboneViewMixin = { 110 | getModel: function() { 111 | return this.props.model; 112 | }, 113 | 114 | model: function() { 115 | return this.getModel(); 116 | }, 117 | 118 | getCollection: function() { 119 | return this.props.collection; 120 | }, 121 | 122 | collection: function() { 123 | return this.getCollection(); 124 | }, 125 | 126 | el: function() { 127 | return this.isMounted() && this.getDOMNode(); 128 | } 129 | }; 130 | 131 | React.createBackboneClass = function(spec) { 132 | var currentMixins = spec.mixins || []; 133 | 134 | spec.mixins = currentMixins.concat([ 135 | React.BackboneMixin('model'), 136 | React.BackboneMixin('collection'), 137 | React.BackboneViewMixin 138 | ]); 139 | 140 | return React.createClass(spec); 141 | }; 142 | 143 | return React; 144 | })); 145 | -------------------------------------------------------------------------------- /app/styles/grids-responsive-old-ie-min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Pure v0.6.0 3 | Copyright 2014 Yahoo! Inc. All rights reserved. 4 | Licensed under the BSD License. 5 | https://github.com/yahoo/pure/blob/master/LICENSE.md 6 | */ 7 | .pure-u-sm-1,.pure-u-sm-1-1,.pure-u-sm-1-12,.pure-u-sm-1-2,.pure-u-sm-1-24,.pure-u-sm-1-3,.pure-u-sm-1-4,.pure-u-sm-1-5,.pure-u-sm-1-6,.pure-u-sm-1-8,.pure-u-sm-10-24,.pure-u-sm-11-12,.pure-u-sm-11-24,.pure-u-sm-12-24,.pure-u-sm-13-24,.pure-u-sm-14-24,.pure-u-sm-15-24,.pure-u-sm-16-24,.pure-u-sm-17-24,.pure-u-sm-18-24,.pure-u-sm-19-24,.pure-u-sm-2-24,.pure-u-sm-2-3,.pure-u-sm-2-5,.pure-u-sm-20-24,.pure-u-sm-21-24,.pure-u-sm-22-24,.pure-u-sm-23-24,.pure-u-sm-24-24,.pure-u-sm-3-24,.pure-u-sm-3-4,.pure-u-sm-3-5,.pure-u-sm-3-8,.pure-u-sm-4-24,.pure-u-sm-4-5,.pure-u-sm-5-12,.pure-u-sm-5-24,.pure-u-sm-5-5,.pure-u-sm-5-6,.pure-u-sm-5-8,.pure-u-sm-6-24,.pure-u-sm-7-12,.pure-u-sm-7-24,.pure-u-sm-7-8,.pure-u-sm-8-24,.pure-u-sm-9-24{display:inline-block;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-sm-1-24{width:4.1667%}.pure-u-sm-1-12,.pure-u-sm-2-24{width:8.3333%}.pure-u-sm-1-8,.pure-u-sm-3-24{width:12.5%}.pure-u-sm-1-6,.pure-u-sm-4-24{width:16.6667%}.pure-u-sm-1-5{width:20%}.pure-u-sm-5-24{width:20.8333%}.pure-u-sm-1-4,.pure-u-sm-6-24{width:25%}.pure-u-sm-7-24{width:29.1667%}.pure-u-sm-1-3,.pure-u-sm-8-24{width:33.3333%}.pure-u-sm-3-8,.pure-u-sm-9-24{width:37.5%}.pure-u-sm-2-5{width:40%}.pure-u-sm-10-24,.pure-u-sm-5-12{width:41.6667%}.pure-u-sm-11-24{width:45.8333%}.pure-u-sm-1-2,.pure-u-sm-12-24{width:50%}.pure-u-sm-13-24{width:54.1667%}.pure-u-sm-14-24,.pure-u-sm-7-12{width:58.3333%}.pure-u-sm-3-5{width:60%}.pure-u-sm-15-24,.pure-u-sm-5-8{width:62.5%}.pure-u-sm-16-24,.pure-u-sm-2-3{width:66.6667%}.pure-u-sm-17-24{width:70.8333%}.pure-u-sm-18-24,.pure-u-sm-3-4{width:75%}.pure-u-sm-19-24{width:79.1667%}.pure-u-sm-4-5{width:80%}.pure-u-sm-20-24,.pure-u-sm-5-6{width:83.3333%}.pure-u-sm-21-24,.pure-u-sm-7-8{width:87.5%}.pure-u-sm-11-12,.pure-u-sm-22-24{width:91.6667%}.pure-u-sm-23-24{width:95.8333%}.pure-u-sm-1,.pure-u-sm-1-1,.pure-u-sm-24-24,.pure-u-sm-5-5{width:100%}.pure-u-md-1,.pure-u-md-1-1,.pure-u-md-1-12,.pure-u-md-1-2,.pure-u-md-1-24,.pure-u-md-1-3,.pure-u-md-1-4,.pure-u-md-1-5,.pure-u-md-1-6,.pure-u-md-1-8,.pure-u-md-10-24,.pure-u-md-11-12,.pure-u-md-11-24,.pure-u-md-12-24,.pure-u-md-13-24,.pure-u-md-14-24,.pure-u-md-15-24,.pure-u-md-16-24,.pure-u-md-17-24,.pure-u-md-18-24,.pure-u-md-19-24,.pure-u-md-2-24,.pure-u-md-2-3,.pure-u-md-2-5,.pure-u-md-20-24,.pure-u-md-21-24,.pure-u-md-22-24,.pure-u-md-23-24,.pure-u-md-24-24,.pure-u-md-3-24,.pure-u-md-3-4,.pure-u-md-3-5,.pure-u-md-3-8,.pure-u-md-4-24,.pure-u-md-4-5,.pure-u-md-5-12,.pure-u-md-5-24,.pure-u-md-5-5,.pure-u-md-5-6,.pure-u-md-5-8,.pure-u-md-6-24,.pure-u-md-7-12,.pure-u-md-7-24,.pure-u-md-7-8,.pure-u-md-8-24,.pure-u-md-9-24{display:inline-block;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-md-1-24{width:4.1667%}.pure-u-md-1-12,.pure-u-md-2-24{width:8.3333%}.pure-u-md-1-8,.pure-u-md-3-24{width:12.5%}.pure-u-md-1-6,.pure-u-md-4-24{width:16.6667%}.pure-u-md-1-5{width:20%}.pure-u-md-5-24{width:20.8333%}.pure-u-md-1-4,.pure-u-md-6-24{width:25%}.pure-u-md-7-24{width:29.1667%}.pure-u-md-1-3,.pure-u-md-8-24{width:33.3333%}.pure-u-md-3-8,.pure-u-md-9-24{width:37.5%}.pure-u-md-2-5{width:40%}.pure-u-md-10-24,.pure-u-md-5-12{width:41.6667%}.pure-u-md-11-24{width:45.8333%}.pure-u-md-1-2,.pure-u-md-12-24{width:50%}.pure-u-md-13-24{width:54.1667%}.pure-u-md-14-24,.pure-u-md-7-12{width:58.3333%}.pure-u-md-3-5{width:60%}.pure-u-md-15-24,.pure-u-md-5-8{width:62.5%}.pure-u-md-16-24,.pure-u-md-2-3{width:66.6667%}.pure-u-md-17-24{width:70.8333%}.pure-u-md-18-24,.pure-u-md-3-4{width:75%}.pure-u-md-19-24{width:79.1667%}.pure-u-md-4-5{width:80%}.pure-u-md-20-24,.pure-u-md-5-6{width:83.3333%}.pure-u-md-21-24,.pure-u-md-7-8{width:87.5%}.pure-u-md-11-12,.pure-u-md-22-24{width:91.6667%}.pure-u-md-23-24{width:95.8333%}.pure-u-md-1,.pure-u-md-1-1,.pure-u-md-24-24,.pure-u-md-5-5{width:100%}.pure-u-lg-1,.pure-u-lg-1-1,.pure-u-lg-1-12,.pure-u-lg-1-2,.pure-u-lg-1-24,.pure-u-lg-1-3,.pure-u-lg-1-4,.pure-u-lg-1-5,.pure-u-lg-1-6,.pure-u-lg-1-8,.pure-u-lg-10-24,.pure-u-lg-11-12,.pure-u-lg-11-24,.pure-u-lg-12-24,.pure-u-lg-13-24,.pure-u-lg-14-24,.pure-u-lg-15-24,.pure-u-lg-16-24,.pure-u-lg-17-24,.pure-u-lg-18-24,.pure-u-lg-19-24,.pure-u-lg-2-24,.pure-u-lg-2-3,.pure-u-lg-2-5,.pure-u-lg-20-24,.pure-u-lg-21-24,.pure-u-lg-22-24,.pure-u-lg-23-24,.pure-u-lg-24-24,.pure-u-lg-3-24,.pure-u-lg-3-4,.pure-u-lg-3-5,.pure-u-lg-3-8,.pure-u-lg-4-24,.pure-u-lg-4-5,.pure-u-lg-5-12,.pure-u-lg-5-24,.pure-u-lg-5-5,.pure-u-lg-5-6,.pure-u-lg-5-8,.pure-u-lg-6-24,.pure-u-lg-7-12,.pure-u-lg-7-24,.pure-u-lg-7-8,.pure-u-lg-8-24,.pure-u-lg-9-24{display:inline-block;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-lg-1-24{width:4.1667%}.pure-u-lg-1-12,.pure-u-lg-2-24{width:8.3333%}.pure-u-lg-1-8,.pure-u-lg-3-24{width:12.5%}.pure-u-lg-1-6,.pure-u-lg-4-24{width:16.6667%}.pure-u-lg-1-5{width:20%}.pure-u-lg-5-24{width:20.8333%}.pure-u-lg-1-4,.pure-u-lg-6-24{width:25%}.pure-u-lg-7-24{width:29.1667%}.pure-u-lg-1-3,.pure-u-lg-8-24{width:33.3333%}.pure-u-lg-3-8,.pure-u-lg-9-24{width:37.5%}.pure-u-lg-2-5{width:40%}.pure-u-lg-10-24,.pure-u-lg-5-12{width:41.6667%}.pure-u-lg-11-24{width:45.8333%}.pure-u-lg-1-2,.pure-u-lg-12-24{width:50%}.pure-u-lg-13-24{width:54.1667%}.pure-u-lg-14-24,.pure-u-lg-7-12{width:58.3333%}.pure-u-lg-3-5{width:60%}.pure-u-lg-15-24,.pure-u-lg-5-8{width:62.5%}.pure-u-lg-16-24,.pure-u-lg-2-3{width:66.6667%}.pure-u-lg-17-24{width:70.8333%}.pure-u-lg-18-24,.pure-u-lg-3-4{width:75%}.pure-u-lg-19-24{width:79.1667%}.pure-u-lg-4-5{width:80%}.pure-u-lg-20-24,.pure-u-lg-5-6{width:83.3333%}.pure-u-lg-21-24,.pure-u-lg-7-8{width:87.5%}.pure-u-lg-11-12,.pure-u-lg-22-24{width:91.6667%}.pure-u-lg-23-24{width:95.8333%}.pure-u-lg-1,.pure-u-lg-1-1,.pure-u-lg-24-24,.pure-u-lg-5-5{width:100%} -------------------------------------------------------------------------------- /app/styles/main.css: -------------------------------------------------------------------------------- 1 | /* 2 | * -- BASE STYLES -- 3 | * Most of these are inherited from Base, but I want to change a few. 4 | */ 5 | body { 6 | color: #526066; 7 | } 8 | 9 | h2, h3 { 10 | letter-spacing: 0.25em; 11 | text-transform: uppercase; 12 | font-weight: 600; 13 | } 14 | 15 | .l-content { 16 | margin: 0 auto; 17 | } 18 | 19 | .l-box { 20 | padding: 0.5em 2em; 21 | } 22 | 23 | .pure-menu { 24 | box-shadow: 0 1px 1px rgba(0,0,0, 0.10); 25 | } 26 | 27 | .banner { 28 | text-align: center; 29 | height: 80px; 30 | width: 100%; 31 | display: table; 32 | } 33 | 34 | .banner-head { 35 | display: table-cell; 36 | vertical-align: middle; 37 | margin-bottom: 0; 38 | font-size: 2em; 39 | color: white; 40 | font-weight: 500; 41 | text-shadow: 0 1px 1px black; 42 | } 43 | .information-head { 44 | color: black; 45 | font-weight: 500; 46 | } 47 | 48 | .footer { 49 | background: #111; 50 | color: #888; 51 | text-align: center; 52 | bottom: 0px; 53 | width: 100%; 54 | position: absolute; 55 | } 56 | .footer a { 57 | color: #ddd; 58 | text-decoration: none; 59 | } 60 | 61 | @media(min-width: 767px) { 62 | 63 | .banner-head { 64 | font-size: 4em; 65 | } 66 | .pricing-table { 67 | margin-bottom: 0; 68 | } 69 | 70 | } 71 | 72 | @media (min-width: 480px) { 73 | .banner { 74 | height: 180px; 75 | } 76 | .banner-head { 77 | font-size: 3em; 78 | } 79 | } 80 | 81 | .sidr { 82 | display: none; 83 | position: absolute; 84 | position: fixed; 85 | top: 0; 86 | height: 100%; 87 | z-index: 999999; 88 | width: 260px; 89 | overflow-x: none; 90 | overflow-y: auto; 91 | font-family: "lucida grande", tahoma, verdana, arial, sans-serif; 92 | font-size: 15px; 93 | background: #f8f8f8; 94 | color: #333; 95 | -webkit-box-shadow: inset 0 0 5px 5px #ebebeb; 96 | -moz-box-shadow: inset 0 0 5px 5px #ebebeb; 97 | box-shadow: inset 0 0 5px 5px #ebebeb 98 | } 99 | .sidr .sidr-inner { 100 | padding: 0 0 15px 101 | } 102 | .sidr .sidr-inner>p { 103 | margin-left: 15px; 104 | margin-right: 15px 105 | } 106 | .sidr.right { 107 | left: auto; 108 | right: -260px 109 | } 110 | .sidr.left { 111 | left: -260px; 112 | right: auto 113 | } 114 | .sidr h1, .sidr h2, .sidr h3, .sidr h4, .sidr h5, .sidr h6 { 115 | font-size: 11px; 116 | font-weight: normal; 117 | padding: 0 15px; 118 | margin: 0 0 5px; 119 | color: #333; 120 | line-height: 24px; 121 | background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ffffff), color-stop(100%, #dfdfdf)); 122 | background-image: -webkit-linear-gradient(#ffffff, #dfdfdf); 123 | background-image: -moz-linear-gradient(#ffffff, #dfdfdf); 124 | background-image: -o-linear-gradient(#ffffff, #dfdfdf); 125 | background-image: linear-gradient(#ffffff, #dfdfdf); 126 | -webkit-box-shadow: 0 5px 5px 3px rgba(0, 0, 0, 0.2); 127 | -moz-box-shadow: 0 5px 5px 3px rgba(0, 0, 0, 0.2); 128 | box-shadow: 0 5px 5px 3px rgba(0, 0, 0, 0.2) 129 | } 130 | .sidr p { 131 | font-size: 13px; 132 | margin: 0 0 12px 133 | } 134 | .sidr p a { 135 | color: rgba(51, 51, 51, 0.9) 136 | } 137 | .sidr>p { 138 | margin-left: 15px; 139 | margin-right: 15px 140 | } 141 | .sidr ul { 142 | display: block; 143 | margin: 0 0 15px; 144 | padding: 0; 145 | border-top: 1px solid #dfdfdf; 146 | border-bottom: 1px solid #fff 147 | } 148 | .sidr ul li { 149 | display: block; 150 | margin: 0; 151 | line-height: 48px; 152 | border-top: 1px solid #fff; 153 | border-bottom: 1px solid #dfdfdf 154 | } 155 | .sidr ul li:hover, .sidr ul li.active, .sidr ul li.sidr-class-active { 156 | border-top: none; 157 | line-height: 49px 158 | } 159 | .sidr ul li:hover>a, .sidr ul li:hover>span, .sidr ul li.active>a, .sidr ul li.active>span, .sidr ul li.sidr-class-active>a, .sidr ul li.sidr-class-active>span { 160 | -webkit-box-shadow: inset 0 0 15px 3px #ebebeb; 161 | -moz-box-shadow: inset 0 0 15px 3px #ebebeb; 162 | box-shadow: inset 0 0 15px 3px #ebebeb 163 | } 164 | .sidr ul li a, .sidr ul li span { 165 | padding: 0 15px; 166 | display: block; 167 | text-decoration: none; 168 | color: #333 169 | } 170 | .sidr ul li ul { 171 | border-bottom: none; 172 | margin: 0 173 | } 174 | .sidr ul li ul li { 175 | line-height: 40px; 176 | font-size: 13px 177 | } 178 | .sidr ul li ul li:last-child { 179 | border-bottom: none 180 | } 181 | .sidr ul li ul li:hover, .sidr ul li ul li.active, .sidr ul li ul li.sidr-class-active { 182 | border-top: none; 183 | line-height: 41px 184 | } 185 | .sidr ul li ul li:hover>a, .sidr ul li ul li:hover>span, .sidr ul li ul li.active>a, .sidr ul li ul li.active>span, .sidr ul li ul li.sidr-class-active>a, .sidr ul li ul li.sidr-class-active>span { 186 | -webkit-box-shadow: inset 0 0 15px 3px #ebebeb; 187 | -moz-box-shadow: inset 0 0 15px 3px #ebebeb; 188 | box-shadow: inset 0 0 15px 3px #ebebeb 189 | } 190 | .sidr ul li ul li a, .sidr ul li ul li span { 191 | color: rgba(51, 51, 51, 0.8); 192 | padding-left: 30px 193 | } 194 | .sidr form { 195 | margin: 0 15px 196 | } 197 | .sidr label { 198 | font-size: 13px 199 | } 200 | .sidr input[type="text"], .sidr input[type="password"], .sidr input[type="date"], .sidr input[type="datetime"], .sidr input[type="email"], .sidr input[type="number"], .sidr input[type="search"], .sidr input[type="tel"], .sidr input[type="time"], .sidr input[type="url"], .sidr textarea, .sidr select { 201 | width: 100%; 202 | font-size: 13px; 203 | padding: 5px; 204 | -webkit-box-sizing: border-box; 205 | -moz-box-sizing: border-box; 206 | box-sizing: border-box; 207 | margin: 0 0 10px; 208 | -webkit-border-radius: 2px; 209 | -moz-border-radius: 2px; 210 | -ms-border-radius: 2px; 211 | -o-border-radius: 2px; 212 | border-radius: 2px; 213 | border: none; 214 | background: rgba(0, 0, 0, 0.1); 215 | color: rgba(51, 51, 51, 0.6); 216 | display: block; 217 | clear: both 218 | } 219 | .sidr input[type=checkbox] { 220 | width: auto; 221 | display: inline; 222 | clear: none 223 | } 224 | .sidr input[type=button], .sidr input[type=submit] { 225 | color: #f8f8f8; 226 | background: #333 227 | } 228 | .sidr input[type=button]:hover, .sidr input[type=submit]:hover { 229 | background: rgba(51, 51, 51, 0.9) 230 | } 231 | 232 | .blogPosts a { 233 | text-decoration: none; 234 | color:#195A94; 235 | } 236 | .blogPosts .date{ 237 | color: #48A682; 238 | font-size: 11px; 239 | line-height: 1.5em; 240 | margin-bottom: 0px; 241 | } 242 | .navbar-default .navbar-toggle .icon-bar { 243 | background-color: #ccc; 244 | } 245 | 246 | .navbar-toggle .icon-bar { 247 | display: block; 248 | width: 22px; 249 | height: 2px; 250 | border-radius: 1px; 251 | } 252 | .btn{ 253 | color: #333; 254 | background-color: #fff; 255 | border-color: #ccc; 256 | } 257 | .navbar-default .navbar-toggle { 258 | border-color: #ddd; 259 | } 260 | .navbar-toggle { 261 | position: relative; 262 | float: left; 263 | padding: 9px 10px; 264 | margin-top: 8px; 265 | margin-right: 15px; 266 | margin-bottom: 8px; 267 | background-color: transparent; 268 | background-image: none; 269 | border: 1px solid transparent; 270 | border-radius: 4px; 271 | } 272 | .btn { 273 | display: inline-block; 274 | font-size: 16px; 275 | font-weight: 400; 276 | line-height: 1.42857143; 277 | text-align: center; 278 | white-space: nowrap; 279 | vertical-align: middle; 280 | cursor: pointer; 281 | -webkit-user-select: none; 282 | -moz-user-select: none; 283 | -ms-user-select: none; 284 | user-select: none; 285 | background-image: none; 286 | border: 1px solid transparent; 287 | border-radius: 4px; 288 | width:100%; 289 | } 290 | 291 | a.menu { 292 | text-decoration: none; 293 | color: #333; 294 | float:left; 295 | } 296 | 297 | .header{ 298 | color: #888; 299 | text-align: center; 300 | position: relative; 301 | margin-left: auto; 302 | margin-right: auto; 303 | } 304 | .sr-only{ 305 | position: absolute; 306 | width: 1px; 307 | height: 1px; 308 | padding: 0; 309 | margin: -1px; 310 | overflow: hidden; 311 | clip: rect(0,0,0,0); 312 | border: 0; 313 | } 314 | .monav{ 315 | position: fixed; 316 | background-color: #fff; 317 | width:100%; 318 | box-shadow: 0 2px 1px rgba(0,0,0, 0.10); 319 | } 320 | .main { 321 | width: 960px; 322 | } 323 | 324 | input[type=text]{ 325 | text-align: center; 326 | font: inherit; 327 | border: 4px solid #a3d8d6; 328 | padding: 12px 12px; 329 | font-size: 15px; 330 | box-shadow: 0 1px 1px #DDD; 331 | width: 384px; 332 | outline: none; 333 | display: block; 334 | color: #7B8585; 335 | margin: 0 auto 20px; 336 | } 337 | 338 | #library ul{ 339 | padding: 0; 340 | list-style: none; 341 | margin: 0 auto; 342 | width: 420px; 343 | text-align: left; 344 | } 345 | 346 | #library ul li{ 347 | display: block; 348 | padding: 15px 20px; 349 | background-color: #F8F8F8; 350 | color: #7B8585; 351 | margin-bottom: 3px; 352 | position: relative; 353 | transition: 0.3s; 354 | } 355 | 356 | #library ul li a{ 357 | position: absolute; 358 | left: 160px; 359 | font-size: 12px; 360 | line-height: 16px; 361 | color: #969d9d; 362 | } 363 | 364 | #library ul li:hover{ 365 | background-color:#d8f2f1; 366 | } 367 | 368 | #sidr ul{ 369 | list-style:none; 370 | } 371 | 372 | #sidr ul li a{ 373 | font-size: 15px; 374 | vertical-align: middle; 375 | } 376 | 377 | #sidr ul li{ 378 | cursor:pointer; 379 | background-color:#eee; 380 | color:#7B8585; 381 | transition:0.3s; 382 | } 383 | 384 | #sidr ul li:hover{ 385 | background-color:#beecea; 386 | } 387 | 388 | #sidr ul li.focused{ 389 | color:#fff; 390 | background-color:#41c7c2; 391 | } 392 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var LIVERELOAD_PORT = 35729; 3 | var SERVER_PORT = 9000; 4 | var lrSnippet = require('connect-livereload')({port: LIVERELOAD_PORT}); 5 | var mountFolder = function (connect, dir) { 6 | return connect.static(require('path').resolve(dir)); 7 | }; 8 | 9 | // # Globbing 10 | // for performance reasons we're only matching one level down: 11 | // 'test/spec/{,*/}*.js' 12 | // use this if you want to match all subfolders: 13 | // 'test/spec/**/*.js' 14 | // templateFramework: 'lodash' 15 | 16 | module.exports = function (grunt) { 17 | // show elapsed time at the end 18 | require('time-grunt')(grunt); 19 | // load all grunt tasks 20 | require('load-grunt-tasks')(grunt); 21 | 22 | // configurable paths 23 | var yeomanConfig = { 24 | app: 'app', 25 | dist: 'dist' 26 | }; 27 | 28 | grunt.initConfig({ 29 | yeoman: yeomanConfig, 30 | watch: { 31 | options: { 32 | nospawn: true, 33 | livereload: LIVERELOAD_PORT 34 | }, 35 | livereload: { 36 | options: { 37 | livereload: grunt.option('livereloadport') || LIVERELOAD_PORT 38 | }, 39 | files: [ 40 | '<%= yeoman.app %>/*.html', 41 | '{.tmp,<%= yeoman.app %>}/styles/{,*/}*.css', 42 | '{.tmp,<%= yeoman.app %>}/scripts/{,*/}*.js', 43 | '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp}', 44 | '<%= yeoman.app %>/scripts/templates/*.{ejs,mustache,hbs}', 45 | 'test/spec/**/*.js' 46 | ] 47 | }, 48 | jst: { 49 | files: [ 50 | '<%= yeoman.app %>/scripts/templates/*.ejs' 51 | ], 52 | tasks: ['jst'] 53 | }, 54 | test: { 55 | files: ['<%= yeoman.app %>/scripts/{,*/}*.js', 'test/spec/**/*.js'], 56 | tasks: ['test:true'] 57 | } 58 | }, 59 | connect: { 60 | options: { 61 | port: grunt.option('port') || SERVER_PORT, 62 | // change this to '0.0.0.0' to access the server from outside 63 | hostname: 'localhost' 64 | }, 65 | livereload: { 66 | options: { 67 | middleware: function (connect) { 68 | return [ 69 | lrSnippet, 70 | mountFolder(connect, '.tmp'), 71 | mountFolder(connect, yeomanConfig.app) 72 | ]; 73 | } 74 | } 75 | }, 76 | test: { 77 | options: { 78 | port: 9001, 79 | middleware: function (connect) { 80 | return [ 81 | mountFolder(connect, 'test'), 82 | lrSnippet, 83 | mountFolder(connect, '.tmp'), 84 | mountFolder(connect, yeomanConfig.app) 85 | ]; 86 | } 87 | } 88 | }, 89 | dist: { 90 | options: { 91 | middleware: function (connect) { 92 | return [ 93 | mountFolder(connect, yeomanConfig.dist) 94 | ]; 95 | } 96 | } 97 | } 98 | }, 99 | open: { 100 | server: { 101 | path: 'http://localhost:<%= connect.options.port %>' 102 | }, 103 | test: { 104 | path: 'http://localhost:<%= connect.test.options.port %>' 105 | } 106 | }, 107 | clean: { 108 | dist: ['.tmp', '<%= yeoman.dist %>/*'], 109 | server: '.tmp' 110 | }, 111 | jshint: { 112 | options: { 113 | jshintrc: '.jshintrc', 114 | reporter: require('jshint-stylish') 115 | }, 116 | all: [ 117 | 'Gruntfile.js', 118 | '<%= yeoman.app %>/scripts/{,*/}*.js', 119 | '!<%= yeoman.app %>/scripts/vendor/*', 120 | 'test/spec/{,*/}*.js' 121 | ] 122 | }, 123 | mocha: { 124 | all: { 125 | options: { 126 | run: true, 127 | urls: ['http://localhost:<%= connect.test.options.port %>/index.html'] 128 | } 129 | } 130 | }, 131 | requirejs: { 132 | dist: { 133 | // Options: https://github.com/jrburke/r.js/blob/master/build/example.build.js 134 | options: { 135 | baseUrl: '<%= yeoman.app %>/scripts', 136 | optimize: 'none', 137 | paths: { 138 | 'templates': '../../.tmp/scripts/templates', 139 | 'jquery': '../../<%= yeoman.app %>/bower_components/jquery/dist/jquery', 140 | 'underscore': '../../<%= yeoman.app %>/bower_components/lodash/dist/lodash', 141 | 'backbone': '../../<%= yeoman.app %>/bower_components/backbone/backbone' 142 | }, 143 | // TODO: Figure out how to make sourcemaps work with grunt-usemin 144 | // https://github.com/yeoman/grunt-usemin/issues/30 145 | //generateSourceMaps: true, 146 | // required to support SourceMaps 147 | // http://requirejs.org/docs/errors.html#sourcemapcomments 148 | preserveLicenseComments: false, 149 | useStrict: true, 150 | wrap: true 151 | //uglify2: {} // https://github.com/mishoo/UglifyJS2 152 | } 153 | } 154 | }, 155 | useminPrepare: { 156 | html: '<%= yeoman.app %>/index.html', 157 | options: { 158 | dest: '<%= yeoman.dist %>' 159 | } 160 | }, 161 | usemin: { 162 | html: ['<%= yeoman.dist %>/{,*/}*.html'], 163 | css: ['<%= yeoman.dist %>/styles/{,*/}*.css'], 164 | options: { 165 | dirs: ['<%= yeoman.dist %>'] 166 | } 167 | }, 168 | imagemin: { 169 | dist: { 170 | files: [{ 171 | expand: true, 172 | cwd: '<%= yeoman.app %>/images', 173 | src: '{,*/}*.{png,jpg,jpeg}', 174 | dest: '<%= yeoman.dist %>/images' 175 | }] 176 | } 177 | }, 178 | cssmin: { 179 | dist: { 180 | files: { 181 | '<%= yeoman.dist %>/styles/main.css': [ 182 | '.tmp/styles/{,*/}*.css', 183 | '<%= yeoman.app %>/styles/{,*/}*.css' 184 | ] 185 | } 186 | } 187 | }, 188 | htmlmin: { 189 | dist: { 190 | options: { 191 | /*removeCommentsFromCDATA: true, 192 | // https://github.com/yeoman/grunt-usemin/issues/44 193 | //collapseWhitespace: true, 194 | collapseBooleanAttributes: true, 195 | removeAttributeQuotes: true, 196 | removeRedundantAttributes: true, 197 | useShortDoctype: true, 198 | removeEmptyAttributes: true, 199 | removeOptionalTags: true*/ 200 | }, 201 | files: [{ 202 | expand: true, 203 | cwd: '<%= yeoman.app %>', 204 | src: '*.html', 205 | dest: '<%= yeoman.dist %>' 206 | }] 207 | } 208 | }, 209 | copy: { 210 | dist: { 211 | files: [{ 212 | expand: true, 213 | dot: true, 214 | cwd: '<%= yeoman.app %>', 215 | dest: '<%= yeoman.dist %>', 216 | src: [ 217 | '*.{ico,txt}', 218 | 'images/{,*/}*.{webp,gif}', 219 | 'styles/fonts/{,*/}*.*', 220 | ] 221 | }, { 222 | src: 'node_modules/apache-server-configs/dist/.htaccess', 223 | dest: '<%= yeoman.dist %>/.htaccess' 224 | }] 225 | } 226 | }, 227 | bower: { 228 | all: { 229 | rjsConfig: '<%= yeoman.app %>/scripts/main.js' 230 | } 231 | }, 232 | jst: { 233 | options: { 234 | amd: true 235 | }, 236 | compile: { 237 | files: { 238 | '.tmp/scripts/templates.js': ['<%= yeoman.app %>/scripts/templates/*.ejs'] 239 | } 240 | } 241 | }, 242 | rev: { 243 | dist: { 244 | files: { 245 | src: [ 246 | '<%= yeoman.dist %>/scripts/{,*/}*.js', 247 | '<%= yeoman.dist %>/styles/{,*/}*.css', 248 | '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp}', 249 | '/styles/fonts/{,*/}*.*', 250 | ] 251 | } 252 | } 253 | } 254 | }); 255 | 256 | grunt.registerTask('createDefaultTemplate', function () { 257 | grunt.file.write('.tmp/scripts/templates.js', 'this.JST = this.JST || {};'); 258 | }); 259 | 260 | grunt.registerTask('server', function (target) { 261 | grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.'); 262 | grunt.task.run(['serve' + (target ? ':' + target : '')]); 263 | }); 264 | 265 | grunt.registerTask('serve', function (target) { 266 | if (target === 'dist') { 267 | return grunt.task.run(['build', 'open:server', 'connect:dist:keepalive']); 268 | } 269 | 270 | if (target === 'test') { 271 | return grunt.task.run([ 272 | 'clean:server', 273 | 'createDefaultTemplate', 274 | 'jst', 275 | 'connect:test', 276 | 'open:test', 277 | 'watch' 278 | ]); 279 | } 280 | 281 | grunt.task.run([ 282 | 'clean:server', 283 | 'createDefaultTemplate', 284 | 'jst', 285 | 'connect:livereload', 286 | 'open:server', 287 | 'watch' 288 | ]); 289 | }); 290 | 291 | grunt.registerTask('test', function (isConnected) { 292 | isConnected = Boolean(isConnected); 293 | var testTasks = [ 294 | 'clean:server', 295 | 'createDefaultTemplate', 296 | 'jst', 297 | 'connect:test', 298 | 'mocha', 299 | ]; 300 | 301 | if(!isConnected) { 302 | return grunt.task.run(testTasks); 303 | } else { 304 | // already connected so not going to connect again, remove the connect:test task 305 | testTasks.splice(testTasks.indexOf('connect:test'), 1); 306 | return grunt.task.run(testTasks); 307 | } 308 | }); 309 | 310 | grunt.registerTask('build', [ 311 | 'clean:dist', 312 | 'createDefaultTemplate', 313 | 'jst', 314 | 'useminPrepare', 315 | 'requirejs', 316 | 'imagemin', 317 | 'htmlmin', 318 | 'concat', 319 | 'cssmin', 320 | 'uglify', 321 | 'copy', 322 | 'rev', 323 | 'usemin' 324 | ]); 325 | 326 | grunt.registerTask('default', [ 327 | 'jshint', 328 | 'test', 329 | 'build' 330 | ]); 331 | }; 332 | -------------------------------------------------------------------------------- /app/scripts/vendor/text.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license RequireJS text 2.0.5 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. 3 | * Available via the MIT or new BSD license. 4 | * see: http://github.com/requirejs/text for details 5 | */ 6 | /*jslint regexp: true */ 7 | /*global require: false, XMLHttpRequest: false, ActiveXObject: false, 8 | define: false, window: false, process: false, Packages: false, 9 | java: false, location: false */ 10 | 11 | define(['module'], function (module) { 12 | 'use strict'; 13 | 14 | var text, fs, 15 | progIds = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'], 16 | xmlRegExp = /^\s*<\?xml(\s)+version=[\'\"](\d)*.(\d)*[\'\"](\s)*\?>/im, 17 | bodyRegExp = /]*>\s*([\s\S]+)\s*<\/body>/im, 18 | hasLocation = typeof location !== 'undefined' && location.href, 19 | defaultProtocol = hasLocation && location.protocol && location.protocol.replace(/\:/, ''), 20 | defaultHostName = hasLocation && location.hostname, 21 | defaultPort = hasLocation && (location.port || undefined), 22 | buildMap = [], 23 | masterConfig = (module.config && module.config()) || {}; 24 | 25 | text = { 26 | version: '2.0.5', 27 | 28 | strip: function (content) { 29 | //Strips declarations so that external SVG and XML 30 | //documents can be added to a document without worry. Also, if the string 31 | //is an HTML document, only the part inside the body tag is returned. 32 | if (content) { 33 | content = content.replace(xmlRegExp, ""); 34 | var matches = content.match(bodyRegExp); 35 | if (matches) { 36 | content = matches[1]; 37 | } 38 | } else { 39 | content = ""; 40 | } 41 | return content; 42 | }, 43 | 44 | jsEscape: function (content) { 45 | return content.replace(/(['\\])/g, '\\$1') 46 | .replace(/[\f]/g, "\\f") 47 | .replace(/[\b]/g, "\\b") 48 | .replace(/[\n]/g, "\\n") 49 | .replace(/[\t]/g, "\\t") 50 | .replace(/[\r]/g, "\\r") 51 | .replace(/[\u2028]/g, "\\u2028") 52 | .replace(/[\u2029]/g, "\\u2029"); 53 | }, 54 | 55 | createXhr: masterConfig.createXhr || function () { 56 | //Would love to dump the ActiveX crap in here. Need IE 6 to die first. 57 | var xhr, i, progId; 58 | if (typeof XMLHttpRequest !== "undefined") { 59 | return new XMLHttpRequest(); 60 | } else if (typeof ActiveXObject !== "undefined") { 61 | for (i = 0; i < 3; i += 1) { 62 | progId = progIds[i]; 63 | try { 64 | xhr = new ActiveXObject(progId); 65 | } catch (e) {} 66 | 67 | if (xhr) { 68 | progIds = [progId]; // so faster next time 69 | break; 70 | } 71 | } 72 | } 73 | 74 | return xhr; 75 | }, 76 | 77 | /** 78 | * Parses a resource name into its component parts. Resource names 79 | * look like: module/name.ext!strip, where the !strip part is 80 | * optional. 81 | * @param {String} name the resource name 82 | * @returns {Object} with properties "moduleName", "ext" and "strip" 83 | * where strip is a boolean. 84 | */ 85 | parseName: function (name) { 86 | var modName, ext, temp, 87 | strip = false, 88 | index = name.indexOf("."), 89 | isRelative = name.indexOf('./') === 0 || 90 | name.indexOf('../') === 0; 91 | 92 | if (index !== -1 && (!isRelative || index > 1)) { 93 | modName = name.substring(0, index); 94 | ext = name.substring(index + 1, name.length); 95 | } else { 96 | modName = name; 97 | } 98 | 99 | temp = ext || modName; 100 | index = temp.indexOf("!"); 101 | if (index !== -1) { 102 | //Pull off the strip arg. 103 | strip = temp.substring(index + 1) === "strip"; 104 | temp = temp.substring(0, index); 105 | if (ext) { 106 | ext = temp; 107 | } else { 108 | modName = temp; 109 | } 110 | } 111 | 112 | return { 113 | moduleName: modName, 114 | ext: ext, 115 | strip: strip 116 | }; 117 | }, 118 | 119 | xdRegExp: /^((\w+)\:)?\/\/([^\/\\]+)/, 120 | 121 | /** 122 | * Is an URL on another domain. Only works for browser use, returns 123 | * false in non-browser environments. Only used to know if an 124 | * optimized .js version of a text resource should be loaded 125 | * instead. 126 | * @param {String} url 127 | * @returns Boolean 128 | */ 129 | useXhr: function (url, protocol, hostname, port) { 130 | var uProtocol, uHostName, uPort, 131 | match = text.xdRegExp.exec(url); 132 | if (!match) { 133 | return true; 134 | } 135 | uProtocol = match[2]; 136 | uHostName = match[3]; 137 | 138 | uHostName = uHostName.split(':'); 139 | uPort = uHostName[1]; 140 | uHostName = uHostName[0]; 141 | 142 | return (!uProtocol || uProtocol === protocol) && 143 | (!uHostName || uHostName.toLowerCase() === hostname.toLowerCase()) && 144 | ((!uPort && !uHostName) || uPort === port); 145 | }, 146 | 147 | finishLoad: function (name, strip, content, onLoad) { 148 | content = strip ? text.strip(content) : content; 149 | if (masterConfig.isBuild) { 150 | buildMap[name] = content; 151 | } 152 | onLoad(content); 153 | }, 154 | 155 | load: function (name, req, onLoad, config) { 156 | //Name has format: some.module.filext!strip 157 | //The strip part is optional. 158 | //if strip is present, then that means only get the string contents 159 | //inside a body tag in an HTML string. For XML/SVG content it means 160 | //removing the declarations so the content can be inserted 161 | //into the current doc without problems. 162 | 163 | // Do not bother with the work if a build and text will 164 | // not be inlined. 165 | if (config.isBuild && !config.inlineText) { 166 | onLoad(); 167 | return; 168 | } 169 | 170 | masterConfig.isBuild = config.isBuild; 171 | 172 | var parsed = text.parseName(name), 173 | nonStripName = parsed.moduleName + 174 | (parsed.ext ? '.' + parsed.ext : ''), 175 | url = req.toUrl(nonStripName), 176 | useXhr = (masterConfig.useXhr) || 177 | text.useXhr; 178 | 179 | //Load the text. Use XHR if possible and in a browser. 180 | if (!hasLocation || useXhr(url, defaultProtocol, defaultHostName, defaultPort)) { 181 | text.get(url, function (content) { 182 | text.finishLoad(name, parsed.strip, content, onLoad); 183 | }, function (err) { 184 | if (onLoad.error) { 185 | onLoad.error(err); 186 | } 187 | }); 188 | } else { 189 | //Need to fetch the resource across domains. Assume 190 | //the resource has been optimized into a JS module. Fetch 191 | //by the module name + extension, but do not include the 192 | //!strip part to avoid file system issues. 193 | req([nonStripName], function (content) { 194 | text.finishLoad(parsed.moduleName + '.' + parsed.ext, 195 | parsed.strip, content, onLoad); 196 | }); 197 | } 198 | }, 199 | 200 | write: function (pluginName, moduleName, write, config) { 201 | if (buildMap.hasOwnProperty(moduleName)) { 202 | var content = text.jsEscape(buildMap[moduleName]); 203 | write.asModule(pluginName + "!" + moduleName, 204 | "define(function () { return '" + 205 | content + 206 | "';});\n"); 207 | } 208 | }, 209 | 210 | writeFile: function (pluginName, moduleName, req, write, config) { 211 | var parsed = text.parseName(moduleName), 212 | extPart = parsed.ext ? '.' + parsed.ext : '', 213 | nonStripName = parsed.moduleName + extPart, 214 | //Use a '.js' file name so that it indicates it is a 215 | //script that can be loaded across domains. 216 | fileName = req.toUrl(parsed.moduleName + extPart) + '.js'; 217 | 218 | //Leverage own load() method to load plugin value, but only 219 | //write out values that do not have the strip argument, 220 | //to avoid any potential issues with ! in file names. 221 | text.load(nonStripName, req, function (value) { 222 | //Use own write() method to construct full module value. 223 | //But need to create shell that translates writeFile's 224 | //write() to the right interface. 225 | var textWrite = function (contents) { 226 | return write(fileName, contents); 227 | }; 228 | textWrite.asModule = function (moduleName, contents) { 229 | return write.asModule(moduleName, fileName, contents); 230 | }; 231 | 232 | text.write(pluginName, nonStripName, textWrite, config); 233 | }, config); 234 | } 235 | }; 236 | 237 | if (masterConfig.env === 'node' || (!masterConfig.env && 238 | typeof process !== "undefined" && 239 | process.versions && 240 | !!process.versions.node)) { 241 | //Using special require.nodeRequire, something added by r.js. 242 | fs = require.nodeRequire('fs'); 243 | 244 | text.get = function (url, callback) { 245 | var file = fs.readFileSync(url, 'utf8'); 246 | //Remove BOM (Byte Mark Order) from utf8 files if it is there. 247 | if (file.indexOf('\uFEFF') === 0) { 248 | file = file.substring(1); 249 | } 250 | callback(file); 251 | }; 252 | } else if (masterConfig.env === 'xhr' || (!masterConfig.env && 253 | text.createXhr())) { 254 | text.get = function (url, callback, errback, headers) { 255 | var xhr = text.createXhr(), header; 256 | xhr.open('GET', url, true); 257 | 258 | //Allow plugins direct access to xhr headers 259 | if (headers) { 260 | for (header in headers) { 261 | if (headers.hasOwnProperty(header)) { 262 | xhr.setRequestHeader(header.toLowerCase(), headers[header]); 263 | } 264 | } 265 | } 266 | 267 | //Allow overrides specified in config 268 | if (masterConfig.onXhr) { 269 | masterConfig.onXhr(xhr, url); 270 | } 271 | 272 | xhr.onreadystatechange = function (evt) { 273 | var status, err; 274 | //Do not explicitly handle errors, those should be 275 | //visible via console output in the browser. 276 | if (xhr.readyState === 4) { 277 | status = xhr.status; 278 | if (status > 399 && status < 600) { 279 | //An http 4xx or 5xx error. Signal an error. 280 | err = new Error(url + ' HTTP status: ' + status); 281 | err.xhr = xhr; 282 | errback(err); 283 | } else { 284 | callback(xhr.responseText); 285 | } 286 | } 287 | }; 288 | xhr.send(null); 289 | }; 290 | } else if (masterConfig.env === 'rhino' || (!masterConfig.env && 291 | typeof Packages !== 'undefined' && typeof java !== 'undefined')) { 292 | //Why Java, why is this so awkward? 293 | text.get = function (url, callback) { 294 | var stringBuffer, line, 295 | encoding = "utf-8", 296 | file = new java.io.File(url), 297 | lineSeparator = java.lang.System.getProperty("line.separator"), 298 | input = new java.io.BufferedReader(new java.io.InputStreamReader(new java.io.FileInputStream(file), encoding)), 299 | content = ''; 300 | try { 301 | stringBuffer = new java.lang.StringBuffer(); 302 | line = input.readLine(); 303 | 304 | // Byte Order Mark (BOM) - The Unicode Standard, version 3.0, page 324 305 | // http://www.unicode.org/faq/utf_bom.html 306 | 307 | // Note that when we use utf-8, the BOM should appear as "EF BB BF", but it doesn't due to this bug in the JDK: 308 | // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4508058 309 | if (line && line.length() && line.charAt(0) === 0xfeff) { 310 | // Eat the BOM, since we've already found the encoding on this file, 311 | // and we plan to concatenating this buffer with others; the BOM should 312 | // only appear at the top of a file. 313 | line = line.substring(1); 314 | } 315 | 316 | stringBuffer.append(line); 317 | 318 | while ((line = input.readLine()) !== null) { 319 | stringBuffer.append(lineSeparator); 320 | stringBuffer.append(line); 321 | } 322 | //Make sure we return a JavaScript string and not a Java string. 323 | content = String(stringBuffer.toString()); //String 324 | } finally { 325 | input.close(); 326 | } 327 | callback(content); 328 | }; 329 | } 330 | 331 | return text; 332 | }); 333 | -------------------------------------------------------------------------------- /app/styles/pure-min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Pure v0.6.0 3 | Copyright 2014 Yahoo! Inc. All rights reserved. 4 | Licensed under the BSD License. 5 | https://github.com/yahoo/pure/blob/master/LICENSE.md 6 | */ 7 | /*! 8 | normalize.css v^3.0 | MIT License | git.io/normalize 9 | Copyright (c) Nicolas Gallagher and Jonathan Neal 10 | */ 11 | /*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}.hidden,[hidden]{display:none!important}.pure-img{max-width:100%;height:auto;display:block}.pure-g{letter-spacing:-.31em;*letter-spacing:normal;*word-spacing:-.43em;text-rendering:optimizespeed;font-family:FreeSans,Arimo,"Droid Sans",Helvetica,Arial,sans-serif;display:-webkit-flex;-webkit-flex-flow:row wrap;display:-ms-flexbox;-ms-flex-flow:row wrap;-ms-align-content:flex-start;-webkit-align-content:flex-start;align-content:flex-start}.opera-only :-o-prefocus,.pure-g{word-spacing:-.43em}.pure-u{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-g [class *="pure-u"]{font-family:sans-serif}.pure-u-1,.pure-u-1-1,.pure-u-1-2,.pure-u-1-3,.pure-u-2-3,.pure-u-1-4,.pure-u-3-4,.pure-u-1-5,.pure-u-2-5,.pure-u-3-5,.pure-u-4-5,.pure-u-5-5,.pure-u-1-6,.pure-u-5-6,.pure-u-1-8,.pure-u-3-8,.pure-u-5-8,.pure-u-7-8,.pure-u-1-12,.pure-u-5-12,.pure-u-7-12,.pure-u-11-12,.pure-u-1-24,.pure-u-2-24,.pure-u-3-24,.pure-u-4-24,.pure-u-5-24,.pure-u-6-24,.pure-u-7-24,.pure-u-8-24,.pure-u-9-24,.pure-u-10-24,.pure-u-11-24,.pure-u-12-24,.pure-u-13-24,.pure-u-14-24,.pure-u-15-24,.pure-u-16-24,.pure-u-17-24,.pure-u-18-24,.pure-u-19-24,.pure-u-20-24,.pure-u-21-24,.pure-u-22-24,.pure-u-23-24,.pure-u-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-1-24{width:4.1667%;*width:4.1357%}.pure-u-1-12,.pure-u-2-24{width:8.3333%;*width:8.3023%}.pure-u-1-8,.pure-u-3-24{width:12.5%;*width:12.469%}.pure-u-1-6,.pure-u-4-24{width:16.6667%;*width:16.6357%}.pure-u-1-5{width:20%;*width:19.969%}.pure-u-5-24{width:20.8333%;*width:20.8023%}.pure-u-1-4,.pure-u-6-24{width:25%;*width:24.969%}.pure-u-7-24{width:29.1667%;*width:29.1357%}.pure-u-1-3,.pure-u-8-24{width:33.3333%;*width:33.3023%}.pure-u-3-8,.pure-u-9-24{width:37.5%;*width:37.469%}.pure-u-2-5{width:40%;*width:39.969%}.pure-u-5-12,.pure-u-10-24{width:41.6667%;*width:41.6357%}.pure-u-11-24{width:45.8333%;*width:45.8023%}.pure-u-1-2,.pure-u-12-24{width:50%;*width:49.969%}.pure-u-13-24{width:54.1667%;*width:54.1357%}.pure-u-7-12,.pure-u-14-24{width:58.3333%;*width:58.3023%}.pure-u-3-5{width:60%;*width:59.969%}.pure-u-5-8,.pure-u-15-24{width:62.5%;*width:62.469%}.pure-u-2-3,.pure-u-16-24{width:66.6667%;*width:66.6357%}.pure-u-17-24{width:70.8333%;*width:70.8023%}.pure-u-3-4,.pure-u-18-24{width:75%;*width:74.969%}.pure-u-19-24{width:79.1667%;*width:79.1357%}.pure-u-4-5{width:80%;*width:79.969%}.pure-u-5-6,.pure-u-20-24{width:83.3333%;*width:83.3023%}.pure-u-7-8,.pure-u-21-24{width:87.5%;*width:87.469%}.pure-u-11-12,.pure-u-22-24{width:91.6667%;*width:91.6357%}.pure-u-23-24{width:95.8333%;*width:95.8023%}.pure-u-1,.pure-u-1-1,.pure-u-5-5,.pure-u-24-24{width:100%}.pure-button{display:inline-block;zoom:1;line-height:normal;white-space:nowrap;vertical-align:middle;text-align:center;cursor:pointer;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button{font-family:inherit;font-size:100%;padding:.5em 1em;color:#444;color:rgba(0,0,0,.8);border:1px solid #999;border:0 rgba(0,0,0,0);background-color:#E6E6E6;text-decoration:none;border-radius:2px}.pure-button-hover,.pure-button:hover,.pure-button:focus{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#1a000000', GradientType=0);background-image:-webkit-gradient(linear,0 0,0 100%,from(transparent),color-stop(40%,rgba(0,0,0,.05)),to(rgba(0,0,0,.1)));background-image:-webkit-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:-moz-linear-gradient(top,rgba(0,0,0,.05) 0,rgba(0,0,0,.1));background-image:-o-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1))}.pure-button:focus{outline:0}.pure-button-active,.pure-button:active{box-shadow:0 0 0 1px rgba(0,0,0,.15) inset,0 0 6px rgba(0,0,0,.2) inset;border-color:#000\9}.pure-button[disabled],.pure-button-disabled,.pure-button-disabled:hover,.pure-button-disabled:focus,.pure-button-disabled:active{border:0;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);filter:alpha(opacity=40);-khtml-opacity:.4;-moz-opacity:.4;opacity:.4;cursor:not-allowed;box-shadow:none}.pure-button-hidden{display:none}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button-primary,.pure-button-selected,a.pure-button-primary,a.pure-button-selected{background-color:#0078e7;color:#fff}.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form select,.pure-form textarea{padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;vertical-align:middle;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-form input:not([type]){padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-form input[type=color]{padding:.2em .5em}.pure-form input[type=text]:focus,.pure-form input[type=password]:focus,.pure-form input[type=email]:focus,.pure-form input[type=url]:focus,.pure-form input[type=date]:focus,.pure-form input[type=month]:focus,.pure-form input[type=time]:focus,.pure-form input[type=datetime]:focus,.pure-form input[type=datetime-local]:focus,.pure-form input[type=week]:focus,.pure-form input[type=number]:focus,.pure-form input[type=search]:focus,.pure-form input[type=tel]:focus,.pure-form input[type=color]:focus,.pure-form select:focus,.pure-form textarea:focus{outline:0;border-color:#129FEA}.pure-form input:not([type]):focus{outline:0;border-color:#129FEA}.pure-form input[type=file]:focus,.pure-form input[type=radio]:focus,.pure-form input[type=checkbox]:focus{outline:thin solid #129FEA;outline:1px auto #129FEA}.pure-form .pure-checkbox,.pure-form .pure-radio{margin:.5em 0;display:block}.pure-form input[type=text][disabled],.pure-form input[type=password][disabled],.pure-form input[type=email][disabled],.pure-form input[type=url][disabled],.pure-form input[type=date][disabled],.pure-form input[type=month][disabled],.pure-form input[type=time][disabled],.pure-form input[type=datetime][disabled],.pure-form input[type=datetime-local][disabled],.pure-form input[type=week][disabled],.pure-form input[type=number][disabled],.pure-form input[type=search][disabled],.pure-form input[type=tel][disabled],.pure-form input[type=color][disabled],.pure-form select[disabled],.pure-form textarea[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input:not([type])[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input[readonly],.pure-form select[readonly],.pure-form textarea[readonly]{background-color:#eee;color:#777;border-color:#ccc}.pure-form input:focus:invalid,.pure-form textarea:focus:invalid,.pure-form select:focus:invalid{color:#b94a48;border-color:#e9322d}.pure-form input[type=file]:focus:invalid:focus,.pure-form input[type=radio]:focus:invalid:focus,.pure-form input[type=checkbox]:focus:invalid:focus{outline-color:#e9322d}.pure-form select{height:2.25em;border:1px solid #ccc;background-color:#fff}.pure-form select[multiple]{height:auto}.pure-form label{margin:.5em 0 .2em}.pure-form fieldset{margin:0;padding:.35em 0 .75em;border:0}.pure-form legend{display:block;width:100%;padding:.3em 0;margin-bottom:.3em;color:#333;border-bottom:1px solid #e5e5e5}.pure-form-stacked input[type=text],.pure-form-stacked input[type=password],.pure-form-stacked input[type=email],.pure-form-stacked input[type=url],.pure-form-stacked input[type=date],.pure-form-stacked input[type=month],.pure-form-stacked input[type=time],.pure-form-stacked input[type=datetime],.pure-form-stacked input[type=datetime-local],.pure-form-stacked input[type=week],.pure-form-stacked input[type=number],.pure-form-stacked input[type=search],.pure-form-stacked input[type=tel],.pure-form-stacked input[type=color],.pure-form-stacked input[type=file],.pure-form-stacked select,.pure-form-stacked label,.pure-form-stacked textarea{display:block;margin:.25em 0}.pure-form-stacked input:not([type]){display:block;margin:.25em 0}.pure-form-aligned input,.pure-form-aligned textarea,.pure-form-aligned select,.pure-form-aligned .pure-help-inline,.pure-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.pure-form-aligned textarea{vertical-align:top}.pure-form-aligned .pure-control-group{margin-bottom:.5em}.pure-form-aligned .pure-control-group label{text-align:right;display:inline-block;vertical-align:middle;width:10em;margin:0 1em 0 0}.pure-form-aligned .pure-controls{margin:1.5em 0 0 11em}.pure-form input.pure-input-rounded,.pure-form .pure-input-rounded{border-radius:2em;padding:.5em 1em}.pure-form .pure-group fieldset{margin-bottom:10px}.pure-form .pure-group input,.pure-form .pure-group textarea{display:block;padding:10px;margin:0 0 -1px;border-radius:0;position:relative;top:-1px}.pure-form .pure-group input:focus,.pure-form .pure-group textarea:focus{z-index:3}.pure-form .pure-group input:first-child,.pure-form .pure-group textarea:first-child{top:1px;border-radius:4px 4px 0 0;margin:0}.pure-form .pure-group input:first-child:last-child,.pure-form .pure-group textarea:first-child:last-child{top:1px;border-radius:4px;margin:0}.pure-form .pure-group input:last-child,.pure-form .pure-group textarea:last-child{top:-2px;border-radius:0 0 4px 4px;margin:0}.pure-form .pure-group button{margin:.35em 0}.pure-form .pure-input-1{width:100%}.pure-form .pure-input-2-3{width:66%}.pure-form .pure-input-1-2{width:50%}.pure-form .pure-input-1-3{width:33%}.pure-form .pure-input-1-4{width:25%}.pure-form .pure-help-inline,.pure-form-message-inline{display:inline-block;padding-left:.3em;color:#666;vertical-align:middle;font-size:.875em}.pure-form-message{display:block;color:#666;font-size:.875em}@media only screen and (max-width :480px){.pure-form button[type=submit]{margin:.7em 0 0}.pure-form input:not([type]),.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form label{margin-bottom:.3em;display:block}.pure-group input:not([type]),.pure-group input[type=text],.pure-group input[type=password],.pure-group input[type=email],.pure-group input[type=url],.pure-group input[type=date],.pure-group input[type=month],.pure-group input[type=time],.pure-group input[type=datetime],.pure-group input[type=datetime-local],.pure-group input[type=week],.pure-group input[type=number],.pure-group input[type=search],.pure-group input[type=tel],.pure-group input[type=color]{margin-bottom:0}.pure-form-aligned .pure-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.pure-form-aligned .pure-controls{margin:1.5em 0 0}.pure-form .pure-help-inline,.pure-form-message-inline,.pure-form-message{display:block;font-size:.75em;padding:.2em 0 .8em}}.pure-menu{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-menu-fixed{position:fixed;left:0;top:0;z-index:3}.pure-menu-list,.pure-menu-item{position:relative}.pure-menu-list{list-style:none;margin:0;padding:0}.pure-menu-item{padding:0;margin:0;height:100%}.pure-menu-link,.pure-menu-heading{display:block;text-decoration:none;white-space:nowrap}.pure-menu-horizontal{width:100%;white-space:nowrap}.pure-menu-horizontal .pure-menu-list{display:inline-block}.pure-menu-horizontal .pure-menu-item,.pure-menu-horizontal .pure-menu-heading,.pure-menu-horizontal .pure-menu-separator{display:inline-block;*display:inline;zoom:1;vertical-align:middle}.pure-menu-item .pure-menu-item{display:block}.pure-menu-children{display:none;position:absolute;left:100%;top:0;margin:0;padding:0;z-index:3}.pure-menu-horizontal .pure-menu-children{left:0;top:auto;width:inherit}.pure-menu-allow-hover:hover>.pure-menu-children,.pure-menu-active>.pure-menu-children{display:block;position:absolute}.pure-menu-has-children>.pure-menu-link:after{padding-left:.5em;content:"\25B8";font-size:small}.pure-menu-horizontal .pure-menu-has-children>.pure-menu-link:after{content:"\25BE"}.pure-menu-scrollable{overflow-y:scroll;overflow-x:hidden}.pure-menu-scrollable .pure-menu-list{display:block}.pure-menu-horizontal.pure-menu-scrollable .pure-menu-list{display:inline-block}.pure-menu-horizontal.pure-menu-scrollable{white-space:nowrap;overflow-y:hidden;overflow-x:auto;-ms-overflow-style:none;-webkit-overflow-scrolling:touch;padding:.5em 0}.pure-menu-horizontal.pure-menu-scrollable::-webkit-scrollbar{display:none}.pure-menu-separator{background-color:#ccc;height:1px;margin:.3em 0}.pure-menu-horizontal .pure-menu-separator{width:1px;height:1.3em;margin:0 .3em}.pure-menu-heading{text-transform:uppercase;color:#565d64}.pure-menu-link{color:#777}.pure-menu-children{background-color:#fff}.pure-menu-link,.pure-menu-disabled,.pure-menu-heading{padding:.5em 1em}.pure-menu-disabled{opacity:.5}.pure-menu-disabled .pure-menu-link:hover{background-color:transparent}.pure-menu-active>.pure-menu-link,.pure-menu-link:hover,.pure-menu-link:focus{background-color:#eee}.pure-menu-selected .pure-menu-link,.pure-menu-selected .pure-menu-link:visited{color:#000}.pure-table{border-collapse:collapse;border-spacing:0;empty-cells:show;border:1px solid #cbcbcb}.pure-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.pure-table td,.pure-table th{border-left:1px solid #cbcbcb;border-width:0 0 0 1px;font-size:inherit;margin:0;overflow:visible;padding:.5em 1em}.pure-table td:first-child,.pure-table th:first-child{border-left-width:0}.pure-table thead{background-color:#e0e0e0;color:#000;text-align:left;vertical-align:bottom}.pure-table td{background-color:transparent}.pure-table-odd td{background-color:#f2f2f2}.pure-table-striped tr:nth-child(2n-1) td{background-color:#f2f2f2}.pure-table-bordered td{border-bottom:1px solid #cbcbcb}.pure-table-bordered tbody>tr:last-child>td{border-bottom-width:0}.pure-table-horizontal td,.pure-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #cbcbcb}.pure-table-horizontal tbody>tr:last-child>td{border-bottom-width:0} -------------------------------------------------------------------------------- /app/scripts/vendor/lodash.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Lo-Dash 2.4.1 (Custom Build) lodash.com/license | Underscore.js 1.5.2 underscorejs.org/LICENSE 4 | * Build: `lodash modern -o ./dist/lodash.js` 5 | */ 6 | ;(function(){function n(n,t,e){e=(e||0)-1;for(var r=n?n.length:0;++ea||typeof i=="undefined")return 1;if(ie?0:e);++r=b&&i===n,l=[];if(f){var p=o(r);p?(i=t,r=p):f=false}for(;++ui(r,p)&&l.push(p);return f&&c(r),l}function ut(n,t,e,r){r=(r||0)-1;for(var u=n?n.length:0,o=[];++r=b&&f===n,h=u||v?a():s; 18 | for(v&&(h=o(h),f=t);++if(h,y))&&((u||v)&&h.push(y),s.push(g))}return v?(l(h.k),c(h)):u&&l(h),s}function lt(n){return function(t,e,r){var u={};e=J.createCallback(e,r,3),r=-1;var o=t?t.length:0;if(typeof o=="number")for(;++re?Ie(0,o+e):e)||0,Te(n)?i=-1o&&(o=a)}}else t=null==t&&kt(n)?r:J.createCallback(t,e,3),St(n,function(n,e,r){e=t(n,e,r),e>u&&(u=e,o=n)});return o}function Dt(n,t,e,r){if(!n)return e;var u=3>arguments.length;t=J.createCallback(t,r,4);var o=-1,i=n.length;if(typeof i=="number")for(u&&(e=n[++o]);++oarguments.length;return t=J.createCallback(t,r,4),Et(n,function(n,r,o){e=u?(u=false,n):t(e,n,r,o)}),e}function Tt(n){var t=-1,e=n?n.length:0,r=Xt(typeof e=="number"?e:0);return St(n,function(n){var e=at(0,++t);r[t]=r[e],r[e]=n}),r}function Ft(n,t,e){var r;t=J.createCallback(t,e,3),e=-1;var u=n?n.length:0;if(typeof u=="number")for(;++er?Ie(0,u+r):r||0}else if(r)return r=zt(t,e),t[r]===e?r:-1;return n(t,e,r)}function qt(n,t,e){if(typeof t!="number"&&null!=t){var r=0,u=-1,o=n?n.length:0;for(t=J.createCallback(t,e,3);++u>>1,e(n[r])e?0:e);++t=v; 29 | m?(i&&(i=ve(i)),s=f,a=n.apply(l,o)):i||(i=_e(r,v))}return m&&c?c=ve(c):c||t===h||(c=_e(u,t)),e&&(m=true,a=n.apply(l,o)),!m||c||i||(o=l=null),a}}function Ut(n){return n}function Gt(n,t,e){var r=true,u=t&&bt(t);t&&(e||u.length)||(null==e&&(e=t),o=Q,t=n,n=J,u=bt(t)),false===e?r=false:wt(e)&&"chain"in e&&(r=e.chain);var o=n,i=dt(o);St(u,function(e){var u=n[e]=t[e];i&&(o.prototype[e]=function(){var t=this.__chain__,e=this.__wrapped__,i=[e];if(be.apply(i,arguments),i=u.apply(n,i),r||t){if(e===i&&wt(i))return this; 30 | i=new o(i),i.__chain__=t}return i})})}function Ht(){}function Jt(n){return function(t){return t[n]}}function Qt(){return this.__wrapped__}e=e?Y.defaults(G.Object(),e,Y.pick(G,A)):G;var Xt=e.Array,Yt=e.Boolean,Zt=e.Date,ne=e.Function,te=e.Math,ee=e.Number,re=e.Object,ue=e.RegExp,oe=e.String,ie=e.TypeError,ae=[],fe=re.prototype,le=e._,ce=fe.toString,pe=ue("^"+oe(ce).replace(/[.*+?^${}()|[\]\\]/g,"\\$&").replace(/toString| for [^\]]+/g,".*?")+"$"),se=te.ceil,ve=e.clearTimeout,he=te.floor,ge=ne.prototype.toString,ye=vt(ye=re.getPrototypeOf)&&ye,me=fe.hasOwnProperty,be=ae.push,_e=e.setTimeout,de=ae.splice,we=ae.unshift,je=function(){try{var n={},t=vt(t=re.defineProperty)&&t,e=t(n,n,n)&&t 31 | }catch(r){}return e}(),ke=vt(ke=re.create)&&ke,xe=vt(xe=Xt.isArray)&&xe,Ce=e.isFinite,Oe=e.isNaN,Ne=vt(Ne=re.keys)&&Ne,Ie=te.max,Se=te.min,Ee=e.parseInt,Re=te.random,Ae={};Ae[$]=Xt,Ae[T]=Yt,Ae[F]=Zt,Ae[B]=ne,Ae[q]=re,Ae[W]=ee,Ae[z]=ue,Ae[P]=oe,Q.prototype=J.prototype;var De=J.support={};De.funcDecomp=!vt(e.a)&&E.test(s),De.funcNames=typeof ne.name=="string",J.templateSettings={escape:/<%-([\s\S]+?)%>/g,evaluate:/<%([\s\S]+?)%>/g,interpolate:N,variable:"",imports:{_:J}},ke||(nt=function(){function n(){}return function(t){if(wt(t)){n.prototype=t; 32 | var r=new n;n.prototype=null}return r||e.Object()}}());var $e=je?function(n,t){M.value=t,je(n,"__bindData__",M)}:Ht,Te=xe||function(n){return n&&typeof n=="object"&&typeof n.length=="number"&&ce.call(n)==$||false},Fe=Ne?function(n){return wt(n)?Ne(n):[]}:H,Be={"&":"&","<":"<",">":">",'"':""","'":"'"},We=_t(Be),qe=ue("("+Fe(We).join("|")+")","g"),ze=ue("["+Fe(Be).join("")+"]","g"),Pe=ye?function(n){if(!n||ce.call(n)!=q)return false;var t=n.valueOf,e=vt(t)&&(e=ye(t))&&ye(e);return e?n==e||ye(n)==e:ht(n) 33 | }:ht,Ke=lt(function(n,t,e){me.call(n,e)?n[e]++:n[e]=1}),Le=lt(function(n,t,e){(me.call(n,e)?n[e]:n[e]=[]).push(t)}),Me=lt(function(n,t,e){n[e]=t}),Ve=Rt,Ue=vt(Ue=Zt.now)&&Ue||function(){return(new Zt).getTime()},Ge=8==Ee(d+"08")?Ee:function(n,t){return Ee(kt(n)?n.replace(I,""):n,t||0)};return J.after=function(n,t){if(!dt(t))throw new ie;return function(){return 1>--n?t.apply(this,arguments):void 0}},J.assign=U,J.at=function(n){for(var t=arguments,e=-1,r=ut(t,true,false,1),t=t[2]&&t[2][t[1]]===n?1:r.length,u=Xt(t);++e=b&&o(r?e[r]:s)))}var p=e[0],h=-1,g=p?p.length:0,y=[];n:for(;++h(m?t(m,v):f(s,v))){for(r=u,(m||s).push(v);--r;)if(m=i[r],0>(m?t(m,v):f(e[r],v)))continue n;y.push(v)}}for(;u--;)(m=i[u])&&c(m);return l(i),l(s),y},J.invert=_t,J.invoke=function(n,t){var e=p(arguments,2),r=-1,u=typeof t=="function",o=n?n.length:0,i=Xt(typeof o=="number"?o:0);return St(n,function(n){i[++r]=(u?t:n[t]).apply(n,e)}),i},J.keys=Fe,J.map=Rt,J.mapValues=function(n,t,e){var r={}; 39 | return t=J.createCallback(t,e,3),h(n,function(n,e,u){r[e]=t(n,e,u)}),r},J.max=At,J.memoize=function(n,t){function e(){var r=e.cache,u=t?t.apply(this,arguments):m+arguments[0];return me.call(r,u)?r[u]:r[u]=n.apply(this,arguments)}if(!dt(n))throw new ie;return e.cache={},e},J.merge=function(n){var t=arguments,e=2;if(!wt(n))return n;if("number"!=typeof t[2]&&(e=t.length),3e?Ie(0,r+e):Se(e,r-1))+1);r--;)if(n[r]===t)return r;return-1},J.mixin=Gt,J.noConflict=function(){return e._=le,this},J.noop=Ht,J.now=Ue,J.parseInt=Ge,J.random=function(n,t,e){var r=null==n,u=null==t;return null==e&&(typeof n=="boolean"&&u?(e=n,n=1):u||typeof t!="boolean"||(e=t,u=true)),r&&u&&(t=1),n=+n||0,u?(t=n,n=0):t=+t||0,e||n%1||t%1?(e=Re(),Se(n+e*(t-n+parseFloat("1e-"+((e+"").length-1))),t)):at(n,t) 50 | },J.reduce=Dt,J.reduceRight=$t,J.result=function(n,t){if(n){var e=n[t];return dt(e)?n[t]():e}},J.runInContext=s,J.size=function(n){var t=n?n.length:0;return typeof t=="number"?t:Fe(n).length},J.some=Ft,J.sortedIndex=zt,J.template=function(n,t,e){var r=J.templateSettings;n=oe(n||""),e=_({},e,r);var u,o=_({},e.imports,r.imports),r=Fe(o),o=xt(o),a=0,f=e.interpolate||S,l="__p+='",f=ue((e.escape||S).source+"|"+f.source+"|"+(f===N?x:S).source+"|"+(e.evaluate||S).source+"|$","g");n.replace(f,function(t,e,r,o,f,c){return r||(r=o),l+=n.slice(a,c).replace(R,i),e&&(l+="'+__e("+e+")+'"),f&&(u=true,l+="';"+f+";\n__p+='"),r&&(l+="'+((__t=("+r+"))==null?'':__t)+'"),a=c+t.length,t 51 | }),l+="';",f=e=e.variable,f||(e="obj",l="with("+e+"){"+l+"}"),l=(u?l.replace(w,""):l).replace(j,"$1").replace(k,"$1;"),l="function("+e+"){"+(f?"":e+"||("+e+"={});")+"var __t,__p='',__e=_.escape"+(u?",__j=Array.prototype.join;function print(){__p+=__j.call(arguments,'')}":";")+l+"return __p}";try{var c=ne(r,"return "+l).apply(v,o)}catch(p){throw p.source=l,p}return t?c(t):(c.source=l,c)},J.unescape=function(n){return null==n?"":oe(n).replace(qe,gt)},J.uniqueId=function(n){var t=++y;return oe(null==n?"":n)+t 52 | },J.all=Ot,J.any=Ft,J.detect=It,J.findWhere=It,J.foldl=Dt,J.foldr=$t,J.include=Ct,J.inject=Dt,Gt(function(){var n={};return h(J,function(t,e){J.prototype[e]||(n[e]=t)}),n}(),false),J.first=Bt,J.last=function(n,t,e){var r=0,u=n?n.length:0;if(typeof t!="number"&&null!=t){var o=u;for(t=J.createCallback(t,e,3);o--&&t(n[o],o,n);)r++}else if(r=t,null==r||e)return n?n[u-1]:v;return p(n,Ie(0,u-r))},J.sample=function(n,t,e){return n&&typeof n.length!="number"&&(n=xt(n)),null==t||e?n?n[at(0,n.length-1)]:v:(n=Tt(n),n.length=Se(Ie(0,t),n.length),n) 53 | },J.take=Bt,J.head=Bt,h(J,function(n,t){var e="sample"!==t;J.prototype[t]||(J.prototype[t]=function(t,r){var u=this.__chain__,o=n(this.__wrapped__,t,r);return u||null!=t&&(!r||e&&typeof t=="function")?new Q(o,u):o})}),J.VERSION="2.4.1",J.prototype.chain=function(){return this.__chain__=true,this},J.prototype.toString=function(){return oe(this.__wrapped__)},J.prototype.value=Qt,J.prototype.valueOf=Qt,St(["join","pop","shift"],function(n){var t=ae[n];J.prototype[n]=function(){var n=this.__chain__,e=t.apply(this.__wrapped__,arguments); 54 | return n?new Q(e,n):e}}),St(["push","reverse","sort","unshift"],function(n){var t=ae[n];J.prototype[n]=function(){return t.apply(this.__wrapped__,arguments),this}}),St(["concat","slice","splice"],function(n){var t=ae[n];J.prototype[n]=function(){return new Q(t.apply(this.__wrapped__,arguments),this.__chain__)}}),J}var v,h=[],g=[],y=0,m=+new Date+"",b=75,_=40,d=" \t\x0B\f\xa0\ufeff\n\r\u2028\u2029\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000",w=/\b__p\+='';/g,j=/\b(__p\+=)''\+/g,k=/(__e\(.*?\)|\b__t\))\+'';/g,x=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,C=/\w*$/,O=/^\s*function[ \n\r\t]+\w/,N=/<%=([\s\S]+?)%>/g,I=RegExp("^["+d+"]*0+(?=.$)"),S=/($^)/,E=/\bthis\b/,R=/['\n\r\t\u2028\u2029\\]/g,A="Array Boolean Date Function Math Number Object RegExp String _ attachEvent clearTimeout isFinite isNaN parseInt setTimeout".split(" "),D="[object Arguments]",$="[object Array]",T="[object Boolean]",F="[object Date]",B="[object Function]",W="[object Number]",q="[object Object]",z="[object RegExp]",P="[object String]",K={}; 55 | K[B]=false,K[D]=K[$]=K[T]=K[F]=K[W]=K[q]=K[z]=K[P]=true;var L={leading:false,maxWait:0,trailing:false},M={configurable:false,enumerable:false,value:null,writable:false},V={"boolean":false,"function":true,object:true,number:false,string:false,undefined:false},U={"\\":"\\","'":"'","\n":"n","\r":"r","\t":"t","\u2028":"u2028","\u2029":"u2029"},G=V[typeof window]&&window||this,H=V[typeof exports]&&exports&&!exports.nodeType&&exports,J=V[typeof module]&&module&&!module.nodeType&&module,Q=J&&J.exports===H&&H,X=V[typeof global]&&global;!X||X.global!==X&&X.window!==X||(G=X); 56 | var Y=s();typeof define=="function"&&typeof define.amd=="object"&&define.amd?(G._=Y, define(function(){return Y})):H&&J?Q?(J.exports=Y)._=Y:H._=Y:G._=Y}).call(this); -------------------------------------------------------------------------------- /app/scripts/vendor/Markdown.Converter.js: -------------------------------------------------------------------------------- 1 | //>>excludeStart('excludeMdown', pragmas.excludeMdown) 2 | // 3 | // edited original source code to convert API and wrap into 4 | // an AMD module, changes are before/after the wrapped code. 5 | // 6 | // you can replace the markdown converter as long as it 7 | // implements the `makeHtml()` method 8 | // 9 | define(['require', 'exports'], function(require, exports){ 10 | // ======= START WRAP 11 | 12 | 13 | var Markdown; 14 | 15 | if (typeof exports === "object" && typeof require === "function") // we're in a CommonJS (e.g. Node.js) module 16 | Markdown = exports; 17 | else 18 | Markdown = {}; 19 | 20 | // The following text is included for historical reasons, but should 21 | // be taken with a pinch of salt; it's not all true anymore. 22 | 23 | // 24 | // Wherever possible, Showdown is a straight, line-by-line port 25 | // of the Perl version of Markdown. 26 | // 27 | // This is not a normal parser design; it's basically just a 28 | // series of string substitutions. It's hard to read and 29 | // maintain this way, but keeping Showdown close to the original 30 | // design makes it easier to port new features. 31 | // 32 | // More importantly, Showdown behaves like markdown.pl in most 33 | // edge cases. So web applications can do client-side preview 34 | // in Javascript, and then build identical HTML on the server. 35 | // 36 | // This port needs the new RegExp functionality of ECMA 262, 37 | // 3rd Edition (i.e. Javascript 1.5). Most modern web browsers 38 | // should do fine. Even with the new regular expression features, 39 | // We do a lot of work to emulate Perl's regex functionality. 40 | // The tricky changes in this file mostly have the "attacklab:" 41 | // label. Major or self-explanatory changes don't. 42 | // 43 | // Smart diff tools like Araxis Merge will be able to match up 44 | // this file with markdown.pl in a useful way. A little tweaking 45 | // helps: in a copy of markdown.pl, replace "#" with "//" and 46 | // replace "$text" with "text". Be sure to ignore whitespace 47 | // and line endings. 48 | // 49 | 50 | 51 | // 52 | // Usage: 53 | // 54 | // var text = "Markdown *rocks*."; 55 | // 56 | // var converter = new Markdown.Converter(); 57 | // var html = converter.makeHtml(text); 58 | // 59 | // alert(html); 60 | // 61 | // Note: move the sample code to the bottom of this 62 | // file before uncommenting it. 63 | // 64 | 65 | (function () { 66 | 67 | function identity(x) { return x; } 68 | function returnFalse(x) { return false; } 69 | 70 | function HookCollection() { } 71 | 72 | HookCollection.prototype = { 73 | 74 | chain: function (hookname, func) { 75 | var original = this[hookname]; 76 | if (!original) 77 | throw new Error("unknown hook " + hookname); 78 | 79 | if (original === identity) 80 | this[hookname] = func; 81 | else 82 | this[hookname] = function (x) { return func(original(x)); } 83 | }, 84 | set: function (hookname, func) { 85 | if (!this[hookname]) 86 | throw new Error("unknown hook " + hookname); 87 | this[hookname] = func; 88 | }, 89 | addNoop: function (hookname) { 90 | this[hookname] = identity; 91 | }, 92 | addFalse: function (hookname) { 93 | this[hookname] = returnFalse; 94 | } 95 | }; 96 | 97 | Markdown.HookCollection = HookCollection; 98 | 99 | // g_urls and g_titles allow arbitrary user-entered strings as keys. This 100 | // caused an exception (and hence stopped the rendering) when the user entered 101 | // e.g. [push] or [__proto__]. Adding a prefix to the actual key prevents this 102 | // (since no builtin property starts with "s_"). See 103 | // http://meta.stackoverflow.com/questions/64655/strange-wmd-bug 104 | // (granted, switching from Array() to Object() alone would have left only __proto__ 105 | // to be a problem) 106 | function SaveHash() { } 107 | SaveHash.prototype = { 108 | set: function (key, value) { 109 | this["s_" + key] = value; 110 | }, 111 | get: function (key) { 112 | return this["s_" + key]; 113 | } 114 | }; 115 | 116 | Markdown.Converter = function () { 117 | var pluginHooks = this.hooks = new HookCollection(); 118 | pluginHooks.addNoop("plainLinkText"); // given a URL that was encountered by itself (without markup), should return the link text that's to be given to this link 119 | pluginHooks.addNoop("preConversion"); // called with the orignal text as given to makeHtml. The result of this plugin hook is the actual markdown source that will be cooked 120 | pluginHooks.addNoop("postConversion"); // called with the final cooked HTML code. The result of this plugin hook is the actual output of makeHtml 121 | 122 | // 123 | // Private state of the converter instance: 124 | // 125 | 126 | // Global hashes, used by various utility routines 127 | var g_urls; 128 | var g_titles; 129 | var g_html_blocks; 130 | 131 | // Used to track when we're inside an ordered or unordered list 132 | // (see _ProcessListItems() for details): 133 | var g_list_level; 134 | 135 | this.makeHtml = function (text) { 136 | 137 | // 138 | // Main function. The order in which other subs are called here is 139 | // essential. Link and image substitutions need to happen before 140 | // _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the 141 | // and tags get encoded. 142 | // 143 | 144 | // This will only happen if makeHtml on the same converter instance is called from a plugin hook. 145 | // Don't do that. 146 | if (g_urls) 147 | throw new Error("Recursive call to converter.makeHtml"); 148 | 149 | // Create the private state objects. 150 | g_urls = new SaveHash(); 151 | g_titles = new SaveHash(); 152 | g_html_blocks = []; 153 | g_list_level = 0; 154 | 155 | text = pluginHooks.preConversion(text); 156 | 157 | // attacklab: Replace ~ with ~T 158 | // This lets us use tilde as an escape char to avoid md5 hashes 159 | // The choice of character is arbitray; anything that isn't 160 | // magic in Markdown will work. 161 | text = text.replace(/~/g, "~T"); 162 | 163 | // attacklab: Replace $ with ~D 164 | // RegExp interprets $ as a special character 165 | // when it's in a replacement string 166 | text = text.replace(/\$/g, "~D"); 167 | 168 | // Standardize line endings 169 | text = text.replace(/\r\n/g, "\n"); // DOS to Unix 170 | text = text.replace(/\r/g, "\n"); // Mac to Unix 171 | 172 | // Make sure text begins and ends with a couple of newlines: 173 | text = "\n\n" + text + "\n\n"; 174 | 175 | // Convert all tabs to spaces. 176 | text = _Detab(text); 177 | 178 | // Strip any lines consisting only of spaces and tabs. 179 | // This makes subsequent regexen easier to write, because we can 180 | // match consecutive blank lines with /\n+/ instead of something 181 | // contorted like /[ \t]*\n+/ . 182 | text = text.replace(/^[ \t]+$/mg, ""); 183 | 184 | // Turn block-level HTML blocks into hash entries 185 | text = _HashHTMLBlocks(text); 186 | 187 | // Strip link definitions, store in hashes. 188 | text = _StripLinkDefinitions(text); 189 | 190 | text = _RunBlockGamut(text); 191 | 192 | text = _UnescapeSpecialChars(text); 193 | 194 | // attacklab: Restore dollar signs 195 | text = text.replace(/~D/g, "$$"); 196 | 197 | // attacklab: Restore tildes 198 | text = text.replace(/~T/g, "~"); 199 | 200 | text = pluginHooks.postConversion(text); 201 | 202 | g_html_blocks = g_titles = g_urls = null; 203 | 204 | return text; 205 | }; 206 | 207 | function _StripLinkDefinitions(text) { 208 | // 209 | // Strips link definitions from text, stores the URLs and titles in 210 | // hash references. 211 | // 212 | 213 | // Link defs are in the form: ^[id]: url "optional title" 214 | 215 | /* 216 | text = text.replace(/ 217 | ^[ ]{0,3}\[(.+)\]: // id = $1 attacklab: g_tab_width - 1 218 | [ \t]* 219 | \n? // maybe *one* newline 220 | [ \t]* 221 | ? // url = $2 222 | (?=\s|$) // lookahead for whitespace instead of the lookbehind removed below 223 | [ \t]* 224 | \n? // maybe one newline 225 | [ \t]* 226 | ( // (potential) title = $3 227 | (\n*) // any lines skipped = $4 attacklab: lookbehind removed 228 | [ \t]+ 229 | ["(] 230 | (.+?) // title = $5 231 | [")] 232 | [ \t]* 233 | )? // title is optional 234 | (?:\n+|$) 235 | /gm, function(){...}); 236 | */ 237 | 238 | text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*?(?=\s|$)[ \t]*\n?[ \t]*((\n*)["(](.+?)[")][ \t]*)?(?:\n+)/gm, 239 | function (wholeMatch, m1, m2, m3, m4, m5) { 240 | m1 = m1.toLowerCase(); 241 | g_urls.set(m1, _EncodeAmpsAndAngles(m2)); // Link IDs are case-insensitive 242 | if (m4) { 243 | // Oops, found blank lines, so it's not a title. 244 | // Put back the parenthetical statement we stole. 245 | return m3; 246 | } else if (m5) { 247 | g_titles.set(m1, m5.replace(/"/g, """)); 248 | } 249 | 250 | // Completely remove the definition from the text 251 | return ""; 252 | } 253 | ); 254 | 255 | return text; 256 | } 257 | 258 | function _HashHTMLBlocks(text) { 259 | 260 | // Hashify HTML blocks: 261 | // We only want to do this for block-level HTML tags, such as headers, 262 | // lists, and tables. That's because we still want to wrap

s around 263 | // "paragraphs" that are wrapped in non-block-level tags, such as anchors, 264 | // phrase emphasis, and spans. The list of tags we're looking for is 265 | // hard-coded: 266 | var block_tags_a = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del" 267 | var block_tags_b = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math" 268 | 269 | // First, look for nested blocks, e.g.: 270 | //

271 | //
272 | // tags for inner block must be indented. 273 | //
274 | //
275 | // 276 | // The outermost tags must start at the left margin for this to match, and 277 | // the inner nested divs must be indented. 278 | // We need to do this before the next, more liberal match, because the next 279 | // match will start at the first `
` and stop at the first `
`. 280 | 281 | // attacklab: This regex can be expensive when it fails. 282 | 283 | /* 284 | text = text.replace(/ 285 | ( // save in $1 286 | ^ // start of line (with /m) 287 | <($block_tags_a) // start tag = $2 288 | \b // word break 289 | // attacklab: hack around khtml/pcre bug... 290 | [^\r]*?\n // any number of lines, minimally matching 291 | // the matching end tag 292 | [ \t]* // trailing spaces/tabs 293 | (?=\n+) // followed by a newline 294 | ) // attacklab: there are sentinel newlines at end of document 295 | /gm,function(){...}}; 296 | */ 297 | text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm, hashElement); 298 | 299 | // 300 | // Now match more liberally, simply from `\n` to `\n` 301 | // 302 | 303 | /* 304 | text = text.replace(/ 305 | ( // save in $1 306 | ^ // start of line (with /m) 307 | <($block_tags_b) // start tag = $2 308 | \b // word break 309 | // attacklab: hack around khtml/pcre bug... 310 | [^\r]*? // any number of lines, minimally matching 311 | .* // the matching end tag 312 | [ \t]* // trailing spaces/tabs 313 | (?=\n+) // followed by a newline 314 | ) // attacklab: there are sentinel newlines at end of document 315 | /gm,function(){...}}; 316 | */ 317 | text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm, hashElement); 318 | 319 | // Special case just for
. It was easier to make a special case than 320 | // to make the other regex more complicated. 321 | 322 | /* 323 | text = text.replace(/ 324 | \n // Starting after a blank line 325 | [ ]{0,3} 326 | ( // save in $1 327 | (<(hr) // start tag = $2 328 | \b // word break 329 | ([^<>])*? 330 | \/?>) // the matching end tag 331 | [ \t]* 332 | (?=\n{2,}) // followed by a blank line 333 | ) 334 | /g,hashElement); 335 | */ 336 | text = text.replace(/\n[ ]{0,3}((<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g, hashElement); 337 | 338 | // Special case for standalone HTML comments: 339 | 340 | /* 341 | text = text.replace(/ 342 | \n\n // Starting after a blank line 343 | [ ]{0,3} // attacklab: g_tab_width - 1 344 | ( // save in $1 345 | -]|-[^>])(?:[^-]|-[^-])*)--) // see http://www.w3.org/TR/html-markup/syntax.html#comments and http://meta.stackoverflow.com/q/95256 347 | > 348 | [ \t]* 349 | (?=\n{2,}) // followed by a blank line 350 | ) 351 | /g,hashElement); 352 | */ 353 | text = text.replace(/\n\n[ ]{0,3}(-]|-[^>])(?:[^-]|-[^-])*)--)>[ \t]*(?=\n{2,}))/g, hashElement); 354 | 355 | // PHP and ASP-style processor instructions ( and <%...%>) 356 | 357 | /* 358 | text = text.replace(/ 359 | (?: 360 | \n\n // Starting after a blank line 361 | ) 362 | ( // save in $1 363 | [ ]{0,3} // attacklab: g_tab_width - 1 364 | (?: 365 | <([?%]) // $2 366 | [^\r]*? 367 | \2> 368 | ) 369 | [ \t]* 370 | (?=\n{2,}) // followed by a blank line 371 | ) 372 | /g,hashElement); 373 | */ 374 | text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g, hashElement); 375 | 376 | return text; 377 | } 378 | 379 | function hashElement(wholeMatch, m1) { 380 | var blockText = m1; 381 | 382 | // Undo double lines 383 | blockText = blockText.replace(/^\n+/, ""); 384 | 385 | // strip trailing blank lines 386 | blockText = blockText.replace(/\n+$/g, ""); 387 | 388 | // Replace the element text with a marker ("~KxK" where x is its key) 389 | blockText = "\n\n~K" + (g_html_blocks.push(blockText) - 1) + "K\n\n"; 390 | 391 | return blockText; 392 | } 393 | 394 | function _RunBlockGamut(text, doNotUnhash) { 395 | // 396 | // These are all the transformations that form block-level 397 | // tags like paragraphs, headers, and list items. 398 | // 399 | text = _DoHeaders(text); 400 | 401 | // Do Horizontal Rules: 402 | var replacement = "
\n"; 403 | text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm, replacement); 404 | text = text.replace(/^[ ]{0,2}([ ]?-[ ]?){3,}[ \t]*$/gm, replacement); 405 | text = text.replace(/^[ ]{0,2}([ ]?_[ ]?){3,}[ \t]*$/gm, replacement); 406 | 407 | text = _DoLists(text); 408 | text = _DoCodeBlocks(text); 409 | text = _DoBlockQuotes(text); 410 | 411 | // We already ran _HashHTMLBlocks() before, in Markdown(), but that 412 | // was to escape raw HTML in the original Markdown source. This time, 413 | // we're escaping the markup we've just created, so that we don't wrap 414 | //

tags around block-level tags. 415 | text = _HashHTMLBlocks(text); 416 | text = _FormParagraphs(text, doNotUnhash); 417 | 418 | return text; 419 | } 420 | 421 | function _RunSpanGamut(text) { 422 | // 423 | // These are all the transformations that occur *within* block-level 424 | // tags like paragraphs, headers, and list items. 425 | // 426 | 427 | text = _DoCodeSpans(text); 428 | text = _EscapeSpecialCharsWithinTagAttributes(text); 429 | text = _EncodeBackslashEscapes(text); 430 | 431 | // Process anchor and image tags. Images must come first, 432 | // because ![foo][f] looks like an anchor. 433 | text = _DoImages(text); 434 | text = _DoAnchors(text); 435 | 436 | // Make links out of things like `` 437 | // Must come after _DoAnchors(), because you can use < and > 438 | // delimiters in inline links like [this](). 439 | text = _DoAutoLinks(text); 440 | 441 | text = text.replace(/~P/g, "://"); // put in place to prevent autolinking; reset now 442 | 443 | text = _EncodeAmpsAndAngles(text); 444 | text = _DoItalicsAndBold(text); 445 | 446 | // Do hard breaks: 447 | text = text.replace(/ +\n/g, "
\n"); 448 | 449 | return text; 450 | } 451 | 452 | function _EscapeSpecialCharsWithinTagAttributes(text) { 453 | // 454 | // Within tags -- meaning between < and > -- encode [\ ` * _] so they 455 | // don't conflict with their use in Markdown for code, italics and strong. 456 | // 457 | 458 | // Build a regex to find HTML tags and comments. See Friedl's 459 | // "Mastering Regular Expressions", 2nd Ed., pp. 200-201. 460 | 461 | // SE: changed the comment part of the regex 462 | 463 | var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|-]|-[^>])(?:[^-]|-[^-])*)--)>)/gi; 464 | 465 | text = text.replace(regex, function (wholeMatch) { 466 | var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g, "$1`"); 467 | tag = escapeCharacters(tag, wholeMatch.charAt(1) == "!" ? "\\`*_/" : "\\`*_"); // also escape slashes in comments to prevent autolinking there -- http://meta.stackoverflow.com/questions/95987 468 | return tag; 469 | }); 470 | 471 | return text; 472 | } 473 | 474 | function _DoAnchors(text) { 475 | // 476 | // Turn Markdown link shortcuts into XHTML
tags. 477 | // 478 | // 479 | // First, handle reference-style links: [link text] [id] 480 | // 481 | 482 | /* 483 | text = text.replace(/ 484 | ( // wrap whole match in $1 485 | \[ 486 | ( 487 | (?: 488 | \[[^\]]*\] // allow brackets nested one level 489 | | 490 | [^\[] // or anything else 491 | )* 492 | ) 493 | \] 494 | 495 | [ ]? // one optional space 496 | (?:\n[ ]*)? // one optional newline followed by spaces 497 | 498 | \[ 499 | (.*?) // id = $3 500 | \] 501 | ) 502 | ()()()() // pad remaining backreferences 503 | /g, writeAnchorTag); 504 | */ 505 | text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g, writeAnchorTag); 506 | 507 | // 508 | // Next, inline-style links: [link text](url "optional title") 509 | // 510 | 511 | /* 512 | text = text.replace(/ 513 | ( // wrap whole match in $1 514 | \[ 515 | ( 516 | (?: 517 | \[[^\]]*\] // allow brackets nested one level 518 | | 519 | [^\[\]] // or anything else 520 | )* 521 | ) 522 | \] 523 | \( // literal paren 524 | [ \t]* 525 | () // no id, so leave $3 empty 526 | ? 533 | [ \t]* 534 | ( // $5 535 | (['"]) // quote char = $6 536 | (.*?) // Title = $7 537 | \6 // matching quote 538 | [ \t]* // ignore any spaces/tabs between closing quote and ) 539 | )? // title is optional 540 | \) 541 | ) 542 | /g, writeAnchorTag); 543 | */ 544 | 545 | text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g, writeAnchorTag); 546 | 547 | // 548 | // Last, handle reference-style shortcuts: [link text] 549 | // These must come last in case you've also got [link test][1] 550 | // or [link test](/foo) 551 | // 552 | 553 | /* 554 | text = text.replace(/ 555 | ( // wrap whole match in $1 556 | \[ 557 | ([^\[\]]+) // link text = $2; can't contain '[' or ']' 558 | \] 559 | ) 560 | ()()()()() // pad rest of backreferences 561 | /g, writeAnchorTag); 562 | */ 563 | text = text.replace(/(\[([^\[\]]+)\])()()()()()/g, writeAnchorTag); 564 | 565 | return text; 566 | } 567 | 568 | function writeAnchorTag(wholeMatch, m1, m2, m3, m4, m5, m6, m7) { 569 | if (m7 == undefined) m7 = ""; 570 | var whole_match = m1; 571 | var link_text = m2.replace(/:\/\//g, "~P"); // to prevent auto-linking withing the link. will be converted back after the auto-linker runs 572 | var link_id = m3.toLowerCase(); 573 | var url = m4; 574 | var title = m7; 575 | 576 | if (url == "") { 577 | if (link_id == "") { 578 | // lower-case and turn embedded newlines into spaces 579 | link_id = link_text.toLowerCase().replace(/ ?\n/g, " "); 580 | } 581 | url = "#" + link_id; 582 | 583 | if (g_urls.get(link_id) != undefined) { 584 | url = g_urls.get(link_id); 585 | if (g_titles.get(link_id) != undefined) { 586 | title = g_titles.get(link_id); 587 | } 588 | } 589 | else { 590 | if (whole_match.search(/\(\s*\)$/m) > -1) { 591 | // Special case for explicit empty url 592 | url = ""; 593 | } else { 594 | return whole_match; 595 | } 596 | } 597 | } 598 | url = encodeProblemUrlChars(url); 599 | url = escapeCharacters(url, "*_"); 600 | var result = ""; 609 | 610 | return result; 611 | } 612 | 613 | function _DoImages(text) { 614 | // 615 | // Turn Markdown image shortcuts into tags. 616 | // 617 | 618 | // 619 | // First, handle reference-style labeled images: ![alt text][id] 620 | // 621 | 622 | /* 623 | text = text.replace(/ 624 | ( // wrap whole match in $1 625 | !\[ 626 | (.*?) // alt text = $2 627 | \] 628 | 629 | [ ]? // one optional space 630 | (?:\n[ ]*)? // one optional newline followed by spaces 631 | 632 | \[ 633 | (.*?) // id = $3 634 | \] 635 | ) 636 | ()()()() // pad rest of backreferences 637 | /g, writeImageTag); 638 | */ 639 | text = text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g, writeImageTag); 640 | 641 | // 642 | // Next, handle inline images: ![alt text](url "optional title") 643 | // Don't forget: encode * and _ 644 | 645 | /* 646 | text = text.replace(/ 647 | ( // wrap whole match in $1 648 | !\[ 649 | (.*?) // alt text = $2 650 | \] 651 | \s? // One optional whitespace character 652 | \( // literal paren 653 | [ \t]* 654 | () // no id, so leave $3 empty 655 | ? // src url = $4 656 | [ \t]* 657 | ( // $5 658 | (['"]) // quote char = $6 659 | (.*?) // title = $7 660 | \6 // matching quote 661 | [ \t]* 662 | )? // title is optional 663 | \) 664 | ) 665 | /g, writeImageTag); 666 | */ 667 | text = text.replace(/(!\[(.*?)\]\s?\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g, writeImageTag); 668 | 669 | return text; 670 | } 671 | 672 | function attributeEncode(text) { 673 | // unconditionally replace angle brackets here -- what ends up in an attribute (e.g. alt or title) 674 | // never makes sense to have verbatim HTML in it (and the sanitizer would totally break it) 675 | return text.replace(/>/g, ">").replace(/" + _RunSpanGamut(m1) + "\n\n"; } 734 | ); 735 | 736 | text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm, 737 | function (matchFound, m1) { return "

" + _RunSpanGamut(m1) + "

\n\n"; } 738 | ); 739 | 740 | // atx-style headers: 741 | // # Header 1 742 | // ## Header 2 743 | // ## Header 2 with closing hashes ## 744 | // ... 745 | // ###### Header 6 746 | // 747 | 748 | /* 749 | text = text.replace(/ 750 | ^(\#{1,6}) // $1 = string of #'s 751 | [ \t]* 752 | (.+?) // $2 = Header text 753 | [ \t]* 754 | \#* // optional closing #'s (not counted) 755 | \n+ 756 | /gm, function() {...}); 757 | */ 758 | 759 | text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm, 760 | function (wholeMatch, m1, m2) { 761 | var h_level = m1.length; 762 | return "" + _RunSpanGamut(m2) + "\n\n"; 763 | } 764 | ); 765 | 766 | return text; 767 | } 768 | 769 | function _DoLists(text) { 770 | // 771 | // Form HTML ordered (numbered) and unordered (bulleted) lists. 772 | // 773 | 774 | // attacklab: add sentinel to hack around khtml/safari bug: 775 | // http://bugs.webkit.org/show_bug.cgi?id=11231 776 | text += "~0"; 777 | 778 | // Re-usable pattern to match any entirel ul or ol list: 779 | 780 | /* 781 | var whole_list = / 782 | ( // $1 = whole list 783 | ( // $2 784 | [ ]{0,3} // attacklab: g_tab_width - 1 785 | ([*+-]|\d+[.]) // $3 = first list item marker 786 | [ \t]+ 787 | ) 788 | [^\r]+? 789 | ( // $4 790 | ~0 // sentinel for workaround; should be $ 791 | | 792 | \n{2,} 793 | (?=\S) 794 | (?! // Negative lookahead for another list item marker 795 | [ \t]* 796 | (?:[*+-]|\d+[.])[ \t]+ 797 | ) 798 | ) 799 | ) 800 | /g 801 | */ 802 | var whole_list = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm; 803 | 804 | if (g_list_level) { 805 | text = text.replace(whole_list, function (wholeMatch, m1, m2) { 806 | var list = m1; 807 | var list_type = (m2.search(/[*+-]/g) > -1) ? "ul" : "ol"; 808 | 809 | var result = _ProcessListItems(list, list_type); 810 | 811 | // Trim any trailing whitespace, to put the closing `` 812 | // up on the preceding line, to get it past the current stupid 813 | // HTML block parser. This is a hack to work around the terrible 814 | // hack that is the HTML block parser. 815 | result = result.replace(/\s+$/, ""); 816 | result = "<" + list_type + ">" + result + "\n"; 817 | return result; 818 | }); 819 | } else { 820 | whole_list = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g; 821 | text = text.replace(whole_list, function (wholeMatch, m1, m2, m3) { 822 | var runup = m1; 823 | var list = m2; 824 | 825 | var list_type = (m3.search(/[*+-]/g) > -1) ? "ul" : "ol"; 826 | var result = _ProcessListItems(list, list_type); 827 | result = runup + "<" + list_type + ">\n" + result + "\n"; 828 | return result; 829 | }); 830 | } 831 | 832 | // attacklab: strip sentinel 833 | text = text.replace(/~0/, ""); 834 | 835 | return text; 836 | } 837 | 838 | var _listItemMarkers = { ol: "\\d+[.]", ul: "[*+-]" }; 839 | 840 | function _ProcessListItems(list_str, list_type) { 841 | // 842 | // Process the contents of a single ordered or unordered list, splitting it 843 | // into individual list items. 844 | // 845 | // list_type is either "ul" or "ol". 846 | 847 | // The $g_list_level global keeps track of when we're inside a list. 848 | // Each time we enter a list, we increment it; when we leave a list, 849 | // we decrement. If it's zero, we're not in a list anymore. 850 | // 851 | // We do this because when we're not inside a list, we want to treat 852 | // something like this: 853 | // 854 | // I recommend upgrading to version 855 | // 8. Oops, now this line is treated 856 | // as a sub-list. 857 | // 858 | // As a single paragraph, despite the fact that the second line starts 859 | // with a digit-period-space sequence. 860 | // 861 | // Whereas when we're inside a list (or sub-list), that line will be 862 | // treated as the start of a sub-list. What a kludge, huh? This is 863 | // an aspect of Markdown's syntax that's hard to parse perfectly 864 | // without resorting to mind-reading. Perhaps the solution is to 865 | // change the syntax rules such that sub-lists must start with a 866 | // starting cardinal number; e.g. "1." or "a.". 867 | 868 | g_list_level++; 869 | 870 | // trim trailing blank lines: 871 | list_str = list_str.replace(/\n{2,}$/, "\n"); 872 | 873 | // attacklab: add sentinel to emulate \z 874 | list_str += "~0"; 875 | 876 | // In the original attacklab showdown, list_type was not given to this function, and anything 877 | // that matched /[*+-]|\d+[.]/ would just create the next
  • , causing this mismatch: 878 | // 879 | // Markdown rendered by WMD rendered by MarkdownSharp 880 | // ------------------------------------------------------------------ 881 | // 1. first 1. first 1. first 882 | // 2. second 2. second 2. second 883 | // - third 3. third * third 884 | // 885 | // We changed this to behave identical to MarkdownSharp. This is the constructed RegEx, 886 | // with {MARKER} being one of \d+[.] or [*+-], depending on list_type: 887 | 888 | /* 889 | list_str = list_str.replace(/ 890 | (^[ \t]*) // leading whitespace = $1 891 | ({MARKER}) [ \t]+ // list marker = $2 892 | ([^\r]+? // list item text = $3 893 | (\n+) 894 | ) 895 | (?= 896 | (~0 | \2 ({MARKER}) [ \t]+) 897 | ) 898 | /gm, function(){...}); 899 | */ 900 | 901 | var marker = _listItemMarkers[list_type]; 902 | var re = new RegExp("(^[ \\t]*)(" + marker + ")[ \\t]+([^\\r]+?(\\n+))(?=(~0|\\1(" + marker + ")[ \\t]+))", "gm"); 903 | var last_item_had_a_double_newline = false; 904 | list_str = list_str.replace(re, 905 | function (wholeMatch, m1, m2, m3) { 906 | var item = m3; 907 | var leading_space = m1; 908 | var ends_with_double_newline = /\n\n$/.test(item); 909 | var contains_double_newline = ends_with_double_newline || item.search(/\n{2,}/) > -1; 910 | 911 | if (contains_double_newline || last_item_had_a_double_newline) { 912 | item = _RunBlockGamut(_Outdent(item), /* doNotUnhash = */true); 913 | } 914 | else { 915 | // Recursion for sub-lists: 916 | item = _DoLists(_Outdent(item)); 917 | item = item.replace(/\n$/, ""); // chomp(item) 918 | item = _RunSpanGamut(item); 919 | } 920 | last_item_had_a_double_newline = ends_with_double_newline; 921 | return "
  • " + item + "
  • \n"; 922 | } 923 | ); 924 | 925 | // attacklab: strip sentinel 926 | list_str = list_str.replace(/~0/g, ""); 927 | 928 | g_list_level--; 929 | return list_str; 930 | } 931 | 932 | function _DoCodeBlocks(text) { 933 | // 934 | // Process Markdown `
    ` blocks.
     935 |             //
     936 | 
     937 |             /*
     938 |             text = text.replace(/
     939 |                 (?:\n\n|^)
     940 |                 (                               // $1 = the code block -- one or more lines, starting with a space/tab
     941 |                     (?:
     942 |                         (?:[ ]{4}|\t)           // Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
     943 |                         .*\n+
     944 |                     )+
     945 |                 )
     946 |                 (\n*[ ]{0,3}[^ \t\n]|(?=~0))    // attacklab: g_tab_width
     947 |             /g ,function(){...});
     948 |             */
     949 | 
     950 |             // attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
     951 |             text += "~0";
     952 | 
     953 |             text = text.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,
     954 |                 function (wholeMatch, m1, m2) {
     955 |                     var codeblock = m1;
     956 |                     var nextChar = m2;
     957 | 
     958 |                     codeblock = _EncodeCode(_Outdent(codeblock));
     959 |                     codeblock = _Detab(codeblock);
     960 |                     codeblock = codeblock.replace(/^\n+/g, ""); // trim leading newlines
     961 |                     codeblock = codeblock.replace(/\n+$/g, ""); // trim trailing whitespace
     962 | 
     963 |                     codeblock = "
    " + codeblock + "\n
    "; 964 | 965 | return "\n\n" + codeblock + "\n\n" + nextChar; 966 | } 967 | ); 968 | 969 | // attacklab: strip sentinel 970 | text = text.replace(/~0/, ""); 971 | 972 | return text; 973 | } 974 | 975 | function hashBlock(text) { 976 | text = text.replace(/(^\n+|\n+$)/g, ""); 977 | return "\n\n~K" + (g_html_blocks.push(text) - 1) + "K\n\n"; 978 | } 979 | 980 | function _DoCodeSpans(text) { 981 | // 982 | // * Backtick quotes are used for spans. 983 | // 984 | // * You can use multiple backticks as the delimiters if you want to 985 | // include literal backticks in the code span. So, this input: 986 | // 987 | // Just type ``foo `bar` baz`` at the prompt. 988 | // 989 | // Will translate to: 990 | // 991 | //

    Just type foo `bar` baz at the prompt.

    992 | // 993 | // There's no arbitrary limit to the number of backticks you 994 | // can use as delimters. If you need three consecutive backticks 995 | // in your code, use four for delimiters, etc. 996 | // 997 | // * You can use spaces to get literal backticks at the edges: 998 | // 999 | // ... type `` `bar` `` ... 1000 | // 1001 | // Turns to: 1002 | // 1003 | // ... type `bar` ... 1004 | // 1005 | 1006 | /* 1007 | text = text.replace(/ 1008 | (^|[^\\]) // Character before opening ` can't be a backslash 1009 | (`+) // $2 = Opening run of ` 1010 | ( // $3 = The code block 1011 | [^\r]*? 1012 | [^`] // attacklab: work around lack of lookbehind 1013 | ) 1014 | \2 // Matching closer 1015 | (?!`) 1016 | /gm, function(){...}); 1017 | */ 1018 | 1019 | text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm, 1020 | function (wholeMatch, m1, m2, m3, m4) { 1021 | var c = m3; 1022 | c = c.replace(/^([ \t]*)/g, ""); // leading whitespace 1023 | c = c.replace(/[ \t]*$/g, ""); // trailing whitespace 1024 | c = _EncodeCode(c); 1025 | c = c.replace(/:\/\//g, "~P"); // to prevent auto-linking. Not necessary in code *blocks*, but in code spans. Will be converted back after the auto-linker runs. 1026 | return m1 + "" + c + ""; 1027 | } 1028 | ); 1029 | 1030 | return text; 1031 | } 1032 | 1033 | function _EncodeCode(text) { 1034 | // 1035 | // Encode/escape certain characters inside Markdown code runs. 1036 | // The point is that in code, these characters are literals, 1037 | // and lose their special Markdown meanings. 1038 | // 1039 | // Encode all ampersands; HTML entities are not 1040 | // entities within a Markdown code span. 1041 | text = text.replace(/&/g, "&"); 1042 | 1043 | // Do the angle bracket song and dance: 1044 | text = text.replace(//g, ">"); 1046 | 1047 | // Now, escape characters that are magic in Markdown: 1048 | text = escapeCharacters(text, "\*_{}[]\\", false); 1049 | 1050 | // jj the line above breaks this: 1051 | //--- 1052 | 1053 | //* Item 1054 | 1055 | // 1. Subitem 1056 | 1057 | // special char: * 1058 | //--- 1059 | 1060 | return text; 1061 | } 1062 | 1063 | function _DoItalicsAndBold(text) { 1064 | 1065 | // must go first: 1066 | text = text.replace(/([\W_]|^)(\*\*|__)(?=\S)([^\r]*?\S[\*_]*)\2([\W_]|$)/g, 1067 | "$1$3$4"); 1068 | 1069 | text = text.replace(/([\W_]|^)(\*|_)(?=\S)([^\r\*_]*?\S)\2([\W_]|$)/g, 1070 | "$1$3$4"); 1071 | 1072 | return text; 1073 | } 1074 | 1075 | function _DoBlockQuotes(text) { 1076 | 1077 | /* 1078 | text = text.replace(/ 1079 | ( // Wrap whole match in $1 1080 | ( 1081 | ^[ \t]*>[ \t]? // '>' at the start of a line 1082 | .+\n // rest of the first line 1083 | (.+\n)* // subsequent consecutive lines 1084 | \n* // blanks 1085 | )+ 1086 | ) 1087 | /gm, function(){...}); 1088 | */ 1089 | 1090 | text = text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm, 1091 | function (wholeMatch, m1) { 1092 | var bq = m1; 1093 | 1094 | // attacklab: hack around Konqueror 3.5.4 bug: 1095 | // "----------bug".replace(/^-/g,"") == "bug" 1096 | 1097 | bq = bq.replace(/^[ \t]*>[ \t]?/gm, "~0"); // trim one level of quoting 1098 | 1099 | // attacklab: clean up hack 1100 | bq = bq.replace(/~0/g, ""); 1101 | 1102 | bq = bq.replace(/^[ \t]+$/gm, ""); // trim whitespace-only lines 1103 | bq = _RunBlockGamut(bq); // recurse 1104 | 1105 | bq = bq.replace(/(^|\n)/g, "$1 "); 1106 | // These leading spaces screw with
     content, so we need to fix that:
    1107 |                     bq = bq.replace(
    1108 |                             /(\s*
    [^\r]+?<\/pre>)/gm,
    1109 |                         function (wholeMatch, m1) {
    1110 |                             var pre = m1;
    1111 |                             // attacklab: hack around Konqueror 3.5.4 bug:
    1112 |                             pre = pre.replace(/^  /mg, "~0");
    1113 |                             pre = pre.replace(/~0/g, "");
    1114 |                             return pre;
    1115 |                         });
    1116 | 
    1117 |                     return hashBlock("
    \n" + bq + "\n
    "); 1118 | } 1119 | ); 1120 | return text; 1121 | } 1122 | 1123 | function _FormParagraphs(text, doNotUnhash) { 1124 | // 1125 | // Params: 1126 | // $text - string to process with html

    tags 1127 | // 1128 | 1129 | // Strip leading and trailing lines: 1130 | text = text.replace(/^\n+/g, ""); 1131 | text = text.replace(/\n+$/g, ""); 1132 | 1133 | var grafs = text.split(/\n{2,}/g); 1134 | var grafsOut = []; 1135 | 1136 | var markerRe = /~K(\d+)K/; 1137 | 1138 | // 1139 | // Wrap

    tags. 1140 | // 1141 | var end = grafs.length; 1142 | for (var i = 0; i < end; i++) { 1143 | var str = grafs[i]; 1144 | 1145 | // if this is an HTML marker, copy it 1146 | if (markerRe.test(str)) { 1147 | grafsOut.push(str); 1148 | } 1149 | else if (/\S/.test(str)) { 1150 | str = _RunSpanGamut(str); 1151 | str = str.replace(/^([ \t]*)/g, "

    "); 1152 | str += "

    " 1153 | grafsOut.push(str); 1154 | } 1155 | 1156 | } 1157 | // 1158 | // Unhashify HTML blocks 1159 | // 1160 | if (!doNotUnhash) { 1161 | end = grafsOut.length; 1162 | for (var i = 0; i < end; i++) { 1163 | var foundAny = true; 1164 | while (foundAny) { // we may need several runs, since the data may be nested 1165 | foundAny = false; 1166 | grafsOut[i] = grafsOut[i].replace(/~K(\d+)K/g, function (wholeMatch, id) { 1167 | foundAny = true; 1168 | return g_html_blocks[id]; 1169 | }); 1170 | } 1171 | } 1172 | } 1173 | return grafsOut.join("\n\n"); 1174 | } 1175 | 1176 | function _EncodeAmpsAndAngles(text) { 1177 | // Smart processing for ampersands and angle brackets that need to be encoded. 1178 | 1179 | // Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin: 1180 | // http://bumppo.net/projects/amputator/ 1181 | text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g, "&"); 1182 | 1183 | // Encode naked <'s 1184 | text = text.replace(/<(?![a-z\/?\$!])/gi, "<"); 1185 | 1186 | return text; 1187 | } 1188 | 1189 | function _EncodeBackslashEscapes(text) { 1190 | // 1191 | // Parameter: String. 1192 | // Returns: The string, with after processing the following backslash 1193 | // escape sequences. 1194 | // 1195 | 1196 | // attacklab: The polite way to do this is with the new 1197 | // escapeCharacters() function: 1198 | // 1199 | // text = escapeCharacters(text,"\\",true); 1200 | // text = escapeCharacters(text,"`*_{}[]()>#+-.!",true); 1201 | // 1202 | // ...but we're sidestepping its use of the (slow) RegExp constructor 1203 | // as an optimization for Firefox. This function gets called a LOT. 1204 | 1205 | text = text.replace(/\\(\\)/g, escapeCharacters_callback); 1206 | text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g, escapeCharacters_callback); 1207 | return text; 1208 | } 1209 | 1210 | function _DoAutoLinks(text) { 1211 | 1212 | // note that at this point, all other URL in the text are already hyperlinked as
    1213 | // *except* for the case 1214 | 1215 | // automatically add < and > around unadorned raw hyperlinks 1216 | // must be preceded by space/BOF and followed by non-word/EOF character 1217 | text = text.replace(/(^|\s)(https?|ftp)(:\/\/[-A-Z0-9+&@#\/%?=~_|\[\]\(\)!:,\.;]*[-A-Z0-9+&@#\/%=~_|\[\]])($|\W)/gi, "$1<$2$3>$4"); 1218 | 1219 | // autolink anything like 1220 | 1221 | var replacer = function (wholematch, m1) { return "" + pluginHooks.plainLinkText(m1) + ""; } 1222 | text = text.replace(/<((https?|ftp):[^'">\s]+)>/gi, replacer); 1223 | 1224 | // Email addresses: 1225 | /* 1226 | text = text.replace(/ 1227 | < 1228 | (?:mailto:)? 1229 | ( 1230 | [-.\w]+ 1231 | \@ 1232 | [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+ 1233 | ) 1234 | > 1235 | /gi, _DoAutoLinks_callback()); 1236 | */ 1237 | 1238 | /* disabling email autolinking, since we don't do that on the server, either 1239 | text = text.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi, 1240 | function(wholeMatch,m1) { 1241 | return _EncodeEmailAddress( _UnescapeSpecialChars(m1) ); 1242 | } 1243 | ); 1244 | */ 1245 | return text; 1246 | } 1247 | 1248 | function _UnescapeSpecialChars(text) { 1249 | // 1250 | // Swap back in all the special characters we've hidden. 1251 | // 1252 | text = text.replace(/~E(\d+)E/g, 1253 | function (wholeMatch, m1) { 1254 | var charCodeToReplace = parseInt(m1); 1255 | return String.fromCharCode(charCodeToReplace); 1256 | } 1257 | ); 1258 | return text; 1259 | } 1260 | 1261 | function _Outdent(text) { 1262 | // 1263 | // Remove one level of line-leading tabs or spaces 1264 | // 1265 | 1266 | // attacklab: hack around Konqueror 3.5.4 bug: 1267 | // "----------bug".replace(/^-/g,"") == "bug" 1268 | 1269 | text = text.replace(/^(\t|[ ]{1,4})/gm, "~0"); // attacklab: g_tab_width 1270 | 1271 | // attacklab: clean up hack 1272 | text = text.replace(/~0/g, "") 1273 | 1274 | return text; 1275 | } 1276 | 1277 | function _Detab(text) { 1278 | if (!/\t/.test(text)) 1279 | return text; 1280 | 1281 | var spaces = [" ", " ", " ", " "], 1282 | skew = 0, 1283 | v; 1284 | 1285 | return text.replace(/[\n\t]/g, function (match, offset) { 1286 | if (match === "\n") { 1287 | skew = offset + 1; 1288 | return match; 1289 | } 1290 | v = (offset - skew) % 4; 1291 | skew = offset + 1; 1292 | return spaces[v]; 1293 | }); 1294 | } 1295 | 1296 | // 1297 | // attacklab: Utility functions 1298 | // 1299 | 1300 | var _problemUrlChars = /(?:["'*()[\]:]|~D)/g; 1301 | 1302 | // hex-encodes some unusual "problem" chars in URLs to avoid URL detection problems 1303 | function encodeProblemUrlChars(url) { 1304 | if (!url) 1305 | return ""; 1306 | 1307 | var len = url.length; 1308 | 1309 | return url.replace(_problemUrlChars, function (match, offset) { 1310 | if (match == "~D") // escape for dollar 1311 | return "%24"; 1312 | if (match == ":") { 1313 | if (offset == len - 1 || /[0-9\/]/.test(url.charAt(offset + 1))) 1314 | return ":" 1315 | } 1316 | return "%" + match.charCodeAt(0).toString(16); 1317 | }); 1318 | } 1319 | 1320 | 1321 | function escapeCharacters(text, charsToEscape, afterBackslash) { 1322 | // First we have to escape the escape characters so that 1323 | // we can build a character class out of them 1324 | var regexString = "([" + charsToEscape.replace(/([\[\]\\])/g, "\\$1") + "])"; 1325 | 1326 | if (afterBackslash) { 1327 | regexString = "\\\\" + regexString; 1328 | } 1329 | 1330 | var regex = new RegExp(regexString, "g"); 1331 | text = text.replace(regex, escapeCharacters_callback); 1332 | 1333 | return text; 1334 | } 1335 | 1336 | 1337 | function escapeCharacters_callback(wholeMatch, m1) { 1338 | var charCodeToEscape = m1.charCodeAt(0); 1339 | return "~E" + charCodeToEscape + "E"; 1340 | } 1341 | 1342 | }; // end of the Markdown.Converter constructor 1343 | 1344 | })(); 1345 | 1346 | 1347 | // ======= END WRAP 1348 | 1349 | // no reason for multiple instances, 1350 | // just call `makeHtml` 1351 | return new Markdown.Converter(); 1352 | }); 1353 | //>>excludeEnd('excludeMdown') 1354 | --------------------------------------------------------------------------------