Browse Source

Finalized package details page and a crapton of other consistency tweaks across all pages

Jordi Boggiano 9 years ago
parent
commit
d7cb1de53e

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

@@ -7,7 +7,7 @@
     {%- if app.user.apiToken %}
         <h3 class="font-normal profile-title">Your API Token</h3>
 
-        <div class="input-group clearfix">
+        <div class="input-group api-token-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>

+ 273 - 100
src/Packagist/WebBundle/Resources/public/css/main.css

@@ -1,4 +1,4 @@
-@import url(http://fonts.googleapis.com/css?family=Open+Sans:400,300,600,700);
+@import url(http://fonts.googleapis.com/css?family=Open+Sans:400,300,600,300italic);
 
 @font-face {
     font-family: 'fontello';
@@ -15,13 +15,29 @@ html, body {
     height: 100%;
 }
 
+pre, code {
+  font-family: Consolas, "Courier New", Courier, monospace;
+}
+
 body {
     background: #fff;
     font-family: 'Open Sans', sans-serif;
 }
 
 h1, h2, h3, h4, h5, h6 {
-    font-family: 'Open Sans', sans-serif;
+  font-family: 'Open Sans', sans-serif;
+  font-weight: 300;
+}
+i, cite, em, var, address, dfn {
+  font-style: italic;
+  font-weight: 300;
+}
+strong {
+  font-weight: 600;
+}
+.h3, h3 {
+    font-size: 16px;
+    font-weight: 600;
 }
 
 .navbar-wrapper {
@@ -30,6 +46,8 @@ h1, h2, h3, h4, h5, h6 {
     top: 0;
     z-index: 10;
     background: #2c3e50;
+    min-height: 59px;
+    border-bottom: 5px solid #068FD5;
 }
 
 .navbar {
@@ -37,7 +55,7 @@ h1, h2, h3, h4, h5, h6 {
 }
 
 .navbar .navbar-brand {
-    padding: 12px 15px;
+    padding: 12px 4px;
     line-height: 32px;
     margin: 0;
 }
@@ -51,7 +69,6 @@ h1, h2, h3, h4, h5, h6 {
 }
 
 .navbar .navbar-brand em {
-    float: left;
     padding-left: 15px;
     padding-top: 8px;
     font: italic 14px 'Times New Roman';
@@ -78,6 +95,9 @@ h1, h2, h3, h4, h5, h6 {
     text-shadow: none;
     color: #fff;
 }
+.nav .nav-user-signin {
+    padding: 18px 25px 17px !important;
+}
 
 .navbar .nav > .active > a, .navbar .nav > .active > a:hover, .navbar .nav > .active > a:focus {
     background: #068fd5;
@@ -85,6 +105,8 @@ h1, h2, h3, h4, h5, h6 {
     color: #fff;
 }
 
+
+
 .navbar .nav > li:last-child > a {
     border-right: 1px solid rgba(69, 78, 92, 0.5);
 }
@@ -117,7 +139,6 @@ h1, h2, h3, h4, h5, h6 {
 }
 
 .navbar .nav > .nav-user > section {
-    float: left;
     padding: 5px 20px 0 0;
     cursor: default;
 }
@@ -193,7 +214,7 @@ h1, h2, h3, h4, h5, h6 {
 
 .nav-user .signin-box {
     top: 57px;
-    left: -209px;
+    right: 0;
     width: 300px;
     position: absolute;
     z-index: 110;
@@ -264,6 +285,28 @@ h1, h2, h3, h4, h5, h6 {
 }
 
 
+/* overrides for dropdown menu on mobile */
+@media (max-width: 767px) {
+    .navbar .nav > li {
+        height: 57px;
+    }
+    .navbar-collapse.in {
+        overflow-y: visible;
+    }
+    .nav-user .nav-user-menu {
+        right: 0;
+    }
+    .nav-user-signin {
+        width: 100%;
+    }
+    .nav-user .signin-box {
+        left: auto;
+        right: 0;
+    }
+}
+
+
+
 .wrap {
     min-height: 100%;
     height: auto;
@@ -277,11 +320,11 @@ body > section:first-child {
 
 .wrapper .container {
     padding: 26px 20px;
-    color: #1a1d24;
+    color: #2c3e50;
 }
 
-.wrapper .container p {
-    font-size: 16px;
+.content p {
+    font-size: 14px;
     margin-top: 10px;
     line-height: 22px;
 }
@@ -324,7 +367,7 @@ body > section:first-child {
 footer p {
   float: left;
   margin-left: 90px;
-  color: #ccc;
+  color: #dddfe1;
 }
 
 .wrapper-footer ul {
@@ -429,16 +472,6 @@ input:focus:invalid:focus, textarea:focus:invalid:focus, select:focus:invalid:fo
     display: inline;
 }
 
-.input-group .form-control {
-    width: 90%;
-    float: right;
-    margin: 0 0 6px;
-}
-
-.input-group #api-token {
-    width: 81%;
-}
-
 .input-group .input-group-addon {
     float: left;
     min-height: 22px;
@@ -452,6 +485,28 @@ input:focus:invalid:focus, textarea:focus:invalid:focus, select:focus:invalid:fo
     font-size: 16px;
 }
 
+.input-group .form-control {
+    width: 90%;
+    float: right;
+    margin: 0 0 6px;
+}
+
+.api-token-group {
+    display: block;
+    min-height: 45px;
+}
+.api-token-group #api-token {
+  width: 85%;
+  position: absolute;
+  right: 0;
+  text-align: center;
+}
+.api-token-group .btn-show-api-token {
+  width: 145px;
+  position: absolute;
+  z-index: 10;
+}
+
 .input-group input.input-lg ~ .input-group-addon {
     line-height: 32px;
     font-size: 20px;
@@ -594,6 +649,10 @@ ul.packages .abandoned {
     text-shadow: none;
     font-weight: 600;
 }
+.btn-group-xs .btn {
+    padding: 5px 10px;
+    font-size: 12px;
+}
 
 .btn.loading {
     background-image: url("../img/loader.gif");
@@ -626,7 +685,6 @@ ul.packages .abandoned {
     background: #ff4533;
     border-bottom-color: #cd3729;
 }
-
 .btn-danger:focus, .btn-danger:active {
     border-bottom-color: #cd3729;
 }
@@ -636,7 +694,6 @@ ul.packages .abandoned {
     background: #454e5c;
     border-bottom-color: #303339;
 }
-
 .btn-inverse:focus, .btn-inverse:active {
     border-bottom-color: #303339;
 }
@@ -645,12 +702,10 @@ ul.packages .abandoned {
     background: #36b7e6;
     border-bottom-color: #2fa1ca;
 }
-
 .btn-primary:hover, .btn-github:hover {
-    background: #2fa1ca;
-    border-bottom-color: #2980b9;
+    background: #36b7e6;
+    border-bottom-color: #2fa1ca;
 }
-
 .btn-primary:focus, .btn-primary:active {
     border-bottom-color: #2fa1ca;
 }
@@ -659,11 +714,18 @@ ul.packages .abandoned {
     background: #83c129;
     border-bottom-color: #69ad21;
 }
-
 .btn-success:focus, .btn-success:active {
     border-bottom-color: #69ad21;
 }
 
+.btn-warning, .btn-warning:hover {
+    background: #ed9e2e;
+    border-bottom-color: #d58512;
+}
+.btn-warning:focus, .btn-warning:active {
+    border-bottom-color: #d58512;
+}
+
 .btn-github {
     line-height: 26px;
     font-weight: 600;
@@ -739,11 +801,25 @@ ul.packages .abandoned {
 }
 
 
+.package h2.title {
+    margin-bottom: -1px !important;
+    position: relative;
+}
+.package .versions .version {
+  position: relative;
+  display: block;
+  height: 26px;
+}
 .package .delete-version {
   display: inline-block;
 }
 .package .delete-version .submit {
-  cursor: pointer;
+  position: absolute;
+  right: 5px;
+  top: 7px;
+}
+.package .delete-version .submit:hover {
+  color: #CD3729;
 }
 .package .action input.loading {
   background-position: 10px center;
@@ -752,13 +828,51 @@ ul.packages .abandoned {
   padding-left: 35px;
 }
 .package .action {
-  margin-left: 5px;
-  margin-bottom: 5px;
+  margin-right: 5px;
+  margin-bottom: 15px;
+  display: inline-block;
+}
+.package, .package p {
+    font-size: 14px;
 }
 
+.package p.description {
+    margin-top: 0;
+    font-size: 28px;
+    font-style: italic;
+    font-weight: 300;
+    margin-bottom: 15px;
+}
+@media (max-width: 767px) {
+    .package p.description {
+        font-size: 20px;
+    }
+}
 .package .details {
     line-height: 200%;
 }
+.package .details p > i:first-child {
+    margin-right: 5px;
+}
+.package .details p {
+    margin: 0;
+}
+.package .details .canonical {
+  text-overflow: ellipsis;
+  overflow: hidden;
+  width: 100%;
+  white-space: nowrap;
+}
+
+@media (max-width: 991px) and (min-width: 768px) {
+    .package .downloads {
+        margin-top: 0;
+    }
+}
+.package .downloads span {
+    display: inline-block;
+    width: 60px;
+}
 
 .package .tags a:before {
   content: "#";
@@ -773,125 +887,161 @@ ul.packages .abandoned {
     color: #bdc3c7;
 }
 
-.btn-show-api-token {
-    width: 19%;
-}
-
-
-
 
 
 
 
+@media (min-width: 992px) {
+    .package .package-aside {
+      background: #2C3E50;
+      padding-top: 15px;
+      padding-bottom: 15px;
+      border-radius: 2px;
+    }
+    .package .package-aside {
+      color: #BDC3C7;
+    }
+    .package .package-aside a, .package .package-aside i {
+      color: #fff;
+    }
+    .package .details-toggler:hover a {
+      color: #068FD5;
+    }
+}
+.package .details-toggler.open, .package .details-toggler.open a, .package .details-toggler.open i {
+  background: #068FD5;
+  color: #fff;
+}
 
-.package .versions, .package .version-details{
+.package .versions-wrapper, .package .version-details {
   margin-top: 20px;
 }
+.package .version-details .title {
+  border-top: 1px solid #dddfe1;
+  padding-top: 10px;
+}
 .package .versions {
   list-style: none;
   line-height: 170%;
+  overflow-y: hidden;
+  max-height: 300px;
+  padding: 0;
+  margin: 0;
+}
+.package .versions-expander {
+  text-align: center;
+  cursor: pointer;
+  padding: 10px 0 0;
 }
 .package .versions .version {
   padding: 2px 0 2px 5px;
 }
 .package .versions .version-number {
-  display: inline;
-  font-size: 12px;
+  display: block;
+  float: left;
+  width: 91%;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  overflow: hidden;
 }
-.package .versions .open {
-  background: #F3F4F5;
+.package .details-toggler {
+  cursor: pointer;
 }
 
+
 .package .version-details .version-number {
-  font-size: 20px;
-  font-weight: bold;
+  font-size: 16px;
+  font-weight: 600;
   color: #2C3E50;
 }
 .package .version-details .release-date {
-    float: right;
+  float: right;
+  position: relative;
+  top: 3px;
+  font-size: 12px;
+}
+.package .version-details .link-type {
+    font-weight: 600;
+    text-transform: lowercase;
 }
 .package i {
     color: #2C3E50;
 }
-.package .version {
-}
-.package .version.last {
-}
-.package .version h1 {
-}
-.package .version .source-reference {
+.package .version-details .source-reference {
+    margin-left: 10px;
+    display: inline-block;
 }
 .package .version .license .unknown {
   color: #c00;
 }
-.package .version .details {
-  display: none;
-}
-.package .version .details.open {
-  display: block;
-}
-.package .package-links {
-  border-top: 1px solid #ccc;
-  border-bottom: 1px solid #ccc;
-  margin-top: 10px;
-  padding-top: 10px;
-}
-.package .authors {
+.package .version-details .authors {
   list-style: none;
   display: inline;
   margin: 0;
   padding: 0;
 }
-.package .authors li {
+.package .version-details .authors li {
   display: inline;
   margin-right: 5px;
 }
-.package .authors li:after {
+.package .version-details .authors li:after {
   content: ", ";
 }
-.package .authors li:last-child:after {
+.package .version-details .authors li:last-child:after {
   content: "";
 }
-.package .package-links ul {
+.package .version-details .package-links {
+  border-top: 1px solid #dddfe1;
+  margin-top: 10px;
+  padding-top: 10px;
+  padding-bottom: 10px;
+}
+.package .version-details .package-links ul  {
     padding-left: 18px;
 }
-.package .package-links .no-links {
+.package .version-details .package-links .no-links {
     color: #999;
 }
-.package .package-links li,
-.package .package-links .no-links,
-.package .version-details .license,
-.package .version-details .source-reference,
-.package .version-details .authors,
-.package .version-details .tags {
+.package .version-details .metadata,
+.package .version-details .metadata p,
+.package .version-details .package-links ul,
+.package .version-details .package-links .no-links {
     font-size: 12px;
 }
+
 .package .requireme {
-  padding: 3px 0 3px 0;
+    margin-top: 15px;
 }
 .package .requireme input {
+  cursor: text;
   border: 0 !important;
   border-radius: 0;
   background-color: transparent;
-  font-family: Courier;
-  min-width: 500px;
-  width: auto;
-}
-.package .details-toggler {
-}
-.package .details-toggler.open {
-}
-.package .details-toggler:hover {
+  font-family: Consolas, "Courier New", Courier, monospace;
+  min-width: 80%;
+  position: relative;
+  top: -3px;
+  left: 2px;
 }
 
-/* TODO !!! */
+
 .package .mark-favorite {
   font-size: 20px;
   cursor: pointer;
-  color: #c4b90c;
+  color: #BDC3C7;
+  position: absolute;
+  left: -25px;
+  top: 15px;
 }
-.package .mark-favorite.icon-star {
-  color: #eadc00;
+.package .mark-favorite.is-starred {
+  color: #F7EB2A;
+  text-shadow: 0px 0px 0px rgba(100, 70, 16, 1);
+}
+@media (max-width: 767px) {
+    .package .mark-favorite {
+      left: auto;
+      right: 0;
+    }
 }
 
 
@@ -922,7 +1072,7 @@ ul.packages .abandoned {
 
 .pagination ul > li > a, .pagination ul > li > span {
     font-size: 12px;
-    font-weight: bold;
+    font-weight: 600;
     height: 40px;
     text-align: center;
     text-decoration: none;
@@ -942,15 +1092,17 @@ ul.packages .abandoned {
   font-size: 1.5em;
 }
 .legend-first {
-  color: rgb(0,0,255);
+  color: #068FD5;
 }
 .legend-second {
-  color: rgb(255,153,0);
+  color: #2c3e50;
 }
 
-pre {
+pre, code {
     background: #F3F4F5;
     border: none;
+    border-radius: 2px;
+    color: #2C3E50;
 }
 
 .wrapper .content h2.title, .wrapper .content h3.title {
@@ -984,10 +1136,17 @@ pre {
 
 .wrapper .content h2 a {
     color: #2C3E50;
-    font-weight: bold;
+    font-weight: 600;
     text-decoration: none;
 }
 
+@media (max-width: 767px) {
+    .wrapper .content h2.title {
+        font-size: 26px;
+        padding-bottom: 5px;
+    }
+}
+
 .wrapper .content p {
     line-height: 150%;
     vertical-align: baseline;
@@ -1042,12 +1201,14 @@ pre {
 }
 
 .packages-short li {
-    margin-bottom: 6px;
+    margin-bottom: 12px;
+    font-style: italic;
+    font-weight: 400;
 }
 
 .packages-short li a {
-    font-size: 20px;
     display: block;
+    font-style: normal;
 }
 
 .packages-short li a strong {
@@ -1055,24 +1216,24 @@ pre {
     font-size: 14px;
 }
 
-.packages > li {
+.packages .package-item {
     padding: 5px 15px;
     margin-bottom: 10px;
     background: #F3F4F5;
     border-radius: 4px;
 }
 
-.packages li.selected {
+.packages .selected .package-item {
     padding: 3px 13px;
     border: 2px solid #2980b9;
 }
 
-.packages li h4 {
+.packages .package-item h4 {
     font-size: 20px;
     margin-bottom: 0;
 }
 
-.packages li h4 small {
+.packages .package-item h4 small {
     margin-left: 5px;
     color: #83C129;
     font-size: 16px;
@@ -1084,6 +1245,7 @@ pre {
     border-left: 10px solid #000;
     box-shadow: none;
     border-radius: 2px;
+    padding: 5px 0 5px 10px;
 }
 .alert-warning {
     border-left-color: #D58512;
@@ -1099,6 +1261,17 @@ pre {
 }
 
 
+@media (max-width: 1199px) {
+    .publishing-packages {
+        margin-top: 40px;
+    }
+}
+#how-to-submit-packages {
+    margin-top: 40px;
+}
+
+
+
 [class^="icon-"]:before,
 [class*=" icon-"]:before {
     font-family: 'fontello';

+ 2 - 2
src/Packagist/WebBundle/Resources/public/js/charts.js

@@ -2,8 +2,8 @@
     "use strict";
 
     var colors = [
-        'rgba(0,0,255,1)',
-        'rgba(255,153,0,1)'
+        'rgba(6,143,213,1)',
+        'rgba(44,62,80,1)'
     ];
 
     Chart.defaults.global.responsive = true;

+ 17 - 6
src/Packagist/WebBundle/Resources/public/js/view.js

@@ -62,7 +62,7 @@
             dataType: 'json',
             cache: false,
             success: function () {
-                $(this).toggleClass('icon-star icon-star-empty');
+                $(this).toggleClass('is-starred');
             },
             context: this
         };
@@ -70,7 +70,7 @@
         if ($(this).is('.loading')) {
             return;
         }
-        if ($(this).is('.icon-star')) {
+        if ($(this).is('.is-starred')) {
             options.type = 'DELETE';
             options.url = $(this).data('remove-url');
         } else {
@@ -83,12 +83,14 @@
     });
     $('.package .delete').submit(function (e) {
         e.preventDefault();
-        if (window.confirm('Are you sure?')) {
-            e.target.submit();
-        }
+        e.target.submit();
     });
-    $('.package .delete-version').click(function (e) {
+    $('.package .delete-version .submit').click(function (e) {
+        e.preventDefault();
         e.stopImmediatePropagation();
+        if (window.confirm('Are you sure?')) {
+            $(e.target).closest('.delete-version').submit();
+        }
     });
     $('.package .delete-version').submit(function (e) {
         e.preventDefault();
@@ -104,6 +106,15 @@
         forceUpdatePackage(null, true);
     }
 
+    var versionsList = $('.package .versions')[0];
+    if (versionsList.offsetHeight < versionsList.scrollHeight) {
+        $('.package .versions-expander').removeClass('hidden').on('click', function () {
+            $(this).addClass('hidden')
+            $(versionsList).css('overflow-y', 'visible')
+                .css('max-height', 'auto');
+        });
+    }
+
     ZeroClipboard.setMoviePath("/js/libs/ZeroClipboard.swf");
     var clip = new ZeroClipboard.Client("#copy");
     clip.on("complete", function () {

+ 11 - 9
src/Packagist/WebBundle/Resources/views/About/about.html.twig

@@ -5,10 +5,10 @@
     <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" id="how-to-submit-packages">How to submit packages?</h2>
+    <h2 class="title" id="how-to-submit-packages">How to submit packages?</h2>
     <section class="row">
         <section class="col-md-6">
-            <h3 class="font-slim" id="naming-your-package">Naming your package</h3>
+            <h3 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>
@@ -27,7 +27,7 @@ acme/email</code></pre>
         </section>
 
         <section class="col-md-6">
-            <h3 class="font-slim" id="creating-a-composerjson-file">Creating a composer.json file</h3>
+            <h3 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><code>{
@@ -59,7 +59,7 @@ acme/email</code></pre>
         </section>
 
         <section class="col-md-6">
-            <h3 class="font-slim" id="managing-package-versions">Managing package versions</h3>
+            <h3 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>
@@ -73,20 +73,22 @@ v2.0.4-p1</code></pre>
         </section>
 
         <section class="col-md-6">
-            <h3 class="font-slim" id="update-schedule">Update Schedule</h3>
+            <h3 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="col-md-12">
-            <h3 class="font-slim" id="community">Community</h3>
+        <div class="clearfix"></div>
+
+        <section class="col-md-6">
+            <h3 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="col-md-12">
-            <h3 class="font-slim" id="community">Contributing</h3>
+        <section class="col-md-6">
+            <h3 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>

+ 4 - 4
src/Packagist/WebBundle/Resources/views/Feed/feeds.html.twig

@@ -3,15 +3,15 @@
 {% block content %}
     <h2 class="title">Atom/RSS Feeds</h2>
 
-    <h3 class="font-normal">Global Feeds</h3>
+    <h3>Global Feeds</h3>
     <p>Newly Submitted Packages: <a href="{{ url('feed_packages', {_format: 'rss'}) }}">RSS</a>, <a href="{{ url('feed_packages', {_format: 'atom'}) }}">Atom</a></p>
     <p>New Releases: <a href="{{ url('feed_releases', {_format: 'rss'}) }}">RSS</a>, <a href="{{ url('feed_releases', {_format: 'atom'}) }}">Atom</a></p>
 
-    <h3 class="font-normal">Vendor Feed</h3>
+    <h3>Vendor Feed</h3>
     <p>New Releases for a specific vendor namespace: <code>{{ url('feed_vendor', {vendor: 'XXX', _format: 'rss'})|replace({XXX: '%vendor%'}) }}</code></p>
     <p>Replace <code>%vendor%</code> by the vendor name, and change rss to atom if you would like an atom feed.</p>
 
-    <h3 class="font-normal">Package Feed</h3>
+    <h3>Package Feed</h3>
     <p>New Releases for a specific package: <code>{{ url('feed_package', {package: 'X/X', _format: 'rss'})|replace({'X/X': '%vendor/package%'}) }}</code></p>
     <p>Replace <code>%vendor/package%</code> by the package name, and change rss to atom if you would like an atom feed.</p>
-{% endblock %}
+{% endblock %}

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

@@ -29,7 +29,7 @@
     <section class="{{ isActualUser ? 'col-md-9' : 'col-md-12' }}">
         {% embed "PackagistWebBundle:Web:list.html.twig" with {noLayout: 'true', showAutoUpdateWarning: isActualUser} %}
             {% block content_title %}
-                <h3 class="font-normal profile-title">{{ isActualUser ? 'Your' : user.username~'\'s' }} favorite packages</h3>
+                <h3 class="font-normal profile-title">{{ isActualUser ? 'My' : user.username~'\'s' }} favorite packages</h3>
             {% endblock %}
         {% endembed %}
     </section>

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

@@ -29,7 +29,7 @@
     <section class="{{ isActualUser ? 'col-md-9' : 'col-md-12' }}">
         {% embed "PackagistWebBundle:Web:list.html.twig" with {noLayout: 'true', showAutoUpdateWarning: isActualUser} %}
             {% block content_title %}
-                <h3 class="font-normal profile-title">Packages maintained by {{ isActualUser ? 'you' : user.username }}</h3>
+                <h3 class="font-normal profile-title">{{ isActualUser ? 'My packages' : 'Packages maintained by ' ~ user.username }}</h3>
             {% endblock %}
         {% endembed %}
     </section>

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

@@ -21,7 +21,7 @@
         </section>
 
         <section class="packages-short col-lg-6">
-            <h3>Popular Packages</h3>
+            <h3>Popular Packages <a href="{{ path('browse_popular') }}">View All</a></h3>
 
             {{ macros.listPackagesShort(popular, false, true) }}
         </section>

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

@@ -3,9 +3,9 @@
 {% block content %}
 <section class="row">
     <section class="col-lg-6 getting-started">
-        <h2 class="font-normal">Getting Started</h2>
+        <h2 class="title">Getting Started</h2>
         <div>
-            <h3 class="font-normal">Define Your Dependencies</h3>
+            <h3>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><code>{
     "require": {
@@ -15,16 +15,16 @@
     }
 }</code></pre>
 
-            <h3 class="font-normal">Install Composer In Your Project</h3>
+            <h3>Install Composer In Your Project</h3>
             <p>Run this in your command line:</p>
             <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>
+            <h3>Install Dependencies</h3>
             <p>Execute this in your project root.</p>
             <pre><code>php composer.phar install</code></pre>
 
-            <h3 class="font-normal">Autoload Dependencies</h3>
+            <h3>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><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>
@@ -32,9 +32,9 @@
     </section>
 
     <section class="col-lg-6 publishing-packages">
-        <h2 class="font-normal">Publishing Packages</h2>
+        <h2 class="title">Publishing Packages</h2>
         <div>
-            <h3 class="font-normal">Define Your Package</h3>
+            <h3>Define Your Package</h3>
             <p>Put a file named <em>composer.json</em> at the root of your package, containing this information:</p>
             <pre><code>{
     "name": "your-vendor-name/package-name",
@@ -47,10 +47,10 @@
             <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>
 
-            <h3 class="font-normal">Commit The File</h3>
+            <h3>Commit The File</h3>
             <p>You surely don't need help with that.</p>
 
-            <h3 class="font-normal">Publish It</h3>
+            <h3>Publish It</h3>
             <p><a href="{{ path('hwi_oauth_connect') }}">Login</a> or <a href="{{ path('fos_user_registration_register') }}">register</a> on this site, then hit the big fat green button above that says <a href="{{ path('submit') }}">submit</a>.</p>
             <p>Once you entered your public repository URL in there, your package will be automatically crawled periodically. You just have to make sure you keep the composer.json file up to date.</p>
         </div>

+ 1 - 1
src/Packagist/WebBundle/Resources/views/Web/searchForm.html.twig

@@ -1,7 +1,7 @@
 <form id="search-form" action="{{ path('search.ajax') }}" method="GET" {{ form_enctype(searchForm) }} autocomplete="off">
     <div class="{% if orderBys is defined %}sortable{% endif %} row">
         <div class="hidden">{{ form_errors(searchForm.query) }}</div>
-        <div class="col-xs-8 col-lg-10">
+        <div class="col-xs-9">
             {{ form_widget(searchForm.query, {'attr': {'autocomplete': 'off', 'autofocus': 'autofocus', 'placeholder': 'Search packages...', 'tabindex': 1}}) }}
         </div>
 

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

@@ -9,7 +9,7 @@
 
     <section class="row">
         <div class="col-lg-12">
-            <h2>Packages/versions over time</h2>
+            <h3>Packages/versions over time</h3>
 
             <p class="row">
                 <canvas class="col-xs-12" width="500" height="200" data-labels="{{ chart.months|join(',') }}" data-scale="1000" data-values="{{ chart.versions|join(',') }}|{{ chart.packages|join(',') }}">
@@ -23,7 +23,7 @@
             <p>The last data point is for the current month and shows partial data.</p>
 
             {% if downloadsChart %}
-                <h2>Packages installed in the last 30 days</h2>
+                <h3>Packages installed in the last 30 days</h3>
                 <p class="row">
                     <canvas class="col-xs-12" width="500" height="200" data-labels="{{ downloadsChart.labels|join(',') }}" data-scale="1000" data-values="{{ downloadsChart.values|join(',') }}">
                         Sorry, the graph can't be displayed because your browser doesn't support &lt;canvas&gt; html element.
@@ -34,7 +34,7 @@
                 </ul>
             {% endif %}
             {% if downloadsChartMonthly %}
-                <h2>Packages installed per month</h2>
+                <h3>Packages installed per month</h3>
                 <p class="row">
                     <canvas class="col-xs-12" width="500" height="200" data-labels="{{ downloadsChartMonthly.labels|join(',') }}" data-scale="1000000" data-values="{{ downloadsChartMonthly.values|join(',') }}">
                         Sorry, the graph can't be displayed because your browser doesn't support &lt;canvas&gt; html element.
@@ -46,22 +46,22 @@
                 <p>The last data point is for the current month and shows partial data.</p>
             {% endif %}
 
-            <h2>Totals</h2>
+            <h3>Totals</h3>
             <dl class="dl-horizontal">
-                <dt class="font-bold">Packages registered</dt>
-                <dd class="font-slim">{{ packages|number_format(0, '.', " ") }}</dd>
+                <dt class="font-normal">Packages registered</dt>
+                <dd class="font-normal">{{ packages|number_format(0, '.', "&#8201;")|raw }}</dd>
 
-                <dt class="font-bold">Versions available</dt>
-                <dd class="font-slim">{{ versions|number_format(0, '.', " ") }}</dd>
+                <dt class="font-normal">Versions available</dt>
+                <dd class="font-normal">{{ versions|number_format(0, '.', "&#8201;")|raw }}</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>
+                <dt class="font-normal">Packages installed<br><small>(since {{ downloadsStartDate }})</small></dt>
+                <dd class="font-normal">{% if downloads == 'N/A' %}{{ downloads }}{% else %}{{ downloads|number_format(0, '.', "&#8201;")|raw }}{% endif %}</dd>
             </dl>
         </div>
     </section>
 {% endblock %}
 
 {% block scripts %}
-    <script src="{{ asset('js/libs/Chart.js/Chart.min.js') }}"></script>
-    <script src="{{ asset('bundles/packagistweb/js/charts.js') }}"></script>
+    <script src="{{ asset('js/libs/Chart.js/Chart.min.js?v=2') }}"></script>
+    <script src="{{ asset('bundles/packagistweb/js/charts.js?v=2') }}"></script>
 {% endblock %}

+ 31 - 29
src/Packagist/WebBundle/Resources/views/Web/versionDetails.html.twig

@@ -1,5 +1,5 @@
 {% import "PackagistWebBundle::macros.html.twig" as packagist %}
-<div>
+<div class="title">
     <span class="version-number">
         {{- version.version -}}
         {% if version.hasVersionAlias() %}
@@ -11,10 +11,12 @@
 </div class="version-number">
 
 <div class="clearfix package-links">
-    {% for types in [["require", "devRequire", "suggest"], ["provide", "conflict", "replace"]] %}
+    {% for types in [["require", "devRequire", "suggest", "provide", "conflict", "replace"]] %}
         <div class="row">
             {% for type in types %}
-                <div class="{{ type }}s col-md-4 col-sm-6">
+                {% if loop.index0 is even %}<div class="clearfix visible-sm-block"></div>{% endif %}
+                {% if loop.index0 % 3 == 0 %}<div class="clearfix visible-md-block visible-lg-block"></div>{% endif %}
+                <div class="{{ type }}s col-sm-6 col-md-4 {% if not attribute(version, type)|length %}hidden-xs{% endif %}">
                     <p class="link-type">{{ ('link_type.' ~ type)|trans }}</p>
                     {% if attribute(version, type)|length %}
                         <ul>
@@ -31,31 +33,31 @@
     {% endfor %}
 </div>
 
-<p class="license"><i class="glyphicon glyphicon-copyright-mark" title="License"></i> {% if not version.license %}<span class="unknown">{% endif %}{{ version.license ? version.license|join(', ') : 'Unknown License' }}{% if not version.license %}</span>{% endif %}</p>
+<div class="metadata">
+    <p class="license"><i class="glyphicon glyphicon-copyright-mark" title="License"></i> {% if not version.license %}<span class="unknown">{% endif %}{{ version.license ? version.license|join(', ') : 'Unknown License' }}{% if not version.license %}</span>{% endif %} <span class="source-reference"><i class="glyphicon glyphicon-bookmark" title="Source Reference"></i> {{ version.source.reference }}</span></p>
 
-<p class="source-reference"><i class="glyphicon glyphicon-bookmark" title="Source Reference"></i> {{ version.source.reference }}</p>
-
-{% if version.authors|length %}
-    <i class="glyphicon glyphicon-user" title="Authors"></i>
-    <ul class="authors">
-        {% for author in version.authors %}
-            <li>
-                {%- if author.homepage -%}
-                    <a href="{{ author.homepage }}">{{ author.name }}</a>
-                {%- else -%}
-                    {{ author.name }}
-                {%- endif -%}
-                {% if author.email %} &lt;<a href="mailto:{{ author.email }}">{{ author.email }}</a>&gt;{% endif -%}
-            </li>
-        {% endfor %}
-    </ul>
-{% endif %}
+    {% if version.authors|length %}
+        <i class="glyphicon glyphicon-user" title="Authors"></i>
+        <ul class="authors">
+            {% for author in version.authors %}
+                <li>
+                    {%- if author.homepage -%}
+                        <a href="{{ author.homepage }}">{{ author.name }}</a>
+                    {%- else -%}
+                        {{ author.name }}
+                    {%- endif -%}
+                    {% if author.email %} &lt;<a href="mailto:{{ author.email }}">{{ author.email }}</a>&gt;{% endif -%}
+                </li>
+            {% endfor %}
+        </ul>
+    {% endif %}
 
-{% if version.tags|length %}
-    <p class="tags">
-        <i class="glyphicon glyphicon-tag" title="Authors"></i>
-        {% for tag in version.tags -%}
-            <a href="{{ url("search", {tags: tag.name}) }}">{{ tag.name }}</a>
-        {%- endfor -%}
-    </p>
-{% endif %}
+    {% if version.tags|length %}
+        <p class="tags">
+            <i class="glyphicon glyphicon-tag" title="Authors"></i>
+            {% for tag in version.tags -%}
+                <a href="{{ url("search", {tags: tag.name}) }}">{{ tag.name }}</a>
+            {%- endfor -%}
+        </p>
+    {% endif %}
+</div>

+ 157 - 136
src/Packagist/WebBundle/Resources/views/Web/viewPackage.html.twig

@@ -11,162 +11,180 @@
 {% endblock %}
 
 {% block scripts %}
-    <script src="{{ asset('bundles/packagistweb/js/view.js?v=13')}}"></script>
+    <script src="{{ asset('bundles/packagistweb/js/view.js?v=14')}}"></script>
 {% endblock %}
 
+{% block description -%}
+    {{ package.description }}
+{%- endblock %}
+
+{% set hasActions = is_granted('ROLE_EDIT_PACKAGES') or is_granted('ROLE_EDIT_PACKAGES') or package.maintainers.contains(app.user) %}
+
 {% block content %}
-    <div class="box">
-        <div class="package"{% if app.user and package.crawledAt is null and (is_granted('ROLE_EDIT_PACKAGES') or package.maintainers.contains(app.user)) %} data-force-crawl="true"{% endif %}>
-            <section class="row">
-                {% set hasActions = is_granted('ROLE_EDIT_PACKAGES') or is_granted('ROLE_EDIT_PACKAGES') or package.maintainers.contains(app.user) %}
-
-                <div class="{% if hasActions %}col-sm-6 col-md-8{% else %}col-xs-12{% endif %}">
-                    <h2 class="title">
-                        {% if is_favorite is defined %}
-                            <i class="mark-favorite icon-star{% if not is_favorite %}-empty{% endif %}" data-remove-url="{{ path('user_remove_fav', {name: app.user.username, package: package.name}) }}" data-add-url="{{ path('user_add_fav', {name: app.user.username}) }}" data-package="{{ package.name }}"></i>
-                        {% endif %}
-                        <a href="{{ path("view_vendor", {"vendor": package.vendor}) }}">{{ package.vendor }}/</a>{{ package.packageName }}
-                        <a id="copy" data-clipboard-text="{{ package.name }}" title="Copy to clipboard"><i class="icon-copy"></i></a>
-                    </h2>
-                    <p class="requireme"><i class="glyphicon glyphicon-save"></i> <input type="text" readonly="readonly" value="composer require {{ "#{version.package.vendor}/#{version.package.packageName}" }}" /></p>
+    <div class="row">
+        <div class="col-xs-12 package"{% if app.user and package.crawledAt is null and (is_granted('ROLE_EDIT_PACKAGES') or package.maintainers.contains(app.user)) %} data-force-crawl="true"{% endif %}>
+            <div class="package-header">
+
+                <div class="row">
+                    <div class="col-md-9">
+                        <h2 class="title">
+                            {% if is_favorite is defined %}
+                                <i class="mark-favorite glyphicon glyphicon-star {% if is_favorite %}is-starred{% endif %}" data-remove-url="{{ path('user_remove_fav', {name: app.user.username, package: package.name}) }}" data-add-url="{{ path('user_add_fav', {name: app.user.username}) }}" data-package="{{ package.name }}"></i>
+                            {% endif %}
+                            <a href="{{ path("view_vendor", {"vendor": package.vendor}) }}">{{ package.vendor }}/</a>{{ package.packageName }}
+                        </h2>
+                    </div>
                 </div>
+            </div>
 
-                {% if hasActions %}
-                    <div class="col-sm-6 col-md-4 btn-group btn-group-xs">
-                        {% if is_granted('ROLE_EDIT_PACKAGES') or package.maintainers.contains(app.user) %}
-                            <form class="pull-right action" action="{{ path("edit_package", {name: package.name}) }}">
-                                <input class="btn btn-default" type="submit" value="Edit" />
-                            </form>
-                        {% endif %}
-                        {% if is_granted('ROLE_UPDATE_PACKAGES') or package.maintainers.contains(app.user) %}
-                            <form class="force-update pull-right action" action="{{ path('update_package', {name: package.name}) }}" method="POST">
-                                <input type="hidden" name="_method" value="PUT" />
-                                <input type="hidden" name="update" value="1" />
-                                <input class="btn btn-success" type="submit" value="Update" />
-                            </form>
-                        {% endif %}
-                        {% if deleteForm is defined %}
-                            <form class="delete pull-right action" action="{{ path('delete_package', {name: package.name}) }}" method="POST">
-                                <input type="hidden" name="_method" value="DELETE" />
-                                {{ form_widget(deleteForm._token) }}
-                                <input class="btn btn-danger" type="submit" value="Delete" />
-                            </form>
-                        {% endif %}
-                        {% if (is_granted('ROLE_EDIT_PACKAGES') or package.maintainers.contains(app.user)) and not package.abandoned %}
-                            <form class="pull-right action abandon" action="{{ path('abandon_package', {name: package.name}) }}">
-                                <input class="btn btn-warning" type="submit" value="Abandon" />
-                            </form>
-                        {% endif %}
-                        {% if (is_granted('ROLE_EDIT_PACKAGES') or package.maintainers.contains(app.user)) and package.abandoned %}
-                            <form class="pull-right action un-abandon" action="{{ path('unabandon_package', {name: package.name}) }}">
-                                <input class="btn btn-default" type="submit" value="Un-abandon" />
-                            </form>
-                        {% endif %}
-                    </div>
-                {% endif %}
-            </section>
-
-            {% if not package.autoUpdated and app.user and (package.maintainers.contains(app.user) or is_granted('ROLE_UPDATE_PACKAGES')) %}
-                {% if "github.com" in package.repository %}
-                    <div class="alert alert-danger">This package is not auto-updated. Please set up the <a href="{{ path('fos_user_profile_show') }}">GitHub Service Hook</a> for Packagist so that it gets updated whenever you push!</div>
-                {% elseif "bitbucket.org" in package.repository %}
-                    <div class="alert alert-danger">This package is not auto-updated. Please set up the <a href="{{ path('fos_user_profile_show') }}">BitBucket POST Service</a> for Packagist so that it gets updated whenever you push!</div>
-                {% endif %}
-            {% endif %}
+            <div class="row">
+                <div class="col-md-8">
+                    <p class="requireme"><i class="glyphicon glyphicon-save"></i> <input type="text" readonly="readonly" value="composer require {{ "#{version.package.vendor}/#{version.package.packageName}" }}" /></p>
 
-            {% if package.abandoned %}
-                <div class="alert alert-warning">
-                    This package is <strong>abandoned</strong> and no longer maintained.
-                    {% if package.replacementPackage is not empty %}
-                        The author suggests using the <a href="{{ path('view_package', {name: package.replacementPackage}) }}">{{ package.replacementPackage }}</a> package instead.
-                    {% else %}
-                        No replacement package was suggested.
-                        {% if (is_granted('ROLE_EDIT_PACKAGES') or package.maintainers.contains(app.user)) %}
-                            <a href="{{ path('abandon_package', {name: package.name}) }}">Suggest a replacement.</a>
+                    {% if not package.autoUpdated and app.user and (package.maintainers.contains(app.user) or is_granted('ROLE_UPDATE_PACKAGES')) %}
+                        {% if "github.com" in package.repository %}
+                            <div class="alert alert-danger">This package is not auto-updated. Please set up the <a href="{{ path('fos_user_profile_show') }}">GitHub Service Hook</a> for Packagist so that it gets updated whenever you push!</div>
+                        {% elseif "bitbucket.org" in package.repository %}
+                            <div class="alert alert-danger">This package is not auto-updated. Please set up the <a href="{{ path('fos_user_profile_show') }}">BitBucket POST Service</a> for Packagist so that it gets updated whenever you push!</div>
                         {% endif %}
                     {% endif %}
-                </div>
-            {% endif %}
-            {% if package.updateFailureNotified
-                and app.user and (package.maintainers.contains(app.user) or is_granted('ROLE_UPDATE_PACKAGES'))
-            %}
-                <div class="alert alert-danger">This package is in a broken state and will not update anymore. Some branches contain invalid data and until you fix them the entire package is frozen. Click "Force Update" above to see details.</div>
-            {% endif %}
 
-            <p class="description">{{ package.description }}</p>
-
-            <div class="row">
-                <p class="details col-md-6">
-                    {% set repoUrl = package.repository|replace({'git://github.com/': 'https://github.com/', 'git@github.com:': 'https://github.com/'}) %}
-                    <span class="canonical"><i class="icon-box"></i> <a href="{{ repoUrl }}">{{ repoUrl|replace({'https://':'', 'http://':''}) }}</a></span><br />
-                    <span class="maintainers"><i class="glyphicon glyphicon-user"></i>
-                    {% for maintainer in package.maintainers -%}
-                        <a href="{{ path('user_profile', {'name': maintainer.username}) }}">{{ maintainer.username }}</a>{{ loop.last ? '' : ', ' }}
-                    {% endfor %}
-                    {% if addMaintainerForm is defined or removeMaintainerForm is defined %}
-                        {% if addMaintainerForm is defined %}<a id="add-maintainer" href="{{ path('add_maintainer', {'name': package.name}) }}"><i class="glyphicon glyphicon-plus"></i></a>{% endif %}
-                        {% if addMaintainerForm is defined and removeMaintainerForm is defined %} / {% endif %}
-                        {% if removeMaintainerForm is defined %}<a id="remove-maintainer" href="{{ path('remove_maintainer', {'name': package.name}) }}"><i class="glyphicon glyphicon-remove"></i></a>{% endif %}
-                    {% endif %}
-                    <br />
-                    {% if version and version.homepage %}
-                        <i class="glyphicon glyphicon-home"></i> <a href="{{ version.homepage }}">Homepage</a><br />
-                    {% endif %}
-                    {% if version.support.source is defined %}
-                        <i class="glyphicon glyphicon-home"></i> <a href="{{ version.support.source }}">Source</a><br />
-                    {% endif %}
-                    {% if version and version.support.issues is defined %}
-                        <i class="glyphicon glyphicon-home"></i> <a href="{{ version.support.issues }}">Issues</a><br />
-                    {% endif %}
-                    {% if version and version.support.irc is defined %}
-                        <i class="glyphicon glyphicon-home"></i> <a href="{{ version.support.irc }}">IRC</a><br />
-                    {% endif %}
-                    {% if version and version.support.forum is defined %}
-                        <i class="glyphicon glyphicon-home"></i> <a href="{{ version.support.forum }}">Forum</a><br />
+                    {% if package.abandoned %}
+                        <div class="alert alert-warning">
+                            This package is <strong>abandoned</strong> and no longer maintained.
+                            {% if package.replacementPackage is not empty %}
+                                The author suggests using the <a href="{{ path('view_package', {name: package.replacementPackage}) }}">{{ package.replacementPackage }}</a> package instead.
+                            {% else %}
+                                No replacement package was suggested.
+                                {% if (is_granted('ROLE_EDIT_PACKAGES') or package.maintainers.contains(app.user)) %}
+                                    <a href="{{ path('abandon_package', {name: package.name}) }}">Suggest a replacement.</a>
+                                {% endif %}
+                            {% endif %}
+                        </div>
                     {% endif %}
-                    {% if version and version.support.wiki is defined %}
-                        <i class="glyphicon glyphicon-home"></i> <a href="{{ version.support.wiki }}">Wiki</a><br />
+                    {% if package.updateFailureNotified
+                        and app.user and (package.maintainers.contains(app.user) or is_granted('ROLE_UPDATE_PACKAGES'))
+                    %}
+                        <div class="alert alert-danger">This package is in a broken state and will not update anymore. Some branches contain invalid data and until you fix them the entire package is frozen. Click "Force Update" above to see details.</div>
                     {% endif %}
-                    {% if version and version.support.docs is defined %}
-                        <i class="glyphicon glyphicon-home"></i> <a href="{{ version.support.docs }}">Documentation</a><br />
+
+                    <p class="description">{{ package.description }}</p>
+
+                    {% if hasActions %}
+                        <div class="btn-group btn-group-xs">
+                            {% if (is_granted('ROLE_EDIT_PACKAGES') or package.maintainers.contains(app.user)) and not package.abandoned %}
+                                <form class="action abandon" action="{{ path('abandon_package', {name: package.name}) }}">
+                                    <input class="btn btn-warning" type="submit" value="Abandon" />
+                                </form>
+                            {% endif %}
+                            {% if (is_granted('ROLE_EDIT_PACKAGES') or package.maintainers.contains(app.user)) and package.abandoned %}
+                                <form class="action un-abandon" action="{{ path('unabandon_package', {name: package.name}) }}">
+                                    <input class="btn btn-default" type="submit" value="Un-abandon" />
+                                </form>
+                            {% endif %}
+                            {% if deleteForm is defined %}
+                                <form class="delete action" action="{{ path('delete_package', {name: package.name}) }}" method="POST">
+                                    <input type="hidden" name="_method" value="DELETE" />
+                                    {{ form_widget(deleteForm._token) }}
+                                    <input class="btn btn-danger" type="submit" value="Delete" />
+                                </form>
+                            {% endif %}
+                            {% if is_granted('ROLE_UPDATE_PACKAGES') or package.maintainers.contains(app.user) %}
+                                <form class="force-update action" action="{{ path('update_package', {name: package.name}) }}" method="POST">
+                                    <input type="hidden" name="_method" value="PUT" />
+                                    <input type="hidden" name="update" value="1" />
+                                    <input class="btn btn-success" type="submit" value="Update" />
+                                </form>
+                            {% endif %}
+                            {% if is_granted('ROLE_EDIT_PACKAGES') or package.maintainers.contains(app.user) %}
+                                <form class="action" action="{{ path("edit_package", {name: package.name}) }}">
+                                    <input class="btn btn-primary" type="submit" value="Edit" />
+                                </form>
+                            {% endif %}
+                        </div>
                     {% endif %}
-                </p>
+                </div>
 
-                <p class="downloads col-md-6">
-                    <span>Overall:</span> {% if downloads.total is defined %}{{ downloads.total|number_format(0, '.', ' ') }} install{{ downloads.total == 1 ? '' : 's' }}{% else %}N/A{% endif %}<br />
-                    <span>30 days:</span> {% if downloads.monthly is defined %}{{ downloads.monthly|number_format(0, '.', ' ') }} install{{ downloads.monthly == 1 ? '' : 's' }}{% else %}N/A{% endif %}<br />
-                    <span>Today:</span> {% if downloads.daily is defined %}{{ downloads.daily|number_format(0, '.', ' ') }} install{{ downloads.daily == 1 ? '' : 's' }}{% else %}N/A{% endif %}<br />
-                </p>
+                <div class="col-md-offset-1 col-md-3">
+                    <div class="row package-aside">
+                        <div class="details col-xs-12 col-sm-6 col-md-12">
+                            {% set repoUrl = package.repository|replace({'git://github.com/': 'https://github.com/', 'git@github.com:': 'https://github.com/'}) %}
+                            <p class="canonical">
+                                <i class="icon-box" title="Canonical Repository URL"></i>
+                                <a href="{{ repoUrl }}" title="Canonical Repository URL">{{ repoUrl|replace({'https://':'', 'http://':''}) }}</a>
+                            </p>
+                            <p class="maintainers">
+                                <i class="glyphicon glyphicon-user"></i>
+                                {% for maintainer in package.maintainers -%}
+                                    <a href="{{ path('user_profile', {'name': maintainer.username}) }}">{{ maintainer.username }}</a>{{ loop.last ? '' : ', ' }}
+                                {% endfor %}
+                                {% if addMaintainerForm is defined or removeMaintainerForm is defined %}
+                                    {% if addMaintainerForm is defined %}<a id="add-maintainer" href="{{ path('add_maintainer', {'name': package.name}) }}"><i class="glyphicon glyphicon-plus"></i></a>{% endif %}
+                                    {% if addMaintainerForm is defined and removeMaintainerForm is defined %} / {% endif %}
+                                    {% if removeMaintainerForm is defined %}<a id="remove-maintainer" href="{{ path('remove_maintainer', {'name': package.name}) }}"><i class="glyphicon glyphicon-remove"></i></a>{% endif %}
+                                {% endif %}
+                            </p>
+
+                            {% if version and version.homepage %}
+                                <p><i class="glyphicon glyphicon-home"></i> <a href="{{ version.homepage }}">Homepage</a></p>
+                            {% endif %}
+                            {% if version.support.source is defined %}
+                                <p><i class="glyphicon glyphicon-align-left"></i> <a href="{{ version.support.source }}">Source</a></p>
+                            {% endif %}
+                            {% if version and version.support.issues is defined %}
+                                <p><i class="glyphicon glyphicon-exclamation-sign"></i> <a href="{{ version.support.issues }}">Issues</a></p>
+                            {% endif %}
+                            {% if version and version.support.irc is defined %}
+                                <p><i class="glyphicon glyphicon-comment"></i> <a href="{{ version.support.irc }}">IRC</a></p>
+                            {% endif %}
+                            {% if version and version.support.forum is defined %}
+                                <p><i class="glyphicon glyphicon-comment"></i> <a href="{{ version.support.forum }}">Forum</a></p>
+                            {% endif %}
+                            {% if version and version.support.wiki is defined %}
+                                <p><i class="glyphicon glyphicon-book"></i> <a href="{{ version.support.wiki }}">Wiki</a></p>
+                            {% endif %}
+                            {% if version and version.support.docs is defined %}
+                                <p><i class="glyphicon glyphicon-book"></i> <a href="{{ version.support.docs }}">Documentation</a></p>
+                            {% endif %}
+                        </div>
+
+                        <p class="downloads col-xs-12 col-sm-6 col-md-12">
+                            <span>Overall:</span> {% if downloads.total is defined %}{{ downloads.total|number_format(0, '.', '&#8201;')|raw }} install{{ downloads.total == 1 ? '' : 's' }}{% else %}N/A{% endif %}<br />
+                            <span>30 days:</span> {% if downloads.monthly is defined %}{{ downloads.monthly|number_format(0, '.', '&#8201;')|raw }} install{{ downloads.monthly == 1 ? '' : 's' }}{% else %}N/A{% endif %}<br />
+                            <span>Today:</span> {% if downloads.daily is defined %}{{ downloads.daily|number_format(0, '.', '&#8201;')|raw }} install{{ downloads.daily == 1 ? '' : 's' }}{% else %}N/A{% endif %}<br />
+                        </p>
+                    </div>
+                </div>
             </div>
 
             {% if addMaintainerForm is defined or removeMaintainerForm is defined %}
-                <div>
+                <div class="row">
                     {% if addMaintainerForm is defined %}
-                        <form id="add-maintainer-form" class="{{ show_add_maintainer_form|default(false) ? '': 'hidden' }}" action="{{ path('add_maintainer', {'name': package.name}) }}" method="POST" {{ form_enctype(addMaintainerForm) }}>
+                        <form id="add-maintainer-form" class="col-sm-6 col-sm-offset-6 {{ show_add_maintainer_form|default(false) ? '': 'hidden' }}" action="{{ path('add_maintainer', {'name': package.name}) }}" method="POST" {{ form_enctype(addMaintainerForm) }}>
                             <div>
-                                <h2>Add Maintainer</h2>
+                                <h4>Add Maintainer</h4>
                                 <p>
                                     {{ form_label(addMaintainerForm.user, "Username") }}
                                     {{ form_errors(addMaintainerForm.user) }}
                                     {{ form_widget(addMaintainerForm.user) }}
                                 </p>
                                 {{ form_rest(addMaintainerForm) }}
-                                <input id="submit-new-maintainer" type="submit" value="Submit" />
+                                <input class="btn btn-block btn-success btn-lg" type="submit" value="Add Maintainer" />
                             </div>
                         </form>
                     {% endif %}
 
                     {% if removeMaintainerForm is defined %}
-                        <form id="remove-maintainer-form" class="{{ show_remove_maintainer_form|default(false) ? '': 'hidden' }}" action="{{ path('remove_maintainer', {'name': package.name}) }}" method="POST" {{ form_enctype(removeMaintainerForm) }}>
+                        <form id="remove-maintainer-form" class="col-sm-6 col-sm-offset-6 {{ show_remove_maintainer_form|default(false) ? '': 'hidden' }}" action="{{ path('remove_maintainer', {'name': package.name}) }}" method="POST" {{ form_enctype(removeMaintainerForm) }}>
                             <div>
-                                <h2>Remove Maintainer</h2>
+                                <h4>Remove Maintainer</h4>
                                 <p>
                                     {{ form_label(removeMaintainerForm.user, "Username") }}
                                     {{ form_errors(removeMaintainerForm.user) }}
                                     {{ form_widget(removeMaintainerForm.user) }}
                                 </p>
                                 {{ form_rest(removeMaintainerForm) }}
-                                <input id="submit-del-maintainer" type="submit" value="Submit" />
+                                <input class="btn btn-block btn-danger btn-lg" type="submit" value="Remove Maintainer" />
                             </div>
                         </form>
                     {% endif %}
@@ -174,12 +192,17 @@
             {% endif %}
 
             {% if versions|length %}
-            <div class="row">
-                <ul class="versions col-md-3 col-lg-2">
-                    {% for version in versions %}
-                        {% set expanded = version.id == expandedVersion.id %}
-                        <li class="version{% if loop.last %} last{% endif %}{% if expanded %} open{% endif %}" data-version-id="{{ version.version }}">
-                            <span class="details-toggler" data-load-more="{{ path('view_version', {versionId: version.id, _format: 'json'}) }}">
+            <div class="row versions-section">
+                <div class="version-details col-md-9">
+                    {% if version %}
+                        {% include 'PackagistWebBundle:Web:versionDetails.html.twig' with {version: expandedVersion} %}
+                    {% endif %}
+                </div>
+                <div class="col-md-3 package-aside versions-wrapper">
+                    <ul class="versions">
+                        {% for version in versions %}
+                            {% set expanded = version.id == expandedVersion.id %}
+                            <li class="details-toggler version{% if loop.last %} last{% endif %}{% if expanded %} open{% endif %}" data-version-id="{{ version.version }}" data-load-more="{{ path('view_version', {versionId: version.id, _format: 'json'}) }}">
                                 <a href="#{{ version.version }}" class="version-number">
                                     {{- version.version -}}
                                     {% if version.hasVersionAlias() %}
@@ -194,19 +217,17 @@
                                     <i class="submit glyphicon glyphicon-remove"></i>
                                 </form>
                                 {% endif %}
-                            </span>
-                        </li>
-                    {% endfor %}
-                </ul>
-                <div class="version-details col-md-9 col-lg-10">
-                    {% if version %}
-                        {% include 'PackagistWebBundle:Web:versionDetails.html.twig' with {version: expandedVersion} %}
-                    {% endif %}
+                            </li>
+                        {% endfor %}
+                    </ul>
+                    <div class="hidden versions-expander">
+                        <i class="glyphicon glyphicon-chevron-down"></i>
+                    </div>
                 </div>
             {% elseif package.crawledAt is null %}
-                <p>This package has not been crawled yet, some information is missing.</p>
+                <p class="col-xs-12">This package has not been crawled yet, some information is missing.</p>
             {% else %}
-                <p>This package has no released version yet, and little information is available.</p>
+                <p class="col-xs-12">This package has no released version yet, and little information is available.</p>
             {% endif %}
         </div>
     </div>

+ 6 - 3
src/Packagist/WebBundle/Resources/views/layout.html.twig

@@ -4,7 +4,7 @@
         <meta charset="UTF-8" />
 
         <title>{% block title %}Packagist{% endblock %}</title>
-        <meta name="description" content="The PHP package archivist." />
+        <meta name="description" content="{% block description %}The PHP Package Repository{% endblock %}" />
         <meta name="author" content="Jordi Boggiano" />
 
         <meta name="viewport" content="width=device-width, initial-scale=1.0" />
@@ -39,7 +39,7 @@
                             <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>
+                        <h1 class="navbar-brand"><a href="{{ path('home') }}">Packagist</a> <em class="hidden-sm hidden-xs">The PHP Package Repository</em></h1>
                     </div>
 
                     <div class="collapse navbar-collapse">
@@ -99,7 +99,10 @@
                             </li>
                         {%- endif %}
                             <li>
-                                <a style="background: #83c129;" href="{{ path('submit') }}">Submit Package</a>
+                                <a style="background: #83c129;" href="{{ path('browse') }}">Browse</a>
+                            </li>
+                            <li>
+                                <a style="background: #83c129;" href="{{ path('submit') }}">Submit</a>
                             </li>
                         </ul>
                     </div>

+ 31 - 27
src/Packagist/WebBundle/Resources/views/macros.html.twig

@@ -7,32 +7,36 @@
                 {% set packageUrl = path('view_providers', { 'name' : package.name }) %}
             {% endif %}
             <li data-url="{{ packageUrl }}" class="row">
-                <div class="col-sm-9 col-lg-10">
-                    <h4 class="font-bold">
-                        <a href="{{ packageUrl }}">{{ package.name }}</a>
-                        {% if package.id is not numeric or package.name == 'nelmio/alice' %}
-                            <small>(Virtual Package)</small>
-                        {% endif %}
-                        {% if showAutoUpdateWarning and not package.autoUpdated %}
-                            <small>(Not Auto-Updated)</small>
-                        {% endif %}
-                    </h4>
-                    {% if package.description is defined and package.description %}
-                        <p>{{ package.description }}</p>
-                    {% endif %}
-                    {% if package.abandoned is defined and package.abandoned %}
-                        <p class="abandoned">
-                            <i class="glyphicon glyphicon-exclamation-sign"></i> Abandoned! {% if package.replacementPackage %}See <a href="{{ path('view_package', {name: package.replacementPackage}) }}">{{ package.replacementPackage }}</a>{% endif %}
-                        </p>
-                    {% endif %}
-                </div>
-                <div class="col-sm-3 col-lg-2">
-                    {% if meta %}
-                        <p class="metadata">
-                            <span class="metadata-block"><i class="glyphicon glyphicon-arrow-down"></i> {{ meta.downloads[package.id]|default(0)|number_format(0, '.', '&#8201;')|raw }}</span>
-                            <span class="metadata-block"><i class="glyphicon glyphicon-star"></i> {{ meta.favers[package.id]|number_format(0, '.', '&#8201;')|raw }}</span>
-                        </p>
-                    {% endif %}
+                <div class="col-xs-12 package-item">
+                    <div class="row">
+                        <div class="col-sm-9 col-lg-10">
+                            <h4 class="font-bold">
+                                <a href="{{ packageUrl }}">{{ package.name }}</a>
+                                {% if package.id is not numeric or package.name == 'nelmio/alice' %}
+                                    <small>(Virtual Package)</small>
+                                {% endif %}
+                                {% if showAutoUpdateWarning and not package.autoUpdated %}
+                                    <small>(Not Auto-Updated)</small>
+                                {% endif %}
+                            </h4>
+                            {% if package.description is defined and package.description %}
+                                <p>{{ package.description }}</p>
+                            {% endif %}
+                            {% if package.abandoned is defined and package.abandoned %}
+                                <p class="abandoned">
+                                    <i class="glyphicon glyphicon-exclamation-sign"></i> Abandoned! {% if package.replacementPackage %}See <a href="{{ path('view_package', {name: package.replacementPackage}) }}">{{ package.replacementPackage }}</a>{% endif %}
+                                </p>
+                            {% endif %}
+                        </div>
+                        <div class="col-sm-3 col-lg-2">
+                            {% if meta %}
+                                <p class="metadata">
+                                    <span class="metadata-block"><i class="glyphicon glyphicon-arrow-down"></i> {{ meta.downloads[package.id]|default(0)|number_format(0, '.', '&#8201;')|raw }}</span>
+                                    <span class="metadata-block"><i class="glyphicon glyphicon-star"></i> {{ meta.favers[package.id]|number_format(0, '.', '&#8201;')|raw }}</span>
+                                </p>
+                            {% endif %}
+                        </div>
+                    </div>
                 </div>
             </li>
         {% endfor %}
@@ -52,7 +56,7 @@
             </li>
         {% endfor %}
         {% if showMoreUrl is defined and showMoreUrl %}
-            <li><a href="{{ path('browse_popular') }}"><strong>See more...</strong></a></li>
+            <li><a href="{{ path('browse_popular') }}">See more...</a></li>
         {% endif %}
     </ul>
 {% endmacro %}