├── .gitignore ├── LICENSE ├── README.md ├── app ├── config │ ├── dev.php │ └── prod.php ├── db_dump.sql └── views │ ├── admin_artists.html.twig │ ├── admin_comments.html.twig │ ├── admin_layout.html.twig │ ├── admin_users.html.twig │ ├── artist.html.twig │ ├── artists.html.twig │ ├── comments.html.twig │ ├── common │ └── form_div_layout.html.twig │ ├── form.html.twig │ ├── index.html.twig │ ├── layout.html.twig │ ├── login.html.twig │ ├── pagination.html.twig │ └── profile.html.twig ├── composer.json ├── composer.lock ├── src ├── MusicBox │ ├── Controller │ │ ├── AdminArtistController.php │ │ ├── AdminCommentController.php │ │ ├── AdminController.php │ │ ├── AdminUserController.php │ │ ├── ApiArtistController.php │ │ ├── ArtistController.php │ │ ├── IndexController.php │ │ └── UserController.php │ ├── Entity │ │ ├── Artist.php │ │ ├── Comment.php │ │ ├── Like.php │ │ └── User.php │ ├── Form │ │ └── Type │ │ │ ├── ArtistType.php │ │ │ ├── CommentType.php │ │ │ └── UserType.php │ ├── Repository │ │ ├── ArtistRepository.php │ │ ├── CommentRepository.php │ │ ├── LikeRepository.php │ │ ├── RepositoryInterface.php │ │ └── UserRepository.php │ └── Service │ │ └── SoundCloud.php ├── app.php └── routes.php └── web ├── .htaccess ├── css ├── bootstrap-responsive.css ├── bootstrap-responsive.min.css ├── bootstrap.css ├── bootstrap.min.css ├── docs.css └── main.css ├── font ├── FontAwesome.otf ├── fontawesome-webfont.eot ├── fontawesome-webfont.svg ├── fontawesome-webfont.ttf └── fontawesome-webfont.woff ├── ico ├── Thumbs.db ├── apple-touch-icon-114-precomposed.png ├── apple-touch-icon-144-precomposed.png ├── apple-touch-icon-57-precomposed.png ├── apple-touch-icon-72-precomposed.png └── favicon.ico ├── img ├── artists │ ├── 11.jpg │ ├── 12.jpg │ ├── 13.jpg │ ├── 14.jpg │ ├── 15.jpg │ ├── 16.jpg │ ├── 17.jpg │ ├── 18.jpg │ ├── 19.jpg │ ├── 2.jpg │ ├── 24.jpg │ ├── 27.jpeg │ └── placeholder.gif ├── heart hover.png ├── heart.png └── users │ └── placeholder.gif ├── index.php ├── js ├── .jshintrc ├── README.md ├── bootstrap-affix.js ├── bootstrap-alert.js ├── bootstrap-button.js ├── bootstrap-carousel.js ├── bootstrap-collapse.js ├── bootstrap-dropdown.js ├── bootstrap-modal.js ├── bootstrap-popover.js ├── bootstrap-scrollspy.js ├── bootstrap-tab.js ├── bootstrap-tooltip.js ├── bootstrap-transition.js ├── bootstrap-typeahead.js ├── bootstrap.js ├── bootstrap.min.js ├── google-code-prettify │ ├── prettify.css │ └── prettify.js ├── holder │ └── holder.js ├── html5shiv.js ├── jquery.js ├── main.js └── tests │ ├── index.html │ ├── phantom.js │ ├── server.js │ ├── unit │ ├── bootstrap-affix.js │ ├── bootstrap-alert.js │ ├── bootstrap-button.js │ ├── bootstrap-carousel.js │ ├── bootstrap-collapse.js │ ├── bootstrap-dropdown.js │ ├── bootstrap-modal.js │ ├── bootstrap-phantom.js │ ├── bootstrap-popover.js │ ├── bootstrap-scrollspy.js │ ├── bootstrap-tab.js │ ├── bootstrap-tooltip.js │ ├── bootstrap-transition.js │ └── bootstrap-typeahead.js │ └── vendor │ ├── jquery.js │ ├── qunit.css │ └── qunit.js ├── less ├── accordion.less ├── alerts.less ├── bootstrap.less ├── breadcrumbs.less ├── button-groups.less ├── buttons.less ├── carousel.less ├── close.less ├── code.less ├── component-animations.less ├── dropdowns.less ├── font-awesome │ ├── bootstrap.less │ ├── core.less │ ├── extras.less │ ├── font-awesome-ie7.less │ ├── font-awesome.less │ ├── icons.less │ ├── mixins.less │ ├── path.less │ └── variables.less ├── forms.less ├── grid.less ├── hero-unit.less ├── labels-badges.less ├── layouts.less ├── media.less ├── mixins.less ├── modals.less ├── navbar.less ├── navs.less ├── pager.less ├── pagination.less ├── popovers.less ├── progress-bars.less ├── reset.less ├── responsive-1200px-min.less ├── responsive-767px-max.less ├── responsive-768px-979px.less ├── responsive-navbar.less ├── responsive-utilities.less ├── responsive.less ├── scaffolding.less ├── sprites.less ├── tables.less ├── tests │ ├── buttons.html │ ├── css-tests.css │ ├── css-tests.html │ ├── forms-responsive.html │ ├── forms.html │ ├── navbar-fixed-top.html │ ├── navbar-static-top.html │ └── navbar.html ├── thumbnails.less ├── tooltip.less ├── type.less ├── utilities.less ├── variables.less └── wells.less └── scss ├── _accordion.scss ├── _alerts.scss ├── _breadcrumbs.scss ├── _button-groups.scss ├── _buttons.scss ├── _carousel.scss ├── _close.scss ├── _code.scss ├── _component-animations.scss ├── _dropdowns.scss ├── _font-awesome.scss ├── _forms.scss ├── _grid.scss ├── _hero-unit.scss ├── _labels-badges.scss ├── _layouts.scss ├── _media.scss ├── _mixins.scss ├── _modals.scss ├── _navbar.scss ├── _navs.scss ├── _pager.scss ├── _pagination.scss ├── _popovers.scss ├── _progress-bars.scss ├── _reset.scss ├── _responsive-1200px-min.scss ├── _responsive-767px-max.scss ├── _responsive-768px-979px.scss ├── _responsive-navbar.scss ├── _responsive-utilities.scss ├── _scaffolding.scss ├── _tables.scss ├── _thumbnails.scss ├── _tooltip.scss ├── _type.scss ├── _utilities.scss ├── _variables.scss ├── _wells.scss ├── bootstrap-responsive.scss ├── bootstrap.scss └── tests ├── bootstrap-responsive.css └── bootstrap.css /.gitignore: -------------------------------------------------------------------------------- 1 | # Bootstrap 2 | app/bootstrap* 3 | 4 | # Symfony directories 5 | vendor/* 6 | */logs/* 7 | */cache/* 8 | web/uploads/* 9 | web/bundles/* 10 | 11 | # Configuration files 12 | app/config/parameters.ini 13 | app/config/parameters.yml 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Bojan Zivanovic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Musicbox 2 | ======== 3 | MusicBox is a portal based on the Silex framework, built for PHP 5.3 and MySQL 5. 4 | It tries to demonstrate best practices in today's world of development, and has 5 | been written as a university project by Bojan Zivanovic and Lana Petkovic. 6 | 7 | Uses Doctrine DBAL for accessing the database, and Twig for its templates. 8 | Relies on SwiftMailer to send emails. 9 | The UI is based on Bootstrap 2. 10 | 11 | Getting started 12 | --------------- 13 | 1. Move the files 14 | Extract the contents of the "web" folder into your public html directory. 15 | Copy all other folders one level above. Your structure should look like this: 16 | 17 | - app 18 | - src 19 | - vendor 20 | - public_html (Your public html directory, and inside the contents of the "web" folder) 21 | 22 | Make sure that the img/artists and img/users directories in your public_html directory 23 | are writable. 24 | 25 | 2. Create a new empty database, and import the sql dump provided in app/db_dump.sql 26 | Open app/config/prod.php and under $app['db.options'] set your database credentials 27 | 28 | 3. MusicBox should now be functional. An admin user with the admin/admin username/password has been created for you. 29 | 30 | Application structure 31 | --------------------- 32 | 33 | - app 34 | -- cache - Used by Twig to save compiled templates 35 | -- config - Contains the dev.php and prod.php files with configuration data (db credentials, etc). 36 | -- views - Contains the Twig templates called by the controllers, including the main layout file. 37 | - src - Contains the main application code 38 | -- app.php - Called by index.php, initializes the app by registering libraries into the dependency injection container. 39 | -- routes.php - Called by index.php, routes map urls to controller classes. 40 | Each route has a name (such as "admin_artist_edit"), used to generate urls from the views and controllers. 41 | -- MusicBox - this folder contains the root of the namespace. All classes are nested below. 42 | --- Controller - Contains the controller classes. 43 | --- Entity - Contains the entities, POPO (plan old PHP objects) that represent the data that is manipulated by the repositories. 44 | An entity class has properties that mostly correspond to database columns, as well as related getters and setters. 45 | --- Form - Contains the Form classes used by the Symfony Form library to render each form. 46 | --- Repository - Contains classes that manipulate entity data by doing queries against the database. This includes all CRUD operations. 47 | - vendor - Contains the dependencies managed by composer. 48 | - web - Contains static files (CSS, JS, etc) as well as the main entrypoint (index.php) 49 | 50 | Application flow 51 | ---------------- 52 | index.php is called for all requests (directed by htaccess file). 53 | index.php includes the config file, and then app.php and routes.php. 54 | At this point, $app knows about all parts of the application, and the routes. 55 | index.php then calls $app->run(), which instantiates the correct controller for the current url. 56 | The controller manipulates data (creating, reading, updating, deleting Entities from their Repository), and passes it to a Twig template. 57 | Twig has template inheritance, so the template file inherits the main layout file in order to provide all of the page elements. 58 | 59 | The API 60 | ------- 61 | The application provides a REST API for managing artists. 62 | - GET api/artists - Provides a listing of artists. Possible parameters: limit (default: 20), offset. 63 | - GET api/artist - Retrieves a single artist. 64 | - POST api/artist - Creates a new artist. 65 | - PUT api/artist/{artist} - Updates a single artist. 66 | - DELETE api/artist/{artist} - Deletes a single artist. 67 | 68 | The output format is JSON. 69 | 70 | Emails 71 | ------ 72 | The application notifies the admin by email of every new posted comment. 73 | The sending is done in the ArtistController. 74 | The recepient email address is configured in the dev.php config file. 75 | 76 | Blog post: http://bojanz.wordpress.com/2013/11/11/learning-php-development-with-silex 77 | -------------------------------------------------------------------------------- /app/config/dev.php: -------------------------------------------------------------------------------- 1 | 'pdo_mysql', 19 | 'host' => '127.0.0.1', 20 | 'port' => '3306', 21 | 'dbname' => 'musicbox', 22 | 'user' => '', 23 | 'password' => '', 24 | ); 25 | // SwiftMailer 26 | // See http://silex.sensiolabs.org/doc/providers/swiftmailer.html 27 | $app['swiftmailer.options'] = array( 28 | 'host' => 'host', 29 | 'port' => '25', 30 | 'username' => 'username', 31 | 'password' => 'password', 32 | 'encryption' => null, 33 | 'auth_mode' => null 34 | ); 35 | -------------------------------------------------------------------------------- /app/views/admin_artists.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'admin_layout.html.twig' %} 2 | {% set active = 'admin' %} 3 | {% set adminActive = 'artists' %} 4 | 5 | {% block content %} 6 |
7 | {{ 'Add artist'|trans }} 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | {% for artist in artists %} 19 | 20 | 21 | 22 | 23 | 27 | 28 | {% else %} 29 | 30 | 31 | 32 | {% endfor %} 33 | 34 |
{{ 'Name'|trans }} {{ 'Likes'|trans }} {{ 'Created on'|trans }} {{ 'Operations'|trans }}
{{ artist.name|e }} {{ artist.likes }} {{ artist.createdAt|date('Y-m-d H:i') }} 24 | edit 25 | delete 26 |
{{ 'No artists have been created yet.'|trans }}
35 | 36 | {% include "pagination.html.twig" %} 37 |
38 | {% endblock %} 39 | -------------------------------------------------------------------------------- /app/views/admin_comments.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'admin_layout.html.twig' %} 2 | {% set active = 'admin' %} 3 | {% set adminActive = 'comments' %} 4 | 5 | {% block content %} 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | {% for comment in comments %} 20 | 21 | 22 | 23 | 24 | 25 | 26 | 43 | 44 | {% else %} 45 | 46 | 47 | 48 | {% endfor %} 49 | 50 |
{{ 'Artist'|trans }} {{ 'User'|trans }} {{ 'Comment'|trans }} {{ 'Published'|trans }} {{ 'Created on'|trans }} {{ 'Operations'|trans }}
{{ comment.artist.name|e }} {{ comment.user.username|e }} {{ comment.comment|e }} {% if comment.published %}Yes{% else %}No{% endif %} {{ comment.createdAt|date('Y-m-d H:i') }} 27 | {% if comment.published %} 28 | 29 | {{ 'unapprove'|trans }} 30 | 31 | {% else %} 32 | 33 | {{ 'approve'|trans }} 34 | 35 | {% endif %} 36 | 37 | {{ 'edit'|trans }} 38 | 39 | 40 | {{ 'delete'|trans }} 41 | 42 |
{{ 'No comments have been created yet.'|trans }}
51 | 52 | {% include "pagination.html.twig" %} 53 |
54 | {% endblock %} 55 | -------------------------------------------------------------------------------- /app/views/admin_layout.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html.twig' %} 2 | 3 | {% block secondaryNavigation %} 4 |
5 | 16 |
17 | {% endblock %} 18 | -------------------------------------------------------------------------------- /app/views/admin_users.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'admin_layout.html.twig' %} 2 | {% set active = 'admin' %} 3 | {% set adminActive = 'users' %} 4 | 5 | {% block content %} 6 |
7 | {{ 'Add user'|trans }} 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | {% for user in users %} 20 | 21 | 22 | 23 | 30 | 31 | 35 | 36 | {% else %} 37 | 38 | 39 | 40 | {% endfor %} 41 | 42 |
{{ 'Username'|trans }} {{ 'Mail'|trans }} {{ 'Role'|trans }} {{ 'Created on'|trans }} {{ 'Operations'|trans }}
{{ user.username|e }} {{ user.mail }} 24 | {% if user.role == 'ROLE_ADMIN' %} 25 | {{ 'Admin'|trans }} 26 | {% else %} 27 | {{ 'User'|trans }} 28 | {% endif %} 29 | {{ user.createdAt|date('Y-m-d H:i') }} 32 | edit 33 | delete 34 |
{{ 'No users have been created yet.'|trans }}
43 | 44 | {% include "pagination.html.twig" %} 45 |
46 | {% endblock %} 47 | -------------------------------------------------------------------------------- /app/views/artist.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html.twig' %} 2 | {% set active = 'artists' %} 3 | 4 | {% block content %} 5 | 6 |
7 |
8 |
9 |
10 |
11 |
12 | 13 |
14 |
15 |
{{ artist.name|e }}
16 |
17 | {{ artist.biography|nl2br }} 18 |
19 |
20 | {% if is_granted('ROLE_USER') %} 21 |
22 | 23 | 24 | 25 |
26 | {% endif %} 27 |
28 |
29 |
30 |
31 |
32 | 33 |
listen
34 |
35 |
36 | {{ soundcloudWidget|raw }} 37 |
38 |
39 |
comments
40 |
41 |
42 |
43 | 46 |
47 |
48 | {% for comment in comments %} 49 |
{{ comment.user.username }}
50 |
51 | {{ comment.comment|e }} 52 |
53 | {% else %} 54 | {{ 'No comments have been posted yet.'|trans }} 55 | {% endfor %} 56 |
57 |
58 |
59 |
60 | 61 |
62 |
63 | {% if newCommentForm %} 64 |
65 |
66 | {{ 'Leave your comment here'|trans }} 67 |
68 |
69 |
70 | {{ form_errors(newCommentForm.comment) }} 71 | {{ form_widget(newCommentForm.comment) }} 72 |
73 |
74 |
75 |
76 | {{ form_row(newCommentForm.save) }} 77 |
78 |
79 | {{ form_rest(newCommentForm) }} 80 |
81 | {% else %} 82 | Login to post comments. 83 | {% endif %} 84 |
85 | {% endblock %} 86 | -------------------------------------------------------------------------------- /app/views/artists.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html.twig' %} 2 | {% set active = 'artists' %} 3 | 4 | {% block content %} 5 |
6 | {% for artistGroup in groupedArtists %} 7 |
8 | {% for artist in artistGroup %} 9 |
10 | 11 |
12 |

{{ artist.name|e }}

13 | {{ artist.shortBiography|e }} 14 | 19 |
20 |
21 | {% endfor %} 22 |
23 | {% endfor %} 24 | 25 | {% include "pagination.html.twig" %} 26 |
27 | {% endblock %} 28 | -------------------------------------------------------------------------------- /app/views/comments.html.twig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bojanz/musicbox/f372461f9841175ce93c7d5ffc89a90390dd2a7e/app/views/comments.html.twig -------------------------------------------------------------------------------- /app/views/form.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html.twig' %} 2 | {% set active = 'form' %} 3 | 4 | {% block content %} 5 |
6 | {% set alertTypeAvaillable = [ 'info', 'success', 'warning', 'error'] %} 7 | {% for alert in alertTypeAvaillable %} 8 | {% for message in app.session.getFlashBag.get(alert) %} 9 |
10 | 11 | {{ message|trans }} 12 |
13 | {% endfor %} 14 | {% endfor %} 15 | 16 |

{{ title }}

17 | 18 |
19 | {{ form_widget(form) }} 20 |
21 |
22 | 23 | {% endblock %} 24 | -------------------------------------------------------------------------------- /app/views/index.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html.twig' %} 2 | {% set active = 'homepage' %} 3 | 4 | {% block content %} 5 |
6 |
7 | 15 | 16 |
17 |
18 | {% for likedArtistGroup in groupedLikedArtists %} 19 |
20 | {% for likedArtist in likedArtistGroup %} 21 |
22 |
23 | 24 | 25 | 26 |
27 |

{{ likedArtist.name|e }}

28 | {{ likedArtist.shortBiography|e }} 29 |
30 |
31 |
32 | {% endfor %} 33 |
34 | {% endfor %} 35 |
36 | 37 |
38 | {% for newestArtistGroup in groupedNewestArtists %} 39 |
40 | {% for newestArtist in newestArtistGroup %} 41 |
42 |
43 | 44 | 45 | 46 |
47 |

{{ newestArtist.name|e }}

48 | {{ newestArtist.shortBiography|e }} 49 |
50 |
51 |
52 | {% endfor %} 53 |
54 | {% endfor %} 55 |
56 |
57 | {% endblock %} 58 | -------------------------------------------------------------------------------- /app/views/layout.html.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ 'musicbox'|trans }} 5 | 6 | 7 | 8 | 9 | 10 | 11 | {% set active = active|default(null) %} 12 | 31 | 32 |
{{ 'musicbox'|trans }}
33 | 34 | {% block secondaryNavigation %} 35 | {% endblock %} 36 | {% block content %} 37 | {% endblock %} 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /app/views/login.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html.twig' %} 2 | {% set active = 'account' %} 3 | 4 | {% block content %} 5 |
6 |

{{ 'Login'|trans }}

7 | {% if error %} 8 |
9 | {{ error }} 10 |
11 | {% endif %} 12 | 13 |
14 | {{ form_widget(form) }} 15 |
16 |
17 | {% endblock %} 18 | -------------------------------------------------------------------------------- /app/views/pagination.html.twig: -------------------------------------------------------------------------------- 1 | {% if numPages > 1 %} 2 | 17 | {% endif %} 18 | -------------------------------------------------------------------------------- /app/views/profile.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html.twig' %} 2 | {% set active = 'homepage' %} 3 | 4 | {% block content %} 5 |
6 |
7 |
8 |
9 | 10 |
11 |
12 |

{{ user.username|e }}

13 |
14 |
{{ 'email:'|e}}
15 |
{{ user.mail|e }}
16 |
{{ 'member for:'|e }}
17 |
{{ memberSince }}
18 |
19 |
20 |
21 |
22 |
23 |
{{ 'I think these artists are amazing:'|e }}
24 |
25 | {% for likeGroup in groupedLikes %} 26 |
27 | {% for like in likeGroup %} 28 | 34 | {% endfor %} 35 |
36 | {% endfor %} 37 |
38 | {% endblock %} 39 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "doctrine/dbal": "~2.3", 4 | "twig/twig": "1.*", 5 | "silex/silex": "~1.1", 6 | "symfony/config": "~2.3", 7 | "symfony/form": "~2.3", 8 | "symfony/locale": "~2.3", 9 | "symfony/security": "~2.3", 10 | "symfony/translation": "~2.3", 11 | "symfony/twig-bridge": "~2.3", 12 | "symfony/validator": "~2.3", 13 | "swiftmailer/swiftmailer": "~5.0" 14 | }, 15 | "require-dev": { 16 | "symfony/browser-kit" : "~2.3", 17 | "symfony/css-selector" : "~2.3", 18 | "symfony/dom-crawler" : "~2.3" 19 | }, 20 | "autoload": { 21 | "psr-0": {"MusicBox": "src/"} 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/MusicBox/Controller/AdminArtistController.php: -------------------------------------------------------------------------------- 1 | getCount(); 18 | $numPages = ceil($total / $limit); 19 | $currentPage = $request->query->get('page', 1); 20 | $offset = ($currentPage - 1) * $limit; 21 | $artists = $app['repository.artist']->findAll($limit, $offset); 22 | 23 | $data = array( 24 | 'artists' => $artists, 25 | 'currentPage' => $currentPage, 26 | 'numPages' => $numPages, 27 | 'here' => $app['url_generator']->generate('admin_artists'), 28 | ); 29 | return $app['twig']->render('admin_artists.html.twig', $data); 30 | } 31 | 32 | public function addAction(Request $request, Application $app) 33 | { 34 | $artist = new Artist(); 35 | $form = $app['form.factory']->create(new ArtistType(), $artist); 36 | 37 | if ($request->isMethod('POST')) { 38 | $form->bind($request); 39 | if ($form->isValid()) { 40 | $app['repository.artist']->save($artist); 41 | $message = 'The artist ' . $artist->getName() . ' has been saved.'; 42 | $app['session']->getFlashBag()->add('success', $message); 43 | // Redirect to the edit page. 44 | $redirect = $app['url_generator']->generate('admin_artist_edit', array('artist' => $artist->getId())); 45 | return $app->redirect($redirect); 46 | } 47 | } 48 | 49 | $data = array( 50 | 'form' => $form->createView(), 51 | 'title' => 'Add new artist', 52 | ); 53 | return $app['twig']->render('form.html.twig', $data); 54 | } 55 | 56 | public function editAction(Request $request, Application $app) 57 | { 58 | $artist = $request->attributes->get('artist'); 59 | if (!$artist) { 60 | $app->abort(404, 'The requested artist was not found.'); 61 | } 62 | $form = $app['form.factory']->create(new ArtistType(), $artist); 63 | 64 | if ($request->isMethod('POST')) { 65 | $form->bind($request); 66 | if ($form->isValid()) { 67 | $app['repository.artist']->save($artist); 68 | $message = 'The artist ' . $artist->getName() . ' has been saved.'; 69 | $app['session']->getFlashBag()->add('success', $message); 70 | } 71 | } 72 | 73 | $data = array( 74 | 'form' => $form->createView(), 75 | 'title' => 'Edit artist ' . $artist->getName(), 76 | ); 77 | return $app['twig']->render('form.html.twig', $data); 78 | } 79 | 80 | public function deleteAction(Request $request, Application $app) 81 | { 82 | $artist = $request->attributes->get('artist'); 83 | if (!$artist) { 84 | $app->abort(404, 'The requested artist was not found.'); 85 | } 86 | 87 | $app['repository.artist']->delete($artist); 88 | return $app->redirect($app['url_generator']->generate('admin_artists')); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/MusicBox/Controller/AdminCommentController.php: -------------------------------------------------------------------------------- 1 | getCount(); 18 | $numPages = ceil($total / $limit); 19 | $currentPage = $request->query->get('page', 1); 20 | $offset = ($currentPage - 1) * $limit; 21 | $comments = $app['repository.comment']->findAll($limit, $offset); 22 | 23 | $data = array( 24 | 'comments' => $comments, 25 | 'currentPage' => $currentPage, 26 | 'numPages' => $numPages, 27 | 'here' => $app['url_generator']->generate('admin_comments'), 28 | ); 29 | return $app['twig']->render('admin_comments.html.twig', $data); 30 | } 31 | 32 | public function editAction(Request $request, Application $app) 33 | { 34 | $comment = $request->attributes->get('comment'); 35 | if (!$comment) { 36 | $app->abort(404, 'The requested comment was not found.'); 37 | } 38 | $form = $app['form.factory']->create(new CommentType(), $comment); 39 | 40 | if ($request->isMethod('POST')) { 41 | $form->bind($request); 42 | if ($form->isValid()) { 43 | $app['repository.comment']->save($comment); 44 | $message = 'The comment has been saved.'; 45 | $app['session']->getFlashBag()->add('success', $message); 46 | } 47 | } 48 | 49 | $data = array( 50 | 'form' => $form->createView(), 51 | 'title' => 'Edit comment', 52 | ); 53 | return $app['twig']->render('form.html.twig', $data); 54 | } 55 | 56 | public function deleteAction(Request $request, Application $app) 57 | { 58 | $comment = $request->attributes->get('comment'); 59 | if (!$comment) { 60 | $app->abort(404, 'The requested comment was not found.'); 61 | } 62 | 63 | $app['repository.comment']->delete($comment->getId()); 64 | return $app->redirect($app['url_generator']->generate('admin_comments')); 65 | } 66 | 67 | public function approveAction(Request $request, Application $app) 68 | { 69 | $comment = $request->attributes->get('comment'); 70 | if (!$comment) { 71 | $app->abort(404, 'The requested comment was not found.'); 72 | } 73 | 74 | $app['repository.comment']->delete($comment->getId()); 75 | return $app->redirect($app['url_generator']->generate('admin_comments')); 76 | } 77 | 78 | public function unapproveAction(Request $request, Application $app) 79 | { 80 | $comment = $request->attributes->get('comment'); 81 | if (!$comment) { 82 | $app->abort(404, 'The requested comment was not found.'); 83 | } 84 | 85 | $app['repository.comment']->delete($comment->getId()); 86 | return $app->redirect($app['url_generator']->generate('admin_comments')); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/MusicBox/Controller/AdminController.php: -------------------------------------------------------------------------------- 1 | redirect($app['url_generator']->generate('admin_artists')); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/MusicBox/Controller/AdminUserController.php: -------------------------------------------------------------------------------- 1 | getCount(); 18 | $numPages = ceil($total / $limit); 19 | $currentPage = $request->query->get('page', 1); 20 | $offset = ($currentPage - 1) * $limit; 21 | $users = $app['repository.user']->findAll($limit, $offset); 22 | 23 | $data = array( 24 | 'users' => $users, 25 | 'currentPage' => $currentPage, 26 | 'numPages' => $numPages, 27 | 'here' => $app['url_generator']->generate('admin_users'), 28 | ); 29 | return $app['twig']->render('admin_users.html.twig', $data); 30 | } 31 | 32 | public function addAction(Request $request, Application $app) 33 | { 34 | $user = new User(); 35 | $form = $app['form.factory']->create(new UserType(), $user); 36 | 37 | if ($request->isMethod('POST')) { 38 | $form->bind($request); 39 | if ($form->isValid()) { 40 | $app['repository.user']->save($user); 41 | $message = 'The user ' . $user->getUsername() . ' has been saved.'; 42 | $app['session']->getFlashBag()->add('success', $message); 43 | // Redirect to the edit page. 44 | $redirect = $app['url_generator']->generate('admin_user_edit', array('user' => $user->getId())); 45 | return $app->redirect($redirect); 46 | } 47 | } 48 | 49 | $data = array( 50 | 'form' => $form->createView(), 51 | 'title' => 'Add new user', 52 | ); 53 | return $app['twig']->render('form.html.twig', $data); 54 | } 55 | 56 | public function editAction(Request $request, Application $app) 57 | { 58 | $user = $request->attributes->get('user'); 59 | if (!$user) { 60 | $app->abort(404, 'The requested user was not found.'); 61 | } 62 | $form = $app['form.factory']->create(new UserType(), $user); 63 | 64 | if ($request->isMethod('POST')) { 65 | $previousPassword = $user->getPassword(); 66 | $form->bind($request); 67 | if ($form->isValid()) { 68 | // If an empty password was entered, restore the previous one. 69 | $password = $user->getPassword(); 70 | if (!$password) { 71 | $user->setPassword($previousPassword); 72 | } 73 | 74 | $app['repository.user']->save($user); 75 | $message = 'The user ' . $user->getUsername() . ' has been saved.'; 76 | $app['session']->getFlashBag()->add('success', $message); 77 | } 78 | } 79 | 80 | $data = array( 81 | 'form' => $form->createView(), 82 | 'title' => 'Edit user ' . $user->getUsername(), 83 | ); 84 | return $app['twig']->render('form.html.twig', $data); 85 | } 86 | 87 | public function deleteAction(Request $request, Application $app) 88 | { 89 | $user = $request->attributes->get('user'); 90 | if (!$user) { 91 | $app->abort(404, 'The requested user was not found.'); 92 | } 93 | 94 | $app['repository.user']->delete($user->getId()); 95 | return $app->redirect($app['url_generator']->generate('admin_users')); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/MusicBox/Controller/ApiArtistController.php: -------------------------------------------------------------------------------- 1 | query->get('limit', 20); 15 | $offset = $request->query->get('offset', 0); 16 | $artists = $app['repository.artist']->findAll($limit, $offset); 17 | $data = array(); 18 | foreach ($artists as $artist) { 19 | $data[] = array( 20 | 'id' => $artist->getId(), 21 | 'name' => $artist->getName(), 22 | 'short_biography' => $artist->getShortBiography(), 23 | 'biography' => $artist->getBiography(), 24 | 'soundcloud_url' => $artist->getSoundCloudUrl(), 25 | 'likes' => $artist->getLikes(), 26 | ); 27 | } 28 | 29 | return $app->json($data); 30 | } 31 | 32 | public function viewAction(Request $request, Application $app) 33 | { 34 | $artist = $request->attributes->get('artist'); 35 | if (!$artist) { 36 | return $app->json('Not Found', 404); 37 | } 38 | $data = array( 39 | 'id' => $artist->getId(), 40 | 'name' => $artist->getName(), 41 | 'short_biography' => $artist->getShortBiography(), 42 | 'biography' => $artist->getBiography(), 43 | 'soundcloud_url' => $artist->getSoundCloudUrl(), 44 | 'likes' => $artist->getLikes(), 45 | ); 46 | 47 | return $app->json($data); 48 | } 49 | 50 | public function addAction(Request $request, Application $app) 51 | { 52 | if (!$request->request->has('name')) { 53 | return $app->json('Missing required parameter: name', 400); 54 | } 55 | if (!$request->request->has('short_biography')) { 56 | return $app->json('Missing required parameter: short_biography', 400); 57 | } 58 | 59 | $artist = new Artist(); 60 | $artist->setName($request->request->get('name')); 61 | $artist->setShortBiography($request->request->get('short_biography')); 62 | $artist->setBiography($request->request->get('biography')); 63 | $artist->setSoundCloudUrl($request->request->get('soundcloud_url')); 64 | $app['repository.artist']->save($artist); 65 | 66 | $headers = array('Location' => '/api/artist/' . $artist->getId()); 67 | return $app->json('Created', 201, $headers); 68 | } 69 | 70 | public function editAction(Request $request, Application $app) 71 | { 72 | $artist = $request->attributes->get('artist'); 73 | if (!$artist) { 74 | return $app->json('Not Found', 404); 75 | } 76 | if (!$request->request->has('name')) { 77 | return $app->json('Missing required parameter: name', 400); 78 | } 79 | if (!$request->request->has('short_biography')) { 80 | return $app->json('Missing required parameter: short_biography', 400); 81 | } 82 | $artist->setName($request->request->get('name')); 83 | $artist->setShortBiography($request->request->get('short_biography')); 84 | $artist->setBiography($request->request->get('biography')); 85 | $artist->setSoundCloudUrl($request->request->get('soundcloud_url')); 86 | $app['repository.artist']->save($artist); 87 | 88 | return $app->json('OK', 200); 89 | } 90 | 91 | public function deleteAction(Request $request, Application $app) 92 | { 93 | $artist = $request->attributes->get('artist'); 94 | if (!$artist) { 95 | return $app->json('Not Found', 404); 96 | } 97 | $app['repository.artist']->delete($artist); 98 | 99 | return $app->json('No Content', 204); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/MusicBox/Controller/IndexController.php: -------------------------------------------------------------------------------- 1 | 'DESC'); 17 | $newestOrderBy = array('created_at' => 'DESC'); 18 | $likedArtists = $app['repository.artist']->findAll($limit, $offset, $likedOrderBy); 19 | $newestArtists = $app['repository.artist']->findAll($limit, $offset, $newestOrderBy); 20 | // Divide artists into groups of 2. 21 | $groupSize = 2; 22 | $groupedLikedArtists = array(); 23 | $groupedNewestArtists = array(); 24 | $progress = 0; 25 | while ($progress < $limit) { 26 | $groupedLikedArtists[] = array_slice($likedArtists, $progress, $groupSize); 27 | $groupedNewestArtists[] = array_slice($newestArtists, $progress, $groupSize); 28 | $progress += $groupSize; 29 | } 30 | 31 | $data = array( 32 | 'groupedLikedArtists' => $groupedLikedArtists, 33 | 'groupedNewestArtists' => $groupedNewestArtists, 34 | ); 35 | return $app['twig']->render('index.html.twig', $data); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/MusicBox/Controller/UserController.php: -------------------------------------------------------------------------------- 1 | getToken(); 15 | $user = $token->getUser(); 16 | $now = new \DateTime(); 17 | $interval = $now->diff($user->getCreatedAt()); 18 | $memberSince = $interval->format('%d days %H hours %I minutes ago'); 19 | $limit = 60; 20 | $likes = $app['repository.like']->findAllByUser($user->getId(), $limit); 21 | // Divide artists into groups of 6. 22 | $groupSize = 6; 23 | $groupedLikes = array(); 24 | $progress = 0; 25 | while ($progress < $limit) { 26 | $groupedLikes[] = array_slice($likes, $progress, $groupSize); 27 | $progress += $groupSize; 28 | } 29 | 30 | $data = array( 31 | 'user' => $user, 32 | 'memberSince' => $memberSince, 33 | 'groupedLikes' => $groupedLikes, 34 | ); 35 | return $app['twig']->render('profile.html.twig', $data); 36 | } 37 | 38 | public function loginAction(Request $request, Application $app) 39 | { 40 | $form = $app['form.factory']->createBuilder('form') 41 | ->add('username', 'text', array('label' => 'Username', 'data' => $app['session']->get('_security.last_username'))) 42 | ->add('password', 'password', array('label' => 'Password')) 43 | ->add('login', 'submit') 44 | ->getForm(); 45 | 46 | $data = array( 47 | 'form' => $form->createView(), 48 | 'error' => $app['security.last_error']($request), 49 | ); 50 | return $app['twig']->render('login.html.twig', $data); 51 | } 52 | 53 | public function logoutAction(Request $request, Application $app) 54 | { 55 | $app['session']->clear(); 56 | return $app->redirect($app['url_generator']->generate('homepage')); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/MusicBox/Entity/Artist.php: -------------------------------------------------------------------------------- 1 | image stores the filename after the file gets moved to "images/". 69 | * 70 | * @var \Symfony\Component\HttpFoundation\File\UploadedFile 71 | */ 72 | protected $file; 73 | 74 | public function getId() 75 | { 76 | return $this->id; 77 | } 78 | 79 | public function setId($id) 80 | { 81 | $this->id = $id; 82 | } 83 | 84 | public function getName() 85 | { 86 | return $this->name; 87 | } 88 | 89 | public function setName($name) 90 | { 91 | $this->name = $name; 92 | } 93 | 94 | public function getShortBiography() 95 | { 96 | return $this->shortBiography; 97 | } 98 | 99 | public function setShortBiography($shortBiography) 100 | { 101 | $this->shortBiography = $shortBiography; 102 | } 103 | 104 | public function getBiography() 105 | { 106 | return $this->biography; 107 | } 108 | 109 | public function setBiography($biography) 110 | { 111 | $this->biography = $biography; 112 | } 113 | 114 | public function getSoundCloudUrl() 115 | { 116 | return $this->soundCloudUrl; 117 | } 118 | 119 | public function setSoundCloudUrl($soundCloudUrl) 120 | { 121 | $this->soundCloudUrl = $soundCloudUrl; 122 | } 123 | 124 | public function getLikes() 125 | { 126 | return $this->likes; 127 | } 128 | 129 | public function setLikes($likes) 130 | { 131 | $this->likes = $likes; 132 | } 133 | 134 | public function getImage() { 135 | // Make sure the image is never empty. 136 | if (empty($this->image)) { 137 | $this->image = 'placeholder.gif'; 138 | } 139 | 140 | return $this->image; 141 | } 142 | 143 | public function setImage($image) { 144 | $this->image = $image; 145 | } 146 | 147 | public function getCreatedAt() 148 | { 149 | return $this->createdAt; 150 | } 151 | 152 | public function setCreatedAt(\DateTime $createdAt) 153 | { 154 | $this->createdAt = $createdAt; 155 | } 156 | 157 | public function getFile() { 158 | return $this->file; 159 | } 160 | 161 | public function setFile(UploadedFile $file = null) 162 | { 163 | $this->file = $file; 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/MusicBox/Entity/Comment.php: -------------------------------------------------------------------------------- 1 | id; 52 | } 53 | 54 | public function setId($id) 55 | { 56 | $this->id = $id; 57 | } 58 | 59 | public function getArtist() 60 | { 61 | return $this->artist; 62 | } 63 | 64 | public function setArtist($artist) 65 | { 66 | $this->artist = $artist; 67 | } 68 | 69 | public function getUser() 70 | { 71 | return $this->user; 72 | } 73 | 74 | public function setUser($user) 75 | { 76 | $this->user = $user; 77 | } 78 | 79 | public function getComment() 80 | { 81 | return $this->comment; 82 | } 83 | 84 | public function setComment($comment) 85 | { 86 | $this->comment = $comment; 87 | } 88 | 89 | public function getPublished() 90 | { 91 | return $this->published; 92 | } 93 | 94 | public function setPublished($published) 95 | { 96 | $this->published = $published; 97 | } 98 | 99 | public function getCreatedAt() 100 | { 101 | return $this->createdAt; 102 | } 103 | 104 | public function setCreatedAt(\DateTime $createdAt) 105 | { 106 | $this->createdAt = $createdAt; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/MusicBox/Entity/Like.php: -------------------------------------------------------------------------------- 1 | id; 38 | } 39 | 40 | public function setId($id) 41 | { 42 | $this->id = $id; 43 | } 44 | 45 | public function getArtist() 46 | { 47 | return $this->artist; 48 | } 49 | 50 | public function setArtist($artist) 51 | { 52 | $this->artist = $artist; 53 | } 54 | 55 | public function getUser() 56 | { 57 | return $this->user; 58 | } 59 | 60 | public function setUser($user) 61 | { 62 | $this->user = $user; 63 | } 64 | 65 | public function getCreatedAt() 66 | { 67 | return $this->createdAt; 68 | } 69 | 70 | public function setCreatedAt(\DateTime $createdAt) 71 | { 72 | $this->createdAt = $createdAt; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/MusicBox/Entity/User.php: -------------------------------------------------------------------------------- 1 | image stores the filename after the file gets moved to "images/". 72 | * 73 | * @var \Symfony\Component\HttpFoundation\File\UploadedFile 74 | */ 75 | protected $file; 76 | 77 | public function getId() 78 | { 79 | return $this->id; 80 | } 81 | 82 | public function setId($id) 83 | { 84 | $this->id = $id; 85 | } 86 | 87 | /** 88 | * @inheritDoc 89 | */ 90 | public function getUsername() 91 | { 92 | return $this->username; 93 | } 94 | 95 | public function setUsername($username) 96 | { 97 | $this->username = $username; 98 | } 99 | 100 | /** 101 | * @inheritDoc 102 | */ 103 | public function getSalt() 104 | { 105 | return $this->salt; 106 | } 107 | 108 | public function setSalt($salt) 109 | { 110 | $this->salt = $salt; 111 | } 112 | 113 | /** 114 | * @inheritDoc 115 | */ 116 | public function getPassword() 117 | { 118 | return $this->password; 119 | } 120 | 121 | public function setPassword($password) 122 | { 123 | $this->password = $password; 124 | } 125 | 126 | public function getMail() 127 | { 128 | return $this->mail; 129 | } 130 | 131 | public function setMail($mail) 132 | { 133 | $this->mail = $mail; 134 | } 135 | 136 | public function getImage() { 137 | // Make sure the image is never empty. 138 | if (empty($this->image)) { 139 | $this->image = 'placeholder.gif'; 140 | } 141 | 142 | return $this->image; 143 | } 144 | 145 | public function setImage($image) { 146 | $this->image = $image; 147 | } 148 | 149 | public function getCreatedAt() 150 | { 151 | return $this->createdAt; 152 | } 153 | 154 | public function setCreatedAt(\DateTime $createdAt) 155 | { 156 | $this->createdAt = $createdAt; 157 | } 158 | 159 | public function getFile() { 160 | return $this->file; 161 | } 162 | 163 | public function setFile(UploadedFile $file = null) 164 | { 165 | $this->file = $file; 166 | } 167 | 168 | /** 169 | * @inheritDoc 170 | */ 171 | public function getRoles() 172 | { 173 | return array($this->getRole()); 174 | } 175 | 176 | public function getRole() 177 | { 178 | return $this->role; 179 | } 180 | 181 | public function setRole($role) { 182 | $this->role = $role; 183 | } 184 | 185 | /** 186 | * @inheritDoc 187 | */ 188 | public function eraseCredentials() 189 | { 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/MusicBox/Form/Type/ArtistType.php: -------------------------------------------------------------------------------- 1 | add('name', 'text', array( 16 | 'constraints' => new Assert\NotBlank(), 17 | )) 18 | ->add('shortBiography', 'textarea', array( 19 | 'attr' => array( 20 | 'rows' => '7', 21 | ) 22 | )) 23 | ->add('biography', 'textarea', array( 24 | 'attr' => array( 25 | 'rows' => '15', 26 | ) 27 | )) 28 | ->add('soundCloudUrl', 'url') 29 | ->add('file', 'file', array( 30 | 'required' => FALSE, 31 | 'label' => 'Image', 32 | )) 33 | ->add('save', 'submit'); 34 | } 35 | 36 | public function getName() 37 | { 38 | return 'artist'; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/MusicBox/Form/Type/CommentType.php: -------------------------------------------------------------------------------- 1 | add('comment', 'textarea', array( 15 | 'label' => '', 16 | 'attr' => array( 17 | 'rows' => '7', 18 | ), 19 | 'constraints' => new Assert\NotBlank(), 20 | )) 21 | ->add('save', 'submit', array( 22 | 'label' => 'Post Comment', 23 | 'attr' => array('class' => 'btn btn-inverse'), 24 | )); 25 | } 26 | 27 | public function getName() 28 | { 29 | return 'comment'; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/MusicBox/Form/Type/UserType.php: -------------------------------------------------------------------------------- 1 | add('username', 'text', array( 15 | 'constraints' => new Assert\NotBlank(), 16 | )) 17 | ->add('password', 'repeated', array( 18 | 'type' => 'password', 19 | 'invalid_message' => 'The password fields must match.', 20 | 'options' => array('required' => false), 21 | 'first_options' => array('label' => 'Password'), 22 | 'second_options' => array('label' => 'Repeat Password'), 23 | 'required' => FALSE, 24 | )) 25 | ->add('mail', 'email', array( 26 | 'constraints' => array(new Assert\NotBlank(), new Assert\Email()), 27 | )) 28 | ->add('file', 'file', array( 29 | 'required' => FALSE, 30 | 'label' => 'Image', 31 | )) 32 | ->add('role', 'choice', array( 33 | 'choices' => array('ROLE_USER' => 'User', 'ROLE_ADMIN' => 'Admin') 34 | )) 35 | ->add('save', 'submit'); 36 | } 37 | 38 | public function getName() 39 | { 40 | return 'user'; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/MusicBox/Repository/RepositoryInterface.php: -------------------------------------------------------------------------------- 1 | $direction format. 55 | * 56 | * @return array A collection of entity objects. 57 | */ 58 | public function findAll($limit, $offset = 0, $orderBy = array()); 59 | } 60 | -------------------------------------------------------------------------------- /src/MusicBox/Service/SoundCloud.php: -------------------------------------------------------------------------------- 1 | register(new Silex\Provider\DoctrineServiceProvider()); 9 | $app->register(new Silex\Provider\FormServiceProvider()); 10 | $app->register(new Silex\Provider\SessionServiceProvider()); 11 | $app->register(new Silex\Provider\ValidatorServiceProvider()); 12 | $app->register(new Silex\Provider\UrlGeneratorServiceProvider()); 13 | $app->register(new Silex\Provider\TranslationServiceProvider()); 14 | $app->register(new Silex\Provider\SwiftmailerServiceProvider()); 15 | 16 | $app->register(new Silex\Provider\SecurityServiceProvider(), array( 17 | 'security.firewalls' => array( 18 | 'admin' => array( 19 | 'pattern' => '^/', 20 | 'form' => array( 21 | 'login_path' => '/login', 22 | 'check_path' => '/admin/login_check', 23 | 'username_parameter' => 'form[username]', 24 | 'password_parameter' => 'form[password]', 25 | ), 26 | 'logout' => true, 27 | 'anonymous' => true, 28 | 'users' => $app->share(function () use ($app) { 29 | return new MusicBox\Repository\UserRepository($app['db'], $app['security.encoder.digest']); 30 | }), 31 | ), 32 | ), 33 | 'security.role_hierarchy' => array( 34 | 'ROLE_ADMIN' => array('ROLE_USER'), 35 | ), 36 | )); 37 | $app->register(new Silex\Provider\TwigServiceProvider(), array( 38 | 'twig.options' => array( 39 | 'cache' => isset($app['twig.options.cache']) ? $app['twig.options.cache'] : false, 40 | 'strict_variables' => true, 41 | ), 42 | 'twig.form.templates' => array('form_div_layout.html.twig', 'common/form_div_layout.html.twig'), 43 | 'twig.path' => array(__DIR__ . '/../app/views') 44 | )); 45 | 46 | // Register repositories. 47 | $app['repository.artist'] = $app->share(function ($app) { 48 | return new MusicBox\Repository\ArtistRepository($app['db']); 49 | }); 50 | $app['repository.user'] = $app->share(function ($app) { 51 | return new MusicBox\Repository\UserRepository($app['db'], $app['security.encoder.digest']); 52 | }); 53 | $app['repository.comment'] = $app->share(function ($app) { 54 | return new MusicBox\Repository\CommentRepository($app['db'], $app['repository.artist'], $app['repository.user']); 55 | }); 56 | $app['repository.like'] = $app->share(function ($app) { 57 | return new MusicBox\Repository\LikeRepository($app['db'], $app['repository.artist'], $app['repository.user']); 58 | }); 59 | // Register custom services. 60 | $app['soundcloud'] = $app->share(function ($app) { 61 | return new MusicBox\Service\SoundCloud(); 62 | }); 63 | 64 | // Protect admin urls. 65 | $app->before(function (Request $request) use ($app) { 66 | $protected = array( 67 | '/admin/' => 'ROLE_ADMIN', 68 | '/me' => 'ROLE_USER', 69 | ); 70 | $path = $request->getPathInfo(); 71 | foreach ($protected as $protectedPath => $role) { 72 | if (strpos($path, $protectedPath) !== FALSE && !$app['security']->isGranted($role)) { 73 | throw new AccessDeniedException(); 74 | } 75 | } 76 | }); 77 | 78 | // Register the error handler. 79 | $app->error(function (\Exception $e, $code) use ($app) { 80 | if ($app['debug']) { 81 | return; 82 | } 83 | 84 | switch ($code) { 85 | case 404: 86 | $message = 'The requested page could not be found.'; 87 | break; 88 | default: 89 | $message = 'We are sorry, but something went terribly wrong.'; 90 | } 91 | 92 | return new Response($message, $code); 93 | }); 94 | 95 | return $app; 96 | -------------------------------------------------------------------------------- /src/routes.php: -------------------------------------------------------------------------------- 1 | convert('artist', function ($id) use ($app) { 7 | if ($id) { 8 | return $app['repository.artist']->find($id); 9 | } 10 | }); 11 | $app['controllers']->convert('comment', function ($id) use ($app) { 12 | if ($id) { 13 | return $app['repository.comment']->find($id); 14 | } 15 | }); 16 | $app['controllers']->convert('user', function ($id) use ($app) { 17 | if ($id) { 18 | return $app['repository.user']->find($id); 19 | } 20 | }); 21 | 22 | // Register routes. 23 | $app->get('/', 'MusicBox\Controller\IndexController::indexAction') 24 | ->bind('homepage'); 25 | 26 | $app->get('/me', 'MusicBox\Controller\UserController::meAction') 27 | ->bind('me'); 28 | $app->match('/login', 'MusicBox\Controller\UserController::loginAction') 29 | ->bind('login'); 30 | $app->get('/logout', 'MusicBox\Controller\UserController::logoutAction') 31 | ->bind('logout'); 32 | 33 | $app->get('/artists', 'MusicBox\Controller\ArtistController::indexAction') 34 | ->bind('artists'); 35 | $app->match('/artist/{artist}', 'MusicBox\Controller\ArtistController::viewAction') 36 | ->bind('artist'); 37 | $app->match('/artist/{artist}/like', 'MusicBox\Controller\ArtistController::likeAction') 38 | ->bind('artist_like'); 39 | $app->get('/api/artists', 'MusicBox\Controller\ApiArtistController::indexAction'); 40 | $app->get('/api/artist/{artist}', 'MusicBox\Controller\ApiArtistController::viewAction'); 41 | $app->post('/api/artist', 'MusicBox\Controller\ApiArtistController::addAction'); 42 | $app->put('/api/artist/{artist}', 'MusicBox\Controller\ApiArtistController::editAction'); 43 | $app->delete('/api/artist/{artist}', 'MusicBox\Controller\ApiArtistController::deleteAction'); 44 | 45 | $app->get('/admin', 'MusicBox\Controller\AdminController::indexAction') 46 | ->bind('admin'); 47 | 48 | $app->get('/admin/artists', 'MusicBox\Controller\AdminArtistController::indexAction') 49 | ->bind('admin_artists'); 50 | $app->match('/admin/artists/add', 'MusicBox\Controller\AdminArtistController::addAction') 51 | ->bind('admin_artist_add'); 52 | $app->match('/admin/artists/{artist}/edit', 'MusicBox\Controller\AdminArtistController::editAction') 53 | ->bind('admin_artist_edit'); 54 | $app->match('/admin/artists/{artist}/delete', 'MusicBox\Controller\AdminArtistController::deleteAction') 55 | ->bind('admin_artist_delete'); 56 | 57 | $app->get('/admin/users', 'MusicBox\Controller\AdminUserController::indexAction') 58 | ->bind('admin_users'); 59 | $app->match('/admin/users/add', 'MusicBox\Controller\AdminUserController::addAction') 60 | ->bind('admin_user_add'); 61 | $app->match('/admin/users/{user}/edit', 'MusicBox\Controller\AdminUserController::editAction') 62 | ->bind('admin_user_edit'); 63 | $app->match('/admin/users/{user}/delete', 'MusicBox\Controller\AdminUserController::deleteAction') 64 | ->bind('admin_user_delete'); 65 | 66 | $app->get('/admin/comments', 'MusicBox\Controller\AdminCommentController::indexAction') 67 | ->bind('admin_comments'); 68 | $app->match('/admin/comments/{comment}/edit', 'MusicBox\Controller\AdminCommentController::editAction') 69 | ->bind('admin_comment_edit'); 70 | $app->match('/admin/comments/{comment}/delete', 'MusicBox\Controller\AdminCommentController::deleteAction') 71 | ->bind('admin_comment_delete'); 72 | $app->match('/admin/comments/{comment}/approve', 'MusicBox\Controller\AdminCommentController::approveAction') 73 | ->bind('admin_comment_approve'); 74 | $app->match('/admin/comments/{comment}/unapprove', 'MusicBox\Controller\AdminCommentController::unapproveAction') 75 | ->bind('admin_comment_unapprove'); 76 | -------------------------------------------------------------------------------- /web/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | Options -MultiViews 3 | 4 | RewriteEngine On 5 | #RewriteBase /musicbox 6 | RewriteCond %{REQUEST_FILENAME} !-f 7 | RewriteRule ^ index.php [L] 8 | 9 | -------------------------------------------------------------------------------- /web/font/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bojanz/musicbox/f372461f9841175ce93c7d5ffc89a90390dd2a7e/web/font/FontAwesome.otf -------------------------------------------------------------------------------- /web/font/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bojanz/musicbox/f372461f9841175ce93c7d5ffc89a90390dd2a7e/web/font/fontawesome-webfont.eot -------------------------------------------------------------------------------- /web/font/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bojanz/musicbox/f372461f9841175ce93c7d5ffc89a90390dd2a7e/web/font/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /web/font/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bojanz/musicbox/f372461f9841175ce93c7d5ffc89a90390dd2a7e/web/font/fontawesome-webfont.woff -------------------------------------------------------------------------------- /web/ico/Thumbs.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bojanz/musicbox/f372461f9841175ce93c7d5ffc89a90390dd2a7e/web/ico/Thumbs.db -------------------------------------------------------------------------------- /web/ico/apple-touch-icon-114-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bojanz/musicbox/f372461f9841175ce93c7d5ffc89a90390dd2a7e/web/ico/apple-touch-icon-114-precomposed.png -------------------------------------------------------------------------------- /web/ico/apple-touch-icon-144-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bojanz/musicbox/f372461f9841175ce93c7d5ffc89a90390dd2a7e/web/ico/apple-touch-icon-144-precomposed.png -------------------------------------------------------------------------------- /web/ico/apple-touch-icon-57-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bojanz/musicbox/f372461f9841175ce93c7d5ffc89a90390dd2a7e/web/ico/apple-touch-icon-57-precomposed.png -------------------------------------------------------------------------------- /web/ico/apple-touch-icon-72-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bojanz/musicbox/f372461f9841175ce93c7d5ffc89a90390dd2a7e/web/ico/apple-touch-icon-72-precomposed.png -------------------------------------------------------------------------------- /web/ico/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bojanz/musicbox/f372461f9841175ce93c7d5ffc89a90390dd2a7e/web/ico/favicon.ico -------------------------------------------------------------------------------- /web/img/artists/11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bojanz/musicbox/f372461f9841175ce93c7d5ffc89a90390dd2a7e/web/img/artists/11.jpg -------------------------------------------------------------------------------- /web/img/artists/12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bojanz/musicbox/f372461f9841175ce93c7d5ffc89a90390dd2a7e/web/img/artists/12.jpg -------------------------------------------------------------------------------- /web/img/artists/13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bojanz/musicbox/f372461f9841175ce93c7d5ffc89a90390dd2a7e/web/img/artists/13.jpg -------------------------------------------------------------------------------- /web/img/artists/14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bojanz/musicbox/f372461f9841175ce93c7d5ffc89a90390dd2a7e/web/img/artists/14.jpg -------------------------------------------------------------------------------- /web/img/artists/15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bojanz/musicbox/f372461f9841175ce93c7d5ffc89a90390dd2a7e/web/img/artists/15.jpg -------------------------------------------------------------------------------- /web/img/artists/16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bojanz/musicbox/f372461f9841175ce93c7d5ffc89a90390dd2a7e/web/img/artists/16.jpg -------------------------------------------------------------------------------- /web/img/artists/17.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bojanz/musicbox/f372461f9841175ce93c7d5ffc89a90390dd2a7e/web/img/artists/17.jpg -------------------------------------------------------------------------------- /web/img/artists/18.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bojanz/musicbox/f372461f9841175ce93c7d5ffc89a90390dd2a7e/web/img/artists/18.jpg -------------------------------------------------------------------------------- /web/img/artists/19.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bojanz/musicbox/f372461f9841175ce93c7d5ffc89a90390dd2a7e/web/img/artists/19.jpg -------------------------------------------------------------------------------- /web/img/artists/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bojanz/musicbox/f372461f9841175ce93c7d5ffc89a90390dd2a7e/web/img/artists/2.jpg -------------------------------------------------------------------------------- /web/img/artists/24.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bojanz/musicbox/f372461f9841175ce93c7d5ffc89a90390dd2a7e/web/img/artists/24.jpg -------------------------------------------------------------------------------- /web/img/artists/27.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bojanz/musicbox/f372461f9841175ce93c7d5ffc89a90390dd2a7e/web/img/artists/27.jpeg -------------------------------------------------------------------------------- /web/img/artists/placeholder.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bojanz/musicbox/f372461f9841175ce93c7d5ffc89a90390dd2a7e/web/img/artists/placeholder.gif -------------------------------------------------------------------------------- /web/img/heart hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bojanz/musicbox/f372461f9841175ce93c7d5ffc89a90390dd2a7e/web/img/heart hover.png -------------------------------------------------------------------------------- /web/img/heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bojanz/musicbox/f372461f9841175ce93c7d5ffc89a90390dd2a7e/web/img/heart.png -------------------------------------------------------------------------------- /web/img/users/placeholder.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bojanz/musicbox/f372461f9841175ce93c7d5ffc89a90390dd2a7e/web/img/users/placeholder.gif -------------------------------------------------------------------------------- /web/index.php: -------------------------------------------------------------------------------- 1 | run(); 14 | -------------------------------------------------------------------------------- /web/js/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "validthis": true, 3 | "laxcomma" : true, 4 | "laxbreak" : true, 5 | "browser" : true, 6 | "eqnull" : true, 7 | "debug" : true, 8 | "devel" : true, 9 | "boss" : true, 10 | "expr" : true, 11 | "asi" : true 12 | } -------------------------------------------------------------------------------- /web/js/README.md: -------------------------------------------------------------------------------- 1 | ## 2.0 BOOTSTRAP JS PHILOSOPHY 2 | These are the high-level design rules which guide the development of Bootstrap's plugin apis. 3 | 4 | --- 5 | 6 | ### DATA-ATTRIBUTE API 7 | 8 | We believe you should be able to use all plugins provided by Bootstrap purely through the markup API without writing a single line of javascript. 9 | 10 | We acknowledge that this isn't always the most performant and sometimes it may be desirable to turn this functionality off altogether. Therefore, as of 2.0 we provide the ability to disable the data attribute API by unbinding all events on the body namespaced with `'data-api'`. This looks like this: 11 | 12 | $('body').off('.data-api') 13 | 14 | To target a specific plugin, just include the plugins name as a namespace along with the data-api namespace like this: 15 | 16 | $('body').off('.alert.data-api') 17 | 18 | --- 19 | 20 | ### PROGRAMMATIC API 21 | 22 | We also believe you should be able to use all plugins provided by Bootstrap purely through the JS API. 23 | 24 | All public APIs should be single, chainable methods, and return the collection acted upon. 25 | 26 | $(".btn.danger").button("toggle").addClass("fat") 27 | 28 | All methods should accept an optional options object, a string which targets a particular method, or null which initiates the default behavior: 29 | 30 | $("#myModal").modal() // initialized with defaults 31 | $("#myModal").modal({ keyboard: false }) // initialized with now keyboard 32 | $("#myModal").modal('show') // initializes and invokes show immediately afterqwe2 33 | 34 | --- 35 | 36 | ### OPTIONS 37 | 38 | Options should be sparse and add universal value. We should pick the right defaults. 39 | 40 | All plugins should have a default object which can be modified to effect all instance's default options. The defaults object should be available via `$.fn.plugin.defaults`. 41 | 42 | $.fn.modal.defaults = { … } 43 | 44 | An options definition should take the following form: 45 | 46 | *noun*: *adjective* - describes or modifies a quality of an instance 47 | 48 | examples: 49 | 50 | backdrop: true 51 | keyboard: false 52 | placement: 'top' 53 | 54 | --- 55 | 56 | ### EVENTS 57 | 58 | All events should have an infinitive and past participle form. The infinitive is fired just before an action takes place, the past participle on completion of the action. 59 | 60 | show | shown 61 | hide | hidden 62 | 63 | --- 64 | 65 | ### CONSTRUCTORS 66 | 67 | Each plugin should expose it's raw constructor on a `Constructor` property -- accessed in the following way: 68 | 69 | 70 | $.fn.popover.Constructor 71 | 72 | --- 73 | 74 | ### DATA ACCESSOR 75 | 76 | Each plugin stores a copy of the invoked class on an object. This class instance can be accessed directly through jQuery's data API like this: 77 | 78 | $('[rel=popover]').data('popover') instanceof $.fn.popover.Constructor 79 | 80 | --- 81 | 82 | ### DATA ATTRIBUTES 83 | 84 | Data attributes should take the following form: 85 | 86 | - data-{{verb}}={{plugin}} - defines main interaction 87 | - data-target || href^=# - defined on "control" element (if element controls an element other than self) 88 | - data-{{noun}} - defines class instance options 89 | 90 | examples: 91 | 92 | // control other targets 93 | data-toggle="modal" data-target="#foo" 94 | data-toggle="collapse" data-target="#foo" data-parent="#bar" 95 | 96 | // defined on element they control 97 | data-spy="scroll" 98 | 99 | data-dismiss="modal" 100 | data-dismiss="alert" 101 | 102 | data-toggle="dropdown" 103 | 104 | data-toggle="button" 105 | data-toggle="buttons-checkbox" 106 | data-toggle="buttons-radio" -------------------------------------------------------------------------------- /web/js/bootstrap-affix.js: -------------------------------------------------------------------------------- 1 | /* ========================================================== 2 | * bootstrap-affix.js v2.3.2 3 | * http://twitter.github.com/bootstrap/javascript.html#affix 4 | * ========================================================== 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ========================================================== */ 19 | 20 | 21 | !function ($) { 22 | 23 | "use strict"; // jshint ;_; 24 | 25 | 26 | /* AFFIX CLASS DEFINITION 27 | * ====================== */ 28 | 29 | var Affix = function (element, options) { 30 | this.options = $.extend({}, $.fn.affix.defaults, options) 31 | this.$window = $(window) 32 | .on('scroll.affix.data-api', $.proxy(this.checkPosition, this)) 33 | .on('click.affix.data-api', $.proxy(function () { setTimeout($.proxy(this.checkPosition, this), 1) }, this)) 34 | this.$element = $(element) 35 | this.checkPosition() 36 | } 37 | 38 | Affix.prototype.checkPosition = function () { 39 | if (!this.$element.is(':visible')) return 40 | 41 | var scrollHeight = $(document).height() 42 | , scrollTop = this.$window.scrollTop() 43 | , position = this.$element.offset() 44 | , offset = this.options.offset 45 | , offsetBottom = offset.bottom 46 | , offsetTop = offset.top 47 | , reset = 'affix affix-top affix-bottom' 48 | , affix 49 | 50 | if (typeof offset != 'object') offsetBottom = offsetTop = offset 51 | if (typeof offsetTop == 'function') offsetTop = offset.top() 52 | if (typeof offsetBottom == 'function') offsetBottom = offset.bottom() 53 | 54 | affix = this.unpin != null && (scrollTop + this.unpin <= position.top) ? 55 | false : offsetBottom != null && (position.top + this.$element.height() >= scrollHeight - offsetBottom) ? 56 | 'bottom' : offsetTop != null && scrollTop <= offsetTop ? 57 | 'top' : false 58 | 59 | if (this.affixed === affix) return 60 | 61 | this.affixed = affix 62 | this.unpin = affix == 'bottom' ? position.top - scrollTop : null 63 | 64 | this.$element.removeClass(reset).addClass('affix' + (affix ? '-' + affix : '')) 65 | } 66 | 67 | 68 | /* AFFIX PLUGIN DEFINITION 69 | * ======================= */ 70 | 71 | var old = $.fn.affix 72 | 73 | $.fn.affix = function (option) { 74 | return this.each(function () { 75 | var $this = $(this) 76 | , data = $this.data('affix') 77 | , options = typeof option == 'object' && option 78 | if (!data) $this.data('affix', (data = new Affix(this, options))) 79 | if (typeof option == 'string') data[option]() 80 | }) 81 | } 82 | 83 | $.fn.affix.Constructor = Affix 84 | 85 | $.fn.affix.defaults = { 86 | offset: 0 87 | } 88 | 89 | 90 | /* AFFIX NO CONFLICT 91 | * ================= */ 92 | 93 | $.fn.affix.noConflict = function () { 94 | $.fn.affix = old 95 | return this 96 | } 97 | 98 | 99 | /* AFFIX DATA-API 100 | * ============== */ 101 | 102 | $(window).on('load', function () { 103 | $('[data-spy="affix"]').each(function () { 104 | var $spy = $(this) 105 | , data = $spy.data() 106 | 107 | data.offset = data.offset || {} 108 | 109 | data.offsetBottom && (data.offset.bottom = data.offsetBottom) 110 | data.offsetTop && (data.offset.top = data.offsetTop) 111 | 112 | $spy.affix(data) 113 | }) 114 | }) 115 | 116 | 117 | }(window.jQuery); -------------------------------------------------------------------------------- /web/js/bootstrap-alert.js: -------------------------------------------------------------------------------- 1 | /* ========================================================== 2 | * bootstrap-alert.js v2.3.2 3 | * http://twitter.github.com/bootstrap/javascript.html#alerts 4 | * ========================================================== 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ========================================================== */ 19 | 20 | 21 | !function ($) { 22 | 23 | "use strict"; // jshint ;_; 24 | 25 | 26 | /* ALERT CLASS DEFINITION 27 | * ====================== */ 28 | 29 | var dismiss = '[data-dismiss="alert"]' 30 | , Alert = function (el) { 31 | $(el).on('click', dismiss, this.close) 32 | } 33 | 34 | Alert.prototype.close = function (e) { 35 | var $this = $(this) 36 | , selector = $this.attr('data-target') 37 | , $parent 38 | 39 | if (!selector) { 40 | selector = $this.attr('href') 41 | selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 42 | } 43 | 44 | $parent = $(selector) 45 | 46 | e && e.preventDefault() 47 | 48 | $parent.length || ($parent = $this.hasClass('alert') ? $this : $this.parent()) 49 | 50 | $parent.trigger(e = $.Event('close')) 51 | 52 | if (e.isDefaultPrevented()) return 53 | 54 | $parent.removeClass('in') 55 | 56 | function removeElement() { 57 | $parent 58 | .trigger('closed') 59 | .remove() 60 | } 61 | 62 | $.support.transition && $parent.hasClass('fade') ? 63 | $parent.on($.support.transition.end, removeElement) : 64 | removeElement() 65 | } 66 | 67 | 68 | /* ALERT PLUGIN DEFINITION 69 | * ======================= */ 70 | 71 | var old = $.fn.alert 72 | 73 | $.fn.alert = function (option) { 74 | return this.each(function () { 75 | var $this = $(this) 76 | , data = $this.data('alert') 77 | if (!data) $this.data('alert', (data = new Alert(this))) 78 | if (typeof option == 'string') data[option].call($this) 79 | }) 80 | } 81 | 82 | $.fn.alert.Constructor = Alert 83 | 84 | 85 | /* ALERT NO CONFLICT 86 | * ================= */ 87 | 88 | $.fn.alert.noConflict = function () { 89 | $.fn.alert = old 90 | return this 91 | } 92 | 93 | 94 | /* ALERT DATA-API 95 | * ============== */ 96 | 97 | $(document).on('click.alert.data-api', dismiss, Alert.prototype.close) 98 | 99 | }(window.jQuery); -------------------------------------------------------------------------------- /web/js/bootstrap-button.js: -------------------------------------------------------------------------------- 1 | /* ============================================================ 2 | * bootstrap-button.js v2.3.2 3 | * http://twitter.github.com/bootstrap/javascript.html#buttons 4 | * ============================================================ 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ============================================================ */ 19 | 20 | 21 | !function ($) { 22 | 23 | "use strict"; // jshint ;_; 24 | 25 | 26 | /* BUTTON PUBLIC CLASS DEFINITION 27 | * ============================== */ 28 | 29 | var Button = function (element, options) { 30 | this.$element = $(element) 31 | this.options = $.extend({}, $.fn.button.defaults, options) 32 | } 33 | 34 | Button.prototype.setState = function (state) { 35 | var d = 'disabled' 36 | , $el = this.$element 37 | , data = $el.data() 38 | , val = $el.is('input') ? 'val' : 'html' 39 | 40 | state = state + 'Text' 41 | data.resetText || $el.data('resetText', $el[val]()) 42 | 43 | $el[val](data[state] || this.options[state]) 44 | 45 | // push to event loop to allow forms to submit 46 | setTimeout(function () { 47 | state == 'loadingText' ? 48 | $el.addClass(d).attr(d, d) : 49 | $el.removeClass(d).removeAttr(d) 50 | }, 0) 51 | } 52 | 53 | Button.prototype.toggle = function () { 54 | var $parent = this.$element.closest('[data-toggle="buttons-radio"]') 55 | 56 | $parent && $parent 57 | .find('.active') 58 | .removeClass('active') 59 | 60 | this.$element.toggleClass('active') 61 | } 62 | 63 | 64 | /* BUTTON PLUGIN DEFINITION 65 | * ======================== */ 66 | 67 | var old = $.fn.button 68 | 69 | $.fn.button = function (option) { 70 | return this.each(function () { 71 | var $this = $(this) 72 | , data = $this.data('button') 73 | , options = typeof option == 'object' && option 74 | if (!data) $this.data('button', (data = new Button(this, options))) 75 | if (option == 'toggle') data.toggle() 76 | else if (option) data.setState(option) 77 | }) 78 | } 79 | 80 | $.fn.button.defaults = { 81 | loadingText: 'loading...' 82 | } 83 | 84 | $.fn.button.Constructor = Button 85 | 86 | 87 | /* BUTTON NO CONFLICT 88 | * ================== */ 89 | 90 | $.fn.button.noConflict = function () { 91 | $.fn.button = old 92 | return this 93 | } 94 | 95 | 96 | /* BUTTON DATA-API 97 | * =============== */ 98 | 99 | $(document).on('click.button.data-api', '[data-toggle^=button]', function (e) { 100 | var $btn = $(e.target) 101 | if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn') 102 | $btn.button('toggle') 103 | }) 104 | 105 | }(window.jQuery); -------------------------------------------------------------------------------- /web/js/bootstrap-popover.js: -------------------------------------------------------------------------------- 1 | /* =========================================================== 2 | * bootstrap-popover.js v2.3.2 3 | * http://twitter.github.com/bootstrap/javascript.html#popovers 4 | * =========================================================== 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * =========================================================== */ 19 | 20 | 21 | !function ($) { 22 | 23 | "use strict"; // jshint ;_; 24 | 25 | 26 | /* POPOVER PUBLIC CLASS DEFINITION 27 | * =============================== */ 28 | 29 | var Popover = function (element, options) { 30 | this.init('popover', element, options) 31 | } 32 | 33 | 34 | /* NOTE: POPOVER EXTENDS BOOTSTRAP-TOOLTIP.js 35 | ========================================== */ 36 | 37 | Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype, { 38 | 39 | constructor: Popover 40 | 41 | , setContent: function () { 42 | var $tip = this.tip() 43 | , title = this.getTitle() 44 | , content = this.getContent() 45 | 46 | $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title) 47 | $tip.find('.popover-content')[this.options.html ? 'html' : 'text'](content) 48 | 49 | $tip.removeClass('fade top bottom left right in') 50 | } 51 | 52 | , hasContent: function () { 53 | return this.getTitle() || this.getContent() 54 | } 55 | 56 | , getContent: function () { 57 | var content 58 | , $e = this.$element 59 | , o = this.options 60 | 61 | content = (typeof o.content == 'function' ? o.content.call($e[0]) : o.content) 62 | || $e.attr('data-content') 63 | 64 | return content 65 | } 66 | 67 | , tip: function () { 68 | if (!this.$tip) { 69 | this.$tip = $(this.options.template) 70 | } 71 | return this.$tip 72 | } 73 | 74 | , destroy: function () { 75 | this.hide().$element.off('.' + this.type).removeData(this.type) 76 | } 77 | 78 | }) 79 | 80 | 81 | /* POPOVER PLUGIN DEFINITION 82 | * ======================= */ 83 | 84 | var old = $.fn.popover 85 | 86 | $.fn.popover = function (option) { 87 | return this.each(function () { 88 | var $this = $(this) 89 | , data = $this.data('popover') 90 | , options = typeof option == 'object' && option 91 | if (!data) $this.data('popover', (data = new Popover(this, options))) 92 | if (typeof option == 'string') data[option]() 93 | }) 94 | } 95 | 96 | $.fn.popover.Constructor = Popover 97 | 98 | $.fn.popover.defaults = $.extend({} , $.fn.tooltip.defaults, { 99 | placement: 'right' 100 | , trigger: 'click' 101 | , content: '' 102 | , template: '

' 103 | }) 104 | 105 | 106 | /* POPOVER NO CONFLICT 107 | * =================== */ 108 | 109 | $.fn.popover.noConflict = function () { 110 | $.fn.popover = old 111 | return this 112 | } 113 | 114 | 115 | }(window.jQuery); 116 | -------------------------------------------------------------------------------- /web/js/bootstrap-tab.js: -------------------------------------------------------------------------------- 1 | /* ======================================================== 2 | * bootstrap-tab.js v2.3.2 3 | * http://twitter.github.com/bootstrap/javascript.html#tabs 4 | * ======================================================== 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ======================================================== */ 19 | 20 | 21 | !function ($) { 22 | 23 | "use strict"; // jshint ;_; 24 | 25 | 26 | /* TAB CLASS DEFINITION 27 | * ==================== */ 28 | 29 | var Tab = function (element) { 30 | this.element = $(element) 31 | } 32 | 33 | Tab.prototype = { 34 | 35 | constructor: Tab 36 | 37 | , show: function () { 38 | var $this = this.element 39 | , $ul = $this.closest('ul:not(.dropdown-menu)') 40 | , selector = $this.attr('data-target') 41 | , previous 42 | , $target 43 | , e 44 | 45 | if (!selector) { 46 | selector = $this.attr('href') 47 | selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 48 | } 49 | 50 | if ( $this.parent('li').hasClass('active') ) return 51 | 52 | previous = $ul.find('.active:last a')[0] 53 | 54 | e = $.Event('show', { 55 | relatedTarget: previous 56 | }) 57 | 58 | $this.trigger(e) 59 | 60 | if (e.isDefaultPrevented()) return 61 | 62 | $target = $(selector) 63 | 64 | this.activate($this.parent('li'), $ul) 65 | this.activate($target, $target.parent(), function () { 66 | $this.trigger({ 67 | type: 'shown' 68 | , relatedTarget: previous 69 | }) 70 | }) 71 | } 72 | 73 | , activate: function ( element, container, callback) { 74 | var $active = container.find('> .active') 75 | , transition = callback 76 | && $.support.transition 77 | && $active.hasClass('fade') 78 | 79 | function next() { 80 | $active 81 | .removeClass('active') 82 | .find('> .dropdown-menu > .active') 83 | .removeClass('active') 84 | 85 | element.addClass('active') 86 | 87 | if (transition) { 88 | element[0].offsetWidth // reflow for transition 89 | element.addClass('in') 90 | } else { 91 | element.removeClass('fade') 92 | } 93 | 94 | if ( element.parent('.dropdown-menu') ) { 95 | element.closest('li.dropdown').addClass('active') 96 | } 97 | 98 | callback && callback() 99 | } 100 | 101 | transition ? 102 | $active.one($.support.transition.end, next) : 103 | next() 104 | 105 | $active.removeClass('in') 106 | } 107 | } 108 | 109 | 110 | /* TAB PLUGIN DEFINITION 111 | * ===================== */ 112 | 113 | var old = $.fn.tab 114 | 115 | $.fn.tab = function ( option ) { 116 | return this.each(function () { 117 | var $this = $(this) 118 | , data = $this.data('tab') 119 | if (!data) $this.data('tab', (data = new Tab(this))) 120 | if (typeof option == 'string') data[option]() 121 | }) 122 | } 123 | 124 | $.fn.tab.Constructor = Tab 125 | 126 | 127 | /* TAB NO CONFLICT 128 | * =============== */ 129 | 130 | $.fn.tab.noConflict = function () { 131 | $.fn.tab = old 132 | return this 133 | } 134 | 135 | 136 | /* TAB DATA-API 137 | * ============ */ 138 | 139 | $(document).on('click.tab.data-api', '[data-toggle="tab"], [data-toggle="pill"]', function (e) { 140 | e.preventDefault() 141 | $(this).tab('show') 142 | }) 143 | 144 | }(window.jQuery); -------------------------------------------------------------------------------- /web/js/bootstrap-transition.js: -------------------------------------------------------------------------------- 1 | /* =================================================== 2 | * bootstrap-transition.js v2.3.2 3 | * http://twitter.github.com/bootstrap/javascript.html#transitions 4 | * =================================================== 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ========================================================== */ 19 | 20 | 21 | !function ($) { 22 | 23 | "use strict"; // jshint ;_; 24 | 25 | 26 | /* CSS TRANSITION SUPPORT (http://www.modernizr.com/) 27 | * ======================================================= */ 28 | 29 | $(function () { 30 | 31 | $.support.transition = (function () { 32 | 33 | var transitionEnd = (function () { 34 | 35 | var el = document.createElement('bootstrap') 36 | , transEndEventNames = { 37 | 'WebkitTransition' : 'webkitTransitionEnd' 38 | , 'MozTransition' : 'transitionend' 39 | , 'OTransition' : 'oTransitionEnd otransitionend' 40 | , 'transition' : 'transitionend' 41 | } 42 | , name 43 | 44 | for (name in transEndEventNames){ 45 | if (el.style[name] !== undefined) { 46 | return transEndEventNames[name] 47 | } 48 | } 49 | 50 | }()) 51 | 52 | return transitionEnd && { 53 | end: transitionEnd 54 | } 55 | 56 | })() 57 | 58 | }) 59 | 60 | }(window.jQuery); -------------------------------------------------------------------------------- /web/js/google-code-prettify/prettify.css: -------------------------------------------------------------------------------- 1 | .com { color: #93a1a1; } 2 | .lit { color: #195f91; } 3 | .pun, .opn, .clo { color: #93a1a1; } 4 | .fun { color: #dc322f; } 5 | .str, .atv { color: #D14; } 6 | .kwd, .prettyprint .tag { color: #1e347b; } 7 | .typ, .atn, .dec, .var { color: teal; } 8 | .pln { color: #48484c; } 9 | 10 | .prettyprint { 11 | padding: 8px; 12 | background-color: #f7f7f9; 13 | border: 1px solid #e1e1e8; 14 | } 15 | .prettyprint.linenums { 16 | -webkit-box-shadow: inset 40px 0 0 #fbfbfc, inset 41px 0 0 #ececf0; 17 | -moz-box-shadow: inset 40px 0 0 #fbfbfc, inset 41px 0 0 #ececf0; 18 | box-shadow: inset 40px 0 0 #fbfbfc, inset 41px 0 0 #ececf0; 19 | } 20 | 21 | /* Specify class=linenums on a pre to get line numbering */ 22 | ol.linenums { 23 | margin: 0 0 0 33px; /* IE indents via margin-left */ 24 | } 25 | ol.linenums li { 26 | padding-left: 12px; 27 | color: #bebec5; 28 | line-height: 20px; 29 | text-shadow: 0 1px 0 #fff; 30 | } -------------------------------------------------------------------------------- /web/js/html5shiv.js: -------------------------------------------------------------------------------- 1 | /* 2 | HTML5 Shiv v3.6.2pre | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | (function(l,f){function m(){var a=e.elements;return"string"==typeof a?a.split(" "):a}function i(a){var b=n[a[o]];b||(b={},h++,a[o]=h,n[h]=b);return b}function p(a,b,c){b||(b=f);if(g)return b.createElement(a);c||(c=i(b));b=c.cache[a]?c.cache[a].cloneNode():r.test(a)?(c.cache[a]=c.createElem(a)).cloneNode():c.createElem(a);return b.canHaveChildren&&!s.test(a)?c.frag.appendChild(b):b}function t(a,b){if(!b.cache)b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag(); 5 | a.createElement=function(c){return!e.shivMethods?b.createElem(c):p(c,a,b)};a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+m().join().replace(/\w+/g,function(a){b.createElem(a);b.frag.createElement(a);return'c("'+a+'")'})+");return n}")(e,b.frag)}function q(a){a||(a=f);var b=i(a);if(e.shivCSS&&!j&&!b.hasCSS){var c,d=a;c=d.createElement("p");d=d.getElementsByTagName("head")[0]||d.documentElement;c.innerHTML="x"; 6 | c=d.insertBefore(c.lastChild,d.firstChild);b.hasCSS=!!c}g||t(a,b);return a}var k=l.html5||{},s=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,r=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,j,o="_html5shiv",h=0,n={},g;(function(){try{var a=f.createElement("a");a.innerHTML="";j="hidden"in a;var b;if(!(b=1==a.childNodes.length)){f.createElement("a");var c=f.createDocumentFragment();b="undefined"==typeof c.cloneNode|| 7 | "undefined"==typeof c.createDocumentFragment||"undefined"==typeof c.createElement}g=b}catch(d){g=j=!0}})();var e={elements:k.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",version:"3.6.2pre",shivCSS:!1!==k.shivCSS,supportsUnknownElements:g,shivMethods:!1!==k.shivMethods,type:"default",shivDocument:q,createElement:p,createDocumentFragment:function(a,b){a||(a=f);if(g)return a.createDocumentFragment(); 8 | for(var b=b||i(a),c=b.frag.cloneNode(),d=0,e=m(),h=e.length;d 2 | 3 | 4 | Bootstrap Plugin Test Suite 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |
49 |

Bootstrap Plugin Test Suite

50 |

51 |

52 |
    53 |
    54 |
    55 | 56 | -------------------------------------------------------------------------------- /web/js/tests/phantom.js: -------------------------------------------------------------------------------- 1 | // Simple phantom.js integration script 2 | // Adapted from Modernizr 3 | 4 | function waitFor(testFx, onReady, timeOutMillis) { 5 | var maxtimeOutMillis = timeOutMillis ? timeOutMillis : 5001 //< Default Max Timout is 5s 6 | , start = new Date().getTime() 7 | , condition = false 8 | , interval = setInterval(function () { 9 | if ((new Date().getTime() - start < maxtimeOutMillis) && !condition) { 10 | // If not time-out yet and condition not yet fulfilled 11 | condition = (typeof(testFx) === "string" ? eval(testFx) : testFx()) //< defensive code 12 | } else { 13 | if (!condition) { 14 | // If condition still not fulfilled (timeout but condition is 'false') 15 | console.log("'waitFor()' timeout") 16 | phantom.exit(1) 17 | } else { 18 | // Condition fulfilled (timeout and/or condition is 'true') 19 | typeof(onReady) === "string" ? eval(onReady) : onReady() //< Do what it's supposed to do once the condition is fulfilled 20 | clearInterval(interval) //< Stop this interval 21 | } 22 | } 23 | }, 100) //< repeat check every 100ms 24 | } 25 | 26 | 27 | if (phantom.args.length === 0 || phantom.args.length > 2) { 28 | console.log('Usage: phantom.js URL') 29 | phantom.exit() 30 | } 31 | 32 | var page = new WebPage() 33 | 34 | // Route "console.log()" calls from within the Page context to the main Phantom context (i.e. current "this") 35 | page.onConsoleMessage = function(msg) { 36 | console.log(msg) 37 | }; 38 | 39 | page.open(phantom.args[0], function(status){ 40 | if (status !== "success") { 41 | console.log("Unable to access network") 42 | phantom.exit() 43 | } else { 44 | waitFor(function(){ 45 | return page.evaluate(function(){ 46 | var el = document.getElementById('qunit-testresult') 47 | if (el && el.innerText.match('completed')) { 48 | return true 49 | } 50 | return false 51 | }) 52 | }, function(){ 53 | var failedNum = page.evaluate(function(){ 54 | var el = document.getElementById('qunit-testresult') 55 | try { 56 | return el.getElementsByClassName('failed')[0].innerHTML 57 | } catch (e) { } 58 | return 10000 59 | }); 60 | phantom.exit((parseInt(failedNum, 10) > 0) ? 1 : 0) 61 | }) 62 | } 63 | }) -------------------------------------------------------------------------------- /web/js/tests/server.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Simple connect server for phantom.js 3 | * Adapted from Modernizr 4 | */ 5 | 6 | var connect = require('connect') 7 | , http = require('http') 8 | , fs = require('fs') 9 | , app = connect() 10 | .use(connect.static(__dirname + '/../../')); 11 | 12 | http.createServer(app).listen(3000); 13 | 14 | fs.writeFileSync(__dirname + '/pid.txt', process.pid, 'utf-8') -------------------------------------------------------------------------------- /web/js/tests/unit/bootstrap-affix.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | 3 | module("bootstrap-affix") 4 | 5 | test("should provide no conflict", function () { 6 | var affix = $.fn.affix.noConflict() 7 | ok(!$.fn.affix, 'affix was set back to undefined (org value)') 8 | $.fn.affix = affix 9 | }) 10 | 11 | test("should be defined on jquery object", function () { 12 | ok($(document.body).affix, 'affix method is defined') 13 | }) 14 | 15 | test("should return element", function () { 16 | ok($(document.body).affix()[0] == document.body, 'document.body returned') 17 | }) 18 | 19 | test("should exit early if element is not visible", function () { 20 | var $affix = $('
    ').affix() 21 | $affix.data('affix').checkPosition() 22 | ok(!$affix.hasClass('affix'), 'affix class was not added') 23 | }) 24 | 25 | }) -------------------------------------------------------------------------------- /web/js/tests/unit/bootstrap-alert.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | 3 | module("bootstrap-alerts") 4 | 5 | test("should provide no conflict", function () { 6 | var alert = $.fn.alert.noConflict() 7 | ok(!$.fn.alert, 'alert was set back to undefined (org value)') 8 | $.fn.alert = alert 9 | }) 10 | 11 | test("should be defined on jquery object", function () { 12 | ok($(document.body).alert, 'alert method is defined') 13 | }) 14 | 15 | test("should return element", function () { 16 | ok($(document.body).alert()[0] == document.body, 'document.body returned') 17 | }) 18 | 19 | test("should fade element out on clicking .close", function () { 20 | var alertHTML = '
    ' 21 | + '×' 22 | + '

    Holy guacamole! Best check yo self, you\'re not looking too good.

    ' 23 | + '
    ' 24 | , alert = $(alertHTML).alert() 25 | 26 | alert.find('.close').click() 27 | 28 | ok(!alert.hasClass('in'), 'remove .in class on .close click') 29 | }) 30 | 31 | test("should remove element when clicking .close", function () { 32 | $.support.transition = false 33 | 34 | var alertHTML = '
    ' 35 | + '×' 36 | + '

    Holy guacamole! Best check yo self, you\'re not looking too good.

    ' 37 | + '
    ' 38 | , alert = $(alertHTML).appendTo('#qunit-fixture').alert() 39 | 40 | ok($('#qunit-fixture').find('.alert-message').length, 'element added to dom') 41 | 42 | alert.find('.close').click() 43 | 44 | ok(!$('#qunit-fixture').find('.alert-message').length, 'element removed from dom') 45 | }) 46 | 47 | test("should not fire closed when close is prevented", function () { 48 | $.support.transition = false 49 | stop(); 50 | $('
    ') 51 | .bind('close', function (e) { 52 | e.preventDefault(); 53 | ok(true); 54 | start(); 55 | }) 56 | .bind('closed', function () { 57 | ok(false); 58 | }) 59 | .alert('close') 60 | }) 61 | 62 | }) -------------------------------------------------------------------------------- /web/js/tests/unit/bootstrap-collapse.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | 3 | module("bootstrap-collapse") 4 | 5 | test("should provide no conflict", function () { 6 | var collapse = $.fn.collapse.noConflict() 7 | ok(!$.fn.collapse, 'collapse was set back to undefined (org value)') 8 | $.fn.collapse = collapse 9 | }) 10 | 11 | test("should be defined on jquery object", function () { 12 | ok($(document.body).collapse, 'collapse method is defined') 13 | }) 14 | 15 | test("should return element", function () { 16 | ok($(document.body).collapse()[0] == document.body, 'document.body returned') 17 | }) 18 | 19 | test("should show a collapsed element", function () { 20 | var el = $('
    ').collapse('show') 21 | ok(el.hasClass('in'), 'has class in') 22 | ok(/height/.test(el.attr('style')), 'has height set') 23 | }) 24 | 25 | test("should hide a collapsed element", function () { 26 | var el = $('
    ').collapse('hide') 27 | ok(!el.hasClass('in'), 'does not have class in') 28 | ok(/height/.test(el.attr('style')), 'has height set') 29 | }) 30 | 31 | test("should not fire shown when show is prevented", function () { 32 | $.support.transition = false 33 | stop() 34 | $('
    ') 35 | .bind('show', function (e) { 36 | e.preventDefault(); 37 | ok(true); 38 | start(); 39 | }) 40 | .bind('shown', function () { 41 | ok(false); 42 | }) 43 | .collapse('show') 44 | }) 45 | 46 | test("should reset style to auto after finishing opening collapse", function () { 47 | $.support.transition = false 48 | stop() 49 | $('
    ') 50 | .bind('show', function () { 51 | ok(this.style.height == '0px') 52 | }) 53 | .bind('shown', function () { 54 | ok(this.style.height == 'auto') 55 | start() 56 | }) 57 | .collapse('show') 58 | }) 59 | 60 | test("should add active class to target when collapse shown", function () { 61 | $.support.transition = false 62 | stop() 63 | 64 | var target = $('') 65 | .appendTo($('#qunit-fixture')) 66 | 67 | var collapsible = $('
    ') 68 | .appendTo($('#qunit-fixture')) 69 | .on('show', function () { 70 | ok(!target.hasClass('collapsed')) 71 | start() 72 | }) 73 | 74 | target.click() 75 | }) 76 | 77 | test("should remove active class to target when collapse hidden", function () { 78 | $.support.transition = false 79 | stop() 80 | 81 | var target = $('') 82 | .appendTo($('#qunit-fixture')) 83 | 84 | var collapsible = $('
    ') 85 | .appendTo($('#qunit-fixture')) 86 | .on('hide', function () { 87 | ok(target.hasClass('collapsed')) 88 | start() 89 | }) 90 | 91 | target.click() 92 | }) 93 | 94 | }) -------------------------------------------------------------------------------- /web/js/tests/unit/bootstrap-phantom.js: -------------------------------------------------------------------------------- 1 | // Logging setup for phantom integration 2 | // adapted from Modernizr 3 | 4 | QUnit.begin = function () { 5 | console.log("Starting test suite") 6 | console.log("================================================\n") 7 | } 8 | 9 | QUnit.moduleDone = function (opts) { 10 | if (opts.failed === 0) { 11 | console.log("\u2714 All tests passed in '" + opts.name + "' module") 12 | } else { 13 | console.log("\u2716 " + opts.failed + " tests failed in '" + opts.name + "' module") 14 | } 15 | } 16 | 17 | QUnit.done = function (opts) { 18 | console.log("\n================================================") 19 | console.log("Tests completed in " + opts.runtime + " milliseconds") 20 | console.log(opts.passed + " tests of " + opts.total + " passed, " + opts.failed + " failed.") 21 | } -------------------------------------------------------------------------------- /web/js/tests/unit/bootstrap-scrollspy.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | 3 | module("bootstrap-scrollspy") 4 | 5 | test("should provide no conflict", function () { 6 | var scrollspy = $.fn.scrollspy.noConflict() 7 | ok(!$.fn.scrollspy, 'scrollspy was set back to undefined (org value)') 8 | $.fn.scrollspy = scrollspy 9 | }) 10 | 11 | test("should be defined on jquery object", function () { 12 | ok($(document.body).scrollspy, 'scrollspy method is defined') 13 | }) 14 | 15 | test("should return element", function () { 16 | ok($(document.body).scrollspy()[0] == document.body, 'document.body returned') 17 | }) 18 | 19 | test("should switch active class on scroll", function () { 20 | var sectionHTML = '
    ' 21 | , $section = $(sectionHTML).append('#qunit-fixture') 22 | , topbarHTML ='
    ' 23 | + '
    ' 24 | + '
    ' 25 | + '

    Bootstrap

    ' 26 | + '' 29 | + '
    ' 30 | + '
    ' 31 | + '
    ' 32 | , $topbar = $(topbarHTML).scrollspy() 33 | 34 | ok($topbar.find('.active', true)) 35 | }) 36 | 37 | }) -------------------------------------------------------------------------------- /web/js/tests/unit/bootstrap-tab.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | 3 | module("bootstrap-tabs") 4 | 5 | test("should provide no conflict", function () { 6 | var tab = $.fn.tab.noConflict() 7 | ok(!$.fn.tab, 'tab was set back to undefined (org value)') 8 | $.fn.tab = tab 9 | }) 10 | 11 | test("should be defined on jquery object", function () { 12 | ok($(document.body).tab, 'tabs method is defined') 13 | }) 14 | 15 | test("should return element", function () { 16 | ok($(document.body).tab()[0] == document.body, 'document.body returned') 17 | }) 18 | 19 | test("should activate element by tab id", function () { 20 | var tabsHTML = 21 | '' 25 | 26 | $('
    ').appendTo("#qunit-fixture") 27 | 28 | $(tabsHTML).find('li:last a').tab('show') 29 | equals($("#qunit-fixture").find('.active').attr('id'), "profile") 30 | 31 | $(tabsHTML).find('li:first a').tab('show') 32 | equals($("#qunit-fixture").find('.active').attr('id'), "home") 33 | }) 34 | 35 | test("should activate element by tab id", function () { 36 | var pillsHTML = 37 | '' 41 | 42 | $('
    ').appendTo("#qunit-fixture") 43 | 44 | $(pillsHTML).find('li:last a').tab('show') 45 | equals($("#qunit-fixture").find('.active').attr('id'), "profile") 46 | 47 | $(pillsHTML).find('li:first a').tab('show') 48 | equals($("#qunit-fixture").find('.active').attr('id'), "home") 49 | }) 50 | 51 | 52 | test("should not fire closed when close is prevented", function () { 53 | $.support.transition = false 54 | stop(); 55 | $('
    ') 56 | .bind('show', function (e) { 57 | e.preventDefault(); 58 | ok(true); 59 | start(); 60 | }) 61 | .bind('shown', function () { 62 | ok(false); 63 | }) 64 | .tab('show') 65 | }) 66 | 67 | test("show and shown events should reference correct relatedTarget", function () { 68 | var dropHTML = 69 | '
      ' 70 | + '' 76 | + '
    ' 77 | 78 | $(dropHTML).find('ul>li:first a').tab('show').end() 79 | .find('ul>li:last a').on('show', function(event){ 80 | equals(event.relatedTarget.hash, "#1-1") 81 | }).on('shown', function(event){ 82 | equals(event.relatedTarget.hash, "#1-1") 83 | }).tab('show') 84 | }) 85 | 86 | }) -------------------------------------------------------------------------------- /web/js/tests/unit/bootstrap-transition.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | 3 | module("bootstrap-transition") 4 | 5 | test("should be defined on jquery support object", function () { 6 | ok($.support.transition !== undefined, 'transition object is defined') 7 | }) 8 | 9 | test("should provide an end object", function () { 10 | ok($.support.transition ? $.support.transition.end : true, 'end string is defined') 11 | }) 12 | 13 | }) -------------------------------------------------------------------------------- /web/less/accordion.less: -------------------------------------------------------------------------------- 1 | // 2 | // Accordion 3 | // -------------------------------------------------- 4 | 5 | 6 | // Parent container 7 | .accordion { 8 | margin-bottom: @baseLineHeight; 9 | } 10 | 11 | // Group == heading + body 12 | .accordion-group { 13 | margin-bottom: 2px; 14 | border: 1px solid #e5e5e5; 15 | } 16 | .accordion-heading { 17 | border-bottom: 0; 18 | } 19 | .accordion-heading .accordion-toggle { 20 | display: block; 21 | padding: 8px 15px; 22 | } 23 | 24 | // General toggle styles 25 | .accordion-toggle { 26 | cursor: pointer; 27 | } 28 | 29 | // Inner needs the styles because you can't animate properly with any styles on the element 30 | .accordion-inner { 31 | padding: 9px 15px; 32 | border-top: 1px solid #e5e5e5; 33 | } 34 | -------------------------------------------------------------------------------- /web/less/alerts.less: -------------------------------------------------------------------------------- 1 | // 2 | // Alerts 3 | // -------------------------------------------------- 4 | 5 | 6 | // Base styles 7 | // ------------------------- 8 | 9 | .alert { 10 | padding: 8px 35px 8px 14px; 11 | margin-bottom: @baseLineHeight; 12 | background-color: @warningBackground; 13 | border: 1px solid @warningBorder; 14 | } 15 | .alert, 16 | .alert h4 { 17 | // Specified for the h4 to prevent conflicts of changing @headingsColor 18 | color: @warningText; 19 | } 20 | .alert h4 { 21 | margin: 0; 22 | } 23 | 24 | // Adjust close link position 25 | .alert .close { 26 | position: relative; 27 | top: -2px; 28 | right: -21px; 29 | line-height: @baseLineHeight; 30 | } 31 | 32 | 33 | // Alternate styles 34 | // ------------------------- 35 | 36 | .alert-success { 37 | background-color: @successBackground; 38 | border-color: @successBorder; 39 | color: @successText; 40 | } 41 | .alert-success h4 { 42 | color: @successText; 43 | } 44 | .alert-danger, 45 | .alert-error { 46 | background-color: @errorBackground; 47 | border-color: @errorBorder; 48 | color: @errorText; 49 | } 50 | .alert-danger h4, 51 | .alert-error h4 { 52 | color: @errorText; 53 | } 54 | .alert-info { 55 | background-color: @infoBackground; 56 | border-color: @infoBorder; 57 | color: @infoText; 58 | } 59 | .alert-info h4 { 60 | color: @infoText; 61 | } 62 | 63 | 64 | // Block alerts 65 | // ------------------------- 66 | 67 | .alert-block { 68 | padding-top: 14px; 69 | padding-bottom: 14px; 70 | } 71 | .alert-block > p, 72 | .alert-block > ul { 73 | margin-bottom: 0; 74 | } 75 | .alert-block p + p { 76 | margin-top: 5px; 77 | } 78 | -------------------------------------------------------------------------------- /web/less/bootstrap.less: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v2.3.2 3 | * 4 | * Copyright 2012 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 | */ 10 | 11 | // Core variables and mixins 12 | @import "variables.less"; // Modify this for custom colors, font-sizes, etc 13 | @import "mixins.less"; 14 | 15 | // CSS Reset 16 | @import "reset.less"; 17 | 18 | // Grid system and page structure 19 | @import "scaffolding.less"; 20 | @import "grid.less"; 21 | @import "layouts.less"; 22 | 23 | // Base CSS 24 | @import "type.less"; 25 | @import "code.less"; 26 | @import "forms.less"; 27 | @import "tables.less"; 28 | 29 | // Components: common 30 | @import "font-awesome/font-awesome.less"; 31 | @import "font-awesome/font-awesome-ie7.less"; 32 | @import "dropdowns.less"; 33 | @import "wells.less"; 34 | @import "component-animations.less"; 35 | @import "close.less"; 36 | 37 | // Components: Buttons & Alerts 38 | @import "buttons.less"; 39 | @import "button-groups.less"; 40 | @import "alerts.less"; // Note: alerts share common CSS with buttons and thus have styles in buttons.less 41 | 42 | // Components: Nav 43 | @import "navs.less"; 44 | @import "navbar.less"; 45 | @import "breadcrumbs.less"; 46 | @import "pagination.less"; 47 | @import "pager.less"; 48 | 49 | // Components: Popovers 50 | @import "modals.less"; 51 | @import "tooltip.less"; 52 | @import "popovers.less"; 53 | 54 | // Components: Misc 55 | @import "thumbnails.less"; 56 | @import "media.less"; 57 | @import "labels-badges.less"; 58 | @import "progress-bars.less"; 59 | @import "accordion.less"; 60 | @import "carousel.less"; 61 | @import "hero-unit.less"; 62 | 63 | // Utility classes 64 | @import "utilities.less"; // Has to be last to override when necessary 65 | -------------------------------------------------------------------------------- /web/less/breadcrumbs.less: -------------------------------------------------------------------------------- 1 | // 2 | // Breadcrumbs 3 | // -------------------------------------------------- 4 | 5 | 6 | .breadcrumb { 7 | padding: 8px 15px; 8 | margin: 0 0 @baseLineHeight; 9 | list-style: none; 10 | background-color: #f5f5f5; 11 | > li { 12 | display: inline-block; 13 | .ie7-inline-block(); 14 | > .divider { 15 | padding: 0 5px; 16 | color: #ccc; 17 | } 18 | } 19 | > .active { 20 | color: @grayLight; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /web/less/carousel.less: -------------------------------------------------------------------------------- 1 | // 2 | // Carousel 3 | // -------------------------------------------------- 4 | 5 | 6 | .carousel { 7 | position: relative; 8 | margin-bottom: @baseLineHeight; 9 | line-height: 1; 10 | } 11 | 12 | .carousel-inner { 13 | overflow: hidden; 14 | width: 100%; 15 | position: relative; 16 | } 17 | 18 | .carousel-inner { 19 | 20 | > .item { 21 | display: none; 22 | position: relative; 23 | .transition(.6s ease-in-out left); 24 | 25 | // Account for jankitude on images 26 | > img, 27 | > a > img { 28 | display: block; 29 | line-height: 1; 30 | } 31 | } 32 | 33 | > .active, 34 | > .next, 35 | > .prev { display: block; } 36 | 37 | > .active { 38 | left: 0; 39 | } 40 | 41 | > .next, 42 | > .prev { 43 | position: absolute; 44 | top: 0; 45 | width: 100%; 46 | } 47 | 48 | > .next { 49 | left: 100%; 50 | } 51 | > .prev { 52 | left: -100%; 53 | } 54 | > .next.left, 55 | > .prev.right { 56 | left: 0; 57 | } 58 | 59 | > .active.left { 60 | left: -100%; 61 | } 62 | > .active.right { 63 | left: 100%; 64 | } 65 | 66 | } 67 | 68 | // Left/right controls for nav 69 | // --------------------------- 70 | 71 | .carousel-control { 72 | position: absolute; 73 | top: 40%; 74 | left: 15px; 75 | width: 40px; 76 | height: 40px; 77 | margin-top: -20px; 78 | font-size: 60px; 79 | font-weight: 100; 80 | line-height: 30px; 81 | color: @white; 82 | text-align: center; 83 | background: @grayDarker; 84 | border: 3px solid @white; 85 | .opacity(50); 86 | 87 | // we can't have this transition here 88 | // because webkit cancels the carousel 89 | // animation if you trip this while 90 | // in the middle of another animation 91 | // ;_; 92 | // .transition(opacity .2s linear); 93 | 94 | // Reposition the right one 95 | &.right { 96 | left: auto; 97 | right: 15px; 98 | } 99 | 100 | // Hover/focus state 101 | &:hover, 102 | &:focus { 103 | color: @white; 104 | text-decoration: none; 105 | .opacity(90); 106 | } 107 | } 108 | 109 | // Carousel indicator pips 110 | // ----------------------------- 111 | .carousel-indicators { 112 | position: absolute; 113 | top: 15px; 114 | right: 15px; 115 | z-index: 5; 116 | margin: 0; 117 | list-style: none; 118 | 119 | li { 120 | display: block; 121 | float: left; 122 | width: 10px; 123 | height: 10px; 124 | margin-left: 5px; 125 | text-indent: -999px; 126 | background-color: #ccc; 127 | background-color: rgba(255,255,255,.25); 128 | } 129 | .active { 130 | background-color: #fff; 131 | } 132 | } 133 | 134 | // Caption for text below images 135 | // ----------------------------- 136 | 137 | .carousel-caption { 138 | position: absolute; 139 | left: 0; 140 | right: 0; 141 | bottom: 0; 142 | padding: 15px; 143 | background: @grayDark; 144 | background: rgba(0,0,0,.75); 145 | } 146 | .carousel-caption h4, 147 | .carousel-caption p { 148 | color: @white; 149 | line-height: @baseLineHeight; 150 | } 151 | .carousel-caption h4 { 152 | margin: 0 0 5px; 153 | } 154 | .carousel-caption p { 155 | margin-bottom: 0; 156 | } 157 | -------------------------------------------------------------------------------- /web/less/close.less: -------------------------------------------------------------------------------- 1 | // 2 | // Close icons 3 | // -------------------------------------------------- 4 | 5 | 6 | .close { 7 | float: right; 8 | font-size: 20px; 9 | font-weight: bold; 10 | line-height: @baseLineHeight; 11 | color: @black; 12 | .opacity(20); 13 | &:hover, 14 | &:focus { 15 | color: @black; 16 | text-decoration: none; 17 | cursor: pointer; 18 | .opacity(40); 19 | } 20 | } 21 | 22 | // Additional properties for button version 23 | // iOS requires the button element instead of an anchor tag. 24 | // If you want the anchor version, it requires `href="#"`. 25 | button.close { 26 | padding: 0; 27 | cursor: pointer; 28 | background: transparent; 29 | border: 0; 30 | -webkit-appearance: none; 31 | } -------------------------------------------------------------------------------- /web/less/code.less: -------------------------------------------------------------------------------- 1 | // 2 | // Code (inline and blocK) 3 | // -------------------------------------------------- 4 | 5 | 6 | // Inline and block code styles 7 | code, 8 | pre { 9 | padding: 0 3px 2px; 10 | #font > #family > .monospace; 11 | font-size: @baseFontSize - 2; 12 | color: @grayDark; 13 | } 14 | 15 | // Inline code 16 | code { 17 | padding: 2px 4px; 18 | color: #d14; 19 | background-color: #f7f7f9; 20 | border: 1px solid #e1e1e8; 21 | white-space: nowrap; 22 | } 23 | 24 | // Blocks of code 25 | pre { 26 | display: block; 27 | padding: (@baseLineHeight - 1) / 2; 28 | margin: 0 0 @baseLineHeight / 2; 29 | font-size: @baseFontSize - 1; // 14px to 13px 30 | line-height: @baseLineHeight; 31 | word-break: break-all; 32 | word-wrap: break-word; 33 | white-space: pre; 34 | white-space: pre-wrap; 35 | background-color: #f5f5f5; 36 | border: 1px solid #ccc; // fallback for IE7-8 37 | border: 1px solid rgba(0,0,0,.15); 38 | 39 | // Make prettyprint styles more spaced out for readability 40 | &.prettyprint { 41 | margin-bottom: @baseLineHeight; 42 | } 43 | 44 | // Account for some code outputs that place code tags in pre tags 45 | code { 46 | padding: 0; 47 | color: inherit; 48 | white-space: pre; 49 | white-space: pre-wrap; 50 | background-color: transparent; 51 | border: 0; 52 | } 53 | } 54 | 55 | // Enable scrollable blocks of code 56 | .pre-scrollable { 57 | max-height: 340px; 58 | overflow-y: scroll; 59 | } -------------------------------------------------------------------------------- /web/less/component-animations.less: -------------------------------------------------------------------------------- 1 | // 2 | // Component animations 3 | // -------------------------------------------------- 4 | 5 | 6 | .fade { 7 | opacity: 0; 8 | .transition(opacity .15s linear); 9 | &.in { 10 | opacity: 1; 11 | } 12 | } 13 | 14 | .collapse { 15 | position: relative; 16 | height: 0; 17 | overflow: hidden; 18 | .transition(height .35s ease); 19 | &.in { 20 | height: auto; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /web/less/font-awesome/bootstrap.less: -------------------------------------------------------------------------------- 1 | /* BOOTSTRAP SPECIFIC CLASSES 2 | * -------------------------- */ 3 | 4 | /* Bootstrap 2.0 sprites.less reset */ 5 | [class^="icon-"], 6 | [class*=" icon-"] { 7 | display: inline; 8 | width: auto; 9 | height: auto; 10 | line-height: normal; 11 | vertical-align: baseline; 12 | background-image: none; 13 | background-position: 0% 0%; 14 | background-repeat: repeat; 15 | margin-top: 0; 16 | } 17 | 18 | /* more sprites.less reset */ 19 | .icon-white, 20 | .nav-pills > .active > a > [class^="icon-"], 21 | .nav-pills > .active > a > [class*=" icon-"], 22 | .nav-list > .active > a > [class^="icon-"], 23 | .nav-list > .active > a > [class*=" icon-"], 24 | .navbar-inverse .nav > .active > a > [class^="icon-"], 25 | .navbar-inverse .nav > .active > a > [class*=" icon-"], 26 | .dropdown-menu > li > a:hover > [class^="icon-"], 27 | .dropdown-menu > li > a:hover > [class*=" icon-"], 28 | .dropdown-menu > .active > a > [class^="icon-"], 29 | .dropdown-menu > .active > a > [class*=" icon-"], 30 | .dropdown-submenu:hover > a > [class^="icon-"], 31 | .dropdown-submenu:hover > a > [class*=" icon-"] { 32 | background-image: none; 33 | } 34 | 35 | 36 | /* keeps Bootstrap styles with and without icons the same */ 37 | .btn, .nav { 38 | [class^="icon-"], 39 | [class*=" icon-"] { 40 | // display: inline; 41 | &.icon-large { line-height: .9em; } 42 | &.icon-spin { display: inline-block; } 43 | } 44 | } 45 | .nav-tabs, .nav-pills { 46 | [class^="icon-"], 47 | [class*=" icon-"] { 48 | &, &.icon-large { line-height: .9em; } 49 | } 50 | } 51 | .btn { 52 | [class^="icon-"], 53 | [class*=" icon-"] { 54 | &.pull-left, &.pull-right { 55 | &.icon-2x { margin-top: .18em; } 56 | } 57 | &.icon-spin.icon-large { line-height: .8em; } 58 | } 59 | } 60 | .btn.btn-small { 61 | [class^="icon-"], 62 | [class*=" icon-"] { 63 | &.pull-left, &.pull-right { 64 | &.icon-2x { margin-top: .25em; } 65 | } 66 | } 67 | } 68 | .btn.btn-large { 69 | [class^="icon-"], 70 | [class*=" icon-"] { 71 | margin-top: 0; // overrides bootstrap default 72 | &.pull-left, &.pull-right { 73 | &.icon-2x { margin-top: .05em; } 74 | } 75 | &.pull-left.icon-2x { margin-right: .2em; } 76 | &.pull-right.icon-2x { margin-left: .2em; } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /web/less/font-awesome/core.less: -------------------------------------------------------------------------------- 1 | /* FONT AWESOME CORE 2 | * -------------------------- */ 3 | 4 | [class^="icon-"], 5 | [class*=" icon-"] { 6 | font-family: FontAwesome; 7 | font-weight: normal; 8 | font-style: normal; 9 | text-decoration: inherit; 10 | -webkit-font-smoothing: antialiased; 11 | *margin-right: .3em; // fixes ie7 issues 12 | } 13 | 14 | [class^="icon-"]:before, 15 | [class*=" icon-"]:before { 16 | text-decoration: inherit; 17 | display: inline-block; 18 | speak: none; 19 | } 20 | 21 | /* makes the font 33% larger relative to the icon container */ 22 | .icon-large:before { 23 | vertical-align: -10%; 24 | font-size: 4/3em; 25 | } 26 | 27 | /* makes sure icons active on rollover in links */ 28 | a { 29 | [class^="icon-"], 30 | [class*=" icon-"] { 31 | &, &:before { display: inline; } 32 | } 33 | } 34 | 35 | /* increased font size for icon-large */ 36 | [class^="icon-"], 37 | [class*=" icon-"] { 38 | &.icon-fixed-width { 39 | display: inline-block; 40 | width: 18/14em; 41 | text-align: center; 42 | &.icon-large { 43 | width: 22/14em; 44 | } 45 | } 46 | } 47 | 48 | ul.icons-ul { 49 | list-style-type: none; 50 | text-indent: -10/14em; 51 | margin-left: 30/14em; 52 | 53 | > li { 54 | .icon-li { 55 | width: 10/14em; 56 | display: inline-block; 57 | text-align: center; 58 | } 59 | } 60 | } 61 | 62 | // allows usage of the hide class directly on font awesome icons 63 | [class^="icon-"], 64 | [class*=" icon-"] { 65 | &.hide { 66 | display: none; 67 | } 68 | } 69 | 70 | .icon-muted { color: @iconMuted; } 71 | .icon-light { color: @iconLight; } 72 | .icon-dark { color: @iconDark; } 73 | 74 | // Icon Borders 75 | // ------------------------- 76 | 77 | .icon-border { 78 | border: solid 1px @borderColor; 79 | padding: .2em .25em .15em; 80 | .border-radius(3px); 81 | } 82 | 83 | // Icon Sizes 84 | // ------------------------- 85 | 86 | .icon-2x { 87 | font-size: 2em; 88 | &.icon-border { 89 | border-width: 2px; 90 | .border-radius(4px); 91 | } 92 | } 93 | .icon-3x { 94 | font-size: 3em; 95 | &.icon-border { 96 | border-width: 3px; 97 | .border-radius(5px); 98 | } 99 | } 100 | .icon-4x { 101 | font-size: 4em; 102 | &.icon-border { 103 | border-width: 4px; 104 | .border-radius(6px); 105 | } 106 | } 107 | 108 | .icon-5x { 109 | font-size: 5em; 110 | &.icon-border { 111 | border-width: 5px; 112 | .border-radius(7px); 113 | } 114 | } 115 | 116 | 117 | // Floats & Margins 118 | // ------------------------- 119 | 120 | // Quick floats 121 | .pull-right { float: right; } 122 | .pull-left { float: left; } 123 | 124 | [class^="icon-"], 125 | [class*=" icon-"] { 126 | &.pull-left { 127 | margin-right: .3em; 128 | } 129 | &.pull-right { 130 | margin-left: .3em; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /web/less/font-awesome/extras.less: -------------------------------------------------------------------------------- 1 | /* EXTRAS 2 | * -------------------------- */ 3 | 4 | /* Stacked and layered icon */ 5 | .icon-stack(); 6 | 7 | /* Animated rotating icon */ 8 | .icon-spin { 9 | display: inline-block; 10 | -moz-animation: spin 2s infinite linear; 11 | -o-animation: spin 2s infinite linear; 12 | -webkit-animation: spin 2s infinite linear; 13 | animation: spin 2s infinite linear; 14 | } 15 | 16 | @-moz-keyframes spin { 17 | 0% { -moz-transform: rotate(0deg); } 18 | 100% { -moz-transform: rotate(359deg); } 19 | } 20 | @-webkit-keyframes spin { 21 | 0% { -webkit-transform: rotate(0deg); } 22 | 100% { -webkit-transform: rotate(359deg); } 23 | } 24 | @-o-keyframes spin { 25 | 0% { -o-transform: rotate(0deg); } 26 | 100% { -o-transform: rotate(359deg); } 27 | } 28 | @-ms-keyframes spin { 29 | 0% { -ms-transform: rotate(0deg); } 30 | 100% { -ms-transform: rotate(359deg); } 31 | } 32 | @keyframes spin { 33 | 0% { transform: rotate(0deg); } 34 | 100% { transform: rotate(359deg); } 35 | } 36 | 37 | /* Icon rotations and mirroring */ 38 | .icon-rotate-90:before{ 39 | -webkit-transform: rotate(90deg); 40 | -moz-transform: rotate(90deg); 41 | -ms-transform: rotate(90deg); 42 | -o-transform: rotate(90deg); 43 | transform: rotate(90deg); 44 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1); 45 | } 46 | 47 | .icon-rotate-180:before{ 48 | -webkit-transform: rotate(180deg); 49 | -moz-transform: rotate(180deg); 50 | -ms-transform: rotate(180deg); 51 | -o-transform: rotate(180deg); 52 | transform: rotate(180deg); 53 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2); 54 | } 55 | 56 | .icon-rotate-270:before{ 57 | -webkit-transform: rotate(270deg); 58 | -moz-transform: rotate(270deg); 59 | -ms-transform: rotate(270deg); 60 | -o-transform: rotate(270deg); 61 | transform: rotate(270deg); 62 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3); 63 | } 64 | 65 | .icon-flip-horizontal:before { 66 | -webkit-transform: scale(-1, 1); 67 | -moz-transform: scale(-1, 1); 68 | -ms-transform: scale(-1, 1); 69 | -o-transform: scale(-1, 1); 70 | transform: scale(-1, 1); 71 | } 72 | 73 | .icon-flip-vertical:before { 74 | -webkit-transform: scale(1, -1); 75 | -moz-transform: scale(1, -1); 76 | -ms-transform: scale(1, -1); 77 | -o-transform: scale(1, -1); 78 | transform: scale(1, -1); 79 | } -------------------------------------------------------------------------------- /web/less/font-awesome/font-awesome.less: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 3.1.0 3 | * the iconic font designed for Bootstrap 4 | * ------------------------------------------------------- 5 | * The full suite of pictographic icons, examples, and documentation 6 | * can be found at: http://fontawesome.io 7 | * 8 | * License 9 | * ------------------------------------------------------- 10 | * - The Font Awesome font is licensed under the SIL Open Font License v1.1 - 11 | * http://scripts.sil.org/OFL 12 | * - Font Awesome CSS, LESS, and SASS files are licensed under the MIT License - 13 | * http://opensource.org/licenses/mit-license.html 14 | * - Font Awesome documentation licensed under CC BY 3.0 License - 15 | * http://creativecommons.org/licenses/by/3.0/ 16 | * - Attribution is no longer required in Font Awesome 3.0, but much appreciated: 17 | * "Font Awesome by Dave Gandy - http://fontawesome.io" 18 | 19 | * Contact 20 | * ------------------------------------------------------- 21 | * Email: dave@fontawesome.io 22 | * Twitter: http://twitter.com/fortaweso_me 23 | * Work: Lead Product Designer @ http://kyruus.com 24 | */ 25 | 26 | @import "variables.less"; 27 | @import "mixins.less"; 28 | @import "path.less"; 29 | @import "core.less"; 30 | @import "bootstrap.less"; 31 | @import "extras.less"; 32 | @import "icons.less"; 33 | -------------------------------------------------------------------------------- /web/less/font-awesome/mixins.less: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | .border-radius(@radius) { 5 | -webkit-border-radius: @radius; 6 | -moz-border-radius: @radius; 7 | border-radius: @radius; 8 | } 9 | 10 | .icon-stack(@width: 2em, @height: 2em, @top-font-size: 1em, @base-font-size: 2em) { 11 | .icon-stack { 12 | position: relative; 13 | display: inline-block; 14 | width: @width; 15 | height: @height; 16 | line-height: @width; 17 | vertical-align: -35%; 18 | [class^="icon-"], 19 | [class*=" icon-"] { 20 | display: block; 21 | text-align: center; 22 | position: absolute; 23 | width: 100%; 24 | height: 100%; 25 | font-size: @top-font-size; 26 | line-height: inherit; 27 | *line-height: @height; 28 | } 29 | .icon-stack-base { 30 | font-size: @base-font-size; 31 | *line-height: @height / @base-font-size; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /web/less/font-awesome/path.less: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: url('@{FontAwesomePath}/fontawesome-webfont.eot?v=@{FontAwesomeVersion}'); 7 | src: url('@{FontAwesomePath}/fontawesome-webfont.eot?#iefix&v=@{FontAwesomeVersion}') format('embedded-opentype'), 8 | url('@{FontAwesomePath}/fontawesome-webfont.woff?v=@{FontAwesomeVersion}') format('woff'), 9 | url('@{FontAwesomePath}/fontawesome-webfont.ttf?v=@{FontAwesomeVersion}') format('truetype'), 10 | url('@{FontAwesomePath}/fontawesome-webfont.svg#fontawesomeregular?v=@{FontAwesomeVersion}') format('svg'); 11 | // src: url('@{FontAwesomePath}/FontAwesome.otf') format('opentype'); // used when developing fonts 12 | 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | -------------------------------------------------------------------------------- /web/less/font-awesome/variables.less: -------------------------------------------------------------------------------- 1 | // Variables 2 | // -------------------------- 3 | 4 | @FontAwesomePath: "../font"; 5 | @FontAwesomeVersion: "3.1.0"; 6 | @borderColor: #eee; 7 | @iconMuted: #eee; 8 | @iconLight: #fff; 9 | @iconDark: #333; -------------------------------------------------------------------------------- /web/less/grid.less: -------------------------------------------------------------------------------- 1 | // 2 | // Grid system 3 | // -------------------------------------------------- 4 | 5 | 6 | // Fixed (940px) 7 | #grid > .core(@gridColumnWidth, @gridGutterWidth); 8 | 9 | // Fluid (940px) 10 | #grid > .fluid(@fluidGridColumnWidth, @fluidGridGutterWidth); 11 | 12 | // Reset utility classes due to specificity 13 | [class*="span"].hide, 14 | .row-fluid [class*="span"].hide { 15 | display: none; 16 | } 17 | 18 | [class*="span"].pull-right, 19 | .row-fluid [class*="span"].pull-right { 20 | float: right; 21 | } 22 | -------------------------------------------------------------------------------- /web/less/hero-unit.less: -------------------------------------------------------------------------------- 1 | // 2 | // Hero unit 3 | // -------------------------------------------------- 4 | 5 | 6 | .hero-unit { 7 | padding: 60px; 8 | margin-bottom: 30px; 9 | font-size: 18px; 10 | font-weight: 200; 11 | line-height: @baseLineHeight * 1.5; 12 | color: @heroUnitLeadColor; 13 | background-color: @heroUnitBackground; 14 | h1 { 15 | margin-bottom: 0; 16 | font-size: 60px; 17 | line-height: 1; 18 | color: @heroUnitHeadingColor; 19 | letter-spacing: -1px; 20 | } 21 | li { 22 | line-height: @baseLineHeight * 1.5; // Reset since we specify in type.less 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /web/less/labels-badges.less: -------------------------------------------------------------------------------- 1 | // 2 | // Labels and badges 3 | // -------------------------------------------------- 4 | 5 | 6 | // Base classes 7 | .label, 8 | .badge { 9 | display: inline-block; 10 | padding: 2px 4px; 11 | font-size: @baseFontSize * .846; 12 | font-weight: bold; 13 | line-height: 14px; // ensure proper line-height if floated 14 | color: @white; 15 | vertical-align: baseline; 16 | white-space: nowrap; 17 | background-color: @grayLight; 18 | } 19 | // Set unique padding and border-radii 20 | .badge { 21 | padding-left: 9px; 22 | padding-right: 9px; 23 | } 24 | 25 | // Empty labels/badges collapse 26 | .label, 27 | .badge { 28 | &:empty { 29 | display: none; 30 | } 31 | } 32 | 33 | // Hover/focus state, but only for links 34 | a { 35 | &.label:hover, 36 | &.label:focus, 37 | &.badge:hover, 38 | &.badge:focus { 39 | color: @white; 40 | text-decoration: none; 41 | cursor: pointer; 42 | } 43 | } 44 | 45 | // Colors 46 | // Only give background-color difference to links (and to simplify, we don't qualifty with `a` but [href] attribute) 47 | .label, 48 | .badge { 49 | // Important (red) 50 | &-important { background-color: @errorText; } 51 | &-important[href] { background-color: darken(@errorText, 10%); } 52 | // Warnings (orange) 53 | &-warning { background-color: @orange; } 54 | &-warning[href] { background-color: darken(@orange, 10%); } 55 | // Success (green) 56 | &-success { background-color: @successText; } 57 | &-success[href] { background-color: darken(@successText, 10%); } 58 | // Info (turquoise) 59 | &-info { background-color: @infoText; } 60 | &-info[href] { background-color: darken(@infoText, 10%); } 61 | // Inverse (black) 62 | &-inverse { background-color: @grayDark; } 63 | &-inverse[href] { background-color: darken(@grayDark, 10%); } 64 | } 65 | 66 | // Quick fix for labels/badges in buttons 67 | .btn { 68 | .label, 69 | .badge { 70 | position: relative; 71 | top: -1px; 72 | } 73 | } 74 | .btn-mini { 75 | .label, 76 | .badge { 77 | top: 0; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /web/less/layouts.less: -------------------------------------------------------------------------------- 1 | // 2 | // Layouts 3 | // -------------------------------------------------- 4 | 5 | 6 | // Container (centered, fixed-width layouts) 7 | .container { 8 | .container-fixed(); 9 | } 10 | 11 | // Fluid layouts (left aligned, with sidebar, min- & max-width content) 12 | .container-fluid { 13 | padding-right: @gridGutterWidth; 14 | padding-left: @gridGutterWidth; 15 | .clearfix(); 16 | } -------------------------------------------------------------------------------- /web/less/media.less: -------------------------------------------------------------------------------- 1 | // Media objects 2 | // Source: http://stubbornella.org/content/?p=497 3 | // -------------------------------------------------- 4 | 5 | 6 | // Common styles 7 | // ------------------------- 8 | 9 | // Clear the floats 10 | .media, 11 | .media-body { 12 | overflow: hidden; 13 | *overflow: visible; 14 | zoom: 1; 15 | } 16 | 17 | // Proper spacing between instances of .media 18 | .media, 19 | .media .media { 20 | margin-top: 15px; 21 | } 22 | .media:first-child { 23 | margin-top: 0; 24 | } 25 | 26 | // For images and videos, set to block 27 | .media-object { 28 | display: block; 29 | } 30 | 31 | // Reset margins on headings for tighter default spacing 32 | .media-heading { 33 | margin: 0 0 5px; 34 | } 35 | 36 | 37 | // Media image alignment 38 | // ------------------------- 39 | 40 | .media > .pull-left { 41 | margin-right: 10px; 42 | } 43 | .media > .pull-right { 44 | margin-left: 10px; 45 | } 46 | 47 | 48 | // Media list variation 49 | // ------------------------- 50 | 51 | // Undo default ul/ol styles 52 | .media-list { 53 | margin-left: 0; 54 | list-style: none; 55 | } 56 | -------------------------------------------------------------------------------- /web/less/modals.less: -------------------------------------------------------------------------------- 1 | // 2 | // Modals 3 | // -------------------------------------------------- 4 | 5 | // Background 6 | .modal-backdrop { 7 | position: fixed; 8 | top: 0; 9 | right: 0; 10 | bottom: 0; 11 | left: 0; 12 | z-index: @zindexModalBackdrop; 13 | background-color: @black; 14 | // Fade for backdrop 15 | &.fade { opacity: 0; } 16 | } 17 | 18 | .modal-backdrop, 19 | .modal-backdrop.fade.in { 20 | .opacity(80); 21 | } 22 | 23 | // Base modal 24 | .modal { 25 | position: fixed; 26 | top: 10%; 27 | left: 50%; 28 | z-index: @zindexModal; 29 | width: 560px; 30 | margin-left: -280px; 31 | background-color: @white; 32 | border: 1px solid #999; 33 | border: 1px solid rgba(0,0,0,.3); 34 | *border: 1px solid #999; /* IE6-7 */ 35 | .background-clip(padding-box); 36 | // Remove focus outline from opened modal 37 | outline: none; 38 | 39 | &.fade { 40 | .transition(e('opacity .3s linear, top .3s ease-out')); 41 | top: -25%; 42 | } 43 | &.fade.in { top: 10%; } 44 | } 45 | .modal-header { 46 | padding: 9px 15px; 47 | border-bottom: 1px solid #eee; 48 | // Close icon 49 | .close { margin-top: 2px; } 50 | // Heading 51 | h3 { 52 | margin: 0; 53 | line-height: 30px; 54 | } 55 | } 56 | 57 | // Body (where all modal content resides) 58 | .modal-body { 59 | position: relative; 60 | overflow-y: auto; 61 | max-height: 400px; 62 | padding: 15px; 63 | } 64 | // Remove bottom margin if need be 65 | .modal-form { 66 | margin-bottom: 0; 67 | } 68 | 69 | // Footer (for actions) 70 | .modal-footer { 71 | padding: 14px 15px 15px; 72 | margin-bottom: 0; 73 | text-align: right; // right align buttons 74 | background-color: #f5f5f5; 75 | border-top: 1px solid #ddd; 76 | .clearfix(); // clear it in case folks use .pull-* classes on buttons 77 | 78 | // Properly space out buttons 79 | .btn + .btn { 80 | margin-left: 5px; 81 | margin-bottom: 0; // account for input[type="submit"] which gets the bottom margin like all other inputs 82 | } 83 | // but override that for button groups 84 | .btn-group .btn + .btn { 85 | margin-left: -1px; 86 | } 87 | // and override it for block buttons as well 88 | .btn-block + .btn-block { 89 | margin-left: 0; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /web/less/pager.less: -------------------------------------------------------------------------------- 1 | // 2 | // Pager pagination 3 | // -------------------------------------------------- 4 | 5 | 6 | .pager { 7 | margin: @baseLineHeight 0; 8 | list-style: none; 9 | text-align: center; 10 | .clearfix(); 11 | } 12 | .pager li { 13 | display: inline; 14 | } 15 | .pager li > a, 16 | .pager li > span { 17 | display: inline-block; 18 | padding: 5px 14px; 19 | background-color: #fff; 20 | border: 1px solid #ddd; 21 | } 22 | .pager li > a:hover, 23 | .pager li > a:focus { 24 | text-decoration: none; 25 | background-color: #f5f5f5; 26 | } 27 | .pager .next > a, 28 | .pager .next > span { 29 | float: right; 30 | } 31 | .pager .previous > a, 32 | .pager .previous > span { 33 | float: left; 34 | } 35 | .pager .disabled > a, 36 | .pager .disabled > a:hover, 37 | .pager .disabled > a:focus, 38 | .pager .disabled > span { 39 | color: @grayLight; 40 | background-color: #fff; 41 | cursor: default; 42 | } -------------------------------------------------------------------------------- /web/less/pagination.less: -------------------------------------------------------------------------------- 1 | // 2 | // Pagination (multiple pages) 3 | // -------------------------------------------------- 4 | 5 | // Space out pagination from surrounding content 6 | .pagination { 7 | margin: @baseLineHeight 0; 8 | } 9 | 10 | .pagination ul { 11 | // Allow for text-based alignment 12 | display: inline-block; 13 | .ie7-inline-block(); 14 | // Reset default ul styles 15 | margin-left: 0; 16 | margin-bottom: 0; 17 | } 18 | .pagination ul > li { 19 | display: inline; // Remove list-style and block-level defaults 20 | } 21 | .pagination ul > li > a, 22 | .pagination ul > li > span { 23 | float: left; // Collapse white-space 24 | padding: 4px 12px; 25 | line-height: @baseLineHeight; 26 | text-decoration: none; 27 | background-color: @paginationBackground; 28 | border: 1px solid @paginationBorder; 29 | border-left-width: 0; 30 | } 31 | .pagination ul > li > a:hover, 32 | .pagination ul > li > a:focus, 33 | .pagination ul > .active > a, 34 | .pagination ul > .active > span { 35 | background-color: @paginationActiveBackground; 36 | } 37 | .pagination ul > .active > a, 38 | .pagination ul > .active > span { 39 | color: @grayLight; 40 | cursor: default; 41 | } 42 | .pagination ul > .disabled > span, 43 | .pagination ul > .disabled > a, 44 | .pagination ul > .disabled > a:hover, 45 | .pagination ul > .disabled > a:focus { 46 | color: @grayLight; 47 | background-color: transparent; 48 | cursor: default; 49 | } 50 | .pagination ul > li:first-child > a, 51 | .pagination ul > li:first-child > span { 52 | border-left-width: 1px; 53 | } 54 | 55 | 56 | // Alignment 57 | // -------------------------------------------------- 58 | 59 | .pagination-centered { 60 | text-align: center; 61 | } 62 | .pagination-right { 63 | text-align: right; 64 | } 65 | 66 | 67 | // Sizing 68 | // -------------------------------------------------- 69 | 70 | // Large 71 | .pagination-large { 72 | ul > li > a, 73 | ul > li > span { 74 | padding: @paddingLarge; 75 | font-size: @fontSizeLarge; 76 | } 77 | 78 | } 79 | 80 | // Small 81 | .pagination-small { 82 | ul > li > a, 83 | ul > li > span { 84 | padding: @paddingSmall; 85 | font-size: @fontSizeSmall; 86 | } 87 | } 88 | // Mini 89 | .pagination-mini { 90 | ul > li > a, 91 | ul > li > span { 92 | padding: @paddingMini; 93 | font-size: @fontSizeMini; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /web/less/popovers.less: -------------------------------------------------------------------------------- 1 | // 2 | // Popovers 3 | // -------------------------------------------------- 4 | 5 | 6 | .popover { 7 | position: absolute; 8 | top: 0; 9 | left: 0; 10 | z-index: @zindexPopover; 11 | display: none; 12 | max-width: 276px; 13 | padding: 1px; 14 | text-align: left; // Reset given new insertion method 15 | background-color: @popoverBackground; 16 | -webkit-background-clip: padding-box; 17 | -moz-background-clip: padding; 18 | background-clip: padding-box; 19 | border: 1px solid #ccc; 20 | border: 1px solid rgba(0,0,0,.2); 21 | 22 | // Overrides for proper insertion 23 | white-space: normal; 24 | 25 | // Offset the popover to account for the popover arrow 26 | &.top { margin-top: -10px; } 27 | &.right { margin-left: 10px; } 28 | &.bottom { margin-top: 10px; } 29 | &.left { margin-left: -10px; } 30 | } 31 | 32 | .popover-title { 33 | margin: 0; // reset heading margin 34 | padding: 8px 14px; 35 | font-size: 14px; 36 | font-weight: normal; 37 | line-height: 18px; 38 | background-color: @popoverTitleBackground; 39 | border-bottom: 1px solid darken(@popoverTitleBackground, 5%); 40 | 41 | &:empty { 42 | display: none; 43 | } 44 | } 45 | 46 | .popover-content { 47 | padding: 9px 14px; 48 | } 49 | 50 | // Arrows 51 | // 52 | // .arrow is outer, .arrow:after is inner 53 | 54 | .popover .arrow, 55 | .popover .arrow:after { 56 | position: absolute; 57 | display: block; 58 | width: 0; 59 | height: 0; 60 | border-color: transparent; 61 | border-style: solid; 62 | } 63 | .popover .arrow { 64 | border-width: @popoverArrowOuterWidth; 65 | } 66 | .popover .arrow:after { 67 | border-width: @popoverArrowWidth; 68 | content: ""; 69 | } 70 | 71 | .popover { 72 | &.top .arrow { 73 | left: 50%; 74 | margin-left: -@popoverArrowOuterWidth; 75 | border-bottom-width: 0; 76 | border-top-color: #999; // IE8 fallback 77 | border-top-color: @popoverArrowOuterColor; 78 | bottom: -@popoverArrowOuterWidth; 79 | &:after { 80 | bottom: 1px; 81 | margin-left: -@popoverArrowWidth; 82 | border-bottom-width: 0; 83 | border-top-color: @popoverArrowColor; 84 | } 85 | } 86 | &.right .arrow { 87 | top: 50%; 88 | left: -@popoverArrowOuterWidth; 89 | margin-top: -@popoverArrowOuterWidth; 90 | border-left-width: 0; 91 | border-right-color: #999; // IE8 fallback 92 | border-right-color: @popoverArrowOuterColor; 93 | &:after { 94 | left: 1px; 95 | bottom: -@popoverArrowWidth; 96 | border-left-width: 0; 97 | border-right-color: @popoverArrowColor; 98 | } 99 | } 100 | &.bottom .arrow { 101 | left: 50%; 102 | margin-left: -@popoverArrowOuterWidth; 103 | border-top-width: 0; 104 | border-bottom-color: #999; // IE8 fallback 105 | border-bottom-color: @popoverArrowOuterColor; 106 | top: -@popoverArrowOuterWidth; 107 | &:after { 108 | top: 1px; 109 | margin-left: -@popoverArrowWidth; 110 | border-top-width: 0; 111 | border-bottom-color: @popoverArrowColor; 112 | } 113 | } 114 | 115 | &.left .arrow { 116 | top: 50%; 117 | right: -@popoverArrowOuterWidth; 118 | margin-top: -@popoverArrowOuterWidth; 119 | border-right-width: 0; 120 | border-left-color: #999; // IE8 fallback 121 | border-left-color: @popoverArrowOuterColor; 122 | &:after { 123 | right: 1px; 124 | border-right-width: 0; 125 | border-left-color: @popoverArrowColor; 126 | bottom: -@popoverArrowWidth; 127 | } 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /web/less/progress-bars.less: -------------------------------------------------------------------------------- 1 | // 2 | // Progress bars 3 | // -------------------------------------------------- 4 | 5 | 6 | // ANIMATIONS 7 | // ---------- 8 | 9 | // Webkit 10 | @-webkit-keyframes progress-bar-stripes { 11 | from { background-position: 40px 0; } 12 | to { background-position: 0 0; } 13 | } 14 | 15 | // Firefox 16 | @-moz-keyframes progress-bar-stripes { 17 | from { background-position: 40px 0; } 18 | to { background-position: 0 0; } 19 | } 20 | 21 | // IE9 22 | @-ms-keyframes progress-bar-stripes { 23 | from { background-position: 40px 0; } 24 | to { background-position: 0 0; } 25 | } 26 | 27 | // Opera 28 | @-o-keyframes progress-bar-stripes { 29 | from { background-position: 0 0; } 30 | to { background-position: 40px 0; } 31 | } 32 | 33 | // Spec 34 | @keyframes progress-bar-stripes { 35 | from { background-position: 40px 0; } 36 | to { background-position: 0 0; } 37 | } 38 | 39 | 40 | 41 | // THE BARS 42 | // -------- 43 | 44 | // Outer container 45 | .progress { 46 | overflow: hidden; 47 | height: @baseLineHeight; 48 | margin-bottom: @baseLineHeight; 49 | #gradient > .vertical(#f5f5f5, #f9f9f9); 50 | } 51 | 52 | // Bar of progress 53 | .progress .bar { 54 | width: 0%; 55 | height: 100%; 56 | color: @white; 57 | float: left; 58 | font-size: 12px; 59 | text-align: center; 60 | #gradient > .vertical(#149bdf, #0480be); 61 | .box-sizing(border-box); 62 | .transition(width .6s ease); 63 | } 64 | 65 | // Striped bars 66 | .progress-striped .bar { 67 | #gradient > .striped(#149bdf); 68 | .background-size(40px 40px); 69 | } 70 | 71 | // Call animation for the active one 72 | .progress.active .bar { 73 | -webkit-animation: progress-bar-stripes 2s linear infinite; 74 | -moz-animation: progress-bar-stripes 2s linear infinite; 75 | -ms-animation: progress-bar-stripes 2s linear infinite; 76 | -o-animation: progress-bar-stripes 2s linear infinite; 77 | animation: progress-bar-stripes 2s linear infinite; 78 | } 79 | 80 | 81 | 82 | // COLORS 83 | // ------ 84 | 85 | // Danger (red) 86 | .progress-danger .bar, .progress .bar-danger { 87 | #gradient > .vertical(#ee5f5b, #c43c35); 88 | } 89 | .progress-danger.progress-striped .bar, .progress-striped .bar-danger { 90 | #gradient > .striped(#ee5f5b); 91 | } 92 | 93 | // Success (green) 94 | .progress-success .bar, .progress .bar-success { 95 | #gradient > .vertical(#62c462, #57a957); 96 | } 97 | .progress-success.progress-striped .bar, .progress-striped .bar-success { 98 | #gradient > .striped(#62c462); 99 | } 100 | 101 | // Info (teal) 102 | .progress-info .bar, .progress .bar-info { 103 | #gradient > .vertical(#5bc0de, #339bb9); 104 | } 105 | .progress-info.progress-striped .bar, .progress-striped .bar-info { 106 | #gradient > .striped(#5bc0de); 107 | } 108 | 109 | // Warning (orange) 110 | .progress-warning .bar, .progress .bar-warning { 111 | #gradient > .vertical(lighten(@orange, 15%), @orange); 112 | } 113 | .progress-warning.progress-striped .bar, .progress-striped .bar-warning { 114 | #gradient > .striped(lighten(@orange, 15%)); 115 | } 116 | -------------------------------------------------------------------------------- /web/less/responsive-1200px-min.less: -------------------------------------------------------------------------------- 1 | // 2 | // Responsive: Large desktop and up 3 | // -------------------------------------------------- 4 | 5 | 6 | @media (min-width: 1200px) { 7 | 8 | // Fixed grid 9 | #grid > .core(@gridColumnWidth1200, @gridGutterWidth1200); 10 | 11 | // Fluid grid 12 | #grid > .fluid(@fluidGridColumnWidth1200, @fluidGridGutterWidth1200); 13 | 14 | // Input grid 15 | #grid > .input(@gridColumnWidth1200, @gridGutterWidth1200); 16 | 17 | // Thumbnails 18 | .thumbnails { 19 | margin-left: -@gridGutterWidth1200; 20 | } 21 | .thumbnails > li { 22 | margin-left: @gridGutterWidth1200; 23 | } 24 | .row-fluid .thumbnails { 25 | margin-left: 0; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /web/less/responsive-768px-979px.less: -------------------------------------------------------------------------------- 1 | // 2 | // Responsive: Tablet to desktop 3 | // -------------------------------------------------- 4 | 5 | 6 | @media (min-width: 768px) and (max-width: 979px) { 7 | 8 | // Fixed grid 9 | #grid > .core(@gridColumnWidth768, @gridGutterWidth768); 10 | 11 | // Fluid grid 12 | #grid > .fluid(@fluidGridColumnWidth768, @fluidGridGutterWidth768); 13 | 14 | // Input grid 15 | #grid > .input(@gridColumnWidth768, @gridGutterWidth768); 16 | 17 | // No need to reset .thumbnails here since it's the same @gridGutterWidth 18 | 19 | } 20 | -------------------------------------------------------------------------------- /web/less/responsive-utilities.less: -------------------------------------------------------------------------------- 1 | // 2 | // Responsive: Utility classes 3 | // -------------------------------------------------- 4 | 5 | 6 | // IE10 Metro responsive 7 | // Required for Windows 8 Metro split-screen snapping with IE10 8 | // Source: http://timkadlec.com/2012/10/ie10-snap-mode-and-responsive-design/ 9 | @-ms-viewport{ 10 | width: device-width; 11 | } 12 | 13 | // Hide from screenreaders and browsers 14 | // Credit: HTML5 Boilerplate 15 | .hidden { 16 | display: none; 17 | visibility: hidden; 18 | } 19 | 20 | // Visibility utilities 21 | 22 | // For desktops 23 | .visible-phone { display: none !important; } 24 | .visible-tablet { display: none !important; } 25 | .hidden-phone { } 26 | .hidden-tablet { } 27 | .hidden-desktop { display: none !important; } 28 | .visible-desktop { display: inherit !important; } 29 | 30 | // Tablets & small desktops only 31 | @media (min-width: 768px) and (max-width: 979px) { 32 | // Hide everything else 33 | .hidden-desktop { display: inherit !important; } 34 | .visible-desktop { display: none !important ; } 35 | // Show 36 | .visible-tablet { display: inherit !important; } 37 | // Hide 38 | .hidden-tablet { display: none !important; } 39 | } 40 | 41 | // Phones only 42 | @media (max-width: 767px) { 43 | // Hide everything else 44 | .hidden-desktop { display: inherit !important; } 45 | .visible-desktop { display: none !important; } 46 | // Show 47 | .visible-phone { display: inherit !important; } // Use inherit to restore previous behavior 48 | // Hide 49 | .hidden-phone { display: none !important; } 50 | } 51 | 52 | // Print utilities 53 | .visible-print { display: none !important; } 54 | .hidden-print { } 55 | 56 | @media print { 57 | .visible-print { display: inherit !important; } 58 | .hidden-print { display: none !important; } 59 | } 60 | -------------------------------------------------------------------------------- /web/less/responsive.less: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Responsive v2.3.2 3 | * 4 | * Copyright 2012 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 | */ 10 | 11 | 12 | // Responsive.less 13 | // For phone and tablet devices 14 | // ------------------------------------------------------------- 15 | 16 | 17 | // REPEAT VARIABLES & MIXINS 18 | // ------------------------- 19 | // Required since we compile the responsive stuff separately 20 | 21 | @import "variables.less"; // Modify this for custom colors, font-sizes, etc 22 | @import "mixins.less"; 23 | 24 | 25 | // RESPONSIVE CLASSES 26 | // ------------------ 27 | 28 | @import "responsive-utilities.less"; 29 | 30 | 31 | // MEDIA QUERIES 32 | // ------------------ 33 | 34 | // Large desktops 35 | @import "responsive-1200px-min.less"; 36 | 37 | // Tablets to regular desktops 38 | @import "responsive-768px-979px.less"; 39 | 40 | // Phones to portrait tablets and narrow desktops 41 | @import "responsive-767px-max.less"; 42 | 43 | 44 | // RESPONSIVE NAVBAR 45 | // ------------------ 46 | 47 | // From 979px and below, show a button to toggle navbar contents 48 | @import "responsive-navbar.less"; 49 | -------------------------------------------------------------------------------- /web/less/scaffolding.less: -------------------------------------------------------------------------------- 1 | // 2 | // Scaffolding 3 | // -------------------------------------------------- 4 | 5 | 6 | // Body reset 7 | // ------------------------- 8 | 9 | body { 10 | margin: 0; 11 | font-family: @baseFontFamily; 12 | font-size: @baseFontSize; 13 | line-height: @baseLineHeight; 14 | color: @textColor; 15 | background-color: @bodyBackground; 16 | } 17 | 18 | 19 | // Links 20 | // ------------------------- 21 | 22 | a { 23 | color: @linkColor; 24 | text-decoration: none; 25 | } 26 | a:hover, 27 | a:focus { 28 | color: @linkColorHover; 29 | text-decoration: underline; 30 | } 31 | 32 | 33 | // Images 34 | // ------------------------- 35 | 36 | // Add polaroid-esque trim 37 | .img-polaroid { 38 | padding: 4px; 39 | background-color: #fff; 40 | border: 1px solid #ccc; 41 | border: 1px solid rgba(0,0,0,.2); 42 | } 43 | -------------------------------------------------------------------------------- /web/less/tests/forms-responsive.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Bootstrap, from Twitter 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 19 | 20 | 21 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |
    36 | 37 | 40 | 41 |

    Vertical alignment

    42 | 43 | 44 | span1 45 | 46 |

    Width across elements

    47 |
    48 | 49 |
    50 |
    51 | 52 |
    53 |
    54 | span2 55 |
    56 | 57 | 58 | 61 | 62 |
    63 | 64 | 65 | span1 66 |
    67 | 68 |
    69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /web/less/thumbnails.less: -------------------------------------------------------------------------------- 1 | // 2 | // Thumbnails 3 | // -------------------------------------------------- 4 | 5 | 6 | // Note: `.thumbnails` and `.thumbnails > li` are overriden in responsive files 7 | 8 | // Make wrapper ul behave like the grid 9 | .thumbnails { 10 | margin-left: -@gridGutterWidth; 11 | list-style: none; 12 | .clearfix(); 13 | } 14 | // Fluid rows have no left margin 15 | .row-fluid .thumbnails { 16 | margin-left: 0; 17 | } 18 | 19 | // Float li to make thumbnails appear in a row 20 | .thumbnails > li { 21 | float: left; // Explicity set the float since we don't require .span* classes 22 | margin-bottom: @baseLineHeight; 23 | margin-left: @gridGutterWidth; 24 | } 25 | 26 | // The actual thumbnail (can be `a` or `div`) 27 | .thumbnail { 28 | display: block; 29 | padding: 4px; 30 | line-height: @baseLineHeight; 31 | border: 1px solid #ddd; 32 | .transition(all .2s ease-in-out); 33 | } 34 | // Add a hover/focus state for linked versions only 35 | a.thumbnail:hover, 36 | a.thumbnail:focus { 37 | border-color: @linkColor; 38 | } 39 | 40 | // Images and captions 41 | .thumbnail > img { 42 | display: block; 43 | max-width: 100%; 44 | margin-left: auto; 45 | margin-right: auto; 46 | } 47 | .thumbnail .caption { 48 | padding: 9px; 49 | color: @gray; 50 | } 51 | -------------------------------------------------------------------------------- /web/less/tooltip.less: -------------------------------------------------------------------------------- 1 | // 2 | // Tooltips 3 | // -------------------------------------------------- 4 | 5 | 6 | // Base class 7 | .tooltip { 8 | position: absolute; 9 | z-index: @zindexTooltip; 10 | display: block; 11 | visibility: visible; 12 | font-size: 11px; 13 | line-height: 1.4; 14 | .opacity(0); 15 | &.in { .opacity(80); } 16 | &.top { margin-top: -3px; padding: 5px 0; } 17 | &.right { margin-left: 3px; padding: 0 5px; } 18 | &.bottom { margin-top: 3px; padding: 5px 0; } 19 | &.left { margin-left: -3px; padding: 0 5px; } 20 | } 21 | 22 | // Wrapper for the tooltip content 23 | .tooltip-inner { 24 | max-width: 200px; 25 | padding: 8px; 26 | color: @tooltipColor; 27 | text-align: center; 28 | text-decoration: none; 29 | background-color: @tooltipBackground; 30 | } 31 | 32 | // Arrows 33 | .tooltip-arrow { 34 | position: absolute; 35 | width: 0; 36 | height: 0; 37 | border-color: transparent; 38 | border-style: solid; 39 | } 40 | .tooltip { 41 | &.top .tooltip-arrow { 42 | bottom: 0; 43 | left: 50%; 44 | margin-left: -@tooltipArrowWidth; 45 | border-width: @tooltipArrowWidth @tooltipArrowWidth 0; 46 | border-top-color: @tooltipArrowColor; 47 | } 48 | &.right .tooltip-arrow { 49 | top: 50%; 50 | left: 0; 51 | margin-top: -@tooltipArrowWidth; 52 | border-width: @tooltipArrowWidth @tooltipArrowWidth @tooltipArrowWidth 0; 53 | border-right-color: @tooltipArrowColor; 54 | } 55 | &.left .tooltip-arrow { 56 | top: 50%; 57 | right: 0; 58 | margin-top: -@tooltipArrowWidth; 59 | border-width: @tooltipArrowWidth 0 @tooltipArrowWidth @tooltipArrowWidth; 60 | border-left-color: @tooltipArrowColor; 61 | } 62 | &.bottom .tooltip-arrow { 63 | top: 0; 64 | left: 50%; 65 | margin-left: -@tooltipArrowWidth; 66 | border-width: 0 @tooltipArrowWidth @tooltipArrowWidth; 67 | border-bottom-color: @tooltipArrowColor; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /web/less/utilities.less: -------------------------------------------------------------------------------- 1 | // 2 | // Utility classes 3 | // -------------------------------------------------- 4 | 5 | 6 | // Quick floats 7 | .pull-right { 8 | float: right; 9 | } 10 | .pull-left { 11 | float: left; 12 | } 13 | 14 | // Toggling content 15 | .hide { 16 | display: none; 17 | } 18 | .show { 19 | display: block; 20 | } 21 | 22 | // Visibility 23 | .invisible { 24 | visibility: hidden; 25 | } 26 | 27 | // For Affix plugin 28 | .affix { 29 | position: fixed; 30 | } 31 | -------------------------------------------------------------------------------- /web/less/wells.less: -------------------------------------------------------------------------------- 1 | // 2 | // Wells 3 | // -------------------------------------------------- 4 | 5 | 6 | // Base class 7 | .well { 8 | min-height: 20px; 9 | padding: 19px; 10 | margin-bottom: 20px; 11 | background-color: @wellBackground; 12 | border: 1px solid darken(@wellBackground, 7%); 13 | blockquote { 14 | border-color: #ddd; 15 | border-color: rgba(0,0,0,.15); 16 | } 17 | } 18 | 19 | // Sizes 20 | .well-large { 21 | padding: 24px; 22 | } 23 | .well-small { 24 | padding: 9px; 25 | } 26 | -------------------------------------------------------------------------------- /web/scss/_accordion.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Accordion 3 | // -------------------------------------------------- 4 | 5 | 6 | // Parent container 7 | .accordion { 8 | margin-bottom: $baseLineHeight; 9 | } 10 | 11 | // Group == heading + body 12 | .accordion-group { 13 | margin-bottom: 2px; 14 | border: 1px solid #e5e5e5; 15 | } 16 | .accordion-heading { 17 | border-bottom: 0; 18 | } 19 | .accordion-heading .accordion-toggle { 20 | display: block; 21 | padding: 8px 15px; 22 | } 23 | 24 | // General toggle styles 25 | .accordion-toggle { 26 | cursor: pointer; 27 | } 28 | 29 | // Inner needs the styles because you can't animate properly with any styles on the element 30 | .accordion-inner { 31 | padding: 9px 15px; 32 | border-top: 1px solid #e5e5e5; 33 | } 34 | -------------------------------------------------------------------------------- /web/scss/_alerts.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Alerts 3 | // -------------------------------------------------- 4 | 5 | 6 | // Base styles 7 | // ------------------------- 8 | 9 | .alert { 10 | padding: 8px 35px 8px 14px; 11 | margin-bottom: $baseLineHeight; 12 | text-shadow: 0 1px 0 rgba(255,255,255,.5); 13 | background-color: $warningBackground; 14 | border: 1px solid $warningBorder; 15 | } 16 | .alert, 17 | .alert h4 { 18 | // Specified for the h4 to prevent conflicts of changing $headingsColor 19 | color: $warningText; 20 | } 21 | .alert h4 { 22 | margin: 0; 23 | } 24 | 25 | // Adjust close link position 26 | .alert .close { 27 | position: relative; 28 | top: -2px; 29 | right: -21px; 30 | line-height: $baseLineHeight; 31 | } 32 | 33 | 34 | // Alternate styles 35 | // ------------------------- 36 | 37 | .alert-success { 38 | background-color: $successBackground; 39 | border-color: $successBorder; 40 | color: $successText; 41 | } 42 | .alert-success h4 { 43 | color: $successText; 44 | } 45 | .alert-danger, 46 | .alert-error { 47 | background-color: $errorBackground; 48 | border-color: $errorBorder; 49 | color: $errorText; 50 | } 51 | .alert-danger h4, 52 | .alert-error h4 { 53 | color: $errorText; 54 | } 55 | .alert-info { 56 | background-color: $infoBackground; 57 | border-color: $infoBorder; 58 | color: $infoText; 59 | } 60 | .alert-info h4 { 61 | color: $infoText; 62 | } 63 | 64 | 65 | // Block alerts 66 | // ------------------------- 67 | 68 | .alert-block { 69 | padding-top: 14px; 70 | padding-bottom: 14px; 71 | } 72 | .alert-block > p, 73 | .alert-block > ul { 74 | margin-bottom: 0; 75 | } 76 | .alert-block p + p { 77 | margin-top: 5px; 78 | } 79 | -------------------------------------------------------------------------------- /web/scss/_breadcrumbs.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Breadcrumbs 3 | // -------------------------------------------------- 4 | 5 | 6 | .breadcrumb { 7 | padding: 8px 15px; 8 | margin: 0 0 $baseLineHeight; 9 | list-style: none; 10 | background-color: #f5f5f5; 11 | > li { 12 | display: inline-block; 13 | @include ie7-inline-block(); 14 | text-shadow: 0 1px 0 $white; 15 | > .divider { 16 | padding: 0 5px; 17 | color: #ccc; 18 | } 19 | } 20 | .active { 21 | color: $grayLight; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /web/scss/_carousel.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Carousel 3 | // -------------------------------------------------- 4 | 5 | 6 | .carousel { 7 | position: relative; 8 | margin-bottom: $baseLineHeight; 9 | line-height: 1; 10 | } 11 | 12 | .carousel-inner { 13 | overflow: hidden; 14 | width: 100%; 15 | position: relative; 16 | } 17 | 18 | .carousel-inner { 19 | 20 | > .item { 21 | display: none; 22 | position: relative; 23 | @include transition(.6s ease-in-out left); 24 | 25 | // Account for jankitude on images 26 | > img, 27 | > a > img { 28 | display: block; 29 | line-height: 1; 30 | } 31 | } 32 | 33 | > .active, 34 | > .next, 35 | > .prev { display: block; } 36 | 37 | > .active { 38 | left: 0; 39 | } 40 | 41 | > .next, 42 | > .prev { 43 | position: absolute; 44 | top: 0; 45 | width: 100%; 46 | } 47 | 48 | > .next { 49 | left: 100%; 50 | } 51 | > .prev { 52 | left: -100%; 53 | } 54 | > .next.left, 55 | > .prev.right { 56 | left: 0; 57 | } 58 | 59 | > .active.left { 60 | left: -100%; 61 | } 62 | > .active.right { 63 | left: 100%; 64 | } 65 | 66 | } 67 | 68 | // Left/right controls for nav 69 | // --------------------------- 70 | 71 | .carousel-control { 72 | position: absolute; 73 | top: 40%; 74 | left: 15px; 75 | width: 40px; 76 | height: 40px; 77 | margin-top: -20px; 78 | font-size: 60px; 79 | font-weight: 100; 80 | line-height: 30px; 81 | color: $white; 82 | text-align: center; 83 | background: $grayDarker; 84 | border: 3px solid $white; 85 | @include opacity(50); 86 | 87 | // we can't have this transition here 88 | // because webkit cancels the carousel 89 | // animation if you trip this while 90 | // in the middle of another animation 91 | // ;_; 92 | // .transition(opacity .2s linear); 93 | 94 | // Reposition the right one 95 | &.right { 96 | left: auto; 97 | right: 15px; 98 | } 99 | 100 | // Hover/focus state 101 | &:hover, 102 | &:focus { 103 | color: $white; 104 | text-decoration: none; 105 | @include opacity(90); 106 | } 107 | } 108 | 109 | // Carousel indicator pips 110 | // ----------------------------- 111 | .carousel-indicators { 112 | position: absolute; 113 | top: 15px; 114 | right: 15px; 115 | z-index: 5; 116 | margin: 0; 117 | list-style: none; 118 | 119 | li { 120 | display: block; 121 | float: left; 122 | width: 10px; 123 | height: 10px; 124 | margin-left: 5px; 125 | text-indent: -999px; 126 | background-color: #ccc; 127 | background-color: rgba(255,255,255,.25); 128 | } 129 | .active { 130 | background-color: #fff; 131 | } 132 | } 133 | 134 | // Caption for text below images 135 | // ----------------------------- 136 | 137 | .carousel-caption { 138 | position: absolute; 139 | left: 0; 140 | right: 0; 141 | bottom: 0; 142 | padding: 15px; 143 | background: $grayDark; 144 | background: rgba(0,0,0,.75); 145 | } 146 | .carousel-caption h4, 147 | .carousel-caption p { 148 | color: $white; 149 | line-height: $baseLineHeight; 150 | } 151 | .carousel-caption h4 { 152 | margin: 0 0 5px; 153 | } 154 | .carousel-caption p { 155 | margin-bottom: 0; 156 | } 157 | -------------------------------------------------------------------------------- /web/scss/_close.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Close icons 3 | // -------------------------------------------------- 4 | 5 | 6 | .close { 7 | float: right; 8 | font-size: 20px; 9 | font-weight: bold; 10 | line-height: $baseLineHeight; 11 | color: $black; 12 | text-shadow: 0 1px 0 rgba(255,255,255,1); 13 | @include opacity(20); 14 | &:hover, 15 | &:focus { 16 | color: $black; 17 | text-decoration: none; 18 | cursor: pointer; 19 | @include opacity(40); 20 | } 21 | } 22 | 23 | // Additional properties for button version 24 | // iOS requires the button element instead of an anchor tag. 25 | // If you want the anchor version, it requires `href="#"`. 26 | button.close { 27 | padding: 0; 28 | cursor: pointer; 29 | background: transparent; 30 | border: 0; 31 | -webkit-appearance: none; 32 | } 33 | -------------------------------------------------------------------------------- /web/scss/_code.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Code (inline and blocK) 3 | // -------------------------------------------------- 4 | 5 | 6 | // Inline and block code styles 7 | code, 8 | pre { 9 | padding: 0 3px 2px; 10 | @include font-family-monospace; 11 | font-size: $baseFontSize - 2; 12 | color: $grayDark; 13 | } 14 | 15 | // Inline code 16 | code { 17 | padding: 2px 4px; 18 | color: #d14; 19 | background-color: #f7f7f9; 20 | border: 1px solid #e1e1e8; 21 | white-space: nowrap; 22 | } 23 | 24 | // Blocks of code 25 | pre { 26 | display: block; 27 | padding: ($baseLineHeight - 1) / 2; 28 | margin: 0 0 $baseLineHeight / 2; 29 | font-size: $baseFontSize - 1; // 14px to 13px 30 | line-height: $baseLineHeight; 31 | word-break: break-all; 32 | word-wrap: break-word; 33 | white-space: pre; 34 | white-space: pre-wrap; 35 | background-color: #f5f5f5; 36 | border: 1px solid #ccc; // fallback for IE7-8 37 | border: 1px solid rgba(0,0,0,.15); 38 | 39 | // Make prettyprint styles more spaced out for readability 40 | &.prettyprint { 41 | margin-bottom: $baseLineHeight; 42 | } 43 | 44 | // Account for some code outputs that place code tags in pre tags 45 | code { 46 | padding: 0; 47 | color: inherit; 48 | white-space: pre; 49 | white-space: pre-wrap; 50 | background-color: transparent; 51 | border: 0; 52 | } 53 | } 54 | 55 | // Enable scrollable blocks of code 56 | .pre-scrollable { 57 | max-height: 340px; 58 | overflow-y: scroll; 59 | } 60 | -------------------------------------------------------------------------------- /web/scss/_component-animations.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Component animations 3 | // -------------------------------------------------- 4 | 5 | 6 | .fade { 7 | opacity: 0; 8 | @include transition(opacity .15s linear); 9 | &.in { 10 | opacity: 1; 11 | } 12 | } 13 | 14 | .collapse { 15 | position: relative; 16 | height: 0; 17 | overflow: hidden; 18 | @include transition(height .35s ease); 19 | &.in { 20 | height: auto; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /web/scss/_grid.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Grid system 3 | // -------------------------------------------------- 4 | 5 | 6 | // Fixed (940px) 7 | @include grid-core($gridColumnWidth, $gridGutterWidth); 8 | 9 | // Fluid (940px) 10 | @include grid-fluid($fluidGridColumnWidth, $fluidGridGutterWidth); 11 | 12 | // Reset utility classes due to specificity 13 | [class*="span"].hide, 14 | .row-fluid [class*="span"].hide { 15 | display: none; 16 | } 17 | 18 | [class*="span"].pull-right, 19 | .row-fluid [class*="span"].pull-right { 20 | float: right; 21 | } 22 | -------------------------------------------------------------------------------- /web/scss/_hero-unit.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Hero unit 3 | // -------------------------------------------------- 4 | 5 | 6 | .hero-unit { 7 | padding: 60px; 8 | margin-bottom: 30px; 9 | font-size: 18px; 10 | font-weight: 200; 11 | line-height: $baseLineHeight * 1.5; 12 | color: $heroUnitLeadColor; 13 | background-color: $heroUnitBackground; 14 | h1 { 15 | margin-bottom: 0; 16 | font-size: 60px; 17 | line-height: 1; 18 | color: $heroUnitHeadingColor; 19 | letter-spacing: -1px; 20 | } 21 | li { 22 | line-height: $baseLineHeight * 1.5; // Reset since we specify in type.scss 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /web/scss/_labels-badges.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Labels and badges 3 | // -------------------------------------------------- 4 | 5 | 6 | // Base classes 7 | .label, 8 | .badge { 9 | display: inline-block; 10 | padding: 2px 4px; 11 | font-size: $baseFontSize * .846; 12 | font-weight: bold; 13 | line-height: 14px; // ensure proper line-height if floated 14 | color: $white; 15 | vertical-align: baseline; 16 | white-space: nowrap; 17 | text-shadow: 0 -1px 0 rgba(0,0,0,.25); 18 | background-color: $grayLight; 19 | } 20 | 21 | .badge { 22 | padding-left: 9px; 23 | padding-right: 9px; 24 | } 25 | 26 | // Empty labels/badges collapse 27 | .label, 28 | .badge { 29 | &:empty { 30 | display: none; 31 | } 32 | } 33 | 34 | // Hover/focus state, but only for links 35 | a { 36 | &.label:hover, 37 | &.label:focus, 38 | &.badge:hover, 39 | &.badge:focus { 40 | color: $white; 41 | text-decoration: none; 42 | cursor: pointer; 43 | } 44 | } 45 | 46 | // Colors 47 | // Only give background-color difference to links (and to simplify, we don't qualifty with `a` but [href] attribute) 48 | @each $item in label, badge { 49 | // Important (red) 50 | .#{$item}-important { background-color: $errorText; } 51 | .#{$item}-important[href] { background-color: darken($errorText, 10%); } 52 | // Warnings (orange) 53 | .#{$item}-warning { background-color: $orange; } 54 | .#{$item}-warning[href] { background-color: darken($orange, 10%); } 55 | // Success (green) 56 | .#{$item}-success { background-color: $successText; } 57 | .#{$item}-success[href] { background-color: darken($successText, 10%); } 58 | // Info (turquoise) 59 | .#{$item}-info { background-color: $infoText; } 60 | .#{$item}-info[href] { background-color: darken($infoText, 10%); } 61 | // Inverse (black) 62 | .#{$item}-inverse { background-color: $grayDark; } 63 | .#{$item}-inverse[href] { background-color: darken($grayDark, 10%); } 64 | } 65 | 66 | // Quick fix for labels/badges in buttons 67 | .btn { 68 | .label, 69 | .badge { 70 | position: relative; 71 | top: -1px; 72 | } 73 | } 74 | .btn-mini { 75 | .label, 76 | .badge { 77 | top: 0; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /web/scss/_layouts.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Layouts 3 | // -------------------------------------------------- 4 | 5 | 6 | // Container (centered, fixed-width layouts) 7 | .container { 8 | @include container-fixed(); 9 | } 10 | 11 | // Fluid layouts (left aligned, with sidebar, min- & max-width content) 12 | .container-fluid { 13 | padding-right: $gridGutterWidth; 14 | padding-left: $gridGutterWidth; 15 | @include clearfix(); 16 | } 17 | -------------------------------------------------------------------------------- /web/scss/_media.scss: -------------------------------------------------------------------------------- 1 | // Media objects 2 | // Source: http://stubbornella.org/content/?p=497 3 | // -------------------------------------------------- 4 | 5 | 6 | // Common styles 7 | // ------------------------- 8 | 9 | // Clear the floats 10 | .media, 11 | .media-body { 12 | overflow: hidden; 13 | *overflow: visible; 14 | zoom: 1; 15 | } 16 | 17 | // Proper spacing between instances of .media 18 | .media, 19 | .media .media { 20 | margin-top: 15px; 21 | } 22 | .media:first-child { 23 | margin-top: 0; 24 | } 25 | 26 | // For images and videos, set to block 27 | .media-object { 28 | display: block; 29 | } 30 | 31 | // Reset margins on headings for tighter default spacing 32 | .media-heading { 33 | margin: 0 0 5px; 34 | } 35 | 36 | 37 | // Media image alignment 38 | // ------------------------- 39 | 40 | .media > .pull-left { 41 | margin-right: 10px; 42 | } 43 | .media > .pull-right { 44 | margin-left: 10px; 45 | } 46 | 47 | 48 | // Media list variation 49 | // ------------------------- 50 | 51 | // Undo default ul/ol styles 52 | .media-list { 53 | margin-left: 0; 54 | list-style: none; 55 | } 56 | -------------------------------------------------------------------------------- /web/scss/_modals.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Modals 3 | // -------------------------------------------------- 4 | 5 | // Background 6 | .modal-backdrop { 7 | position: fixed; 8 | top: 0; 9 | right: 0; 10 | bottom: 0; 11 | left: 0; 12 | z-index: $zindexModalBackdrop; 13 | background-color: $black; 14 | // Fade for backdrop 15 | &.fade { opacity: 0; } 16 | } 17 | 18 | .modal-backdrop, 19 | .modal-backdrop.fade.in { 20 | @include opacity(80); 21 | } 22 | 23 | // Base modal 24 | .modal { 25 | position: fixed; 26 | top: 10%; 27 | left: 50%; 28 | z-index: $zindexModal; 29 | width: 560px; 30 | margin-left: -280px; 31 | background-color: $white; 32 | border: 1px solid #999; 33 | border: 1px solid rgba(0,0,0,.3); 34 | *border: 1px solid #999; /* IE6-7 */ 35 | @include background-clip(padding-box); 36 | // Remove focus outline from opened modal 37 | outline: none; 38 | 39 | &.fade { 40 | @include transition(opacity .3s linear, top .3s ease-out); 41 | top: -25%; 42 | } 43 | &.fade.in { top: 10%; } 44 | } 45 | .modal-header { 46 | padding: 9px 15px; 47 | border-bottom: 1px solid #eee; 48 | // Close icon 49 | .close { margin-top: 2px; } 50 | // Heading 51 | h3 { 52 | margin: 0; 53 | line-height: 30px; 54 | } 55 | } 56 | 57 | // Body (where all modal content resides) 58 | .modal-body { 59 | position: relative; 60 | overflow-y: auto; 61 | max-height: 400px; 62 | padding: 15px; 63 | } 64 | // Remove bottom margin if need be 65 | .modal-form { 66 | margin-bottom: 0; 67 | } 68 | 69 | // Footer (for actions) 70 | .modal-footer { 71 | padding: 14px 15px 15px; 72 | margin-bottom: 0; 73 | text-align: right; // right align buttons 74 | background-color: #f5f5f5; 75 | border-top: 1px solid #ddd; 76 | @include clearfix(); // clear it in case folks use .pull-* classes on buttons 77 | 78 | // Properly space out buttons 79 | .btn + .btn { 80 | margin-left: 5px; 81 | margin-bottom: 0; // account for input[type="submit"] which gets the bottom margin like all other inputs 82 | } 83 | // but override that for button groups 84 | .btn-group .btn + .btn { 85 | margin-left: -1px; 86 | } 87 | // and override it for block buttons as well 88 | .btn-block + .btn-block { 89 | margin-left: 0; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /web/scss/_pager.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Pager pagination 3 | // -------------------------------------------------- 4 | 5 | 6 | .pager { 7 | margin: $baseLineHeight 0; 8 | list-style: none; 9 | text-align: center; 10 | @include clearfix(); 11 | } 12 | .pager li { 13 | display: inline; 14 | } 15 | .pager li > a, 16 | .pager li > span { 17 | display: inline-block; 18 | padding: 5px 14px; 19 | background-color: #fff; 20 | border: 1px solid #ddd; 21 | } 22 | .pager li > a:hover, 23 | .pager li > a:focus { 24 | text-decoration: none; 25 | background-color: #f5f5f5; 26 | } 27 | .pager .next > a, 28 | .pager .next > span { 29 | float: right; 30 | } 31 | .pager .previous > a, 32 | .pager .previous > span { 33 | float: left; 34 | } 35 | .pager .disabled > a, 36 | .pager .disabled > a:hover, 37 | .pager .disabled > a:focus, 38 | .pager .disabled > span { 39 | color: $grayLight; 40 | background-color: #fff; 41 | cursor: default; 42 | } 43 | -------------------------------------------------------------------------------- /web/scss/_pagination.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Pagination (multiple pages) 3 | // -------------------------------------------------- 4 | 5 | // Space out pagination from surrounding content 6 | .pagination { 7 | margin: $baseLineHeight 0; 8 | } 9 | 10 | .pagination ul { 11 | // Allow for text-based alignment 12 | display: inline-block; 13 | @include ie7-inline-block(); 14 | // Reset default ul styles 15 | margin-left: 0; 16 | margin-bottom: 0; 17 | } 18 | .pagination ul > li { 19 | display: inline; // Remove list-style and block-level defaults 20 | } 21 | .pagination ul > li > a, 22 | .pagination ul > li > span { 23 | float: left; // Collapse white-space 24 | padding: 4px 12px; 25 | line-height: $baseLineHeight; 26 | text-decoration: none; 27 | background-color: $paginationBackground; 28 | border: 1px solid $paginationBorder; 29 | border-left-width: 0; 30 | } 31 | .pagination ul > li > a:hover, 32 | .pagination ul > li > a:focus, 33 | .pagination ul > .active > a, 34 | .pagination ul > .active > span { 35 | background-color: $paginationActiveBackground; 36 | } 37 | .pagination ul > .active > a, 38 | .pagination ul > .active > span { 39 | color: $grayLight; 40 | cursor: default; 41 | } 42 | .pagination ul > .disabled > span, 43 | .pagination ul > .disabled > a, 44 | .pagination ul > .disabled > a:hover, 45 | .pagination ul > .disabled > a:focus { 46 | color: $grayLight; 47 | background-color: transparent; 48 | cursor: default; 49 | } 50 | .pagination ul > li:first-child > a, 51 | .pagination ul > li:first-child > span { 52 | border-left-width: 1px; 53 | } 54 | 55 | 56 | // Alignment 57 | // -------------------------------------------------- 58 | 59 | .pagination-centered { 60 | text-align: center; 61 | } 62 | .pagination-right { 63 | text-align: right; 64 | } 65 | 66 | 67 | // Sizing 68 | // -------------------------------------------------- 69 | 70 | // Large 71 | .pagination-large { 72 | ul > li > a, 73 | ul > li > span { 74 | padding: $paddingLarge; 75 | font-size: $fontSizeLarge; 76 | } 77 | } 78 | 79 | // Small 80 | .pagination-small { 81 | ul > li > a, 82 | ul > li > span { 83 | padding: $paddingSmall; 84 | font-size: $fontSizeSmall; 85 | } 86 | } 87 | // Mini 88 | .pagination-mini { 89 | ul > li > a, 90 | ul > li > span { 91 | padding: $paddingMini; 92 | font-size: $fontSizeMini; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /web/scss/_popovers.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Popovers 3 | // -------------------------------------------------- 4 | 5 | 6 | .popover { 7 | position: absolute; 8 | top: 0; 9 | left: 0; 10 | z-index: $zindexPopover; 11 | display: none; 12 | max-width: 276px; 13 | padding: 1px; 14 | text-align: left; // Reset given new insertion method 15 | background-color: $popoverBackground; 16 | -webkit-background-clip: padding-box; 17 | -moz-background-clip: padding; 18 | background-clip: padding-box; 19 | border: 1px solid #ccc; 20 | border: 1px solid rgba(0,0,0,.2); 21 | 22 | // Overrides for proper insertion 23 | white-space: normal; 24 | 25 | // Offset the popover to account for the popover arrow 26 | &.top { margin-top: -10px; } 27 | &.right { margin-left: 10px; } 28 | &.bottom { margin-top: 10px; } 29 | &.left { margin-left: -10px; } 30 | } 31 | 32 | .popover-title { 33 | margin: 0; // reset heading margin 34 | padding: 8px 14px; 35 | font-size: 14px; 36 | font-weight: normal; 37 | line-height: 18px; 38 | background-color: $popoverTitleBackground; 39 | border-bottom: 1px solid darken($popoverTitleBackground, 5%); 40 | 41 | &:empty { 42 | display: none; 43 | } 44 | } 45 | 46 | .popover-content { 47 | padding: 9px 14px; 48 | } 49 | 50 | // Arrows 51 | // 52 | // .arrow is outer, .arrow:after is inner 53 | 54 | .popover .arrow, 55 | .popover .arrow:after { 56 | position: absolute; 57 | display: block; 58 | width: 0; 59 | height: 0; 60 | border-color: transparent; 61 | border-style: solid; 62 | } 63 | .popover .arrow { 64 | border-width: $popoverArrowOuterWidth; 65 | } 66 | .popover .arrow:after { 67 | border-width: $popoverArrowWidth; 68 | content: ""; 69 | } 70 | 71 | .popover { 72 | &.top .arrow { 73 | left: 50%; 74 | margin-left: -$popoverArrowOuterWidth; 75 | border-bottom-width: 0; 76 | border-top-color: #999; // IE8 fallback 77 | border-top-color: $popoverArrowOuterColor; 78 | bottom: -$popoverArrowOuterWidth; 79 | &:after { 80 | bottom: 1px; 81 | margin-left: -$popoverArrowWidth; 82 | border-bottom-width: 0; 83 | border-top-color: $popoverArrowColor; 84 | } 85 | } 86 | &.right .arrow { 87 | top: 50%; 88 | left: -$popoverArrowOuterWidth; 89 | margin-top: -$popoverArrowOuterWidth; 90 | border-left-width: 0; 91 | border-right-color: #999; // IE8 fallback 92 | border-right-color: $popoverArrowOuterColor; 93 | &:after { 94 | left: 1px; 95 | bottom: -$popoverArrowWidth; 96 | border-left-width: 0; 97 | border-right-color: $popoverArrowColor; 98 | } 99 | } 100 | &.bottom .arrow { 101 | left: 50%; 102 | margin-left: -$popoverArrowOuterWidth; 103 | border-top-width: 0; 104 | border-bottom-color: #999; // IE8 fallback 105 | border-bottom-color: $popoverArrowOuterColor; 106 | top: -$popoverArrowOuterWidth; 107 | &:after { 108 | top: 1px; 109 | margin-left: -$popoverArrowWidth; 110 | border-top-width: 0; 111 | border-bottom-color: $popoverArrowColor; 112 | } 113 | } 114 | 115 | &.left .arrow { 116 | top: 50%; 117 | right: -$popoverArrowOuterWidth; 118 | margin-top: -$popoverArrowOuterWidth; 119 | border-right-width: 0; 120 | border-left-color: #999; // IE8 fallback 121 | border-left-color: $popoverArrowOuterColor; 122 | &:after { 123 | right: 1px; 124 | border-right-width: 0; 125 | border-left-color: $popoverArrowColor; 126 | bottom: -$popoverArrowWidth; 127 | } 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /web/scss/_progress-bars.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Progress bars 3 | // -------------------------------------------------- 4 | 5 | 6 | // ANIMATIONS 7 | // ---------- 8 | 9 | // Webkit 10 | @-webkit-keyframes progress-bar-stripes { 11 | from { background-position: 40px 0; } 12 | to { background-position: 0 0; } 13 | } 14 | 15 | // Firefox 16 | @-moz-keyframes progress-bar-stripes { 17 | from { background-position: 40px 0; } 18 | to { background-position: 0 0; } 19 | } 20 | 21 | // IE9 22 | @-ms-keyframes progress-bar-stripes { 23 | from { background-position: 40px 0; } 24 | to { background-position: 0 0; } 25 | } 26 | 27 | // Opera 28 | @-o-keyframes progress-bar-stripes { 29 | from { background-position: 0 0; } 30 | to { background-position: 40px 0; } 31 | } 32 | 33 | // Spec 34 | @keyframes progress-bar-stripes { 35 | from { background-position: 40px 0; } 36 | to { background-position: 0 0; } 37 | } 38 | 39 | 40 | 41 | // THE BARS 42 | // -------- 43 | 44 | // Outer container 45 | .progress { 46 | overflow: hidden; 47 | height: $baseLineHeight; 48 | margin-bottom: $baseLineHeight; 49 | @include gradient-vertical(#f5f5f5, #f9f9f9); 50 | } 51 | 52 | // Bar of progress 53 | .progress .bar { 54 | width: 0%; 55 | height: 100%; 56 | color: $white; 57 | float: left; 58 | font-size: 12px; 59 | text-align: center; 60 | text-shadow: 0 -1px 0 rgba(0,0,0,.25); 61 | @include gradient-vertical(#149bdf, #0480be); 62 | @include box-sizing(border-box); 63 | @include transition(width .6s ease); 64 | } 65 | .progress .bar + .bar { 66 | } 67 | 68 | // Striped bars 69 | .progress-striped .bar { 70 | @include gradient-striped(#149bdf); 71 | @include background-size(40px 40px); 72 | } 73 | 74 | // Call animation for the active one 75 | .progress.active .bar { 76 | -webkit-animation: progress-bar-stripes 2s linear infinite; 77 | -moz-animation: progress-bar-stripes 2s linear infinite; 78 | -ms-animation: progress-bar-stripes 2s linear infinite; 79 | -o-animation: progress-bar-stripes 2s linear infinite; 80 | animation: progress-bar-stripes 2s linear infinite; 81 | } 82 | 83 | 84 | 85 | // COLORS 86 | // ------ 87 | 88 | // Danger (red) 89 | .progress-danger .bar, .progress .bar-danger { 90 | @include gradient-vertical(#ee5f5b, #c43c35); 91 | } 92 | .progress-danger.progress-striped .bar, .progress-striped .bar-danger { 93 | @include gradient-striped(#ee5f5b); 94 | } 95 | 96 | // Success (green) 97 | .progress-success .bar, .progress .bar-success { 98 | @include gradient-vertical(#62c462, #57a957); 99 | } 100 | .progress-success.progress-striped .bar, .progress-striped .bar-success { 101 | @include gradient-striped(#62c462); 102 | } 103 | 104 | // Info (teal) 105 | .progress-info .bar, .progress .bar-info { 106 | @include gradient-vertical(#5bc0de, #339bb9); 107 | } 108 | .progress-info.progress-striped .bar, .progress-striped .bar-info { 109 | @include gradient-striped(#5bc0de); 110 | } 111 | 112 | // Warning (orange) 113 | .progress-warning .bar, .progress .bar-warning { 114 | @include gradient-vertical(lighten($orange, 15%), $orange); 115 | } 116 | .progress-warning.progress-striped .bar, .progress-striped .bar-warning { 117 | @include gradient-striped(lighten($orange, 15%)); 118 | } 119 | -------------------------------------------------------------------------------- /web/scss/_responsive-1200px-min.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Responsive: Large desktop and up 3 | // -------------------------------------------------- 4 | 5 | 6 | @media (min-width: 1200px) { 7 | 8 | // Fixed grid 9 | @include grid-core($gridColumnWidth1200, $gridGutterWidth1200); 10 | 11 | // Fluid grid 12 | @include grid-fluid($fluidGridColumnWidth1200, $fluidGridGutterWidth1200); 13 | 14 | // Input grid 15 | @include grid-input($gridColumnWidth1200, $gridGutterWidth1200); 16 | 17 | // Thumbnails 18 | .thumbnails { 19 | margin-left: -$gridGutterWidth1200; 20 | } 21 | .thumbnails > li { 22 | margin-left: $gridGutterWidth1200; 23 | } 24 | .row-fluid .thumbnails { 25 | margin-left: 0; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /web/scss/_responsive-768px-979px.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Responsive: Tablet to desktop 3 | // -------------------------------------------------- 4 | 5 | 6 | @media (min-width: 768px) and (max-width: 979px) { 7 | 8 | // Fixed grid 9 | @include grid-core($gridColumnWidth768, $gridGutterWidth768); 10 | 11 | // Fluid grid 12 | @include grid-fluid($fluidGridColumnWidth768, $fluidGridGutterWidth768); 13 | 14 | // Input grid 15 | @include grid-input($gridColumnWidth768, $gridGutterWidth768); 16 | 17 | // No need to reset .thumbnails here since it's the same $gridGutterWidth 18 | 19 | } 20 | -------------------------------------------------------------------------------- /web/scss/_responsive-utilities.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Responsive: Utility classes 3 | // -------------------------------------------------- 4 | 5 | 6 | // IE10 Metro responsive 7 | // Required for Windows 8 Metro split-screen snapping with IE10 8 | // Source: http://timkadlec.com/2012/10/ie10-snap-mode-and-responsive-design/ 9 | @-ms-viewport{ 10 | width: device-width; 11 | } 12 | 13 | // Hide from screenreaders and browsers 14 | // Credit: HTML5 Boilerplate 15 | .hidden { 16 | display: none; 17 | visibility: hidden; 18 | } 19 | 20 | // Visibility utilities 21 | 22 | // For desktops 23 | .visible-phone { display: none !important; } 24 | .visible-tablet { display: none !important; } 25 | .hidden-phone { } 26 | .hidden-tablet { } 27 | .hidden-desktop { display: none !important; } 28 | .visible-desktop { display: inherit !important; } 29 | 30 | // Tablets & small desktops only 31 | @media (min-width: 768px) and (max-width: 979px) { 32 | // Hide everything else 33 | .hidden-desktop { display: inherit !important; } 34 | .visible-desktop { display: none !important ; } 35 | // Show 36 | .visible-tablet { display: inherit !important; } 37 | // Hide 38 | .hidden-tablet { display: none !important; } 39 | } 40 | 41 | // Phones only 42 | @media (max-width: 767px) { 43 | // Hide everything else 44 | .hidden-desktop { display: inherit !important; } 45 | .visible-desktop { display: none !important; } 46 | // Show 47 | .visible-phone { display: inherit !important; } // Use inherit to restore previous behavior 48 | // Hide 49 | .hidden-phone { display: none !important; } 50 | } 51 | 52 | // Print utilities 53 | .visible-print { display: none !important; } 54 | .hidden-print { } 55 | 56 | @media print { 57 | .visible-print { display: inherit !important; } 58 | .hidden-print { display: none !important; } 59 | } 60 | 61 | // Clearing floats 62 | .clearfix { 63 | @include clearfix(); 64 | } 65 | 66 | // Accessible yet invisible text 67 | .hide-text { 68 | @include hide-text(); 69 | } 70 | 71 | // Uses box-sizing mixin, so must be defined here 72 | .input-block-level { 73 | @include input-block-level(); 74 | } 75 | -------------------------------------------------------------------------------- /web/scss/_scaffolding.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Scaffolding 3 | // -------------------------------------------------- 4 | 5 | 6 | // Body reset 7 | // ------------------------- 8 | 9 | body { 10 | margin: 0; 11 | font-family: $baseFontFamily; 12 | font-size: $baseFontSize; 13 | line-height: $baseLineHeight; 14 | color: $textColor; 15 | background-color: $bodyBackground; 16 | } 17 | 18 | 19 | // Links 20 | // ------------------------- 21 | 22 | a { 23 | color: $linkColor; 24 | text-decoration: none; 25 | } 26 | a:hover, 27 | a:focus { 28 | color: $linkColorHover; 29 | text-decoration: underline; 30 | } 31 | 32 | 33 | // Images 34 | // ------------------------- 35 | 36 | // Add polaroid-esque trim 37 | .img-polaroid { 38 | padding: 4px; 39 | background-color: #fff; 40 | border: 1px solid #ccc; 41 | border: 1px solid rgba(0,0,0,.2); 42 | } -------------------------------------------------------------------------------- /web/scss/_thumbnails.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Thumbnails 3 | // -------------------------------------------------- 4 | 5 | 6 | // Note: `.thumbnails` and `.thumbnails > li` are overriden in responsive files 7 | 8 | // Make wrapper ul behave like the grid 9 | .thumbnails { 10 | margin-left: -$gridGutterWidth; 11 | list-style: none; 12 | @include clearfix(); 13 | } 14 | // Fluid rows have no left margin 15 | .row-fluid .thumbnails { 16 | margin-left: 0; 17 | } 18 | 19 | // Float li to make thumbnails appear in a row 20 | .thumbnails > li { 21 | float: left; // Explicity set the float since we don't require .span* classes 22 | margin-bottom: $baseLineHeight; 23 | margin-left: $gridGutterWidth; 24 | } 25 | 26 | // The actual thumbnail (can be `a` or `div`) 27 | .thumbnail { 28 | display: block; 29 | padding: 4px; 30 | line-height: $baseLineHeight; 31 | border: 1px solid #ddd; 32 | @include transition(all .2s ease-in-out); 33 | } 34 | // Add a hover/focus state for linked versions only 35 | a.thumbnail:hover, 36 | a.thumbnail:focus { 37 | border-color: $linkColor; 38 | } 39 | 40 | // Images and captions 41 | .thumbnail > img { 42 | display: block; 43 | max-width: 100%; 44 | margin-left: auto; 45 | margin-right: auto; 46 | } 47 | .thumbnail .caption { 48 | padding: 9px; 49 | color: $gray; 50 | } 51 | -------------------------------------------------------------------------------- /web/scss/_tooltip.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Tooltips 3 | // -------------------------------------------------- 4 | 5 | 6 | // Base class 7 | .tooltip { 8 | position: absolute; 9 | z-index: $zindexTooltip; 10 | display: block; 11 | visibility: visible; 12 | font-size: 11px; 13 | line-height: 1.4; 14 | @include opacity(0); 15 | &.in { @include opacity(80); } 16 | &.top { margin-top: -3px; padding: 5px 0; } 17 | &.right { margin-left: 3px; padding: 0 5px; } 18 | &.bottom { margin-top: 3px; padding: 5px 0; } 19 | &.left { margin-left: -3px; padding: 0 5px; } 20 | } 21 | 22 | // Wrapper for the tooltip content 23 | .tooltip-inner { 24 | max-width: 200px; 25 | padding: 8px; 26 | color: $tooltipColor; 27 | text-align: center; 28 | text-decoration: none; 29 | background-color: $tooltipBackground; 30 | } 31 | 32 | // Arrows 33 | .tooltip-arrow { 34 | position: absolute; 35 | width: 0; 36 | height: 0; 37 | border-color: transparent; 38 | border-style: solid; 39 | } 40 | .tooltip { 41 | &.top .tooltip-arrow { 42 | bottom: 0; 43 | left: 50%; 44 | margin-left: -$tooltipArrowWidth; 45 | border-width: $tooltipArrowWidth $tooltipArrowWidth 0; 46 | border-top-color: $tooltipArrowColor; 47 | } 48 | &.right .tooltip-arrow { 49 | top: 50%; 50 | left: 0; 51 | margin-top: -$tooltipArrowWidth; 52 | border-width: $tooltipArrowWidth $tooltipArrowWidth $tooltipArrowWidth 0; 53 | border-right-color: $tooltipArrowColor; 54 | } 55 | &.left .tooltip-arrow { 56 | top: 50%; 57 | right: 0; 58 | margin-top: -$tooltipArrowWidth; 59 | border-width: $tooltipArrowWidth 0 $tooltipArrowWidth $tooltipArrowWidth; 60 | border-left-color: $tooltipArrowColor; 61 | } 62 | &.bottom .tooltip-arrow { 63 | top: 0; 64 | left: 50%; 65 | margin-left: -$tooltipArrowWidth; 66 | border-width: 0 $tooltipArrowWidth $tooltipArrowWidth; 67 | border-bottom-color: $tooltipArrowColor; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /web/scss/_utilities.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Utility classes 3 | // -------------------------------------------------- 4 | 5 | 6 | // Quick floats 7 | .pull-right { 8 | float: right; 9 | } 10 | .pull-left { 11 | float: left; 12 | } 13 | 14 | // Toggling content 15 | .hide { 16 | display: none; 17 | } 18 | .show { 19 | display: block; 20 | } 21 | 22 | // Visibility 23 | .invisible { 24 | visibility: hidden; 25 | } 26 | 27 | // For Affix plugin 28 | .affix { 29 | position: fixed; 30 | } 31 | 32 | // Clearing floats 33 | .clearfix { 34 | @include clearfix(); 35 | } 36 | 37 | // Accessible yet invisible text 38 | .hide-text { 39 | @include hide-text(); 40 | } 41 | 42 | // Uses box-sizing mixin, so must be defined here 43 | .input-block-level { 44 | @include input-block-level(); 45 | } 46 | -------------------------------------------------------------------------------- /web/scss/_wells.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Wells 3 | // -------------------------------------------------- 4 | 5 | 6 | // Base class 7 | .well { 8 | min-height: 20px; 9 | padding: 19px; 10 | margin-bottom: 20px; 11 | background-color: $wellBackground; 12 | border: 1px solid darken($wellBackground, 7%); 13 | blockquote { 14 | border-color: #ddd; 15 | border-color: rgba(0,0,0,.15); 16 | } 17 | } 18 | 19 | // Sizes 20 | .well-large { 21 | padding: 24px; 22 | } 23 | .well-small { 24 | padding: 9px; 25 | } 26 | -------------------------------------------------------------------------------- /web/scss/bootstrap-responsive.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Responsive v2.3.1 3 | * 4 | * Copyright 2012 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 | */ 10 | 11 | 12 | // Responsive 13 | // For phone and tablet devices 14 | // ------------------------------------------------------------- 15 | 16 | 17 | // REPEAT VARIABLES & MIXINS 18 | // ------------------------- 19 | // Required since we compile the responsive stuff separately 20 | 21 | @import "variables"; // Modify this for custom colors, font-sizes, etc 22 | @import "mixins"; 23 | 24 | 25 | // RESPONSIVE CLASSES 26 | // ------------------ 27 | 28 | @import "responsive-utilities"; 29 | 30 | 31 | // MEDIA QUERIES 32 | // ------------------ 33 | 34 | // Large desktops 35 | @import "responsive-1200px-min"; 36 | 37 | // Tablets to regular desktops 38 | @import "responsive-768px-979px"; 39 | 40 | // Phones to portrait tablets and narrow desktops 41 | @import "responsive-767px-max"; 42 | 43 | 44 | // RESPONSIVE NAVBAR 45 | // ------------------ 46 | 47 | // From 979px and below, show a button to toggle navbar contents 48 | @import "responsive-navbar"; 49 | -------------------------------------------------------------------------------- /web/scss/bootstrap.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v2.3.1 3 | * 4 | * Copyright 2012 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 | */ 10 | 11 | // Core variables and mixins 12 | @import "variables"; // Modify this for custom colors, font-sizes, etc 13 | @import "mixins"; 14 | 15 | // CSS Reset 16 | @import "reset"; 17 | 18 | // Grid system and page structure 19 | @import "scaffolding"; 20 | @import "grid"; 21 | @import "layouts"; 22 | 23 | // Base CSS 24 | @import "type"; 25 | @import "code"; 26 | @import "forms"; 27 | @import "tables"; 28 | 29 | // Components: common 30 | @import "font-awesome"; 31 | @import "dropdowns"; 32 | @import "wells"; 33 | @import "component-animations"; 34 | @import "close"; 35 | 36 | // Components: Buttons & Alerts 37 | @import "buttons"; 38 | @import "button-groups"; 39 | @import "alerts"; // Note: alerts share common CSS with buttons and thus have styles in buttons 40 | 41 | // Components: Nav 42 | @import "navs"; 43 | @import "navbar"; 44 | @import "breadcrumbs"; 45 | @import "pagination"; 46 | @import "pager"; 47 | 48 | // Components: Popovers 49 | @import "modals"; 50 | @import "tooltip"; 51 | @import "popovers"; 52 | 53 | // Components: Misc 54 | @import "thumbnails"; 55 | @import "media"; 56 | @import "labels-badges"; 57 | @import "progress-bars"; 58 | @import "accordion"; 59 | @import "carousel"; 60 | @import "hero-unit"; 61 | 62 | // Utility classes 63 | @import "utilities"; // Has to be last to override when necessary 64 | --------------------------------------------------------------------------------