Переглянути джерело

Next big step in redesing.
Adjust whole user parts to new design, polish some javascripts
Cleaned-up package lists, adjust form elements to new design
And moar =)

Joseph Bielawski 11 роки тому
батько
коміт
e070d7aff0
32 змінених файлів з 994 додано та 515 видалено
  1. 20 16
      app/Resources/FOSUserBundle/views/ChangePassword/changePassword_content.html.twig
  2. 29 21
      app/Resources/FOSUserBundle/views/Profile/edit_content.html.twig
  3. 33 34
      app/Resources/FOSUserBundle/views/Profile/show.html.twig
  4. 1 0
      app/Resources/FOSUserBundle/views/Registration/register.html.twig
  5. 41 0
      app/Resources/FOSUserBundle/views/Registration/register_content.html.twig
  6. 29 0
      app/Resources/FOSUserBundle/views/Resetting/request.html.twig
  7. 3 1
      app/Resources/FOSUserBundle/views/layout.html.twig
  8. 55 29
      app/Resources/HWIOAuthBundle/views/Connect/login.html.twig
  9. 3 3
      app/Resources/HWIOAuthBundle/views/layout.html.twig
  10. 3 0
      app/config/config.yml
  11. 45 33
      src/Packagist/WebBundle/Controller/WebController.php
  12. 5 1
      src/Packagist/WebBundle/Form/Type/PackageType.php
  13. 1 1
      src/Packagist/WebBundle/Menu/MenuBuilder.php
  14. 271 111
      src/Packagist/WebBundle/Resources/public/css/main.css
  15. BIN
      src/Packagist/WebBundle/Resources/public/img/loader.gif
  16. 15 4
      src/Packagist/WebBundle/Resources/public/js/layout.js
  17. 4 4
      src/Packagist/WebBundle/Resources/public/js/submitPackage.js
  18. 19 25
      src/Packagist/WebBundle/Resources/views/About/about.html.twig
  19. 36 4
      src/Packagist/WebBundle/Resources/views/User/favorites.html.twig
  20. 36 4
      src/Packagist/WebBundle/Resources/views/User/packages.html.twig
  21. 18 15
      src/Packagist/WebBundle/Resources/views/User/profile.html.twig
  22. 82 38
      src/Packagist/WebBundle/Resources/views/Web/explore.html.twig
  23. 10 19
      src/Packagist/WebBundle/Resources/views/Web/index.html.twig
  24. 2 2
      src/Packagist/WebBundle/Resources/views/Web/list.html.twig
  25. 5 3
      src/Packagist/WebBundle/Resources/views/Web/popular.html.twig
  26. 10 8
      src/Packagist/WebBundle/Resources/views/Web/search.html.twig
  27. 6 8
      src/Packagist/WebBundle/Resources/views/Web/searchSection.html.twig
  28. 55 45
      src/Packagist/WebBundle/Resources/views/Web/stats.html.twig
  29. 21 23
      src/Packagist/WebBundle/Resources/views/Web/submitPackage.html.twig
  30. 45 0
      src/Packagist/WebBundle/Resources/views/forms.html.twig
  31. 62 47
      src/Packagist/WebBundle/Resources/views/layout.html.twig
  32. 29 16
      src/Packagist/WebBundle/Resources/views/macros.html.twig

+ 20 - 16
app/Resources/FOSUserBundle/views/ChangePassword/changePassword_content.html.twig

@@ -1,27 +1,31 @@
-<form action="{{ path('fos_user_change_password') }}" {{ form_enctype(form) }} method="POST" class="fos_user_change_password span4">
+<form action="{{ path('fos_user_change_password') }}" {{ form_enctype(form) }} method="POST" class="fos_user_change_password col-md-6">
     {{ form_errors(form) }}
 
-    {{ form_label(form.current_password, 'Current password') }}
-    <div class="input-prepend">
-        {{ form_widget(form.current_password) }}
-        <span class="add-on"><span class="icon-key"></span></span>
+    <div class="form-group">
+        {{ form_label(form.current_password, 'Current password') }}
+        <div class="input-group">
+            {{ form_widget(form.current_password, {'attr': {'class': 'input-lg'}}) }}
+            <span class="input-group-addon"><span class="icon-key"></span></span>
+        </div>
     </div>
 
-    {{ form_label(form.new.first, 'New password') }}
-    <div class="input-prepend">
-        {{ form_widget(form.new.first) }}
-        <span class="add-on"><span class="icon-lock"></span></span>
+    <div class="form-group">
+        {{ form_label(form.new.first, 'New password') }}
+        <div class="input-group">
+            {{ form_widget(form.new.first, {'attr': {'class': 'input-lg'}}) }}
+            <span class="input-group-addon"><span class="icon-lock"></span></span>
+        </div>
     </div>
 
-    {{ form_label(form.new.second, 'Confirm new password') }}
-    <div class="input-prepend">
-        {{ form_widget(form.new.second) }}
-        <span class="add-on"><span class="icon-lock-open"></span></span>
+    <div class="form-group">
+        {{ form_label(form.new.second, 'Confirm new password') }}
+        <div class="input-group">
+            {{ form_widget(form.new.second, {'attr': {'class': 'input-lg'}}) }}
+            <span class="input-group-addon"><span class="icon-lock-open"></span></span>
+        </div>
     </div>
 
     {{ form_widget(form) }}
 
-    <div>
-        <input type="submit" class="btn btn-inverse" value="{{ 'change_password.submit'|trans({}, 'FOSUserBundle') }}" />
-    </div>
+    <input type="submit" class="btn btn-block btn-inverse btn-lg" value="{{ 'change_password.submit'|trans({}, 'FOSUserBundle') }}" />
 </form>

+ 29 - 21
app/Resources/FOSUserBundle/views/Profile/edit_content.html.twig

@@ -1,22 +1,28 @@
-<form action="{{ path('fos_user_profile_edit') }}" {{ form_enctype(form) }} method="POST" class="fos_user_profile_edit span4">
+<form action="{{ path('fos_user_profile_edit') }}" {{ form_enctype(form) }} method="POST" class="fos_user_profile_edit col-md-6">
     {{ form_errors(form) }}
 
-    {{ form_label(form.username) }}
-    <div class="input-prepend">
-        {{ form_widget(form.username) }}
-        <span class="add-on"><span class="icon-user"></span></span>
+    <div class="form-group">
+        {{ form_label(form.username) }}
+        <div class="input-group">
+            {{ form_widget(form.username, {'attr': {'class': 'input-lg'}}) }}
+            <span class="input-group-addon"><span class="icon-user"></span></span>
+        </div>
     </div>
 
-    {{ form_label(form.email) }}
-    <div class="input-prepend">
-        {{ form_widget(form.email) }}
-        <span class="add-on"><span class="icon-mail"></span></span>
+    <div class="form-group">
+        {{ form_label(form.email) }}
+        <div class="input-group clearfix">
+            {{ form_widget(form.email, {'attr': {'class': 'input-lg'}}) }}
+            <span class="input-group-addon"><span class="icon-mail"></span></span>
+        </div>
     </div>
 
-    {{ form_label(form.current_password) }}
-    <div class="input-prepend">
-        {{ form_widget(form.current_password) }}
-        <span class="add-on"><span class="icon-lock"></span></span>
+    <div class="form-group">
+        {{ form_label(form.current_password) }}
+        <div class="input-group">
+            {{ form_widget(form.current_password, {'attr': {'class': 'input-lg'}}) }}
+            <span class="input-group-addon"><span class="icon-lock"></span></span>
+        </div>
     </div>
 
     <div class="notifications">
@@ -26,17 +32,19 @@
         </label>
     </div>
 
-    <div>
-        {{ form_widget(form) }}
-        <input type="submit" class="btn btn-success" value="{{ 'profile.edit.submit'|trans({}, 'FOSUserBundle') }}" />
-    </div>
-</form>
+    {{ form_widget(form) }}
+
+    <input type="submit" class="btn btn-block btn-success btn-lg" value="{{ 'profile.edit.submit'|trans({}, 'FOSUserBundle') }}" />
+
+    <hr>
 
-<div class="span4">
     <h5>Using GitHub:</h5>
 
-    <a href="{{ app.user.githubId ? '#' : hwi_oauth_login_url('github') }}" class="btn btn-github {{ app.user.githubId ? 'disabled' : '' }}">
-        <span class="icon-github" style="margin: 0 30px 0 50px"></span>
+    <a href="{{ app.user.githubId ? '#' : hwi_oauth_login_url('github') }}" class="btn btn-block btn-github btn-lg {{ app.user.githubId ? 'disabled' : '' }}">
+        <span class="icon-github"></span>
         {{ app.user.githubId ? 'Accounts connected' : 'Connect accounts' }}
     </a>
+</form>
+
+<div class="col-md-2">
 </div>

+ 33 - 34
app/Resources/FOSUserBundle/views/Profile/show.html.twig

@@ -1,39 +1,38 @@
-{% extends "PackagistWebBundle::layout.html.twig" %}
+{% extends "FOSUserBundle::layout.html.twig" %}
 
 {% import "PackagistWebBundle::macros.html.twig" as macros %}
 
-{% block content %}
-<h2 class="title">
-    {{ user.username }} <small>(that's you!)</small>
-</h2>
-
-<section class="row">
-    <div class="span3">
-        {{ knp_menu_render('profile_menu', {'currentClass': 'active', 'allow_safe_labels': true}) }}
-    </div>
-
-    <section class="span9">
-        {%- if app.user.apiToken %}
-            <h3 class="font-normal">Your API Token</h3>
-            <p>
-                <a id="show-api-token" href="#">Show API Token</a>
-            </p>
-            <pre id="api-token" class="hidden"><code>{{ app.user.apiToken }}</code></pre>
-            <p>You can use your API token to interact with the Packagist API.</p>
-
-            <h3 class="font-normal">GitHub Service Hook</h3>
-            <p>Enabling the Packagist service hook ensures that your package will always be updated instantly when you push to GitHub. To do so you can go to your GitHub repository, click the "Settings" button, then "Webhooks &amp; Services". Add a "Packagist" service, and configure it with your API token (see above), plus your Packagist username. Check the "Active" box and submit the form. You can then hit the "Test Service" button to trigger it and check if Packagist removes the warning about the package not being auto-updated.</p>
-
-            <h3 class="font-normal">Bitbucket POST Service</h3>
-            <p>To enable the Bitbucket service hook, go to your BitBucket repository, open the "Admin" tab and select "Services" in the menu. Pick "POST" in the list and add it to your repository. Afterwards, you have to enter the Packagist endpoint, containing both your username and API token (see above). Enter <code>https://packagist.org/api/bitbucket?username={{ app.user.username }}&amp;apiToken=&hellip;</code> for the service's URL. Save your changes and you're done.</p>
-        {%- endif %}
-
-        <h3 class="font-normal">Your packages</h3>
-        {% if packages|length %}
-            {{ macros.listPackages(packages, true, true, meta) }}
-        {% else %}
-            <p>No packages found.</p>
-        {% endif %}
-    </section>
+{% block fos_user_content %}
+<section class="col-md-9">
+    {%- if app.user.apiToken %}
+        <h3 class="font-normal">Your API Token</h3>
+
+        <div class="input-group clearfix">
+            <input id="api-token" type="text" class="form-control" value="" data-api-token="{{ app.user.apiToken }}" readonly="readonly">
+            <span class="input-group">
+                <button class="btn btn-success btn-show-api-token" type="button">Show API Token</button>
+            </span>
+        </div>
+
+        <p>You can use your API token to interact with the Packagist API.</p>
+
+        <hr>
+
+        <h3 class="font-normal">GitHub Service Hook</h3>
+        <p>Enabling the Packagist service hook ensures that your package will always be updated instantly when you push to GitHub. To do so you can go to your GitHub repository, click the "Settings" button, then "Webhooks &amp; Services". Add a "Packagist" service, and configure it with your API token (see above), plus your Packagist username. Check the "Active" box and submit the form. You can then hit the "Test Service" button to trigger it and check if Packagist removes the warning about the package not being auto-updated.</p>
+
+        <hr>
+
+        <h3 class="font-normal">Bitbucket POST Service</h3>
+        <p>To enable the Bitbucket service hook, go to your BitBucket repository, open the "Admin" tab and select "Services" in the menu. Pick "POST" in the list and add it to your repository. Afterwards, you have to enter the Packagist endpoint, containing both your username and API token (see above). Enter <code>https://packagist.org/api/bitbucket?username={{ app.user.username }}&amp;apiToken=&hellip;</code> for the service's URL. Save your changes and you're done.</p>
+
+        <hr>
+    {%- endif %}
+
+    {% embed "PackagistWebBundle:Web:list.html.twig" with {'noLayout': 'true'} %}
+        {% block content_title %}
+            <h3 class="font-normal">Your packages</h3>
+        {% endblock %}
+    {% endembed %}
 </section>
 {% endblock %}

+ 1 - 0
app/Resources/FOSUserBundle/views/Registration/register.html.twig

@@ -0,0 +1 @@
+{% include "HWIOAuthBundle:Connect:login.html.twig" %}

+ 41 - 0
app/Resources/FOSUserBundle/views/Registration/register_content.html.twig

@@ -0,0 +1,41 @@
+{% trans_default_domain 'FOSUserBundle' %}
+
+<form action="{{ path('fos_user_registration_register') }}" method="POST" class="fos_user_registration_register col-md-6">
+    {% if form is defined %}
+        {{ form_errors(form) }}
+    {% endif %}
+
+    <div class="form-group clearfix">
+        <label for="fos_user_registration_email">Email:</label>
+        <div class="input-group clearfix">
+            <input class="form-control input-lg" type="text" id="fos_user_registration_email" name="fos_user_registration_email">
+            <span class="input-group-addon"><span class="icon-mail"></span></span>
+        </div>
+    </div>
+
+    <div class="form-group">
+        <label for="fos_user_registration_username">Username:</label>
+        <div class="input-group">
+            <input class="form-control input-lg" type="text" id="fos_user_registration_username" name="fos_user_registration_username">
+            <span class="input-group-addon"><span class="icon-user"></span></span>
+        </div>
+    </div>
+
+    <div class="form-group">
+        <label for="fos_user_registration_plainPassword_first">Password:</label>
+        <div class="input-group">
+            <input class="form-control input-lg" type="password" id="fos_user_registration_plainPassword_first" name="fos_user_registration_plainPassword[first]">
+            <span class="input-group-addon"><span class="icon-lock"></span></span>
+        </div>
+    </div>
+
+    <div class="form-group">
+        <label for="fos_user_registration_plainPassword_second">Repeat Password:</label>
+        <div class="input-group">
+            <input class="form-control input-lg" type="password" id="fos_user_registration_plainPassword_second" name="fos_user_registration_plainPassword[second]">
+            <span class="input-group-addon"><span class="icon-lock-open"></span></span>
+        </div>
+    </div>
+
+    <input type="submit" class="btn btn-block btn-primary btn-lg" value="{{ 'registration.submit'|trans }}" />
+</form>

+ 29 - 0
app/Resources/FOSUserBundle/views/Resetting/request.html.twig

@@ -0,0 +1,29 @@
+{% extends "FOSUserBundle::layout.html.twig" %}
+
+{% trans_default_domain 'FOSUserBundle' %}
+
+{% block fos_user_header %}
+<h2 class="title">
+    Resetting password
+</h2>
+{% endblock %}
+
+{% block fos_user_content %}
+<form action="{{ path('fos_user_resetting_send_email') }}" method="POST" class="fos_user_resetting_request col-md-offset-3 col-md-6">
+    {% if invalid_username is defined %}
+        <div class="alert alert-warning">
+            <p>{{ 'resetting.request.invalid_username'|trans({'%username%': invalid_username}) }}</p>
+        </div>
+    {% endif %}
+
+    <div class="form-group">
+        <label for="username">{{ 'resetting.request.username'|trans }}</label>
+        <div class="input-group">
+            <input type="text" id="username" name="username" required="required" class="form-control input-lg" />
+            <span class="input-group-addon"><span class="icon-user"></span></span>
+        </div>
+    </div>
+
+    <input type="submit" class="btn btn-block btn-inverse btn-lg" value="{{ 'resetting.request.submit'|trans }}" />
+</form>
+{% endblock %}

+ 3 - 1
app/Resources/FOSUserBundle/views/layout.html.twig

@@ -2,6 +2,8 @@
 
 {% block content %}
 {% if app.user is null %}
+    {{ block('fos_user_header') }}
+
     <section class="row">
         {{ block('fos_user_content') }}
     </section>
@@ -11,7 +13,7 @@
     </h2>
 
     <section class="row">
-        <div class="span3">
+        <div class="col-md-3">
             {{ knp_menu_render('profile_menu', {'currentClass': 'active', 'allow_safe_labels': true}) }}
         </div>
 

+ 55 - 29
app/Resources/HWIOAuthBundle/views/Connect/login.html.twig

@@ -1,42 +1,68 @@
-{% extends 'HWIOAuthBundle::layout.html.twig' %}
+{% extends "FOSUserBundle::layout.html.twig" %}
 
-{% block hwi_oauth_content %}
-    <section class="row-fluid">
-    {% if error %}
-        <div>{{ error }}</div>
-    {% endif %}
+{% trans_default_domain 'FOSUserBundle' %}
 
-        {# HWIOAuthBundle uses the same template for the login and the connect functionality currently
-           so we need to check if the user is already authenticated. #}
-        <div class="span12">
+{% block fos_user_header %}
+<h3 class="row title">
+    <span class="col-md-6">Sign in</span>
+
+    <span class="col-md-6">Or create new account</span>
+</h3>
+{% endblock %}
+
+{% block fos_user_content %}
+    {# HWIOAuthBundle uses the same template for the login and the connect functionality currently
+       so we need to check if the user is already authenticated. #}
+    <div class="col-md-6">
         {% if not app.user %}
-            <form class="offset1 span4" action="{{ path('login_check') }}" method="post">
-                <div class="input-prepend">
-                    <input type="text" id="username" name="_username" placeholder="{{ 'security.login.username'|trans({}, 'FOSUserBundle') }}">
-                    <span class="add-on"><span class="icon-user"></span></span>
+            {% if error is defined and error is not empty %}
+                <div class="alert alert-warning">
+                    {{ error }}
+                </div>
+            {% endif %}
+
+            <form action="{{ path('login_check') }}" method="POST">
+                <div class="form-group">
+                    <label for="username">{{ 'security.login.username'|trans({}, 'FOSUserBundle') }}</label>
+                    <div class="input-group">
+                        <input class="form-control input-lg" type="text" id="username" name="_username">
+                        <span class="input-group-addon"><span class="icon-user"></span></span>
+                    </div>
                 </div>
 
-                <div class="input-prepend">
-                    <input type="password" id="password" name="_password" placeholder="{{ 'security.login.password'|trans({}, 'FOSUserBundle') }}">
-                    <span class="add-on"><span class="icon-lock"></span></span>
+                <div class="form-group">
+                    <label for="password">{{ 'security.login.password'|trans({}, 'FOSUserBundle') }}</label>
+                    <div class="input-group">
+                        <input class="form-control input-lg" type="text" id="password" name="_password">
+                        <span class="input-group-addon"><span class="icon-lock"></span></span>
+                    </div>
                 </div>
 
+                <div class="checkbox">
+                    <a class="pull-right" href="{{ path('fos_user_resetting_request') }}">Forgot password?</a>
+
+                    <label for="remember_me">
+                        <input type="checkbox" id="remember_me" name="_remember_me" value="on" checked="checked" />
+                        {{- 'security.login.remember_me'|trans({}, 'FOSUserBundle') }}
+                    </label>
+                </div>
+
+                <hr>
+
                 {% if packagist_host and packagist_host in app.request.headers.get('Referer') %}
                     <input type="hidden" name="_target_path" value="{{ app.request.headers.get('Referer') }}" />
                 {% endif %}
-                <input class="btn btn-primary span12" type="submit" id="_submit" name="_submit" value="{{ 'security.login.submit'|trans({}, 'FOSUserBundle') }}" />
+
+                <input class="btn btn-block btn-success btn-lg" type="submit" id="_submit" name="_submit" value="{{ 'security.login.submit'|trans({}, 'FOSUserBundle') }}" />
             </form>
 
-            <div class="offset1 span4">
-                {% for owner in hwi_oauth_resource_owners() %}
-                    <a class="btn btn-{{ owner }}" href="{{ hwi_oauth_login_url(owner) }}"><span class="icon-{{ owner }}"></span>Login with {{ owner | trans({}, 'HWIOAuthBundle') }}</a>{% if not loop.last %}<br />{% endif %}
-                {% endfor %}
-            </div>
-        {% else %}
-            {% for owner in hwi_oauth_resource_owners() %}
-                <a class="btn btn-{{ owner }}" href="{{ hwi_oauth_login_url(owner) }}"><span class="icon-{{ owner }}"></span>Login with {{ owner | trans({}, 'HWIOAuthBundle') }}</a>{% if not loop.last %}<br />{% endif %}
-            {% endfor %}
+            <hr>
         {% endif %}
-        </div>
-    </section>
-{% endblock hwi_oauth_content %}
+
+        {% for owner in hwi_oauth_resource_owners() %}
+            <a class="btn btn-block btn-{{ owner }} btn-lg" href="{{ hwi_oauth_login_url(owner) }}"><span class="icon-{{ owner }}"></span>Login with {{ owner | trans({}, 'HWIOAuthBundle') }}</a>{% if not loop.last %}<br />{% endif %}
+        {% endfor %}
+    </div>
+
+    {% include "FOSUserBundle:Registration:register_content.html.twig" %}
+{% endblock %}

+ 3 - 3
app/Resources/HWIOAuthBundle/views/layout.html.twig

@@ -1,7 +1,7 @@
 {% extends 'PackagistWebBundle::layout.html.twig' %}
 
 {% block content %}
-    <div class="box clearfix">
-        {% block hwi_oauth_content %}{% endblock %}
-    </div>
+<section class="row">
+     {% block hwi_oauth_content %}{% endblock %}
+</section>
 {% endblock %}

+ 3 - 0
app/config/config.yml

@@ -26,6 +26,9 @@ framework:
 
 # Twig Configuration
 twig:
+    form:
+        resources:
+           - 'PackagistWebBundle::forms.html.twig'
     debug:            %kernel.debug%
     strict_variables: %kernel.debug%
     globals:

+ 45 - 33
src/Packagist/WebBundle/Controller/WebController.php

@@ -131,35 +131,39 @@ class WebController extends Controller
      */
     public function popularAction(Request $req)
     {
-        $redis = $this->get('snc_redis.default');
-        $perPage = $req->query->getInt('per_page', 15);
-        if ($perPage <= 0 || $perPage > 100) {
-            if ($req->getRequestFormat() === 'json') {
-                return new JsonResponse(array(
-                    'status' => 'error',
-                    'message' => 'The optional packages per_page parameter must be an integer between 1 and 100 (default: 15)',
-                ), 400);
+        try {
+            $redis = $this->get('snc_redis.default');
+            $perPage = $req->query->getInt('per_page', 15);
+            if ($perPage <= 0 || $perPage > 100) {
+                if ($req->getRequestFormat() === 'json') {
+                    return new JsonResponse(array(
+                        'status' => 'error',
+                        'message' => 'The optional packages per_page parameter must be an integer between 1 and 100 (default: 15)',
+                    ), 400);
+                }
+
+                $perPage = max(0, min(100, $perPage));
             }
 
-            $perPage = max(0, min(100, $perPage));
+            $popularIds = $redis->zrevrange(
+                'downloads:trending',
+                ($req->get('page', 1) - 1) * $perPage,
+                $req->get('page', 1) * $perPage - 1
+            );
+            $popular = $this->getDoctrine()->getRepository('PackagistWebBundle:Package')
+                ->createQueryBuilder('p')->where('p.id IN (:ids)')->setParameter('ids', $popularIds)
+                ->getQuery()->useResultCache(true, 900, 'popular_packages')->getResult();
+            usort($popular, function ($a, $b) use ($popularIds) {
+                return array_search($a->getId(), $popularIds) > array_search($b->getId(), $popularIds) ? 1 : -1;
+            });
+
+            $packages = new Pagerfanta(new FixedAdapter($redis->zcard('downloads:trending'), $popular));
+            $packages->setMaxPerPage($perPage);
+            $packages->setCurrentPage($req->get('page', 1), false, true);
+        } catch (ConnectionException $e) {
+            $packages = new Pagerfanta(new FixedAdapter(0, array()));
         }
 
-        $popularIds = $redis->zrevrange(
-            'downloads:trending',
-            ($req->get('page', 1) - 1) * $perPage,
-            $req->get('page', 1) * $perPage - 1
-        );
-        $popular = $this->getDoctrine()->getRepository('PackagistWebBundle:Package')
-            ->createQueryBuilder('p')->where('p.id IN (:ids)')->setParameter('ids', $popularIds)
-            ->getQuery()->useResultCache(true, 900, 'popular_packages')->getResult();
-        usort($popular, function ($a, $b) use ($popularIds) {
-            return array_search($a->getId(), $popularIds) > array_search($b->getId(), $popularIds) ? 1 : -1;
-        });
-
-        $packages = new Pagerfanta(new FixedAdapter($redis->zcard('downloads:trending'), $popular));
-        $packages->setMaxPerPage($perPage);
-        $packages->setCurrentPage($req->get('page', 1), false, true);
-
         $data = array(
             'packages' => $packages,
             'searchForm' => $this->createSearchForm()->createView(),
@@ -325,7 +329,14 @@ class WebController extends Controller
 
             $paginator->setCurrentPage($req->query->get('page', 1), false, true);
 
-            $metadata = $this->getPackagesMetadata($paginator);
+            try {
+                $metadata = $this->getPackagesMetadata($paginator);
+            } catch (\Solarium_Client_HttpException $e) {
+                return new JsonResponse(array(
+                    'status' => 'error',
+                    'message' => 'Could not connect to the search server',
+                ), 500);
+            }
 
             if ($req->getRequestFormat() === 'json') {
                 try {
@@ -406,11 +417,13 @@ class WebController extends Controller
                 'meta' => $metadata,
                 'searchForm' => $form->createView(),
             ));
-        } elseif ($req->getRequestFormat() === 'json') {
+        }
+
+        if ($req->getRequestFormat() === 'json') {
             return new JsonResponse(array('error' => 'Missing search query, example: ?q=example'), 400);
         }
 
-        return $this->render('PackagistWebBundle:Web:search.html.twig', array('searchForm' => $form->createView()));
+        return $this->render('PackagistWebBundle:Web:search.html.twig', array('searchForm' => $form->createView(), 'packages' => array()));
     }
 
     /**
@@ -450,7 +463,7 @@ class WebController extends Controller
     /**
      * @Route("/packages/fetch-info", name="submit.fetch_info", defaults={"_format"="json"})
      */
-    public function fetchInfoAction()
+    public function fetchInfoAction(Request $req)
     {
         $package = new Package;
         $package->setEntityRepository($this->getDoctrine()->getRepository('PackagistWebBundle:Package'));
@@ -458,7 +471,6 @@ class WebController extends Controller
         $form = $this->createForm(new PackageType, $package);
 
         $response = array('status' => 'error', 'reason' => 'No data posted.');
-        $req = $this->getRequest();
         if ('POST' === $req->getMethod()) {
             $form->bind($req);
             if ($form->isValid()) {
@@ -500,7 +512,7 @@ class WebController extends Controller
             }
         }
 
-        return new Response(json_encode($response));
+        return new JsonResponse($response);
     }
 
     /**
@@ -618,7 +630,7 @@ class WebController extends Controller
             }
 
             // TODO invalidate cache on update and make the ttl longer
-            $response = new Response(json_encode(array('package' => $data)), 200);
+            $response = new JsonResponse(array('package' => $data));
             $response->setSharedMaxAge(3600);
 
             return $response;
@@ -867,7 +879,7 @@ class WebController extends Controller
             return new Response('{"status": "success"}', 202);
         }
 
-        return new Response(json_encode(array('status' => 'error', 'message' => 'Could not find a package that matches this request (does user maintain the package?)',)), 404);
+        return new JsonResponse(array('status' => 'error', 'message' => 'Could not find a package that matches this request (does user maintain the package?)',), 404);
     }
 
     /**

+ 5 - 1
src/Packagist/WebBundle/Form/Type/PackageType.php

@@ -24,7 +24,11 @@ class PackageType extends AbstractType
     public function buildForm(FormBuilderInterface $builder, array $options)
     {
         $builder->add('repository', 'text', array(
-            'label' => 'Repository URL (Git/Svn/Hg)'
+            'label' => 'Repository URL (Git/Svn/Hg)',
+            'attr'  => array(
+                'class'       => 'input-lg',
+                'placeholder' => 'i.e.: git://github.com/composer/composer.git',
+            )
         ));
     }
 

+ 1 - 1
src/Packagist/WebBundle/Menu/MenuBuilder.php

@@ -20,7 +20,7 @@ class MenuBuilder
     public function createUserMenu()
     {
         $menu = $this->factory->createItem('root');
-        $menu->setChildrenAttribute('class', 'nav-user-menu');
+        $menu->setChildrenAttribute('class', 'list-unstyled');
 
         $menu->addChild('Profile', array('label' => '<span class="icon-vcard"></span>Profile', 'route' => 'fos_user_profile_show', 'extras' => array('safe_label' => true)));
         $menu->addChild('Settings', array('label' => '<span class="icon-tools"></span>Settings', 'route' => 'fos_user_profile_edit', 'extras' => array('safe_label' => true)));

+ 271 - 111
src/Packagist/WebBundle/Resources/public/css/main.css

@@ -11,11 +11,19 @@
     font-style: normal;
 }
 
+html, body {
+    height: 100%;
+}
+
 body {
     background: #fff;
     font-family: 'Open Sans', sans-serif;
 }
 
+h1, h2, h3, h4, h5, h6 {
+    font-family: 'Open Sans', sans-serif;
+}
+
 .navbar-wrapper {
     left: 0;
     right: 0;
@@ -28,36 +36,40 @@ body {
     margin: 0;
 }
 
-.navbar-inner {
-    padding-right: 0;
-    background: none;
-    box-shadow: none;
-    border: 0;
-}
-
-.navbar-fixed-top {
-    height: 57px;
-}
-
-.navbar h1 {
+.navbar .navbar-brand {
+    padding: 12px 15px;
     line-height: 32px;
     margin: 0;
 }
 
-.navbar h1 .brand {
-    padding-left: 0;
+.navbar .navbar-brand a {
+    float: left;
     font-size: 24px;
     font-weight: 600;
     color: #fff;
+    text-decoration: none;
 }
 
-.navbar h1 em {
-    display: block;
-    padding-top: 18px;
+.navbar .navbar-brand em {
+    float: left;
+    padding-left: 15px;
+    padding-top: 8px;
     font: italic 14px 'Times New Roman';
     color: #BDC3C7;
 }
 
+.navbar-collapse {
+    border: 0;
+}
+
+.navbar-toggle {
+    background: rgba(0, 0, 0, 0.3);
+}
+
+.navbar-toggle span {
+    background: #fff;
+}
+
 .navbar .nav > li > a, .navbar .nav > .active > a {
     padding: 18px 25px 19px;
     background: none;
@@ -88,6 +100,7 @@ body {
 }
 
 .navbar .nav {
+    float: right;
     margin: 0;
     color: #fff;
     font-family: 'Open Sans', sans-serif;
@@ -110,41 +123,28 @@ body {
 }
 
 .navbar .nav > .nav-user > .nav-user-signin {
-    padding: 18px 20px 19px;
+    padding: 17px 20px;
     border-left: 1px solid rgba(69, 78, 92, 0.5);
     color: #454e5c;
 }
 
 .navbar .nav > .nav-user > section a {
     color: #fff;
+    text-decoration: none;
 }
 
 .navbar .nav > .nav-user > section:hover {
-    background: #f6f3ef;
-    color: #454e5c;
-}
-
-.navbar .nav > .nav-user > section:hover a {
-    color: #454e5c;
-}
-
-.navbar .nav > .nav-user > .nav-user-signin:hover a.btn {
-    color: #fff;
+    background: rgba(0, 0, 0, 0.3);
 }
 
-.navbar .nav > .nav-user > section > .nav-user-menu, .navbar .nav > .nav-user > section > .nav-user-signin-menu {
+.navbar .nav > .nav-user > section > .nav-user-menu, .navbar .nav > .nav-user > section > .signin-box {
     display: none;
 }
 
-.navbar .nav > .nav-user > section:hover > .nav-user-menu, .navbar .nav > .nav-user > section:hover > .nav-user-signin-menu {
+.navbar .nav > .nav-user > section:hover > .nav-user-menu, .navbar .nav > .nav-user > section:hover > .signin-box {
     display: block;
 }
 
-.navbar .nav > .nav-user > section:hover > .nav-user-menu a {
-    color: #454e5c;
-    text-decoration: none;
-}
-
 .navbar .nav > .nav-user > section:hover > .nav-user-menu a:hover {
     color: #08c;
 }
@@ -152,7 +152,7 @@ body {
 .nav-user section img {
     width: 57px;
     height: 57px;
-    padding-right: 15px;
+    margin-right: 15px;
     margin-top: -5px;
 }
 
@@ -160,19 +160,24 @@ body {
     top: 57px;
     position: absolute;
     z-index: 110;
-    width: 174px;
+    width: 220px;
+    background: #2C3E50;
+    box-shadow: 2px 3px 3px #454e5c;
+}
+
+.nav-user .nav-user-menu ul {
     padding: 12px 15px 12px 20px;
-    margin: 0;
-    list-style: none;
-    background: #f6f3ef;
-    box-shadow: 3px 2px 4px #ddd;
-    color: #454e5c;
+    background: rgba(0, 0, 0, 0.3);
 }
 
 .nav-user .nav-user-menu li {
     line-height: 31px;
 }
 
+.nav-user .nav-user-menu a {
+    color: #fff;
+}
+
 .nav-user .nav-user-menu span {
     float: right;
     font-size: 20px;
@@ -186,52 +191,86 @@ body {
     margin: 7px 0 5px;
 }
 
-.nav-user .nav-user-signin-menu {
+.nav-user .signin-box {
     top: 57px;
-    left: -228px;
+    left: -209px;
+    width: 300px;
     position: absolute;
     z-index: 110;
-    padding: 12px 20px;
-    margin: 0;
-    list-style: none;
-    background: #f6f3ef;
-    box-shadow: 0 3px 3px #ccc;
-    color: #454e5c;
+    background: #1f2b38;
+    box-shadow: 0 3px 3px rgba(0, 0, 0, 0.3);
+    color: #fff;
 }
 
-.nav-user .nav-user-signin-menu .input-prepend input {
-    width: 83%;
+.nav-user .signin-box form {
+    padding: 15px 20px 12px;
 }
 
-.nav-user .nav-user-signin-menu .input-prepend .add-on {
+.nav-user .signin-box .input-group-addon {
     background: #fff;
+    border-top-left-radius: 3px;
+    border-bottom-left-radius: 3px;
 }
 
-.nav-user .nav-user-signin-menu div {
-    font-size: 12px;
+.nav-user .signin-box .form-control {
+    border-left: 0;
+    box-shadow: none;
+    border-radius: 3px;
 }
 
-.nav-user .nav-user-signin-menu label {
-    width: 99%;
+.nav-user .signin-box div {
+    font-size: 13px;
 }
 
-.nav-user .btn-github {
+.nav-user .signin-box .checkbox {
+    float: left;
+    margin-top: 4px;
+}
+
+.nav-user .signin-box .signin-box-buttons {
+    float: left;
+    width: 100%;
+}
+
+.nav-user .signin-box .signin-box-register {
+    background: #7f8c8d;
+    float: left;
+    font-size: 14px;
     font-weight: 600;
-    text-align: left;
-    line-height: inherit;
-    width: 37%;
+    margin: 11px 0 0;
+    padding: 7px 21px;
+    width: 100%;
+    text-align: center;
 }
 
-.nav-user .btn-github span {
-    font-size: 18px;
-    margin: 0 6px 0 0;
+.nav-user .signin-box label {
+    vertical-align: middle;
 }
 
 .nav-user .btn-success {
-    width: 47%;
+    width: 42%;
+}
+
+.nav-user .btn-github {
+    font-weight: 600;
+    padding: 7px 12px 5px;
+    width: 56%;
+}
+
+.nav-user .btn-github span {
+    font-size: 20px;
+    margin: 0 4px 0 0;
+    vertical-align: bottom;
 }
 
 
+.wrap {
+    min-height: 100%;
+    height: auto;
+    margin: 0 auto -135px;
+    padding: 0 0 135px;
+}
+
 .wrapper .container {
     padding: 26px 20px;
     color: #1a1d24;
@@ -312,19 +351,25 @@ footer p {
 
 
 #search-form {
-    margin-top: 19px;
+    margin-top: 5px;
 }
 
 #search-form input[type="search"] {
-    width: 98%;
     padding: 0 0 0 15px;
     font: 20px 'Open Sans', sans-serif;
     border: 0;
     height: 58px;
     color: #555;
     outline: none;
-    float: left;
-    box-sizing: border-box;
+}
+
+#search-form input[type="search"]:focus {
+    border-radius: 2px;
+    color: #2c3e50;
+}
+
+#search-form .form-control:focus::-moz-placeholder {
+    color: #2c3e50;
 }
 
 
@@ -332,12 +377,11 @@ button {
     font-family: 'Open Sans';
 }
 
-input[type="email"], input[type="text"], input[type="password"], textarea, select {
+.form-control {
     min-height: 20px;
     padding: 7px 11px 6px 9px;
     box-shadow: none;
     border-radius: 2px;
-    font-size: 13px;
     font-family: 'Open Sans', sans-serif;
 
     transition: none;
@@ -346,36 +390,57 @@ input[type="email"], input[type="text"], input[type="password"], textarea, selec
     -webkit-transition: none;
 }
 
-input[disabled], select[disabled], textarea[disabled] {
+.form-control[disabled] {
     background: #fff;
 }
 
-input[type="email"]:focus, input[type="text"]:focus, input[type="password"]:focus, textarea:focus, select:focus {
+.form-control:focus {
     padding: 6px 9px 5px;
     box-shadow: none;
     border-width: 2px;
     border-radius: 0 2px 2px 0;
 }
 
+.form-control[readonly] {
+    cursor: pointer;
+}
+
+.form-control[readonly]:focus {
+    padding: 7px 11px 6px 9px;
+    border-radius: 2px;
+    box-shadow: none;
+}
+
+.form-control[data-api-token] {
+    font-size: 16px;
+    height: 42px;
+    padding: 5px 11px 7px 9px;
+}
+
 input:focus:invalid:focus, textarea:focus:invalid:focus, select:focus:invalid:focus {
     box-shadow: none;
 }
 
-.input-append, .input-prepend {
+.input-group {
     display: inline;
 }
 
-.input-prepend input {
+.input-group .form-control {
+    width: 90%;
     float: right;
     margin: 0 0 6px;
-    width: 85%;
 }
 
-.input-prepend .add-on {
+.input-group #api-token {
+    width: 81%;
+}
+
+.input-group .input-group-addon {
     float: left;
     min-height: 22px;
     width: 10%;
-    padding: 5px 0 6px;
+    line-height: 20px;
+    padding: 6px 0;
     margin: 0 -4px 6px 0;
     border-right: 0;
     background: none;
@@ -383,24 +448,29 @@ input:focus:invalid:focus, textarea:focus:invalid:focus, select:focus:invalid:fo
     font-size: 16px;
 }
 
-.input-append .add-on-button {
+.input-group input.input-lg ~ .input-group-addon {
+    line-height: 32px;
+    font-size: 20px;
+}
+
+.input-group .input-group-addon-button {
     padding: 0;
 }
 
-.input-append .add-on .btn {
+.input-group .input-group-addon .btn {
     padding: 6px 20px;
     border-width: 0 0 2px;
 }
 
-.input-prepend input:focus ~ .add-on {
-    padding-top: 4px;
+.input-group input:focus ~ .input-group-addon {
+    padding-top: 5px;
     padding-bottom: 5px;
-    border-width: 2px;
-    border-color: rgba(82, 168, 236, 0.8);
+    border: 2px solid rgba(82, 168, 236, 0.8);
+    border-right: 0;
     border-radius: 2px 0 0 2px;
 }
 
-.input-prepend input:focus ~ .add-on span {
+.input-group input:focus ~ .input-group-addon span {
     margin-left: -1px;
     color: rgb(82, 168, 236);
 }
@@ -434,27 +504,38 @@ ul.packages .abandoned {
   margin-top: 5px;
 }
 
-.input-prepend input:focus:invalid:focus ~ .add-on {
+.input-group input:focus:invalid:focus {
+    border-color: #fa3a15;
+}
+
+.input-group input:focus:invalid:focus ~ .input-group-addon {
     border-color: #fa3a15;
 }
 
-.input-prepend input:focus:invalid:focus ~ .add-on span {
+.input-group input:focus:invalid:focus ~ .input-group-addon span {
     color: #fa3a15;
 }
 
-.input-prepend input, .input-prepend select {
+.input-group input, .input-group select {
     border-left: 0;
 }
 
-.input-prepend .add-on:first-child, .input-prepend .btn:first-child {
+.input-group-addon:last-child, .input-group .btn:last-child {
     border-radius: 2px 0 0 2px;
 }
 
-.nav-tabs {
-    border: 0;
+.input-group .form-control:first-child, .input-group-addon:first-child {
+    border-bottom-left-radius: 0;
+    border-top-left-radius: 0;
 }
 
-.nav-tabs li {
+.input-group-addon:last-child {
+    border-left: 1px solid;
+    border-right: 0;
+}
+
+.nav-tabs {
+    border: 0;
 }
 
 .nav-tabs li a {
@@ -463,6 +544,15 @@ ul.packages .abandoned {
     color: #515966;
     font-size: 15px;
     font-weight: 600;
+    border-radius: 0;
+}
+
+.nav-tabs li:first-child a {
+    border-radius: 4px 4px 0 0;
+}
+
+.nav-tabs li:last-child a {
+    border-radius: 0 0 4px 4px;
 }
 
 .nav-tabs li.active a, .nav-tabs.nav-stacked > li > a:hover {
@@ -481,6 +571,7 @@ ul.packages .abandoned {
 .nav-tabs.nav-stacked [class^="icon-"], .nav-tabs.nav-stacked [class*=" icon-"] {
     float: right;
     font-size: 20px;
+    margin-top: -2px;
 }
 
 
@@ -488,13 +579,23 @@ ul.packages .abandoned {
     padding: 9px 20px;
     box-shadow: none;
     border: 0;
-    border-bottom: 4px solid #a5aab0;
+    border-bottom: 4px solid;
     border-radius: 3px;
-    background: #bbbfc4;
     text-shadow: none;
-    font-size: 14px;
     font-weight: 600;
+}
+
+.btn.loading {
+    background-image: url("../img/loader.gif");
+    background-position: 95% 50%;
+    background-repeat: no-repeat;
+}
+
+.btn-default {
+    background: #bbbfc4;
+    border-bottom-color: #a5aab0;
     color: #fff;
+    font-size: 14px;
 }
 
 .btn.disabled {
@@ -511,11 +612,6 @@ ul.packages .abandoned {
     color: #333;
 }
 
-.btn:focus, .btn:active {
-    padding-bottom: 11px;
-    border-bottom: 2px solid;
-}
-
 .btn-danger, .btn-danger:hover {
     background: #ff4533;
     border-bottom-color: #cd3729;
@@ -526,6 +622,7 @@ ul.packages .abandoned {
 }
 
 .btn-inverse, .btn-inverse:hover {
+    color: #fff;
     background: #454e5c;
     border-bottom-color: #303339;
 }
@@ -534,11 +631,16 @@ ul.packages .abandoned {
     border-bottom-color: #303339;
 }
 
-.btn-primary, .btn-primary:hover {
+.btn-primary {
     background: #36b7e6;
     border-bottom-color: #2fa1ca;
 }
 
+.btn-primary:hover, .btn-github:hover {
+    background: #2fa1ca;
+    border-bottom-color: #2980b9;
+}
+
 .btn-primary:focus, .btn-primary:active {
     border-bottom-color: #2fa1ca;
 }
@@ -553,18 +655,21 @@ ul.packages .abandoned {
 }
 
 .btn-github {
-    width: 88%;
     line-height: 26px;
-    text-align: left;
-    font-weight: 400;
+    font-weight: 600;
+    color: #fff;
+    background: #36b7e6;
+    border-bottom-color: #2fa1ca;
+    text-align: center;
 }
 
 .btn-github span {
-    margin: 0 25px;
+    margin: 0 15px 0 0;
     vertical-align: text-top;
     font-size: 22px;
 }
 
+
 .package .action.abandon input, .package .action.un-abandon input {
   background: #ec400b;
   background: -moz-linear-gradient(top, #ec400b 0%, #f5813f 100%);
@@ -588,6 +693,16 @@ ul.packages .abandoned {
   padding-left: 30px;
 }
 
+.btn-github.disabled {
+    background: #ecf0f1;
+    color: #bdc3c7;
+}
+
+.btn-show-api-token {
+    width: 19%;
+}
+
+
 .pagination {
     margin: 6px 0;
 }
@@ -658,7 +773,7 @@ pre {
     margin: -15px 0 15px;
     padding: 0 0 15px;
     border-bottom: 1px solid #DDDFE1;
-    color: #191D23;
+    color: #2c3e50;
     font-size: 55px;
     font-weight: 600;
     line-height: 60px;
@@ -717,19 +832,17 @@ pre {
 }
 
 
-.span6 .input-prepend input {
+.col-lg-6 .input-group input {
     width: 89%;
 }
 
-.span6 .input-prepend .add-on {
+.col-lg-6 .input-group .input-group-addon {
     margin-right: -5px;
     width: 8%;
 }
 
 
-#submit-package-form input[type="text"] {
-    font-size: 14px;
-    height: 40px;
+#submit-package-form .form-control {
     border-radius: 4px;
 }
 
@@ -741,6 +854,53 @@ pre {
 }
 
 
+.packages-short ul {
+    list-style: none;
+    margin: 0;
+}
+
+.packages-short li {
+    margin-bottom: 6px;
+}
+
+.packages-short li a {
+    font-size: 20px;
+    display: block;
+}
+
+.packages-short li a strong {
+    margin-left: 5px;
+    font-size: 14px;
+}
+
+.packages li {
+    padding: 5px 15px;
+    margin-bottom: 10px;
+    background: #f0f5f3;
+    border-radius: 4px;
+}
+
+.packages li.selected {
+    padding: 3px 13px;
+    border: 2px solid #2980b9;
+}
+
+.packages li h4 {
+    color: #2c3e50;
+    font-size: 20px;
+    margin-bottom: 0;
+}
+
+.packages li h4 small {
+    margin-left: 5px;
+}
+
+.packages li .metadata span {
+    margin-left: 8px;
+}
+
+
+
 [class^="icon-"]:before,
 [class*=" icon-"]:before {
     font-family: 'fontello';

BIN
src/Packagist/WebBundle/Resources/public/img/loader.gif


+ 15 - 4
src/Packagist/WebBundle/Resources/public/js/layout.js

@@ -26,9 +26,20 @@
         }
     });
 
-    $('#show-api-token').click(function (e) {
-        $(this).parent().hide();
-        $('#api-token').toggleClass('hidden');
-        e.preventDefault();
+    /**
+     * API Token visibility toggling
+     */
+    var token = $('#api-token');
+    token.val('');
+
+    $('.btn-show-api-token,#api-token').each(function() {
+        $(this).click(function (e) {
+            token.val(token.data('api-token'));
+            token.select();
+
+            $('.btn-show-api-token').text('Your API token');
+
+            e.preventDefault();
+        });
     });
 })(jQuery, humane);

+ 4 - 4
src/Packagist/WebBundle/Resources/public/js/submitPackage.js

@@ -2,18 +2,18 @@
     var showSimilarMax = 5;
     var onSubmit = function(e) {
         var success;
-        $('div > ul, div.confirmation', this).remove();
+        $('ul.package-errors, ul.similar-packages, div.confirmation', this).remove();
         success = function (data) {
             var html = '';
             $('#submit').removeClass('loading');
             if (data.status === 'error') {
                 $.each(data.reason, function (k, v) {
-                    html += '<li>'+v+'</li>';
+                    html += '<li><div class="alert alert-warning">'+v+'</div></li>';
                 });
-                $('#submit-package-form div').prepend('<ul>'+html+'</ul>');
+                $('#submit-package-form').prepend('<ul class="list-unstyled package-errors">'+html+'</ul>');
             } else {
                 if (data.similar.length) {
-                    var $similar = $('<ul>');
+                    var $similar = $('<ul class="list-unstyled similar-packages">');
                     var limit = data.similar.length > showSimilarMax ? showSimilarMax : data.similar.length;
                     for ( var i = 0; i < limit; i++ ) {
                         var similar = data.similar[i];

+ 19 - 25
src/Packagist/WebBundle/Resources/views/About/about.html.twig

@@ -5,16 +5,15 @@
     <p>Packagist is a Composer package repository. It lets you find packages and lets Composer know where to get the code from. You can use Composer to manage your project or libraries' dependencies - read more about it on the <a href="http://getcomposer.org/">Composer website</a>.</p>
     <p>You can find the packagist.org source on <a href="https://github.com/composer/packagist">GitHub</a>.</p>
 
-    <h2 class="font-normal">How to submit packages?</h2>
+    <h2 class="font-normal" id="how-to-submit-packages">How to submit packages?</h2>
     <section class="row">
-        <section class="span6">
-            <h3 class="font-slim">Naming your package</h3>
+        <section class="col-md-6">
+            <h3 class="font-slim" id="naming-your-package">Naming your package</h3>
             <p>First of all, you must pick a package name. This is a very important step since it can not change and it should be unique enough to avoid conflicts in the future.</p>
             <p>The package name consists of a vendor name and a project name joined by a <code>/</code>. The vendor name exists to prevent naming conflicts. For example, by including a vendor name both <code>igorw</code> and <code>seldaek</code> can have a library named <code>json</code> by naming their packages <code>igorw/json</code> and <code>seldaek/json</code>.</p>
             <p>In some cases the vendor name and the package name may be identical. An example of this would be `monolog/monolog`. For projects with a unique name this is recommended. It also allows adding more related projects under the same vendor later on. If you are maintaining a library, this would make it really easy to split it up into smaller decoupled parts.</p>
             <p>Here is a list of typical package names for reference:</p>
-            <pre>
-// Monolog is a library, so the vendor name and package name are the same.
+            <pre><code>// Monolog is a library, so the vendor name and package name are the same.
 monolog/monolog
 
 // That could be the name of a drupal module (maintained/provided by monolog,
@@ -23,17 +22,15 @@ monolog/monolog-drupal-module
 
 // Acme is a company or person here, they can name their package with a common name (Email).
 // As long as it's in their own vendor namespace it does not conflict with anyone else.
-acme/email
-</pre>
+acme/email</code></pre>
             <p>Note that package names are case-insensitive, but it's encouraged to use a dash (-) as separator instead of CamelCased names.</p>
         </section>
 
-        <section class="span6">
-            <h3 class="font-slim">Creating a composer.json file</h3>
+        <section class="col-md-6">
+            <h3 class="font-slim" id="creating-a-composerjson-file">Creating a composer.json file</h3>
             <p>The composer.json file should reside at the top of your package's git/svn/.. repository, and is the way you describe your package to both packagist and composer.</p>
             <p>A typical composer.json file looks like this:</p>
-            <pre>
-{
+            <pre><code>{
     "name": "monolog/monolog",
     "type": "library",
     "description": "Logging for PHP 5.3",
@@ -56,43 +53,40 @@ acme/email
             "Monolog": "src"
         }
     }
-}
-</pre>
+}</code></pre>
             <p>Most of this information is obvious, keywords are tags, require are list of dependencies that your package has. This can of course be packages, not only a php version. You can use ext-foo to require php extensions (e.g. ext-curl). Note that most extensions don't expose version information, so unless you know for sure it does, it's safer to use <code>"ext-curl": "*"</code> to allow any version of it. Finally the type field is in this case indicating that this is a library. If you do plugins for frameworks etc, and if they integrate composer, they may have a custom package type for their plugins that you can use to install the package with their own installer. In the absence of custom type, you can omit it or use "library".</p>
             <p>Once you have this file committed in your repository root, you can <a href="{{ path('submit') }}">submit the package</a> to Packagist by entering the public repository URL.</p>
         </section>
 
-        <section class="span6">
-            <h3 class="font-slim">Managing package versions</h3>
+        <section class="col-md-6">
+            <h3 class="font-slim" id="managing-package-versions">Managing package versions</h3>
             <p>New versions of your package are automatically fetched from tags you create in your VCS repository.</p>
             <p>The easiest way to manage versioning is to just omit the version field from the composer.json file. The version numbers will then be parsed from the tag and branch names.</p>
             <p>Tag/version names should match 'X.Y.Z', or 'vX.Y.Z', with an optional suffix for RC, beta, alpha or patch versions. Here are a few examples of valid tag names:</p>
-<pre>
-1.0.0
+            <pre><code>1.0.0
 v1.0.0
 1.10.5-RC1
 v4.4.4beta2
 v2.0.0-alpha
-v2.0.4-p1
-</pre>
+v2.0.4-p1</code></pre>
             <p>Branches will automatically appear as "dev" versions that are easily installable by anyone that wants to try your library's latest and greatest, but that does not mean you should not tag releases. The use of <a href="http://semver.org/">Semantic Versioning</a> is strongly encouraged.</p>
         </section>
 
-        <section class="span6">
-            <h3 class="font-slim">Update Schedule</h3>
+        <section class="col-md-6">
+            <h3 class="font-slim" id="update-schedule">Update Schedule</h3>
             <p>New packages will be crawled <strong>immediately</strong> after submission if you have JS enabled.</p>
             <p>Existing packages without auto-updating (GitHub/BitBucket hook) will be crawled <strong>once a day</strong> for updates. When a hook is enabled packages are crawled whenever you push, or at least once a week in case the crawl failed. You can also trigger a manual update on your package page if you are logged-in as a maintainer.</p>
             <p>It is highly recommended to set up the <strong>GitHub/BitBucket service hook</strong> for all your packages. This reduces the load on our side, and ensures your package is updated almost instantly. Check the how-to in your <a href="{{ path('fos_user_profile_show') }}">profile</a> page.</p>
             <p>The search index is updated <strong>every five minutes</strong>. It will index (or reindex) any package that has been crawled since the last time the search indexer ran.</p>
         </section>
 
-        <section class="span12">
-            <h3 class="font-slim">Community</h3>
+        <section class="col-md-12">
+            <h3 class="font-slim" id="community">Community</h3>
             <p>If you have questions about composer or want to help out, come and join us in the <em><a href="irc://irc.freenode.net/#composer">#composer</a></em> channel on irc.freenode.net. You can find more community resources in the <a href="https://getcomposer.org/doc/06-community.md">Composer documentation</a>.</p>
         </section>
 
-        <section class="span12">
-            <h3 class="font-slim">Contributing</h3>
+        <section class="col-md-12">
+            <h3 class="font-slim" id="community">Contributing</h3>
             <p>To report issues or contribute code you can find the source repository on <a href="https://github.com/composer/packagist">GitHub</a>.</p>
         </section>
     </section>

+ 36 - 4
src/Packagist/WebBundle/Resources/views/User/favorites.html.twig

@@ -1,5 +1,37 @@
-{% extends "PackagistWebBundle:Web:list.html.twig" %}
+{% extends "PackagistWebBundle:User:packages.html.twig" %}
 
-{% block content_title %}
-    {{ user.username }}'s favorite packages
-{% endblock %}
+{% import "PackagistWebBundle::macros.html.twig" as macros %}
+
+{% set isActualUser = app.user and app.user.username is sameas(user.username) %}
+
+{% block content %}
+<h2 class="title">
+    {{ user.username }}
+    <small>
+        {%- if isActualUser %}
+            (that's you!)
+        {%- else %}
+            member since: {{ user.createdAt|date('M d, Y') }}
+            {%- if is_granted('ROLE_ADMIN') %}
+                <strong>email: <a href="mailto:{{ user.email }}">{{ user.email }}</a></strong>
+            {%- endif %}
+        {%- endif %}
+    </small>
+</h2>
+
+<section class="row">
+    {% if isActualUser %}
+        <section class="col-md-3">
+            {{ knp_menu_render('profile_menu', {'currentClass': 'active', 'allow_safe_labels': true}) }}
+        </section>
+    {% endif %}
+
+    <section class="{{ isActualUser ? 'col-md-9' : 'col-md-12' }}">
+        {% embed "PackagistWebBundle:Web:list.html.twig" with {'noLayout': 'true'} %}
+            {% block content_title %}
+                <h3 class="font-normal">{{ isActualUser ? 'Your' : user.username~'\'s' }} favorite packages</h3>
+            {% endblock %}
+        {% endembed %}
+    </section>
+</section>
+{% endblock %}

+ 36 - 4
src/Packagist/WebBundle/Resources/views/User/packages.html.twig

@@ -1,5 +1,37 @@
-{% extends "PackagistWebBundle:Web:list.html.twig" %}
+{% extends "PackagistWebBundle::layout.html.twig" %}
 
-{% block content_title %}
-    Packages maintained by {{ user.username }}
-{% endblock %}
+{% import "PackagistWebBundle::macros.html.twig" as macros %}
+
+{% set isActualUser = app.user and app.user.username is sameas(user.username) %}
+
+{% block content %}
+<h2 class="title">
+    {{ user.username }}
+    <small>
+        {%- if isActualUser %}
+            (that's you!)
+        {%- else %}
+            member since: {{ user.createdAt|date('M d, Y') }}
+            {%- if is_granted('ROLE_ADMIN') %}
+                <strong>email: <a href="mailto:{{ user.email }}">{{ user.email }}</a></strong>
+            {%- endif %}
+        {%- endif %}
+    </small>
+</h2>
+
+<section class="row">
+    {% if isActualUser %}
+        <section class="col-md-3">
+            {{ knp_menu_render('profile_menu', {'currentClass': 'active', 'allow_safe_labels': true}) }}
+        </section>
+    {% endif %}
+
+    <section class="{{ isActualUser ? 'col-md-9' : 'col-md-12' }}">
+        {% embed "PackagistWebBundle:Web:list.html.twig" with {'noLayout': 'true'} %}
+            {% block content_title %}
+                <h3 class="font-normal">Packages maintain by {{ isActualUser ? 'you' : user.username }}</h3>
+            {% endblock %}
+        {% endembed %}
+    </section>
+</section>
+{% endblock %}

+ 18 - 15
src/Packagist/WebBundle/Resources/views/User/profile.html.twig

@@ -3,20 +3,23 @@
 {% import "PackagistWebBundle::macros.html.twig" as macros %}
 
 {% block content %}
-    <h2 class="title">
-        {{ user.username }}
-        <small>
-            member since: {{ user.createdAt|date('M d, Y') }}
-            {%- if is_granted('ROLE_ADMIN') %}
-                <strong>email: <a href="mailto:{{ user.email }}">{{ user.email }}</a></strong>
-            {%- endif %}
-        </small>
-    </h2>
+<h2 class="title">
+    {{ user.username }}
+    <small>
+        member since: {{ user.createdAt|date('M d, Y') }}
+        {%- if is_granted('ROLE_ADMIN') %}
+            <strong>email: <a href="mailto:{{ user.email }}">{{ user.email }}</a></strong>
+        {%- endif %}
+    </small>
+</h2>
 
-    <h3>Packages</h3>
-    {% if packages|length %}
-        {{ macros.listPackages(packages, true, false, meta) }}
-    {% else %}
-        <p>No packages found.</p>
-    {% endif %}
+<section class="row">
+    <section class="col-md-12">
+        {% embed "PackagistWebBundle:Web:list.html.twig" with {'noLayout': 'true'} %}
+            {% block content_title %}
+                <h3 class="font-normal">{{ user.username }}'s packages</h3>
+            {% endblock %}
+        {% endembed %}
+    </section>
+</section>
 {% endblock %}

+ 82 - 38
src/Packagist/WebBundle/Resources/views/Web/explore.html.twig

@@ -2,43 +2,87 @@
 
 {% import "PackagistWebBundle::macros.html.twig" as macros %}
 
+{% set showSearchDesc = 'none' %}
+
+{% macro list() %}
+<ul class="list-unstyled">
+    <li>
+        <a>vespakoen/menu <strong>2.0.4</strong></a>
+        Managing menu's the easy way.
+    </li>
+    <li>
+        <a>vnenkpet/nette-box <strong>1.2.2</strong></a>
+        This sandbox is a pre-packaged Nette Fra...
+    </li>
+    <li>
+        <a>pklink/hahns <strong>0.3.0</strong></a>
+        micro framework for PHP 5.4+
+    </li>
+    <li>
+        <a>vnenkpet/nette-box <strong>1.2.0</strong></a>
+        This sandbox is a pre-packaged Nette Fra...
+    </li>
+    <li>
+        <a>endroid/google-analytics-bundle <strong>1.0.2</strong></a>
+        Endroid Google Analytics for Symfony
+    </li>
+    <li>
+        <a>pklink/hahns <strong>0.2.3</strong></a>
+        micro framework for PHP 5.4+
+    </li>
+    <li>
+        <a>guimdev/bdd</a>
+        BdD is php class for easy use PDO with o...
+    </li>
+    <li>
+        <a>pklink/hahns</a>
+        micro framework for PHP 5.4+
+    </li>
+    <li>
+        <a>ginger/ginger <strong>v1.0.3</strong></a>
+    </li>
+    <li>
+        <a>asper/cakephp-enum-behavior <strong>1.0</strong></a>
+        Introduces an alternative way to support...
+    </li>
+</ul>
+{% endmacro %}
+
 {% block content %}
-    <div class="box clearfix">
-        {% block content_title %}<h1>Packages</h1>{% endblock %}
-        {% block lists %}
-            <div class="packages-short">
-                <h2>New Releases <a href="{{ url('feed_releases', {_format: 'rss'}) }}">RSS</a></h2>
-                <ul>
-                    {% for version in newlyReleased %}
-                        <li><a href="{{ path('view_package', {name: version.name}) }}">{{ version.name }} {{ version.version }}</a> {{ version.description|truncate(40) }}</li>
-                    {% endfor %}
-                </ul>
-            </div>
-            <div class="packages-short">
-                <h2>New Packages <a href="{{ url('feed_packages', {_format: 'rss'}) }}">RSS</a></h2>
-                <ul>
-                    {% for pkg in newlySubmitted %}
-                        <li><a href="{{ path('view_package', {name: pkg.name}) }}">{{ pkg.name }}</a> {{ pkg.description|truncate(40) }}</li>
-                    {% endfor %}
-                </ul>
-            </div>
-            <div class="packages-short">
-                <h2>Popular Packages</h2>
-                <ul>
-                    {% for pkg in popular %}
-                        <li><a href="{{ path('view_package', {name: pkg.name}) }}">{{ pkg.name }}</a> {{ pkg.description|truncate(40) }}</li>
-                    {% endfor %}
-                    <li><a href="{{ path('browse_popular') }}">See more...</a></li>
-                </ul>
-            </div>
-            <div class="packages-short">
-                <h2>Random Packages</h2>
-                <ul>
-                    {% for pkg in random %}
-                        <li><a href="{{ path('view_package', {name: pkg.name}) }}">{{ pkg.name }}</a> {{ pkg.description|truncate(40) }}</li>
-                    {% endfor %}
-                </ul>
-            </div>
-        {% endblock %}
-    </div>
+    {% block content_title %}<h2 class="title">Packages</h2>{% endblock %}
+    {% block lists %}
+    <section class="row">
+        <section class="packages-short col-lg-6">
+            <h3>New Releases <a href="{{ url('feed_releases', {_format: 'rss'}) }}">RSS</a></h3>
+
+            {{ _self.list() }}
+
+            {{ macros.listPackagesShort(newlyReleased, true) }}
+        </section>
+
+        <section class="packages-short col-lg-6">
+            <h3>New Packages <a href="{{ url('feed_packages', {_format: 'rss'}) }}">RSS</a></h3>
+
+            {{ _self.list() }}
+
+            {{ macros.listPackagesShort(newlySubmitted) }}
+        </section>
+
+        <section class="packages-short col-lg-6">
+            <h3>Popular Packages</h3>
+
+            {{ _self.list() }}
+
+            {{ macros.listPackagesShort(popular, false, true) }}
+        </section>
+
+        <section class="packages-short col-lg-6">
+            <h3>Random Packages</h3>
+
+            {{ _self.list() }}
+
+            {{ macros.listPackagesShort(random) }}
+        </section>
+    </section>
+    {% endblock %}
 {% endblock %}

+ 10 - 19
src/Packagist/WebBundle/Resources/views/Web/index.html.twig

@@ -2,57 +2,48 @@
 
 {% block content %}
 <section class="row">
-    <section class="span6 getting-started">
+    <section class="col-lg-6 getting-started">
         <h2 class="font-normal">Getting Started</h2>
         <div>
             <h3 class="font-normal">Define Your Dependencies</h3>
             <p>Put a file named <em>composer.json</em> at the root of your project, containing your project dependencies:</p>
-            <pre>
-{
+            <pre><code>{
     "require": {
         "vendor/package": "1.3.2",
         "vendor/package2": "1.*",
         "vendor/package3": ">=2.0.3"
     }
-}
-</pre>
+}</code></pre>
 
             <h3 class="font-normal">Install Composer In Your Project</h3>
             <p>Run this in your command line:</p>
-            <pre>
-curl -sS http://getcomposer.org/installer | php
-</pre>
+            <pre><code>curl -sS http://getcomposer.org/installer | php</code></pre>
             <p>Or <a href="http://getcomposer.org/composer.phar">download composer.phar</a> into your project root.</p>
 
             <h3 class="font-normal">Install Dependencies</h3>
             <p>Execute this in your project root.</p>
-            <pre>
-php composer.phar install
-</pre>
+            <pre><code>php composer.phar install</code></pre>
+
             <h3 class="font-normal">Autoload Dependencies</h3>
             <p>If your packages specify <a href="https://getcomposer.org/doc/01-basic-usage.md#autoloading">autoloading information</a>, you can autoload all the dependencies by adding this to your code:</p>
-            <pre>
-require 'vendor/autoload.php';
-</pre>
+            <pre><code>require 'vendor/autoload.php';</code></pre>
             <p><a href="{{ path('browse') }}">Browse</a> the packages we have to find more great libraries you can use in your project.</p>
         </div>
     </section>
 
-    <section class="span6 publishing-packages">
+    <section class="col-lg-6 publishing-packages">
         <h2 class="font-normal">Publishing Packages</h2>
         <div>
             <h3 class="font-normal">Define Your Package</h3>
             <p>Put a file named <em>composer.json</em> at the root of your package, containing this information:</p>
-            <pre>
-{
+            <pre><code>{
     "name": "your-vendor-name/package-name",
     "description": "A short description of what your package does",
     "require": {
         "php": ">=5.3.0",
         "another-vendor/package": "1.*"
     }
-}
-</pre>
+}</code></pre>
             <p>This is the strictly minimal information you have to give.</p>
             <p>For more details about package naming and the fields you can use to document your package better, see the <a href="{{ path('about') }}">about</a> page.</p>
 

+ 2 - 2
src/Packagist/WebBundle/Resources/views/Web/list.html.twig

@@ -2,10 +2,10 @@
 
 {% import "PackagistWebBundle::macros.html.twig" as macros %}
 
-{% set showSearchDesc = 'hide' %}
+{% set showSearchDesc = showSearchDesc is defined ? showSearchDesc : 'hide' %}
 
 {% block content %}
-    <h3 class="title">{% block content_title %}Packages{% endblock %}</h3>
+    {% block content_title %}<h3 class="title">Packages</h3>{% endblock %}
 
     {% block list %}
         {% if packages|length %}

+ 5 - 3
src/Packagist/WebBundle/Resources/views/Web/popular.html.twig

@@ -1,3 +1,5 @@
-{% extends "PackagistWebBundle:Web:list.html.twig" %}
-
-{% block content_title %}<h1>Popular Packages</h1>{% endblock %}
+{% embed "PackagistWebBundle:Web:list.html.twig" %}
+    {% block content_title %}
+        <h3 class="title">Popular Packages</h3>
+    {% endblock %}
+{% endembed %}

+ 10 - 8
src/Packagist/WebBundle/Resources/views/Web/search.html.twig

@@ -1,11 +1,13 @@
-{% extends "PackagistWebBundle:Web:list.html.twig" %}
+{% set showSearchDesc = packages is empty ? 'show' : 'hide' %}
 
-{% set showSearchDesc = 'hide' %}
+{% embed "PackagistWebBundle:Web:list.html.twig" %}
+    {% block content_title %}
+        <h3 class="title">Packages</h3>
+    {% endblock %}
 
-{% block content %}
-    <div class="search-list {% if packages is not defined %}hidden{% endif %}">
-        {% if packages is defined %}
+    {% block content %}
+        <div class="search-list">
             {{ block('list') }}
-        {% endif %}
-    </div>
-{% endblock %}
+        </div>
+    {% endblock %}
+{% endembed %}

+ 6 - 8
src/Packagist/WebBundle/Resources/views/Web/searchSection.html.twig

@@ -1,16 +1,14 @@
-<section class="wrapper wrapper-blue">
+<section class="wrapper wrapper-blue" style="padding-top: 59px">
     <div class="container">
-        <div class="row">
-            <div class="span12">
-                {% include "PackagistWebBundle:Web:searchForm.html.twig" %}
-            </div>
+        {% include "PackagistWebBundle:Web:searchForm.html.twig" %}
 
-            {%- if showSearchDesc == 'show' %}
-            <div class="span12">
+        {%- if showSearchDesc == 'show' %}
+        <div class="row">
+            <div class="col-lg-12 hidden-xs">
                 <p>Packagist is the main <a href="http://getcomposer.org/">Composer</a> repository. It aggregates all sorts of PHP packages that are installable with Composer.</p>
                 <p><a href="{{ path('browse') }}">Browse packages</a> or <a href="{{ path('submit') }}">submit your own</a>.</p>
             </div>
-            {%- endif %}
         </div>
+        {%- endif %}
     </div>
 </section>

+ 55 - 45
src/Packagist/WebBundle/Resources/views/Web/stats.html.twig

@@ -7,57 +7,67 @@
 
     <h2 class="title">Statistics</h2>
 
-    <section class="row-fluid">
-        <div class="span3 pull-right">
-            <h2 class="font-slim">Totals</h2>
-            <p>{{ packages|number_format(0, '.', " ") }} packages registered</p>
-            <p>{{ versions|number_format(0, '.', " ") }} versions available</p>
-            <p>{{ downloads == 'N/A' ? downloads : downloads|number_format(0, '.', " ") }} packages installed<br><small>(since {{ downloadsStartDate }})</small></p>
-        </div>
-
-        <h2 class="font-normal">Packages/versions over time</h2>
-
-        <p><img src="http://chart.apis.google.com/chart?chxr=0,0,{{ versions }}&amp;chxl=1:|{{ chart.months|join('|') }}&amp;chxt=y,x&amp;chs=850x250&amp;chds=0,{{ versions }},0,{{ versions }}&amp;cht=lc&amp;chco=0000FF,FF9900&amp;chd=t:{{ chart.versions|join(',') }}|{{ chart.packages|join(',') }}&amp;chdl=Versions|Packages&amp;chls=2|2" /></p>
-        <p>
-            <canvas width="900" height="250" data-labels="{{ chart.months|join(',') }}" data-values="{{ chart.versions|join(',') }}|{{ chart.packages|join(',') }}">
-                Sorry, the graph can't be displayed because your browser doesn't support &lt;canvas&gt; html element.
-            </canvas>
-        </p>
-        <p>The last data point is for the current month and shows partial data.</p>
-
-        {% if downloadsChart %}
-            <h2>Packages installed per day</h2>
-            <p><img src="http://chart.apis.google.com/chart?chxr=0,0,{{ maxDailyDownloads }}&amp;chxl=1:|{{ downloadsChart.labels|join('|') }}&amp;chxt=y,x&amp;chs=850x250&amp;chds=0,{{ maxDailyDownloads }},0,{{ maxDailyDownloads }}&amp;cht=lc&amp;chco=0000FF,FF9900&amp;chd=t:{{ downloadsChart.values|join(',') }}&amp;chdl=Installs&amp;chls=2|2" /></p>
-        {% endif %}
-        {% if downloadsChartMonthly %}
-            <h2>Packages installed per month</h2>
-            <p><img src="http://chart.apis.google.com/chart?chxr=0,0,{{ maxMonthlyDownloads }}&amp;chxl=1:|{{ downloadsChartMonthly.labels|join('|') }}&amp;chxt=y,x&amp;chs=850x250&amp;chds=0,{{ maxMonthlyDownloads }},0,{{ maxMonthlyDownloads }}&amp;cht=lc&amp;chco=0000FF,FF9900&amp;chd=t:{{ downloadsChartMonthly.values|join(',') }}&amp;chdl=Installs&amp;chls=2|2" /></p>
-            <p>The last data point is for the current month and shows partial data.</p>
-        {% endif %}
+    <section class="row">
+        <div class="col-lg-9">
+            <h2>Packages/versions over time</h2>
 
-        {% if downloadsChart %}
-            <h2>Packages installed in the last 30 days</h2>
-            <p>
-                <canvas width="900" height="250" data-labels="{{ downloadsChart.labels|join(',') }}" data-values="{{ downloadsChart.values|join(',') }}">
-                    Sorry, the graph can't be displayed because your browser doesn't support &lt;canvas&gt; html element.
-                </canvas>
-            </p>
-            <ul class="legend">
-                <li class="legend-first"><span>&#9632;</span> Installs</li>
-            </ul>
-        {% endif %}
-        {% if downloadsChartMonthly %}
-            <h2>Packages installed per month</h2>
+            <p><img src="http://chart.apis.google.com/chart?chxr=0,0,{{ versions }}&amp;chxl=1:|{{ chart.months|join('|') }}&amp;chxt=y,x&amp;chs=850x250&amp;chds=0,{{ versions }},0,{{ versions }}&amp;cht=lc&amp;chco=0000FF,FF9900&amp;chd=t:{{ chart.versions|join(',') }}|{{ chart.packages|join(',') }}&amp;chdl=Versions|Packages&amp;chls=2|2" /></p>
             <p>
-                <canvas width="900" height="250" data-labels="{{ downloadsChartMonthly.labels|join(',') }}" data-values="{{ downloadsChartMonthly.values|join(',') }}">
+                <canvas width="900" height="250" data-labels="{{ chart.months|join(',') }}" data-values="{{ chart.versions|join(',') }}|{{ chart.packages|join(',') }}">
                     Sorry, the graph can't be displayed because your browser doesn't support &lt;canvas&gt; html element.
                 </canvas>
             </p>
-            <ul class="legend">
-                <li class="legend-first"><span>&#9632;</span> Installs</li>
-            </ul>
             <p>The last data point is for the current month and shows partial data.</p>
-        {% endif %}
+
+            {% if downloadsChart %}
+                <h2>Packages installed per day</h2>
+                <p><img src="http://chart.apis.google.com/chart?chxr=0,0,{{ maxDailyDownloads }}&amp;chxl=1:|{{ downloadsChart.labels|join('|') }}&amp;chxt=y,x&amp;chs=850x250&amp;chds=0,{{ maxDailyDownloads }},0,{{ maxDailyDownloads }}&amp;cht=lc&amp;chco=0000FF,FF9900&amp;chd=t:{{ downloadsChart.values|join(',') }}&amp;chdl=Installs&amp;chls=2|2" /></p>
+            {% endif %}
+            {% if downloadsChartMonthly %}
+                <h2>Packages installed per month</h2>
+                <p><img src="http://chart.apis.google.com/chart?chxr=0,0,{{ maxMonthlyDownloads }}&amp;chxl=1:|{{ downloadsChartMonthly.labels|join('|') }}&amp;chxt=y,x&amp;chs=850x250&amp;chds=0,{{ maxMonthlyDownloads }},0,{{ maxMonthlyDownloads }}&amp;cht=lc&amp;chco=0000FF,FF9900&amp;chd=t:{{ downloadsChartMonthly.values|join(',') }}&amp;chdl=Installs&amp;chls=2|2" /></p>
+                <p>The last data point is for the current month and shows partial data.</p>
+            {% endif %}
+
+            {% if downloadsChart %}
+                <h2>Packages installed in the last 30 days</h2>
+                <p>
+                    <canvas width="900" height="250" data-labels="{{ downloadsChart.labels|join(',') }}" data-values="{{ downloadsChart.values|join(',') }}">
+                        Sorry, the graph can't be displayed because your browser doesn't support &lt;canvas&gt; html element.
+                    </canvas>
+                </p>
+                <ul class="legend">
+                    <li class="legend-first"><span>&#9632;</span> Installs</li>
+                </ul>
+            {% endif %}
+            {% if downloadsChartMonthly %}
+                <h2>Packages installed per month</h2>
+                <p>
+                    <canvas width="900" height="250" data-labels="{{ downloadsChartMonthly.labels|join(',') }}" data-values="{{ downloadsChartMonthly.values|join(',') }}">
+                        Sorry, the graph can't be displayed because your browser doesn't support &lt;canvas&gt; html element.
+                    </canvas>
+                </p>
+                <ul class="legend">
+                    <li class="legend-first"><span>&#9632;</span> Installs</li>
+                </ul>
+                <p>The last data point is for the current month and shows partial data.</p>
+            {% endif %}
+
+            <div class="col-lg-3">
+                <h2>Totals</h2>
+
+                <dl class="dl-horizontal">
+                    <dt class="font-bold">Packages registered</dt>
+                    <dd class="font-slim">{{ packages|number_format(0, '.', " ") }}</dd>
+
+                    <dt class="font-bold">Versions available</dt>
+                    <dd class="font-slim">{{ versions|number_format(0, '.', " ") }}</dd>
+
+                    <dt class="font-bold">Packages installed<br><small>(since {{ downloadsStartDate }})</small></dt>
+                    <dd class="font-slim">{{ downloads == 'N/A' ? downloads : downloads|number_format(0, '.', " ") }}</dd>
+                </dl>
+            </div>
+        </div>
     </section>
 {% endblock %}
 

+ 21 - 23
src/Packagist/WebBundle/Resources/views/Web/submitPackage.html.twig

@@ -7,27 +7,25 @@
 {% set showSearchDesc = 'hide' %}
 
 {% block content %}
-    <h2 class="title">Submit package</h2>
-
-    <section class="row-fluid">
-        <div class="span7 pull-right">
-            <p>Please make sure you have read the package <a href="{{ path('about') }}">naming conventions</a> before submitting your package. The authoritative name of your package will be taken from the composer.json file inside the master branch or trunk of your repository, and it can not be changed after that.</p>
-            <p><strong>Do not submit forks of existing packages.</strong> If you need to test changes to a package that you forked to patch, use <a href="http://getcomposer.org/doc/05-repositories.md#vcs">VCS Repositories</a> instead. If however it is a real long-term fork you intend on maintaining feel free to submit it.</p>
-            <p>If you need help or if you have any questions please get in touch with the Composer <a href="http://getcomposer.org/doc/06-community.md">community</a>.</p>
-        </div>
-
-        <form class="span5" id="submit-package-form" action="{{ path('submit') }}" data-check-url="{{ path('submit.fetch_info') }}" method="POST" {{ form_enctype(form) }}>
-            <fieldset class="row">
-                <div class="span11">
-                    {{ form_label(form.repository) }}
-                    {{ form_errors(form.repository) }}
-                    {{ form_widget(form.repository, {'attr': {'class': 'span12'}}) }}
-                </div>
-
-                {{ form_rest(form) }}
-
-                <input class="btn btn-success span11" id="submit" type="submit" value="Submit" />
-            </fieldset>
-        </form>
-    </section>
+<h2 class="title">Submit package</h2>
+
+<section class="row">
+    <form class="col-md-6" id="submit-package-form" action="{{ path('submit') }}" data-check-url="{{ path('submit.fetch_info') }}" method="POST" {{ form_enctype(form) }}>
+        {{ form_label(form.repository) }}
+        {{ form_errors(form.repository) }}
+        {{ form_widget(form.repository) }}
+
+        {{ form_rest(form) }}
+
+        <hr>
+
+        <input class="btn btn-block btn-success btn-lg" id="submit" type="submit" value="Submit" />
+    </form>
+
+    <div class="col-md-6">
+        <p>Please make sure you have read the package <a href="{{ path('about') }}#naming-your-package">naming conventions</a> before submitting your package. The authoritative name of your package will be taken from the composer.json file inside the master branch or trunk of your repository, and it can not be changed after that.</p>
+        <p><strong>Do not submit forks of existing packages.</strong> If you need to test changes to a package that you forked to patch, use <a href="http://getcomposer.org/doc/05-repositories.md#vcs">VCS Repositories</a> instead. If however it is a real long-term fork you intend on maintaining feel free to submit it.</p>
+        <p>If you need help or if you have any questions please get in touch with the Composer <a href="http://getcomposer.org/doc/06-community.md">community</a>.</p>
+    </div>
+</section>
 {% endblock %}

+ 45 - 0
src/Packagist/WebBundle/Resources/views/forms.html.twig

@@ -0,0 +1,45 @@
+{% extends 'form_div_layout.html.twig' %}
+
+{% block textarea_widget %}
+{% set attr = attr|merge({'class': attr.class|default('') ~ ' form-control'}) %}
+{{ parent() }}
+{% endblock textarea_widget %}
+
+{% block form_widget_simple %}
+{% spaceless %}
+    {% set attr = attr|merge({'class': attr.class|default('') ~ ' form-control'}) %}
+    {% set type = type|default('text') %}
+
+    {{ parent() }}
+{% endspaceless %}
+{% endblock form_widget_simple %}
+
+{% block form_row %}
+{% spaceless %}
+    <div class="form-group{% if errors|length > 0 %} has-error{% endif %}">
+        {{ form_label(form) }}
+
+        {{ form_widget(form) }}
+
+        {% for error in errors %}
+            <span class="help-block form-error">
+                {{
+                    error.messagePluralization is null
+                    ? error.messageTemplate|trans(error.messageParameters, 'validators')
+                    : error.messageTemplate|transchoice(error.messagePluralization, error.messageParameters, 'validators')
+                }}
+            </span>
+        {% endfor %}
+    </div>
+{% endspaceless %}
+{% endblock form_row %}
+
+{% block form_errors %}
+{% spaceless %}
+    {% if errors|length > 0 %}
+        {% for error in errors %}
+            <div class="alert alert-danger">{{ error.message }}</div>
+        {% endfor %}
+    {% endif %}
+{% endspaceless %}
+{% endblock form_errors %}

+ 62 - 47
src/Packagist/WebBundle/Resources/views/layout.html.twig

@@ -12,7 +12,7 @@
         <link rel="shortcut icon" href="{{ asset('favicon.ico') }}" />
         <link rel="apple-touch-icon" href="{{ asset('apple-touch-icon.png') }}" />
 
-        <link rel="stylesheet" href="{{ asset('bundles/packagistweb/css/bootstrap.min.css') }}" />
+        <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css" type="text/css" />
         <link rel="stylesheet" href="{{ asset('bundles/packagistweb/css/main.css?v=15') }}" />
         <link rel="stylesheet" href="{{ asset('css/humane/jackedup.css?v=3') }}" />
         <link rel="stylesheet" href="{{ asset('css/fontawesome/font-awesome.css') }}" />
@@ -27,23 +27,35 @@
 
         <link rel="search" type="application/opensearchdescription+xml" href="{{ asset('search.osd') }}" title="Packagist" />
 
-
         {% block head_additions %}{% endblock %}
     </head>
 
     <body>
-        <header class="navbar-wrapper">
+    <section class="wrap">
+        <header class="navbar-wrapper navbar-fixed-top">
             <nav class="container">
-                <div class="navbar navbar-inner">
-                    <div class="pull-right">
-                        <ul class="nav">
+                <div class="navbar" role="navigation">
+                    <div class="navbar-header">
+                        <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
+                            <span class="sr-only">Toggle navigation</span>
+                            <span class="icon-bar"></span>
+                            <span class="icon-bar"></span>
+                            <span class="icon-bar"></span>
+                        </button>
+                        <h1 class="navbar-brand"><a href="{{ path('home') }}">Packagist</a> <em class="hidden-sm">The PHP package archivist.</em></h1>
+                    </div>
+
+                    <div class="collapse navbar-collapse">
+                        <ul class="nav navbar-nav">
                         {%- if app.user %}
                             <li class="nav-user">
                                 <section>
                                     <img width="57" height="57" alt="" src="https://secure.gravatar.com/avatar/b494363ed38b483b240180920c0d38c2?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-user-420.png">
-                                    <a href="{{ path('fos_user_profile_show') }}"><span>{{ app.user.username }}</span></a>
+                                    <a href="{{ path('fos_user_profile_show') }}">{{ app.user.username }}</a>
 
-                                    {{ knp_menu_render('user_menu', {'allow_safe_labels': true}) }}
+                                    <section class="nav-user-menu">
+                                        {{ knp_menu_render('user_menu', {'allow_safe_labels': true}) }}
+                                    </section>
                                 </section>
                             </li>
                         {%- else %}
@@ -54,49 +66,49 @@
                                 <section class="nav-user-signin">
                                     <a href="{{ path('hwi_oauth_connect') }}">Sign in</a>
 
-                                    <form action="" method="POST" class="nav-user-signin-menu">
-                                        <div class="input-prepend">
-                                            <input type="text" id="username" name="_username" placeholder="{{ 'security.login.username'|trans({}, 'FOSUserBundle') }}">
-                                            <span class="add-on"><span class="icon-user"></span></span>
-                                        </div>
-                                        <div class="input-prepend">
-                                            <input type="password" id="password" name="_password" placeholder="{{ 'security.login.password'|trans({}, 'FOSUserBundle') }}">
-                                            <span class="add-on"><span class="icon-lock"></span></span>
-                                        </div>
-
-                                        <label for="remember_me" class="checkbox pull-left">
-                                            <input type="checkbox" id="remember_me" name="_remember_me" value="on" checked="checked" />
-                                            {{- 'security.login.remember_me'|trans({}, 'FOSUserBundle') }}
-                                        </label>
-
-                                        {%- if packagist_host and packagist_host in app.request.headers.get('Referer') %}
-                                            <input type="hidden" name="_target_path" value="{{ app.request.headers.get('Referer') }}" />
-                                        {%- endif %}
-
-                                        <div style="width: 95%;float: left">
-                                            <a href="{{ hwi_oauth_login_url('github') }}" class="pull-right btn btn-github"><span class="icon-github"></span>Use Github</a>
-                                            <button type="submit" class="btn btn-success" id="_submit" name="_submit">{{ 'security.login.submit'|trans({}, 'FOSUserBundle') }}</button>
-                                        </div>
-
-                                        <div style="background: #ddd; float: left; font-size: 14px; margin: 11px -20px -12px; padding: 7px 21px; width: 100%; text-align: center;">
+                                    <section class="signin-box">
+                                        <form action="{{ path('login_check') }}" method="POST">
+                                            <div class="input-group">
+                                                <input class="form-control" type="text" id="_username" name="_username" placeholder="{{ 'security.login.username'|trans({}, 'FOSUserBundle') }}">
+                                                <span class="input-group-addon"><span class="icon-user"></span></span>
+                                            </div>
+                                            <div class="input-group">
+                                                <input class="form-control" type="password" id="_password" name="_password" placeholder="{{ 'security.login.password'|trans({}, 'FOSUserBundle') }}">
+                                                <span class="input-group-addon"><span class="icon-lock"></span></span>
+                                            </div>
+
+                                            <div class="checkbox">
+                                                <label for="_remember_me">
+                                                    <input type="checkbox" id="_remember_me" name="_remember_me" value="on" checked="checked" />
+                                                    {{- 'security.login.remember_me'|trans({}, 'FOSUserBundle') }}
+                                                </label>
+                                            </div>
+
+                                            {%- if packagist_host and packagist_host in app.request.headers.get('Referer') %}
+                                                <input type="hidden" name="_target_path" value="{{ app.request.headers.get('Referer') }}" />
+                                            {%- endif %}
+
+                                            <div class="signin-box-buttons">
+                                                <a href="{{ hwi_oauth_login_url('github') }}" class="pull-right btn btn-github"><span class="icon-github"></span>Use Github</a>
+                                                <button type="submit" class="btn btn-success" id="_submit" name="_submit">{{ 'security.login.submit'|trans({}, 'FOSUserBundle') }}</button>
+                                            </div>
+                                        </form>
+
+                                        <div class="signin-box-register">
                                             <a href="{{ path('fos_user_registration_register') }}">No account yet? Create one now!</a>
                                         </div>
-                                    </form>
+                                    </section>
                                 </section>
                             </li>
                         {%- endif %}
-                        {%- if page is not defined or page != 'submit' %}
                             <li>
                                 <a style="background: #83c129;" href="{{ path('submit') }}">Submit Package</a>
                             </li>
-                        {%- endif %}
                         </ul>
                     </div>
-
-                    <h1><a class="brand" href="{{ path('home') }}">Packagist</a> <em>The PHP package archivist.</em></h1>
                 </div>
             </nav>
-
+{#
             {%- if app.session.flashbag.all()|length > 0 %}
             <section class="wrapper wrapper-white">
                 <div class="container">
@@ -116,6 +128,7 @@
                 </div>
             </section>
             {%- endif %}
+#}
         </header>
 
         {%- if searchForm is defined %}
@@ -124,31 +137,32 @@
 
         {% block content_header %}{% endblock %}
 
-        <section class="wrapper">
+        <section class="wrapper"{% if searchForm is not defined %} style="padding-top: 59px"{% endif %}>
             <section class="container content" role="main">
                 {% block content %}{% endblock %}
 
                 <div class="row hidden">
-                    <div class="search-list span12"></div>
+                    <div class="search-list col-md-12"></div>
                 </div>
             </section>
         </section>
+    </section>
 
         <footer class="wrapper-footer">
             <nav class="container">
-                <ul class="social span2 pull-right">
+                <ul class="social col-xs-3 col-md-2 pull-right">
                     <li><a href="http://github.com/composer/packagist" title="{{ 'menu.github'|trans }}"><span class="icon-github"></span></a></li>
                     <li><a href="https://twitter.com/packagist" title="{{ 'menu.twitter'|trans }}"><span class="icon-twitter"></span></a></li>
                     <li><a href="mailto:contact@packagist.org" title="{{ 'menu.contact'|trans }}"><span class="icon-mail"></span></a></li>
                 </ul>
 
-                <ul class="span2">
+                <ul class="col-xs-3 col-md-2">
                     <li><a href="{{ path('about') }}">{{ 'menu.about_packagist'|trans }}</a></li>
                     <li><a href="{{ path('feeds') }}">{{ 'menu.rss_feeds'|trans }}</a></li>
                     <li><a href="{{ path('home') }}">{{ 'menu.home'|trans }}</a></li>
                 </ul>
 
-                <ul class="span2">
+                <ul class="col-xs-3 col-md-2">
                 {%- if app.user %}
                     <li><a href="{{ path('fos_user_profile_show') }}">{{ 'menu.profile'|trans }}</a></li>
                     <li><a href="{{ path('logout') }}">{{ 'menu.logout'|trans }}</a></li>
@@ -158,7 +172,7 @@
                 {%- endif %}
                 </ul>
 
-                <ul class="span2">
+                <ul class="col-xs-3 col-md-2">
                     <li><a href="{{ path('browse') }}">{{ 'menu.browse_packages'|trans }}</a></li>
                     <li><a href="{{ path('stats') }}">{{ 'menu.stats'|trans }}</a></li>
                 </ul>
@@ -176,7 +190,8 @@
         <script src="{{ asset('js/libs/ZeroClipboard.min.js') }}"></script>
         <script src="{{ asset('bundles/packagistweb/js/layout.js?v=2') }}"></script>
         <script src="{{ asset('bundles/packagistweb/js/search.js?v=6')}}"></script>
-
+        <script src="//getbootstrap.com/dist/js/bootstrap.js"></script>
+{#
         {%- if not app.debug and google_analytics.ga_key %}
         <script>
             var _gaq=[['_setAccount','{{ google_analytics.ga_key }}'],['_trackPageview']];
@@ -185,7 +200,7 @@
             s.parentNode.insertBefore(g,s)}(document,'script'));
         </script>
         {%- endif %}
-
+#}
         {% block scripts %}{% endblock %}
     </body>
 </html>

+ 29 - 16
src/Packagist/WebBundle/Resources/views/macros.html.twig

@@ -1,5 +1,5 @@
 {% macro listPackages(packages, paginate, showAutoUpdateWarning, meta) %}
-    <ul class="packages">
+    <ul class="packages list-unstyled">
         {% for package in packages %}
             {% if package.id is numeric %}
                 {% set packageUrl = path('view_package', { 'name' : package.name }) %}
@@ -7,30 +7,29 @@
                 {% set packageUrl = path('view_providers', { 'name' : package.name }) %}
             {% endif %}
             <li data-url="{{ packageUrl }}">
-                {% if meta and package.id is numeric %}
-                    <span class="metadata">
-                        <i class="icon-download"></i> {{ meta.downloads[package.id]|default(0)|number_format(0, '.', ' ') }}
-                        <i class="icon-star"></i> {{ meta.favers[package.id]|number_format(0, '.', ' ') }}
-                    </span>
-                {% endif %}
-                <h1>
+                <h4 class="font-bold">
+                    {% if meta %}
+                        <span class="metadata pull-right">
+                            <span class="icon-box"></span> {{ meta.downloads[package.id]|default(0)|number_format(0, '.', ' ') }}
+                            <span class="glyphicon glyphicon-star"></span> {{ meta.favers[package.id]|number_format(0, '.', ' ') }}
+                        </span>
+                    {% endif %}
                     <a href="{{ packageUrl }}">{{ package.name }}</a>
                     {% if package.id is not numeric %}
-                        (virtual package)
+                        <small>(virtual package)</small>
                     {% endif %}
                     {% if showAutoUpdateWarning and not package.autoUpdated %}
-                        [Not auto-updated]
+                        <small>[Not auto-updated]</small>
                     {% endif %}
-                </h1>
+                </h4>
                 {% if package.abandoned is defined and package.abandoned %}
-                <span class="abandoned">
-                    <i class="icon-warning-sign"></i> Abandoned! {% if package.replacementPackage %}Use: <a href="{{ path('view_package', {name: package.replacementPackage}) }}">{{ package.replacementPackage }}</a>{% endif %}
-                </span>
+                    <span class="abandoned">
+                        <i class="icon-warning-sign"></i> Abandoned! {% if package.replacementPackage %}Use: <a href="{{ path('view_package', {name: package.replacementPackage}) }}">{{ package.replacementPackage }}</a>{% endif %}
+                    </span>
                 {% endif %}
                 {% if package.description is defined and package.description %}
-                    <p class="package-description">{{ package.description }}</p>
+                    <p>{{ package.description }}</p>
                 {% endif %}
-
             </li>
         {% endfor %}
 
@@ -40,6 +39,20 @@
     </ul>
 {% endmacro %}
 
+{% macro listPackagesShort(packages, showVersion, showMoreUrl) %}
+    <ul class="list-unstyled">
+        {% for package in packages %}
+            <li>
+                <a href="{{ path('view_package', {'name' : package.name }) }}">{{ package.name }}{% if showVersion is defined %} <strong>{{ package.version }}</strong>{% endif %}</a>
+                {{ package.description|truncate(60) }}
+            </li>
+        {% endfor %}
+        {% if showMoreUrl is defined %}
+            <li><a href="{{ path('browse_popular') }}"><strong>See more...</strong></a></li>
+        {% endif %}
+    </ul>
+{% endmacro %}
+
 {% macro packageLink(packageName, type) %}
     {%- if type == 'provide' and (packageName is existing_provider or packageName is existing_package) -%}
         <a href="{{ path('view_providers', { 'name': packageName }) }}">{{ packageName }}</a>