├── README ├── assets ├── robots.txt ├── img │ ├── sad.png │ ├── wp7.png │ ├── happy.png │ ├── android.png │ ├── appstore.png │ ├── bg-body.gif │ └── review.png ├── css │ ├── bootstrap │ │ ├── bootstrap.less │ │ ├── variables.less │ │ ├── scaffolding.less │ │ ├── type.less │ │ ├── reset.less │ │ ├── tables.less │ │ ├── mixins.less │ │ ├── forms.less │ │ └── patterns.less │ ├── style.less │ ├── style.css │ ├── GGS.css │ └── GGS.less └── javascript │ ├── lib │ ├── yepnope-min.js │ ├── store.js │ ├── underscore-min.js │ ├── backbone-min.js │ └── GGS.js │ ├── app.coffee │ └── app.js ├── lib ├── __init__.py ├── appstore │ ├── __init__.py │ └── api.py ├── android_market │ ├── __init__.py │ └── api.py └── model_to_json.py ├── .gitignore ├── templates ├── sidebar.html ├── projects.html ├── interactions.html ├── dashboard.html └── index.html ├── tasks.py ├── app.yaml ├── index.yaml ├── main.py └── models.py /README: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/robots.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | -------------------------------------------------------------------------------- /lib/appstore/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/android_market/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/img/sad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buger/helpdesk/master/assets/img/sad.png -------------------------------------------------------------------------------- /assets/img/wp7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buger/helpdesk/master/assets/img/wp7.png -------------------------------------------------------------------------------- /assets/img/happy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buger/helpdesk/master/assets/img/happy.png -------------------------------------------------------------------------------- /assets/img/android.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buger/helpdesk/master/assets/img/android.png -------------------------------------------------------------------------------- /assets/img/appstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buger/helpdesk/master/assets/img/appstore.png -------------------------------------------------------------------------------- /assets/img/bg-body.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buger/helpdesk/master/assets/img/bg-body.gif -------------------------------------------------------------------------------- /assets/img/review.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buger/helpdesk/master/assets/img/review.png -------------------------------------------------------------------------------- /templates/sidebar.html: -------------------------------------------------------------------------------- 1 | {% raw %} 2 | 3 |

Your Projects

4 | 5 | 12 | 13 | Add Project 14 | 15 | {% endraw %} -------------------------------------------------------------------------------- /tasks.py: -------------------------------------------------------------------------------- 1 | import webapp2 2 | from models import * 3 | 4 | class Interaction(webapp2.RequestHandler): 5 | 6 | def get(self, project_id, market_type, app_id): 7 | if market_type == 'appstore': 8 | check_appstore_reviews(int(project_id), app_id) 9 | elif market_type == "android_market": 10 | check_android_reviews(int(project_id), app_id) 11 | 12 | 13 | app = webapp2.WSGIApplication( 14 | [ 15 | (r'/task/interactions/(\d+)/(\w+)/(.*)', Interaction) 16 | ], 17 | debug=True) 18 | -------------------------------------------------------------------------------- /templates/projects.html: -------------------------------------------------------------------------------- 1 | {% raw %} 2 | {{#if projects.length}} 3 |

List of your Projects

4 | 5 | 14 | {{else}} 15 |

Start by creating new project

16 | Create Project 17 | {{/if}} 18 | {% endraw %} -------------------------------------------------------------------------------- /app.yaml: -------------------------------------------------------------------------------- 1 | application: helpdesk-app 2 | version: 1 3 | runtime: python27 4 | api_version: 1 5 | threadsafe: true 6 | 7 | handlers: 8 | - url: /favicon.ico 9 | static_files: assets/img/favicon.ico 10 | upload: assets/img/favicon.ico 11 | 12 | - url: /robots.txt 13 | static_files: assets/robots.txt 14 | upload: assets/robots.txt 15 | 16 | - url: /assets 17 | static_dir: assets 18 | 19 | - url: /task/.* 20 | script: tasks.app 21 | 22 | - url: /.* 23 | script: main.app 24 | 25 | libraries: 26 | - name: jinja2 27 | version: latest 28 | - name: lxml 29 | version: latest -------------------------------------------------------------------------------- /assets/css/bootstrap/bootstrap.less: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap @VERSION 3 | * 4 | * Copyright 2011 Twitter, Inc 5 | * Licensed under the Apache License v2.0 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Designed and built with all the love in the world @twitter by @mdo and @fat. 9 | * Date: @DATE 10 | */ 11 | 12 | // CSS Reset 13 | @import "reset.less"; 14 | 15 | // Core variables and mixins 16 | @import "variables.less"; // Modify this for custom colors, font-sizes, etc 17 | @import "mixins.less"; 18 | 19 | // Grid system and page structure 20 | @import "scaffolding.less"; 21 | 22 | // Styled patterns and elements 23 | @import "type.less"; 24 | @import "forms.less"; 25 | @import "tables.less"; 26 | @import "patterns.less"; -------------------------------------------------------------------------------- /index.yaml: -------------------------------------------------------------------------------- 1 | indexes: 2 | 3 | # AUTOGENERATED 4 | 5 | # This index.yaml is automatically updated whenever the dev_appserver 6 | # detects that a new type of query is run. If you want to manage the 7 | # index.yaml file manually, remove the above marker line (the line 8 | # saying "# AUTOGENERATED"). If you want to manage some indexes 9 | # manually, move them above the marker line. The index.yaml file is 10 | # automatically uploaded to the admin console when you next deploy 11 | # your application using appcfg.py. 12 | 13 | - kind: Interaction 14 | ancestor: yes 15 | properties: 16 | - name: created_at 17 | direction: desc 18 | 19 | - kind: Interaction 20 | ancestor: yes 21 | properties: 22 | - name: date 23 | direction: desc 24 | -------------------------------------------------------------------------------- /templates/interactions.html: -------------------------------------------------------------------------------- 1 | {% raw %} 2 | {{#if interactions.length}} 3 |

{{project.name}} reviews

4 | 5 | 27 | {{else}} 28 | Loading... 29 | {{/if}} 30 | {% endraw %} -------------------------------------------------------------------------------- /lib/model_to_json.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import time 3 | 4 | from google.appengine.ext import db 5 | 6 | import json 7 | 8 | SIMPLE_TYPES = (int, long, float, bool, dict, basestring, list) 9 | 10 | def model_to_json(model): 11 | output = {} 12 | 13 | for key, prop in model.properties().iteritems(): 14 | value = getattr(model, key) 15 | 16 | if value is None or isinstance(value, SIMPLE_TYPES): 17 | output[key] = value 18 | elif isinstance(value, datetime.date): 19 | # Convert date/datetime to ms-since-epoch ("new Date()"). 20 | ms = time.mktime(value.utctimetuple()) * 1000 21 | ms += getattr(value, 'microseconds', 0) / 1000 22 | output[key] = int(ms) 23 | elif isinstance(value, db.GeoPt): 24 | output[key] = {'lat': value.lat, 'lon': value.lon} 25 | elif isinstance(value, db.Model): 26 | output[key] = model_to_json(value) 27 | else: 28 | raise ValueError('cannot encode ' + repr(prop)) 29 | 30 | try: 31 | if model.key().id(): 32 | output['id'] = str(model.key().id()) 33 | else: 34 | output['id'] = str(model.key()) 35 | except: 36 | pass 37 | 38 | return json.loads(json.dumps(output)) 39 | -------------------------------------------------------------------------------- /assets/css/style.less: -------------------------------------------------------------------------------- 1 | // Core variables and mixins 2 | @import "bootstrap/variables.less"; // Modify this for custom colors, font-sizes, etc 3 | @import "bootstrap/mixins.less"; 4 | 5 | body { 6 | padding-top: 40px; 7 | } 8 | 9 | 10 | article { 11 | 12 | aside { 13 | margin-top: 20px; 14 | 15 | > div { 16 | min-height: 500px; 17 | background: #efefef; 18 | .border-radius(10px); 19 | border: 1px solid #ccc; 20 | padding: 20px; 21 | 22 | .pills li { 23 | display: block; 24 | float: none; 25 | font-size: 20px; 26 | 27 | a { 28 | padding: 5px 15px; 29 | } 30 | } 31 | } 32 | } 33 | 34 | section { 35 | margin-top: 20px; 36 | .border-radius(10px); 37 | border: 1px solid #ccc; 38 | padding: 20px; 39 | display: none !important; 40 | background: white; 41 | 42 | &.active { 43 | display: block !important; 44 | } 45 | 46 | .interactions { 47 | li { 48 | margin-bottom: 10px; 49 | padding: 5px; 50 | padding-left: 10px; 51 | list-style: none; 52 | 53 | &.rank_1, &.rank_2, &.rank_3, &.rank_4 { 54 | color: #333; 55 | } 56 | 57 | &.rank_1, &.rank_2 { border-left: 4px solid #E31230; } 58 | &.rank_3 { border-left: 4px solid #F89406; } 59 | &.rank_4 { border-left: 4px solid #EEDC94; } 60 | &.rank_5 { border-left: 4px solid #eee; } 61 | 62 | .label.IOS { background-color: #62CFFC; } 63 | .label.Android { background-color: #A4C639; } 64 | 65 | .preview { display: none; } 66 | 67 | &.rank_5 { 68 | color: #aaa; 69 | .preview { display: block; } 70 | .full { display: none; } 71 | 72 | div { 73 | overflow: hidden; 74 | white-space: nowrap; 75 | text-overflow: ellipsis; 76 | } 77 | } 78 | } 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /assets/css/bootstrap/variables.less: -------------------------------------------------------------------------------- 1 | /* Variables.less 2 | * Variables to customize the look and feel of Bootstrap 3 | * ----------------------------------------------------- */ 4 | 5 | 6 | // Links 7 | @linkColor: #0069d6; 8 | @linkColorHover: darken(@linkColor, 15); 9 | 10 | // Grays 11 | @black: #000; 12 | @grayDark: lighten(@black, 25%); 13 | @gray: lighten(@black, 50%); 14 | @grayLight: lighten(@black, 75%); 15 | @grayLighter: lighten(@black, 90%); 16 | @white: #fff; 17 | 18 | // Accent Colors 19 | @blue: #049CDB; 20 | @blueDark: #0064CD; 21 | @green: #46a546; 22 | @red: #9d261d; 23 | @yellow: #ffc40d; 24 | @orange: #f89406; 25 | @pink: #c3325f; 26 | @purple: #7a43b6; 27 | 28 | // Baseline grid 29 | @basefont: 13px; 30 | @baseline: 18px; 31 | 32 | // Griditude 33 | // Modify the grid styles in mixins.less 34 | @gridColumns: 16; 35 | @gridColumnWidth: 40px; 36 | @gridGutterWidth: 20px; 37 | @extraSpace: (@gridGutterWidth * 2); // For our grid calculations 38 | @siteWidth: (@gridColumns * @gridColumnWidth) + (@gridGutterWidth * (@gridColumns - 1)); 39 | 40 | // Color Scheme 41 | // Use this to roll your own color schemes if you like (unused by Bootstrap by default) 42 | @baseColor: @blue; // Set a base color 43 | @complement: spin(@baseColor, 180); // Determine a complementary color 44 | @split1: spin(@baseColor, 158); // Split complements 45 | @split2: spin(@baseColor, -158); 46 | @triad1: spin(@baseColor, 135); // Triads colors 47 | @triad2: spin(@baseColor, -135); 48 | @tetra1: spin(@baseColor, 90); // Tetra colors 49 | @tetra2: spin(@baseColor, -90); 50 | @analog1: spin(@baseColor, 22); // Analogs colors 51 | @analog2: spin(@baseColor, -22); 52 | 53 | 54 | 55 | // More variables coming soon: 56 | // - @basefont to @baseFontSize 57 | // - @baseline to @baseLineHeight 58 | // - @baseFontFamily 59 | // - @primaryButtonColor 60 | // - anything else? File an issue on GitHub -------------------------------------------------------------------------------- /assets/css/style.css: -------------------------------------------------------------------------------- 1 | /* Variables.less 2 | * Variables to customize the look and feel of Bootstrap 3 | * ----------------------------------------------------- */ 4 | /* Mixins.less 5 | * Snippets of reusable CSS to develop faster and keep code readable 6 | * ----------------------------------------------------------------- */ 7 | body { 8 | padding-top: 40px; 9 | } 10 | article aside { 11 | margin-top: 20px; 12 | } 13 | article aside > div { 14 | min-height: 500px; 15 | background: #efefef; 16 | -webkit-border-radius: 10px; 17 | -moz-border-radius: 10px; 18 | border-radius: 10px; 19 | border: 1px solid #ccc; 20 | padding: 20px; 21 | } 22 | article aside > div .pills li { 23 | display: block; 24 | float: none; 25 | font-size: 20px; 26 | } 27 | article aside > div .pills li a { 28 | padding: 5px 15px; 29 | } 30 | article section { 31 | margin-top: 20px; 32 | -webkit-border-radius: 10px; 33 | -moz-border-radius: 10px; 34 | border-radius: 10px; 35 | border: 1px solid #ccc; 36 | padding: 20px; 37 | display: none !important; 38 | background: white; 39 | } 40 | article section.active { 41 | display: block !important; 42 | } 43 | article section .interactions li { 44 | margin-bottom: 10px; 45 | padding: 5px; 46 | padding-left: 10px; 47 | list-style: none; 48 | } 49 | article section .interactions li.rank_1, 50 | article section .interactions li.rank_2, 51 | article section .interactions li.rank_3, 52 | article section .interactions li.rank_4 { 53 | color: #333; 54 | } 55 | article section .interactions li.rank_1, article section .interactions li.rank_2 { 56 | border-left: 4px solid #E31230; 57 | } 58 | article section .interactions li.rank_3 { 59 | border-left: 4px solid #F89406; 60 | } 61 | article section .interactions li.rank_4 { 62 | border-left: 4px solid #EEDC94; 63 | } 64 | article section .interactions li.rank_5 { 65 | border-left: 4px solid #eee; 66 | } 67 | article section .interactions li .label.IOS { 68 | background-color: #62CFFC; 69 | } 70 | article section .interactions li .label.Android { 71 | background-color: #A4C639; 72 | } 73 | article section .interactions li .preview { 74 | display: none; 75 | } 76 | article section .interactions li.rank_5 { 77 | color: #aaa; 78 | } 79 | article section .interactions li.rank_5 .preview { 80 | display: block; 81 | } 82 | article section .interactions li.rank_5 .full { 83 | display: none; 84 | } 85 | article section .interactions li.rank_5 div { 86 | overflow: hidden; 87 | white-space: nowrap; 88 | text-overflow: ellipsis; 89 | } 90 | -------------------------------------------------------------------------------- /templates/dashboard.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |
10 |
11 | Helpdesk 12 | 13 | 18 |
19 |
20 |
21 | 22 |
23 |
24 | 29 |
30 |
31 | Loading... 32 |
33 |
34 | Loading... 35 |
36 |
37 |
38 |

Creating Project

39 | 40 |
41 |
42 | 43 |
44 | 45 |
46 |
47 |
48 | 49 |
50 |
51 | 52 |
53 | 54 |
55 |
56 |
57 | 58 |
59 | 60 |
61 |
62 |
63 |
64 | 65 | Cancel 66 |
67 |
68 |
69 |
70 |
71 |
72 | 73 | 76 | 79 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /assets/javascript/lib/yepnope-min.js: -------------------------------------------------------------------------------- 1 | /*yepnope1.0.2|WTFPL*/(function(a,b,c){function H(){var a=z;a.loader={load:G,i:0};return a}function G(a,b,c){var e=b=="c"?r:q;i=0,b=b||"j",u(a)?F(e,a,b,this.i++,d,c):(h.splice(this.i++,0,a),h.length==1&&E());return this}function F(a,c,d,g,j,l){function q(){!o&&A(n.readyState)&&(p.r=o=1,!i&&B(),n.onload=n.onreadystatechange=null,e(function(){m.removeChild(n)},0))}var n=b.createElement(a),o=0,p={t:d,s:c,e:l};n.src=n.data=c,!k&&(n.style.display="none"),n.width=n.height="0",a!="object"&&(n.type=d),n.onload=n.onreadystatechange=q,a=="img"?n.onerror=q:a=="script"&&(n.onerror=function(){p.e=p.r=1,E()}),h.splice(g,0,p),m.insertBefore(n,k?null:f),e(function(){o||(m.removeChild(n),p.r=p.e=o=1,B())},z.errorTimeout)}function E(){var a=h.shift();i=1,a?a.t?e(function(){a.t=="c"?D(a):C(a)},0):(a(),B()):i=0}function D(a){var c=b.createElement("link"),d;c.href=a.s,c.rel="stylesheet",c.type="text/css";if(!a.e&&(o||j)){var g=function(a){e(function(){if(!d)try{a.sheet.cssRules.length?(d=1,B()):g(a)}catch(b){b.code==1e3||b.message=="security"||b.message=="denied"?(d=1,e(function(){B()},0)):g(a)}},0)};g(c)}else c.onload=function(){d||(d=1,e(function(){B()},0))},a.e&&c.onload();e(function(){d||(d=1,B())},z.errorTimeout),!a.e&&f.parentNode.insertBefore(c,f)}function C(a){var c=b.createElement("script"),d;c.src=a.s,c.onreadystatechange=c.onload=function(){!d&&A(c.readyState)&&(d=1,B(),c.onload=c.onreadystatechange=null)},e(function(){d||(d=1,B())},z.errorTimeout),a.e?c.onload():f.parentNode.insertBefore(c,f)}function B(){var a=1,b=-1;while(h.length- ++b)if(h[b].s&&!(a=h[b].r))break;a&&E()}function A(a){return!a||a=="loaded"||a=="complete"}var d=b.documentElement,e=a.setTimeout,f=b.getElementsByTagName("script")[0],g={}.toString,h=[],i=0,j="MozAppearance"in d.style,k=j&&!!b.createRange().compareNode,l=j&&!k,m=k?d:f.parentNode,n=a.opera&&g.call(a.opera)=="[object Opera]",o="webkitAppearance"in d.style,p=o&&"async"in b.createElement("script"),q=j?"object":n||p?"img":"script",r=o?"img":q,s=Array.isArray||function(a){return g.call(a)=="[object Array]"},t=function(a){return Object(a)===a},u=function(a){return typeof a=="string"},v=function(a){return g.call(a)=="[object Function]"},w=[],x={},y,z;z=function(a){function h(a,b){function i(a){if(u(a))g(a,f,b,0,c);else if(t(a))for(h in a)a.hasOwnProperty(h)&&g(a[h],f,b,h,c)}var c=!!a.test,d=c?a.yep:a.nope,e=a.load||a.both,f=a.callback,h;i(d),i(e),a.complete&&b.load(a.complete)}function g(a,b,d,e,g){var h=f(a),i=h.autoCallback;if(!h.bypass){b&&(b=v(b)?b:b[a]||b[e]||b[a.split("/").pop().split("?")[0]]);if(h.instead)return h.instead(a,b,d,e,g);d.load(h.url,h.forceCSS||!h.forceJS&&/css$/.test(h.url)?"c":c,h.noexec),(v(b)||v(i))&&d.load(function(){H(),b&&b(h.origUrl,g,e),i&&i(h.origUrl,g,e)})}}function f(a){var b=a.split("!"),c=w.length,d=b.pop(),e=b.length,f={url:d,origUrl:d,prefixes:b},g,h;for(h=0;h 0: 65 | version = el.xpath("text()")[0].strip() 66 | else: 67 | version = "" 68 | 69 | if re.search(u"with", version): 70 | # Phone with version - Samsung Galaxy S with version 1.2.03 71 | m = re.search("\((.*) with version (.*)\)", version) 72 | version = m.group(2) 73 | device = m.group(1) 74 | else: 75 | m = re.search("Version ([\d\.]*)", version) 76 | 77 | if m is None: 78 | # Phone without version - SEMC Xperia X10 79 | m = re.search("\((.*)\)", version) 80 | version = None 81 | if m: 82 | device = m.group(1) 83 | else: 84 | device = None 85 | else: 86 | # Version without phone 87 | version = m.group(1) 88 | device = None 89 | 90 | title = el.find('div/h4').text 91 | 92 | if device is None: 93 | m1 = re.search("galaxy nexus", review or "", re.I) 94 | m2 = re.search("galaxy nexus", title or "", re.I) 95 | 96 | if m1 is not None or m2 is not None: 97 | device = "Galaxy Nexus" 98 | 99 | 100 | review = { 101 | "user": user, 102 | "date": date, 103 | "version": version, 104 | "device": device, 105 | "title": title, 106 | "rank": int(el.xpath("count(div/div/div[contains(@class,'SPRITE_star_on_dark')])")), 107 | "review": review 108 | } 109 | 110 | reviews.append(review) 111 | 112 | return reviews -------------------------------------------------------------------------------- /assets/css/bootstrap/scaffolding.less: -------------------------------------------------------------------------------- 1 | /* 2 | * Scaffolding 3 | * Basic and global styles for generating a grid system, structural layout, and page templates 4 | * ------------------------------------------------------------------------------------------- */ 5 | 6 | 7 | // STRUCTURAL LAYOUT 8 | // ----------------- 9 | 10 | body { 11 | background-color: @white; 12 | margin: 0; 13 | #font > .sans-serif(normal,@basefont,@baseline); 14 | color: @grayDark; 15 | } 16 | 17 | // Container (centered, fixed-width layouts) 18 | .container { 19 | .fixed-container(); 20 | } 21 | 22 | // Fluid layouts (left aligned, with sidebar, min- & max-width content) 23 | .container-fluid { 24 | position: relative; 25 | min-width: 940px; 26 | padding-left: 20px; 27 | padding-right: 20px; 28 | .clearfix(); 29 | > .sidebar { 30 | position: absolute; 31 | top: 0; 32 | left: 20px; 33 | width: 220px; 34 | } 35 | // TODO in v2: rename this and .popover .content to be more specific 36 | > .content { 37 | margin-left: 240px; 38 | } 39 | } 40 | 41 | 42 | // BASE STYLES 43 | // ----------- 44 | 45 | // Links 46 | a { 47 | color: @linkColor; 48 | text-decoration: none; 49 | line-height: inherit; 50 | font-weight: inherit; 51 | &:hover { 52 | color: @linkColorHover; 53 | text-decoration: underline; 54 | } 55 | } 56 | 57 | // Quick floats 58 | .pull-right { 59 | float: right; 60 | } 61 | .pull-left { 62 | float: left; 63 | } 64 | 65 | // Toggling content 66 | .hide { 67 | display: none; 68 | } 69 | .show { 70 | display: block; 71 | } 72 | 73 | 74 | // GRID SYSTEM 75 | // ----------- 76 | // To customize the grid system, bring up the variables.less file and change the column count, size, and gutter there 77 | 78 | .row { 79 | .clearfix(); 80 | margin-left: -@gridGutterWidth; 81 | } 82 | 83 | // Find all .span# classes within .row and give them the necessary properties for grid columns (supported by all browsers back to IE7) 84 | // Credit to @dhg for the idea 85 | .row > [class*="span"] { 86 | .gridColumn(); 87 | } 88 | 89 | // Default columns 90 | .span1 { .columns(1); } 91 | .span2 { .columns(2); } 92 | .span3 { .columns(3); } 93 | .span4 { .columns(4); } 94 | .span5 { .columns(5); } 95 | .span6 { .columns(6); } 96 | .span7 { .columns(7); } 97 | .span8 { .columns(8); } 98 | .span9 { .columns(9); } 99 | .span10 { .columns(10); } 100 | .span11 { .columns(11); } 101 | .span12 { .columns(12); } 102 | .span13 { .columns(13); } 103 | .span14 { .columns(14); } 104 | .span15 { .columns(15); } 105 | .span16 { .columns(16); } 106 | 107 | // For optional 24-column grid 108 | .span17 { .columns(17); } 109 | .span18 { .columns(18); } 110 | .span19 { .columns(19); } 111 | .span20 { .columns(20); } 112 | .span21 { .columns(21); } 113 | .span22 { .columns(22); } 114 | .span23 { .columns(23); } 115 | .span24 { .columns(24); } 116 | 117 | // Offset column options 118 | .row { 119 | > .offset1 { .offset(1); } 120 | > .offset2 { .offset(2); } 121 | > .offset3 { .offset(3); } 122 | > .offset4 { .offset(4); } 123 | > .offset5 { .offset(5); } 124 | > .offset6 { .offset(6); } 125 | > .offset7 { .offset(7); } 126 | > .offset8 { .offset(8); } 127 | > .offset9 { .offset(9); } 128 | > .offset10 { .offset(10); } 129 | > .offset11 { .offset(11); } 130 | > .offset12 { .offset(12); } 131 | } 132 | 133 | // Unique column sizes for 16-column grid 134 | .span-one-third { width: 300px; } 135 | .span-two-thirds { width: 620px; } 136 | .row { 137 | > .offset-one-third { margin-left: 340px; } 138 | > .offset-two-thirds { margin-left: 660px; } 139 | } -------------------------------------------------------------------------------- /assets/css/bootstrap/type.less: -------------------------------------------------------------------------------- 1 | /* Typography.less 2 | * Headings, body text, lists, code, and more for a versatile and durable typography system 3 | * ---------------------------------------------------------------------------------------- */ 4 | 5 | 6 | // BODY TEXT 7 | // --------- 8 | 9 | p { 10 | #font > .shorthand(normal,@basefont,@baseline); 11 | margin-bottom: @baseline / 2; 12 | small { 13 | font-size: @basefont - 2; 14 | color: @grayLight; 15 | } 16 | } 17 | 18 | 19 | // HEADINGS 20 | // -------- 21 | 22 | h1, h2, h3, h4, h5, h6 { 23 | font-weight: bold; 24 | color: @grayDark; 25 | small { 26 | color: @grayLight; 27 | } 28 | } 29 | h1 { 30 | margin-bottom: @baseline; 31 | font-size: 30px; 32 | line-height: @baseline * 2; 33 | small { 34 | font-size: 18px; 35 | } 36 | } 37 | h2 { 38 | font-size: 24px; 39 | line-height: @baseline * 2; 40 | small { 41 | font-size: 14px; 42 | } 43 | } 44 | h3, h4, h5, h6 { 45 | line-height: @baseline * 2; 46 | } 47 | h3 { 48 | font-size: 18px; 49 | small { 50 | font-size: 14px; 51 | } 52 | } 53 | h4 { 54 | font-size: 16px; 55 | small { 56 | font-size: 12px; 57 | } 58 | } 59 | h5 { 60 | font-size: 14px; 61 | } 62 | h6 { 63 | font-size: 13px; 64 | color: @grayLight; 65 | text-transform: uppercase; 66 | } 67 | 68 | 69 | // COLORS 70 | // ------ 71 | 72 | // Unordered and Ordered lists 73 | ul, ol { 74 | margin: 0 0 @baseline 25px; 75 | } 76 | ul ul, 77 | ul ol, 78 | ol ol, 79 | ol ul { 80 | margin-bottom: 0; 81 | } 82 | ul { 83 | list-style: disc; 84 | } 85 | ol { 86 | list-style: decimal; 87 | } 88 | li { 89 | line-height: @baseline; 90 | color: @gray; 91 | } 92 | ul.unstyled { 93 | list-style: none; 94 | margin-left: 0; 95 | } 96 | 97 | // Description Lists 98 | dl { 99 | margin-bottom: @baseline; 100 | dt, dd { 101 | line-height: @baseline; 102 | } 103 | dt { 104 | font-weight: bold; 105 | } 106 | dd { 107 | margin-left: @baseline / 2; 108 | } 109 | } 110 | 111 | // MISC 112 | // ---- 113 | 114 | // Horizontal rules 115 | hr { 116 | margin: 20px 0 19px; 117 | border: 0; 118 | border-bottom: 1px solid #eee; 119 | } 120 | 121 | // Emphasis 122 | strong { 123 | font-style: inherit; 124 | font-weight: bold; 125 | } 126 | em { 127 | font-style: italic; 128 | font-weight: inherit; 129 | line-height: inherit; 130 | } 131 | .muted { 132 | color: @grayLight; 133 | } 134 | 135 | // Blockquotes 136 | blockquote { 137 | margin-bottom: @baseline; 138 | border-left: 5px solid #eee; 139 | padding-left: 15px; 140 | p { 141 | #font > .shorthand(300,14px,@baseline); 142 | margin-bottom: 0; 143 | } 144 | small { 145 | display: block; 146 | #font > .shorthand(300,12px,@baseline); 147 | color: @grayLight; 148 | &:before { 149 | content: '\2014 \00A0'; 150 | } 151 | } 152 | } 153 | 154 | // Addresses 155 | address { 156 | display: block; 157 | line-height: @baseline; 158 | margin-bottom: @baseline; 159 | } 160 | 161 | // Inline and block code styles 162 | code, pre { 163 | padding: 0 3px 2px; 164 | font-family: Monaco, Andale Mono, Courier New, monospace; 165 | font-size: 12px; 166 | .border-radius(3px); 167 | } 168 | code { 169 | background-color: lighten(@orange, 40%); 170 | color: rgba(0,0,0,.75); 171 | padding: 1px 3px; 172 | } 173 | pre { 174 | background-color: #f5f5f5; 175 | display: block; 176 | padding: (@baseline - 1) / 2; 177 | margin: 0 0 @baseline; 178 | line-height: @baseline; 179 | font-size: 12px; 180 | border: 1px solid #ccc; 181 | border: 1px solid rgba(0,0,0,.15); 182 | .border-radius(3px); 183 | white-space: pre; 184 | white-space: pre-wrap; 185 | word-wrap: break-word; 186 | 187 | } -------------------------------------------------------------------------------- /assets/javascript/app.coffee: -------------------------------------------------------------------------------- 1 | @App = 2 | views: {} 3 | 4 | 5 | class Interaction extends Backbone.Model 6 | 7 | class InteractionsCollection extends Backbone.Collection 8 | model: Interaction 9 | 10 | 11 | class Project extends Backbone.Model 12 | initialize: -> 13 | @interactions = new InteractionsCollection() 14 | @interactions.url = "/api/projects/#{@id}" 15 | 16 | 17 | class ProjectsCollection extends Backbone.Collection 18 | 19 | model: Project 20 | 21 | url: "/api/projects" 22 | 23 | App.projects = new ProjectsCollection() 24 | App.projects.reset(projects) 25 | 26 | 27 | class Section extends Backbone.View 28 | 29 | render: -> 30 | $('#content .active').removeClass() 31 | @el.addClass('active') 32 | @el 33 | 34 | 35 | class ProjectsList extends Section 36 | template: Handlebars.compile $("#projects_tmpl").html() 37 | 38 | el: $('#projects_list') 39 | 40 | events: 41 | "click .delete": 'deleteProject' 42 | 43 | 44 | render: -> 45 | Section::render.call @ 46 | 47 | @el.html @template 'projects': App.projects.toJSON() 48 | 49 | 50 | deleteProject: (evt) -> 51 | if confirm('Delete project?') 52 | pid = $.attr evt.currentTarget, 'data-project' 53 | 54 | project = App.projects.get(pid) 55 | 56 | project.destroy 57 | success: => @render() 58 | error: -> console.error("Can't delete project") 59 | 60 | 61 | App.views.projects_list = new ProjectsList() 62 | 63 | 64 | class AddProjectForm extends Section 65 | 66 | el: $('#add_project') 67 | 68 | events: 69 | "submit form": 'submit' 70 | 71 | 72 | render: -> 73 | Section::render.call @ 74 | 75 | @$('form').trigger('reset') 76 | 77 | 78 | submit: (evt) -> 79 | fields = @$('form').serializeArray() 80 | 81 | project = {} 82 | 83 | for field in fields 84 | project[field.name] = field.value 85 | 86 | App.projects.create project, 87 | success: -> 88 | App.router.navigate('', true) 89 | error: -> 90 | console.error ':(' 91 | 92 | App.views.add_project_form = new AddProjectForm() 93 | 94 | 95 | class InteractionsList extends Section 96 | 97 | template: Handlebars.compile $("#interactions_tmpl").html() 98 | 99 | el: $('#interactions') 100 | 101 | 102 | render: (project_id) -> 103 | Section::render.call @ 104 | 105 | project = App.projects.get(project_id) 106 | 107 | @el.html @template { 108 | 'interactions': project.interactions.toJSON() 109 | 'project': project.toJSON() 110 | } 111 | 112 | project.interactions.fetch 113 | success: => 114 | @el.html @template { 115 | 'interactions': project.interactions.toJSON() 116 | 'project': project.toJSON() 117 | } 118 | 119 | App.views.interactions_list = new InteractionsList() 120 | 121 | 122 | class Sidebar extends Backbone.View 123 | 124 | template: Handlebars.compile $("#sidebar_tmpl").html() 125 | 126 | el: $('#sidebar') 127 | 128 | initialize: -> @render() 129 | 130 | render: (project_id) -> 131 | projects = App.projects.toJSON() 132 | projects = _.map projects, (p) -> 133 | p.active = project_id is p.id 134 | p 135 | 136 | @el.html @template { 137 | 'projects': projects 138 | 'active_project': project_id 139 | } 140 | 141 | App.views.sidebar = new Sidebar() 142 | 143 | 144 | 145 | class AppRouter extends Backbone.Router 146 | 147 | routes: 148 | ".*": "home" 149 | "add": "addProject" 150 | "interactions/:pid": "showInteractions" 151 | 152 | home: -> 153 | App.views.projects_list.render() 154 | App.views.sidebar.render() 155 | 156 | addProject: -> 157 | App.views.add_project_form.render() 158 | 159 | showInteractions: (project_id) -> 160 | App.views.interactions_list.render(project_id) 161 | App.views.sidebar.render(project_id) 162 | 163 | 164 | App.router = new AppRouter() 165 | 166 | $('a').live 'click', (evt) -> 167 | href = $.attr(evt.currentTarget, "href") 168 | 169 | if href?.charAt(0) is '/' 170 | href = href.substr(1) 171 | App.router.navigate href, true 172 | false 173 | 174 | 175 | _.defer -> Backbone.history.start pushState: true -------------------------------------------------------------------------------- /assets/css/bootstrap/reset.less: -------------------------------------------------------------------------------- 1 | /* Reset.less 2 | * Props to Eric Meyer (meyerweb.com) for his CSS reset file. We're using an adapted version here that cuts out some of the reset HTML elements we will never need here (i.e., dfn, samp, etc). 3 | * ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- */ 4 | 5 | 6 | // ERIC MEYER RESET 7 | // -------------------------------------------------- 8 | 9 | html, body { margin: 0; padding: 0; } 10 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, cite, code, del, dfn, em, img, q, s, samp, small, strike, strong, sub, sup, tt, var, dd, dl, dt, li, ol, ul, fieldset, form, label, legend, button, table, caption, tbody, tfoot, thead, tr, th, td { margin: 0; padding: 0; border: 0; font-weight: normal; font-style: normal; font-size: 100%; line-height: 1; font-family: inherit; } 11 | table { border-collapse: collapse; border-spacing: 0; } 12 | ol, ul { list-style: none; } 13 | q:before, q:after, blockquote:before, blockquote:after { content: ""; } 14 | 15 | 16 | // Normalize.css 17 | // Pulling in select resets form the normalize.css project 18 | // -------------------------------------------------- 19 | 20 | // Display in IE6-9 and FF3 21 | // ------------------------- 22 | // Source: http://github.com/necolas/normalize.css 23 | html { 24 | overflow-y: scroll; 25 | font-size: 100%; 26 | -webkit-text-size-adjust: 100%; 27 | -ms-text-size-adjust: 100%; 28 | } 29 | // Focus states 30 | a:focus { 31 | outline: thin dotted; 32 | } 33 | // Hover & Active 34 | a:hover, 35 | a:active { 36 | outline: 0; 37 | } 38 | 39 | // Display in IE6-9 and FF3 40 | // ------------------------- 41 | // Source: http://github.com/necolas/normalize.css 42 | article, 43 | aside, 44 | details, 45 | figcaption, 46 | figure, 47 | footer, 48 | header, 49 | hgroup, 50 | nav, 51 | section { 52 | display: block; 53 | } 54 | 55 | // Display block in IE6-9 and FF3 56 | // ------------------------- 57 | // Source: http://github.com/necolas/normalize.css 58 | audio, 59 | canvas, 60 | video { 61 | display: inline-block; 62 | *display: inline; 63 | *zoom: 1; 64 | } 65 | 66 | // Prevents modern browsers from displaying 'audio' without controls 67 | // ------------------------- 68 | // Source: http://github.com/necolas/normalize.css 69 | audio:not([controls]) { 70 | display: none; 71 | } 72 | 73 | // Prevents sub and sup affecting line-height in all browsers 74 | // ------------------------- 75 | // Source: http://github.com/necolas/normalize.css 76 | sub, 77 | sup { 78 | font-size: 75%; 79 | line-height: 0; 80 | position: relative; 81 | vertical-align: baseline; 82 | } 83 | sup { 84 | top: -0.5em; 85 | } 86 | sub { 87 | bottom: -0.25em; 88 | } 89 | 90 | // Img border in a's and image quality 91 | // ------------------------- 92 | // Source: http://github.com/necolas/normalize.css 93 | img { 94 | border: 0; 95 | -ms-interpolation-mode: bicubic; 96 | } 97 | 98 | // Forms 99 | // ------------------------- 100 | // Source: http://github.com/necolas/normalize.css 101 | 102 | // Font size in all browsers, margin changes, misc consistency 103 | button, 104 | input, 105 | select, 106 | textarea { 107 | font-size: 100%; 108 | margin: 0; 109 | vertical-align: baseline; 110 | *vertical-align: middle; 111 | } 112 | button, 113 | input { 114 | line-height: normal; // FF3/4 have !important on line-height in UA stylesheet 115 | *overflow: visible; // Inner spacing ie IE6/7 116 | } 117 | button::-moz-focus-inner, 118 | input::-moz-focus-inner { // Inner padding and border oddities in FF3/4 119 | border: 0; 120 | padding: 0; 121 | } 122 | button, 123 | input[type="button"], 124 | input[type="reset"], 125 | input[type="submit"] { 126 | cursor: pointer; // Cursors on all buttons applied consistently 127 | -webkit-appearance: button; // Style clicable inputs in iOS 128 | } 129 | input[type="search"] { // Appearance in Safari/Chrome 130 | -webkit-appearance: textfield; 131 | -webkit-box-sizing: content-box; 132 | -moz-box-sizing: content-box; 133 | box-sizing: content-box; 134 | } 135 | input[type="search"]::-webkit-search-decoration { 136 | -webkit-appearance: none; // Inner-padding issues in Chrome OSX, Safari 5 137 | } 138 | textarea { 139 | overflow: auto; // Remove vertical scrollbar in IE6-9 140 | vertical-align: top; // Readability and alignment cross-browser 141 | } -------------------------------------------------------------------------------- /assets/javascript/lib/store.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2010-2011 Marcus Westin 2 | * 3 | * Permission is hereby granted, free of charge, to any person obtaining a copy 4 | * of this software and associated documentation files (the "Software"), to deal 5 | * in the Software without restriction, including without limitation the rights 6 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | * copies of the Software, and to permit persons to whom the Software is 8 | * furnished to do so, subject to the following conditions: 9 | * 10 | * The above copyright notice and this permission notice shall be included in 11 | * all copies or substantial portions of the Software. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | * THE SOFTWARE. 20 | */ 21 | 22 | var store = (function(){ 23 | var api = {}, 24 | win = window, 25 | doc = win.document, 26 | localStorageName = 'localStorage', 27 | globalStorageName = 'globalStorage', 28 | namespace = '__storejs__', 29 | storage 30 | 31 | api.disabled = false 32 | api.set = function(key, value) {} 33 | api.get = function(key) {} 34 | api.remove = function(key) {} 35 | api.clear = function() {} 36 | api.transact = function(key, transactionFn) { 37 | var val = api.get(key) 38 | if (typeof val == 'undefined') { val = {} } 39 | transactionFn(val) 40 | api.set(key, val) 41 | } 42 | 43 | api.serialize = function(value) { 44 | return JSON.stringify(value) 45 | } 46 | api.deserialize = function(value) { 47 | if (typeof value === "undefined" || typeof value != 'string' || value === "undefined" || value === "null") { return undefined } 48 | return JSON.parse(value) 49 | } 50 | 51 | // Functions to encapsulate questionable FireFox 3.6.13 behavior 52 | // when about.config::dom.storage.enabled === false 53 | // See https://github.com/marcuswestin/store.js/issues#issue/13 54 | function isLocalStorageNameSupported() { 55 | try { return (localStorageName in win && win[localStorageName]) } 56 | catch(err) { return false } 57 | } 58 | 59 | function isGlobalStorageNameSupported() { 60 | try { return (globalStorageName in win && win[globalStorageName] && win[globalStorageName][win.location.hostname]) } 61 | catch(err) { return false } 62 | } 63 | 64 | if (isLocalStorageNameSupported()) { 65 | storage = win[localStorageName] 66 | api.set = function(key, val) { 67 | if (typeof val === "undefined" || val === null) 68 | api.remove(key) 69 | else 70 | storage.setItem(key, api.serialize(val)) 71 | } 72 | api.get = function(key) { return api.deserialize(storage.getItem(key)) } 73 | api.remove = function(key) { storage.removeItem(key) } 74 | api.clear = function() { storage.clear() } 75 | 76 | } else if (isGlobalStorageNameSupported()) { 77 | storage = win[globalStorageName][win.location.hostname] 78 | api.set = function(key, val) { storage[key] = api.serialize(val) } 79 | api.get = function(key) { return api.deserialize(storage[key] && storage[key].value) } 80 | api.remove = function(key) { delete storage[key] } 81 | api.clear = function() { for (var key in storage ) { delete storage[key] } } 82 | 83 | } else if (doc.documentElement.addBehavior) { 84 | var storage = doc.createElement('div') 85 | function withIEStorage(storeFunction) { 86 | return function() { 87 | var args = Array.prototype.slice.call(arguments, 0) 88 | args.unshift(storage) 89 | // See http://msdn.microsoft.com/en-us/library/ms531081(v=VS.85).aspx 90 | // and http://msdn.microsoft.com/en-us/library/ms531424(v=VS.85).aspx 91 | doc.body.appendChild(storage) 92 | storage.addBehavior('#default#userData') 93 | storage.load(localStorageName) 94 | var result = storeFunction.apply(api, args) 95 | doc.body.removeChild(storage) 96 | return result 97 | } 98 | } 99 | api.set = withIEStorage(function(storage, key, val) { 100 | storage.setAttribute(key, api.serialize(val)) 101 | storage.save(localStorageName) 102 | }) 103 | api.get = withIEStorage(function(storage, key) { 104 | return api.deserialize(storage.getAttribute(key)) 105 | }) 106 | api.remove = withIEStorage(function(storage, key) { 107 | storage.removeAttribute(key) 108 | storage.save(localStorageName) 109 | }) 110 | api.clear = withIEStorage(function(storage) { 111 | var attributes = storage.XMLDocument.documentElement.attributes 112 | storage.load(localStorageName) 113 | for (var i=0, attr; attr = attributes[i]; i++) { 114 | storage.removeAttribute(attr.name) 115 | } 116 | storage.save(localStorageName) 117 | }) 118 | } 119 | 120 | try { 121 | api.set(namespace, namespace) 122 | if (api.get(namespace) != namespace) { api.disabled = true } 123 | api.remove(namespace) 124 | } catch(e) { 125 | api.disabled = true 126 | } 127 | 128 | return api 129 | })(); 130 | 131 | if (typeof module != 'undefined') { module.exports = store } 132 | -------------------------------------------------------------------------------- /assets/css/bootstrap/tables.less: -------------------------------------------------------------------------------- 1 | /* 2 | * Tables.less 3 | * Tables for, you guessed it, tabular data 4 | * ---------------------------------------- */ 5 | 6 | 7 | // BASELINE STYLES 8 | // --------------- 9 | 10 | table { 11 | width: 100%; 12 | margin-bottom: @baseline; 13 | padding: 0; 14 | font-size: @basefont; 15 | border-collapse: collapse; 16 | th, 17 | td { 18 | padding: 10px 10px 9px; 19 | line-height: @baseline; 20 | text-align: left; 21 | } 22 | th { 23 | padding-top: 9px; 24 | font-weight: bold; 25 | vertical-align: middle; 26 | } 27 | td { 28 | vertical-align: top; 29 | border-top: 1px solid #ddd; 30 | } 31 | // When scoped to row, fix th in tbody 32 | tbody th { 33 | border-top: 1px solid #ddd; 34 | vertical-align: top; 35 | } 36 | } 37 | 38 | 39 | // CONDENSED VERSION 40 | // ----------------- 41 | .condensed-table { 42 | th, 43 | td { 44 | padding: 5px 5px 4px; 45 | } 46 | } 47 | 48 | 49 | // BORDERED VERSION 50 | // ---------------- 51 | 52 | .bordered-table { 53 | border: 1px solid #ddd; 54 | border-collapse: separate; // Done so we can round those corners! 55 | *border-collapse: collapse; /* IE7, collapse table to remove spacing */ 56 | .border-radius(4px); 57 | th + th, 58 | td + td, 59 | th + td { 60 | border-left: 1px solid #ddd; 61 | } 62 | thead tr:first-child th:first-child, 63 | tbody tr:first-child td:first-child { 64 | .border-radius(4px 0 0 0); 65 | } 66 | thead tr:first-child th:last-child, 67 | tbody tr:first-child td:last-child { 68 | .border-radius(0 4px 0 0); 69 | } 70 | tbody tr:last-child td:first-child { 71 | .border-radius(0 0 0 4px); 72 | } 73 | tbody tr:last-child td:last-child { 74 | .border-radius(0 0 4px 0); 75 | } 76 | } 77 | 78 | 79 | // TABLE CELL SIZES 80 | // ---------------- 81 | 82 | // This is a duplication of the main grid .columns() mixin, but subtracts 20px to account for input padding and border 83 | .tableColumns(@columnSpan: 1) { 84 | width: ((@gridColumnWidth - 20) * @columnSpan) + ((@gridColumnWidth - 20) * (@columnSpan - 1)); 85 | } 86 | table { 87 | // Default columns 88 | .span1 { .tableColumns(1); } 89 | .span2 { .tableColumns(2); } 90 | .span3 { .tableColumns(3); } 91 | .span4 { .tableColumns(4); } 92 | .span5 { .tableColumns(5); } 93 | .span6 { .tableColumns(6); } 94 | .span7 { .tableColumns(7); } 95 | .span8 { .tableColumns(8); } 96 | .span9 { .tableColumns(9); } 97 | .span10 { .tableColumns(10); } 98 | .span11 { .tableColumns(11); } 99 | .span12 { .tableColumns(12); } 100 | .span13 { .tableColumns(13); } 101 | .span14 { .tableColumns(14); } 102 | .span15 { .tableColumns(15); } 103 | .span16 { .tableColumns(16); } 104 | } 105 | 106 | 107 | // ZEBRA-STRIPING 108 | // -------------- 109 | 110 | // Default zebra-stripe styles (alternating gray and transparent backgrounds) 111 | .zebra-striped { 112 | tbody { 113 | tr:nth-child(odd) td, 114 | tr:nth-child(odd) th { 115 | background-color: #f9f9f9; 116 | } 117 | tr:hover td, 118 | tr:hover th { 119 | background-color: #f5f5f5; 120 | } 121 | } 122 | } 123 | 124 | table { 125 | // Tablesorting styles w/ jQuery plugin 126 | .header { 127 | cursor: pointer; 128 | &:after { 129 | content: ""; 130 | float: right; 131 | margin-top: 7px; 132 | border-width: 0 4px 4px; 133 | border-style: solid; 134 | border-color: #000 transparent; 135 | visibility: hidden; 136 | } 137 | } 138 | // Style the sorted column headers (THs) 139 | .headerSortUp, 140 | .headerSortDown { 141 | background-color: rgba(141,192,219,.25); 142 | text-shadow: 0 1px 1px rgba(255,255,255,.75); 143 | } 144 | // Style the ascending (reverse alphabetical) column header 145 | .header:hover { 146 | &:after { 147 | visibility:visible; 148 | } 149 | } 150 | // Style the descending (alphabetical) column header 151 | .headerSortDown, 152 | .headerSortDown:hover { 153 | &:after { 154 | visibility:visible; 155 | .opacity(60); 156 | } 157 | } 158 | // Style the ascending (reverse alphabetical) column header 159 | .headerSortUp { 160 | &:after { 161 | border-bottom: none; 162 | border-left: 4px solid transparent; 163 | border-right: 4px solid transparent; 164 | border-top: 4px solid #000; 165 | visibility:visible; 166 | .box-shadow(none); //can't add boxshadow to downward facing arrow :( 167 | .opacity(60); 168 | } 169 | } 170 | // Blue Table Headings 171 | .blue { 172 | color: @blue; 173 | border-bottom-color: @blue; 174 | } 175 | .headerSortUp.blue, 176 | .headerSortDown.blue { 177 | background-color: lighten(@blue, 40%); 178 | } 179 | // Green Table Headings 180 | .green { 181 | color: @green; 182 | border-bottom-color: @green; 183 | } 184 | .headerSortUp.green, 185 | .headerSortDown.green { 186 | background-color: lighten(@green, 40%); 187 | } 188 | // Red Table Headings 189 | .red { 190 | color: @red; 191 | border-bottom-color: @red; 192 | } 193 | .headerSortUp.red, 194 | .headerSortDown.red { 195 | background-color: lighten(@red, 50%); 196 | } 197 | // Yellow Table Headings 198 | .yellow { 199 | color: @yellow; 200 | border-bottom-color: @yellow; 201 | } 202 | .headerSortUp.yellow, 203 | .headerSortDown.yellow { 204 | background-color: lighten(@yellow, 40%); 205 | } 206 | // Orange Table Headings 207 | .orange { 208 | color: @orange; 209 | border-bottom-color: @orange; 210 | } 211 | .headerSortUp.orange, 212 | .headerSortDown.orange { 213 | background-color: lighten(@orange, 40%); 214 | } 215 | // Purple Table Headings 216 | .purple { 217 | color: @purple; 218 | border-bottom-color: @purple; 219 | } 220 | .headerSortUp.purple, 221 | .headerSortDown.purple { 222 | background-color: lighten(@purple, 40%); 223 | } 224 | } -------------------------------------------------------------------------------- /lib/appstore/api.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | #!/usr/bin/env python 4 | ''' Apple AppStore reviews scrapper 5 | version 2011-04-12 6 | Tomek "Grych" Gryszkiewicz, grych@tg.pl 7 | http://www.tg.pl 8 | 9 | based on "Scraping AppStore Reviews" blog by Erica Sadun 10 | - http://blogs.oreilly.com/iphone/2008/08/scraping-appstore-reviews.html 11 | AppStore codes are based on "appstore_reviews" by Jeremy Wohl 12 | - https://github.com/jeremywohl/iphone-scripts/blob/master/appstore_reviews 13 | ''' 14 | from google.appengine.api import urlfetch 15 | 16 | import lxml 17 | from lxml import etree as ElementTree 18 | from codecs import getdecoder, getencoder, getwriter, getreader 19 | 20 | import sys 21 | import string 22 | import argparse 23 | import re 24 | from datetime import datetime 25 | 26 | appStores = { 27 | 'Argentina': 143505, 28 | 'Australia': 143460, 29 | 'Belgium': 143446, 30 | 'Brazil': 143503, 31 | 'Canada': 143455, 32 | 'Chile': 143483, 33 | 'China': 143465, 34 | 'Colombia': 143501, 35 | 'Costa Rica': 143495, 36 | 'Croatia': 143494, 37 | 'Czech Republic': 143489, 38 | 'Denmark': 143458, 39 | 'Deutschland': 143443, 40 | 'El Salvador': 143506, 41 | 'Espana': 143454, 42 | 'Finland': 143447, 43 | 'France': 143442, 44 | 'Greece': 143448, 45 | 'Guatemala': 143504, 46 | 'Hong Kong': 143463, 47 | 'Hungary': 143482, 48 | 'India': 143467, 49 | 'Indonesia': 143476, 50 | 'Ireland': 143449, 51 | 'Israel': 143491, 52 | 'Italia': 143450, 53 | 'Korea': 143466, 54 | 'Kuwait': 143493, 55 | 'Lebanon': 143497, 56 | 'Luxembourg': 143451, 57 | 'Malaysia': 143473, 58 | 'Mexico': 143468, 59 | 'Nederland': 143452, 60 | 'New Zealand': 143461, 61 | 'Norway': 143457, 62 | 'Osterreich': 143445, 63 | 'Pakistan': 143477, 64 | 'Panama': 143485, 65 | 'Peru': 143507, 66 | 'Phillipines': 143474, 67 | 'Poland': 143478, 68 | 'Portugal': 143453, 69 | 'Qatar': 143498, 70 | 'Romania': 143487, 71 | 'Russia': 143469, 72 | 'Saudi Arabia': 143479, 73 | 'Schweiz/Suisse': 143459, 74 | 'Singapore': 143464, 75 | 'Slovakia': 143496, 76 | 'Slovenia': 143499, 77 | 'South Africa': 143472, 78 | 'Sri Lanka': 143486, 79 | 'Sweden': 143456, 80 | 'Taiwan': 143470, 81 | 'Thailand': 143475, 82 | 'Turkey': 143480, 83 | 'United Arab Emirates' :143481, 84 | 'United Kingdom': 143444, 85 | 'United States': 143441, 86 | 'Venezuela': 143502, 87 | 'Vietnam': 143471, 88 | 'Japan': 143462, 89 | 'Dominican Republic': 143508, 90 | 'Ecuador': 143509, 91 | 'Egypt': 143516, 92 | 'Estonia': 143518, 93 | 'Honduras': 143510, 94 | 'Jamaica': 143511, 95 | 'Kazakhstan': 143517, 96 | 'Latvia': 143519, 97 | 'Lithuania': 143520, 98 | 'Macau': 143515, 99 | 'Malta': 143521, 100 | 'Moldova': 143523, 101 | 'Nicaragua': 143512, 102 | 'Paraguay': 143513, 103 | 'Uruguay': 143514 104 | } 105 | 106 | 107 | def _unique(seq): 108 | seen = set() 109 | return [x for x in seq if x not in seen and not seen.add(x)] 110 | 111 | def getReviews(appStoreId, appId): 112 | ''' returns list of reviews for given AppStore ID and application Id 113 | return list format: [{"topic": unicode string, "review": unicode string, "rank": int}] 114 | ''' 115 | reviews=[] 116 | i=0 117 | while True: 118 | ret = _getReviewsForPage(appStoreId, appId, i) 119 | if len(ret)==0: # funny do while emulation ;) 120 | break 121 | reviews += ret 122 | i += 1 123 | 124 | if i > 1: 125 | break 126 | 127 | return reviews 128 | 129 | def _getReviewsForPage(appStoreId, appId, pageNo): 130 | userAgent = 'iTunes/9.2 (Macintosh; U; Mac OS X 10.6)' 131 | front = "%d-1" % appStoreId 132 | url = "http://ax.phobos.apple.com.edgesuite.net/WebObjects/MZStore.woa/wa/viewContentsUserReviews?id=%s&pageNumber=%d&sortOrdering=4&onlyLatestVersion=false&type=Purple+Software" % (appId, pageNo) 133 | 134 | req = urlfetch.fetch(url = url, 135 | headers={ 136 | "X-Apple-Store-Front": front, 137 | "User-Agent": userAgent 138 | }).content 139 | 140 | req = unicode(req, 'utf-8').encode('utf-8') 141 | 142 | root = lxml.etree.fromstring(req) 143 | 144 | 145 | #root = ElementTree.parse(req).getroot() 146 | reviews=[] 147 | iterator = root.findall('{http://www.apple.com/itms/}View/{http://www.apple.com/itms/}ScrollView/{http://www.apple.com/itms/}VBoxView/{http://www.apple.com/itms/}View/{http://www.apple.com/itms/}MatrixView/{http://www.apple.com/itms/}VBoxView/{http://www.apple.com/itms/}VBoxView/{http://www.apple.com/itms/}VBoxView') 148 | 149 | for node in iterator: 150 | 151 | review = {} 152 | 153 | review_node = node.find("{http://www.apple.com/itms/}TextView/{http://www.apple.com/itms/}SetFontStyle") 154 | 155 | if review_node is None: 156 | review["review"] = None 157 | else: 158 | review["review"] = review_node.text 159 | 160 | version_node = node.find("{http://www.apple.com/itms/}HBoxView/{http://www.apple.com/itms/}TextView/{http://www.apple.com/itms/}SetFontStyle/{http://www.apple.com/itms/}GotoURL") 161 | 162 | if version_node is None: 163 | review["version"] = None 164 | review["date"] = None 165 | else: 166 | match = re.search("Version ([^\n^\ ]+)\n\s+\n.*\n\s+(.*)", version_node.tail) 167 | review["version"] = match.group(1) 168 | review["date"] = match.group(2) 169 | review["date"] = datetime.strptime(review["date"], "%b %d, %Y") 170 | 171 | user_node = node.find("{http://www.apple.com/itms/}HBoxView/{http://www.apple.com/itms/}TextView/{http://www.apple.com/itms/}SetFontStyle/{http://www.apple.com/itms/}GotoURL/{http://www.apple.com/itms/}b") 172 | if user_node is None: 173 | review["user"] = None 174 | else: 175 | review["user"] = user_node.text.strip() 176 | 177 | rank_node = node.find("{http://www.apple.com/itms/}HBoxView/{http://www.apple.com/itms/}HBoxView/{http://www.apple.com/itms/}HBoxView") 178 | try: 179 | alt = rank_node.attrib['alt'] 180 | st = int(alt.strip(' stars')) 181 | review["rank"] = st 182 | except AttributeError: 183 | review["rank"] = None 184 | 185 | topic_node = node.find("{http://www.apple.com/itms/}HBoxView/{http://www.apple.com/itms/}TextView/{http://www.apple.com/itms/}SetFontStyle/{http://www.apple.com/itms/}b") 186 | if topic_node is None: 187 | review["title"] = None 188 | else: 189 | review["title"] = topic_node.text 190 | 191 | reviews.append(review) 192 | 193 | return reviews -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Customer feedback for Mobile Apps 5 | 6 | 7 | 8 | 130 | 131 | 133 | 134 | 135 | 136 | 137 |
138 |
139 |
140 |

Suckless Mobile Apps Support

141 | 142 |
143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 |
158 | 159 |
160 | 161 |
162 | 163 | 167 | 168 | 169 |
170 |

1Up-to-date Customer Reviews in one place

171 |

2Get only Helpfull Reviews

172 |

3Get notified when your Customers are Sad

173 |

4Integration with existing Helpdesk Solutions

174 |
175 |
176 |
177 |
178 | 179 |
180 | Launching early 2012 181 |
182 | 183 | 186 | 187 | Real Time Web Analytics 188 |
189 | 190 | 191 | 192 | 209 | 210 | 211 |
221 | 222 | 223 | 224 | 225 | -------------------------------------------------------------------------------- /assets/css/GGS.css: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Golden Grid System (1.01) 4 | * by Joni Korpi 5 | * licensed under MIT 6 | * 7 | */ 8 | /* 9 | * 10 | * Margin, padding, and border resets 11 | * except for form elements 12 | * 13 | */ 14 | body, 15 | div, 16 | h1, 17 | h2, 18 | h3, 19 | h4, 20 | h5, 21 | h6, 22 | p, 23 | blockquote, 24 | pre, 25 | dl, 26 | dt, 27 | dd, 28 | ol, 29 | ul, 30 | li, 31 | fieldset, 32 | form, 33 | label, 34 | legend, 35 | th, 36 | td, 37 | article, 38 | aside, 39 | figure, 40 | footer, 41 | header, 42 | hgroup, 43 | menu, 44 | nav, 45 | section { 46 | margin: 0; 47 | padding: 0; 48 | border: 0; 49 | } 50 | /* 51 | * 52 | * Consistency fixes 53 | * adopted from http://necolas.github.com/normalize.css/ 54 | * 55 | */ 56 | html { 57 | height: 100%; 58 | -webkit-text-size-adjust: 100%; 59 | -ms-text-size-adjust: 100%; 60 | } 61 | body { 62 | min-height: 100%; 63 | font-size: 100%; 64 | } 65 | article, 66 | aside, 67 | details, 68 | figcaption, 69 | figure, 70 | footer, 71 | header, 72 | hgroup, 73 | nav, 74 | section, 75 | audio, 76 | canvas, 77 | video { 78 | display: block; 79 | } 80 | sub, sup { 81 | font-size: 75%; 82 | line-height: 0; 83 | position: relative; 84 | vertical-align: baseline; 85 | } 86 | sup { 87 | top: -0.5em; 88 | } 89 | sub { 90 | bottom: -0.25em; 91 | } 92 | pre { 93 | white-space: pre; 94 | white-space: pre-wrap; 95 | word-wrap: break-word; 96 | } 97 | b, strong { 98 | font-weight: bold; 99 | } 100 | abbr[title] { 101 | border-bottom: 1px dotted; 102 | } 103 | input, 104 | textarea, 105 | button, 106 | select { 107 | margin: 0; 108 | font-size: 100%; 109 | line-height: normal; 110 | vertical-align: baseline; 111 | } 112 | button, 113 | html input[type="button"], 114 | input[type="reset"], 115 | input[type="submit"] { 116 | cursor: pointer; 117 | -webkit-appearance: button; 118 | } 119 | input[type="checkbox"], input[type="radio"] { 120 | -webkit-box-sizing: border-box; 121 | -moz-box-sizing: border-box; 122 | -o-box-sizing: border-box; 123 | -ms-box-sizing: border-box; 124 | box-sizing: border-box; 125 | } 126 | textarea { 127 | overflow: auto; 128 | } 129 | table { 130 | border-collapse: collapse; 131 | border-spacing: 0; 132 | } 133 | /* 134 | * 135 | * Simple fluid media 136 | * 137 | */ 138 | figure { 139 | position: relative; 140 | } 141 | figure img, 142 | figure object, 143 | figure embed, 144 | figure video { 145 | max-width: 100%; 146 | display: block; 147 | } 148 | img { 149 | border: 0; 150 | -ms-interpolation-mode: bicubic; 151 | } 152 | /* 153 | * 154 | * Zoomable baseline grid 155 | * type size presets 156 | * 157 | */ 158 | body { 159 | /* 16px / 24px */ 160 | 161 | font-size: 1em; 162 | line-height: 1.5em; 163 | } 164 | .small { 165 | /* 13px / 18px */ 166 | 167 | font-size: 0.8125em; 168 | line-height: 1.3846153846153845em; 169 | } 170 | .normal, h3 { 171 | /* 16px / 24px */ 172 | 173 | font-size: 1em; 174 | line-height: 1.5em; 175 | /* 24 */ 176 | 177 | } 178 | .large, h2, h1 { 179 | /* 26 / 36px */ 180 | 181 | font-size: 1.625em; 182 | line-height: 1.3846153846153845em; 183 | } 184 | .huge { 185 | /* 42px / 48px */ 186 | 187 | font-size: 2.625em; 188 | line-height: 1.1428571428571428em; 189 | } 190 | .massive { 191 | /* 68px / 72px */ 192 | 193 | font-size: 4.25em; 194 | line-height: 1.0588235294117647em; 195 | } 196 | .gigantic { 197 | /* 110px / 120px */ 198 | 199 | font-size: 6.875em; 200 | line-height: 1.0909090909090908em; 201 | } 202 | /* 203 | * 204 | * Four-column grid active 205 | * ---------------------------------------- 206 | * Margin | # 1 2 3 4 | Margin 207 | * 5.55555% | % 25 50 75 100 | 5.55555% 208 | * 209 | */ 210 | header, #twoway { 211 | margin: 0 5.555555555555555%; 212 | } 213 | h1, h2 { 214 | margin: 0.9230769230769231em 0 1.3846153846153845em; 215 | } 216 | h2 { 217 | font-weight: normal; 218 | } 219 | h3 { 220 | margin: 1.5em 0 1.5em; 221 | } 222 | /* 223 | * Simple elastic gutters 224 | * Note: box-sizing will not work in IE6-8 225 | */ 226 | .wrapper { 227 | padding: 0 0.75em; 228 | -webkit-box-sizing: border-box; 229 | -moz-box-sizing: border-box; 230 | -o-box-sizing: border-box; 231 | box-sizing: border-box; 232 | } 233 | /* 234 | * 235 | * Fixes for IE6-8 236 | * http://jonikorpi.com/leaving-old-IE-behind/ 237 | * 238 | */ 239 | .ie body { 240 | width: 40em; 241 | margin: 0 auto; 242 | font-size: 1.0625em; 243 | } 244 | .ie h1 { 245 | /* 42px / 48px */ 246 | 247 | font-size: 2.625em; 248 | line-height: 1.1428571428571428em; 249 | margin: 1.1428571428571428em 0 0.5714285714285714em; 250 | } 251 | /* @media screen and (min-width: 640px) */ 252 | @media screen and (min-width: 40em) { 253 | body { 254 | /* Zoom baseline grid to 17/16 = 1.0625 */ 255 | 256 | font-size: 1.0625em; 257 | } 258 | h1 { 259 | /* 42px / 48px */ 260 | 261 | font-size: 2.625em; 262 | line-height: 1.1428571428571428em; 263 | margin: 1.1428571428571428em 0 0.5714285714285714em; 264 | } 265 | } 266 | /* 267 | * 268 | * Eight-column grid active 269 | * ---------------------------------------------------------------------- 270 | * Margin | # 1 2 3 4 5 6 7 8 | Margin 271 | * 5.55555% | % 12.5 25.0 37.5 50.0 62.5 75.0 87.5 100 | 5.55555% 272 | * 273 | * 274 | */ 275 | /* @media screen and (min-width: 720px) */ 276 | @media screen and (min-width: 45em) { 277 | body { 278 | /* Reset baseline grid to 16/16 = 1 */ 279 | 280 | font-size: 1em; 281 | } 282 | #twoway .wrapper { 283 | float: left; 284 | width: 50%; 285 | } 286 | } 287 | /* @media screen and (min-width: 888px) */ 288 | @media screen and (min-width: 55.5em) { 289 | body { 290 | /* Zoom baseline grid to 17/16 = 1.0625 */ 291 | 292 | font-size: 1.0625em; 293 | } 294 | } 295 | /* @media screen and (min-width: 984px) */ 296 | @media screen and (min-width: 61.5em) { 297 | body { 298 | /* Reset baseline grid to 16/16 = 1.0 */ 299 | 300 | font-size: 1em; 301 | } 302 | header, #twoway { 303 | margin: 0 16.666666666666664%; 304 | } 305 | } 306 | /* @media screen and (min-width: 1200px) */ 307 | @media screen and (min-width: 75em) { 308 | body { 309 | /* Zoom baseline grid to 17/16 = 1.0625 */ 310 | 311 | font-size: 1.0625em; 312 | } 313 | } 314 | /* @media screen and (min-width: 1392px) */ 315 | @media screen and (min-width: 87em) { 316 | body { 317 | /* Reset baseline grid to 16/16 = 1.0 */ 318 | 319 | font-size: 1em; 320 | } 321 | header, #twoway { 322 | margin: 0 27.77777777777778%; 323 | } 324 | } 325 | /* @media screen and (min-width: 1680px) */ 326 | @media screen and (min-width: 105em) { 327 | body { 328 | /* Zoom baseline grid to 17/16 = 1.0625 */ 329 | 330 | font-size: 1.0625em; 331 | } 332 | } 333 | /* 334 | * 335 | * Sixteen-column grid active 336 | * ---------------------------------------------------------------------------------------------------------------------- 337 | * Margin | # 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | Margin 338 | * 5.55555% | % 6.25 12.5 18.75 25.0 31.25 37.5 43.75 50.0 56.25 62.5 68.75 75.0 81.25 87.5 93.75 100 | 5.55555% 339 | * 340 | * 341 | */ 342 | /* @media screen and (min-width: 1872px) */ 343 | @media screen and (min-width: 117em) { 344 | header, #twoway { 345 | margin: 0 5.555555555555555%; 346 | } 347 | header .wrapper { 348 | width: 37.5%; 349 | margin-left: 25%; 350 | } 351 | #twoway .wrapper { 352 | width: 25%; 353 | } 354 | #twoway .wrapper:first-child { 355 | margin-left: 25%; 356 | } 357 | } 358 | /* @media screen and (min-width: 2080px) */ 359 | @media screen and (min-width: 130em) { 360 | body { 361 | /* Zoom baseline grid to 18/16 = 1.125 */ 362 | 363 | font-size: 1.125em; 364 | max-width: 160em; 365 | } 366 | } 367 | -------------------------------------------------------------------------------- /assets/css/GGS.less: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Golden Grid System (1.01) 4 | * by Joni Korpi 5 | * licensed under MIT 6 | * 7 | */ 8 | 9 | 10 | // 11 | // Important numbers 12 | // 13 | 14 | @line: 24; 15 | @column: 100% / 18; 16 | @font-size: 16; 17 | @em:@font-size*1em; 18 | 19 | 20 | /* 21 | * 22 | * Margin, padding, and border resets 23 | * except for form elements 24 | * 25 | */ 26 | 27 | body, 28 | div, 29 | h1, 30 | h2, 31 | h3, 32 | h4, 33 | h5, 34 | h6, 35 | p, 36 | blockquote, 37 | pre, 38 | dl, 39 | dt, 40 | dd, 41 | ol, 42 | ul, 43 | li, 44 | fieldset, 45 | form, 46 | label, 47 | legend, 48 | th, 49 | td, 50 | article, 51 | aside, 52 | figure, 53 | footer, 54 | header, 55 | hgroup, 56 | menu, 57 | nav, 58 | section { 59 | margin: 0; 60 | padding: 0; 61 | border: 0; 62 | } 63 | 64 | 65 | /* 66 | * 67 | * Consistency fixes 68 | * adopted from http://necolas.github.com/normalize.css/ 69 | * 70 | */ 71 | 72 | html { 73 | height: 100%; 74 | -webkit-text-size-adjust: 100%; 75 | -ms-text-size-adjust: 100%; 76 | } 77 | body { 78 | min-height: 100%; 79 | font-size: 100%; 80 | } 81 | article, aside, details, figcaption, figure, 82 | footer, header, hgroup, nav, section, audio, canvas, video { 83 | display: block; 84 | } 85 | sub, sup { 86 | font-size: 75%; 87 | line-height: 0; 88 | position: relative; 89 | vertical-align: baseline; 90 | } 91 | sup { 92 | top: -0.5em; 93 | } 94 | sub { 95 | bottom: -0.25em; 96 | } 97 | pre { 98 | white-space: pre; 99 | white-space: pre-wrap; 100 | word-wrap: break-word; 101 | } 102 | b, strong { 103 | font-weight: bold; 104 | } 105 | abbr[title] { 106 | border-bottom: 1px dotted; 107 | } 108 | input, textarea, button, select { 109 | margin: 0; 110 | font-size: 100%; 111 | line-height: normal; 112 | vertical-align: baseline; 113 | } 114 | button, 115 | html input[type="button"], 116 | input[type="reset"], 117 | input[type="submit"] { 118 | cursor: pointer; 119 | -webkit-appearance: button; 120 | } 121 | input[type="checkbox"], 122 | input[type="radio"] { 123 | -webkit-box-sizing: border-box; 124 | -moz-box-sizing: border-box; 125 | -o-box-sizing: border-box; 126 | -ms-box-sizing: border-box; 127 | box-sizing: border-box; 128 | } 129 | textarea { 130 | overflow: auto; 131 | } 132 | table { 133 | border-collapse: collapse; 134 | border-spacing: 0; 135 | } 136 | 137 | 138 | /* 139 | * 140 | * Simple fluid media 141 | * 142 | */ 143 | 144 | figure { 145 | position: relative; 146 | } 147 | figure img, figure object, figure embed, figure video { 148 | max-width: 100%; 149 | display: block; 150 | } 151 | img { 152 | border: 0; 153 | -ms-interpolation-mode: bicubic; 154 | } 155 | 156 | 157 | /* 158 | * 159 | * Zoomable baseline grid 160 | * type size presets 161 | * 162 | */ 163 | 164 | body { 165 | /* 16px / 24px */ 166 | font-size: @font-size / 16 * 1em; 167 | line-height: @line / @em; 168 | } 169 | 170 | .small { 171 | /* 13px / 18px */ 172 | font-size: (@font-size*0.8125) / @em; 173 | line-height: (@line*0.75) / (@font-size*0.8125) * 1em; 174 | } 175 | 176 | .normal, h3 { 177 | /* 16px / 24px */ 178 | font-size: @font-size / @em; 179 | line-height: @line / @em; /* 24 */ 180 | } 181 | 182 | .large, h2, h1 { 183 | /* 26 / 36px */ 184 | font-size: 26 / @em; 185 | line-height: (@line*1.5) / 26 * 1em; 186 | } 187 | 188 | .huge { 189 | /* 42px / 48px */ 190 | font-size: 42 / @em; 191 | line-height: (@line*2) / 42 * 1em; 192 | } 193 | 194 | .massive { 195 | /* 68px / 72px */ 196 | font-size: 68 / @em; 197 | line-height: (@line*3) / 68 * 1em; 198 | } 199 | 200 | .gigantic { 201 | /* 110px / 120px */ 202 | font-size: 110 / @em; 203 | line-height: (@line*5) / 110 * 1em; 204 | } 205 | 206 | 207 | /* 208 | * 209 | * Four-column grid active 210 | * ---------------------------------------- 211 | * Margin | # 1 2 3 4 | Margin 212 | * 5.55555% | % 25 50 75 100 | 5.55555% 213 | * 214 | */ 215 | 216 | header, #twoway { 217 | margin: 0 @column; 218 | } 219 | 220 | h1, h2 { 221 | margin: (24/26*1em) 0 (36/26*1em); 222 | } 223 | 224 | h2 { 225 | font-weight: normal; 226 | } 227 | 228 | h3 { 229 | margin: (24/@em) 0 (24/@em); 230 | } 231 | 232 | /* 233 | * Simple elastic gutters 234 | * Note: box-sizing will not work in IE6-8 235 | */ 236 | 237 | .wrapper { 238 | padding: 0 (@line/2)/@em; 239 | -webkit-box-sizing: border-box; 240 | -moz-box-sizing: border-box; 241 | -o-box-sizing: border-box; 242 | box-sizing: border-box; 243 | } 244 | 245 | 246 | /* 247 | * 248 | * Fixes for IE6-8 249 | * http://jonikorpi.com/leaving-old-IE-behind/ 250 | * 251 | */ 252 | 253 | .ie body { 254 | width: 640/@em; 255 | margin: 0 auto; 256 | font-size: (@font-size + 1) / @em; 257 | } 258 | 259 | .ie h1 { 260 | .huge(); 261 | margin: (48/42*1em) 0 (24/42*1em); 262 | } 263 | 264 | /* @media screen and (min-width: 640px) */ 265 | @media screen and (min-width: 40em) { 266 | 267 | body { 268 | /* Zoom baseline grid to 17/16 = 1.0625 */ 269 | font-size: (@font-size + 1) / @em; 270 | } 271 | 272 | h1 { 273 | .huge(); 274 | margin: (48/42*1em) 0 (24/42*1em); 275 | } 276 | 277 | } 278 | 279 | 280 | /* 281 | * 282 | * Eight-column grid active 283 | * ---------------------------------------------------------------------- 284 | * Margin | # 1 2 3 4 5 6 7 8 | Margin 285 | * 5.55555% | % 12.5 25.0 37.5 50.0 62.5 75.0 87.5 100 | 5.55555% 286 | * 287 | * 288 | */ 289 | 290 | /* @media screen and (min-width: 720px) */ 291 | @media screen and (min-width: 45em) { 292 | 293 | body { 294 | /* Reset baseline grid to 16/16 = 1 */ 295 | font-size: (@font-size) / @em; 296 | } 297 | 298 | #twoway .wrapper { 299 | float: left; 300 | width: 50%; 301 | } 302 | 303 | } 304 | 305 | 306 | /* @media screen and (min-width: 888px) */ 307 | @media screen and (min-width: 55.5em) { 308 | 309 | body { 310 | /* Zoom baseline grid to 17/16 = 1.0625 */ 311 | font-size: (@font-size + 1) / @em; 312 | } 313 | } 314 | 315 | 316 | /* @media screen and (min-width: 984px) */ 317 | @media screen and (min-width: 61.5em) { 318 | 319 | body { 320 | /* Reset baseline grid to 16/16 = 1.0 */ 321 | font-size: (@font-size) / @em; 322 | } 323 | 324 | header, #twoway { 325 | margin: 0 @column*3; 326 | } 327 | 328 | } 329 | 330 | 331 | /* @media screen and (min-width: 1200px) */ 332 | @media screen and (min-width: 75em) { 333 | 334 | body { 335 | /* Zoom baseline grid to 17/16 = 1.0625 */ 336 | font-size: (@font-size + 1) / @em; 337 | } 338 | 339 | } 340 | 341 | 342 | /* @media screen and (min-width: 1392px) */ 343 | @media screen and (min-width: 87em) { 344 | 345 | body { 346 | /* Reset baseline grid to 16/16 = 1.0 */ 347 | font-size: (@font-size) / @em; 348 | } 349 | 350 | header, #twoway { 351 | margin: 0 @column*5; 352 | } 353 | 354 | } 355 | 356 | 357 | /* @media screen and (min-width: 1680px) */ 358 | @media screen and (min-width: 105em) { 359 | 360 | body { 361 | /* Zoom baseline grid to 17/16 = 1.0625 */ 362 | font-size: (@font-size + 1) / @em; 363 | } 364 | 365 | } 366 | 367 | 368 | /* 369 | * 370 | * Sixteen-column grid active 371 | * ---------------------------------------------------------------------------------------------------------------------- 372 | * Margin | # 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | Margin 373 | * 5.55555% | % 6.25 12.5 18.75 25.0 31.25 37.5 43.75 50.0 56.25 62.5 68.75 75.0 81.25 87.5 93.75 100 | 5.55555% 374 | * 375 | * 376 | */ 377 | 378 | /* @media screen and (min-width: 1872px) */ 379 | @media screen and (min-width: 117em) { 380 | 381 | header, #twoway { 382 | margin: 0 @column*1; 383 | } 384 | 385 | header .wrapper { 386 | width: 37.5%; 387 | margin-left: 25%; 388 | } 389 | 390 | #twoway .wrapper { 391 | width: 25%; 392 | } 393 | 394 | #twoway .wrapper:first-child { 395 | margin-left: 25%; 396 | } 397 | 398 | } 399 | 400 | 401 | /* @media screen and (min-width: 2080px) */ 402 | @media screen and (min-width: 130em) { 403 | 404 | body { 405 | /* Zoom baseline grid to 18/16 = 1.125 */ 406 | font-size: (@font-size + 2) / @em; 407 | max-width: 2560/@em; 408 | } 409 | 410 | } -------------------------------------------------------------------------------- /assets/javascript/app.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var AddProjectForm, AppRouter, Interaction, InteractionsCollection, InteractionsList, Project, ProjectsCollection, ProjectsList, Section, Sidebar; 3 | var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; }; 4 | 5 | this.App = { 6 | views: {} 7 | }; 8 | 9 | Interaction = (function() { 10 | 11 | __extends(Interaction, Backbone.Model); 12 | 13 | function Interaction() { 14 | Interaction.__super__.constructor.apply(this, arguments); 15 | } 16 | 17 | return Interaction; 18 | 19 | })(); 20 | 21 | InteractionsCollection = (function() { 22 | 23 | __extends(InteractionsCollection, Backbone.Collection); 24 | 25 | function InteractionsCollection() { 26 | InteractionsCollection.__super__.constructor.apply(this, arguments); 27 | } 28 | 29 | InteractionsCollection.prototype.model = Interaction; 30 | 31 | return InteractionsCollection; 32 | 33 | })(); 34 | 35 | Project = (function() { 36 | 37 | __extends(Project, Backbone.Model); 38 | 39 | function Project() { 40 | Project.__super__.constructor.apply(this, arguments); 41 | } 42 | 43 | Project.prototype.initialize = function() { 44 | this.interactions = new InteractionsCollection(); 45 | return this.interactions.url = "/api/projects/" + this.id; 46 | }; 47 | 48 | return Project; 49 | 50 | })(); 51 | 52 | ProjectsCollection = (function() { 53 | 54 | __extends(ProjectsCollection, Backbone.Collection); 55 | 56 | function ProjectsCollection() { 57 | ProjectsCollection.__super__.constructor.apply(this, arguments); 58 | } 59 | 60 | ProjectsCollection.prototype.model = Project; 61 | 62 | ProjectsCollection.prototype.url = "/api/projects"; 63 | 64 | return ProjectsCollection; 65 | 66 | })(); 67 | 68 | App.projects = new ProjectsCollection(); 69 | 70 | App.projects.reset(projects); 71 | 72 | Section = (function() { 73 | 74 | __extends(Section, Backbone.View); 75 | 76 | function Section() { 77 | Section.__super__.constructor.apply(this, arguments); 78 | } 79 | 80 | Section.prototype.render = function() { 81 | $('#content .active').removeClass(); 82 | this.el.addClass('active'); 83 | return this.el; 84 | }; 85 | 86 | return Section; 87 | 88 | })(); 89 | 90 | ProjectsList = (function() { 91 | 92 | __extends(ProjectsList, Section); 93 | 94 | function ProjectsList() { 95 | ProjectsList.__super__.constructor.apply(this, arguments); 96 | } 97 | 98 | ProjectsList.prototype.template = Handlebars.compile($("#projects_tmpl").html()); 99 | 100 | ProjectsList.prototype.el = $('#projects_list'); 101 | 102 | ProjectsList.prototype.events = { 103 | "click .delete": 'deleteProject' 104 | }; 105 | 106 | ProjectsList.prototype.render = function() { 107 | Section.prototype.render.call(this); 108 | return this.el.html(this.template({ 109 | 'projects': App.projects.toJSON() 110 | })); 111 | }; 112 | 113 | ProjectsList.prototype.deleteProject = function(evt) { 114 | var pid, project; 115 | var _this = this; 116 | if (confirm('Delete project?')) { 117 | pid = $.attr(evt.currentTarget, 'data-project'); 118 | project = App.projects.get(pid); 119 | return project.destroy({ 120 | success: function() { 121 | return _this.render(); 122 | }, 123 | error: function() { 124 | return console.error("Can't delete project"); 125 | } 126 | }); 127 | } 128 | }; 129 | 130 | return ProjectsList; 131 | 132 | })(); 133 | 134 | App.views.projects_list = new ProjectsList(); 135 | 136 | AddProjectForm = (function() { 137 | 138 | __extends(AddProjectForm, Section); 139 | 140 | function AddProjectForm() { 141 | AddProjectForm.__super__.constructor.apply(this, arguments); 142 | } 143 | 144 | AddProjectForm.prototype.el = $('#add_project'); 145 | 146 | AddProjectForm.prototype.events = { 147 | "submit form": 'submit' 148 | }; 149 | 150 | AddProjectForm.prototype.render = function() { 151 | Section.prototype.render.call(this); 152 | return this.$('form').trigger('reset'); 153 | }; 154 | 155 | AddProjectForm.prototype.submit = function(evt) { 156 | var field, fields, project, _i, _len; 157 | fields = this.$('form').serializeArray(); 158 | project = {}; 159 | for (_i = 0, _len = fields.length; _i < _len; _i++) { 160 | field = fields[_i]; 161 | project[field.name] = field.value; 162 | } 163 | return App.projects.create(project, { 164 | success: function() { 165 | return App.router.navigate('', true); 166 | }, 167 | error: function() { 168 | return console.error(':('); 169 | } 170 | }); 171 | }; 172 | 173 | return AddProjectForm; 174 | 175 | })(); 176 | 177 | App.views.add_project_form = new AddProjectForm(); 178 | 179 | InteractionsList = (function() { 180 | 181 | __extends(InteractionsList, Section); 182 | 183 | function InteractionsList() { 184 | InteractionsList.__super__.constructor.apply(this, arguments); 185 | } 186 | 187 | InteractionsList.prototype.template = Handlebars.compile($("#interactions_tmpl").html()); 188 | 189 | InteractionsList.prototype.el = $('#interactions'); 190 | 191 | InteractionsList.prototype.render = function(project_id) { 192 | var project; 193 | var _this = this; 194 | Section.prototype.render.call(this); 195 | project = App.projects.get(project_id); 196 | this.el.html(this.template({ 197 | 'interactions': project.interactions.toJSON(), 198 | 'project': project.toJSON() 199 | })); 200 | return project.interactions.fetch({ 201 | success: function() { 202 | return _this.el.html(_this.template({ 203 | 'interactions': project.interactions.toJSON(), 204 | 'project': project.toJSON() 205 | })); 206 | } 207 | }); 208 | }; 209 | 210 | return InteractionsList; 211 | 212 | })(); 213 | 214 | App.views.interactions_list = new InteractionsList(); 215 | 216 | Sidebar = (function() { 217 | 218 | __extends(Sidebar, Backbone.View); 219 | 220 | function Sidebar() { 221 | Sidebar.__super__.constructor.apply(this, arguments); 222 | } 223 | 224 | Sidebar.prototype.template = Handlebars.compile($("#sidebar_tmpl").html()); 225 | 226 | Sidebar.prototype.el = $('#sidebar'); 227 | 228 | Sidebar.prototype.initialize = function() { 229 | return this.render(); 230 | }; 231 | 232 | Sidebar.prototype.render = function(project_id) { 233 | var projects; 234 | projects = App.projects.toJSON(); 235 | projects = _.map(projects, function(p) { 236 | p.active = project_id === p.id; 237 | return p; 238 | }); 239 | return this.el.html(this.template({ 240 | 'projects': projects, 241 | 'active_project': project_id 242 | })); 243 | }; 244 | 245 | return Sidebar; 246 | 247 | })(); 248 | 249 | App.views.sidebar = new Sidebar(); 250 | 251 | AppRouter = (function() { 252 | 253 | __extends(AppRouter, Backbone.Router); 254 | 255 | function AppRouter() { 256 | AppRouter.__super__.constructor.apply(this, arguments); 257 | } 258 | 259 | AppRouter.prototype.routes = { 260 | ".*": "home", 261 | "add": "addProject", 262 | "interactions/:pid": "showInteractions" 263 | }; 264 | 265 | AppRouter.prototype.home = function() { 266 | App.views.projects_list.render(); 267 | return App.views.sidebar.render(); 268 | }; 269 | 270 | AppRouter.prototype.addProject = function() { 271 | return App.views.add_project_form.render(); 272 | }; 273 | 274 | AppRouter.prototype.showInteractions = function(project_id) { 275 | App.views.interactions_list.render(project_id); 276 | return App.views.sidebar.render(project_id); 277 | }; 278 | 279 | return AppRouter; 280 | 281 | })(); 282 | 283 | App.router = new AppRouter(); 284 | 285 | $('a').live('click', function(evt) { 286 | var href; 287 | href = $.attr(evt.currentTarget, "href"); 288 | if ((href != null ? href.charAt(0) : void 0) === '/') { 289 | href = href.substr(1); 290 | App.router.navigate(href, true); 291 | return false; 292 | } 293 | }); 294 | 295 | _.defer(function() { 296 | return Backbone.history.start({ 297 | pushState: true 298 | }); 299 | }); 300 | 301 | }).call(this); 302 | -------------------------------------------------------------------------------- /assets/css/bootstrap/mixins.less: -------------------------------------------------------------------------------- 1 | /* Mixins.less 2 | * Snippets of reusable CSS to develop faster and keep code readable 3 | * ----------------------------------------------------------------- */ 4 | 5 | 6 | // Clearfix for clearing floats like a boss h5bp.com/q 7 | .clearfix() { 8 | zoom: 1; 9 | &:before, 10 | &:after { 11 | display: table; 12 | content: ""; 13 | zoom: 1; 14 | } 15 | &:after { 16 | clear: both; 17 | } 18 | } 19 | 20 | // Center-align a block level element 21 | .center-block() { 22 | display: block; 23 | margin-left: auto; 24 | margin-right: auto; 25 | } 26 | 27 | // Sizing shortcuts 28 | .size(@height: 5px, @width: 5px) { 29 | height: @height; 30 | width: @width; 31 | } 32 | .square(@size: 5px) { 33 | .size(@size, @size); 34 | } 35 | 36 | // Input placeholder text 37 | .placeholder(@color: @grayLight) { 38 | :-moz-placeholder { 39 | color: @color; 40 | } 41 | ::-webkit-input-placeholder { 42 | color: @color; 43 | } 44 | } 45 | 46 | // Font Stacks 47 | #font { 48 | .shorthand(@weight: normal, @size: 14px, @lineHeight: 20px) { 49 | font-size: @size; 50 | font-weight: @weight; 51 | line-height: @lineHeight; 52 | } 53 | .sans-serif(@weight: normal, @size: 14px, @lineHeight: 20px) { 54 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 55 | font-size: @size; 56 | font-weight: @weight; 57 | line-height: @lineHeight; 58 | } 59 | .serif(@weight: normal, @size: 14px, @lineHeight: 20px) { 60 | font-family: "Georgia", Times New Roman, Times, serif; 61 | font-size: @size; 62 | font-weight: @weight; 63 | line-height: @lineHeight; 64 | } 65 | .monospace(@weight: normal, @size: 12px, @lineHeight: 20px) { 66 | font-family: "Monaco", Courier New, monospace; 67 | font-size: @size; 68 | font-weight: @weight; 69 | line-height: @lineHeight; 70 | } 71 | } 72 | 73 | // Grid System 74 | .fixed-container() { 75 | width: @siteWidth; 76 | margin-left: auto; 77 | margin-right: auto; 78 | .clearfix(); 79 | } 80 | .columns(@columnSpan: 1) { 81 | width: (@gridColumnWidth * @columnSpan) + (@gridGutterWidth * (@columnSpan - 1)); 82 | } 83 | .offset(@columnOffset: 1) { 84 | margin-left: (@gridColumnWidth * @columnOffset) + (@gridGutterWidth * (@columnOffset - 1)) + @extraSpace; 85 | } 86 | // Necessary grid styles for every column to make them appear next to each other horizontally 87 | .gridColumn() { 88 | display: inline; 89 | float: left; 90 | margin-left: @gridGutterWidth; 91 | } 92 | // makeColumn can be used to mark any element (e.g., .content-primary) as a column without changing markup to .span something 93 | .makeColumn(@columnSpan: 1) { 94 | .gridColumn(); 95 | .columns(@columnSpan); 96 | } 97 | 98 | // Border Radius 99 | .border-radius(@radius: 5px) { 100 | -webkit-border-radius: @radius; 101 | -moz-border-radius: @radius; 102 | border-radius: @radius; 103 | } 104 | 105 | // Drop shadows 106 | .box-shadow(@shadow: 0 1px 3px rgba(0,0,0,.25)) { 107 | -webkit-box-shadow: @shadow; 108 | -moz-box-shadow: @shadow; 109 | box-shadow: @shadow; 110 | } 111 | 112 | // Transitions 113 | .transition(@transition) { 114 | -webkit-transition: @transition; 115 | -moz-transition: @transition; 116 | -ms-transition: @transition; 117 | -o-transition: @transition; 118 | transition: @transition; 119 | } 120 | 121 | // Background clipping 122 | .background-clip(@clip) { 123 | -webkit-background-clip: @clip; 124 | -moz-background-clip: @clip; 125 | background-clip: @clip; 126 | } 127 | 128 | // CSS3 Content Columns 129 | .content-columns(@columnCount, @columnGap: 20px) { 130 | -webkit-column-count: @columnCount; 131 | -moz-column-count: @columnCount; 132 | column-count: @columnCount; 133 | -webkit-column-gap: @columnGap; 134 | -moz-column-gap: @columnGap; 135 | column-gap: @columnGap; 136 | } 137 | 138 | // Make any element resizable for prototyping 139 | .resizable(@direction: both) { 140 | resize: @direction; // Options are horizontal, vertical, both 141 | overflow: auto; // Safari fix 142 | } 143 | 144 | // Add an alphatransparency value to any background or border color (via Elyse Holladay) 145 | #translucent { 146 | .background(@color: @white, @alpha: 1) { 147 | background-color: hsla(hue(@color), saturation(@color), lightness(@color), @alpha); 148 | } 149 | .border(@color: @white, @alpha: 1) { 150 | border-color: hsla(hue(@color), saturation(@color), lightness(@color), @alpha); 151 | background-clip: padding-box; 152 | } 153 | } 154 | 155 | // Gradient Bar Colors for buttons and allerts 156 | .gradientBar(@primaryColor, @secondaryColor) { 157 | #gradient > .vertical(@primaryColor, @secondaryColor); 158 | text-shadow: 0 -1px 0 rgba(0,0,0,.25); 159 | border-color: @secondaryColor @secondaryColor darken(@secondaryColor, 15%); 160 | border-color: rgba(0,0,0,.1) rgba(0,0,0,.1) fadein(rgba(0,0,0,.1), 15%); 161 | } 162 | 163 | // Gradients 164 | #gradient { 165 | .horizontal (@startColor: #555, @endColor: #333) { 166 | background-color: @endColor; 167 | background-repeat: repeat-x; 168 | background-image: -khtml-gradient(linear, left top, right top, from(@startColor), to(@endColor)); // Konqueror 169 | background-image: -moz-linear-gradient(left, @startColor, @endColor); // FF 3.6+ 170 | background-image: -ms-linear-gradient(left, @startColor, @endColor); // IE10 171 | background-image: -webkit-gradient(linear, left top, right top, color-stop(0%, @startColor), color-stop(100%, @endColor)); // Safari 4+, Chrome 2+ 172 | background-image: -webkit-linear-gradient(left, @startColor, @endColor); // Safari 5.1+, Chrome 10+ 173 | background-image: -o-linear-gradient(left, @startColor, @endColor); // Opera 11.10 174 | background-image: linear-gradient(left, @startColor, @endColor); // Le standard 175 | filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)",@startColor,@endColor)); // IE9 and down 176 | } 177 | .vertical (@startColor: #555, @endColor: #333) { 178 | background-color: @endColor; 179 | background-repeat: repeat-x; 180 | background-image: -khtml-gradient(linear, left top, left bottom, from(@startColor), to(@endColor)); // Konqueror 181 | background-image: -moz-linear-gradient(top, @startColor, @endColor); // FF 3.6+ 182 | background-image: -ms-linear-gradient(top, @startColor, @endColor); // IE10 183 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, @startColor), color-stop(100%, @endColor)); // Safari 4+, Chrome 2+ 184 | background-image: -webkit-linear-gradient(top, @startColor, @endColor); // Safari 5.1+, Chrome 10+ 185 | background-image: -o-linear-gradient(top, @startColor, @endColor); // Opera 11.10 186 | background-image: linear-gradient(top, @startColor, @endColor); // The standard 187 | filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",@startColor,@endColor)); // IE9 and down 188 | } 189 | .directional (@startColor: #555, @endColor: #333, @deg: 45deg) { 190 | background-color: @endColor; 191 | background-repeat: repeat-x; 192 | background-image: -moz-linear-gradient(@deg, @startColor, @endColor); // FF 3.6+ 193 | background-image: -ms-linear-gradient(@deg, @startColor, @endColor); // IE10 194 | background-image: -webkit-linear-gradient(@deg, @startColor, @endColor); // Safari 5.1+, Chrome 10+ 195 | background-image: -o-linear-gradient(@deg, @startColor, @endColor); // Opera 11.10 196 | background-image: linear-gradient(@deg, @startColor, @endColor); // The standard 197 | } 198 | .vertical-three-colors(@startColor: #00b3ee, @midColor: #7a43b6, @colorStop: 50%, @endColor: #c3325f) { 199 | background-color: @endColor; 200 | background-repeat: no-repeat; 201 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(@startColor), color-stop(@colorStop, @midColor), to(@endColor)); 202 | background-image: -webkit-linear-gradient(@startColor, @midColor @colorStop, @endColor); 203 | background-image: -moz-linear-gradient(top, @startColor, @midColor @colorStop, @endColor); 204 | background-image: -ms-linear-gradient(@startColor, @midColor @colorStop, @endColor); 205 | background-image: -o-linear-gradient(@startColor, @midColor @colorStop, @endColor); 206 | background-image: linear-gradient(@startColor, @midColor @colorStop, @endColor); 207 | filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",@startColor,@endColor)); // IE9 and down, gets no color-stop at all for proper fallback 208 | } 209 | } 210 | 211 | // Reset filters for IE 212 | .reset-filter() { 213 | filter: e(%("progid:DXImageTransform.Microsoft.gradient(enabled = false)")); 214 | } 215 | 216 | // Opacity 217 | .opacity(@opacity: 100) { 218 | filter: e(%("alpha(opacity=%d)", @opacity)); 219 | -khtml-opacity: @opacity / 100; 220 | -moz-opacity: @opacity / 100; 221 | opacity: @opacity / 100; 222 | } -------------------------------------------------------------------------------- /assets/javascript/lib/underscore-min.js: -------------------------------------------------------------------------------- 1 | // Underscore.js 1.2.0 2 | // (c) 2011 Jeremy Ashkenas, DocumentCloud Inc. 3 | // Underscore is freely distributable under the MIT license. 4 | // Portions of Underscore are inspired or borrowed from Prototype, 5 | // Oliver Steele's Functional, and John Resig's Micro-Templating. 6 | // For all details and documentation: 7 | // http://documentcloud.github.com/underscore 8 | (function(){function q(a,c,d){if(a===c)return a!==0||1/a==1/c;if(a==null)return a===c;var e=typeof a;if(e!=typeof c)return false;if(!a!=!c)return false;if(b.isNaN(a))return b.isNaN(c);var f=b.isString(a),g=b.isString(c);if(f||g)return f&&g&&String(a)==String(c);f=b.isNumber(a);g=b.isNumber(c);if(f||g)return f&&g&&+a==+c;f=b.isBoolean(a);g=b.isBoolean(c);if(f||g)return f&&g&&+a==+c;f=b.isDate(a);g=b.isDate(c);if(f||g)return f&&g&&a.getTime()==c.getTime();f=b.isRegExp(a);g=b.isRegExp(c);if(f||g)return f&& 9 | g&&a.source==c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase;if(e!="object")return false;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(b.isFunction(a.isEqual))return a.isEqual(c);for(e=d.length;e--;)if(d[e]==a)return true;d.push(a);e=0;f=true;if(a.length===+a.length||c.length===+c.length){if(e=a.length,f=e==c.length)for(;e--;)if(!(f=e in a==e in c&&q(a[e],c[e],d)))break}else{for(var h in a)if(l.call(a,h)&&(e++,!(f=l.call(c,h)&&q(a[h],c[h],d))))break;if(f){for(h in c)if(l.call(c, 10 | h)&&!e--)break;f=!e}}d.pop();return f}var r=this,F=r._,n={},k=Array.prototype,o=Object.prototype,i=k.slice,G=k.unshift,u=o.toString,l=o.hasOwnProperty,v=k.forEach,w=k.map,x=k.reduce,y=k.reduceRight,z=k.filter,A=k.every,B=k.some,p=k.indexOf,C=k.lastIndexOf,o=Array.isArray,H=Object.keys,s=Function.prototype.bind,b=function(a){return new m(a)};typeof module!=="undefined"&&module.exports?(module.exports=b,b._=b):r._=b;b.VERSION="1.2.0";var j=b.each=b.forEach=function(a,c,b){if(a!=null)if(v&&a.forEach=== 11 | v)a.forEach(c,b);else if(a.length===+a.length)for(var e=0,f=a.length;e=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;bd?1:0}),"value")};b.groupBy=function(a,b){var d={};j(a,function(a,f){var g=b(a,f);(d[g]||(d[g]=[])).push(a)});return d};b.sortedIndex=function(a,c,d){d||(d=b.identity);for(var e=0,f=a.length;e>1;d(a[g])=0})})}; 19 | b.difference=function(a,c){return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e=0;d--)b=[a[d].apply(this,b)];return b[0]}};b.after=function(a,b){return function(){if(--a<1)return b.apply(this, 23 | arguments)}};b.keys=H||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var b=[],d;for(d in a)l.call(a,d)&&(b[b.length]=d);return b};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)b[d]!==void 0&&(a[d]=b[d])});return a};b.defaults=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]==null&&(a[d]= 24 | b[d])});return a};b.clone=function(a){return b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return q(a,b,[])};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(l.call(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=o||function(a){return u.call(a)==="[object Array]"};b.isObject=function(a){return a===Object(a)};b.isArguments=function(a){return!(!a||!l.call(a,"callee"))}; 25 | b.isFunction=function(a){return!(!a||!a.constructor||!a.call||!a.apply)};b.isString=function(a){return!!(a===""||a&&a.charCodeAt&&a.substr)};b.isNumber=function(a){return!!(a===0||a&&a.toExponential&&a.toFixed)};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||u.call(a)=="[object Boolean]"};b.isDate=function(a){return!(!a||!a.getTimezoneOffset||!a.setUTCFullYear)};b.isRegExp=function(a){return!(!a||!a.test||!a.exec||!(a.ignoreCase||a.ignoreCase===false))};b.isNull= 26 | function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.noConflict=function(){r._=F;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};b.mixin=function(a){j(b.functions(a),function(c){I(c,b[c]=a[c])})};var J=0;b.uniqueId=function(a){var b= 27 | J++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};b.template=function(a,c){var d=b.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.escape,function(a,b){return"',_.escape("+b.replace(/\\'/g,"'")+"),'"}).replace(d.interpolate,function(a,b){return"',"+b.replace(/\\'/g,"'")+",'"}).replace(d.evaluate||null,function(a, 28 | b){return"');"+b.replace(/\\'/g,"'").replace(/[\r\n\t]/g," ")+"__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",d=new Function("obj",d);return c?d(c):d};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var t=function(a,c){return c?b(a).chain():a},I=function(a,c){m.prototype[a]=function(){var a=i.call(arguments);G.call(a,this._wrapped);return t(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","), 29 | function(a){var b=k[a];m.prototype[a]=function(){b.apply(this._wrapped,arguments);return t(this._wrapped,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return t(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain=true;return this};m.prototype.value=function(){return this._wrapped}})(); 30 | -------------------------------------------------------------------------------- /assets/css/bootstrap/forms.less: -------------------------------------------------------------------------------- 1 | /* Forms.less 2 | * Base styles for various input types, form layouts, and states 3 | * ------------------------------------------------------------- */ 4 | 5 | 6 | // FORM STYLES 7 | // ----------- 8 | 9 | form { 10 | margin-bottom: @baseline; 11 | } 12 | 13 | // Groups of fields with labels on top (legends) 14 | fieldset { 15 | margin-bottom: @baseline; 16 | padding-top: @baseline; 17 | legend { 18 | display: block; 19 | padding-left: 150px; 20 | font-size: @basefont * 1.5; 21 | line-height: 1; 22 | color: @grayDark; 23 | *padding: 0 0 5px 145px; /* IE6-7 */ 24 | *line-height: 1.5; /* IE6-7 */ 25 | } 26 | } 27 | 28 | // Parent element that clears floats and wraps labels and fields together 29 | form .clearfix { 30 | margin-bottom: @baseline; 31 | .clearfix() 32 | } 33 | 34 | // Set font for forms 35 | label, 36 | input, 37 | select, 38 | textarea { 39 | #font > .sans-serif(normal,13px,normal); 40 | } 41 | 42 | // Float labels left 43 | label { 44 | padding-top: 6px; 45 | font-size: @basefont; 46 | line-height: @baseline; 47 | float: left; 48 | width: 130px; 49 | text-align: right; 50 | color: @grayDark; 51 | } 52 | 53 | // Shift over the inside div to align all label's relevant content 54 | form .input { 55 | margin-left: 150px; 56 | } 57 | 58 | // Checkboxs and radio buttons 59 | input[type=checkbox], 60 | input[type=radio] { 61 | cursor: pointer; 62 | } 63 | 64 | // Inputs, Textareas, Selects 65 | input, 66 | textarea, 67 | select, 68 | .uneditable-input { 69 | display: inline-block; 70 | width: 210px; 71 | height: @baseline; 72 | padding: 4px; 73 | font-size: @basefont; 74 | line-height: @baseline; 75 | color: @gray; 76 | border: 1px solid #ccc; 77 | .border-radius(3px); 78 | } 79 | 80 | // remove padding from select 81 | select { 82 | padding: initial; 83 | } 84 | 85 | // mini reset for non-html5 file types 86 | input[type=checkbox], 87 | input[type=radio] { 88 | width: auto; 89 | height: auto; 90 | padding: 0; 91 | margin: 3px 0; 92 | *margin-top: 0; /* IE6-7 */ 93 | line-height: normal; 94 | border: none; 95 | } 96 | 97 | input[type=file] { 98 | background-color: @white; 99 | padding: initial; 100 | border: initial; 101 | line-height: initial; 102 | .box-shadow(none); 103 | } 104 | 105 | input[type=button], 106 | input[type=reset], 107 | input[type=submit] { 108 | width: auto; 109 | height: auto; 110 | } 111 | 112 | select, 113 | input[type=file] { 114 | height: @baseline * 1.5; // In IE7, the height of the select element cannot be changed by height, only font-size 115 | *height: auto; // Reset for IE7 116 | line-height: @baseline * 1.5; 117 | *margin-top: 4px; /* For IE7, add top margin to align select with labels */ 118 | } 119 | 120 | // Make multiple select elements height not fixed 121 | select[multiple] { 122 | height: inherit; 123 | background-color: @white; // Fixes Chromium bug of unreadable items 124 | } 125 | 126 | textarea { 127 | height: auto; 128 | } 129 | 130 | // For text that needs to appear as an input but should not be an input 131 | .uneditable-input { 132 | background-color: @white; 133 | display: block; 134 | border-color: #eee; 135 | .box-shadow(inset 0 1px 2px rgba(0,0,0,.025)); 136 | cursor: not-allowed; 137 | } 138 | 139 | // Placeholder text gets special styles; can't be bundled together though for some reason 140 | :-moz-placeholder { 141 | color: @grayLight; 142 | } 143 | ::-webkit-input-placeholder { 144 | color: @grayLight; 145 | } 146 | 147 | // Focus states 148 | input, 149 | textarea { 150 | @transition: border linear .2s, box-shadow linear .2s; 151 | .transition(@transition); 152 | .box-shadow(inset 0 1px 3px rgba(0,0,0,.1)); 153 | } 154 | input:focus, 155 | textarea:focus { 156 | outline: 0; 157 | border-color: rgba(82,168,236,.8); 158 | @shadow: inset 0 1px 3px rgba(0,0,0,.1), 0 0 8px rgba(82,168,236,.6); 159 | .box-shadow(@shadow); 160 | } 161 | input[type=file]:focus, 162 | input[type=checkbox]:focus, 163 | select:focus { 164 | .box-shadow(none); // override for file inputs 165 | outline: 1px dotted #666; // Selet elements don't get box-shadow styles, so instead we do outline 166 | } 167 | 168 | 169 | // FORM FIELD FEEDBACK STATES 170 | // -------------------------- 171 | 172 | // Mixin for form field states 173 | .formFieldState(@textColor: #555, @borderColor: #ccc, @backgroundColor: #f5f5f5) { 174 | // Set the text color 175 | > label, 176 | .help-block, 177 | .help-inline { 178 | color: @textColor; 179 | } 180 | // Style inputs accordingly 181 | input, 182 | textarea { 183 | color: @textColor; 184 | border-color: @borderColor; 185 | &:focus { 186 | border-color: darken(@borderColor, 10%); 187 | .box-shadow(0 0 6px lighten(@borderColor, 20%)); 188 | } 189 | } 190 | // Give a small background color for input-prepend/-append 191 | .input-prepend .add-on, 192 | .input-append .add-on { 193 | color: @textColor; 194 | background-color: @backgroundColor; 195 | border-color: @textColor; 196 | } 197 | } 198 | // Error 199 | form .clearfix.error { 200 | .formFieldState(#b94a48, #ee5f5b, lighten(#ee5f5b, 30%)); 201 | } 202 | // Warning 203 | form .clearfix.warning { 204 | .formFieldState(#c09853, #ccae64, lighten(#CCAE64, 5%)); 205 | } 206 | // Success 207 | form .clearfix.success { 208 | .formFieldState(#468847, #57a957, lighten(#57a957, 30%)); 209 | } 210 | 211 | 212 | // Form element sizes 213 | // TODO v2: remove duplication here and just stick to .input-[size] in light of adding .spanN sizes 214 | .input-mini, 215 | input.mini, 216 | textarea.mini, 217 | select.mini { 218 | width: 60px; 219 | } 220 | .input-small, 221 | input.small, 222 | textarea.small, 223 | select.small { 224 | width: 90px; 225 | } 226 | .input-medium, 227 | input.medium, 228 | textarea.medium, 229 | select.medium { 230 | width: 150px; 231 | } 232 | .input-large, 233 | input.large, 234 | textarea.large, 235 | select.large { 236 | width: 210px; 237 | } 238 | .input-xlarge, 239 | input.xlarge, 240 | textarea.xlarge, 241 | select.xlarge { 242 | width: 270px; 243 | } 244 | .input-xxlarge, 245 | input.xxlarge, 246 | textarea.xxlarge, 247 | select.xxlarge { 248 | width: 530px; 249 | } 250 | textarea.xxlarge { 251 | overflow-y: auto; 252 | } 253 | 254 | // Grid style input sizes 255 | // This is a duplication of the main grid .columns() mixin, but subtracts 10px to account for input padding and border 256 | .formColumns(@columnSpan: 1) { 257 | display: inline-block; 258 | float: none; 259 | width: ((@gridColumnWidth) * @columnSpan) + (@gridGutterWidth * (@columnSpan - 1)) - 10; 260 | margin-left: 0; 261 | } 262 | input, 263 | textarea { 264 | // Default columns 265 | &.span1 { .formColumns(1); } 266 | &.span2 { .formColumns(2); } 267 | &.span3 { .formColumns(3); } 268 | &.span4 { .formColumns(4); } 269 | &.span5 { .formColumns(5); } 270 | &.span6 { .formColumns(6); } 271 | &.span7 { .formColumns(7); } 272 | &.span8 { .formColumns(8); } 273 | &.span9 { .formColumns(9); } 274 | &.span10 { .formColumns(10); } 275 | &.span11 { .formColumns(11); } 276 | &.span12 { .formColumns(12); } 277 | &.span13 { .formColumns(13); } 278 | &.span14 { .formColumns(14); } 279 | &.span15 { .formColumns(15); } 280 | &.span16 { .formColumns(16); } 281 | } 282 | 283 | // Disabled and read-only inputs 284 | input[disabled], 285 | select[disabled], 286 | textarea[disabled], 287 | input[readonly], 288 | select[readonly], 289 | textarea[readonly] { 290 | background-color: #f5f5f5; 291 | border-color: #ddd; 292 | cursor: not-allowed; 293 | } 294 | 295 | // Actions (the buttons) 296 | .actions { 297 | background: #f5f5f5; 298 | margin-top: @baseline; 299 | margin-bottom: @baseline; 300 | padding: (@baseline - 1) 20px @baseline 150px; 301 | border-top: 1px solid #ddd; 302 | .border-radius(0 0 3px 3px); 303 | .secondary-action { 304 | float: right; 305 | a { 306 | line-height: 30px; 307 | &:hover { 308 | text-decoration: underline; 309 | } 310 | } 311 | } 312 | } 313 | 314 | // Help Text 315 | // TODO: Do we need to set basefont and baseline here? 316 | .help-inline, 317 | .help-block { 318 | font-size: @basefont; 319 | line-height: @baseline; 320 | color: @grayLight; 321 | } 322 | .help-inline { 323 | padding-left: 5px; 324 | *position: relative; /* IE6-7 */ 325 | *top: -5px; /* IE6-7 */ 326 | } 327 | 328 | // Big blocks of help text 329 | .help-block { 330 | display: block; 331 | max-width: 600px; 332 | } 333 | 334 | // Inline Fields (input fields that appear as inline objects 335 | .inline-inputs { 336 | color: @gray; 337 | span { 338 | padding: 0 2px 0 1px; 339 | } 340 | } 341 | 342 | // Allow us to put symbols and text within the input field for a cleaner look 343 | .input-prepend, 344 | .input-append { 345 | input { 346 | .border-radius(0 3px 3px 0); 347 | } 348 | .add-on { 349 | position: relative; 350 | background: #f5f5f5; 351 | border: 1px solid #ccc; 352 | z-index: 2; 353 | float: left; 354 | display: block; 355 | width: auto; 356 | min-width: 16px; 357 | height: 18px; 358 | padding: 4px 4px 4px 5px; 359 | margin-right: -1px; 360 | font-weight: normal; 361 | line-height: 18px; 362 | color: @grayLight; 363 | text-align: center; 364 | text-shadow: 0 1px 0 @white; 365 | .border-radius(3px 0 0 3px); 366 | } 367 | .active { 368 | background: lighten(@green, 30); 369 | border-color: @green; 370 | } 371 | } 372 | .input-prepend { 373 | .add-on { 374 | *margin-top: 1px; /* IE6-7 */ 375 | } 376 | } 377 | .input-append { 378 | input { 379 | float: left; 380 | .border-radius(3px 0 0 3px); 381 | } 382 | .add-on { 383 | .border-radius(0 3px 3px 0); 384 | margin-right: 0; 385 | margin-left: -1px; 386 | } 387 | } 388 | 389 | // Stacked options for forms (radio buttons or checkboxes) 390 | .inputs-list { 391 | margin: 0 0 5px; 392 | width: 100%; 393 | li { 394 | display: block; 395 | padding: 0; 396 | width: 100%; 397 | } 398 | label { 399 | display: block; 400 | float: none; 401 | width: auto; 402 | padding: 0; 403 | margin-left: 20px; 404 | line-height: @baseline; 405 | text-align: left; 406 | white-space: normal; 407 | strong { 408 | color: @gray; 409 | } 410 | small { 411 | font-size: @basefont - 2; 412 | font-weight: normal; 413 | } 414 | } 415 | .inputs-list { 416 | margin-left: 25px; 417 | margin-bottom: 10px; 418 | padding-top: 0; 419 | } 420 | &:first-child { 421 | padding-top: 6px; 422 | } 423 | li + li { 424 | padding-top: 2px; 425 | } 426 | input[type=radio], 427 | input[type=checkbox] { 428 | margin-bottom: 0; 429 | margin-left: -20px; 430 | float: left; 431 | } 432 | } 433 | 434 | // Stacked forms 435 | .form-stacked { 436 | padding-left: 20px; 437 | fieldset { 438 | padding-top: @baseline / 2; 439 | } 440 | legend { 441 | padding-left: 0; 442 | } 443 | label { 444 | display: block; 445 | float: none; 446 | width: auto; 447 | font-weight: bold; 448 | text-align: left; 449 | line-height: 20px; 450 | padding-top: 0; 451 | } 452 | .clearfix { 453 | margin-bottom: @baseline / 2; 454 | div.input { 455 | margin-left: 0; 456 | } 457 | } 458 | .inputs-list { 459 | margin-bottom: 0; 460 | li { 461 | padding-top: 0; 462 | label { 463 | font-weight: normal; 464 | padding-top: 0; 465 | } 466 | } 467 | } 468 | div.clearfix.error { 469 | padding-top: 10px; 470 | padding-bottom: 10px; 471 | padding-left: 10px; 472 | margin-top: 0; 473 | margin-left: -10px; 474 | } 475 | .actions { 476 | margin-left: -20px; 477 | padding-left: 20px; 478 | } 479 | } 480 | -------------------------------------------------------------------------------- /assets/javascript/lib/backbone-min.js: -------------------------------------------------------------------------------- 1 | // Backbone.js 0.5.3 2 | // (c) 2010 Jeremy Ashkenas, DocumentCloud Inc. 3 | // Backbone may be freely distributed under the MIT license. 4 | // For all details and documentation: 5 | // http://documentcloud.github.com/backbone 6 | (function(){var h=this,p=h.Backbone,e;e=typeof exports!=="undefined"?exports:h.Backbone={};e.VERSION="0.5.3";var f=h._;if(!f&&typeof require!=="undefined")f=require("underscore")._;var g=h.jQuery||h.Zepto;e.noConflict=function(){h.Backbone=p;return this};e.emulateHTTP=!1;e.emulateJSON=!1;e.Events={bind:function(a,b,c){var d=this._callbacks||(this._callbacks={});(d[a]||(d[a]=[])).push([b,c]);return this},unbind:function(a,b){var c;if(a){if(c=this._callbacks)if(b){c=c[a];if(!c)return this;for(var d= 7 | 0,e=c.length;d/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")},has:function(a){return this.attributes[a]!=null},set:function(a,b){b||(b={});if(!a)return this;if(a.attributes)a=a.attributes;var c=this.attributes,d=this._escapedAttributes;if(!b.silent&&this.validate&&!this._performValidation(a,b))return!1;if(this.idAttribute in a)this.id=a[this.idAttribute]; 10 | var e=this._changing;this._changing=!0;for(var g in a){var h=a[g];if(!f.isEqual(c[g],h))c[g]=h,delete d[g],this._changed=!0,b.silent||this.trigger("change:"+g,this,h,b)}!e&&!b.silent&&this._changed&&this.change(b);this._changing=!1;return this},unset:function(a,b){if(!(a in this.attributes))return this;b||(b={});var c={};c[a]=void 0;if(!b.silent&&this.validate&&!this._performValidation(c,b))return!1;delete this.attributes[a];delete this._escapedAttributes[a];a==this.idAttribute&&delete this.id;this._changed= 11 | !0;b.silent||(this.trigger("change:"+a,this,void 0,b),this.change(b));return this},clear:function(a){a||(a={});var b,c=this.attributes,d={};for(b in c)d[b]=void 0;if(!a.silent&&this.validate&&!this._performValidation(d,a))return!1;this.attributes={};this._escapedAttributes={};this._changed=!0;if(!a.silent){for(b in c)this.trigger("change:"+b,this,void 0,a);this.change(a)}return this},fetch:function(a){a||(a={});var b=this,c=a.success;a.success=function(d,e,f){if(!b.set(b.parse(d,f),a))return!1;c&& 12 | c(b,d)};a.error=i(a.error,b,a);return(this.sync||e.sync).call(this,"read",this,a)},save:function(a,b){b||(b={});if(a&&!this.set(a,b))return!1;var c=this,d=b.success;b.success=function(a,e,f){if(!c.set(c.parse(a,f),b))return!1;d&&d(c,a,f)};b.error=i(b.error,c,b);var f=this.isNew()?"create":"update";return(this.sync||e.sync).call(this,f,this,b)},destroy:function(a){a||(a={});if(this.isNew())return this.trigger("destroy",this,this.collection,a);var b=this,c=a.success;a.success=function(d){b.trigger("destroy", 13 | b,b.collection,a);c&&c(b,d)};a.error=i(a.error,b,a);return(this.sync||e.sync).call(this,"delete",this,a)},url:function(){var a=k(this.collection)||this.urlRoot||l();if(this.isNew())return a;return a+(a.charAt(a.length-1)=="/"?"":"/")+encodeURIComponent(this.id)},parse:function(a){return a},clone:function(){return new this.constructor(this)},isNew:function(){return this.id==null},change:function(a){this.trigger("change",this,a);this._previousAttributes=f.clone(this.attributes);this._changed=!1},hasChanged:function(a){if(a)return this._previousAttributes[a]!= 14 | this.attributes[a];return this._changed},changedAttributes:function(a){a||(a=this.attributes);var b=this._previousAttributes,c=!1,d;for(d in a)f.isEqual(b[d],a[d])||(c=c||{},c[d]=a[d]);return c},previous:function(a){if(!a||!this._previousAttributes)return null;return this._previousAttributes[a]},previousAttributes:function(){return f.clone(this._previousAttributes)},_performValidation:function(a,b){var c=this.validate(a);if(c)return b.error?b.error(this,c,b):this.trigger("error",this,c,b),!1;return!0}}); 15 | e.Collection=function(a,b){b||(b={});if(b.comparator)this.comparator=b.comparator;f.bindAll(this,"_onModelEvent","_removeReference");this._reset();a&&this.reset(a,{silent:!0});this.initialize.apply(this,arguments)};f.extend(e.Collection.prototype,e.Events,{model:e.Model,initialize:function(){},toJSON:function(){return this.map(function(a){return a.toJSON()})},add:function(a,b){if(f.isArray(a))for(var c=0,d=a.length;c').hide().appendTo("body")[0].contentWindow,this.navigate(a); 25 | this._hasPushState?g(window).bind("popstate",this.checkUrl):"onhashchange"in window&&!b?g(window).bind("hashchange",this.checkUrl):setInterval(this.checkUrl,this.interval);this.fragment=a;m=!0;a=window.location;b=a.pathname==this.options.root;if(this._wantsPushState&&!this._hasPushState&&!b)return this.fragment=this.getFragment(null,!0),window.location.replace(this.options.root+"#"+this.fragment),!0;else if(this._wantsPushState&&this._hasPushState&&b&&a.hash)this.fragment=a.hash.replace(j,""),window.history.replaceState({}, 26 | document.title,a.protocol+"//"+a.host+this.options.root+this.fragment);if(!this.options.silent)return this.loadUrl()},route:function(a,b){this.handlers.unshift({route:a,callback:b})},checkUrl:function(){var a=this.getFragment();a==this.fragment&&this.iframe&&(a=this.getFragment(this.iframe.location.hash));if(a==this.fragment||a==decodeURIComponent(this.fragment))return!1;this.iframe&&this.navigate(a);this.loadUrl()||this.loadUrl(window.location.hash)},loadUrl:function(a){var b=this.fragment=this.getFragment(a); 27 | return f.any(this.handlers,function(a){if(a.route.test(b))return a.callback(b),!0})},navigate:function(a,b){var c=(a||"").replace(j,"");if(!(this.fragment==c||this.fragment==decodeURIComponent(c))){if(this._hasPushState){var d=window.location;c.indexOf(this.options.root)!=0&&(c=this.options.root+c);this.fragment=c;window.history.pushState({},document.title,d.protocol+"//"+d.host+c)}else if(window.location.hash=this.fragment=c,this.iframe&&c!=this.getFragment(this.iframe.location.hash))this.iframe.document.open().close(), 28 | this.iframe.location.hash=c;b&&this.loadUrl(a)}}});e.View=function(a){this.cid=f.uniqueId("view");this._configure(a||{});this._ensureElement();this.delegateEvents();this.initialize.apply(this,arguments)};var u=/^(\S+)\s*(.*)$/,n=["model","collection","el","id","attributes","className","tagName"];f.extend(e.View.prototype,e.Events,{tagName:"div",$:function(a){return g(a,this.el)},initialize:function(){},render:function(){return this},remove:function(){g(this.el).remove();return this},make:function(a, 29 | b,c){a=document.createElement(a);b&&g(a).attr(b);c&&g(a).html(c);return a},delegateEvents:function(a){if(a||(a=this.events))for(var b in f.isFunction(a)&&(a=a.call(this)),g(this.el).unbind(".delegateEvents"+this.cid),a){var c=this[a[b]];if(!c)throw Error('Event "'+a[b]+'" does not exist');var d=b.match(u),e=d[1];d=d[2];c=f.bind(c,this);e+=".delegateEvents"+this.cid;d===""?g(this.el).bind(e,c):g(this.el).delegate(d,e,c)}},_configure:function(a){this.options&&(a=f.extend({},this.options,a));for(var b= 30 | 0,c=n.length;b a { 30 | background-color: #333; 31 | background-color: rgba(255,255,255,.05); 32 | color: @white; 33 | text-decoration: none; 34 | } 35 | 36 | // Website name 37 | // h3 left for backwards compatibility 38 | h3 { 39 | position: relative; 40 | } 41 | h3 a, 42 | .brand { 43 | float: left; 44 | display: block; 45 | padding: 8px 20px 12px; 46 | margin-left: -20px; // negative indent to left-align the text down the page 47 | color: @white; 48 | font-size: 20px; 49 | font-weight: 200; 50 | line-height: 1; 51 | } 52 | 53 | // Plain text in topbar 54 | p { 55 | margin: 0; 56 | line-height: 40px; 57 | a:hover { 58 | background-color: transparent; 59 | color: @white; 60 | } 61 | } 62 | 63 | // Search Form 64 | form { 65 | float: left; 66 | margin: 5px 0 0 0; 67 | position: relative; 68 | .opacity(100); 69 | } 70 | // Todo: remove from v2.0 when ready, added for legacy 71 | form.pull-right { 72 | float: right; 73 | } 74 | input { 75 | background-color: #444; 76 | background-color: rgba(255,255,255,.3); 77 | #font > .sans-serif(13px, normal, 1); 78 | padding: 4px 9px; 79 | color: @white; 80 | color: rgba(255,255,255,.75); 81 | border: 1px solid #111; 82 | .border-radius(4px); 83 | @shadow: inset 0 1px 2px rgba(0,0,0,.1), 0 1px 0px rgba(255,255,255,.25); 84 | .box-shadow(@shadow); 85 | .transition(none); 86 | 87 | // Placeholder text gets special styles; can't be bundled together though for some reason 88 | &:-moz-placeholder { 89 | color: @grayLighter; 90 | } 91 | &::-webkit-input-placeholder { 92 | color: @grayLighter; 93 | } 94 | // Hover states 95 | &:hover { 96 | background-color: @grayLight; 97 | background-color: rgba(255,255,255,.5); 98 | color: @white; 99 | } 100 | // Focus states (we use .focused since IE8 and down doesn't support :focus) 101 | &:focus, 102 | &.focused { 103 | outline: 0; 104 | background-color: @white; 105 | color: @grayDark; 106 | text-shadow: 0 1px 0 @white; 107 | border: 0; 108 | padding: 5px 10px; 109 | .box-shadow(0 0 3px rgba(0,0,0,.15)); 110 | } 111 | } 112 | } 113 | 114 | // gradient is applied to it's own element because overflow visible is not honored by ie when filter is present 115 | // For backwards compatibility, include .topbar .fill 116 | .topbar-inner, 117 | .topbar .fill { 118 | background-color: #222; 119 | #gradient > .vertical(#333, #222); 120 | @shadow: 0 1px 3px rgba(0,0,0,.25), inset 0 -1px 0 rgba(0,0,0,.1); 121 | .box-shadow(@shadow); 122 | } 123 | 124 | 125 | // NAVIGATION 126 | // ---------- 127 | 128 | // Topbar Nav 129 | // ul.nav for all topbar based navigation to avoid inheritance issues and over-specificity 130 | // For backwards compatibility, leave in .topbar div > ul 131 | .topbar div > ul, 132 | .nav { 133 | display: block; 134 | float: left; 135 | margin: 0 10px 0 0; 136 | position: relative; 137 | left: 0; 138 | > li { 139 | display: block; 140 | float: left; 141 | } 142 | a { 143 | display: block; 144 | float: none; 145 | padding: 10px 10px 11px; 146 | line-height: 19px; 147 | text-decoration: none; 148 | &:hover { 149 | color: @white; 150 | text-decoration: none; 151 | } 152 | } 153 | .active > a { 154 | background-color: #222; 155 | background-color: rgba(0,0,0,.5); 156 | } 157 | 158 | // Secondary (floated right) nav in topbar 159 | &.secondary-nav { 160 | float: right; 161 | margin-left: 10px; 162 | margin-right: 0; 163 | // backwards compatibility 164 | .menu-dropdown, 165 | .dropdown-menu { 166 | right: 0; 167 | border: 0; 168 | } 169 | } 170 | // Dropdowns within the .nav 171 | // a.menu:hover and li.open .menu for backwards compatibility 172 | a.menu:hover, 173 | li.open .menu, 174 | .dropdown-toggle:hover, 175 | .dropdown.open .dropdown-toggle { 176 | background: #444; 177 | background: rgba(255,255,255,.05); 178 | } 179 | // .menu-dropdown for backwards compatibility 180 | .menu-dropdown, 181 | .dropdown-menu { 182 | background-color: #333; 183 | // a.menu for backwards compatibility 184 | a.menu, 185 | .dropdown-toggle { 186 | color: @white; 187 | &.open { 188 | background: #444; 189 | background: rgba(255,255,255,.05); 190 | } 191 | } 192 | li a { 193 | color: #999; 194 | text-shadow: 0 1px 0 rgba(0,0,0,.5); 195 | &:hover { 196 | #gradient > .vertical(#292929,#191919); 197 | color: @white; 198 | } 199 | } 200 | .active a { 201 | color: @white; 202 | } 203 | .divider { 204 | background-color: #222; 205 | border-color: #444; 206 | } 207 | } 208 | } 209 | 210 | // For backwards compatibility with new dropdowns, redeclare dropdown link padding 211 | .topbar ul .menu-dropdown li a, 212 | .topbar ul .dropdown-menu li a { 213 | padding: 4px 15px; 214 | } 215 | 216 | // Dropdown Menus 217 | // Use the .menu class on any
  • element within the topbar or ul.tabs and you'll get some superfancy dropdowns 218 | // li.menu for backwards compatibility 219 | li.menu, 220 | .dropdown { 221 | position: relative; 222 | } 223 | // The link that is clicked to toggle the dropdown 224 | // a.menu for backwards compatibility 225 | a.menu:after, 226 | .dropdown-toggle:after { 227 | width: 0; 228 | height: 0; 229 | display: inline-block; 230 | content: "↓"; 231 | text-indent: -99999px; 232 | vertical-align: top; 233 | margin-top: 8px; 234 | margin-left: 4px; 235 | border-left: 4px solid transparent; 236 | border-right: 4px solid transparent; 237 | border-top: 4px solid @white; 238 | .opacity(50); 239 | } 240 | // The dropdown menu (ul) 241 | // .menu-dropdown for backwards compatibility 242 | .menu-dropdown, 243 | .dropdown-menu { 244 | background-color: @white; 245 | float: left; 246 | display: none; // None by default, but block on "open" of the menu 247 | position: absolute; 248 | top: 40px; 249 | z-index: 900; 250 | min-width: 160px; 251 | max-width: 220px; 252 | _width: 160px; 253 | margin-left: 0; // override default ul styles 254 | margin-right: 0; 255 | padding: 6px 0; 256 | zoom: 1; // do we need this? 257 | border-color: #999; 258 | border-color: rgba(0,0,0,.2); 259 | border-style: solid; 260 | border-width: 0 1px 1px; 261 | .border-radius(0 0 6px 6px); 262 | .box-shadow(0 2px 4px rgba(0,0,0,.2)); 263 | .background-clip(padding-box); 264 | 265 | // Unfloat any li's to make them stack 266 | li { 267 | float: none; 268 | display: block; 269 | background-color: none; 270 | } 271 | // Dividers (basically an hr) within the dropdown 272 | .divider { 273 | height: 1px; 274 | margin: 5px 0; 275 | overflow: hidden; 276 | background-color: #eee; 277 | border-bottom: 1px solid @white; 278 | } 279 | } 280 | 281 | .topbar .dropdown-menu, 282 | .dropdown-menu { 283 | // Links within the dropdown menu 284 | a { 285 | display: block; 286 | padding: 4px 15px; 287 | clear: both; 288 | font-weight: normal; 289 | line-height: 18px; 290 | color: @gray; 291 | text-shadow: 0 1px 0 @white; 292 | // Hover state 293 | &:hover, 294 | &.hover { 295 | #gradient > .vertical(#eeeeee, #dddddd); 296 | color: @grayDark; 297 | text-decoration: none; 298 | @shadow: inset 0 1px 0 rgba(0,0,0,.025), inset 0 -1px rgba(0,0,0,.025); 299 | .box-shadow(@shadow); 300 | } 301 | } 302 | } 303 | 304 | // Open state for the dropdown 305 | // .open for backwards compatibility 306 | .open, 307 | .dropdown.open { 308 | // .menu for backwards compatibility 309 | .menu, 310 | .dropdown-toggle { 311 | color: @white; 312 | background: #ccc; 313 | background: rgba(0,0,0,.3); 314 | } 315 | // .menu-dropdown for backwards compatibility 316 | .menu-dropdown, 317 | .dropdown-menu { 318 | display: block; 319 | } 320 | } 321 | 322 | 323 | // TABS AND PILLS 324 | // -------------- 325 | 326 | // Common styles 327 | .tabs, 328 | .pills { 329 | margin: 0 0 @baseline; 330 | padding: 0; 331 | list-style: none; 332 | .clearfix(); 333 | > li { 334 | float: left; 335 | > a { 336 | display: block; 337 | } 338 | } 339 | } 340 | 341 | // Tabs 342 | .tabs { 343 | border-color: #ddd; 344 | border-style: solid; 345 | border-width: 0 0 1px; 346 | > li { 347 | position: relative; // For the dropdowns mostly 348 | margin-bottom: -1px; 349 | > a { 350 | padding: 0 15px; 351 | margin-right: 2px; 352 | line-height: (@baseline * 2) - 2; 353 | border: 1px solid transparent; 354 | .border-radius(4px 4px 0 0); 355 | &:hover { 356 | text-decoration: none; 357 | background-color: #eee; 358 | border-color: #eee #eee #ddd; 359 | } 360 | } 361 | } 362 | // Active state, and it's :hover to override normal :hover 363 | .active > a, 364 | .active > a:hover { 365 | color: @gray; 366 | background-color: @white; 367 | border: 1px solid #ddd; 368 | border-bottom-color: transparent; 369 | cursor: default; 370 | } 371 | } 372 | 373 | // Dropdowns in tabs 374 | .tabs { 375 | // first one for backwards compatibility 376 | .menu-dropdown, 377 | .dropdown-menu { 378 | top: 35px; 379 | border-width: 1px; 380 | .border-radius(0 6px 6px 6px); 381 | } 382 | // first one for backwards compatibility 383 | a.menu:after, 384 | .dropdown-toggle:after { 385 | border-top-color: #999; 386 | margin-top: 15px; 387 | margin-left: 5px; 388 | } 389 | // first one for backwards compatibility 390 | li.open.menu .menu, 391 | .open.dropdown .dropdown-toggle { 392 | border-color: #999; 393 | } 394 | // first one for backwards compatibility 395 | li.open a.menu:after, 396 | .dropdown.open .dropdown-toggle:after { 397 | border-top-color: #555; 398 | } 399 | } 400 | 401 | // Pills 402 | .pills { 403 | a { 404 | margin: 5px 3px 5px 0; 405 | padding: 0 15px; 406 | line-height: 30px; 407 | text-shadow: 0 1px 1px @white; 408 | .border-radius(15px); 409 | &:hover { 410 | color: @white; 411 | text-decoration: none; 412 | text-shadow: 0 1px 1px rgba(0,0,0,.25); 413 | background-color: @linkColorHover; 414 | } 415 | } 416 | .active a { 417 | color: @white; 418 | text-shadow: 0 1px 1px rgba(0,0,0,.25); 419 | background-color: @linkColor; 420 | } 421 | } 422 | 423 | // Stacked pills 424 | .pills-vertical > li { 425 | float: none; 426 | } 427 | 428 | // Tabbable areas 429 | .tab-content, 430 | .pill-content { 431 | } 432 | .tab-content > .tab-pane, 433 | .pill-content > .pill-pane, 434 | .tab-content > div, 435 | .pill-content > div { 436 | display: none; 437 | } 438 | .tab-content > .active, 439 | .pill-content > .active { 440 | display: block; 441 | } 442 | 443 | 444 | // BREADCRUMBS 445 | // ----------- 446 | 447 | .breadcrumb { 448 | padding: 7px 14px; 449 | margin: 0 0 @baseline; 450 | #gradient > .vertical(#ffffff, #f5f5f5); 451 | border: 1px solid #ddd; 452 | .border-radius(3px); 453 | .box-shadow(inset 0 1px 0 @white); 454 | li { 455 | display: inline; 456 | text-shadow: 0 1px 0 @white; 457 | } 458 | .divider { 459 | padding: 0 5px; 460 | color: @grayLight; 461 | } 462 | .active a { 463 | color: @grayDark; 464 | } 465 | } 466 | 467 | 468 | // PAGE HEADERS 469 | // ------------ 470 | 471 | .hero-unit { 472 | background-color: #f5f5f5; 473 | margin-bottom: 30px; 474 | padding: 60px; 475 | .border-radius(6px); 476 | h1 { 477 | margin-bottom: 0; 478 | font-size: 60px; 479 | line-height: 1; 480 | letter-spacing: -1px; 481 | } 482 | p { 483 | font-size: 18px; 484 | font-weight: 200; 485 | line-height: @baseline * 1.5; 486 | } 487 | } 488 | footer { 489 | margin-top: @baseline - 1; 490 | padding-top: @baseline - 1; 491 | border-top: 1px solid #eee; 492 | } 493 | 494 | 495 | // PAGE HEADERS 496 | // ------------ 497 | 498 | .page-header { 499 | margin-bottom: @baseline - 1; 500 | border-bottom: 1px solid #ddd; 501 | .box-shadow(0 1px 0 rgba(255,255,255,.5)); 502 | h1 { 503 | margin-bottom: (@baseline / 2) - 1px; 504 | } 505 | } 506 | 507 | 508 | // BUTTON STYLES 509 | // ------------- 510 | 511 | // Shared colors for buttons and alerts 512 | .btn, 513 | .alert-message { 514 | // Set text color 515 | &.danger, 516 | &.danger:hover, 517 | &.error, 518 | &.error:hover, 519 | &.success, 520 | &.success:hover, 521 | &.info, 522 | &.info:hover { 523 | color: @white 524 | } 525 | // Sets the close button to the middle of message 526 | .close{ 527 | font-family: Arial, sans-serif; 528 | line-height: 18px; 529 | } 530 | // Danger and error appear as red 531 | &.danger, 532 | &.error { 533 | .gradientBar(#ee5f5b, #c43c35); 534 | } 535 | // Success appears as green 536 | &.success { 537 | .gradientBar(#62c462, #57a957); 538 | } 539 | // Info appears as a neutral blue 540 | &.info { 541 | .gradientBar(#5bc0de, #339bb9); 542 | } 543 | } 544 | 545 | // Base .btn styles 546 | .btn { 547 | // Button Base 548 | cursor: pointer; 549 | display: inline-block; 550 | #gradient > .vertical-three-colors(#ffffff, #ffffff, 25%, darken(#ffffff, 10%)); // Don't use .gradientbar() here since it does a three-color gradient 551 | padding: 5px 14px 6px; 552 | text-shadow: 0 1px 1px rgba(255,255,255,.75); 553 | color: #333; 554 | font-size: @basefont; 555 | line-height: normal; 556 | border: 1px solid #ccc; 557 | border-bottom-color: #bbb; 558 | .border-radius(4px); 559 | @shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05); 560 | .box-shadow(@shadow); 561 | 562 | &:hover { 563 | background-position: 0 -15px; 564 | color: #333; 565 | text-decoration: none; 566 | } 567 | 568 | // Focus state for keyboard and accessibility 569 | &:focus { 570 | outline: 1px dotted #666; 571 | } 572 | 573 | // Primary Button Type 574 | &.primary { 575 | color: @white; 576 | .gradientBar(@blue, @blueDark) 577 | } 578 | 579 | // Transitions 580 | .transition(.1s linear all); 581 | 582 | // Active and Disabled states 583 | &.active, 584 | &:active { 585 | @shadow: inset 0 2px 4px rgba(0,0,0,.25), 0 1px 2px rgba(0,0,0,.05); 586 | .box-shadow(@shadow); 587 | } 588 | &.disabled { 589 | cursor: default; 590 | background-image: none; 591 | .reset-filter(); 592 | .opacity(65); 593 | .box-shadow(none); 594 | } 595 | &[disabled] { 596 | // disabled pseudo can't be included with .disabled 597 | // def because IE8 and below will drop it ;_; 598 | cursor: default; 599 | background-image: none; 600 | .reset-filter(); 601 | .opacity(65); 602 | .box-shadow(none); 603 | } 604 | 605 | // Button Sizes 606 | &.large { 607 | font-size: @basefont + 2px; 608 | line-height: normal; 609 | padding: 9px 14px 9px; 610 | .border-radius(6px); 611 | } 612 | &.small { 613 | padding: 7px 9px 7px; 614 | font-size: @basefont - 2px; 615 | } 616 | } 617 | // Super jank hack for removing border-radius from IE9 so we can keep filter gradients on alerts and buttons 618 | :root .alert-message, 619 | :root .btn { 620 | border-radius: 0 \0; 621 | } 622 | 623 | // Help Firefox not be a jerk about adding extra padding to buttons 624 | button.btn, 625 | input[type=submit].btn { 626 | &::-moz-focus-inner { 627 | padding: 0; 628 | border: 0; 629 | } 630 | } 631 | 632 | 633 | // CLOSE ICONS 634 | // ----------- 635 | .close { 636 | float: right; 637 | color: @black; 638 | font-size: 20px; 639 | font-weight: bold; 640 | line-height: @baseline * .75; 641 | text-shadow: 0 1px 0 rgba(255,255,255,1); 642 | .opacity(25); 643 | &:hover { 644 | color: @black; 645 | text-decoration: none; 646 | .opacity(40); 647 | } 648 | } 649 | 650 | 651 | // ERROR STYLES 652 | // ------------ 653 | 654 | // Base alert styles 655 | .alert-message { 656 | position: relative; 657 | padding: 7px 15px; 658 | margin-bottom: @baseline; 659 | color: @grayDark; 660 | .gradientBar(#fceec1, #eedc94); // warning by default 661 | text-shadow: 0 1px 0 rgba(255,255,255,.5); 662 | border-width: 1px; 663 | border-style: solid; 664 | .border-radius(4px); 665 | .box-shadow(inset 0 1px 0 rgba(255,255,255,.25)); 666 | 667 | // Adjust close icon 668 | .close { 669 | margin-top: 1px; 670 | *margin-top: 0; // For IE7 671 | } 672 | 673 | // Make links same color as text and stand out more 674 | a { 675 | font-weight: bold; 676 | color: @grayDark; 677 | } 678 | &.danger p a, 679 | &.error p a, 680 | &.success p a, 681 | &.info p a { 682 | color: @white; 683 | } 684 | 685 | // Remove extra margin from content 686 | h5 { 687 | line-height: @baseline; 688 | } 689 | p { 690 | margin-bottom: 0; 691 | } 692 | div { 693 | margin-top: 5px; 694 | margin-bottom: 2px; 695 | line-height: 28px; 696 | } 697 | .btn { 698 | // Provide actions with buttons 699 | .box-shadow(0 1px 0 rgba(255,255,255,.25)); 700 | } 701 | 702 | &.block-message { 703 | background-image: none; 704 | background-color: lighten(#fceec1, 5%); 705 | .reset-filter(); 706 | padding: 14px; 707 | border-color: #fceec1; 708 | .box-shadow(none); 709 | ul, p { 710 | margin-right: 30px; 711 | } 712 | ul { 713 | margin-bottom: 0; 714 | } 715 | li { 716 | color: @grayDark; 717 | } 718 | .alert-actions { 719 | margin-top: 5px; 720 | } 721 | &.error, 722 | &.success, 723 | &.info { 724 | color: @grayDark; 725 | text-shadow: 0 1px 0 rgba(255,255,255,.5); 726 | } 727 | &.error { 728 | background-color: lighten(#f56a66, 25%); 729 | border-color: lighten(#f56a66, 20%); 730 | } 731 | &.success { 732 | background-color: lighten(#62c462, 30%); 733 | border-color: lighten(#62c462, 25%); 734 | } 735 | &.info { 736 | background-color: lighten(#6bd0ee, 25%); 737 | border-color: lighten(#6bd0ee, 20%); 738 | } 739 | // Change link color back 740 | &.danger p a, 741 | &.error p a, 742 | &.success p a, 743 | &.info p a { 744 | color: @grayDark; 745 | } 746 | 747 | } 748 | } 749 | 750 | 751 | // PAGINATION 752 | // ---------- 753 | 754 | .pagination { 755 | height: @baseline * 2; 756 | margin: @baseline 0; 757 | ul { 758 | float: left; 759 | margin: 0; 760 | border: 1px solid #ddd; 761 | border: 1px solid rgba(0,0,0,.15); 762 | .border-radius(3px); 763 | .box-shadow(0 1px 2px rgba(0,0,0,.05)); 764 | } 765 | li { 766 | display: inline; 767 | } 768 | a { 769 | float: left; 770 | padding: 0 14px; 771 | line-height: (@baseline * 2) - 2; 772 | border-right: 1px solid; 773 | border-right-color: #ddd; 774 | border-right-color: rgba(0,0,0,.15); 775 | *border-right-color: #ddd; /* IE6-7 */ 776 | text-decoration: none; 777 | } 778 | a:hover, 779 | .active a { 780 | background-color: lighten(@blue, 45%); 781 | } 782 | .disabled a, 783 | .disabled a:hover { 784 | background-color: transparent; 785 | color: @grayLight; 786 | } 787 | .next a { 788 | border: 0; 789 | } 790 | } 791 | 792 | 793 | // WELLS 794 | // ----- 795 | 796 | .well { 797 | background-color: #f5f5f5; 798 | margin-bottom: 20px; 799 | padding: 19px; 800 | min-height: 20px; 801 | border: 1px solid #eee; 802 | border: 1px solid rgba(0,0,0,.05); 803 | .border-radius(4px); 804 | .box-shadow(inset 0 1px 1px rgba(0,0,0,.05)); 805 | blockquote { 806 | border-color: #ddd; 807 | border-color: rgba(0,0,0,.15); 808 | } 809 | } 810 | 811 | 812 | // MODALS 813 | // ------ 814 | 815 | .modal-backdrop { 816 | background-color: @black; 817 | position: fixed; 818 | top: 0; 819 | left: 0; 820 | right: 0; 821 | bottom: 0; 822 | z-index: 10000; 823 | // Fade for backdrop 824 | &.fade { opacity: 0; } 825 | } 826 | 827 | .modal-backdrop, 828 | .modal-backdrop.fade.in { 829 | .opacity(80); 830 | } 831 | 832 | .modal { 833 | position: fixed; 834 | top: 50%; 835 | left: 50%; 836 | z-index: 11000; 837 | width: 560px; 838 | margin: -250px 0 0 -280px; 839 | background-color: @white; 840 | border: 1px solid #999; 841 | border: 1px solid rgba(0,0,0,.3); 842 | *border: 1px solid #999; /* IE6-7 */ 843 | .border-radius(6px); 844 | .box-shadow(0 3px 7px rgba(0,0,0,0.3)); 845 | .background-clip(padding-box); 846 | .close { margin-top: 7px; } 847 | &.fade { 848 | .transition(e('opacity .3s linear, top .3s ease-out')); 849 | top: -25%; 850 | } 851 | &.fade.in { top: 50%; } 852 | } 853 | .modal-header { 854 | border-bottom: 1px solid #eee; 855 | padding: 5px 15px; 856 | } 857 | .modal-body { 858 | padding: 15px; 859 | } 860 | .modal-body form { 861 | margin-bottom: 0; 862 | } 863 | .modal-footer { 864 | background-color: #f5f5f5; 865 | padding: 14px 15px 15px; 866 | border-top: 1px solid #ddd; 867 | .border-radius(0 0 6px 6px); 868 | .box-shadow(inset 0 1px 0 @white); 869 | .clearfix(); 870 | margin-bottom: 0; 871 | .btn { 872 | float: right; 873 | margin-left: 5px; 874 | } 875 | } 876 | 877 | // Fix the stacking of these components when in modals 878 | .modal .popover, 879 | .modal .twipsy { 880 | z-index: 12000; 881 | } 882 | 883 | 884 | // POPOVER ARROWS 885 | // -------------- 886 | 887 | #popoverArrow { 888 | .above(@arrowWidth: 5px) { 889 | bottom: 0; 890 | left: 50%; 891 | margin-left: -@arrowWidth; 892 | border-left: @arrowWidth solid transparent; 893 | border-right: @arrowWidth solid transparent; 894 | border-top: @arrowWidth solid @black; 895 | } 896 | .left(@arrowWidth: 5px) { 897 | top: 50%; 898 | right: 0; 899 | margin-top: -@arrowWidth; 900 | border-top: @arrowWidth solid transparent; 901 | border-bottom: @arrowWidth solid transparent; 902 | border-left: @arrowWidth solid @black; 903 | } 904 | .below(@arrowWidth: 5px) { 905 | top: 0; 906 | left: 50%; 907 | margin-left: -@arrowWidth; 908 | border-left: @arrowWidth solid transparent; 909 | border-right: @arrowWidth solid transparent; 910 | border-bottom: @arrowWidth solid @black; 911 | } 912 | .right(@arrowWidth: 5px) { 913 | top: 50%; 914 | left: 0; 915 | margin-top: -@arrowWidth; 916 | border-top: @arrowWidth solid transparent; 917 | border-bottom: @arrowWidth solid transparent; 918 | border-right: @arrowWidth solid @black; 919 | } 920 | } 921 | 922 | // TWIPSY 923 | // ------ 924 | 925 | .twipsy { 926 | display: block; 927 | position: absolute; 928 | visibility: visible; 929 | padding: 5px; 930 | font-size: 11px; 931 | z-index: 1000; 932 | .opacity(80); 933 | &.fade.in { 934 | .opacity(80); 935 | } 936 | &.above .twipsy-arrow { #popoverArrow > .above(); } 937 | &.left .twipsy-arrow { #popoverArrow > .left(); } 938 | &.below .twipsy-arrow { #popoverArrow > .below(); } 939 | &.right .twipsy-arrow { #popoverArrow > .right(); } 940 | } 941 | .twipsy-inner { 942 | padding: 3px 8px; 943 | background-color: @black; 944 | color: white; 945 | text-align: center; 946 | max-width: 200px; 947 | text-decoration: none; 948 | .border-radius(4px); 949 | } 950 | .twipsy-arrow { 951 | position: absolute; 952 | width: 0; 953 | height: 0; 954 | } 955 | 956 | 957 | // POPOVERS 958 | // -------- 959 | 960 | .popover { 961 | position: absolute; 962 | top: 0; 963 | left: 0; 964 | z-index: 1000; 965 | padding: 5px; 966 | display: none; 967 | &.above .arrow { #popoverArrow > .above(); } 968 | &.right .arrow { #popoverArrow > .right(); } 969 | &.below .arrow { #popoverArrow > .below(); } 970 | &.left .arrow { #popoverArrow > .left(); } 971 | .arrow { 972 | position: absolute; 973 | width: 0; 974 | height: 0; 975 | } 976 | .inner { 977 | background: @black; 978 | background: rgba(0,0,0,.8); 979 | padding: 3px; 980 | overflow: hidden; 981 | width: 280px; 982 | .border-radius(6px); 983 | .box-shadow(0 3px 7px rgba(0,0,0,0.3)); 984 | } 985 | .title { 986 | background-color: #f5f5f5; 987 | padding: 9px 15px; 988 | line-height: 1; 989 | .border-radius(3px 3px 0 0); 990 | border-bottom:1px solid #eee; 991 | } 992 | .content { 993 | background-color: @white; 994 | padding: 14px; 995 | .border-radius(0 0 3px 3px); 996 | .background-clip(padding-box); 997 | p, ul, ol { 998 | margin-bottom: 0; 999 | } 1000 | } 1001 | } 1002 | 1003 | 1004 | // PATTERN ANIMATIONS 1005 | // ------------------ 1006 | 1007 | .fade { 1008 | .transition(opacity .15s linear); 1009 | opacity: 0; 1010 | &.in { 1011 | opacity: 1; 1012 | } 1013 | } 1014 | 1015 | 1016 | // LABELS 1017 | // ------ 1018 | 1019 | .label { 1020 | padding: 1px 3px 2px; 1021 | font-size: @basefont * .75; 1022 | font-weight: bold; 1023 | color: @white; 1024 | text-transform: uppercase; 1025 | white-space: nowrap; 1026 | background-color: @grayLight; 1027 | .border-radius(3px); 1028 | text-shadow: none; 1029 | &.important { background-color: #c43c35; } 1030 | &.warning { background-color: @orange; } 1031 | &.success { background-color: @green; } 1032 | &.notice { background-color: lighten(@blue, 25%); } 1033 | } 1034 | 1035 | 1036 | // MEDIA GRIDS 1037 | // ----------- 1038 | 1039 | .media-grid { 1040 | margin-left: -@gridGutterWidth; 1041 | margin-bottom: 0; 1042 | .clearfix(); 1043 | li { 1044 | display: inline; 1045 | } 1046 | a { 1047 | float: left; 1048 | padding: 4px; 1049 | margin: 0 0 @baseline @gridGutterWidth; 1050 | border: 1px solid #ddd; 1051 | .border-radius(4px); 1052 | .box-shadow(0 1px 1px rgba(0,0,0,.075)); 1053 | img { 1054 | display: block; 1055 | } 1056 | &:hover { 1057 | border-color: @linkColor; 1058 | .box-shadow(0 1px 4px rgba(0,105,214,.25)); 1059 | } 1060 | } 1061 | } 1062 | -------------------------------------------------------------------------------- /assets/javascript/lib/GGS.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Golden Gridlet (1.01) 4 | * by Joni Korpi 5 | * licensed under MIT 6 | * 7 | */ 8 | 9 | var guideColor = 'rgb(255,195,0)'; 10 | var guideInnerColor = 'rgba(255,255,255, 0.91)'; 11 | var guideOpacity = '0.618'; 12 | 13 | var switchColor = 'rgb(0,0,0)'; 14 | var switchOpacity = '0.618'; 15 | 16 | var baseFontSize = 16; 17 | var baselineGridHeight = (24 / baseFontSize)+'em'; 18 | 19 | var eightColBreakpoint = ((720-1) / baseFontSize)+'em'; 20 | var sixteenColBreakpoint = ((1872-1) / baseFontSize)+'em'; 21 | 22 | /* 23 | * Note that the script might not work as expected if 24 | * the element of your page has a set width and 25 | * position: relative;, because the guides are appended 26 | * inside , but positioned in relation to . 27 | * 28 | * Also note that the baseline grid doesn't really align 29 | * up anymore after zooming the baseline grid in or out, 30 | * because of rounding errors. 31 | */ 32 | 33 | 34 | /*! 35 | * Ender: open module JavaScript framework 36 | * copyright Dustin Diaz & Jacob Thornton 2011 (@ded @fat) 37 | * https://ender.no.de 38 | * License MIT 39 | * Build: ender -b jeesh 40 | */ 41 | !function(a){function d(a,b){return c(a,b)}function c(a,e,f){d._select&&(typeof a=="string"||a.nodeName||a.length&&"item"in a||a==window)?(f=d._select(a,e),f.selector=a):f=isFinite(a.length)?a:[a];return b(f,c)}function b(a,b){for(var c in b)c!="noConflict"&&c!="_VERSION"&&(a[c]=b[c]);return a}b(d,{_VERSION:"0.2.4",ender:function(a,e){b(e?c:d,a)},fn:a.$&&a.$.fn||{}}),b(c,{forEach:function(a,b,c){for(c=0,l=this.length;c]+)/.exec(a),d=c.createElement(b&&k[b[1].toLowerCase()]||"div"),e=[];d.innerHTML=a;var f=d.childNodes;d=d.firstChild,e.push(d);while(d=d.nextSibling)d.nodeType==1&&e.push(d);return e}():z(a)?[a.cloneNode(!0)]:[]},J.doc=function(){var a=d.scrollWidth,b=d.scrollHeight,c=this.viewport();return{width:Math.max(a,c.width),height:Math.max(b,c.height)}},J.firstChild=function(a){for(var b=a.childNodes,c=0,d=b&&b.length||0,e;c=0;e=e-2)while(j=H[r[e+1]](j,c[f]))if(p=Q.apply(j,P(r[e])))break;p&&(d[g++]=c[f])}return d}function S(a,b,c){switch(a){case"=":return b==c;case"^=":return b.match(L.g("^="+c)||L.s("^="+c,new RegExp("^"+R(c))));case"$=":return b.match(L.g("$="+c)||L.s("$="+c,new RegExp(R(c)+"$")));case"*=":return b.match(L.g(c)||L.s(c,new RegExp(R(c))));case"~=":return b.match(L.g("~="+c)||L.s("~="+c,new RegExp("(?:^|\\s+)"+R(c)+"(?:\\s+|$)")));case"|=":return b.match(L.g("|="+c)||L.s("|="+c,new RegExp("^"+R(c)+"(-|$)")))}return 0}function R(a){return K.g(a)||K.s(a,a.replace(D,"\\$1"))}function Q(a,b,c,e,f,g,h){var j,k,l;if(b&&this.tagName.toLowerCase()!==b)return!1;if(c&&(j=c.match(u))&&j[1]!==this.id)return!1;if(c&&(q=c.match(v)))for(d=q.length;d--;){k=q[d].slice(1);if(!(J.g(k)||J.s(k,new RegExp("(^|\\s+)"+k+"(\\s+|$)"))).test(this.className))return!1}if(e&&!h){i=this.attributes;for(l in i)if(Object.prototype.hasOwnProperty.call(i,l)&&(i[l].name||l)==f)return this}if(e&&!S(g,this.getAttribute(f)||"",h))return!1;return this}function P(a){return a.match(G)}function O(a){while(a=a.previousSibling)if(a.nodeType==1)break;return a}function N(a){k=[];for(d=0,o=a.length;d])\s*/g,C=/([\s\>\+\~])(?![\s\w\-\/\?\&\=\:\.\(\)\!,@#%<>\{\}\$\*\^'"]*\])/,D=/([.*+?\^=!:${}()|\[\]\/\\])/g,E=/^([a-z0-9]+)?(?:([\.\#]+[\w\-\.#]+)?)/,F=/\[([\w\-]+)(?:([\|\^\$\*\~]?\=)['"]?([ \w\-\/\?\&\=\:\.\(\)\!,@#%<>\{\}\$\*\^]+)["']?)?\]/,G=new RegExp(E.source+"("+F.source+")?"),H={" ":function(a){return a&&a!==A&&a.parentNode},">":function(a,b){return a&&a.parentNode==b.parentNode&&a.parentNode},"~":function(a){return a&&a.previousSibling},"+":function(a,b,c,d){if(!a)return!1;c=O(a),d=O(b);return c&&d&&c==d&&c}};I.prototype={g:function(a){return this.c[a]||undefined},s:function(a,b){this.c[a]=b;return b}};var J=new I,K=new I,L=new I,M=new I,Y="compareDocumentPosition"in A?function(a,b){return(b.compareDocumentPosition(a)&16)==16}:"contains"in A?function(a,c){c=c==b||c==window?A:c;return c!==a&&c.contains(a)}:function(a,b){while(a=a.parentNode)if(a===b)return 1;return 0},Z=b.querySelector&&b.querySelectorAll?function(a,c){if(b.getElementsByClassName&&(h=a.match(x)))return N(c.getElementsByClassName(h[1]));return N(c.querySelectorAll(a))}:function(a,c){a=a.replace(B,"$1");var d=[],f,i=[],j;if(h=a.match(z)){s=c.getElementsByTagName(h[1]||"*"),k=J.g(h[2])||J.s(h[2],new RegExp("(^|\\s+)"+h[2]+"(\\s+|$)"));for(j=0,g=s.length,e=0;j]+)/.exec(b)[1],f=(c||a).createElement(d[e]||"div"),g=[];f.innerHTML=b;var h=f.childNodes;f=f.firstChild,g.push(f);while(f=f.nextSibling)f.nodeType==1&&g.push(f);return g}var b=qwery.noConflict(),c="table",d={thead:c,tbody:c,tfoot:c,tr:"tbody",th:"tr",td:"tr",fieldset:"form",option:"select"};$._select=function(a,c){return/^\s* or ? */ 48 | if (ender('body').offset().height > ender('html').offset().height) { 49 | var largerHeight = ender('body').offset().height; 50 | } 51 | else { 52 | var largerHeight = ender('html').offset().height; 53 | } 54 | 55 | /* Give guides the new height */ 56 | ender('.ggs-guide').each(function() { 57 | ender(this).css('height', largerHeight); 58 | }); 59 | 60 | /* Calculate the amount of lines needed and append them */ 61 | var lines = Math.floor(largerHeight/24); 62 | ender('#ggs-baseline-container').empty(); 63 | for (i=0; i<=lines; i++) { 64 | ender('#ggs-baseline-container').append('
    '); 65 | } 66 | 67 | /* Set the baseline container to the same height as the guides, so there's no overflow */ 68 | ender('#ggs-baseline-container').css('height', largerHeight); 69 | } 70 | } 71 | 72 | ender.domReady(function () { 73 | 74 | /* Add control classes and switch element */ 75 | ender('body').addClass('gg1s-hidden gg1s-animated').append('
    '); 76 | 77 | /* Create CSS */ 78 | var styles = '\ 79 | html{height:100%;position:relative;}\ 80 | #ggs-switch{position:fixed;top:0;right:0;z-index:9500; cursor:pointer; width: 24px; padding: 18px 18px 14px; opacity:'+switchOpacity+'; -webkit-transform: rotate(-90deg); -moz-transform: rotate(-90deg); -ms-transform: rotate(-90deg); transform: rotate(-90deg); -webkit-transition: all 0.145s ease-out; -moz-transition: all 0.145s ease-out; -ms-transition: all 0.145s ease-out; transition: all 0.145s ease-out;}\ 81 | .ggs-switchBar {background: '+switchColor+'; height: 4px; margin-bottom: 4px;}\ 82 | .ggs-animated #ggs-switch {-webkit-transform: rotate(0deg); -moz-transform: rotate(0deg); transform: rotate(0deg);}\ 83 | .ggs-guide{position:absolute;top:0;z-index:9000;height:100%;margin-left:-0.75em;border:solid '+guideColor+';border-width:0 0.75em;background:'+guideColor+';opacity:'+guideOpacity+'; -webkit-transition: all 0.235s ease-out; -moz-transition: all 0.235s ease-out; -ms-transition: all 0.235s ease-out; transition: all 0.235s ease-out;}\ 84 | .ggs-animated .ggs-guide {-webkit-transform: scale(0, 1); -moz-transform: scale(0, 1); -ms-transform: scale(0, 1); transform: scale(0, 1); opacity: 0;}\ 85 | .ggs-animated #ggs-baseline-container {opacity: 0;}\ 86 | .ggs-hidden .ggs-guide, .ggs-hidden #ggs-baseline-container {display: none;}\ 87 | .ggs-0{left:0;}\ 88 | .ggs-1{left:11.11111111111111%;}\ 89 | .ggs-2{left:16.666666666666664%;}\ 90 | .ggs-3{left:22.22222222222222%;}\ 91 | .ggs-4{left:27.77777777777778%;}\ 92 | .ggs-5{left:33.33333333333333%;}\ 93 | .ggs-6{left:38.888888888888886%;}\ 94 | .ggs-7{left:44.44444444444444%;}\ 95 | .ggs-8{left:50%;}\ 96 | .ggs-9{left:55.55555555555556%;}\ 97 | .ggs-10{left:61.11111111111111%;}\ 98 | .ggs-11{left:66.66666666666666%;}\ 99 | .ggs-12{left:72.22222222222221%;}\ 100 | .ggs-13{left:77.77777777777777%;}\ 101 | .ggs-14{left:83.33333333333333%;}\ 102 | .ggs-15{left:88.88888888888889%;}\ 103 | .ggs-16{right:0;}\ 104 | .ggs-0,.ggs-16{width:5.555555555555555%;padding-right:0.75em;border:0;margin:0;}\ 105 | .ggs-guide div{background:'+guideInnerColor+';width:2px;height:100%;position:absolute;left:-1px;top:0;}\ 106 | .ggs-0 div{left:auto;right:0.75em;}\ 107 | .ggs-16 div{left:0.75em;}\ 108 | #ggs-baseline-container {opacity: '+guideOpacity+'; position: absolute; left:0; top:0; z-index: 8000; width: 100%; height: 100%; -webkit-transition: opacity 0.235s ease-out; -moz-transition: opacity 0.235s ease-out; -ms-transition: opacity 0.235s ease-out; transition: opacity 0.235s ease-out; overflow-y: hidden;}\ 109 | .ggs-line {border-top: 1px dotted '+guideColor+'; height: '+baselineGridHeight+'; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; -o-box-sizing: border-box; box-sizing: border-box;}\ 110 | @media screen and (max-width: '+(eightColBreakpoint)+'){.ggs-2,.ggs-6,.ggs-10,.ggs-14{display:none;}}\ 111 | @media screen and (max-width: '+(sixteenColBreakpoint)+'){.ggs-1,.ggs-3,.ggs-5,.ggs-7,.ggs-9,.ggs-11,.ggs-13,.ggs-15{display:none;}}\ 112 | '; 113 | 114 | /* Create guides */ 115 | for (i=0; i<=16; i++) { 116 | ender('body').append(ender('
    ')); 117 | }; 118 | ender('body').append(ender('
    ')); 119 | 120 | /* Append CSS */ 121 | (function(d,u) { 122 | if(d.createStyleSheet) { 123 | d.createStyleSheet( u ); 124 | } 125 | else { 126 | var css=d.createElement('style'); 127 | css.setAttribute("type","text/css"); 128 | css.appendChild(document.createTextNode(u)); 129 | d.getElementsByTagName("head")[0].appendChild(css); 130 | } 131 | }(document, styles)) 132 | 133 | /* Resize guides when window size changes */ 134 | ender(window).on('resize', setHeights); 135 | 136 | /* Add listeners for switch element */ 137 | ender('#ggs-switch').click(function(){ 138 | if (ender('body').hasClass('ggs-hidden')) { 139 | ender('body').removeClass('ggs-hidden'); 140 | setHeights(); 141 | setTimeout( 142 | function () { 143 | ender('body').removeClass('ggs-animated'); 144 | }, 145 | 20 146 | ); 147 | } 148 | else { 149 | ender('body').addClass('ggs-animated'); 150 | setTimeout( 151 | function () { 152 | ender('body').addClass('ggs-hidden'); 153 | }, 154 | 300 155 | ); 156 | } 157 | }); 158 | 159 | }); --------------------------------------------------------------------------------