├── 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 |
6 | {{#each projects}}
7 | -
8 | {{name}}
9 |
10 | {{/each}}
11 |
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 |
6 | {{#projects}}
7 | -
8 | {{name}}
9 | Interactions
10 | delete
11 |
12 | {{/projects}}
13 |
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 |
6 | {{#interactions}}
7 | -
8 |
9 |
{{author}}:{{title}}
10 |
{{content}}
11 |
12 | {{source}}
13 | {{#if device}}
14 | {{device}}
15 | {{/if}}
16 | {{#if version}}
17 | {{version}}
18 | {{/if}}
19 |
20 |
21 |
22 |
{{author}}: {{title}} - {{content}}
23 |
24 |
25 | {{/interactions}}
26 |
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 |
21 |
22 |
23 |
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 |
Thanks for Subscribing!
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 |

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 | });
--------------------------------------------------------------------------------