Merge branch 'master' into features/feeds

This commit is contained in:
Nihad Abbasov 2011-11-14 17:42:12 +04:00
commit 8d74123d61
56 changed files with 1042 additions and 758 deletions

1
.rbenv-version Normal file
View file

@ -0,0 +1 @@
1.9.2-p290

View file

@ -1,3 +1,10 @@
v 1.2.0
- new design
- user dashboard
- markdown support for comments
- encoding issues
- wall like twitter timeline
v 1.1.0
- project dashboard
- wall redesigned

View file

@ -37,7 +37,6 @@ end
group :development, :test do
gem 'rspec-rails'
gem "shoulda", "~> 3.0.0.beta2"
gem 'capybara'
gem 'autotest'
gem 'autotest-rails'
@ -51,4 +50,5 @@ end
group :test do
gem 'turn', :require => false
gem 'simplecov', :require => false
gem "shoulda", "~> 3.0.0.beta2"
end

View file

@ -1 +1 @@
1.1.0
1.2.0

View file

@ -10,6 +10,8 @@
//= require jquery.ui.selectmenu
//= require jquery.tagify
//= require jquery.cookie
//= require raphael
//= require branch-graph
//= require_tree .
$(function(){
@ -19,8 +21,20 @@ $(function(){
$('select#branch').selectmenu({style:'popup', width:200});
$('select#tag').selectmenu({style:'popup', width:200});
$(".account-box").mouseenter(showMenu);
$(".account-box").mouseleave(resetMenu);
});
function updatePage(data){
$.ajax({type: "GET", url: location.href, data: data, dataType: "script"});
}
function showMenu() {
$(this).toggleClass('hover');
}
function resetMenu() {
$(this).removeClass("hover");
}

View file

@ -8,3 +8,34 @@
*= require_self
*= require_tree .
*/
/** COLORS **/
.cgray { color:gray; }
.cred { color:#D12F19; }
.cgreen { color:#44aa22; }
/** COMMON STYLES **/
.left {
float:left;
}
.right {
float:right;
}
.width-50p{
width:50%;
}
.width-49p{
width:49%;
}
.width-30p{
width:30%;
}
.width-65p{
width:65%;
}
.append-bottom-10 {
margin-bottom:10px;
}
.prepend-top-10 {
margin-top:10px;
}

View file

@ -0,0 +1,42 @@
/** Commit diff view **/
.diff_file {
border:1px solid #CCC;
margin-bottom:1em;
.diff_file_header {
padding:5px 5px;
border-bottom:1px solid #CCC;
background: #eee;
}
.diff_file_content {
overflow:auto;
overflow-y:hidden;
background:#fff;
color:#333;
font-size: 12px;
font-family: 'Courier New', 'andale mono','lucida console',monospace;
}
.diff_file_content_image {
background:#eee;
text-align:center;
img {
padding:100px;
max-width:300px;
}
}
}
.diff_file_content{
.old_line, .new_line {
background:#ECECEC;
color:#777;
width:30px;
float:left;
padding: 0px 5px;
border-right: 1px solid #ccc;
}
}
pre.commit_message {
white-space: pre-wrap;
}

View file

@ -1,3 +0,0 @@
// Place all the styles related to the Dashboard controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

View file

@ -1,3 +1,48 @@
// Place all the styles related to the Issues controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
.issue-number {
float: left;
border-radius: 5px;
text-shadow: none;
background: rgba(0, 0, 0, 0.12);
text-align: center;
padding: 14px 8px;
width: 40px;
margin-right: 10px;
color: #444;
}
.issues_filter {
margin-top:10px;
.left {
margin-right:15px;
}
}
.top_panel_issues{
#issue_search_form {
margin:5px 0;
input {
border:1px solid #D3D3D3;
padding: 3px;
height: 28px;
width: 300px;
-webkit-appearance:none;
box-sizing: border-box;
-moz-box-sizing: border-box;
&:focus {
border-color:#c2e1ef;
}
}
}
}
/** ISSUES LIST **/
.issue .action-links {
display:none;
a {
margin-left:10px;
}
}
.issue:hover .action-links { display:block; }

View file

@ -0,0 +1,44 @@
/** Notes **/
#notes-list {
display:block;
list-style:none;
margin:0px;
padding:0px;
}
.issue_notes {
.note_content {
float:left;
width:400px;
}
}
/* Note textare */
#note_note {
height:100px;
width:97%;
font-size:14px;
}
#new_note {
#note_note {
height:25px;
}
.attach_holder {
display:none;
}
}
#notes-list .note .delete-note { display:none; }
#notes-list .note:hover .delete-note { display:block; }
body.project-page #notes-list .note {padding: 10px; border-bottom: 1px solid #eee; overflow: hidden; display: block;}
body.project-page #notes-list .note {padding: 10px; border-bottom: 1px solid #eee; overflow: hidden; display: block;}
body.project-page #notes-list .note img{float: left; margin-right: 10px;}
body.project-page #notes-list .note span.note-title{display: block;}
body.project-page #notes-list .note span.note-title{margin-bottom: 10px}
body.project-page #notes-list .note span.note-author{color: #999; font-weight: normal; font-style: italic;}
body.project-page #notes-list .note span.note-author strong{font-weight: bold; font-style: normal;}

View file

@ -1,3 +0,0 @@
// Place all the styles related to the Profile controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

View file

@ -1,29 +1,20 @@
// Place all the styles related to the Projects controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
/** MIXINS **/
@mixin round-borders-bottom($radius) {
border-top: 1px solid #eaeaea;
-moz-border-radius-bottomright: $radius;
-moz-border-radius-bottomleft: $radius;
border-bottom-right-radius: $radius;
border-bottom-left-radius: $radius;
-webkit-border-bottom-left-radius: $radius;
-webkit-border-bottom-right-radius: $radius;
}
@mixin round-borders-top($radius) {
border-top: 1px solid #eaeaea;
-moz-border-radius-topright: $radius;
-moz-border-radius-topleft: $radius;
border-top-right-radius: $radius;
border-top-left-radius: $radius;
-webkit-border-top-left-radius: $radius;
-webkit-border-top-right-radius: $radius;
}
@ -35,46 +26,7 @@
border-radius: $radius;
}
@mixin hover-color {
background: #fff !important;
background: -webkit-gradient(linear,left top,left bottom,from(#fff),to(#FFF6BF)) !important;
background: -moz-linear-gradient(top,#fff,#FFF6BF) !important;
background: transparent 9 !important;
}
.diff_file {
border:1px solid #CCC;
margin-bottom:1em;
.diff_file_header {
padding:5px 5px;
border-bottom:1px solid #CCC;
background: #eee;
}
.diff_file_content {
overflow:auto;
overflow-y:hidden;
background:#fff;
color:#333;
font-size: 12px;
font-family: 'Courier New', 'andale mono','lucida console',monospace;
}
.diff_file_content_image {
background:#eee;
text-align:center;
img {
padding:100px;
max-width:300px;
}
}
}
#logo {
&:hover {
background:none;
}
}
/** File stat **/
.file_stats {
margin-bottom:10px;
@include round-borders-all(4px);
@ -99,85 +51,16 @@
@include round-borders-all(4px);
padding: 4px 0px;
}
table.round-borders {
float:left;
}
@mixin panel-color {
background: #111 !important;
background: -webkit-gradient(linear,left top,left bottom,from(#333),to(#111)) !important;
background: -moz-linear-gradient(top,#333,#111) !important;
background: transparent 9 !important;
}
#header-panel {
@include panel-color;
height:40px;
position:fixed;
z-index:999;
top:0px;
width:100%;
margin-bottom:10px;
overflow:hidden;
.button{
color:#bbb;
border:none;
margin:0px;
height:25px;
background:transparent;
padding:10px 20px 5px 20px;
&:hover{
color:white;
}
&.current {
border-bottom: 3px solid #EAEAEA !important;
padding: 10px 20px 0;
color: #eaeaea;
}
}
.search-holder {
float:left;
width:290px;
input {
@include round-borders-all(4px);
width:290px;
border-color:#888;
padding:5px;
background:#666;
color:#222;
&:focus {
background:#fff;
color:#000;
}
}
}
}
#content-container{
min-height:250px;
background: #fff;
@include round-borders-bottom(8px);
borders:2px solid #eaeaea;
border-top: none;
padding:20px;
}
a {
color: #111;
}
.diff_file_content{
.old_line, .new_line {
background:#ECECEC;
color:#777;
width:30px;
float:left;
padding: 0px 5px;
border-right: 1px solid #ccc;
}
}
/** FILE CONTENT VIEW **/
.view_file_content{
.old_line, .new_line {
background:#ECECEC;
@ -216,10 +99,34 @@ a {
}
}
.back_small.button{
td.code {
width: 100%;
.highlight {
margin-left: 55px;
overflow:auto;
overflow-y:hidden;
}
}
.highlight pre {
white-space: pre;
word-wrap:normal;
}
.highlighttable tr:hover {
background:white;
}
table.highlighttable pre{
line-height:16px !important;
font-size:12px !important;
}
table.highlighttable .linenodiv pre {
text-align: right;
padding-right: 4px;
}
/** PROJECTS **/
input.ssh_project_url {
padding:5px;
margin:0px;
@ -243,38 +150,7 @@ input.ssh_project_url {
clear: both;
}
.top_project_menu {
a {
border-right: 1px solid #FFFFFF;
box-shadow: -1px 0 #DDDDDD inset;
color: #666;
display: block;
font-size: 16px;
text-decoration: none;
line-height: 20px;
padding: 11px 26px 12px 24px;
text-shadow: 0 1px 0 #FFFFFF;
float:left;
&.current {
background-color: #FFFFFF;
color: #222222;
}
}
}
.top_bar {
margin-top:50px;
background-color: #F4F4F4;
@include round-borders-top(8px);
box-shadow: 0 1px #FFFFFF inset, 0 -1px #DDDDDD inset;
height: 43px;
overflow: hidden;
width:990px;
}
/** FORM INPUTS **/
.user_new,
.new_key,
.new_issue,
@ -296,7 +172,6 @@ input.ssh_project_url {
}
.input_button {
//@include round-borders-all(4px);
padding:8px;
font-size:14px;
cursor:pointer;
@ -307,36 +182,11 @@ input.ssh_project_url {
border-width: 1px;
}
.top_menu_count {
background: none repeat scroll 0 0 white;
color: #333;
border-color: #4BB8D2;
padding: 2px;
font-size:10px;
border-top:none;
text-align:center;
float:right;
width:25px;
}
#logo {
color: #EAEAEA;
font-family: monospace;
font-size: 26px;
padding: 4px;
text-decoration: none;
text-shadow: #555 1px 1px;
}
/** FLASH **/
#flash_container {
height:40px;
position:fixed;
z-index:1009;
z-index:1209;
top:0px;
width:100%;
margin-bottom:10px;
@ -354,7 +204,6 @@ input.ssh_project_url {
}
/** Buttons **/
.lbutton,
.lite_button {
display:block;
@ -386,89 +235,87 @@ input.ssh_project_url {
}
}
/** Notes **/
#notes-list {
display:block;
list-style:none;
margin:0px;
padding:0px;
}
.notes_count {
background: none repeat scroll 0 0 #FFF6BF;
border-color: #FFD324;
color: #514721;
border: 2px solid #DDDDDD;
margin-bottom: 1em;
margin-top: 3px;
padding: 2px 5px;
position: relative;
right: 6px;
top: 6px;
}
.issue_notes {
.note_content {
float:left;
width:400px;
}
}
#user_projects_limit{
width: 60px;
}
.project_thumb {
margin:20px 0;
width: 250px;
float:left;
padding:20px;
text-align:center;
p, h4 {
text-align:left;
}
.lbutton {
float:left;
}
}
.handle:hover{
cursor: move;
}
.handle{
width: 12px;
height: 12px;
padding: 10px;
}
/* Note textare */
#note_note {
height:100px;
width:97%;
font-size:14px;
}
#new_note {
#note_note {
height:25px;
}
.attach_holder {
display:none;
.project-refs-form {
span {
background: none !important;
position:static !important;
width:auto !important;
height: auto !important;
}
}
.field_with_errors {
input[type="text"],
input[type="password"],
textarea
{
background: none repeat scroll 0 0 #FFBBBB
.project-refs-select {
width:200px;
}
.filter .left { margin-right:15px; }
body.project-page table .commit {
a.tree-commit-link {
color:gray;
&:hover {
text-decoration:underline;
}
}
}
/** NEW PROJECT **/
.new-project-hodler {
.icon span { background-position: -31px -70px; }
td { border-bottom: 1px solid #DEE2E3; }
}
/** Feed entry **/
.commit,
.snippet,
.message {
.title {
color:#666;
a { color:#666 !important; }
p { margin-top:0px; }
}
.author { color: #999 }
}
/** JQuery UI **/
.ui-autocomplete { @include round-borders-all(5px); }
.ui-menu-item { cursor: pointer }
.ui-selectmenu{
@include round-borders-all(4px);
margin-right:10px;
font-size:1.5em;
height:auto;
font-weight:bold;
.ui-selectmenu-status {
padding:3px 10px;
}
}
/** Snippets **/
.new_snippet textarea,
.edit_snippet textarea {
height:300px;
padding: 8px;
width: 95%;
}
.snippet .action-links {
display:none;
a {
margin-left:10px;
}
}
.snippet:hover .action-links { display:block; }
/** ISSUES TAGS **/
.tag {
@include round-borders-all(4px);
padding:2px 4px;
@ -497,187 +344,38 @@ input.ssh_project_url {
background: #2c5c66;
color:white;
}
}
#issues-table .issue {
&.critical {
td {
//background: #D12F19;
//color:#fff;
}
&.note {
background: #2c5c66;
color:white;
}
&.issue {
background: #D12F19;
color:white;
}
&.commit {
background: #44aacc;
color:white;
}
}
.top_panel_issues{
#issue_search_form {
margin:5px 0;
input {
border:1px solid #D3D3D3;
padding: 3px;
height: 28px;
width: 300px;
-webkit-appearance:none;
box-sizing: border-box;
-moz-box-sizing: border-box;
&:focus {
border-color:#c2e1ef;
}
}
}
#holder {
border: solid 1px #999;
cursor: move;
height: 70%;
overflow: hidden;
}
.left {
float:left;
}
.right {
float:right;
}
/* Project Dashboard Page */
html, body { height: 100%; }
.width-50p{
width:50%;
}
.width-49p{
width:49%;
}
.width-30p{
width:30%;
}
.width-65p{
width:65%;
}
pre.commit_message {
white-space: pre-wrap;
}
#container {
min-height:100%;
}
.ui-selectmenu{
@include round-borders-all(4px);
margin-right:10px;
font-size:1.5em;
height:auto;
font-weight:bold;
.ui-selectmenu-status {
padding:3px 10px;
}
}
td.code {
width: 100%;
.highlight {
margin-left: 55px;
overflow:auto;
overflow-y:hidden;
}
}
.highlight pre {
white-space: pre;
word-wrap:normal;
}
.highlighttable tr:hover {
background:white;
}
table.highlighttable pre{
line-height:16px !important;
font-size:12px !important;
}
.project-refs-form {
span {
background: none !important;
position:static !important;
width:auto !important;
height: auto !important;
}
}
.project-refs-select {
width:200px;
}
.issues_filter {
margin-top:10px;
.left {
margin-right:15px;
}
}
.filter .left { margin-right:15px; }
.cgray { color:gray; }
.cred { color:#D12F19; }
.cgreen { color:#44aa22; }
body.project-page table .commit {
a.tree-commit-link {
color:gray;
&:hover {
text-decoration:underline;
}
}
}
#notes-list .note .delete-note { display:none; }
#notes-list .note:hover .delete-note { display:block; }
#issues-table-holder .issue .action-links {
display:none;
a {
margin-left:10px;
}
}
.issue-number {
float: left;
border-radius: 5px;
text-shadow: none;
background: rgba(0, 0, 0, 0.12);
text-align: center;
padding: 14px 8px;
width: 40px;
margin-right: 10px;
color: #444;
}
#issues-table-holder .issue:hover .action-links { display:block; }
body.project-page #notes-list .note {padding: 10px; border-bottom: 1px solid #eee; overflow: hidden; display: block;}
body.project-page #notes-list .note {padding: 10px; border-bottom: 1px solid #eee; overflow: hidden; display: block;}
body.project-page #notes-list .note img{float: left; margin-right: 10px;}
body.project-page #notes-list .note span.note-title{display: block;}
body.project-page #notes-list .note span.note-title{margin-bottom: 10px}
body.project-page #notes-list .note span.note-author{color: #999; font-weight: normal; font-style: italic;}
body.project-page #notes-list .note span.note-author strong{font-weight: bold; font-style: normal;}
/** NEW PROJECT **/
.new-project-hodler {
.icon span {
background-position: -31px -70px;
}
td {
border-bottom: 1px solid #DEE2E3;
}
}
//.message .note-title p { margin-bottom:0px; }
.commit,
.message {
.title {
color:#666;
a {
color:#666 !important;
}
p {
margin-top:0px;
}
}
.author {
color: #999
}
}
body.dashboard.project-page .news-feed h2{float: left;}
body.dashboard.project-page .news-feed .project-updates {margin-bottom: 20px; display: block; width: 100%;}
body.dashboard.project-page .news-feed .project-updates .data{ padding: 0}
body.dashboard.project-page .news-feed .project-updates a.project-update {padding: 10px; border-bottom: 1px solid #eee; overflow: hidden; display: block;}
body.dashboard.project-page .news-feed .project-updates a.project-update:last-child{border-bottom: 0}
body.dashboard.project-page .news-feed .project-updates a.project-update img{float: left; margin-right: 10px;}
body.dashboard.project-page .news-feed .project-updates a.project-update span.update-title, .dashboard-page .news-feed .project-updates li a span.update-author{display: block;}
body.dashboard.project-page .news-feed .project-updates a.project-update span.update-title{margin-bottom: 10px}
body.dashboard.project-page .news-feed .project-updates a.project-update span.update-author{color: #999; font-weight: normal; font-style: italic;}
body.dashboard.project-page .news-feed .project-updates a.project-update span.update-author strong{font-weight: bold; font-style: normal;}
/* eo Dashboard Page */

View file

@ -1,3 +0,0 @@
// Place all the styles related to the Snippets controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

149
app/assets/stylesheets/style.scss Executable file → Normal file
View file

@ -101,7 +101,7 @@ input.text{border: 1px solid #ccc; border-radius: 4px; display: block; padding:
/* eo Forms */
/* Tables */
table {width:100%; border: 1px solid #DEE2E3}
table {width:100%; border: 1px solid #DEE2E3; margin-bottom: 20px}
table thead{
-webkit-border-top-left-radius: 5px;
-webkit-border-top-right-radius: 5px;
@ -144,7 +144,8 @@ table tr:hover, .listed_items tr.odd:hover{background-color:#FFFFCF}
background-image: -o-linear-gradient(#f7f7f7 7.6%, #d5d5d5);
}
.button{
a.button, input.button {
font-weight: bold;
padding: 10px 20px;
text-align: center;
display: inline-block;
@ -159,6 +160,8 @@ table tr:hover, .listed_items tr.odd:hover{background-color:#FFFFCF}
background-image: -o-linear-gradient(#dbf5f6 79.4%, #c5eef0);
}
input.button{margin-bottom: 1.5em}
.button:hover {color: rgba(0,0,0,.8)}
.button.green {margin-right: 0; }
@ -301,14 +304,30 @@ body.login-page{background-color: #f1f1f1; padding-top: 10%}
/* eo Icons*/
/* Errors */
#error_explanation{background: #ffe5eb; padding: 20px; margin-bottom: 20px; border-radius: 5px}
#error_explanation h2{margin: 0; margin-bottom: 20px; color: red}
#error_explanation ul li{margin-bottom: 10px}
#error_explanation ul li:last-child{margin-bottom: 0}
.field_with_errors {
input[type="text"],
input[type="password"],
textarea
{
border: 1px solid #FFBBBB;
background: #fff4f6;
}
}
/* eo Errors */
/* General */
#container{background-color: white; overflow: hidden;}
#container{background-color: white; overflow: hidden; }
body.collapsed #container{margin: auto; width: 980px; border: 1px solid rgba(0,0,0,.22); border-top: 0; box-shadow: 0 0 0px 4px rgba(0,0,0,.04)}
/* Header */
header{background: #474D57 url('bg-header.png') repeat-x bottom; z-index: 10000; height: 44px; padding: 10px 2% 6px 2%}
header{background: #474D57 url('bg-header.png') repeat-x bottom; z-index: 10000; height: 44px; padding: 10px 2% 6px 2%; position: relative}
header a{color: white; text-shadow: 0 -1px 0 black}
header a:hover{color: #f1f1f1}
header h1{
@ -370,40 +389,14 @@ header nav a.admin{
}
header .search{ display: inline-block; float: right; margin-right: 10px}
header .search{ display: inline-block; float: right; margin-right: 46px}
header nav a span{width: 20px; height: 20px; display: inline-block; background: red; position: absolute; left: 8px; top: 6px;}
header nav a.dashboard span{background: url('images.png') no-repeat -161px 0;}
header nav a.admin span{background: url('images.png') no-repeat -184px 0;}
header nav a.project span{background: url('images.png') no-repeat -209px -1px; top: 7px}
/*
header nav a span{width: 20px; height: 20px; display: inline-block; background: red; position: absolute; left: 8px; top: 14px;}
header nav a.dashboard.current span{ background-position: -163px -22px; }
header nav a.admin.current span{ background-position: -186px -22px;}
header nav a.project.current span{ background-position: -211px -23px;}
header nav a.project span{background: url('images.png') no-repeat -209px -1px; top: 15px}
header nav a span.current{top: 18px}
header nav {margin-left: 180px; display: inline-block; float: left;}
header nav a{float: left; background: #31363e; padding: 16px 20px 20px 34px; margin-right: 10px;
-webkit-border-top-left-radius: 5px;
-webkit-border-top-right-radius: 5px;
-moz-border-radius-topleft: 5px;
-moz-border-radius-topright: 5px;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
position: relative;
}
header nav a.current{background: white; color: #333; text-shadow: none;}
*/
header .login-top{float: right; width: 180px;
background-image: -webkit-gradient(linear, 0 0, 0 62, color-stop(0.032, #464c56), to(#363c45));
@ -426,10 +419,11 @@ header{margin-bottom: 0; clear: both; }
.page-title a.grey-button{float: right;}
.right{float: right;}
/* Account box */
header .account-box{position: relative;z-index: 10000; top: -3px; width: 38px; height: 38px; font-size: 11px; float: right; display: block; cursor: pointer; }
header .account-box img{ border-radius: 4px; width: 38px; height: 38px; display: block; box-shadow: 0 1px 2px black}
header .account-box:after{
header .account-box{position: absolute; right: 0; top: 8px; z-index: 10000; width: 128px; font-size: 11px; float: right; display: block; cursor: pointer;}
header .account-box img{ border-radius: 4px; right: 20px; position: absolute; width: 38px; height: 38px; display: block; box-shadow: 0 1px 2px black}
header .account-box img:after{
content: " ";
display: block;
position: absolute;
@ -449,11 +443,38 @@ float: right;
background-origin: border-box;
}
.account-box:hover > .account-links, .account-box:hover > .arrow-up{display: block;}
header .account-links{background: white; display: none; border-radius: 5px; width: 100px; margin-top: 0; float: right; box-shadow: 0 1px 1px rgba(0,0,0,.2); }
header .account-links a{color: #666; padding: 6px 10px; display: block; text-shadow: none; border-bottom: 1px solid #eee}
header .account-links a:hover{background-color: #f1f1f1; text-shadow: none; color: #333}
.account-box.hover{height: 138px;}
.account-box:hover > .account-links{display: block;}
header .account-links{background: white; display: none; border-radius: 5px; width: 100px; margin-top: 0; float: right; box-shadow: 0 1px 1px rgba(0,0,0,.2); position:relative;}
header .account-links:before {
content: ".";
width:0;
height:0;
position:absolute;
border:5px solid transparent;
border-color:rgba(255,255,255,0);
border-bottom-color:#fafafa;
text-indent:-9999px;
top:-10px;
line-height:0;
right:10px;
z-index:10;
}
/* Inspired by http://maxvoltar.com/temp/nowplaying/ */
header .account-links{background: white; display: none; z-index: 100000; border-radius: 5px; width: 100px; position: absolute; right: 20px; top: 46px; margin-top: 0; float: right; box-shadow: 0 1px 1px rgba(0,0,0,.2); }
header .account-links a{color: #666; padding: 6px 10px; display: block; text-shadow: none; border-bottom: 1px solid #eee}
header .account-links a:hover{
background: #3aacec;
background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#39acec), to(#279ada), color-stop(.05, #4cbefe));
background: -moz-linear-gradient(top, #39acec, #4cbefe 5%, #279ada);
background: linear-gradient(top, #39acec, #4cbefe 5%, #279ada);
color: #fff;
text-shadow: #1488c8 0 -1px 0;
}
.account-box.hover .arrow-up{top: 41px; right: 6px; position: absolute}
header .account-links a:first-child{
-webkit-border-top-left-radius: 5px;
-webkit-border-top-right-radius: 5px;
@ -473,18 +494,15 @@ header .account-links a:last-child{
border-bottom: 0;
}
header a.arrow-up{
display: none;
width: 0;
height: 0;
float: right;
margin-right: 26px;
margin-bottom: 0;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-bottom: 5px solid white;
#no_ssh_key_defined {
border:1px solid #ee8801;
margin:20px;
padding:20px;
background:#ffe3f0;
h2{margin:0;}
p {margin:10px 0 0;}
}
/* eo Account Box */
input.search-input{float: left; text-shadow: none; width: 116px; background-image: url('icon-search.png') ; background-repeat: no-repeat; background-position: 10px; border-radius: 100px; border: 1px solid rgba(0,0,0,.7); box-shadow: 0 1px 0 rgba(255,255,255,.2), 0 2px 2px rgba(0,0,0,.4) inset ; background-color: #D2D5DA; background-color: rgba(255,255,255,.5); padding: 5px; padding-left: 26px; margin-top: 4px; margin-right: 10px }
input.search-input:focus{ background-color: white; width: 216px;}
@ -506,9 +524,9 @@ body.dashboard-page header{margin-bottom: 0}
body.dashboard-page .news-feed{padding-left: 1em; margin-right: 450px; margin-left: 1%}
body.dashboard-page .dashboard-content{ position: relative; float: left; width: 100%; height: 100%; }
body.dashboard-page .news-feed h2{float: left;}
body.dashboard-page aside{ width: 420px; float: right; right: 0; height: 100%; bottom: 0; position: absolute; background-color: #f7f7f7; border-left: 1px solid #ccc }
body.dashboard-page aside{ min-height: 700px; width: 420px; float: right; background-color: #f7f7f7; border-left: 1px solid #ccc }
body.dashboard-page aside h4{margin: 0; border-bottom: 1px solid #ccc; padding: 10px 10px; font-size: 11px; font-weight: bold; text-transform: uppercase;}
body.dashboard-page aside h4 a.button-small{float: right; text-transform: none; border-radius: 4px; margin-right: 4%; margin-top: -4px; display: block;}
body.dashboard-page aside h4 a.button-small{float: right; text-transform: none; border-radius: 4px; margin-right: 2%; margin-top: -4px; display: block;}
body.dashboard-page aside .project-list {list-style: none; margin: 0; padding: 0;}
body.dashboard-page aside .project-list li a {background: white; color: #{$blue_link}; display: block; border-bottom: 1px solid #eee; padding: 14px 6% 14px 14px;}
body.dashboard-page aside .project-list li a:hover {background: #f1f1f1}
@ -548,8 +566,16 @@ body.project-page h2.icon .project-name i.arrow{float: right;
body.project-page h2.icon span{ background-position: -78px -68px; }
body.project-page .project-container{ position: relative; float: left; width: 100%; height: 100%; }
body.project-page .page-title{margin-bottom: 0}
body.project-page .project-sidebar {width: 220px; left: 0; top: 0; height: 100%; bottom: 0; position: absolute; background-color: #f7f7f7; border-left: 1px solid #ccc; float: left; display: inline-block; background: #f7f7f7; padding: 20px 0 20px 2%; margin: 0; }
body.project-page .project-sidebar input.text.git-url{ font-size: 12px; border-radius: 5px; color: #666; box-shadow: 0 1px 2px rgba(0,0,0,.2) inset; padding: 8px 14px 8px 30px; margin-bottom: 20px; background: white url('images.png') no-repeat 8px -40px;}
body.project-page .project-sidebar {width: 220px; left: 0; top: 0; height: 100%; bottom: 0; position: absolute; background-color: #f7f7f7; float: left; display: inline-block; background: #f7f7f7; padding: 20px 0 20px 2%; margin: 0; }
body.project-page input.text.git-url,
body.projects-page input.text.git-url { font-size: 12px; border-radius: 5px; color: #666; box-shadow: 0 1px 2px rgba(0,0,0,.2) inset; padding: 8px 14px 8px 30px; margin-bottom: 20px; background: white url('images.png') no-repeat 8px -40px;}
body.projects-page input.text.git-url {margin:10px 0 0 }
.git_url_wrapper { margin-right:50px }
.projects_selector:hover > .project-box{ -moz-box-shadow:0px 0px 10px rgba(0, 0, 0, .1); -webkit-box-shadow:0px 0px 10px rgba(0, 0, 0, .1); box-shadow:0px 0px 10px rgba(0, 0, 0, .1); }
body.project-page .project-sidebar aside{width: 219px}
body.project-page .project-sidebar aside a{display: block; position: relative; background: white; padding: 15px 10px; border-bottom: 1px solid #eee}
body.project-page .project-sidebar aside a:first-child{
@ -565,13 +591,18 @@ body.project-page .project-sidebar aside a:first-child{
body.project-page .project-sidebar aside a:hover{background-color: #eee;}
body.project-page .project-sidebar aside a span.number{float: right; border-radius: 5px; text-shadow: none; background: rgba(0,0,0,.12); text-align: center; padding: 5px 8px; position: absolute; top: 10px; right: 10px}
body.project-page .project-sidebar aside a.current{background-color: #79c3e0; color: white; text-shadow: none; border-color: transparent}
body.project-page .project-content{ padding: 20px; display: block; margin-left: 250px }
body.project-page .project-content{ padding: 20px; display: block; margin-left: 250px; min-height: 400px}
body.project-page .project-content h2{ margin-top: 6px}
body.project-page .project-content .button.right{margin-left: 20px}
body.project-page table .commit a{color: #{$blue_link}}
body.project-page table th, body.project-page table td{ border-bottom: 1px solid #DEE2E3;}
body.project-page .fixed{position: fixed; }
/* New project Page */
.new-project-page .container{width: 600px; background-color: rgba(0,0,0,.02); margin: auto; border: 1px solid #eee; padding: 0 20px; margin: 30px auto 60px auto; border-radius: 5px}
.new-project-page .container table{background: white}
/* eo New Project Page */
/* Commit Page */
body.project-page.commits-page .commit-info{float: right;}
body.project-page.commits-page .commit-info data{
@ -670,3 +701,13 @@ body.projects-page .browse-code{margin-right: 10px}
p, h2, h3 { orphans: 3; widows: 3; }
h2, h3 { page-break-after: avoid; }
}
/**
* author:DZ
* date: Nov 09
* fix different fonts for firefox & webkit
*/
body, button, input, select, textarea {
font-family: "Helvetica", sans-serif;
}

View file

@ -1,9 +1,6 @@
class ApplicationController < ActionController::Base
before_filter :authenticate_user!
before_filter :view_style
protect_from_forgery
helper_method :abilities, :can?
rescue_from Gitosis::AccessDenied do |exception|
@ -64,7 +61,7 @@ class ApplicationController < ActionController::Base
else
@branch = params[:branch].blank? ? nil : params[:branch]
@tag = params[:tag].blank? ? nil : params[:tag]
@ref = @branch || @tag || "master"
@ref = @branch || @tag || Repository.default_ref
end
end
@ -76,20 +73,6 @@ class ApplicationController < ActionController::Base
redirect_to @project unless @project.repo_exists?
end
def view_style
if params[:view_style] == "collapsed"
cookies[:view_style] = "collapsed"
elsif params[:view_style] == "fluid"
cookies[:view_style] = ""
end
@view_mode = if cookies[:view_style] == "collapsed"
:fixed
else
:fluid
end
end
def respond_with_notes
if params[:last_id] && params[:first_id]
@notes = @notes.where("id >= ?", params[:first_id])

View file

@ -8,18 +8,18 @@ class CommitsController < ApplicationController
before_filter :add_project_abilities
before_filter :authorize_read_project!
before_filter :require_non_empty_project
before_filter :load_refs, :only => :index # load @branch, @tag & @ref
def index
load_refs # load @branch, @tag & @ref
@repo = project.repo
limit, offset = (params[:limit] || 20), (params[:offset] || 0)
if params[:path]
@commits = @repo.log(@ref, params[:path], :max_count => limit, :skip => offset)
else
@commits = @repo.commits(@ref, limit, offset)
end
@commits = if params[:path]
@repo.log(@ref, params[:path], :max_count => limit, :skip => offset)
else
@repo.commits(@ref, limit, offset)
end
respond_to do |format|
format.html # index.html.erb
@ -30,8 +30,8 @@ class CommitsController < ApplicationController
def show
@commit = project.repo.commits(params[:id]).first
@notes = project.notes.where(:noteable_id => @commit.id, :noteable_type => "Commit").order("created_at DESC").limit(20)
@note = @project.notes.new(:noteable_id => @commit.id, :noteable_type => "Commit")
@notes = project.commit_notes(@commit).fresh.limit(20)
@note = @project.build_commit_note(@commit)
respond_to do |format|
format.html

View file

@ -1,3 +1,5 @@
require File.join(Rails.root, 'lib', 'graph_commit')
class ProjectsController < ApplicationController
before_filter :project, :except => [:index, :new, :create]
layout :determine_layout
@ -6,8 +8,8 @@ class ProjectsController < ApplicationController
before_filter :add_project_abilities
before_filter :authorize_read_project!, :except => [:index, :new, :create]
before_filter :authorize_admin_project!, :only => [:edit, :update, :destroy]
before_filter :require_non_empty_project, :only => [:blob, :tree]
before_filter :load_refs, :only => :tree # load @branch, @tag & @ref
def index
source = current_user.projects
@ -64,21 +66,8 @@ class ProjectsController < ApplicationController
def show
return render "projects/empty" unless @project.repo_exists?
@date = case params[:view]
when "week" then Date.today - 7.days
when "day" then Date.today
else nil
end
if @date
@date = @date.at_beginning_of_day
@commits = @project.commits_since(@date)
@messages = project.notes.since(@date).order("created_at DESC")
else
@commits = @project.fresh_commits
@messages = project.notes.fresh.limit(10)
end
limit = (params[:limit] || 40).to_i
@activities = @project.updates(limit)
end
#
@ -101,15 +90,13 @@ class ProjectsController < ApplicationController
#
def tree
load_refs # load @branch, @tag & @ref
@repo = project.repo
if params[:commit_id]
@commit = @repo.commits(params[:commit_id]).first
else
@commit = @repo.commits(@ref || "master").first
end
@commit = if params[:commit_id]
@repo.commits(params[:commit_id]).first
else
@repo.commits(@ref).first
end
@tree = @commit.tree
@tree = @tree / params[:path] if params[:path]
@ -127,6 +114,34 @@ class ProjectsController < ApplicationController
return render_404
end
def graph
@repo = project.repo
commits = Grit::Commit.find_all(@repo, nil, {:max_count => 650})
ref_cache = {}
commits.collect! do |commit|
add_refs(commit, ref_cache)
GraphCommit.new(commit)
end
days = GraphCommit.index_commits(commits)
@days_json = days.compact.collect{|d| [d.day, d.strftime("%b")] }.to_json
@commits_json = commits.collect do |c|
h = {}
h[:parents] = c.parents.collect do |p|
[p.id,0,0]
end
h[:author] = c.author.name.force_encoding("UTF-8")
h[:time] = c.time
h[:space] = c.space
h[:refs] = c.refs.collect{|r|r.name}.join(" ") unless c.refs.nil?
h[:id] = c.sha
h[:date] = c.date
h[:message] = c.message.force_encoding("UTF-8")
h[:login] = c.author.email
h
end.to_json
end
def blob
@repo = project.repo
@commit = project.commit(params[:commit_id])
@ -151,6 +166,17 @@ class ProjectsController < ApplicationController
protected
def add_refs(commit, ref_cache)
if ref_cache.empty?
@repo.refs.each do |ref|
ref_cache[ref.commit.id] ||= []
ref_cache[ref.commit.id] << ref
end
end
commit.refs = ref_cache[commit.id] if ref_cache.include? commit.id
commit.refs ||= []
end
def project
@project ||= Project.find_by_code(params[:id])
end

View file

@ -1,11 +1,13 @@
require 'digest/md5'
module ApplicationHelper
def gravatar_icon(user_email)
"http://www.gravatar.com/avatar/#{Digest::MD5.hexdigest(user_email)}?s=40&d=identicon"
gravatar_host = request.ssl? ? "https://secure.gravatar.com" : "http://www.gravatar.com"
"#{gravatar_host}/avatar/#{Digest::MD5.hexdigest(user_email)}?s=40&d=identicon"
end
def fixed_mode?
@view_mode == :fixed
true
end
def body_class(default_class = nil)
@ -13,7 +15,7 @@ module ApplicationHelper
default_class :
content_for(:body_class)
[main, cookies[:view_style]].join(" ")
[main, "collapsed"].join(" ")
end
def commit_name(project, commit)

View file

@ -1,7 +1,7 @@
module DashboardHelper
def dashboard_feed_path(project, object)
case object.class.name.to_s
when "Issue" then project_issues_path(project, project.issues.find(object.id))
when "Issue" then project_issue_path(project, project.issues.find(object.id))
when "Grit::Commit" then project_commit_path(project, project.repo.commits(object.id).first)
when "Note"
then
@ -19,12 +19,15 @@ module DashboardHelper
end
def dashboard_feed_title(object)
title = case object.class.name.to_s
klass = object.class.to_s.split("::").last
title = case klass
when "Note" then markdown(object.note)
when "Issue" then object.title
when "Grit::Commit" then object.safe_message
when "Commit" then object.safe_message
else return "Project Wall"
end
"[#{object.class.name}] #{truncate(sanitize(title, :tags => []), :length => 60)} "
"[#{klass}] #{truncate(sanitize(title, :tags => []), :length => 60)} "
end
end

View file

@ -1,12 +1,12 @@
module IssuesHelper
def sort_class
if can?(current_user, :admin_issue, @project) && (!params[:f] || params[:f] == "0")
"handle"
end
end
def project_issues_filter_path project, params = {}
params[:f] ||= cookies['issue_filter']
project_issues_path project, params
end
def sort_class
if can?(current_user, :admin_issue, @project) && (!params[:f] || params[:f] == "0")
"handle"
end
end
def project_issues_filter_path project, params = {}
params[:f] ||= cookies['issue_filter']
project_issues_path project, params
end
end

View file

@ -1,15 +1,14 @@
module TagsHelper
def tag_path tag
"/tags/#{tag}"
end
def tag_path tag
"/tags/#{tag}"
end
def tag_list project
html = ''
project.tag_list.each do |tag|
html += link_to tag, tag_path(tag)
end
html.html_safe
end
def tag_list project
html = ''
project.tag_list.each do |tag|
html += link_to tag, tag_path(tag)
end
html.html_safe
end
end

View file

@ -54,5 +54,6 @@ end
# updated_at :datetime
# closed :boolean default(FALSE), not null
# position :integer default(0)
# critical :boolean default(FALSE), not null
#

View file

@ -38,7 +38,7 @@ end
# Table name: notes
#
# id :integer not null, primary key
# note :string(255)
# note :text
# noteable_id :string(255)
# noteable_type :string(255)
# author_id :integer

View file

@ -46,6 +46,25 @@ class Project < ActiveRecord::Base
scope :public_only, where(:private_flag => false)
def repository
@repository ||= Repository.new(self)
end
delegate :repo,
:url_to_repo,
:path_to_repo,
:update_gitosis_project,
:destroy_gitosis_project,
:tags,
:repo_exists?,
:commit,
:commits,
:tree,
:heads,
:commits_since,
:fresh_commits,
:to => :repository, :prefix => nil
def to_param
code
end
@ -59,16 +78,12 @@ class Project < ActiveRecord::Base
notes.where(:noteable_type => ["", nil])
end
def update_gitosis_project
Gitosis.new.configure do |c|
c.update_project(path, gitosis_writers)
end
def build_commit_note(commit)
notes.new(:noteable_id => commit.id, :noteable_type => "Commit")
end
def destroy_gitosis_project
Gitosis.new.configure do |c|
c.destroy_project(self)
end
def commit_notes(commit)
notes.where(:noteable_id => commit.id, :noteable_type => "Commit")
end
def add_access(user, *access)
@ -106,26 +121,6 @@ class Project < ActiveRecord::Base
private_flag
end
def url_to_repo
"#{GITOSIS["git_user"]}@#{GITOSIS["host"]}:#{path}.git"
end
def path_to_repo
GITOSIS["base_path"] + path + ".git"
end
def repo
@repo ||= Grit::Repo.new(path_to_repo)
end
def tags
repo.tags.map(&:name).sort.reverse
end
def repo_exists?
repo rescue false
end
def last_activity
updates(1).first
rescue
@ -146,48 +141,6 @@ class Project < ActiveRecord::Base
end[0...n]
end
def commit(commit_id = nil)
if commit_id
repo.commits(commit_id).first
else
repo.commits.first
end
end
def heads
@heads ||= repo.heads
end
def fresh_commits(n = 10)
commits = heads.map do |h|
repo.commits(h.name, n)
end.flatten.uniq { |c| c.id }
commits.sort! do |x, y|
y.committed_date <=> x.committed_date
end
commits[0...n]
end
def commits_since(date)
commits = heads.map do |h|
repo.log(h.name, nil, :since => date)
end.flatten.uniq { |c| c.id }
commits.sort! do |x, y|
y.committed_date <=> x.committed_date
end
commits
end
def tree(fcommit, path = nil)
fcommit = commit if fcommit == :head
tree = fcommit.tree
path ? (tree / path) : tree
end
def check_limit
unless owner.can_create_project?
errors[:base] << ("Your own projects limit is #{owner.projects_limit}! Please contact administrator to increase it")

97
app/models/repository.rb Normal file
View file

@ -0,0 +1,97 @@
class Repository
attr_accessor :project
def self.default_ref
"master"
end
def initialize(project)
@project = project
end
def path
@path ||= project.path
end
def project_id
project.id
end
def repo
@repo ||= Grit::Repo.new(project.path_to_repo)
end
def url_to_repo
if !GITOSIS["port"] or GITOSIS["port"] == 22
"#{GITOSIS["git_user"]}@#{GITOSIS["host"]}:#{path}.git"
else
"ssh://#{GITOSIS["git_user"]}@#{GITOSIS["host"]}:#{GITOSIS["port"]}/#{path}.git"
end
end
def path_to_repo
GITOSIS["base_path"] + path + ".git"
end
def update_gitosis_project
Gitosis.new.configure do |c|
c.update_project(path, project.gitosis_writers)
end
end
def destroy_gitosis_project
Gitosis.new.configure do |c|
c.destroy_project(@project)
end
end
def repo_exists?
repo rescue false
end
def tags
repo.tags.map(&:name).sort.reverse
end
def heads
@heads ||= repo.heads
end
def tree(fcommit, path = nil)
fcommit = commit if fcommit == :head
tree = fcommit.tree
path ? (tree / path) : tree
end
def commit(commit_id = nil)
if commit_id
repo.commits(commit_id).first
else
repo.commits.first
end
end
def fresh_commits(n = 10)
commits = heads.map do |h|
repo.commits(h.name, n).each { |c| c.head = h }
end.flatten.uniq { |c| c.id }
commits.sort! do |x, y|
y.committed_date <=> x.committed_date
end
commits[0...n]
end
def commits_since(date)
commits = heads.map do |h|
repo.log(h.name, nil, :since => date).each { |c| c.head = h }
end.flatten.uniq { |c| c.id }
commits.sort! do |x, y|
y.committed_date <=> x.committed_date
end
commits
end
end

View file

@ -16,6 +16,10 @@ class User < ActiveRecord::Base
:foreign_key => :author_id,
:dependent => :destroy
has_many :notes,
:foreign_key => :author_id,
:dependent => :destroy
has_many :assigned_issues,
:class_name => "Issue",
:foreign_key => :assignee_id,

View file

@ -27,12 +27,13 @@
%a.project-update{:href => dashboard_feed_path(project, update)}
= image_tag gravatar_icon(update.author_email), :class => "left", :width => 40
%span.update-title
- if update.kind_of?(Grit::Commit)
%span.right.tag.commit= update.head.name
= dashboard_feed_title(update)
%span.update-author
%strong= update.author_name
authored
= time_ago_in_words(update.created_at)
ago
%br
/ #news-feed
/ #dashboard-content

View file

@ -8,15 +8,11 @@
<%= image_tag gravatar_icon(current_user.email) %>
<% end %>
<a href="#" class="arrow-up"></a>
<div class="account-links">
<%= link_to profile_path, :class => "username" do %>
<%#= current_user.name %>
Your profile
My profile
<% end %>
<%= link_to "Fluid layout", url_for( :view_style => 'fluid' ) if cookies[:view_style] == "collapsed"%>
<%= link_to "Fixed layout", url_for( :view_style => 'collapsed' ) unless cookies[:view_style] == "collapsed"%>
<%= link_to 'Logout', destroy_user_session_path, :class => "logout", :method => :delete %>
</div>
</div><!-- .account-box -->
@ -50,3 +46,11 @@
});
<% end %>
<% end %>
<% if current_user.keys.all.empty? %>
<div id="no_ssh_key_defined">
<h2>ATTENTION!</h2>
<p>No SSH Key is defined. You won't be able to use any Git command!
<p>Click <%=link_to( 'here', keys_path ) %> to add one!
</div>
<% end %>

View file

@ -20,11 +20,13 @@
.project-container
.project-sidebar
.fixed
%input.git-url.text{:id => "", :name => "", :readonly => "", :type => "text", :value => @project.url_to_repo, :class => "one_click_select"}
.git_url_wrapper
%input.git-url.text{:id => "", :name => "", :readonly => "", :type => "text", :value => @project.url_to_repo, :class => "one_click_select"}
%aside
= link_to "History", project_path(@project), :class => current_page?(:controller => "projects", :action => "show", :id => @project) ? "current" : nil
= link_to "Activities", project_path(@project), :class => current_page?(:controller => "projects", :action => "show", :id => @project) ? "current" : nil
= link_to "Tree", tree_project_path(@project), :class => current_page?(:controller => "projects", :action => "tree", :id => @project) ? "current" : nil
= link_to "Commits", project_commits_path(@project), :class => current_page?(:controller => "commits", :action => "index", :project_id => @project) ? "current" : nil
= link_to "Network graph", graph_project_path(@project), :class => current_page?(:controller => "projects", :action => "graph", :id => @project) ? "current" : nil
= link_to team_project_path(@project), :class => (current_page?(:controller => "projects", :action => "team", :id => @project) || controller.controller_name == "team_members") ? "current" : nil do
Team
- if @project.users_projects.count > 0

View file

@ -28,4 +28,4 @@
.clear
%br
= f.submit 'Add note', :class => "lbutton vm", :id => "submit_note"
= f.submit 'Add note', :class => "button", :id => "submit_note"

View file

@ -0,0 +1,15 @@
%a.project-update{:href => dashboard_feed_path(project, update)}
= image_tag gravatar_icon(update.author_email), :class => "left", :width => 40
%span.update-title
= dashboard_feed_title(update)
%span.update-author
%strong= update.author_name
authored
= time_ago_in_words(update.created_at)
ago
.right
- klass = update.class.to_s.split("::").last.downcase
%span.tag{ :class => klass }= klass
- if update.kind_of?(Grit::Commit)
%span.tag.commit= update.head.name

View file

@ -1,32 +0,0 @@
%table
%thead
%th
Commits
.filter.right
= form_tag project_path(@project), :method => :get, :class => "right" do
.left
= radio_button_tag :view, "recent", (params[:view] || "recent") == "recent", :onclick => "this.form.submit()", :id => "recent_view"
= label_tag "recent_view","Recent"
.left
= radio_button_tag :view, "day", params[:view] == "day", :onclick => "this.form.submit()", :id => "day_view"
= label_tag "day_view","Today"
.left
= radio_button_tag :view, "week", params[:view] == "week", :onclick => "this.form.submit()", :id => "week_view"
= label_tag "week_view","Week"
- @commits.each do |commit|
%tr
%td
%div.commit
- if commit.author.email
= image_tag gravatar_icon(commit.author.email), :class => "left", :width => 40, :style => "padding-right:5px;"
- else
= image_tag "no_avatar.png", :class => "left", :width => 40, :style => "padding-right:5px;"
.title
%p= link_to truncate(commit.safe_message, :length => fixed_mode? ? 40 : 100), project_commit_path(@project, :id => commit.id)
%span
%span.author
%strong= commit.author.name.force_encoding("UTF-8")
%cite.cgray
= time_ago_in_words(commit.committed_date)
ago

View file

@ -1,27 +0,0 @@
- @messages.group_by{ |x| [x.noteable_id, x.noteable_type]}.each do |item, notes|
- id, type = item[0], item[1]
- parent = load_note_parent(id, type, @project)
- next unless parent
%table
%thead
%th
%div{ :class => "recent_message_parent"}
= link_to(truncate(dashboard_feed_title(parent), :length => fixed_mode? ? 40 : 100 ), dashboard_feed_path(@project, parent))
- notes.sort {|x,y| y.updated_at <=> x.updated_at }.each do |note|
%tr
%td
%div.message
= image_tag gravatar_icon(note.author_email), :class => "left", :width => 40, :style => "padding-right:5px;"
%div.title
= link_to markdown(truncate(note.note, :length => fixed_mode? ? 40 : 100)), dashboard_feed_path(@project, parent) + "#note_#{note.id}"
- if note.attachment.url
%br
Attachment:
= link_to note.attachment_identifier, note.attachment.url
%div.author
%strong= note.author_name
%cite.cgray
= time_ago_in_words(note.updated_at)
ago
%br

View file

@ -1,13 +1,13 @@
- @projects.in_groups_of(3, false) do |projects|
- projects.each_with_index do |project, i|
%div.grid_1
%div.grid_1.projects_selector
%div{ :class => "project-box ui-box ui-box-big" }
= link_to project_path(project) do
%h3= project.name
%h3= truncate(project.name, :length => 20)
.data
%p.title.repository
%p.title.repository.git_url_wrapper
%span Repository:
= project.url_to_repo
%input{ :value => project.url_to_repo, :class => ['git-url', 'one_click_select', 'text', 'project_list_url'], :readonly => 'readonly' }
%p.title.activity
%span Last Activity:
- last_note = project.notes.last

View file

@ -12,7 +12,7 @@
= time_ago_in_words(content_commit.committed_date)
ago
%td.commit
= link_to truncate(content_commit.safe_message, :length => fixed_mode? ? 40 : 80), project_commit_path(@project, content_commit), :class => "tree-commit-link"
= link_to truncate(content_commit.safe_message, :length => 40), project_commit_path(@project, content_commit), :class => "tree-commit-link"
- tm = @project.team_member_by_name_or_email(content_commit.author_email, content_commit.author_name)
- if tm
= link_to "[#{tm.user_name}]", project_team_member_path(@project, tm)

View file

@ -28,7 +28,7 @@ eos
<h2>Existing Git Repo?</h2>
<% exist_repo_setup_str = <<eos
cd existing_git_repo
git remote add origin #{@project.url_to_repo}
git remote add origin #{@project.url_to_repo}
git push -u origin master
eos
%>

View file

@ -0,0 +1,9 @@
#holder.graph
:javascript
var chunk1={commits:#{@commits_json}};
var days=#{@days_json};
initGraph();
$(function(){
branchGraph($("#holder")[0]);
});

View file

@ -1,8 +1,10 @@
.new-project-hodler
.container_4
%h2.icon
%span
New Project
- content_for(:body_class, "new-project-page")
- content_for(:page_title) do
.new-project-hodler
.container
%h2.icon
%span
New Project
%div.clear
= render 'form'
%div.clear
= render 'form'

View file

@ -1,8 +1,12 @@
.left.width-49p
=render "projects/recent_commits"
- content_for(:body_class, "project-page dashboard")
.right.width-49p
=render "projects/recent_messages"
#news-feed.news-feed
%h2.icon
%span>
Activities
.project-box.project-updates.ui-box.ui-box-small.ui-box-big
- @activities.each do |update|
= render "projects/feed", :update => update, :project => @project
:javascript
function updateDashboard(){

View file

@ -19,7 +19,8 @@
%td{:colspan => 2}
= f.label :content, "Code"
%br
= f.text_area :content, :style => "height:240px;width:932px;"
%br
= f.text_area :content
.actions.prepend-top
= f.submit 'Save', :class => "lbutton vm"
= f.submit 'Save', :class => "button"

View file

@ -2,11 +2,17 @@
%tr{ :id => dom_id(snippet), :class => "snippet", :url => project_snippet_path(@project, snippet) }
%td
= image_tag gravatar_icon(snippet.author.email), :class => "left", :width => 40, :style => "padding:0 5px;"
= truncate snippet.author.name, :lenght => 20
%td= html_escape snippet.title
%td= html_escape snippet.file_name
%td
- if can?(current_user, :admin_snippet, @project) || snippet.author == current_user
= link_to 'Edit', edit_project_snippet_path(@project, snippet), :class => "lbutton positive"
- if can?(current_user, :admin_snippet, @project) || snippet.author == current_user
= link_to 'Destroy', [@project, snippet], :confirm => 'Are you sure?', :method => :delete, :remote => true, :class => "lbutton delete-snippet negative", :id => "destroy_snippet_#{snippet.id}"
%span
%strong= html_escape snippet.title
%br
%br
%div.author
%strong= truncate snippet.author.name, :lenght => 20
%cite.cgray
= time_ago_in_words(snippet.updated_at)
ago
.right.action-links
- if can?(current_user, :admin_snippet, @project) || snippet.author == current_user
= link_to 'Edit', edit_project_snippet_path(@project, snippet), :class => "cgray"
- if can?(current_user, :admin_snippet, @project) || snippet.author == current_user
= link_to 'Destroy', [@project, snippet], :confirm => 'Are you sure?', :method => :delete, :remote => true, :class => "cred delete-snippet negative", :id => "destroy_snippet_#{snippet.id}"

View file

@ -1,13 +1,10 @@
%div
- if can? current_user, :write_snippet, @project
.left= link_to 'New Snippet', new_project_snippet_path(@project), :class => "lbutton vm"
= link_to 'New Snippet', new_project_snippet_path(@project), :class => "button append-bottom-10"
%table.round-borders#snippets-table
%tr
%th Author
%th Title
%th File name
%th
%thead
%th
= render @snippets.fresh
:javascript
$('.delete-snippet').live('ajax:success', function() {

View file

@ -2,3 +2,4 @@ admin_uri: git@localhost:gitosis-admin.git
base_path: /home/git/repositories/
host: localhost
git_user: git
# port: 22

View file

@ -34,6 +34,7 @@ Gitlab::Application.routes.draw do
get "blob"
get "team"
get "wall"
get "graph"
# tree viewer
get "tree/:commit_id" => "projects#tree"

View file

@ -0,0 +1,178 @@
var commits = {},
comms = {},
pixelsX = [],
pixelsY = [],
mmax = Math.max,
mtime = 0,
mspace = 0,
parents = {},
ii = 0,
colors = ["#000"];
function initGraph(){
commits = chunk1.commits;
ii = commits.length;
for (var i = 0; i < ii; i++) {
for (var j = 0, jj = commits[i].parents.length; j < jj; j++) {
parents[commits[i].parents[j][0]] = true;
}
mtime = Math.max(mtime, commits[i].time);
mspace = Math.max(mspace, commits[i].space);
}
mtime = mtime + 4;
mspace = mspace + 10;
for (i = 0; i < ii; i++) {
if (commits[i].id in parents) {
commits[i].isParent = true;
}
comms[commits[i].id] = commits[i];
}
for (var k = 0; k < mspace; k++) {
colors.push(Raphael.getColor());
}
}
function branchGraph(holder) {
var ch = mspace * 20 + 20, cw = mtime * 20 + 20,
r = Raphael("holder", cw, ch),
top = r.set();
var cuday = 0, cumonth = "";
r.rect(0, 0, days.length * 20 + 20, 20).attr({fill: "#474D57"});
r.rect(0, 20, days.length * 20 + 20, 20).attr({fill: "#f7f7f7"});
for (mm = 0; mm < days.length; mm++) {
if(days[mm] != null){
if(cuday != days[mm][0]){
r.text(10 + mm * 20, 30, days[mm][0]).attr({font: "12px Fontin-Sans, Arial", fill: "#444"});
cuday = days[mm][0]
}
if(cumonth != days[mm][1]){
r.text(10 + mm * 20, 10, days[mm][1]).attr({font: "12px Fontin-Sans, Arial", fill: "#444"});
cumonth = days[mm][1]
}
}
}
for (i = 0; i < ii; i++) {
var x = 10 + 20 * commits[i].time,
y = 70 + 20 * commits[i].space;
r.circle(x, y, 3).attr({fill: colors[commits[i].space], stroke: "none"});
if (commits[i].refs != null && commits[i].refs != "") {
var longrefs = commits[i].refs
var shortrefs = commits[i].refs;
if (shortrefs.length > 15){
shortrefs = shortrefs.substr(0,13) + "...";
}
var t = r.text(x+5, y+5, shortrefs).attr({font: "12px Fontin-Sans, Arial", fill: "#666",
title: longrefs, cursor: "pointer", rotation: "90"});
var textbox = t.getBBox();
t.translate(textbox.height/-4,textbox.width/2);
}
for (var j = 0, jj = commits[i].parents.length; j < jj; j++) {
var c = comms[commits[i].parents[j][0]];
if (c) {
var cx = 10 + 20 * c.time,
cy = 70 + 20 * c.space;
if (c.space == commits[i].space) {
r.path("M" + (x - 5) + "," + (y + .0001) + "L" + (15 + 20 * c.time) + "," + (y + .0001))
.attr({stroke: colors[c.space], "stroke-width": 2});
} else if (c.space < commits[i].space) {
r.path(["M", x - 5, y + .0001, "l-5-2,0,4,5,-2C",x-5,y,x -17, y+2, x -20, y-10,"L", cx,y-10,cx , cy])
.attr({stroke: colors[commits[i].space], "stroke-width": 2});
} else {
r.path(["M", x-5, y, "l-5-2,0,4,5,-2C",x-5,y,x -17, y-2, x -20, y+10,"L", cx,y+10,cx , cy])
.attr({stroke: colors[commits[i].space], "stroke-width": 2});
}
}
}
(function (c, x, y) {
top.push(r.circle(x, y, 10).attr({fill: "#000", opacity: 0, cursor: "pointer"})
.hover(function () {
var s = r.text(100, 100,c.author + "\n \n" +c.id + "\n \n" + c.message).attr({fill: "#fff"});
this.popup = r.popupit(x, y + 5, s, 0);
top.push(this.popup.insertBefore(this));
}, function () {
this.popup && this.popup.remove() && delete this.popup;
}));
}(commits[i], x, y));
}
top.toFront();
var hw = holder.offsetWidth,
hh = holder.offsetHeight,
v = r.rect(hw - 8, 0, 4, Math.pow(hh, 2) / ch, 2).attr({fill: "#000", opacity: 0}),
h = r.rect(0, hh - 8, Math.pow(hw, 2) / cw, 4, 2).attr({fill: "#000", opacity: 0}),
bars = r.set(v, h),
drag,
dragger = function (e) {
if (drag) {
e = e || window.event;
holder.scrollLeft = drag.sl - (e.clientX - drag.x);
holder.scrollTop = drag.st - (e.clientY - drag.y);
}
};
holder.onmousedown = function (e) {
e = e || window.event;
drag = {x: e.clientX, y: e.clientY, st: holder.scrollTop, sl: holder.scrollLeft};
document.onmousemove = dragger;
bars.animate({opacity: .5}, 300);
};
document.onmouseup = function () {
drag = false;
document.onmousemove = null;
bars.animate({opacity: 0}, 300);
};
holder.scrollLeft = cw;
};
Raphael.fn.popupit = function (x, y, set, dir, size) {
dir = dir == null ? 2 : dir;
size = size || 5;
x = Math.round(x);
y = Math.round(y);
var bb = set.getBBox(),
w = Math.round(bb.width / 2),
h = Math.round(bb.height / 2),
dx = [0, w + size * 2, 0, -w - size * 2],
dy = [-h * 2 - size * 3, -h - size, 0, -h - size],
p = ["M", x - dx[dir], y - dy[dir], "l", -size, (dir == 2) * -size, -mmax(w - size, 0), 0, "a", size, size, 0, 0, 1, -size, -size,
"l", 0, -mmax(h - size, 0), (dir == 3) * -size, -size, (dir == 3) * size, -size, 0, -mmax(h - size, 0), "a", size, size, 0, 0, 1, size, -size,
"l", mmax(w - size, 0), 0, size, !dir * -size, size, !dir * size, mmax(w - size, 0), 0, "a", size, size, 0, 0, 1, size, size,
"l", 0, mmax(h - size, 0), (dir == 1) * size, size, (dir == 1) * -size, size, 0, mmax(h - size, 0), "a", size, size, 0, 0, 1, -size, size,
"l", -mmax(w - size, 0), 0, "z"].join(","),
xy = [{x: x, y: y + size * 2 + h}, {x: x - size * 2 - w, y: y}, {x: x, y: y - size * 2 - h}, {x: x + size * 2 + w, y: y}][dir];
set.translate(xy.x - w - bb.x, xy.y - h - bb.y);
return this.set(this.path(p).attr({fill: "#234", stroke: "none"}).insertBefore(set.node ? set : set[0]), set);
};
Raphael.fn.popup = function (x, y, text, dir, size) {
dir = dir == null ? 2 : dir > 3 ? 3 : dir;
size = size || 5;
text = text || "$9.99";
var res = this.set(),
d = 3;
res.push(this.path().attr({fill: "#000", stroke: "#000"}));
res.push(this.text(x, y, text).attr(this.g.txtattr).attr({fill: "#fff", "font-family": "Helvetica, Arial"}));
res.update = function (X, Y, withAnimation) {
X = X || x;
Y = Y || y;
var bb = this[1].getBBox(),
w = bb.width / 2,
h = bb.height / 2,
dx = [0, w + size * 2, 0, -w - size * 2],
dy = [-h * 2 - size * 3, -h - size, 0, -h - size],
p = ["M", X - dx[dir], Y - dy[dir], "l", -size, (dir == 2) * -size, -mmax(w - size, 0), 0, "a", size, size, 0, 0, 1, -size, -size,
"l", 0, -mmax(h - size, 0), (dir == 3) * -size, -size, (dir == 3) * size, -size, 0, -mmax(h - size, 0), "a", size, size, 0, 0, 1, size, -size,
"l", mmax(w - size, 0), 0, size, !dir * -size, size, !dir * size, mmax(w - size, 0), 0, "a", size, size, 0, 0, 1, size, size,
"l", 0, mmax(h - size, 0), (dir == 1) * size, size, (dir == 1) * -size, size, 0, mmax(h - size, 0), "a", size, size, 0, 0, 1, -size, size,
"l", -mmax(w - size, 0), 0, "z"].join(","),
xy = [{x: X, y: Y + size * 2 + h}, {x: X - size * 2 - w, y: Y}, {x: X, y: Y - size * 2 - h}, {x: X + size * 2 + w, y: Y}][dir];
xy.path = p;
if (withAnimation) {
this.animate(xy, 500, ">");
} else {
this.attr(xy);
}
return this;
};
return res.update(x, y);
};

View file

@ -1,4 +1,7 @@
module CommitExt
attr_accessor :head
attr_accessor :refs
def safe_message
message.encode("UTF-8",
:invalid => :replace,

75
lib/graph_commit.rb Normal file
View file

@ -0,0 +1,75 @@
require "grit"
class GraphCommit
attr_accessor :time, :space
def initialize(commit)
@_commit = commit
@time = -1
@space = 0
end
def method_missing(m, *args, &block)
@_commit.send(m, *args, &block)
end
# Method is adding time and space on the
# list of commits. As well as returns date list
# corelated with time set on commits.
#
# @param [Array<GraphCommit>] comits to index
#
# @return [Array<TimeDate>] list of commit dates corelated with time on commits
def self.index_commits(commits)
days, heads = [], []
map = {}
commits.reverse.each_with_index do |c,i|
c.time = i
days[i] = c.committed_date
map[c.id] = c
heads += c.refs unless c.refs.nil?
end
heads.select!{|h| h.is_a? Grit::Head or h.is_a? Grit::Remote}
# sort heads so the master is top and current branches are closer
heads.sort! do |a,b|
if a.name == "master"
-1
elsif b.name == "master"
1
else
b.commit.committed_date <=> a.commit.committed_date
end
end
j = 0
heads.each do |h|
if map.include? h.commit.id then
j = mark_chain(j+=1, map[h.commit.id], map)
end
end
days
end
# Add space mark on commit and its parents
#
# @param [Fixnum] space (row on the graph) to be set
# @param [GraphCommit] the commit object.
# @param [Hash<String,GraphCommit>] map of commits
#
# @return [Fixnum] max space used.
def self.mark_chain(mark, commit, map)
commit.space = mark if commit.space == 0
m1 = mark - 1
marks = commit.parents.collect do |p|
if map.include? p.id and map[p.id].space == 0 then
mark_chain(m1 += 1, map[p.id],map)
else
m1 + 1
end
end
marks << mark
marks.compact.max
end
end

View file

@ -0,0 +1,35 @@
require 'spec_helper'
describe ApplicationHelper do
context ".gravatar_icon" do
context "over http" do
it "returns the correct URL to www.gravatar.com" do
expected = "http://www.gravatar.com/avatar/f7daa65b2aa96290bb47c4d68d11fe6a?s=40&d=identicon"
# Pretend we're running over HTTP
helper.stub(:request) do
request = double('request')
request.stub(:ssl?) { false }
request
end
helper.gravatar_icon("admin@local.host").should == expected
end
end
context "over https" do
it "returns the correct URL to secure.gravatar.com" do
expected = "https://secure.gravatar.com/avatar/f7daa65b2aa96290bb47c4d68d11fe6a?s=40&d=identicon"
# Pretend we're running over HTTPS
helper.stub(:request) do
request = double('request')
request.stub(:ssl?) { true }
request
end
helper.gravatar_icon("admin@local.host").should == expected
end
end
end
end

View file

@ -39,5 +39,6 @@ end
# updated_at :datetime
# closed :boolean default(FALSE), not null
# position :integer default(0)
# critical :boolean default(FALSE), not null
#

View file

@ -66,7 +66,7 @@ end
# Table name: notes
#
# id :integer not null, primary key
# note :string(255)
# note :text
# noteable_id :string(255)
# noteable_type :string(255)
# author_id :integer

View file

@ -18,6 +18,21 @@ describe User do
user = User.new(:email => "test@mail.com")
user.identifier.should == "test_mail.com"
end
describe "dependent" do
before do
@user = Factory :user
@note = Factory :note,
:author => @user,
:project => Factory(:project)
end
it "should destroy all notes with user" do
Note.find_by_id(@note.id).should_not be_nil
@user.destroy
Note.find_by_id(@note.id).should be_nil
end
end
end
# == Schema Information
#

View file

@ -22,6 +22,7 @@ describe "Dashboard" do
it "should have news feed" do
within "#news-feed" do
page.should have_content("master")
page.should have_content(@project.commit.author.name)
page.should have_content(@project.commit.safe_message)
end

View file

@ -72,10 +72,13 @@ describe "Projects" do
current_path.should == project_path(@project)
end
it "should beahave like dashboard" do
page.should have_content("History")
it "should beahave like activities page" do
within ".project-update" do
page.should have_content("master")
page.should have_content(@project.commit.author.name)
page.should have_content(@project.commit.safe_message)
end
end
end
describe "GET /projects/team" do

View file

@ -7,8 +7,9 @@ describe "Top Panel", :js => true do
before do
visit projects_path
fill_in "search", :with => "Ke"
sleep(2)
find(:xpath, "//ul[contains(@class,'ui-autocomplete')]/li/a[.=\"Keys\"]").click
within ".ui-autocomplete" do
find(:xpath, "//a[.=\"Keys\"]").click
end
end
it "should be on projects page" do
@ -23,8 +24,9 @@ describe "Top Panel", :js => true do
visit project_path(@project)
fill_in "search", :with => "Commi"
sleep(2)
find(:xpath, "//ul[contains(@class,'ui-autocomplete')]/li/a[.=\"#{@project.code} / Commits\"]").click
within ".ui-autocomplete" do
find(:xpath, "//a[.=\"#{@project.code} / Commits\"]").click
end
end
it "should be on projects page" do

7
vendor/assets/javascripts/raphael.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -382,15 +382,13 @@
padding:.2em .4em;
line-height:1.5;
zoom:1;
font-weight: normal;
}
.ui-menu .ui-menu-item a.ui-state-hover,
.ui-menu .ui-menu-item a.ui-state-active {
font-weight: normal;
margin: -1px;
background: #fff !important;
background: -webkit-gradient(linear,left top,left bottom,from(#fff),to(#FFF6BF)) !important;
background: -moz-linear-gradient(top,#fff,#FFF6BF) !important;
background: transparent 9 !important;
background: #eee;
border-radius:0px;
border-color:white;
border-bottom: 1px solid #E2EAEE;