').appendTo(document.body).addClass(a.options.className).css({top:d.top,left:d.left,height:b.innerHeight(),width:b.innerWidth(),position:"absolute"}).animate(c,a.duration,a.options.easing,function(){f.remove();a.callback&&a.callback.apply(b[0],arguments);
+b.dequeue()})})}})(jQuery);
+;
\ No newline at end of file
diff --git a/app/assets/javascripts/jquery.ui.selectmenu.js b/app/assets/javascripts/jquery.ui.selectmenu.js
new file mode 100644
index 00000000..d61d75f9
--- /dev/null
+++ b/app/assets/javascripts/jquery.ui.selectmenu.js
@@ -0,0 +1,845 @@
+ /*
+ * jQuery UI selectmenu dev version
+ *
+ * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * http://docs.jquery.com/UI
+ * https://github.com/fnagel/jquery-ui/wiki/Selectmenu
+ */
+
+(function($) {
+
+$.widget("ui.selectmenu", {
+ getter: "value",
+ version: "1.8",
+ eventPrefix: "selectmenu",
+ options: {
+ transferClasses: true,
+ typeAhead: "sequential",
+ style: 'dropdown',
+ positionOptions: {
+ my: "left top",
+ at: "left bottom",
+ offset: null
+ },
+ width: null,
+ menuWidth: null,
+ handleWidth: 26,
+ maxHeight: null,
+ icons: null,
+ format: null,
+ bgImage: function() {},
+ wrapperElement: "
"
+ },
+
+ _create: function() {
+ var self = this, o = this.options;
+
+ // set a default id value, generate a new random one if not set by developer
+ var selectmenuId = this.element.attr( 'id' ) || 'ui-selectmenu-' + Math.random().toString( 16 ).slice( 2, 10 );
+
+ // quick array of button and menu id's
+ this.ids = [ selectmenuId + '-button', selectmenuId + '-menu' ];
+
+ // define safe mouseup for future toggling
+ this._safemouseup = true;
+
+ // create menu button wrapper
+ this.newelement = $( ' ', {
+ 'class': this.widgetBaseClass + ' ui-widget ui-state-default ui-corner-all',
+ 'id' : this.ids[ 0 ],
+ 'role': 'button',
+ 'href': '#nogo',
+ 'tabindex': this.element.attr( 'disabled' ) ? 1 : 0,
+ 'aria-haspopup': true,
+ 'aria-owns': this.ids[ 1 ]
+ });
+ this.newelementWrap = $( o.wrapperElement )
+ .append( this.newelement )
+ .insertAfter( this.element );
+
+ // transfer tabindex
+ var tabindex = this.element.attr( 'tabindex' );
+ if ( tabindex ) {
+ this.newelement.attr( 'tabindex', tabindex );
+ }
+
+ // save reference to select in data for ease in calling methods
+ this.newelement.data( 'selectelement', this.element );
+
+ // menu icon
+ this.selectmenuIcon = $( ' ' )
+ .prependTo( this.newelement );
+
+ // append status span to button
+ this.newelement.prepend( ' ' );
+
+ // make associated form label trigger focus
+ $( 'label[for="' + selectmenuId + '"]' )
+ .attr( 'for', this.ids[0] )
+ .bind( 'click.selectmenu', function() {
+ self.newelement[0].focus();
+ return false;
+ });
+
+ // click toggle for menu visibility
+ this.newelement
+ .bind('mousedown.selectmenu', function(event) {
+ self._toggle(event, true);
+ // make sure a click won't open/close instantly
+ if (o.style == "popup") {
+ self._safemouseup = false;
+ setTimeout(function() { self._safemouseup = true; }, 300);
+ }
+ return false;
+ })
+ .bind('click.selectmenu', function() {
+ return false;
+ })
+ .bind("keydown.selectmenu", function(event) {
+ var ret = false;
+ switch (event.keyCode) {
+ case $.ui.keyCode.ENTER:
+ ret = true;
+ break;
+ case $.ui.keyCode.SPACE:
+ self._toggle(event);
+ break;
+ case $.ui.keyCode.UP:
+ if (event.altKey) {
+ self.open(event);
+ } else {
+ self._moveSelection(-1);
+ }
+ break;
+ case $.ui.keyCode.DOWN:
+ if (event.altKey) {
+ self.open(event);
+ } else {
+ self._moveSelection(1);
+ }
+ break;
+ case $.ui.keyCode.LEFT:
+ self._moveSelection(-1);
+ break;
+ case $.ui.keyCode.RIGHT:
+ self._moveSelection(1);
+ break;
+ case $.ui.keyCode.TAB:
+ ret = true;
+ break;
+ default:
+ ret = true;
+ }
+ return ret;
+ })
+ .bind('keypress.selectmenu', function(event) {
+ self._typeAhead(event.which, 'mouseup');
+ return true;
+ })
+ .bind('mouseover.selectmenu focus.selectmenu', function() {
+ if (!o.disabled) {
+ $(this).addClass(self.widgetBaseClass + '-focus ui-state-hover');
+ }
+ })
+ .bind('mouseout.selectmenu blur.selectmenu', function() {
+ if (!o.disabled) {
+ $(this).removeClass(self.widgetBaseClass + '-focus ui-state-hover');
+ }
+ });
+
+ // document click closes menu
+ $(document).bind("mousedown.selectmenu", function(event) {
+ self.close(event);
+ });
+
+ // change event on original selectmenu
+ this.element
+ .bind("click.selectmenu", function() {
+ self._refreshValue();
+ })
+ // FIXME: newelement can be null under unclear circumstances in IE8
+ // TODO not sure if this is still a problem (fnagel 20.03.11)
+ .bind("focus.selectmenu", function() {
+ if (self.newelement) {
+ self.newelement[0].focus();
+ }
+ });
+
+ // set width when not set via options
+ if (!o.width) {
+ o.width = this.element.outerWidth();
+ }
+ // set menu button width
+ this.newelement.width(o.width);
+
+ // hide original selectmenu element
+ this.element.hide();
+
+ // create menu portion, append to body
+ this.list = $( '', {
+ 'class': 'ui-widget ui-widget-content',
+ 'aria-hidden': true,
+ 'role': 'listbox',
+ 'aria-labelledby': this.ids[0],
+ 'id': this.ids[1]
+ });
+ this.listWrap = $( o.wrapperElement )
+ .addClass( self.widgetBaseClass + '-menu' )
+ .append( this.list )
+ .appendTo( 'body' );
+
+ // transfer menu click to menu button
+ this.list
+ .bind("keydown.selectmenu", function(event) {
+ var ret = false;
+ switch (event.keyCode) {
+ case $.ui.keyCode.UP:
+ if (event.altKey) {
+ self.close(event, true);
+ } else {
+ self._moveFocus(-1);
+ }
+ break;
+ case $.ui.keyCode.DOWN:
+ if (event.altKey) {
+ self.close(event, true);
+ } else {
+ self._moveFocus(1);
+ }
+ break;
+ case $.ui.keyCode.LEFT:
+ self._moveFocus(-1);
+ break;
+ case $.ui.keyCode.RIGHT:
+ self._moveFocus(1);
+ break;
+ case $.ui.keyCode.HOME:
+ self._moveFocus(':first');
+ break;
+ case $.ui.keyCode.PAGE_UP:
+ self._scrollPage('up');
+ break;
+ case $.ui.keyCode.PAGE_DOWN:
+ self._scrollPage('down');
+ break;
+ case $.ui.keyCode.END:
+ self._moveFocus(':last');
+ break;
+ case $.ui.keyCode.ENTER:
+ case $.ui.keyCode.SPACE:
+ self.close(event, true);
+ $(event.target).parents('li:eq(0)').trigger('mouseup');
+ break;
+ case $.ui.keyCode.TAB:
+ ret = true;
+ self.close(event, true);
+ $(event.target).parents('li:eq(0)').trigger('mouseup');
+ break;
+ case $.ui.keyCode.ESCAPE:
+ self.close(event, true);
+ break;
+ default:
+ ret = true;
+ }
+ return ret;
+ })
+ .bind('keypress.selectmenu', function(event) {
+ self._typeAhead(event.which, 'focus');
+ return true;
+ })
+ // this allows for using the scrollbar in an overflowed list
+ .bind( 'mousedown.selectmenu mouseup.selectmenu', function() { return false; });
+
+ // needed when window is resized
+ // TODO seems to be useless, but causes errors (fnagel 01.08.11)
+ // see: https://github.com/fnagel/jquery-ui/issues/147
+ // $(window).bind( "resize.selectmenu", $.proxy( self._refreshPosition, this ) );
+ },
+
+ _init: function() {
+ var self = this, o = this.options;
+
+ // serialize selectmenu element options
+ var selectOptionData = [];
+ this.element
+ .find('option')
+ .each(function() {
+ var opt = $(this);
+ selectOptionData.push({
+ value: opt.attr('value'),
+ text: self._formatText(opt.text()),
+ selected: opt.attr('selected'),
+ disabled: opt.attr('disabled'),
+ classes: opt.attr('class'),
+ typeahead: opt.attr('typeahead'),
+ parentOptGroup: opt.parent('optgroup'),
+ bgImage: o.bgImage.call(opt)
+ });
+ });
+
+ // active state class is only used in popup style
+ var activeClass = (self.options.style == "popup") ? " ui-state-active" : "";
+
+ // empty list so we can refresh the selectmenu via selectmenu()
+ this.list.html("");
+
+ // write li's
+ if (selectOptionData.length) {
+ for (var i = 0; i < selectOptionData.length; i++) {
+ var thisLiAttr = { role : 'presentation' };
+ if ( selectOptionData[ i ].disabled ) {
+ thisLiAttr[ 'class' ] = this.namespace + '-state-disabled';
+ }
+ var thisAAttr = {
+ html: selectOptionData[i].text,
+ href : '#nogo',
+ tabindex : -1,
+ role : 'option',
+ 'aria-selected' : false
+ };
+ if ( selectOptionData[ i ].disabled ) {
+ thisAAttr[ 'aria-disabled' ] = selectOptionData[ i ].disabled;
+ }
+ if ( selectOptionData[ i ].typeahead ) {
+ thisAAttr[ 'typeahead' ] = selectOptionData[ i ].typeahead;
+ }
+ var thisA = $(' ', thisAAttr);
+ var thisLi = $(' ', thisLiAttr)
+ .append(thisA)
+ .data('index', i)
+ .addClass(selectOptionData[i].classes)
+ .data('optionClasses', selectOptionData[i].classes || '')
+ .bind("mouseup.selectmenu", function(event) {
+ if (self._safemouseup && !self._disabled(event.currentTarget) && !self._disabled($( event.currentTarget ).parents( "ul>li." + self.widgetBaseClass + "-group " )) ) {
+ var changed = $(this).data('index') != self._selectedIndex();
+ self.index($(this).data('index'));
+ self.select(event);
+ if (changed) {
+ self.change(event);
+ }
+ self.close(event, true);
+ }
+ return false;
+ })
+ .bind("click.selectmenu", function() {
+ return false;
+ })
+ .bind('mouseover.selectmenu focus.selectmenu', function(e) {
+ // no hover if diabled
+ if (!$(e.currentTarget).hasClass(self.namespace + '-state-disabled') && !$(e.currentTarget).parent("ul").parent("li").hasClass(self.namespace + '-state-disabled')) {
+ self._selectedOptionLi().addClass(activeClass);
+ self._focusedOptionLi().removeClass(self.widgetBaseClass + '-item-focus ui-state-hover');
+ $(this).removeClass('ui-state-active').addClass(self.widgetBaseClass + '-item-focus ui-state-hover');
+ }
+ })
+ .bind('mouseout.selectmenu blur.selectmenu', function() {
+ if ($(this).is(self._selectedOptionLi().selector)) {
+ $(this).addClass(activeClass);
+ }
+ $(this).removeClass(self.widgetBaseClass + '-item-focus ui-state-hover');
+ });
+
+ // optgroup or not...
+ if ( selectOptionData[i].parentOptGroup.length ) {
+ var optGroupName = self.widgetBaseClass + '-group-' + this.element.find( 'optgroup' ).index( selectOptionData[i].parentOptGroup );
+ if (this.list.find( 'li.' + optGroupName ).length ) {
+ this.list.find( 'li.' + optGroupName + ':last ul' ).append( thisLi );
+ } else {
+ $(' ' + selectOptionData[i].parentOptGroup.attr('label') + ' ')
+ .appendTo( this.list )
+ .find( 'ul' )
+ .append( thisLi );
+ }
+ } else {
+ thisLi.appendTo(this.list);
+ }
+
+ // append icon if option is specified
+ if (o.icons) {
+ for (var j in o.icons) {
+ if (thisLi.is(o.icons[j].find)) {
+ thisLi
+ .data('optionClasses', selectOptionData[i].classes + ' ' + self.widgetBaseClass + '-hasIcon')
+ .addClass(self.widgetBaseClass + '-hasIcon');
+ var iconClass = o.icons[j].icon || "";
+ thisLi
+ .find('a:eq(0)')
+ .prepend(' ');
+ if (selectOptionData[i].bgImage) {
+ thisLi.find('span').css('background-image', selectOptionData[i].bgImage);
+ }
+ }
+ }
+ }
+ }
+ } else {
+ $(' ').appendTo(this.list);
+ }
+ // we need to set and unset the CSS classes for dropdown and popup style
+ var isDropDown = ( o.style == 'dropdown' );
+ this.newelement
+ .toggleClass( self.widgetBaseClass + '-dropdown', isDropDown )
+ .toggleClass( self.widgetBaseClass + '-popup', !isDropDown );
+ this.list
+ .toggleClass( self.widgetBaseClass + '-menu-dropdown ui-corner-bottom', isDropDown )
+ .toggleClass( self.widgetBaseClass + '-menu-popup ui-corner-all', !isDropDown )
+ // add corners to top and bottom menu items
+ .find( 'li:first' )
+ .toggleClass( 'ui-corner-top', !isDropDown )
+ .end().find( 'li:last' )
+ .addClass( 'ui-corner-bottom' );
+ this.selectmenuIcon
+ .toggleClass( 'ui-icon-triangle-1-s', isDropDown )
+ .toggleClass( 'ui-icon-triangle-2-n-s', !isDropDown );
+
+ // transfer classes to selectmenu and list
+ if ( o.transferClasses ) {
+ var transferClasses = this.element.attr( 'class' ) || '';
+ this.newelement.add( this.list ).addClass( transferClasses );
+ }
+
+ // set menu width to either menuWidth option value, width option value, or select width
+ if ( o.style == 'dropdown' ) {
+ this.list.width( o.menuWidth ? o.menuWidth : o.width );
+ } else {
+ this.list.width( o.menuWidth ? o.menuWidth : o.width - o.handleWidth );
+ }
+
+ // reset height to auto
+ this.list.css( 'height', 'auto' );
+ var listH = this.listWrap.height();
+ // calculate default max height
+ if ( o.maxHeight && o.maxHeight < listH ) {
+ this.list.height( o.maxHeight );
+ } else {
+ var winH = $( window ).height() / 3;
+ if ( winH < listH ) this.list.height( winH );
+ }
+
+ // save reference to actionable li's (not group label li's)
+ this._optionLis = this.list.find( 'li:not(.' + self.widgetBaseClass + '-group)' );
+
+ // transfer disabled state
+ if ( this.element.attr( 'disabled' ) ) {
+ this.disable();
+ } else {
+ this.enable()
+ }
+
+ // update value
+ this.index( this._selectedIndex() );
+
+ // needed when selectmenu is placed at the very bottom / top of the page
+ window.setTimeout( function() {
+ self._refreshPosition();
+ }, 200 );
+ },
+
+ destroy: function() {
+ this.element.removeData( this.widgetName )
+ .removeClass( this.widgetBaseClass + '-disabled' + ' ' + this.namespace + '-state-disabled' )
+ .removeAttr( 'aria-disabled' )
+ .unbind( ".selectmenu" );
+
+ // TODO unneded as event binding has been disabled
+ // $( window ).unbind( ".selectmenu" );
+ $( document ).unbind( ".selectmenu" );
+
+ // unbind click on label, reset its for attr
+ $( 'label[for=' + this.newelement.attr('id') + ']' )
+ .attr( 'for', this.element.attr( 'id' ) )
+ .unbind( '.selectmenu' );
+
+ this.newelementWrap.remove();
+ this.listWrap.remove();
+
+ this.element.show();
+
+ // call widget destroy function
+ $.Widget.prototype.destroy.apply(this, arguments);
+ },
+
+ _typeAhead: function( code, eventType ){
+ var self = this, focusFound = false, C = String.fromCharCode(code).toUpperCase();
+ c = C.toLowerCase();
+
+ if ( self.options.typeAhead == 'sequential' ) {
+ // clear the timeout so we can use _prevChar
+ window.clearTimeout('ui.selectmenu-' + self.selectmenuId);
+
+ // define our find var
+ var find = typeof( self._prevChar ) == 'undefined' ? '' : self._prevChar.join( '' );
+
+ function focusOptSeq( elem, ind, c ){
+ focusFound = true;
+ $( elem ).trigger( eventType );
+ typeof( self._prevChar ) == 'undefined' ? self._prevChar = [ c ] : self._prevChar[ self._prevChar.length ] = c;
+ }
+ this.list.find( 'li a' ).each( function( i ) {
+ if ( !focusFound ) {
+ // allow the typeahead attribute on the option tag for a more specific lookup
+ var thisText = $( this ).attr( 'typeahead' ) || $(this).text();
+ if ( thisText.indexOf( find + C ) === 0 ) {
+ focusOptSeq( this, i, C );
+ } else if (thisText.indexOf(find+c) === 0 ) {
+ focusOptSeq( this, i, c );
+ }
+ }
+ });
+ // set a 1 second timeout for sequenctial typeahead
+ // keep this set even if we have no matches so it doesnt typeahead somewhere else
+ window.setTimeout( function( el ) {
+ self._prevChar = undefined;
+ }, 1000, self );
+
+ } else {
+ // define self._prevChar if needed
+ if ( !self._prevChar ) { self._prevChar = [ '' , 0 ]; }
+
+ focusFound = false;
+ function focusOpt( elem, ind ){
+ focusFound = true;
+ $( elem ).trigger( eventType );
+ self._prevChar[ 1 ] = ind;
+ }
+ this.list.find( 'li a' ).each(function( i ){
+ if (!focusFound){
+ var thisText = $(this).text();
+ if ( thisText.indexOf( C ) === 0 || thisText.indexOf( c ) === 0 ) {
+ if (self._prevChar[0] == C){
+ if ( self._prevChar[ 1 ] < i ){ focusOpt( this, i ); }
+ } else{
+ focusOpt( this, i );
+ }
+ }
+ }
+ });
+ this._prevChar[ 0 ] = C;
+ }
+ },
+
+ // returns some usefull information, called by callbacks only
+ _uiHash: function() {
+ var index = this.index();
+ return {
+ index: index,
+ option: $("option", this.element).get(index),
+ value: this.element[0].value
+ };
+ },
+
+ open: function(event) {
+ var self = this, o = this.options;
+ if ( self.newelement.attr("aria-disabled") != 'true' ) {
+ self._closeOthers(event);
+ self.newelement.addClass('ui-state-active');
+
+ self.listWrap.appendTo( o.appendTo );
+ self.list.attr('aria-hidden', false);
+
+ if ( o.style == "dropdown" ) {
+ self.newelement.removeClass('ui-corner-all').addClass('ui-corner-top');
+ }
+
+ self.listWrap.addClass( self.widgetBaseClass + '-open' );
+ // positioning needed for IE7 (tested 01.08.11 on MS VPC Image)
+ // see https://github.com/fnagel/jquery-ui/issues/147
+ if ( $.browser.msie && $.browser.version.substr( 0,1 ) == 7 ) {
+ self._refreshPosition();
+ }
+ var selected = self.list.attr('aria-hidden', false).find('li:not(.' + self.widgetBaseClass + '-group):eq(' + self._selectedIndex() + '):visible a');
+ if (selected.length) selected[0].focus();
+ // positioning needed for FF, Chrome, IE8, IE7, IE6 (tested 01.08.11 on MS VPC Image)
+ self._refreshPosition();
+
+ self._trigger("open", event, self._uiHash());
+ }
+ },
+
+ close: function(event, retainFocus) {
+ if ( this.newelement.is('.ui-state-active') ) {
+ this.newelement
+ .removeClass('ui-state-active');
+ this.listWrap.removeClass(this.widgetBaseClass + '-open');
+ this.list.attr('aria-hidden', true);
+ if ( this.options.style == "dropdown" ) {
+ this.newelement.removeClass('ui-corner-top').addClass('ui-corner-all');
+ }
+ if ( retainFocus ) {
+ this.newelement.focus();
+ }
+ this._trigger("close", event, this._uiHash());
+ }
+ },
+
+ change: function(event) {
+ this.element.trigger("change");
+ this._trigger("change", event, this._uiHash());
+ },
+
+ select: function(event) {
+ if (this._disabled(event.currentTarget)) { return false; }
+ this._trigger("select", event, this._uiHash());
+ },
+
+ _closeOthers: function(event) {
+ $('.' + this.widgetBaseClass + '.ui-state-active').not(this.newelement).each(function() {
+ $(this).data('selectelement').selectmenu('close', event);
+ });
+ $('.' + this.widgetBaseClass + '.ui-state-hover').trigger('mouseout');
+ },
+
+ _toggle: function(event, retainFocus) {
+ if ( this.list.parent().is('.' + this.widgetBaseClass + '-open') ) {
+ this.close(event, retainFocus);
+ } else {
+ this.open(event);
+ }
+ },
+
+ _formatText: function(text) {
+ return (this.options.format ? this.options.format(text) : text);
+ },
+
+ _selectedIndex: function() {
+ return this.element[0].selectedIndex;
+ },
+
+ _selectedOptionLi: function() {
+ return this._optionLis.eq(this._selectedIndex());
+ },
+
+ _focusedOptionLi: function() {
+ return this.list.find('.' + this.widgetBaseClass + '-item-focus');
+ },
+
+ _moveSelection: function(amt, recIndex) {
+ // do nothing if disabled
+ if (!this.options.disabled) {
+ var currIndex = parseInt(this._selectedOptionLi().data('index') || 0, 10);
+ var newIndex = currIndex + amt;
+ // do not loop when using up key
+
+ if (newIndex < 0) {
+ newIndex = 0;
+ }
+ if (newIndex > this._optionLis.size() - 1) {
+ newIndex = this._optionLis.size() - 1;
+ }
+ // Occurs when a full loop has been made
+ if (newIndex === recIndex) { return false; }
+
+ if (this._optionLis.eq(newIndex).hasClass( this.namespace + '-state-disabled' )) {
+ // if option at newIndex is disabled, call _moveFocus, incrementing amt by one
+ (amt > 0) ? ++amt : --amt;
+ this._moveSelection(amt, newIndex);
+ } else {
+ return this._optionLis.eq(newIndex).trigger('mouseup');
+ }
+ }
+ },
+
+ _moveFocus: function(amt, recIndex) {
+ if (!isNaN(amt)) {
+ var currIndex = parseInt(this._focusedOptionLi().data('index') || 0, 10);
+ var newIndex = currIndex + amt;
+ } else {
+ var newIndex = parseInt(this._optionLis.filter(amt).data('index'), 10);
+ }
+
+ if (newIndex < 0) {
+ newIndex = 0;
+ }
+ if (newIndex > this._optionLis.size() - 1) {
+ newIndex = this._optionLis.size() - 1;
+ }
+
+ //Occurs when a full loop has been made
+ if (newIndex === recIndex) { return false; }
+
+ var activeID = this.widgetBaseClass + '-item-' + Math.round(Math.random() * 1000);
+
+ this._focusedOptionLi().find('a:eq(0)').attr('id', '');
+
+ if (this._optionLis.eq(newIndex).hasClass( this.namespace + '-state-disabled' )) {
+ // if option at newIndex is disabled, call _moveFocus, incrementing amt by one
+ (amt > 0) ? ++amt : --amt;
+ this._moveFocus(amt, newIndex);
+ } else {
+ this._optionLis.eq(newIndex).find('a:eq(0)').attr('id',activeID).focus();
+ }
+
+ this.list.attr('aria-activedescendant', activeID);
+ },
+
+ _scrollPage: function(direction) {
+ var numPerPage = Math.floor(this.list.outerHeight() / this.list.find('li:first').outerHeight());
+ numPerPage = (direction == 'up' ? -numPerPage : numPerPage);
+ this._moveFocus(numPerPage);
+ },
+
+ _setOption: function(key, value) {
+ this.options[key] = value;
+ // set
+ if (key == 'disabled') {
+ this.close();
+ this.element
+ .add(this.newelement)
+ .add(this.list)[value ? 'addClass' : 'removeClass'](
+ this.widgetBaseClass + '-disabled' + ' ' +
+ this.namespace + '-state-disabled')
+ .attr("aria-disabled", value);
+ }
+ },
+
+ disable: function(index, type){
+ // if options is not provided, call the parents disable function
+ if ( typeof( index ) == 'undefined' ) {
+ this._setOption( 'disabled', true );
+ } else {
+ if ( type == "optgroup" ) {
+ this._disableOptgroup(index);
+ } else {
+ this._disableOption(index);
+ }
+ }
+ },
+
+ enable: function(index, type) {
+ // if options is not provided, call the parents enable function
+ if ( typeof( index ) == 'undefined' ) {
+ this._setOption('disabled', false);
+ } else {
+ if ( type == "optgroup" ) {
+ this._enableOptgroup(index);
+ } else {
+ this._enableOption(index);
+ }
+ }
+ },
+
+ _disabled: function(elem) {
+ return $(elem).hasClass( this.namespace + '-state-disabled' );
+ },
+
+
+ _disableOption: function(index) {
+ var optionElem = this._optionLis.eq(index);
+ if (optionElem) {
+ optionElem.addClass(this.namespace + '-state-disabled')
+ .find("a").attr("aria-disabled", true);
+ this.element.find("option").eq(index).attr("disabled", "disabled");
+ }
+ },
+
+ _enableOption: function(index) {
+ var optionElem = this._optionLis.eq(index);
+ if (optionElem) {
+ optionElem.removeClass( this.namespace + '-state-disabled' )
+ .find("a").attr("aria-disabled", false);
+ this.element.find("option").eq(index).removeAttr("disabled");
+ }
+ },
+
+ _disableOptgroup: function(index) {
+ var optGroupElem = this.list.find( 'li.' + this.widgetBaseClass + '-group-' + index );
+ if (optGroupElem) {
+ optGroupElem.addClass(this.namespace + '-state-disabled')
+ .attr("aria-disabled", true);
+ this.element.find("optgroup").eq(index).attr("disabled", "disabled");
+ }
+ },
+
+ _enableOptgroup: function(index) {
+ var optGroupElem = this.list.find( 'li.' + this.widgetBaseClass + '-group-' + index );
+ if (optGroupElem) {
+ optGroupElem.removeClass(this.namespace + '-state-disabled')
+ .attr("aria-disabled", false);
+ this.element.find("optgroup").eq(index).removeAttr("disabled");
+ }
+ },
+
+ index: function(newValue) {
+ if (arguments.length) {
+ if (!this._disabled($(this._optionLis[newValue]))) {
+ this.element[0].selectedIndex = newValue;
+ this._refreshValue();
+ } else {
+ return false;
+ }
+ } else {
+ return this._selectedIndex();
+ }
+ },
+
+ value: function(newValue) {
+ if (arguments.length) {
+ this.element[0].value = newValue;
+ this._refreshValue();
+ } else {
+ return this.element[0].value;
+ }
+ },
+
+ _refreshValue: function() {
+ var activeClass = (this.options.style == "popup") ? " ui-state-active" : "";
+ var activeID = this.widgetBaseClass + '-item-' + Math.round(Math.random() * 1000);
+ // deselect previous
+ this.list
+ .find('.' + this.widgetBaseClass + '-item-selected')
+ .removeClass(this.widgetBaseClass + "-item-selected" + activeClass)
+ .find('a')
+ .attr('aria-selected', 'false')
+ .attr('id', '');
+ // select new
+ this._selectedOptionLi()
+ .addClass(this.widgetBaseClass + "-item-selected" + activeClass)
+ .find('a')
+ .attr('aria-selected', 'true')
+ .attr('id', activeID);
+
+ // toggle any class brought in from option
+ var currentOptionClasses = (this.newelement.data('optionClasses') ? this.newelement.data('optionClasses') : "");
+ var newOptionClasses = (this._selectedOptionLi().data('optionClasses') ? this._selectedOptionLi().data('optionClasses') : "");
+ this.newelement
+ .removeClass(currentOptionClasses)
+ .data('optionClasses', newOptionClasses)
+ .addClass( newOptionClasses )
+ .find('.' + this.widgetBaseClass + '-status')
+ .html(
+ this._selectedOptionLi()
+ .find('a:eq(0)')
+ .html()
+ );
+
+ this.list.attr('aria-activedescendant', activeID);
+ },
+
+ _refreshPosition: function() {
+ var o = this.options;
+
+ // if its a native pop-up we need to calculate the position of the selected li
+ if ( o.style == "popup" && !o.positionOptions.offset ) {
+ var selected = this._selectedOptionLi();
+ var _offset = "0 -" + ( selected.outerHeight() + selected.offset().top - this.list.offset().top );
+ }
+ // update zIndex if jQuery UI is able to process
+ var zIndexElement = this.element.zIndex();
+ if ( zIndexElement ) {
+ this.listWrap.css( 'zIndex', zIndexElement );
+ }
+ this.listWrap.position({
+ // set options for position plugin
+ of: o.positionOptions.of || this.newelement,
+ my: o.positionOptions.my,
+ at: o.positionOptions.at,
+ offset: o.positionOptions.offset || _offset,
+ collision: o.positionOptions.collision || 'flip'
+ });
+ }
+});
+
+})(jQuery);
diff --git a/app/assets/javascripts/profile.js.coffee b/app/assets/javascripts/profile.js.coffee
new file mode 100644
index 00000000..76156794
--- /dev/null
+++ b/app/assets/javascripts/profile.js.coffee
@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
diff --git a/app/assets/javascripts/projects.js b/app/assets/javascripts/projects.js
new file mode 100644
index 00000000..dc13d006
--- /dev/null
+++ b/app/assets/javascripts/projects.js
@@ -0,0 +1,41 @@
+$(document).ready(function(){
+ $('#tree-slider td.tree-item-file-name a, #tree-breadcrumbs a').live("click", function() {
+ history.pushState({ path: this.path }, '', this.href)
+ })
+
+ $("#tree-slider tr.tree-item").live('click', function(e){
+ if(e.target.nodeName != "A") {
+ e.stopPropagation();
+ $(this).find("td.tree-item-file-name a").click();
+ return false;
+ }
+ });
+
+ $("#projects-list .project").live('click', function(e){
+ if(e.target.nodeName != "A" && e.target.nodeName != "INPUT") {
+ location.href = $(this).attr("url");
+ e.stopPropagation();
+ return false;
+ }
+ });
+
+ $("#issues-table .issue").live('click', function(e){
+ if(e.target.nodeName != "A" && e.target.nodeName != "INPUT") {
+ location.href = $(this).attr("url");
+ e.stopPropagation();
+ return false;
+ }
+ });
+
+ $(document).keypress(function(e) {
+ if( $(e.target).is(":input") ) return;
+ switch(e.which) {
+ case 115: focusSearch();
+ e.preventDefault();
+ }
+ });
+});
+
+function focusSearch() {
+ $("#search").focus();
+}
diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css
new file mode 100644
index 00000000..42fe63db
--- /dev/null
+++ b/app/assets/stylesheets/application.css
@@ -0,0 +1,7 @@
+/*
+ * This is a manifest file that'll automatically include all the stylesheets available in this directory
+ * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
+ * the top of the compiled file, but it's generally better to create a new file per style scope.
+ *= require_self
+ *= require_tree .
+*/
diff --git a/app/assets/stylesheets/dashboard.css.scss b/app/assets/stylesheets/dashboard.css.scss
new file mode 100644
index 00000000..e8f34fdd
--- /dev/null
+++ b/app/assets/stylesheets/dashboard.css.scss
@@ -0,0 +1,3 @@
+// 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/
diff --git a/app/assets/stylesheets/highlight.css.scss b/app/assets/stylesheets/highlight.css.scss
new file mode 100644
index 00000000..05cb98e7
--- /dev/null
+++ b/app/assets/stylesheets/highlight.css.scss
@@ -0,0 +1,135 @@
+@mixin round-borders-all($radius) {
+ border: 1px solid #eaeaea;
+ -moz-border-radius: $radius;
+ -webkit-border-radius: $radius;
+ border-radius: $radius;
+}
+
+table.highlighttable
+{
+ margin:0px;
+ padding:0px;
+ font-size:12px;
+ table-layout:fixed
+}
+
+td.code,
+td.linenos{
+ padding:0;
+ margin:0;
+ vertical-align:top;
+}
+
+.highlight{
+ background:none;
+ padding:10px 0px 0px 0;
+ margin-left:10px;
+}
+.highlight pre{
+}
+
+.linenodiv pre {
+ white-space:pre-line;
+}
+
+td.linenos {
+ background:#ECECEC;
+ color:#777;
+ padding:10px 0px 0px 10px;
+ float:left;
+ width:45px;
+ border-right: 1px solid #ccc;
+
+}
+
+td.code .highlight {
+ overflow-x: scroll;
+}
+table.highlighttable pre{
+ padding:0;
+ margin:0;
+ font-family: 'Courier New', 'andale mono','lucida console',monospace;
+ color: #333;
+ text-align:left;
+}
+
+
+.git-empty .highlight {
+ @include round-borders-all(4px);
+ background:#eee;
+ padding:5px;
+ //overflow-x:scroll;
+ pre{
+ padding:0;
+ line-height:2.0;
+ margin:0;
+ font-family: 'Courier New', 'andale mono','lucida console',monospace;
+ color: #333;
+ text-align:left;}
+ }
+
+.shadow{
+ -webkit-box-shadow:0 5px 15px #000;
+ -moz-box-shadow:0 5px 15px #000;
+ box-shadow:0 5px 15px #000;
+}
+
+
+.hll { background-color: #ffffff }
+.c { color: #888888; font-style: italic } /* Comment */
+.err { color: #a61717; background-color: #e3d2d2 } /* Error */
+.k { color: #000000; font-weight: bold } /* Keyword */
+.cm { color: #888888 } /* Comment.Multiline */
+.cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
+.c1 { color: #888888 } /* Comment.Single */
+.cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
+.gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
+.ge { font-style: italic } /* Generic.Emph */
+.gr { color: #aa0000 } /* Generic.Error */
+.gh { color: #303030 } /* Generic.Heading */
+.gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
+.go { color: #888888 } /* Generic.Output */
+.gp { color: #555555 } /* Generic.Prompt */
+.gs { font-weight: bold } /* Generic.Strong */
+.gu { color: #606060 } /* Generic.Subheading */
+.gt { color: #aa0000 } /* Generic.Traceback */
+.highlight .kc{font-weight:bold;} /* Keyword.Constant */
+.highlight .kd{font-weight:bold;} /* Keyword.Declaration */
+.highlight .kn{font-weight:bold;} /* Keyword.Namespace */
+.highlight .kp{font-weight:bold;} /* Keyword.Pseudo */
+.highlight .kr{font-weight:bold;} /* Keyword.Reserved */
+.highlight .kt{color:#458;font-weight:bold;} /* Keyword.Type */
+.m { color: #0000DD; font-weight: bold } /* Literal.Number */
+.s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
+.highlight .na{color:#008080;} /* Name.Attribute */
+.highlight .nb{color:#0086B3;} /* Name.Builtin */
+.highlight .nc{color:#458;font-weight:bold;} /* Name.Class */
+.highlight .no{color:#008080;} /* Name.Constant */
+.highlight .ni{color:#800080;}
+.highlight .ne{color:#900;font-weight:bold;} /* Name.Exception */
+.highlight .nf{color:#900;font-weight:bold;} /* Name.Function */
+.highlight .nn{color:#005;font-weight:bold;} /* Name.Namespace */
+.highlight .nt{color:#000080;} /* Name.Tag */
+.highlight .nv{color:#008080;} /* Name.Variable */
+.py { color: #336699; font-weight: bold } /* Name.Property */
+.ow { color: #008800 } /* Operator.Word */
+.w { color: #bbbbbb } /* Text.Whitespace */
+.mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
+.mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
+.highlight .mi {color:#099;} /* Literal.Number.Integer */
+.mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
+.sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
+.highlight .sc{color:#d14;} /* Literal.String.Char */
+.sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
+.highlight .s2{color:#d14;} /* Literal.String.Double */
+.highlight .se{color:#d14;} /* Literal.String.Escape */
+.highlight .sh{color:#d14;} /* Literal.String.Heredoc */
+.highlight .si{color:#d14;} /* Literal.String.Interpol */
+.highlight .sx{color:#d14;} /* Literal.String.Other */
+.highlight .sr{color:#d14;} /* Literal.String.Regex */
+.highlight .s1{color:#d14;} /* Literal.String.Single */
+.highlight .ss{color:#d14;} /* Literal.String.Symbol */
+.bp { color: #003388 } /* Name.Builtin.Pseudo */
+.vc { color: #336699 } /* Name.Variable.Class */
+.vg { color: #dd7700 } /* Name.Variable.Global */
+.vi { color: #3333bb }
diff --git a/app/assets/stylesheets/issues.css.scss b/app/assets/stylesheets/issues.css.scss
new file mode 100644
index 00000000..1fbc8cb8
--- /dev/null
+++ b/app/assets/stylesheets/issues.css.scss
@@ -0,0 +1,3 @@
+// 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/
diff --git a/app/assets/stylesheets/jquery.ui.selectmenu.css b/app/assets/stylesheets/jquery.ui.selectmenu.css
new file mode 100644
index 00000000..37cfbd05
--- /dev/null
+++ b/app/assets/stylesheets/jquery.ui.selectmenu.css
@@ -0,0 +1,33 @@
+/* Selectmenu
+----------------------------------*/
+.ui-selectmenu { background:none; font-size:12px;display: block; display: inline-block; position: relative; height: 2.2em; vertical-align: middle; text-decoration: none; overflow: hidden; zoom: 1; }
+.ui-selectmenu-icon { position:absolute; right:6px; margin-top:-8px; top: 50%; }
+.ui-selectmenu-menu { padding:0; margin:0; position:absolute; top: 0; display: none; z-index: 1005;} /* z-index: 1005 to make selectmenu work with dialog */
+.ui-selectmenu-menu ul { padding:0; margin:0; list-style:none; position: relative; overflow: auto; overflow-y: auto ; overflow-x: hidden; }
+.ui-selectmenu-open { display: block; }
+.ui-selectmenu.ui-widget { background:none; }
+.ui-selectmenu-menu-popup { margin-top: -1px; }
+.ui-selectmenu-menu-dropdown { }
+.ui-selectmenu-menu li.ui-state-active { background:#F7FBFC; border:none; padding:1px 0;}
+.ui-selectmenu-menu li { padding:0; margin:0; display: block; border-top: 1px dotted transparent; border-bottom: 1px dotted transparent; border-right-width: 0 !important; border-left-width: 0 !important; font-weight: normal !important; }
+.ui-selectmenu-menu li a,.ui-selectmenu-status { line-height: 1.4em; display: block; padding: .405em 2.1em .405em 1em; outline:none; text-decoration:none; }
+.ui-selectmenu-menu li.ui-state-disabled a, .ui-state-disabled { cursor: default; }
+.ui-selectmenu-menu li.ui-selectmenu-hasIcon a,
+.ui-selectmenu-hasIcon .ui-selectmenu-status { padding-left: 20px; position: relative; margin-left: 5px; }
+.ui-selectmenu-menu li .ui-icon, .ui-selectmenu-status .ui-icon { position: absolute; top: 1em; margin-top: -8px; left: 0; }
+.ui-selectmenu-status { line-height: 1.4em; }
+.ui-selectmenu-open li.ui-selectmenu-item-focus { background: none repeat scroll 0 0 #FFF6BF; border:1px solid #eaeaea;}
+.ui-selectmenu-open li.ui-selectmenu-item-selected { }
+.ui-selectmenu-menu li span,.ui-selectmenu-status span { display:block; margin-bottom: .2em; }
+.ui-selectmenu-menu li .ui-selectmenu-item-header { font-weight: bold; }
+.ui-selectmenu-menu li .ui-selectmenu-item-content { }
+.ui-selectmenu-menu li .ui-selectmenu-item-footer { opacity: .8; }
+/* for optgroups */
+.ui-selectmenu-menu .ui-selectmenu-group { font-size: 1em; }
+.ui-selectmenu-menu .ui-selectmenu-group .ui-selectmenu-group-label { line-height: 1.4em; display:block; padding: .6em .5em 0; font-weight: bold; }
+.ui-selectmenu-menu .ui-selectmenu-group ul { margin: 0; padding: 0; }
+/* IE6 workaround (dotted transparent borders) */
+* html .ui-selectmenu-menu li { border-color: pink; filter:chroma(color=pink); width:100%; }
+* html .ui-selectmenu-menu li a { position: relative }
+/* IE7 workaround (opacity disabled) */
+*+html .ui-state-disabled, *+html .ui-state-disabled a { color: silver; }
diff --git a/app/assets/stylesheets/profile.css.scss b/app/assets/stylesheets/profile.css.scss
new file mode 100644
index 00000000..22ee5087
--- /dev/null
+++ b/app/assets/stylesheets/profile.css.scss
@@ -0,0 +1,3 @@
+// 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/
diff --git a/app/assets/stylesheets/projects.css.scss b/app/assets/stylesheets/projects.css.scss
new file mode 100644
index 00000000..fcefac16
--- /dev/null
+++ b/app/assets/stylesheets/projects.css.scss
@@ -0,0 +1,523 @@
+// 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/
+
+@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;
+}
+
+@mixin round-borders-all($radius) {
+ border: 1px solid #eaeaea;
+ -moz-border-radius: $radius;
+ -webkit-border-radius: $radius;
+ 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-x: scroll;
+ 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_stats {
+ margin-bottom:10px;
+ @include round-borders-all(4px);
+
+ span {
+ border-top: 1px solid #eaeaea;
+ padding:5px 5px;
+ display:block;
+ &:first-child {
+ border-top:none;
+ }
+
+ img {
+ width:18px;
+ float:left;
+ margin-right: 6px;
+ }
+ }
+}
+
+.round-borders {
+ @include round-borders-all(4px);
+ padding: 4px 0px;
+}
+table.round-borders {
+ float:left;
+}
+
+.day-commits-table {
+ @include round-borders-all(4px);
+ padding: 4px 0px;
+ margin-bottom:10px;
+ display:block;
+ width:100%;
+ background: #E6F1F6;
+
+ .day-header {
+ padding:10px;
+ h3 {
+ margin:0px;
+ }
+ }
+
+ ul {
+ display:block;
+ list-style:none;
+ margin:0px;
+ padding:0px;
+
+ li.commit {
+ display:list-item;
+ padding:8px;
+ margin:0px;
+ background: #F7FBFC;
+ border-top: 1px solid #E2EAEE;
+
+ &:first-child {
+ border-top: 1px solid #E2EAEE;
+ }
+ &:nth-child(2n+1) {
+ background: white;
+ }
+
+ a.button {
+ width:85px;
+ padding:10px;
+ margin:0px;
+ float:right;
+ }
+ p {
+ margin-bottom: 3px;
+ font-size: 13px;
+ }
+ }
+ }
+}
+@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;
+}
+
+body {
+ background: #eaeaea;
+}
+
+a {
+ color: #111;
+}
+
+.diff_file_content{
+ .old_line, .new_line {
+ background:#ECECEC;
+ color:#777;
+ width:15px;
+ float:left;
+ padding: 0px 10px;
+ border-right: 1px solid #ccc;
+ }
+}
+
+
+.view_file_content{
+ .old_line, .new_line {
+ background:#ECECEC;
+ color:#777;
+ width:15px;
+ float:left;
+ padding: 0px 10px;
+ border-right: 1px solid #ccc;
+ }
+ .old_line{
+ display:none;
+ }
+}
+
+.view_file {
+ border:1px solid #CCC;
+ margin-bottom:1em;
+
+ .view_file_header {
+ padding:5px 5px;
+ border-bottom:1px solid #CCC;
+ background: #eee;
+ }
+ .view_file_content {
+ background:#fff;
+ color:#514721;
+ font-size: 11px;
+ }
+ .view_file_content_image {
+ background:#eee;
+ text-align:center;
+ img {
+ padding:100px;
+ max-width:300px;
+ }
+ }
+}
+
+.back_small.button{
+
+}
+
+input.ssh_project_url {
+ padding:5px;
+ margin:0px;
+ float:right;
+ width:400px;
+ text-align:center;
+}
+
+
+
+.day-commits-table li.commit {
+ cursor:pointer;
+
+ &:hover {
+ @include hover-color;
+ }
+}
+
+/*
+#FFF6BF
+#FFD324
+*/
+#tree-slider tr.tree-item {
+ cursor:pointer;
+
+ &:hover {
+ @include hover-color;
+ td {
+ @include hover-color;
+ }
+ }
+}
+#projects-list .project {
+ height:50px;
+}
+
+#projects-list .project,
+#issues-table .issue{
+ cursor:pointer;
+
+ &:hover {
+ @include hover-color;
+ td {
+ @include hover-color;
+ }
+ }
+}
+
+.clear {
+ 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,
+.edit_user,
+.new_project,
+.edit_project {
+ input[type='text'],
+ input[type='email'],
+ input[type='password'],
+ textarea {
+ width:400px;
+ padding:8px;
+ font-size:14px;
+ @include round-borders-all(4px);
+ }
+}
+
+.input_button {
+ //@include round-borders-all(4px);
+ padding:8px;
+ font-size:14px;
+ cursor:pointer;
+ background-color: #F5F5F5;
+ border-color: #EEEEEE #DEDEDE #DEDEDE #EEEEEE;
+ border-right: 1px solid #DEDEDE;
+ border-style: solid;
+ border-width: 1px;
+}
+
+tbody tr:nth-child(2n) td, tbody tr.even td {
+ background: none repeat scroll 0 0 #F7FBFC;
+ border-top: 1px solid #E2EAEE;
+ border-bottom: 1px solid #E2EAEE;
+}
+
+.top_menu_count {
+ background: none repeat scroll 0 0 #FFF6BF;
+ border-color: #FFD324;
+ color: #514721;
+ border: 1px solid #DDDDDD;
+ padding: 2px;
+ font-size:12px;
+ position:relative;
+ top:-14px;
+ left:10px;
+ border-top:none;
+}
+
+#logo {
+ color: #EAEAEA;
+ font-family: monospace;
+ font-size: 26px;
+ padding: 4px;
+ text-decoration: none;
+ text-shadow: #555 1px 1px;
+}
+
+/** FALSH **/
+
+#flash_container {
+ height:40px;
+ position:fixed;
+ z-index:1009;
+ top:0px;
+ width:100%;
+ margin-bottom:10px;
+ overflow:hidden;
+ background:white;
+ cursor:pointer;
+ border-bottom:1px solid #777;
+
+ h4 {
+ color:#444;
+ font-size:22px;
+ padding-top:5px;
+ }
+}
+
+/** Buttons **/
+
+.lbutton,
+.lite_button {
+ display:block;
+ float:left;
+ margin: 0px 5px;
+ padding:5px 10px;
+
+ font-family:"Helvetica Neue", Arial, Helvetica, sans-serif;
+ border:1px solid #D3D3D3;
+ background:white;
+ font-size:12px;
+ line-height:130%;
+ text-decoration:none;
+ font-weight:bold;
+ color:#565656;
+ cursor:pointer;
+
+ &:hover {
+ border:1px solid #C2E1EF;
+ color: #0099FF;
+ }
+
+ &.hm {
+ margin: 0px 0px;
+ }
+
+ &.vm {
+ margin: 5px 0px;
+ }
+}
+
+/** Notes **/
+
+#notes-list {
+ display:block;
+ list-style:none;
+ margin:0px;
+ padding:0px;
+
+ li {
+ display:list-item;
+ padding:8px;
+ margin:0px;
+ background: #F7FBFC;
+ border-top: 1px solid #E2EAEE;
+
+ &:first-child {
+ border-top: none;
+ }
+ &:nth-child(2n+1) {
+ background: white;
+ }
+ p {
+ margin-bottom: 3px;
+ font-size: 12px;
+ }
+ }
+}
+
+.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;
+}
+.note_author {
+ float:left;
+ width:60px;
+}
+.note_content {
+ float:left;
+ width:750px;
+}
+
+.issue_notes {
+ .note_content {
+ float:left;
+ width:400px;
+ }
+}
diff --git a/app/controllers/admin/mailer_controller.rb b/app/controllers/admin/mailer_controller.rb
new file mode 100644
index 00000000..05ad267f
--- /dev/null
+++ b/app/controllers/admin/mailer_controller.rb
@@ -0,0 +1,44 @@
+class Admin::MailerController < ApplicationController
+ before_filter :authenticate_user!
+ before_filter :authenticate_admin!
+
+ def preview
+
+ end
+
+ def preview_note
+ @note = Note.first
+ @user = @note.author
+ @project = @note.project
+ case params[:type]
+ when "Commit" then
+ @commit = @project.commit
+ render :file => 'notify/note_commit_email.html.haml', :layout => 'notify'
+ when "Issue" then
+ @issue = Issue.first
+ render :file => 'notify/note_issue_email.html.haml', :layout => 'notify'
+ else
+ render :file => 'notify/note_wall_email.html.haml', :layout => 'notify'
+ end
+ rescue
+ render :text => "Preview not avaialble"
+ end
+
+ def preview_user_new
+ @user = User.first
+ @password = "DHasJKDHAS!"
+
+ render :file => 'notify/new_user_email.html.haml', :layout => 'notify'
+ rescue
+ render :text => "Preview not avaialble"
+ end
+
+ def preview_issue_new
+ @issue = Issue.first
+ @user = @issue.assignee
+ @project = @issue.project
+ render :file => 'notify/new_issue_email.html.haml', :layout => 'notify'
+ rescue
+ render :text => "Preview not avaialble"
+ end
+end
diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb
new file mode 100644
index 00000000..fe5ada8f
--- /dev/null
+++ b/app/controllers/admin/projects_controller.rb
@@ -0,0 +1,73 @@
+class Admin::ProjectsController < ApplicationController
+ before_filter :authenticate_user!
+ before_filter :authenticate_admin!
+
+ def index
+ @admin_projects = Project.page(params[:page])
+
+ respond_to do |format|
+ format.html # index.html.erb
+ format.json { render json: @admin_projects }
+ end
+ end
+
+ def show
+ @admin_project = Project.find_by_code(params[:id])
+
+ respond_to do |format|
+ format.html # show.html.erb
+ format.json { render json: @admin_project }
+ end
+ end
+
+ def new
+ @admin_project = Project.new
+
+ respond_to do |format|
+ format.html # new.html.erb
+ format.json { render json: @admin_project }
+ end
+ end
+
+ def edit
+ @admin_project = Project.find_by_code(params[:id])
+ end
+
+ def create
+ @admin_project = Project.new(params[:project])
+
+ respond_to do |format|
+ if @admin_project.save
+ format.html { redirect_to [:admin, @admin_project], notice: 'Project was successfully created.' }
+ format.json { render json: @admin_project, status: :created, location: @admin_project }
+ else
+ format.html { render action: "new" }
+ format.json { render json: @admin_project.errors, status: :unprocessable_entity }
+ end
+ end
+ end
+
+ def update
+ @admin_project = Project.find_by_code(params[:id])
+
+ respond_to do |format|
+ if @admin_project.update_attributes(params[:project])
+ format.html { redirect_to [:admin, @admin_project], notice: 'Project was successfully updated.' }
+ format.json { head :ok }
+ else
+ format.html { render action: "edit" }
+ format.json { render json: @admin_project.errors, status: :unprocessable_entity }
+ end
+ end
+ end
+
+ def destroy
+ @admin_project = Project.find_by_code(params[:id])
+ @admin_project.destroy
+
+ respond_to do |format|
+ format.html { redirect_to admin_projects_url }
+ format.json { head :ok }
+ end
+ end
+end
diff --git a/app/controllers/admin/team_members_controller.rb b/app/controllers/admin/team_members_controller.rb
new file mode 100644
index 00000000..bca9bfeb
--- /dev/null
+++ b/app/controllers/admin/team_members_controller.rb
@@ -0,0 +1,75 @@
+class Admin::TeamMembersController < ApplicationController
+ before_filter :authenticate_user!
+ before_filter :authenticate_admin!
+
+ def index
+ @admin_team_members = UsersProject.page(params[:page]).per(100).order("project_id DESC")
+
+ respond_to do |format|
+ format.html # index.html.erb
+ format.json { render json: @admin_team_members }
+ end
+ end
+
+ def show
+ @admin_team_member = UsersProject.find(params[:id])
+
+ respond_to do |format|
+ format.html # show.html.erb
+ format.json { render json: @admin_team_member }
+ end
+ end
+
+ def new
+ @admin_team_member = UsersProject.new(params[:team_member])
+
+ respond_to do |format|
+ format.html # new.html.erb
+ format.json { render json: @admin_team_member }
+ end
+ end
+
+ def edit
+ @admin_team_member = UsersProject.find(params[:id])
+ end
+
+ def create
+ @admin_team_member = UsersProject.new(params[:team_member])
+ @admin_team_member.project_id = params[:team_member][:project_id]
+
+ respond_to do |format|
+ if @admin_team_member.save
+ format.html { redirect_to admin_team_member_path(@admin_team_member), notice: 'UsersProject was successfully created.' }
+ format.json { render json: @admin_team_member, status: :created, location: @team_member }
+ else
+ format.html { render action: "new" }
+ format.json { render json: @admin_team_member.errors, status: :unprocessable_entity }
+ end
+ end
+ end
+
+ def update
+ @admin_team_member = UsersProject.find(params[:id])
+ @admin_team_member.project_id = params[:team_member][:project_id]
+
+ respond_to do |format|
+ if @admin_team_member.update_attributes(params[:team_member])
+ format.html { redirect_to admin_team_member_path(@admin_team_member), notice: 'UsersProject was successfully updated.' }
+ format.json { head :ok }
+ else
+ format.html { render action: "edit" }
+ format.json { render json: @admin_team_member.errors, status: :unprocessable_entity }
+ end
+ end
+ end
+
+ def destroy
+ @admin_team_member = UsersProject.find(params[:id])
+ @admin_team_member.destroy
+
+ respond_to do |format|
+ format.html { redirect_to admin_team_members_url }
+ format.json { head :ok }
+ end
+ end
+end
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
new file mode 100644
index 00000000..5190dd06
--- /dev/null
+++ b/app/controllers/admin/users_controller.rb
@@ -0,0 +1,84 @@
+class Admin::UsersController < ApplicationController
+ before_filter :authenticate_user!
+ before_filter :authenticate_admin!
+
+ def index
+ @admin_users = User.page(params[:page])
+
+ respond_to do |format|
+ format.html # index.html.erb
+ format.json { render json: @admin_users }
+ end
+ end
+
+ def show
+ @admin_user = User.find(params[:id])
+
+ respond_to do |format|
+ format.html # show.html.erb
+ format.json { render json: @admin_user }
+ end
+ end
+
+ def new
+ @admin_user = User.new
+
+ respond_to do |format|
+ format.html # new.html.erb
+ format.json { render json: @admin_user }
+ end
+ end
+
+ def edit
+ @admin_user = User.find(params[:id])
+ end
+
+ def create
+ admin = params[:user].delete("admin")
+
+ @admin_user = User.new(params[:user])
+ @admin_user.admin = (admin && admin.to_i > 0)
+
+ respond_to do |format|
+ if @admin_user.save
+ Notify.new_user_email(@admin_user, params[:user][:password]).deliver
+ format.html { redirect_to [:admin, @admin_user], notice: 'User was successfully created.' }
+ format.json { render json: @admin_user, status: :created, location: @admin_user }
+ else
+ format.html { render action: "new" }
+ format.json { render json: @admin_user.errors, status: :unprocessable_entity }
+ end
+ end
+ end
+
+ def update
+ admin = params[:user].delete("admin")
+ if params[:user][:password].empty?
+ params[:user].delete(:password)
+ params[:user].delete(:password_confirmation)
+ end
+
+ @admin_user = User.find(params[:id])
+ @admin_user.admin = (admin && admin.to_i > 0)
+
+ respond_to do |format|
+ if @admin_user.update_attributes(params[:user])
+ format.html { redirect_to [:admin, @admin_user], notice: 'User was successfully updated.' }
+ format.json { head :ok }
+ else
+ format.html { render action: "edit" }
+ format.json { render json: @admin_user.errors, status: :unprocessable_entity }
+ end
+ end
+ end
+
+ def destroy
+ @admin_user = User.find(params[:id])
+ @admin_user.destroy
+
+ respond_to do |format|
+ format.html { redirect_to admin_users_url }
+ format.json { head :ok }
+ end
+ end
+end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
new file mode 100644
index 00000000..09c44502
--- /dev/null
+++ b/app/controllers/application_controller.rb
@@ -0,0 +1,40 @@
+class ApplicationController < ActionController::Base
+ before_filter :authenticate_user!
+ protect_from_forgery
+
+ helper_method :abilities, :can?
+
+ protected
+
+ def abilities
+ @abilities ||= Six.new
+ end
+
+ def can?(object, action, subject)
+ abilities.allowed?(object, action, subject)
+ end
+
+ def project
+ @project ||= Project.find_by_code(params[:project_id])
+ end
+
+ def add_project_abilities
+ abilities << Ability
+ end
+
+ def authenticate_admin!
+ return redirect_to(new_user_session_path) unless current_user.is_admin?
+ end
+
+ def authorize_project!(action)
+ return redirect_to(new_user_session_path) unless can?(current_user, action, project)
+ end
+
+ def method_missing(method_sym, *arguments, &block)
+ if method_sym.to_s =~ /^authorize_(.*)!$/
+ authorize_project!($1.to_sym)
+ else
+ super
+ end
+ end
+end
diff --git a/app/controllers/commits_controller.rb b/app/controllers/commits_controller.rb
new file mode 100644
index 00000000..d7daec13
--- /dev/null
+++ b/app/controllers/commits_controller.rb
@@ -0,0 +1,44 @@
+require "base64"
+
+class CommitsController < ApplicationController
+ before_filter :project
+
+ # Authorize
+ before_filter :add_project_abilities
+ before_filter :authorize_read_project!
+
+ def index
+ @repo = project.repo
+ @branch = if !params[:branch].blank?
+ params[:branch]
+ elsif !params[:tag].blank?
+ params[:tag]
+ else
+ "master"
+ end
+
+ if params[:path]
+ @commits = @repo.log(@branch, params[:path], :max_count => params[:limit] || 100, :skip => params[:offset] || 0)
+ else
+ @commits = @repo.commits(@branch, params[:limit] || 100, params[:offset] || 0)
+ end
+
+ respond_to do |format|
+ format.html # index.html.erb
+ format.js
+ format.json { render json: @commits }
+ end
+ end
+
+ def show
+ @commit = project.repo.commits(params[:id]).first
+ @notes = project.notes.where(:noteable_id => @commit.id, :noteable_type => "Commit")
+ @note = @project.notes.new(:noteable_id => @commit.id, :noteable_type => "Commit")
+
+ respond_to do |format|
+ format.html # show.html.erb
+ format.js
+ format.json { render json: @commit }
+ end
+ end
+end
diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb
new file mode 100644
index 00000000..e1192e4d
--- /dev/null
+++ b/app/controllers/dashboard_controller.rb
@@ -0,0 +1,2 @@
+class DashboardController < ApplicationController
+end
diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb
new file mode 100644
index 00000000..f8b47198
--- /dev/null
+++ b/app/controllers/issues_controller.rb
@@ -0,0 +1,72 @@
+class IssuesController < ApplicationController
+ before_filter :authenticate_user!
+ before_filter :project
+
+ # Authorize
+ before_filter :add_project_abilities
+ before_filter :authorize_read_issue!
+ before_filter :authorize_write_issue!, :only => [:new, :create, :close, :edit, :update]
+ before_filter :authorize_admin_issue!, :only => [:destroy]
+
+ respond_to :js
+
+ def index
+ @issues = case params[:f].to_i
+ when 1 then @project.issues.all
+ when 2 then @project.issues.closed
+ when 3 then @project.issues.opened.assigned(current_user)
+ else @project.issues.opened
+ end
+
+ respond_to do |format|
+ format.html # index.html.erb
+ format.js
+ end
+ end
+
+ def new
+ @issue = @project.issues.new
+ respond_with(@issue)
+ end
+
+ def edit
+ @issue = @project.issues.find(params[:id])
+ respond_with(@issue)
+ end
+
+ def show
+ @issue = @project.issues.find(params[:id])
+ @notes = @issue.notes
+ @note = @project.notes.new(:noteable => @issue)
+ end
+
+ def create
+ @issue = @project.issues.new(params[:issue])
+ @issue.author = current_user
+ if @issue.save
+ Notify.new_issue_email(@issue).deliver
+ end
+
+ respond_with(@issue)
+ end
+
+ def update
+ @issue = @project.issues.find(params[:id])
+ @issue.update_attributes(params[:issue])
+
+ respond_to do |format|
+ format.js
+ format.html { redirect_to [@project, @issue]}
+ end
+ end
+
+
+ def destroy
+ @issue = @project.issues.find(params[:id])
+ @issue.destroy
+
+ respond_to do |format|
+ format.js { render :nothing => true }
+ end
+ end
+end
diff --git a/app/controllers/keys_controller.rb b/app/controllers/keys_controller.rb
new file mode 100644
index 00000000..003de6b3
--- /dev/null
+++ b/app/controllers/keys_controller.rb
@@ -0,0 +1,38 @@
+class KeysController < ApplicationController
+ respond_to :js
+
+ def index
+ @keys = current_user.keys.all
+
+ respond_to do |format|
+ format.html # index.html.erb
+ format.json { render json: @keys }
+ end
+ end
+
+ def new
+ @key = current_user.keys.new
+
+ respond_with(@key)
+ end
+
+ def create
+ @key = current_user.keys.new(params[:key])
+ @key.save
+
+ respond_with(@key)
+ end
+
+ # DELETE /keys/1
+ # DELETE /keys/1.json
+ def destroy
+ @key = current_user.keys.find(params[:id])
+ @key.destroy
+
+ respond_to do |format|
+ format.html { redirect_to keys_url }
+ format.js { render :nothing => true }
+ format.json { head :ok }
+ end
+ end
+end
diff --git a/app/controllers/notes_controller.rb b/app/controllers/notes_controller.rb
new file mode 100644
index 00000000..d0a40eb1
--- /dev/null
+++ b/app/controllers/notes_controller.rb
@@ -0,0 +1,49 @@
+class NotesController < ApplicationController
+ before_filter :project
+
+ # Authorize
+ before_filter :add_project_abilities
+ before_filter :authorize_write_note!, :only => [:create]
+ before_filter :authorize_admin_note!, :only => [:destroy]
+
+ respond_to :js
+
+ def create
+ @note = @project.notes.new(params[:note])
+ @note.author = current_user
+
+ if @note.save
+ notify if params[:notify] == '1'
+ end
+
+
+ respond_to do |format|
+ format.html {redirect_to :back}
+ format.js
+ end
+ end
+
+ def destroy
+ @note = @project.notes.find(params[:id])
+ @note.destroy
+
+ respond_to do |format|
+ format.js { render :nothing => true }
+ end
+ end
+
+ protected
+
+ def notify
+ @project.users.reject { |u| u.id == current_user.id } .each do |u|
+ case @note.noteable_type
+ when "Commit" then
+ Notify.note_commit_email(u, @note).deliver
+ when "Issue" then
+ Notify.note_issue_email(u, @note).deliver
+ else
+ Notify.note_wall_email(u, @note).deliver
+ end
+ end
+ end
+end
diff --git a/app/controllers/profile_controller.rb b/app/controllers/profile_controller.rb
new file mode 100644
index 00000000..666c6309
--- /dev/null
+++ b/app/controllers/profile_controller.rb
@@ -0,0 +1,21 @@
+class ProfileController < ApplicationController
+ def show
+ @user = current_user
+ end
+
+ def password
+ @user = current_user
+ end
+
+ def password_update
+ params[:user].reject!{ |k, v| k != "password" && k != "password_confirmation"}
+ @user = current_user
+
+ if @user.update_attributes(params[:user])
+ flash[:notice] = "Password was successfully updated. Please login with it"
+ redirect_to new_user_session_path
+ else
+ render :action => "password"
+ end
+ end
+end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
new file mode 100644
index 00000000..06f97f57
--- /dev/null
+++ b/app/controllers/projects_controller.rb
@@ -0,0 +1,149 @@
+class ProjectsController < ApplicationController
+ before_filter :project, :except => [:index, :new, :create]
+
+ # Authorize
+ before_filter :add_project_abilities
+ before_filter :authorize_read_project!, :except => [:index, :new, :create]
+ before_filter :authorize_admin_project!, :only => [:edit, :update, :destroy]
+
+ def index
+ @projects = current_user.projects.all
+
+ respond_to do |format|
+ format.html # index.html.erb
+ format.json { render json: @projects }
+ end
+ end
+
+ def show
+ @repo = project.repo
+ @commit = @repo.commits.first
+ @tree = @commit.tree
+ @tree = @tree / params[:path] if params[:path]
+
+ respond_to do |format|
+ format.html # show.html.erb
+ format.json { render json: project }
+ end
+ rescue Grit::NoSuchPathError => ex
+ respond_to do |format|
+ format.html {render "projects/empty"}
+ end
+ end
+
+ def tree
+ @repo = project.repo
+ @branch = if !params[:branch].blank?
+ params[:branch]
+ elsif !params[:tag].blank?
+ params[:tag]
+ else
+ "master"
+ end
+
+ if params[:commit_id]
+ @commit = @repo.commits(params[:commit_id]).first
+ else
+ @commit = @repo.commits(@branch || "master").first
+ end
+ @tree = @commit.tree
+ @tree = @tree / params[:path] if params[:path]
+
+ respond_to do |format|
+ format.html # show.html.erb
+ format.js do
+ # temp solution
+ response.headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate"
+ response.headers["Pragma"] = "no-cache"
+ response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT"
+ end
+ format.json { render json: project }
+ end
+ end
+
+ def blob
+ @repo = project.repo
+ @commit = project.commit(params[:commit_id])
+ @tree = project.tree(@commit, params[:path])
+
+ if @tree.is_a?(Grit::Blob)
+ send_data(@tree.data, :type => @tree.mime_type, :disposition => 'inline', :filename => @tree.name)
+ else
+ head(404)
+ end
+ end
+
+ def new
+ @project = Project.new
+
+ respond_to do |format|
+ format.html # new.html.erb
+ format.json { render json: @project }
+ end
+ end
+
+ def edit
+ end
+
+ def create
+ @project = Project.new(params[:project])
+
+ Project.transaction do
+ @project.save!
+ @project.users_projects.create!(:admin => true, :read => true, :write => true, :user => current_user)
+ end
+
+ respond_to do |format|
+ if @project.valid?
+ format.html { redirect_to @project, notice: 'Project was successfully created.' }
+ format.js
+ format.json { render json: @project, status: :created, location: @project }
+ else
+ format.html { render action: "new" }
+ format.js
+ format.json { render json: @project.errors, status: :unprocessable_entity }
+ end
+ end
+ rescue StandardError => ex
+ @project.errors.add(:base, "Cant save project. Please try again later")
+ respond_to do |format|
+ format.html { render action: "new" }
+ format.js
+ format.json { render json: @project.errors, status: :unprocessable_entity }
+ end
+ end
+
+ def update
+ respond_to do |format|
+ if project.update_attributes(params[:project])
+ format.html { redirect_to project, notice: 'Project was successfully updated.' }
+ format.js
+ format.json { head :ok }
+ else
+ format.html { render action: "edit" }
+ format.js
+ format.json { render json: project.errors, status: :unprocessable_entity }
+ end
+ end
+ end
+
+ def destroy
+ project.destroy
+
+ respond_to do |format|
+ format.html { redirect_to projects_url }
+ format.json { head :ok }
+ end
+ end
+
+ def wall
+ @notes = @project.common_notes
+ @note = Note.new
+ end
+
+ protected
+
+ def project
+ @project ||= Project.find_by_code(params[:id])
+ end
+end
diff --git a/app/controllers/team_members_controller.rb b/app/controllers/team_members_controller.rb
new file mode 100644
index 00000000..fd3c944b
--- /dev/null
+++ b/app/controllers/team_members_controller.rb
@@ -0,0 +1,66 @@
+class TeamMembersController < ApplicationController
+ before_filter :project
+
+ # Authorize
+ before_filter :add_project_abilities
+ before_filter :authorize_read_team_member!
+ before_filter :authorize_admin_team_member!, :only => [:new, :create, :destroy, :update]
+
+ def show
+ @team_member = project.users_projects.find(params[:id])
+
+ respond_to do |format|
+ format.html # show.html.erb
+ format.js
+ format.json { render json: @team_member }
+ end
+ end
+
+ def new
+ @team_member = project.users_projects.new
+
+ respond_to do |format|
+ format.html # new.html.erb
+ format.js
+ format.json { render json: @team_member }
+ end
+ end
+
+ def create
+ @team_member = UsersProject.new(params[:team_member])
+ @team_member.project = project
+
+ respond_to do |format|
+ if @team_member.save
+ format.html { redirect_to @team_member, notice: 'Team member was successfully created.' }
+ format.js
+ format.json { render json: @team_member, status: :created, location: @team_member }
+ else
+ format.html { render action: "new" }
+ format.js
+ format.json { render json: @team_member.errors, status: :unprocessable_entity }
+ end
+ end
+ end
+
+ def update
+ @team_member = project.users_projects.find(params[:id])
+ @team_member.update_attributes(params[:team_member])
+
+ respond_to do |format|
+ format.js
+ format.html { redirect_to team_project_path(@project)}
+ end
+ end
+
+ def destroy
+ @team_member = project.users_projects.find(params[:id])
+ @team_member.destroy
+
+ respond_to do |format|
+ format.html { redirect_to root_path }
+ format.json { head :ok }
+ format.js { render :nothing => true }
+ end
+ end
+end
diff --git a/app/helpers/admin/projects_helper.rb b/app/helpers/admin/projects_helper.rb
new file mode 100644
index 00000000..348fb1f1
--- /dev/null
+++ b/app/helpers/admin/projects_helper.rb
@@ -0,0 +1,2 @@
+module Admin::ProjectsHelper
+end
diff --git a/app/helpers/admin/users_helper.rb b/app/helpers/admin/users_helper.rb
new file mode 100644
index 00000000..5995c2aa
--- /dev/null
+++ b/app/helpers/admin/users_helper.rb
@@ -0,0 +1,2 @@
+module Admin::UsersHelper
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
new file mode 100644
index 00000000..89a906f0
--- /dev/null
+++ b/app/helpers/application_helper.rb
@@ -0,0 +1,77 @@
+require 'digest/md5'
+module ApplicationHelper
+ def gravatar_icon(user_email)
+ "http://www.gravatar.com/avatar/#{Digest::MD5.hexdigest(user_email)}?s=40&d=identicon"
+ end
+
+ def commit_name(project, commit)
+ if project.commit.id == commit.id
+ "master"
+ else
+ commit.id
+ end
+ end
+
+ def admin_namespace?
+ controller.class.name.split("::").first=="Admin"
+ end
+
+ def projects_namespace?
+ !current_page?(root_url) &&
+ controller.controller_name != "keys" &&
+ !admin_namespace?
+ end
+
+ def last_commit(project)
+ if project.repo_exists?
+ time_ago_in_words(project.commit.committed_date) + " ago"
+ else
+ "Never"
+ end
+ end
+
+ def search_autocomplete_source
+ projects = current_user.projects.map{ |p| { :label => p.name, :url => project_path(p) } }
+ default_nav = [
+ { :label => "Keys", :url => keys_path },
+ { :label => "Projects", :url => projects_path },
+ { :label => "Admin", :url => admin_root_path }
+ ]
+
+ project_nav = []
+
+ if @project && !@project.new_record?
+ project_nav = [
+ { :label => "#{@project.code} / Issues", :url => project_issues_path(@project) },
+ { :label => "#{@project.code} / Wall", :url => wall_project_path(@project) },
+ { :label => "#{@project.code} / Tree", :url => tree_project_path(@project) },
+ { :label => "#{@project.code} / Commits", :url => project_commits_path(@project) },
+ { :label => "#{@project.code} / Team", :url => team_project_path(@project) }
+ ]
+ end
+
+ [projects, default_nav, project_nav].flatten.to_json
+ end
+
+ def handle_file_type(file_name, mime_type)
+ if file_name =~ /(\.rb|\.ru|\.rake|Rakefile|\.gemspec|\.rbx|Gemfile)$/
+ :ruby
+ elsif file_name =~ /\.py$/
+ :python
+ elsif file_name =~ /(\.pl|\.scala|\.c|\.cpp|\.java|\.haml|\.html|\.sass|\.scss|\.xml|\.php|\.erb)$/
+ $1[1..-1].to_sym
+ elsif file_name =~ /\.js$/
+ :javascript
+ elsif file_name =~ /\.sh$/
+ :bash
+ elsif file_name =~ /\.coffee$/
+ :coffeescript
+ elsif file_name =~ /\.yml$/
+ :yaml
+ elsif file_name =~ /\.md$/
+ :minid
+ else
+ :text
+ end
+ end
+end
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
new file mode 100644
index 00000000..f1b54668
--- /dev/null
+++ b/app/helpers/commits_helper.rb
@@ -0,0 +1,24 @@
+module CommitsHelper
+ def diff_line(line, line_new = 0, line_old = 0)
+ full_line = html_escape(line.gsub(/\n/, ''))
+ color = if line[0] == "+"
+ full_line = " #{line_new} " + full_line
+ "#DFD"
+ elsif line[0] == "-"
+ full_line = "#{line_old} " + full_line
+ "#FDD"
+ else
+ full_line = "#{line_old} #{line_new} " + full_line
+ "none"
+ end
+
+ raw "#{full_line}
"
+ end
+
+ def more_commits_link
+ offset = params[:offset] || 0
+ limit = params[:limit] || 100
+ link_to "More", project_commits_path(@project, :offset => offset.to_i + limit.to_i, :limit => limit),
+ :remote => true, :class => "lite_button vm", :style => "text-align:center; width:930px; ", :id => "more-commits-link"
+ end
+end
diff --git a/app/helpers/dashboard_helper.rb b/app/helpers/dashboard_helper.rb
new file mode 100644
index 00000000..a94ddfc2
--- /dev/null
+++ b/app/helpers/dashboard_helper.rb
@@ -0,0 +1,2 @@
+module DashboardHelper
+end
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
new file mode 100644
index 00000000..bfb9d25e
--- /dev/null
+++ b/app/helpers/issues_helper.rb
@@ -0,0 +1,2 @@
+module IssuesHelper
+end
diff --git a/app/helpers/keys_helper.rb b/app/helpers/keys_helper.rb
new file mode 100644
index 00000000..d1a77931
--- /dev/null
+++ b/app/helpers/keys_helper.rb
@@ -0,0 +1,2 @@
+module KeysHelper
+end
diff --git a/app/helpers/profile_helper.rb b/app/helpers/profile_helper.rb
new file mode 100644
index 00000000..5a0d6b31
--- /dev/null
+++ b/app/helpers/profile_helper.rb
@@ -0,0 +1,2 @@
+module ProfileHelper
+end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
new file mode 100644
index 00000000..db5c5ce1
--- /dev/null
+++ b/app/helpers/projects_helper.rb
@@ -0,0 +1,2 @@
+module ProjectsHelper
+end
diff --git a/app/helpers/team_members_helper.rb b/app/helpers/team_members_helper.rb
new file mode 100644
index 00000000..6b8a38f8
--- /dev/null
+++ b/app/helpers/team_members_helper.rb
@@ -0,0 +1,2 @@
+module TeamMembersHelper
+end
diff --git a/app/mailers/.gitkeep b/app/mailers/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
new file mode 100644
index 00000000..56e4de9b
--- /dev/null
+++ b/app/mailers/notify.rb
@@ -0,0 +1,41 @@
+class Notify < ActionMailer::Base
+ default_url_options[:host] = "gitlabhq.com"
+ default from: "notify@gitlabhq.com"
+
+ def new_user_email(user, password)
+ @user = user
+ @password = password
+ mail(:to => @user.email, :subject => "gitlab | Account was created for you")
+ end
+
+ def new_issue_email(issue)
+ @user = issue.assignee
+ @project = issue.project
+ @issue = issue
+
+ mail(:to => @user.email, :subject => "gitlab | New Issue was created")
+ end
+
+ def note_wall_email(user, note)
+ @user = user
+ @note = note
+ @project = note.project
+ mail(:to => @user.email, :subject => "gitlab | #{@note.project.name} ")
+ end
+
+ def note_commit_email(user, note)
+ @user = user
+ @note = note
+ @project = note.project
+ @commit = @project.repo.commits(note.noteable_id).first
+ mail(:to => @user.email, :subject => "gitlab | #{@note.project.name} ")
+ end
+
+ def note_issue_email(user, note)
+ @user = user
+ @note = note
+ @project = note.project
+ @issue = note.noteable
+ mail(:to => @user.email, :subject => "gitlab | #{@note.project.name} ")
+ end
+end
diff --git a/app/models/.gitkeep b/app/models/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/app/models/ability.rb b/app/models/ability.rb
new file mode 100644
index 00000000..0a2c45f1
--- /dev/null
+++ b/app/models/ability.rb
@@ -0,0 +1,34 @@
+class Ability
+ def self.allowed(object, subject)
+ case subject.class.name
+ when "Project" then project_abilities(object, subject)
+ else []
+ end
+ end
+
+ def self.project_abilities(user, project)
+ rules = []
+
+ rules << [
+ :read_project,
+ :read_issue,
+ :read_team_member,
+ :read_note
+ ] if project.readers.include?(user)
+
+ rules << [
+ :write_project,
+ :write_issue,
+ :write_note
+ ] if project.writers.include?(user)
+
+ rules << [
+ :admin_project,
+ :admin_issue,
+ :admin_team_member,
+ :admin_note
+ ] if project.admins.include?(user)
+
+ rules.flatten
+ end
+end
diff --git a/app/models/issue.rb b/app/models/issue.rb
new file mode 100644
index 00000000..0399607e
--- /dev/null
+++ b/app/models/issue.rb
@@ -0,0 +1,39 @@
+class Issue < ActiveRecord::Base
+ belongs_to :project
+ belongs_to :author, :class_name => "User"
+ belongs_to :assignee, :class_name => "User"
+ has_many :notes, :as => :noteable
+
+ attr_protected :author, :author_id, :project, :project_id
+
+ validates_presence_of :project_id
+ validates_presence_of :assignee_id
+ validates_presence_of :author_id
+
+ validates :title,
+ :presence => true,
+ :length => { :within => 0..255 }
+
+ validates :content,
+ :presence => true,
+ :length => { :within => 0..2000 }
+
+ scope :opened, where(:closed => false)
+ scope :closed, where(:closed => true)
+ scope :assigned, lambda { |u| where(:assignee_id => u.id)}
+end
+# == Schema Information
+#
+# Table name: issues
+#
+# id :integer not null, primary key
+# title :string(255)
+# content :text
+# assignee_id :integer
+# author_id :integer
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# closed :boolean default(FALSE), not null
+#
+
diff --git a/app/models/key.rb b/app/models/key.rb
new file mode 100644
index 00000000..9fa89587
--- /dev/null
+++ b/app/models/key.rb
@@ -0,0 +1,58 @@
+class Key < ActiveRecord::Base
+ belongs_to :user
+
+ validates :title,
+ :presence => true,
+ :length => { :within => 0..255 }
+
+ validates :key,
+ :presence => true,
+ :uniqueness => true,
+ :length => { :within => 0..555 }
+
+ before_save :set_identifier
+ after_save :update_gitosis
+ after_destroy :gitosis_delete_key
+
+ def set_identifier
+ self.identifier = "#{user.identifier}_#{Time.now.to_i}"
+ end
+
+ def update_gitosis
+ Gitosis.new.configure do |c|
+ c.update_keys(identifier, key)
+
+ projects.each do |project|
+ c.update_project(project.path, project.gitosis_writers)
+ end
+ end
+ end
+
+ def gitosis_delete_key
+ Gitosis.new.configure do |c|
+ c.delete_key(identifier)
+
+ projects.each do |project|
+ c.update_project(project.path, project.gitosis_writers)
+ end
+ end
+ end
+
+ #projects that has this key
+ def projects
+ user.projects
+ end
+end
+# == Schema Information
+#
+# Table name: keys
+#
+# id :integer not null, primary key
+# user_id :integer not null
+# created_at :datetime
+# updated_at :datetime
+# key :text
+# title :string(255)
+# identifier :string(255)
+#
+
diff --git a/app/models/note.rb b/app/models/note.rb
new file mode 100644
index 00000000..f89fb9f8
--- /dev/null
+++ b/app/models/note.rb
@@ -0,0 +1,41 @@
+require 'carrierwave/orm/activerecord'
+require 'file_size_validator'
+
+class Note < ActiveRecord::Base
+ belongs_to :project
+ belongs_to :noteable, :polymorphic => true
+ belongs_to :author,
+ :class_name => "User"
+
+ attr_protected :author, :author_id
+
+ validates_presence_of :project
+
+ validates :note,
+ :presence => true,
+ :length => { :within => 0..255 }
+
+ validates :attachment,
+ :file_size => {
+ :maximum => 10.megabytes.to_i
+ }
+
+ scope :common, where(:noteable_id => nil)
+
+ mount_uploader :attachment, AttachmentUploader
+end
+# == Schema Information
+#
+# Table name: notes
+#
+# id :integer not null, primary key
+# note :string(255)
+# noteable_id :string(255)
+# noteable_type :string(255)
+# author_id :integer
+# created_at :datetime
+# updated_at :datetime
+# project_id :integer
+# attachment :string(255)
+#
+
diff --git a/app/models/project.rb b/app/models/project.rb
new file mode 100644
index 00000000..4d9461a1
--- /dev/null
+++ b/app/models/project.rb
@@ -0,0 +1,149 @@
+require "grit"
+
+class Project < ActiveRecord::Base
+ has_many :issues, :dependent => :destroy
+ has_many :users_projects, :dependent => :destroy
+ has_many :users, :through => :users_projects
+ has_many :notes, :dependent => :destroy
+
+ validates :name,
+ :uniqueness => true,
+ :presence => true,
+ :length => { :within => 0..255 }
+
+ validates :path,
+ :uniqueness => true,
+ :presence => true,
+ :length => { :within => 0..255 }
+
+ validates :description,
+ :length => { :within => 0..2000 }
+
+ validates :code,
+ :presence => true,
+ :uniqueness => true,
+ :length => { :within => 3..12 }
+
+ before_save :format_code
+ after_destroy :destroy_gitosis_project
+ after_save :update_gitosis_project
+
+ attr_protected :private_flag
+
+ scope :public_only, where(:private_flag => false)
+
+ def to_param
+ code
+ end
+
+ def common_notes
+ notes.where(:noteable_type => ["", nil])
+ end
+
+ def format_code
+ read_attribute(:code).downcase.strip.gsub(' ', '')
+ end
+
+
+ def update_gitosis_project
+ Gitosis.new.configure do |c|
+ c.update_project(path, gitosis_writers)
+ end
+ end
+
+ def destroy_gitosis_project
+ Gitosis.new.configure do |c|
+ c.destroy_project(self)
+ end
+ end
+
+ def add_access(user, *access)
+ opts = { :user => user }
+ access.each { |name| opts.merge!(name => true) }
+ users_projects.create(opts)
+ end
+
+ def reset_access(user)
+ users_projects.where(:project_id => self.id, :user_id => user.id).destroy if self.id
+ end
+
+ def writers
+ @writers ||= users_projects.includes(:user).where(:write => true).map(&:user)
+ end
+
+ def gitosis_writers
+ keys = Key.joins({:user => :users_projects}).where("users_projects.project_id = ? AND users_projects.write = ?", id, true)
+ keys.map(&:identifier)
+ end
+
+ def readers
+ @readers ||= users_projects.includes(:user).where(:read => true).map(&:user)
+ end
+
+ def admins
+ @admins ||=users_projects.includes(:user).where(:admin => true).map(&:user)
+ end
+
+ def public?
+ !private_flag
+ end
+
+ def private?
+ 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 commit(commit_id = nil)
+ if commit_id
+ repo.commits(commit_id).first
+ else
+ repo.commits.first
+ end
+ end
+
+ def tree(fcommit, path = nil)
+ fcommit = commit if fcommit == :head
+ tree = fcommit.tree
+ path ? (tree / path) : tree
+ end
+
+ def valid_repo?
+ repo
+ rescue
+ errors.add(:path, "Invalid repository path")
+ false
+ end
+end
+# == Schema Information
+#
+# Table name: projects
+#
+# id :integer not null, primary key
+# name :string(255)
+# path :string(255)
+# description :text
+# created_at :datetime
+# updated_at :datetime
+# private_flag :boolean default(TRUE), not null
+# code :string(255)
+#
+
diff --git a/app/models/user.rb b/app/models/user.rb
new file mode 100644
index 00000000..fdb44145
--- /dev/null
+++ b/app/models/user.rb
@@ -0,0 +1,52 @@
+class User < ActiveRecord::Base
+ # Include default devise modules. Others available are:
+ # :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable
+ devise :database_authenticatable,
+ :recoverable, :rememberable, :trackable, :validatable
+
+ # Setup accessible (or protected) attributes for your model
+ attr_accessible :email, :password, :password_confirmation, :remember_me, :name
+
+ has_many :users_projects, :dependent => :destroy
+ has_many :projects, :through => :users_projects
+ has_many :keys, :dependent => :destroy
+ has_many :issues,
+ :foreign_key => :author_id,
+ :dependent => :destroy
+
+ has_many :assigned_issues,
+ :class_name => "Issue",
+ :foreign_key => :assignee_id,
+ :dependent => :destroy
+
+ scope :not_in_project, lambda { |project| where("id not in (:ids)", :ids => project.users.map(&:id) ) }
+
+ def identifier
+ email.gsub "@", "_"
+ end
+
+ def is_admin?
+ admin
+ end
+end
+# == Schema Information
+#
+# Table name: users
+#
+# id :integer not null, primary key
+# email :string(255) default(""), not null
+# encrypted_password :string(128) default(""), not null
+# reset_password_token :string(255)
+# reset_password_sent_at :datetime
+# remember_created_at :datetime
+# sign_in_count :integer default(0)
+# current_sign_in_at :datetime
+# last_sign_in_at :datetime
+# current_sign_in_ip :string(255)
+# last_sign_in_ip :string(255)
+# created_at :datetime
+# updated_at :datetime
+# name :string(255)
+# admin :boolean default(FALSE), not null
+#
+
diff --git a/app/models/users_project.rb b/app/models/users_project.rb
new file mode 100644
index 00000000..bdc10633
--- /dev/null
+++ b/app/models/users_project.rb
@@ -0,0 +1,35 @@
+class UsersProject < ActiveRecord::Base
+ belongs_to :user
+ belongs_to :project
+
+ attr_protected :project_id, :project
+
+ after_commit :update_gitosis_project
+
+ validates_uniqueness_of :user_id, :scope => [:project_id]
+ validates_presence_of :user_id
+ validates_presence_of :project_id
+
+ delegate :name, :email, :to => :user, :prefix => true
+
+ def update_gitosis_project
+ Gitosis.new.configure do |c|
+ c.update_project(project.path, project.gitosis_writers)
+ end
+ end
+
+end
+# == Schema Information
+#
+# Table name: users_projects
+#
+# id :integer not null, primary key
+# user_id :integer not null
+# project_id :integer not null
+# read :boolean default(FALSE)
+# write :boolean default(FALSE)
+# admin :boolean default(FALSE)
+# created_at :datetime
+# updated_at :datetime
+#
+
diff --git a/app/uploaders/attachment_uploader.rb b/app/uploaders/attachment_uploader.rb
new file mode 100644
index 00000000..4ba19ace
--- /dev/null
+++ b/app/uploaders/attachment_uploader.rb
@@ -0,0 +1,49 @@
+# encoding: utf-8
+
+class AttachmentUploader < CarrierWave::Uploader::Base
+
+ # Include RMagick or ImageScience support:
+ # include CarrierWave::RMagick
+ # include CarrierWave::MiniMagick
+ # include CarrierWave::ImageScience
+
+ # Choose what kind of storage to use for this uploader:
+ storage :file
+ # storage :fog
+
+ # Override the directory where uploaded files will be stored.
+ # This is a sensible default for uploaders that are meant to be mounted:
+ def store_dir
+ "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
+ end
+
+ # Provide a default URL as a default if there hasn't been a file uploaded:
+ # def default_url
+ # "/images/fallback/" + [version_name, "default.png"].compact.join('_')
+ # end
+
+ # Process files as they are uploaded:
+ # process :scale => [200, 300]
+ #
+ # def scale(width, height)
+ # # do something
+ # end
+
+ # Create different versions of your uploaded files:
+ # version :thumb do
+ # process :scale => [50, 50]
+ # end
+
+ # Add a white list of extensions which are allowed to be uploaded.
+ # For images you might use something like this:
+ # def extension_white_list
+ # %w(jpg jpeg gif png)
+ # end
+
+ # Override the filename of the uploaded files:
+ # Avoid using model.id or version_name here, see uploader/store.rb for details.
+ # def filename
+ # "something.jpg" if original_filename
+ # end
+
+end
diff --git a/app/views/admin/_top_menu.html.haml b/app/views/admin/_top_menu.html.haml
new file mode 100644
index 00000000..d63caa7e
--- /dev/null
+++ b/app/views/admin/_top_menu.html.haml
@@ -0,0 +1,6 @@
+%div.top_project_menu
+ %span= link_to "Users", admin_users_path, :style => "width:50px;", :class => controller.controller_name == "users" ? "current" : nil
+ %span= link_to "Projects", admin_projects_path, :style => "width:50px;", :class => controller.controller_name == "projects" ? "current" : nil
+ %span= link_to "Teams", admin_team_members_path, :style => "width:50px;", :class => controller.controller_name == "team_members" ? "current" : nil
+ %span= link_to "Emails", admin_emails_path, :style => "width:50px;", :class => controller.controller_name == "mailer" ? "current" : nil
+
diff --git a/app/views/admin/mailer/preview.html.haml b/app/views/admin/mailer/preview.html.haml
new file mode 100644
index 00000000..7b723cb8
--- /dev/null
+++ b/app/views/admin/mailer/preview.html.haml
@@ -0,0 +1,29 @@
+%p This is page with preview for all system emails that are sent to user
+%p Email previews built based on existing Project/Commit/Issue base - so some preview maybe unavailable unless object appear in system
+
+#accordion
+ %h3
+ %a New user
+ %div
+ %iframe{ :src=> admin_mailer_preview_user_new_path, :width=>"100%", :height=>"350"}
+ %h3
+ %a New issue
+ %div
+ %iframe{ :src=> admin_mailer_preview_issue_new_path, :width=>"100%", :height=>"350"}
+ %h3
+ %a Commit note
+ %div
+ %iframe{ :src=> admin_mailer_preview_note_path(:type => "Commit"), :width=>"100%", :height=>"350"}
+ %h3
+ %a Issue note
+ %div
+ %iframe{ :src=> admin_mailer_preview_note_path(:type => "Issue"), :width=>"100%", :height=>"350"}
+ %h3
+ %a Wall note
+ %div
+ %iframe{ :src=> admin_mailer_preview_note_path(:type => "Wall"), :width=>"100%", :height=>"350"}
+
+
+:javascript
+ $(function() {
+ $( "#accordion" ).accordion(); });
diff --git a/app/views/admin/projects/_form.html.haml b/app/views/admin/projects/_form.html.haml
new file mode 100644
index 00000000..9823e594
--- /dev/null
+++ b/app/views/admin/projects/_form.html.haml
@@ -0,0 +1,30 @@
+= form_for [:admin, @admin_project] do |f|
+ -if @admin_project.errors.any?
+ #error_explanation
+ %h2= "#{pluralize(@admin_project.errors.count, "error")} prohibited this admin_project from being saved:"
+ %ul
+ - @admin_project.errors.full_messages.each do |msg|
+ %li= msg
+
+ .span-24
+ .span-12
+ .field
+ = f.label :name
+ %br
+ = f.text_field :name
+ .field
+ = f.label :code
+ %br
+ = f.text_field :code
+ .field
+ = f.label :path
+ %br
+ = f.text_field :path
+ .span-10
+ .field
+ = f.label :description
+ %br
+ = f.text_area :description
+ .clear
+ .actions
+ = f.submit 'Save', :class => "lbutton"
diff --git a/app/views/admin/projects/edit.html.haml b/app/views/admin/projects/edit.html.haml
new file mode 100644
index 00000000..9d9a1ee0
--- /dev/null
+++ b/app/views/admin/projects/edit.html.haml
@@ -0,0 +1,5 @@
+= render 'form'
+
+= link_to 'Show', [:admin, @admin_project]
+\|
+= link_to 'Back', admin_projects_path
diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml
new file mode 100644
index 00000000..55c41ec3
--- /dev/null
+++ b/app/views/admin/projects/index.html.haml
@@ -0,0 +1,26 @@
+%table
+ %tr
+ %th Name
+ %th Code
+ %th Path
+ %th Team Members
+ %th Last Commit
+ %th
+ %th
+ %th
+
+ - @admin_projects.each do |project|
+ %tr
+ %td= project.name
+ %td= project.code
+ %td= project.path
+ %td= project.users_projects.count
+ %td= last_commit(project)
+ %td= link_to 'Show', [:admin, project]
+ %td= link_to 'Edit', edit_admin_project_path(project), :id => "edit_#{dom_id(project)}"
+ %td= link_to 'Destroy', [:admin, project], :confirm => 'Are you sure?', :method => :delete
+
+%br
+
+= paginate @admin_projects
+= link_to 'New Project', new_admin_project_path
diff --git a/app/views/admin/projects/new.html.haml b/app/views/admin/projects/new.html.haml
new file mode 100644
index 00000000..3a4d50e1
--- /dev/null
+++ b/app/views/admin/projects/new.html.haml
@@ -0,0 +1,5 @@
+%h1 New project
+
+= render 'form'
+
+= link_to 'Back', admin_projects_path
diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml
new file mode 100644
index 00000000..68918941
--- /dev/null
+++ b/app/views/admin/projects/show.html.haml
@@ -0,0 +1,45 @@
+%p#notice= notice
+
+.span-8.colborder
+ %h2= @admin_project.name
+
+ %p
+ %b Name:
+ = @admin_project.name
+ %p
+ %b Code:
+ = @admin_project.code
+ %p
+ %b Path:
+ = @admin_project.path
+ %p
+ %b Description:
+ = @admin_project.description
+
+ = link_to 'Edit', edit_admin_project_path(@admin_project)
+ \|
+ = link_to 'Back', admin_projects_path
+
+.span-14
+
+ %h2 Team
+
+ %table.round-borders
+ %tr
+ %th Name
+ %th Added
+ %th Web
+ %th Git
+ %th Admin
+ %th
+
+ - @admin_project.users_projects.each do |tm|
+ %tr
+ %td= link_to tm.user_name, admin_team_member_path(tm)
+ %td= time_ago_in_words(tm.updated_at) + " ago"
+ %td= check_box_tag "read", 1, @admin_project.readers.include?(tm.user), :disabled => :disabled
+ %td= check_box_tag "commit", 1, @admin_project.writers.include?(tm.user), :disabled => :disabled
+ %td.span-2= check_box_tag "admin", 1, @admin_project.admins.include?(tm.user), :disabled => :disabled
+ %td= link_to 'Destroy', admin_team_member_path(tm), :confirm => 'Are you sure?', :method => :delete
+
+ = link_to 'New Team Member', new_admin_team_member_path(:team_member => {:project_id => @admin_project.id})
diff --git a/app/views/admin/team_members/_form.html.haml b/app/views/admin/team_members/_form.html.haml
new file mode 100644
index 00000000..ab48c513
--- /dev/null
+++ b/app/views/admin/team_members/_form.html.haml
@@ -0,0 +1,34 @@
+= form_for @admin_team_member, :as => :team_member, :url => @admin_team_member.new_record? ? admin_team_members_path(@admin_team_member) : admin_team_member_path(@admin_team_member) do |f|
+ -if @admin_team_member.errors.any?
+ #error_explanation
+ %h2= "#{pluralize(@admin_team_member.errors.count, "error")} prohibited this admin_project from being saved:"
+ %ul
+ - @admin_team_member.errors.full_messages.each do |msg|
+ %li= msg
+
+ .span-10
+ - if @admin_team_member.new_record?
+ .field
+ = f.label :user_id
+ %br
+ = f.select :user_id, User.all.map { |user| [user.name, user.id] }
+ .field
+ = f.label :project_id
+ %br
+ = f.select :project_id, Project.all.map { |user| [user.name, user.id] }
+
+ .span-10
+ .span-6
+ %b Access:
+ .span-8
+ = f.check_box :read
+ Web Access (Browse Repo)
+ .span-8
+ = f.check_box :write
+ Git Access (User will be added to commiters list)
+ .span-6.append-bottom
+ = f.check_box :admin
+ Admin (Can manage project)
+ %hr
+ .actions
+ = f.submit 'Save'
diff --git a/app/views/admin/team_members/edit.html.haml b/app/views/admin/team_members/edit.html.haml
new file mode 100644
index 00000000..d0362812
--- /dev/null
+++ b/app/views/admin/team_members/edit.html.haml
@@ -0,0 +1,5 @@
+= render 'form'
+
+= link_to 'Show', admin_team_member_path(@admin_team_member)
+\|
+= link_to 'Back', admin_team_members_path
diff --git a/app/views/admin/team_members/index.html.haml b/app/views/admin/team_members/index.html.haml
new file mode 100644
index 00000000..4076917d
--- /dev/null
+++ b/app/views/admin/team_members/index.html.haml
@@ -0,0 +1,30 @@
+- @admin_team_members.group_by(&:project).sort.each do |project, members|
+ %h3= link_to project.name, [:admin, project]
+ %table
+ %tr
+ %th Name
+ %th Email
+ %th Read
+ %th Git
+ %th Manage
+ %th Added
+ %th
+ %th
+ %th
+ - members.each do |tm|
+ - user = tm.user
+ %tr
+ %td.span-6= tm.user_name
+ %td.span-6= tm.user_email
+ %td.span-1= check_box_tag "read", 1, project.readers.include?(user), :disabled => :disabled
+ %td.span-1= check_box_tag "commit", 1, project.writers.include?(user), :disabled => :disabled
+ %td.span-2= check_box_tag "admin", 1, project.admins.include?(user), :disabled => :disabled
+ %td.span-3= time_ago_in_words(tm.updated_at) + " ago"
+ %td= link_to 'Show', admin_team_member_path(tm)
+ %td= link_to 'Edit', edit_admin_team_member_path(tm), :id => "edit_#{dom_id(tm)}"
+ %td= link_to 'Destroy', admin_team_member_path(tm), :confirm => 'Are you sure?', :method => :delete
+
+%br
+
+= paginate @admin_team_members
+= link_to 'New Team Member', new_admin_team_member_path
diff --git a/app/views/admin/team_members/new.html.haml b/app/views/admin/team_members/new.html.haml
new file mode 100644
index 00000000..1321f42e
--- /dev/null
+++ b/app/views/admin/team_members/new.html.haml
@@ -0,0 +1,5 @@
+%h1 New team member
+
+= render 'form'
+
+= link_to 'Back', admin_team_members_path
diff --git a/app/views/admin/team_members/show.html.haml b/app/views/admin/team_members/show.html.haml
new file mode 100644
index 00000000..bd30c7b0
--- /dev/null
+++ b/app/views/admin/team_members/show.html.haml
@@ -0,0 +1,32 @@
+%p#notice= notice
+
+.span-10
+ %p
+ %b Name:
+ = @admin_team_member.user_name
+ %p
+ %b Project:
+ = @admin_team_member.project.name
+ %p
+ %b Since:
+ = @admin_team_member.updated_at
+
+
+.span-10
+ .span-6
+ %b Access:
+ .span-8
+ = check_box_tag "read", 1, @admin_team_member.read, :disabled => :disabled
+ Web Access (Browse Repo)
+ .span-8
+ = check_box_tag "commit", 1, @admin_team_member.write, :disabled => :disabled
+ Git Access (User will be added to commiters list)
+ .span-6.append-bottom
+ = check_box_tag "admin", 1, @admin_team_member.admin, :disabled => :disabled
+ Admin (Can manage project)
+
+%hr
+
+= link_to 'Edit', edit_admin_team_member_path(@admin_project)
+\|
+= link_to 'Back', admin_team_members_path
diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml
new file mode 100644
index 00000000..a5e4d8ab
--- /dev/null
+++ b/app/views/admin/users/_form.html.haml
@@ -0,0 +1,38 @@
+.user_new
+ = form_for [:admin, @admin_user] do |f|
+ -if @admin_user.errors.any?
+ #error_explanation
+ %h2= "#{pluralize(@admin_user.errors.count, "error")} prohibited this admin_user from being saved:"
+ %ul
+ - @admin_user.errors.full_messages.each do |msg|
+ %li= msg
+
+ .span-24
+ .span-11.colborder
+ .field
+ = f.label :name
+ %br
+ = f.text_field :name
+ .field
+ = f.label :email
+ %br
+ = f.text_field :email
+ .field
+ = f.label :password
+ %br
+ = f.password_field :password
+ .field
+ = f.label :password_confirmation
+ %br
+ = f.password_field :password_confirmation
+ .span-11
+ .field.prepend-top.append-bottom
+ = f.check_box :admin
+ = f.label :admin
+ .field.prepend-top
+ = f.check_box :allowed_create_repo, :disabled => true
+ = f.label :allowed_create_repo
+ .clear
+ %br
+ .actions
+ = f.submit 'Save', :class => "lbutton"
diff --git a/app/views/admin/users/edit.html.haml b/app/views/admin/users/edit.html.haml
new file mode 100644
index 00000000..fac99517
--- /dev/null
+++ b/app/views/admin/users/edit.html.haml
@@ -0,0 +1,4 @@
+= render 'form'
+
+= link_to 'Show', [:admin, @admin_user], :class => "right lbutton"
+= link_to 'Back', admin_users_path, :class => "right lbutton"
diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml
new file mode 100644
index 00000000..db60a613
--- /dev/null
+++ b/app/views/admin/users/index.html.haml
@@ -0,0 +1,24 @@
+%table
+ %tr
+ %th Admin
+ %th Name
+ %th Email
+ %th Projects
+ %th
+ %th
+ %th
+
+ - @admin_users.each do |user|
+ %tr
+ %td= check_box_tag "admin", 1, user.admin, :disabled => :disabled
+ %td= user.name
+ %td= user.email
+ %td= user.users_projects.count
+ %td= link_to 'Show', [:admin, user]
+ %td= link_to 'Edit', edit_admin_user_path(user), :id => "edit_#{dom_id(user)}"
+ %td= link_to 'Destroy', [:admin, user], :confirm => 'Are you sure?', :method => :delete
+
+%br
+
+= paginate @admin_users
+= link_to 'New User', new_admin_user_path
diff --git a/app/views/admin/users/new.html.haml b/app/views/admin/users/new.html.haml
new file mode 100644
index 00000000..cec6e3e1
--- /dev/null
+++ b/app/views/admin/users/new.html.haml
@@ -0,0 +1,6 @@
+%h1 New user
+
+= render 'form'
+
+
+= link_to 'Back', admin_users_path, :class => "right lbutton"
diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml
new file mode 100644
index 00000000..f41fc51b
--- /dev/null
+++ b/app/views/admin/users/show.html.haml
@@ -0,0 +1,42 @@
+%p#notice= notice
+
+.span-8.colborder
+ %p
+ %b Name:
+ = @admin_user.name
+ %p
+ %b Email:
+ = @admin_user.email
+ %p
+ %b Admin:
+ = @admin_user.admin
+
+ .clear
+ = link_to 'Edit', edit_admin_user_path(@admin_user)
+ \|
+ = link_to 'Back', admin_users_path
+
+.span-14
+ %h2 Projects
+
+ %table.round-borders
+ %tr
+ %th Name
+ %th Added
+ %th Web
+ %th Git
+ %th Admin
+ %th
+
+ - @admin_user.users_projects.each do |tm|
+ - project = tm.project
+ %tr
+ %td= link_to project.name, admin_project_path(project)
+ %td= time_ago_in_words(tm.updated_at) + " ago"
+ %td= check_box_tag "read", 1, project.readers.include?(@admin_user), :disabled => :disabled
+ %td= check_box_tag "commit", 1, project.writers.include?(@admin_usertm), :disabled => :disabled
+ %td.span-2= check_box_tag "admin", 1, project.admins.include?(@admin_user), :disabled => :disabled
+ %td= link_to 'Edit', edit_admin_team_member_path(tm)
+ %td= link_to 'Cancel', admin_team_member_path(tm), :confirm => 'Are you sure?', :method => :delete
+
+ = link_to 'Add To Another Project', new_admin_team_member_path(:team_member => {:user_id => @admin_user.id})
diff --git a/app/views/commits/_commits.html.haml b/app/views/commits/_commits.html.haml
new file mode 100644
index 00000000..4eebb83a
--- /dev/null
+++ b/app/views/commits/_commits.html.haml
@@ -0,0 +1,22 @@
+- @commits.group_by { |c| c.committed_date.to_date }.each do |day, commits|
+ .day-commits-table
+ .day-header
+ %h3= day.stamp("28 Aug, 2010")
+ %ul
+ - commits.each do |commit|
+ %li{ :class => "commit", :url => project_commit_path(@project, :id => commit.id) }
+ - 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;"
+ %p
+ %strong
+ = commit.message.length > 60 ? (commit.message[0..59] + "...") : commit.message
+ = link_to "Browse Code", tree_project_path(@project, :commit_id => commit.id), :class => "lite_button", :style => "float:right"
+ = link_to truncate(commit.id.to_s, :length => 16), project_commit_path(@project, :id => commit.id), :class => "lite_button", :style => "width:120px;float:right"
+ %span
+ %span
+ [ #{commit.author} ]
+ = time_ago_in_words(commit.committed_date)
+ ago
+= more_commits_link if @commits.size > 99
diff --git a/app/views/commits/_diff.html.haml b/app/views/commits/_diff.html.haml
new file mode 100644
index 00000000..dff99bf1
--- /dev/null
+++ b/app/views/commits/_diff.html.haml
@@ -0,0 +1,58 @@
+- require "utils"
+.file_stats
+ - @commit.diffs.each do |diff|
+ - if diff.deleted_file
+ %span.removed_file
+ %a{:href => "##{diff.a_path}"}
+ = diff.a_path
+ = image_tag "blueprint_delete.png"
+ - elsif diff.renamed_file
+ %span.moved_file
+ %a{:href => "##{diff.b_path}"}
+ = diff.a_path
+ = "->"
+ = diff.b_path
+ = image_tag "blueprint_notice.png"
+ - elsif diff.new_file
+ %span.new_file
+ %a{:href => "##{diff.b_path}"}
+ = diff.b_path
+ = image_tag "blueprint_add.png"
+ - else
+ %span.edit_file
+ %a{:href => "##{diff.b_path}"}
+ = diff.b_path
+ = image_tag "blueprint_info.png"
+- @commit.diffs.each do |diff|
+ - next if diff.diff.empty?
+ - file = (@commit.tree / diff.b_path)
+ - next unless file
+ .diff_file
+ .diff_file_header
+ - if diff.deleted_file
+ %strong{:id => "#{diff.b_path}"}= diff.a_path
+ - else
+ %strong{:id => "#{diff.b_path}"}= diff.b_path
+ %br/
+ .diff_file_content
+ - if file.mime_type =~ /application|text/ && !Utils.binary?(file.data)
+ - lines_arr = diff.diff.lines.to_a
+ - line_old = lines_arr[2].match(/-(\d)/)[0].to_i.abs rescue 0
+ - line_new = lines_arr[2].match(/\+(\d)/)[0].to_i.abs rescue 0
+ - lines = lines_arr[3..-1].join
+ - lines.each_line do |line|
+ = diff_line(line, line_new, line_old)
+ - if line[0] == "+"
+ - line_new += 1
+ - elsif
+ - line[0] == "-"
+ - line_old += 1
+ - else
+ - line_new += 1
+ - line_old += 1
+ - elsif file.mime_type =~ /image/
+ .diff_file_content_image
+ %img{:src => "data:image/jpeg;base64,#{Base64.encode64(file.data)}"}
+ - else
+ %p
+ %center No preview for this file type
diff --git a/app/views/commits/_index.html.haml b/app/views/commits/_index.html.haml
new file mode 100644
index 00000000..e4c9cc6e
--- /dev/null
+++ b/app/views/commits/_index.html.haml
@@ -0,0 +1,9 @@
+= form_tag project_commits_path(@project), :method => :get do
+ %h3
+ = @project.name
+ [ #{select_tag "branch", options_for_select(@repo.heads.map(&:name), @branch), :onchange => "this.form.submit();", :class => "small"} ]
+= link_to 'Back', project_path(@project), :class => "button"
+%h1 Listing commits
+%div{:id => dom_id(@project)}
+ = render "commits"
+%br/
\ No newline at end of file
diff --git a/app/views/commits/index.html.haml b/app/views/commits/index.html.haml
new file mode 100644
index 00000000..95579119
--- /dev/null
+++ b/app/views/commits/index.html.haml
@@ -0,0 +1,13 @@
+%div
+ %h3
+ .left
+ = form_tag project_commits_path(@project), :method => :get do
+ = select_tag "branch", options_for_select(@repo.heads.map(&:name), @branch), :onchange => "this.form.submit();", :class => "", :prompt => "Branches"
+
+ .left.prepend-1
+ = form_tag project_commits_path(@project), :method => :get do
+ = select_tag "tag", options_for_select(@project.tags, @branch), :onchange => "this.form.submit();", :class => "", :prompt => "Tags"
+ = text_field_tag "ssh", @project.url_to_repo, :class => ["ssh_project_url", "one_click_select"]
+ .clear
+ %div{:id => dom_id(@project)}
+ = render "commits"
diff --git a/app/views/commits/index.js.erb b/app/views/commits/index.js.erb
new file mode 100644
index 00000000..94daa396
--- /dev/null
+++ b/app/views/commits/index.js.erb
@@ -0,0 +1,2 @@
+$("#more-commits-link").remove();
+$('#<%= dom_id(@project)%>').append('<%= escape_javascript(render("commits")) %>');
diff --git a/app/views/commits/show.html.haml b/app/views/commits/show.html.haml
new file mode 100644
index 00000000..147aaafb
--- /dev/null
+++ b/app/views/commits/show.html.haml
@@ -0,0 +1,39 @@
+%h3
+ = "[ #{@commit.committer} ] #{truncate @commit.message, :length => 80}"
+-#= link_to 'Back', project_commits_path(@project), :class => "button"
+%table.round-borders
+ %tr
+ %td ID
+ %td= @commit.id
+ %tr
+ %td Author
+ %td= @commit.author
+ %tr
+ %td Commiter
+ %td= @commit.committer
+ %tr
+ %td Commited Date
+ %td= @commit.committed_date
+ %tr
+ %td Message
+ %td= @commit.message
+ %tr
+ %td Tree
+ %td= link_to 'Browse Code', tree_project_path(@project, :commit_id => @commit.id)
+.clear
+
+#tabs
+ %ul
+ %li
+ %a{ :href => "#tabs-1" } Diff
+ %li
+ %a{ :href => "#tabs-2" } Comments
+ %span{ :class => "notes_count" }= @notes.count
+ %hr
+ #tabs-1
+ = render "commits/diff"
+ #tabs-2
+ = render "notes/notes"
+
+:javascript
+ $(function() { $( "#tabs" ).tabs(); });
diff --git a/app/views/commits/show.js.haml b/app/views/commits/show.js.haml
new file mode 100644
index 00000000..2c46689b
--- /dev/null
+++ b/app/views/commits/show.js.haml
@@ -0,0 +1,6 @@
+:plain
+ $("#side-commit-preview").remove();
+ var side = $("
");
+ side.html("#{escape_javascript(render "commits/show")}");
+ $("##{dom_id(@project)}").parent().append(side);
+ $("##{dom_id(@project)}").addClass("span-14");
diff --git a/app/views/dashboard/index.html.haml b/app/views/dashboard/index.html.haml
new file mode 100644
index 00000000..e69de29b
diff --git a/app/views/devise/confirmations/new.html.erb b/app/views/devise/confirmations/new.html.erb
new file mode 100644
index 00000000..b7ae403c
--- /dev/null
+++ b/app/views/devise/confirmations/new.html.erb
@@ -0,0 +1,12 @@
+Resend confirmation instructions
+
+<%= form_for(resource, :as => resource_name, :url => confirmation_path(resource_name), :html => { :method => :post }) do |f| %>
+ <%= devise_error_messages! %>
+
+ <%= f.label :email %>
+ <%= f.email_field :email %>
+
+ <%= f.submit "Resend confirmation instructions" %>
+<% end %>
+
+<%= render :partial => "devise/shared/links" %>
\ No newline at end of file
diff --git a/app/views/devise/mailer/confirmation_instructions.html.erb b/app/views/devise/mailer/confirmation_instructions.html.erb
new file mode 100644
index 00000000..a6ea8ca1
--- /dev/null
+++ b/app/views/devise/mailer/confirmation_instructions.html.erb
@@ -0,0 +1,5 @@
+Welcome <%= @resource.email %>!
+
+You can confirm your account through the link below:
+
+<%= link_to 'Confirm my account', confirmation_url(@resource, :confirmation_token => @resource.confirmation_token) %>
diff --git a/app/views/devise/mailer/reset_password_instructions.html.erb b/app/views/devise/mailer/reset_password_instructions.html.erb
new file mode 100644
index 00000000..ae9e888a
--- /dev/null
+++ b/app/views/devise/mailer/reset_password_instructions.html.erb
@@ -0,0 +1,8 @@
+Hello <%= @resource.email %>!
+
+Someone has requested a link to change your password, and you can do this through the link below.
+
+<%= link_to 'Change my password', edit_password_url(@resource, :reset_password_token => @resource.reset_password_token) %>
+
+If you didn't request this, please ignore this email.
+Your password won't change until you access the link above and create a new one.
diff --git a/app/views/devise/mailer/unlock_instructions.html.erb b/app/views/devise/mailer/unlock_instructions.html.erb
new file mode 100644
index 00000000..2263c219
--- /dev/null
+++ b/app/views/devise/mailer/unlock_instructions.html.erb
@@ -0,0 +1,7 @@
+Hello <%= @resource.email %>!
+
+Your account has been locked due to an excessive amount of unsuccessful sign in attempts.
+
+Click the link below to unlock your account:
+
+<%= link_to 'Unlock my account', unlock_url(@resource, :unlock_token => @resource.unlock_token) %>
diff --git a/app/views/devise/passwords/edit.html.erb b/app/views/devise/passwords/edit.html.erb
new file mode 100644
index 00000000..e75c9371
--- /dev/null
+++ b/app/views/devise/passwords/edit.html.erb
@@ -0,0 +1,16 @@
+Change your password
+
+<%= form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :put }) do |f| %>
+ <%= devise_error_messages! %>
+ <%= f.hidden_field :reset_password_token %>
+
+ <%= f.label :password, "New password" %>
+ <%= f.password_field :password %>
+
+ <%= f.label :password_confirmation, "Confirm new password" %>
+ <%= f.password_field :password_confirmation %>
+
+ <%= f.submit "Change my password" %>
+<% end %>
+
+<%= render :partial => "devise/shared/links" %>
\ No newline at end of file
diff --git a/app/views/devise/passwords/new.html.erb b/app/views/devise/passwords/new.html.erb
new file mode 100644
index 00000000..bccf0181
--- /dev/null
+++ b/app/views/devise/passwords/new.html.erb
@@ -0,0 +1,15 @@
+Forgot your password?
+
+
+ <%= form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :post }) do |f| %>
+ <%= devise_error_messages! %>
+
+
<%= f.label :email %>
+ <%= f.email_field :email %>
+
+
<%= f.submit "Send me reset password instructions", :class => "lbutton vm" %>
+ <% end %>
+
+
+ <%= render :partial => "devise/shared/links" %>
+
diff --git a/app/views/devise/registrations/edit.html.erb b/app/views/devise/registrations/edit.html.erb
new file mode 100644
index 00000000..dd26e8a4
--- /dev/null
+++ b/app/views/devise/registrations/edit.html.erb
@@ -0,0 +1,28 @@
+Edit <%= resource_name.to_s.humanize %>
+
+<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put }) do |f| %>
+ <%= devise_error_messages! %>
+
+ <%= f.label :email %>
+ <%= f.email_field :email %>
+
+ <%= f.label :name %>
+ <%= f.text_field :name %>
+
+ <%= f.label :password %> (leave blank if you don't want to change it)
+ <%= f.password_field :password %>
+
+ <%= f.label :password_confirmation %>
+ <%= f.password_field :password_confirmation %>
+
+ <%= f.label :current_password %> (we need your current password to confirm your changes)
+ <%= f.password_field :current_password %>
+
+<%= f.submit "Update", :class => "input_button" %>
+<% end %>
+
+Cancel my account
+
+Unhappy? <%= link_to "Cancel my account", registration_path(resource_name), :confirm => "Are you sure?", :method => :delete %>.
+
+<%= link_to "Back", :back %>
diff --git a/app/views/devise/registrations/new.html.erb b/app/views/devise/registrations/new.html.erb
new file mode 100644
index 00000000..4ac617c5
--- /dev/null
+++ b/app/views/devise/registrations/new.html.erb
@@ -0,0 +1,18 @@
+Sign up
+
+<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f| %>
+ <%= devise_error_messages! %>
+
+ <%= f.label :email %>
+ <%= f.email_field :email %>
+
+ <%= f.label :password %>
+ <%= f.password_field :password %>
+
+ <%= f.label :password_confirmation %>
+ <%= f.password_field :password_confirmation %>
+
+<%= f.submit "Sign up", :class => "input_button" %>
+<% end %>
+
+<%= render :partial => "devise/shared/links" %>
diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb
new file mode 100644
index 00000000..21de54f0
--- /dev/null
+++ b/app/views/devise/sessions/new.html.erb
@@ -0,0 +1,20 @@
+Sign in
+
+
+ <%= form_for(resource, :as => resource_name, :url => session_path(resource_name)) do |f| %>
+
<%= f.label :email %>
+ <%= f.text_field :email %>
+
+
<%= f.label :password %>
+ <%= f.password_field :password %>
+
+ <% if devise_mapping.rememberable? -%>
+
<%= f.check_box :remember_me %> <%= f.label :remember_me %>
+ <% end -%>
+
+
<%= f.submit "Sign in", :class => "lbutton vm" %>
+ <% end %>
+
+
+ <%= render :partial => "devise/shared/links" %>
+
diff --git a/app/views/devise/shared/_links.erb b/app/views/devise/shared/_links.erb
new file mode 100644
index 00000000..eab783a4
--- /dev/null
+++ b/app/views/devise/shared/_links.erb
@@ -0,0 +1,25 @@
+<%- if controller_name != 'sessions' %>
+ <%= link_to "Sign in", new_session_path(resource_name) %>
+<% end -%>
+
+<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
+ <%= link_to "Sign up", new_registration_path(resource_name) %>
+<% end -%>
+
+<%- if devise_mapping.recoverable? && controller_name != 'passwords' %>
+ <%= link_to "Forgot your password?", new_password_path(resource_name) %>
+<% end -%>
+
+<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
+ <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %>
+<% end -%>
+
+<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
+ <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>
+<% end -%>
+
+<%- if devise_mapping.omniauthable? %>
+ <%- resource_class.omniauth_providers.each do |provider| %>
+ <%= link_to "Sign in with #{provider.to_s.titleize}", omniauth_authorize_path(resource_name, provider) %>
+ <% end -%>
+<% end -%>
\ No newline at end of file
diff --git a/app/views/devise/unlocks/new.html.erb b/app/views/devise/unlocks/new.html.erb
new file mode 100644
index 00000000..c6cdcfe5
--- /dev/null
+++ b/app/views/devise/unlocks/new.html.erb
@@ -0,0 +1,12 @@
+Resend unlock instructions
+
+<%= form_for(resource, :as => resource_name, :url => unlock_path(resource_name), :html => { :method => :post }) do |f| %>
+ <%= devise_error_messages! %>
+
+ <%= f.label :email %>
+ <%= f.email_field :email %>
+
+ <%= f.submit "Resend unlock instructions" %>
+<% end %>
+
+<%= render :partial => "devise/shared/links" %>
\ No newline at end of file
diff --git a/app/views/issues/_form.html.haml b/app/views/issues/_form.html.haml
new file mode 100644
index 00000000..71acdba1
--- /dev/null
+++ b/app/views/issues/_form.html.haml
@@ -0,0 +1,24 @@
+%div
+ = form_for [@project, @issue], :remote => "true" do |f|
+ -if @issue.errors.any?
+ %ul
+ - @issue.errors.full_messages.each do |msg|
+ %li= msg
+
+ .span-6
+ = f.label :title
+ = f.text_field :title, :style => "width:450px"
+ .span-6
+ = f.label :content
+ = f.text_area :content, :style => "width:450px; height:130px"
+ .span-6.append-bottom
+ = f.label :assignee_id
+ = f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { :include_blank => "Select user" })
+ - unless @issue.new_record?
+ .span-3.right
+ = f.label :closed
+ %br
+ = f.check_box :closed
+ %hr
+ .span-6
+ = f.submit 'Save', :class => "lbutton vm"
diff --git a/app/views/issues/_issues.html.haml b/app/views/issues/_issues.html.haml
new file mode 100644
index 00000000..d3e21940
--- /dev/null
+++ b/app/views/issues/_issues.html.haml
@@ -0,0 +1,10 @@
+%table.round-borders#issues-table
+ %tr
+ %th Assignee
+ %th ID
+ %th Title
+ %th Closed?
+ %th
+
+ - @issues.each do |issue|
+ = render(:partial => 'show', :locals => {:issue => issue})
diff --git a/app/views/issues/_show.html.haml b/app/views/issues/_show.html.haml
new file mode 100644
index 00000000..131d0d4c
--- /dev/null
+++ b/app/views/issues/_show.html.haml
@@ -0,0 +1,18 @@
+%tr{ :id => dom_id(issue), :class => "issue", :url => project_issue_path(@project, issue) }
+ %td
+ = image_tag gravatar_icon(issue.assignee.email), :class => "left", :width => 40, :style => "padding:0 5px;"
+ = truncate issue.assignee.name, :lenght => 20
+ %td ##{issue.id}
+ %td= html_escape issue.title
+ %td
+ - if can? current_user, :write_issue, @project
+ = form_for([@project, issue], :remote => true) do |f|
+ = f.check_box :closed, :onclick => "$(this).parent().submit();"
+ = hidden_field_tag :status_only, true
+ - else
+ = check_box_tag "closed", 1, issue.closed, :disabled => true
+ %td
+ - if can?(current_user, :admin_issue, @project) || issue.author == current_user
+ = link_to 'Edit', edit_project_issue_path(@project, issue), :class => "lbutton positive", :remote => true
+ - if can?(current_user, :admin_issue, @project) || issue.author == current_user
+ = link_to 'Destroy', [@project, issue], :confirm => 'Are you sure?', :method => :delete, :remote => true, :class => "lbutton delete-issue negative", :id => "destroy_issue_#{issue.id}"
diff --git a/app/views/issues/create.js.haml b/app/views/issues/create.js.haml
new file mode 100644
index 00000000..3713a8af
--- /dev/null
+++ b/app/views/issues/create.js.haml
@@ -0,0 +1,9 @@
+- if @issue.valid?
+ :plain
+ $("#new_issue_dialog").dialog("close");
+ $.ajax({type: "GET", url: location.href, dataType: "script"});
+- else
+ :plain
+ $("#new_issue_dialog").empty();
+ $("#new_issue_dialog").append("#{escape_javascript(render('form'))}");
+ $('select#issue_assignee_id').selectmenu({width:300});
diff --git a/app/views/issues/edit.js.haml b/app/views/issues/edit.js.haml
new file mode 100644
index 00000000..f08e3217
--- /dev/null
+++ b/app/views/issues/edit.js.haml
@@ -0,0 +1,12 @@
+:plain
+ var edit_issue_dialog = $("
");
+ edit_issue_dialog.html("#{escape_javascript(render('form'))}");
+ $(edit_issue_dialog).dialog({
+ width: 500,
+ resizable: false,
+ draggable: false,
+ title: "Issue ##{@issue.id} #{"[CLOSED]" if @issue.closed}",
+ close: function(event, ui) { $("#edit_issue_dialog").remove();},
+ modal: true
+ });
+ $('select#issue_assignee_id').selectmenu({width:300});
diff --git a/app/views/issues/index.html.haml b/app/views/issues/index.html.haml
new file mode 100644
index 00000000..7157d2c3
--- /dev/null
+++ b/app/views/issues/index.html.haml
@@ -0,0 +1,24 @@
+%div
+ - if can? current_user, :write_issue, @project
+ .left= link_to 'New Issue', new_project_issue_path(@project), :remote => true, :class => "lbutton vm"
+ .right
+ = form_tag project_issues_path(@project), :method => :get do
+ .span-2
+ = radio_button_tag :f, 0, (params[:f] || "0") == "0", :onclick => "this.form.submit()", :id => "open_issues"
+ = label_tag "open_issues","Open"
+ .span-2
+ = radio_button_tag :f, 2, params[:f] == "2", :onclick => "this.form.submit()", :id => "closed_issues"
+ = label_tag "closed_issues","Closed"
+ .span-2
+ = radio_button_tag :f, 3, params[:f] == "3", :onclick => "this.form.submit()", :id => "my_issues"
+ = label_tag "my_issues","To Me"
+
+ .span-2
+ = radio_button_tag :f, 1, params[:f] == "1", :onclick => "this.form.submit()", :id => "all_issues"
+ = label_tag "all_issues","All"
+
+ #issues-table-holder= render "issues"
+ %br
+:javascript
+ $('.delete-issue').live('ajax:success', function() {
+ $(this).closest('tr').fadeOut(); });
diff --git a/app/views/issues/index.js.haml b/app/views/issues/index.js.haml
new file mode 100644
index 00000000..1f051309
--- /dev/null
+++ b/app/views/issues/index.js.haml
@@ -0,0 +1,2 @@
+:plain
+ $('#issues-table-holder').html("#{escape_javascript(render('issues'))}");
diff --git a/app/views/issues/new.js.haml b/app/views/issues/new.js.haml
new file mode 100644
index 00000000..53064144
--- /dev/null
+++ b/app/views/issues/new.js.haml
@@ -0,0 +1,12 @@
+:plain
+ var new_issue_dialog = $("
");
+ new_issue_dialog.html("#{escape_javascript(render('form'))}");
+ $(new_issue_dialog).dialog({
+ width: 500,
+ resizable: false,
+ draggable: false,
+ title: "Add new issue",
+ modala: true,
+ close: function(event, ui) { $("#new_issue_dialog").remove();}
+ });
+ $('select#issue_assignee_id').selectmenu({width:300});
diff --git a/app/views/issues/show.html.haml b/app/views/issues/show.html.haml
new file mode 100644
index 00000000..199c80c8
--- /dev/null
+++ b/app/views/issues/show.html.haml
@@ -0,0 +1,44 @@
+%h2
+ = "Issue ##{@issue.id} - #{@issue.title}"
+
+.span-15
+ = simple_format html_escape(@issue.content)
+ .issue_notes= render "notes/notes"
+.span-8.right
+ .span-8
+ - if @issue.closed
+ %center.success Closed
+ - else
+ %center.error Open
+ %table.round-borders
+ %tr
+ %td Title:
+ %td
+ = truncate html_escape(@issue.title)
+ %tr
+ %td Project
+ %td
+ %strong= @issue.project.name
+ %tr
+ %td Author:
+ %td
+ = image_tag gravatar_icon(@issue.author.email), :class => "left", :width => 40, :style => "padding:0 5px;"
+ = @issue.author.name
+ %tr
+ %td Assignee:
+ %td
+ = image_tag gravatar_icon(@issue.assignee.email), :class => "left", :width => 40, :style => "padding:0 5px;"
+ = @issue.assignee.name
+ %tr
+ %td Closed?
+ %td
+ - if can? current_user, :write_issue, @project
+ = form_for([@project, @issue]) do |f|
+ = f.check_box :closed, :onclick => "$(this).parent().submit();"
+ = hidden_field_tag :status_only, true
+ - else
+ = check_box_tag "closed", 1, @issue.closed, :disabled => true
+
+
+.clear
+
diff --git a/app/views/issues/update.js.haml b/app/views/issues/update.js.haml
new file mode 100644
index 00000000..c365c3f5
--- /dev/null
+++ b/app/views/issues/update.js.haml
@@ -0,0 +1,14 @@
+- if params[:status_only]
+ - if @issue.valid?
+ :plain
+ $("##{dom_id(@issue)}").fadeOut();
+- else
+ - if @issue.valid?
+ :plain
+ $("#edit_issue_dialog").dialog("close");
+ $.ajax({type: "GET", url: location.href, dataType: "script"});
+ - else
+ :plain
+ $("#edit_issue_dialog").empty();
+ $("#edit_issue_dialog").append("#{escape_javascript(render('form'))}");
+ $('select#issue_assignee_id').selectmenu({width:300});
diff --git a/app/views/keys/_form.html.haml b/app/views/keys/_form.html.haml
new file mode 100644
index 00000000..7d3e14ef
--- /dev/null
+++ b/app/views/keys/_form.html.haml
@@ -0,0 +1,16 @@
+%div
+ = form_for @key, :remote => true do |f|
+ -if @key.errors.any?
+ %ul
+ - @key.errors.full_messages.each do |msg|
+ %li= msg
+
+ .span-6
+ = f.label :title
+ = f.text_field :title, :style => "width:300px"
+ .span-6
+ = f.label :key
+ = f.text_area :key, :style => "width:300px; height:130px"
+ .span-6
+ = f.submit 'Save', :class => "lbutton vm"
+
diff --git a/app/views/keys/_show.html.haml b/app/views/keys/_show.html.haml
new file mode 100644
index 00000000..a4e23625
--- /dev/null
+++ b/app/views/keys/_show.html.haml
@@ -0,0 +1,4 @@
+%tr
+ %td= truncate key.title, :lenght => 12
+ %td= truncate key.key, :lenght => 1114
+ %td= link_to 'Cancel', key, :confirm => 'Are you sure?', :method => :delete, :class => "lbutton negative delete-key", :id => "destroy_key_#{key.id}", :remote => true
diff --git a/app/views/keys/create.js.haml b/app/views/keys/create.js.haml
new file mode 100644
index 00000000..7b92be77
--- /dev/null
+++ b/app/views/keys/create.js.haml
@@ -0,0 +1,8 @@
+- if @key.valid?
+ :plain
+ $("#new_key_dialog").dialog("close");
+ $("#keys-table").append("#{escape_javascript(render(:partial => 'show', :locals => {:key => @key} ))}");
+- else
+ :plain
+ $("#new_key_dialog").empty();
+ $("#new_key_dialog").append("#{escape_javascript(render('form'))}");
diff --git a/app/views/keys/edit.html.haml b/app/views/keys/edit.html.haml
new file mode 100644
index 00000000..60a3afed
--- /dev/null
+++ b/app/views/keys/edit.html.haml
@@ -0,0 +1,7 @@
+%h1 Editing key
+
+= render 'form'
+
+= link_to 'Show', @key
+\|
+= link_to 'Back', keys_path
diff --git a/app/views/keys/index.html.haml b/app/views/keys/index.html.haml
new file mode 100644
index 00000000..416ae8f8
--- /dev/null
+++ b/app/views/keys/index.html.haml
@@ -0,0 +1,15 @@
+%div#new-key-holder
+ = link_to "Add new", new_key_path, :remote => true, :class => "lbutton vm"
+
+%table.round-borders#keys-table
+ %tr
+ %th title
+ %th key
+ %th Actions
+ - @keys.each do |key|
+ = render(:partial => 'show', :locals => {:key => key})
+
+:javascript
+ $('.delete-key').live('ajax:success', function() {
+ $(this).closest('tr').fadeOut(); });
+
diff --git a/app/views/keys/new.html.haml b/app/views/keys/new.html.haml
new file mode 100644
index 00000000..fed448b4
--- /dev/null
+++ b/app/views/keys/new.html.haml
@@ -0,0 +1,5 @@
+%h1 New key
+
+= render 'form'
+
+= link_to 'Back', keys_path
diff --git a/app/views/keys/new.js.haml b/app/views/keys/new.js.haml
new file mode 100644
index 00000000..86e9db03
--- /dev/null
+++ b/app/views/keys/new.js.haml
@@ -0,0 +1,11 @@
+:plain
+ var new_key_dialog = $("
");
+ new_key_dialog.html("#{escape_javascript(render('form'))}");
+ $(new_key_dialog).dialog({
+ width: 350,
+ resizable: false,
+ draggable: false,
+ title: "Add new public key",
+ close: function(event, ui) { $("#new_key_dialog").remove();},
+ modal: true
+ });
diff --git a/app/views/layouts/_flash.html.haml b/app/views/layouts/_flash.html.haml
new file mode 100644
index 00000000..4b6eb2b5
--- /dev/null
+++ b/app/views/layouts/_flash.html.haml
@@ -0,0 +1,18 @@
+- if alert || notice
+ - text = alert || notice
+ %div{:style => "display:none", :id => "flash_container"}
+ .container
+ %center
+ %h4= text
+ :javascript
+ $(function(){
+ $("#flash_container").slideDown("slow");
+ $("#flash_container").click(function(){
+ $(this).slideUp("slow");
+ });
+ setTimeout("hideFlash()",2000);
+ });
+
+ function hideFlash(){
+ $("#flash_container").slideUp("slow");
+ }
diff --git a/app/views/layouts/_head_panel.html.erb b/app/views/layouts/_head_panel.html.erb
new file mode 100644
index 00000000..f32e4bc2
--- /dev/null
+++ b/app/views/layouts/_head_panel.html.erb
@@ -0,0 +1,34 @@
+
+
+
+<% if current_user %>
+ <%= javascript_tag do %>
+ $(function() {
+ $("#search" ).autocomplete({
+ source: <%= raw search_autocomplete_source %>,
+ select: function(event, ui) { location.href = ui.item.url }
+ });
+ });
+ <% end %>
+<% end %>
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
new file mode 100644
index 00000000..8ac413a1
--- /dev/null
+++ b/app/views/layouts/application.html.haml
@@ -0,0 +1,28 @@
+!!!
+%html
+ %head
+ %title
+ GitLab #{" - #{@project.name}" if @project && !@project.new_record?}
+ = stylesheet_link_tag 'blueprint/screen', :media => "screen, projection"
+ = stylesheet_link_tag 'blueprint/print', :media => "print"
+ = stylesheet_link_tag 'blueprint/plugins/buttons/screen', :media => "screen, projection"
+ = stylesheet_link_tag 'blueprint/plugins/link-icons/screen', :media => "screen, projection"
+ = stylesheet_link_tag 'jquery_ui/jquery-ui-1.8.16.custom', :media => "screen, projection"
+ = stylesheet_link_tag "application"
+ = javascript_include_tag "application"
+ = csrf_meta_tags
+ %link{:href => "/assets/favicon.png", :rel => "icon", :type => "image/png"}/
+ = javascript_tag do
+ REQ_URI = "#{request.env["REQUEST_URI"]}";
+ REQ_REFFER = "#{request.env["HTTP_REFERER"]}";
+ %body#thebody
+ = render :partial => "layouts/flash"
+ - if user_signed_in?
+ = render :partial => "layouts/head_panel"
+ .top_bar.container
+ = render :partial => "projects/top_menu" if @project && !@project.new_record?
+ = render :partial => "profile/top_menu" if ["keys", "profile"].include?(controller.controller_name)
+ = render :partial => "admin/top_menu" if admin_namespace?
+ #content-container.container
+ .span-24
+ = yield
diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml
new file mode 100644
index 00000000..0cef736c
--- /dev/null
+++ b/app/views/layouts/notify.html.haml
@@ -0,0 +1,36 @@
+%html{:lang => "en"}
+ %head
+ %meta{:content => "text/html; charset=utf-8", "http-equiv" => "Content-Type"}
+ %title
+ gitlabhq
+ :css
+ .header h1 {color: #BBBBBB !important; font: bold 32px Helvetica, Arial, sans-serif; margin: 0; padding: 0; line-height: 40px;}
+ .header p {color: #c6c6c6; font: normal 12px Helvetica, Arial, sans-serif; margin: 0; padding: 0; line-height: 18px;}
+ .content h2 {color:#646464 !important; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; }
+ .content p {color:#767676; font-weight: normal; margin: 0; padding: 0; line-height: 20px; font-size: 12px;font-family: Helvetica, Arial, sans-serif;}
+ .content a {color: #0eb6ce; text-decoration: none;}
+ .footer p {font-size: 11px; color:#7d7a7a; margin: 0; padding: 0; font-family: Helvetica, Arial, sans-serif;}
+ .footer a {color: #0eb6ce; text-decoration: none;}
+ %body{:bgcolor => "#EAEAEA", :style => "margin: 0; padding: 0; background: #EAEAEA"}
+ %table{:align => "center", :border => "0", :cellpadding => "0", :cellspacing => "0", :style => "padding: 35px 0; background: #EAEAEA;", :width => "100%"}
+ %tr
+ %td{:align => "center", :style => "margin: 0; padding: 0; background: #EAEAEA;"}
+ %table.header{:align => "center", :border => "0", :cellpadding => "0", :cellspacing => "0", :style => "font-family: Helvetica, Arial, sans-serif; background:#333", :width => "600"}
+ %tr
+ %td{:style => "font-size: 0px;", :width => "20"}
+ \Â
+ %td{:align => "left", :style => "padding: 18px 0 10px;", :width => "580"}
+ %h1{:style => "color: #BBBBBB; font: normal 32px Helvetica, Arial, sans-serif; margin: 0; padding: 0; line-height: 40px;"}
+ gitlab
+ - if @project
+ | #{@project.name}
+ %table{:align => "center", :bgcolor => "#fff", :border => "0", :cellpadding => "0", :cellspacing => "0", :style => "font-family: Helvetica, Arial, sans-serif; background: #fff;", :width => "600"}
+ %tr= yield
+ %tr
+ %td{:align => "left", :colspan => "2", :height => "3", :style => "padding: font-size: 0; line-height: 0; height: 3px;", :width => "600"}
+ %table.footer{:align => "center", :border => "0", :cellpadding => "0", :cellspacing => "0", :style => "font-family: Helvetica, Arial, sans-serif; line-height: 10px;", :width => "600"}
+ %tr
+ %td{:align => "center", :style => "padding: 5px 0 10px; font-size: 11px; color:#7d7a7a; margin: 0; line-height: 1.2;font-family: Helvetica, Arial, sans-serif;", :valign => "top"}
+ %br
+ %p{:style => "font-size: 11px; color:#7d7a7a; margin: 0; padding: 0; font-family: Helvetica, Arial, sans-serif;"}
+ You're receiving this newsletter because you are in project team.
diff --git a/app/views/notes/_form.html.haml b/app/views/notes/_form.html.haml
new file mode 100644
index 00000000..ccb159f1
--- /dev/null
+++ b/app/views/notes/_form.html.haml
@@ -0,0 +1,28 @@
+%div
+ = form_for [@project, @note], :remote => "true", :multipart => true do |f|
+ -if @note.errors.any?
+ .errors.error
+ - @note.errors.full_messages.each do |msg|
+ %div= msg
+
+ = f.hidden_field :noteable_id
+ = f.hidden_field :noteable_type
+
+ %div
+ = f.label :note
+ %cite (255 symbols only)
+ %br
+ = f.text_area :note, :style => "width:97%;height:100px", :size => 255
+
+ %div
+ = f.label :attachment
+ %cite (less than 10 MB)
+ %br
+ = f.file_field :attachment
+
+ = check_box_tag :notify, 1, true
+ = label_tag :notify, "Notify project team about your note"
+
+ .clear
+ %br
+ = f.submit 'Add note', :class => "lbutton vm"
diff --git a/app/views/notes/_notes.html.haml b/app/views/notes/_notes.html.haml
new file mode 100644
index 00000000..457bb8db
--- /dev/null
+++ b/app/views/notes/_notes.html.haml
@@ -0,0 +1,14 @@
+%ul#notes-list
+ - @notes.each do |note|
+ = render :partial => "notes/show", :locals => {:note => note}
+
+%br
+%br
+- if can? current_user, :write_note, @project
+ = render "notes/form"
+
+:javascript
+ $('.delete-note').live('ajax:success', function() {
+ $(this).closest('li').fadeOut(); });
+
+
diff --git a/app/views/notes/_show.html.haml b/app/views/notes/_show.html.haml
new file mode 100644
index 00000000..2b0a6d2e
--- /dev/null
+++ b/app/views/notes/_show.html.haml
@@ -0,0 +1,19 @@
+%li{:id => dom_id(note)}
+ %div.note_author
+ = image_tag gravatar_icon(note.author.email), :class => "left", :width => 40, :style => "padding-right:5px;"
+ %div.note_content
+ = simple_format(html_escape(note.note))
+ - if note.attachment.url
+ Attachment:
+ = link_to note.attachment_identifier, note.attachment.url
+ %br
+ %span
+ %span
+ [ #{note.author.name} ]
+
+ = time_ago_in_words(note.updated_at)
+ ago
+ %br
+ - if(note.author_id == current_user.id) || can?(current_user, :admin_note, @project)
+ = link_to 'Remove', [@project, note], :confirm => 'Are you sure?', :method => :delete, :remote => true, :class => "lbutton delete-note right negative"
+ .clear
diff --git a/app/views/notes/create.js.haml b/app/views/notes/create.js.haml
new file mode 100644
index 00000000..47cff1d8
--- /dev/null
+++ b/app/views/notes/create.js.haml
@@ -0,0 +1,8 @@
+- if @note.valid?
+ :plain
+ $("#new_note .errors").remove();
+ $("#notes-list").append("#{escape_javascript(render(:partial => 'show', :locals => {:note => @note} ))}");
+ $('#note_note').val("");
+- else
+ :plain
+ $("#new_note").replaceWith("#{escape_javascript(render('form'))}");
diff --git a/app/views/notify/new_issue_email.html.haml b/app/views/notify/new_issue_email.html.haml
new file mode 100644
index 00000000..213a7379
--- /dev/null
+++ b/app/views/notify/new_issue_email.html.haml
@@ -0,0 +1,18 @@
+%td.content{:align => "left", :style => "font-family: Helvetica, Arial, sans-serif; padding: 20px 0 0;", :valign => "top", :width => "600"}
+ %table{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "color: #717171; font: normal 11px Helvetica, Arial, sans-serif; margin: 0; padding: 0;", :width => "600"}
+ %tr
+ %td{:style => "font-size: 1px; line-height: 1px;", :width => "21"}
+ %td{:align => "left", :style => "padding: 20px 0 0;"}
+ %h2{:style => "color:#646464; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; "}
+ Hi #{@user.name}! New Issue was created and assigned to you.
+ %td{:style => "font-size: 1px; line-height: 1px;", :width => "21"}
+ %tr
+ %td{:style => "font-size: 1px; line-height: 1px;", :width => "21"}
+ %td{:align => "left", :style => "padding: 20px 0 0;"}
+ %h2{:style => "color:#646464 !important; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; "}
+ = link_to project_issue_url(@project, @issue) do
+ = "Issue ##{@issue.id.to_s}"
+ = truncate(@issue.title, :length => 45)
+ %br
+ %cite{:style => "color:#767676; font-weight: normal; margin: 0; padding: 0; line-height: 20px; font-size: 12px;font-family: Helvetica, Arial, sans-serif; "}
+ = @issue.content
diff --git a/app/views/notify/new_user_email.html.haml b/app/views/notify/new_user_email.html.haml
new file mode 100644
index 00000000..969ea7e5
--- /dev/null
+++ b/app/views/notify/new_user_email.html.haml
@@ -0,0 +1,23 @@
+%td.content{:align => "left", :style => "font-family: Helvetica, Arial, sans-serif; padding: 20px 0 0;", :valign => "top", :width => "600"}
+ %table{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "color: #717171; font: normal 11px Helvetica, Arial, sans-serif; margin: 0; padding: 0;", :width => "600"}
+ %tr
+ %td{:style => "font-size: 1px; line-height: 1px;", :width => "21"}
+ %td{:align => "left", :style => "padding: 20px 0 0;"}
+ %h2{:style => "color:#646464; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; "}
+ Hi #{@user.name}!
+ %p{:style => "color:#767676; font-weight: normal; margin: 0; padding: 0; line-height: 20px; font-size: 12px;font-family: Helvetica, Arial, sans-serif; "}
+ Administrator created account for you. Now you are a member of company gitlab application.
+ %td{:style => "font-size: 1px; line-height: 1px;", :width => "21"}
+ %tr
+ %td{:style => "font-size: 1px; line-height: 1px;", :width => "21"}
+ %td{:style => "padding: 15px 0 15px;", :valign => "top"}
+ %p{:style => "color:#767676; font-weight: normal; margin: 0; padding: 0; line-height: 28px; font-size: 16px;font-family: Helvetica, Arial, sans-serif; "}
+ login..........................................
+ %code= @user.email
+ %p{:style => "color:#767676; font-weight: normal; margin: 0; padding: 0; line-height: 28px; font-size: 16px;font-family: Helvetica, Arial, sans-serif; "}
+ password..................................
+ %code= @password
+ %p{:style => "color:#767676; font-weight: normal; margin: 0; padding: 0; line-height: 28px; font-size: 12px;font-family: Helvetica, Arial, sans-serif; "}
+ = link_to "Click here to login", root_url
+ %td{:style => "font-size: 1px; line-height: 1px;", :width => "21"}
+
diff --git a/app/views/notify/note_commit_email.html.haml b/app/views/notify/note_commit_email.html.haml
new file mode 100644
index 00000000..09ae54ac
--- /dev/null
+++ b/app/views/notify/note_commit_email.html.haml
@@ -0,0 +1,23 @@
+%td.content{:align => "left", :style => "font-family: Helvetica, Arial, sans-serif; padding: 20px 0 0;", :valign => "top", :width => "600"}
+ %table{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "color: #717171; font: normal 11px Helvetica, Arial, sans-serif; margin: 0; padding: 0;", :width => "600"}
+ %tr
+ %td{:style => "font-size: 1px; line-height: 1px;", :width => "21"}
+ %td{:align => "left", :style => "padding: 20px 0 0;"}
+ %h2{:style => "color:#646464; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; "}
+ New comment for commmit
+ = link_to truncate(@commit.id.to_s, :length => 16), project_commit_url(@project, :id => @commit.id)
+ %td{:style => "font-size: 1px; line-height: 1px;", :width => "21"}
+ %tr
+ %td{:style => "font-size: 1px; line-height: 1px;", :width => "21"}
+ %td{:style => "padding: 15px 0 15px;", :valign => "top"}
+ %p{:style => "color:#767676; font-weight: normal; margin: 0; padding: 0; line-height: 20px; font-size: 12px;font-family: Helvetica, Arial, sans-serif; "}
+ %a{:href => "#", :style => "color: #0eb6ce; text-decoration: none;"} #{@note.author.name}
+ left next message:
+ %br
+ %table{:border => "0", :cellpadding => "0", :cellspacing => "0", :width => "558"}
+ %tr
+ %td{:valign => "top"}
+ %cite{:style => "color:#767676; font-weight: normal; margin: 0; padding: 0; line-height: 20px; font-size: 12px;font-family: Helvetica, Arial, sans-serif; "}
+ = @note.note
+ %td{:style => "font-size: 1px; line-height: 1px;", :width => "21"}
+
diff --git a/app/views/notify/note_issue_email.html.haml b/app/views/notify/note_issue_email.html.haml
new file mode 100644
index 00000000..54982af6
--- /dev/null
+++ b/app/views/notify/note_issue_email.html.haml
@@ -0,0 +1,25 @@
+%td.content{:align => "left", :style => "font-family: Helvetica, Arial, sans-serif; padding: 20px 0 0;", :valign => "top", :width => "600"}
+ %table{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "color: #717171; font: normal 11px Helvetica, Arial, sans-serif; margin: 0; padding: 0;", :width => "600"}
+ %tr
+ %td{:style => "font-size: 1px; line-height: 1px;", :width => "21"}
+ %td{:align => "left", :style => "padding: 20px 0 0;"}
+ %h2{:style => "color:#646464 !important; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; "}
+ New comment -
+ = link_to project_issue_url(@project, @issue) do
+ = "Issue ##{@issue.id.to_s}"
+ = truncate(@issue.title, :length => 35)
+ %td{:style => "font-size: 1px; line-height: 1px;", :width => "21"}
+ %tr
+ %td{:style => "font-size: 1px; line-height: 1px;", :width => "21"}
+ %td{:style => "padding: 15px 0 15px;", :valign => "top"}
+ %p{:style => "color:#767676; font-weight: normal; margin: 0; padding: 0; line-height: 20px; font-size: 12px;font-family: Helvetica, Arial, sans-serif; "}
+ %a{:href => "#", :style => "color: #0eb6ce; text-decoration: none;"} #{@note.author.name}
+ left next message:
+ %br
+ %table{:border => "0", :cellpadding => "0", :cellspacing => "0", :width => "558"}
+ %tr
+ %td{:valign => "top"}
+ %cite{:style => "color:#767676; font-weight: normal; margin: 0; padding: 0; line-height: 20px; font-size: 12px;font-family: Helvetica, Arial, sans-serif; "}
+ = @note.note
+ %td{:style => "font-size: 1px; line-height: 1px;", :width => "21"}
+
diff --git a/app/views/notify/note_wall_email.html.haml b/app/views/notify/note_wall_email.html.haml
new file mode 100644
index 00000000..285fc763
--- /dev/null
+++ b/app/views/notify/note_wall_email.html.haml
@@ -0,0 +1,22 @@
+%td.content{:align => "left", :style => "font-family: Helvetica, Arial, sans-serif; padding: 20px 0 0;", :valign => "top", :width => "600"}
+ %table{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "color: #717171; font: normal 11px Helvetica, Arial, sans-serif; margin: 0; padding: 0;", :width => "600"}
+ %tr
+ %td{:style => "font-size: 1px; line-height: 1px;", :width => "21"}
+ %td{:align => "left", :style => "padding: 20px 0 0;"}
+ %h2{:style => "color:#646464; font-weight: bold; margin: 0; padding: 0; line-height: 26px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; "}
+ New message on
+ = link_to "Project Wall", wall_project_url(@project)
+ %td{:style => "font-size: 1px; line-height: 1px;", :width => "21"}
+ %tr
+ %td{:style => "font-size: 1px; line-height: 1px;", :width => "21"}
+ %td{:style => "padding: 15px 0 15px;", :valign => "top"}
+ %p{:style => "color:#767676; font-weight: normal; margin: 0; padding: 0; line-height: 20px; font-size: 12px;font-family: Helvetica, Arial, sans-serif; "}
+ %a{:href => "#", :style => "color: #0eb6ce; text-decoration: none;"} #{@note.author.name}
+ left next message:
+ %br
+ %table{:border => "0", :cellpadding => "0", :cellspacing => "0", :width => "558"}
+ %tr
+ %td{:valign => "top"}
+ %cite{:style => "color:#767676; font-weight: normal; margin: 0; padding: 0; line-height: 20px; font-size: 12px;font-family: Helvetica, Arial, sans-serif; "}
+ = @note.note
+ %td{:style => "font-size: 1px; line-height: 1px;", :width => "21"}
diff --git a/app/views/profile/_top_menu.html.haml b/app/views/profile/_top_menu.html.haml
new file mode 100644
index 00000000..6bf3b09f
--- /dev/null
+++ b/app/views/profile/_top_menu.html.haml
@@ -0,0 +1,5 @@
+%div.top_project_menu
+ %span= link_to "Profile", profile_path, :class => current_page?(:controller => "profile", :action => :show) ? "current" : nil
+ %span= link_to "Password", profile_password_path, :style => "width:70px;", :class => current_page?(:controller => "profile", :action => :password) ? "current" : nil
+ %span= link_to "Keys", keys_path, :class => controller.controller_name == "keys" ? "current" : nil
+
diff --git a/app/views/profile/index.html.haml b/app/views/profile/index.html.haml
new file mode 100644
index 00000000..84174ac5
--- /dev/null
+++ b/app/views/profile/index.html.haml
@@ -0,0 +1 @@
+%h1 Profile
diff --git a/app/views/profile/password.html.haml b/app/views/profile/password.html.haml
new file mode 100644
index 00000000..f77d3855
--- /dev/null
+++ b/app/views/profile/password.html.haml
@@ -0,0 +1,20 @@
+%p Note: after success password update you will be redirected to login page where you should login with new password
+= form_for @user, :url => profile_password_path, :method => :put do |f|
+ -if @user.errors.any?
+ #error_explanation
+ %h2= "#{pluralize(@user.errors.count, "error")} prohibited this password from being saved:"
+ %ul
+ - @user.errors.full_messages.each do |msg|
+ %li= msg
+
+ .div
+ = f.label :password
+ %br
+ = f.password_field :password
+ .div
+ = f.label :password_confirmation
+ %br
+ = f.password_field :password_confirmation
+ .actions
+ = f.submit 'Save', :class => "lbutton vm"
+
diff --git a/app/views/profile/show.html.haml b/app/views/profile/show.html.haml
new file mode 100644
index 00000000..12737ba8
--- /dev/null
+++ b/app/views/profile/show.html.haml
@@ -0,0 +1,8 @@
+.span-2
+ = image_tag gravatar_icon(@user.email), :class => "left", :width => 60, :style => "padding-right:5px;"
+%p
+ %b Name:
+ = @user.name
+%p
+ %b Email:
+ = @user.email
diff --git a/app/views/projects/_form.html.haml b/app/views/projects/_form.html.haml
new file mode 100644
index 00000000..baa1f14f
--- /dev/null
+++ b/app/views/projects/_form.html.haml
@@ -0,0 +1,50 @@
+= form_for(@project, :remote => true) do |f|
+ %div.form_content
+ - if @project.new_record?
+ %h1 New Project
+ - else
+ %h1 Edit Project
+ - if @project.errors.any?
+ #error_explanation
+ %h2
+ = pluralize(@project.errors.count, "error")
+ prohibited this project from being saved:
+ %ul
+ - @project.errors.full_messages.each do |msg|
+ %li= msg
+ %table.round-borders
+ %tr
+ %td= f.label :name
+ %td= f.text_field :name, :placeholder => "Example Project"
+ %tr
+ %td
+ .left= f.label :path
+ %cite.right git@yourserver:
+ %td
+ = f.text_field :path, :placeholder => "example_project", :disabled => !@project.new_record?
+ %tr
+ %td
+ .left= f.label :code
+ %cite.right http://yourserver/
+ %td= f.text_field :code, :placeholder => "example (3..12 symbols only)"
+ .field
+ = f.label :description
+ %br/
+ = f.text_area :description, :style => "height:140px;width:932px;"
+ .clear
+ %hr.prepend-top
+ .actions
+ = f.submit :class => "lbutton vm"
+
+ %div{ :class => "ajax_loader", :style => "display:none;height:200px;"}
+ %center
+ = image_tag "ajax-loader.gif", :class => "append-bottom"
+ - if @project.new_record?
+ %h3.prepend-top Creating project & repository. Please wait for few minutes
+ - else
+ %h3.prepend-top Updating project & repository. Please wait for few minutes
+:javascript
+ $('.new_project, .edit_project').bind('ajax:before', function() {
+ $(this).find(".form_content").hide();
+ $('.ajax_loader').show();
+ });
diff --git a/app/views/projects/_side_panel.html.haml b/app/views/projects/_side_panel.html.haml
new file mode 100644
index 00000000..2f786b4d
--- /dev/null
+++ b/app/views/projects/_side_panel.html.haml
@@ -0,0 +1,14 @@
+%h3.notice{:style => "width:235px;"}
+ = @project.name
+%p
+ %b Path:
+ = @project.path
+%p
+ %b Description:
+ = truncate @project.description
+.left.append-bottom
+ = link_to "Tree", tree_project_path(@project), :class => "button"
+ = link_to "Commits", project_commits_path(@project), :class => "button"
+ = link_to 'Team', team_project_path(@project), :class => "button"
+ - if can? current_user, :admin_project, @project
+ = link_to 'Edit', edit_project_path(@project), :class => "button positive"
diff --git a/app/views/projects/_team.html.haml b/app/views/projects/_team.html.haml
new file mode 100644
index 00000000..bb906336
--- /dev/null
+++ b/app/views/projects/_team.html.haml
@@ -0,0 +1,18 @@
+- if can? current_user, :admin_team_member, @project
+ %div#new-member-holder
+ = link_to "Add new", new_project_team_member_path(@project), :remote => true, :class => "lbutton vm"
+%table.round-borders#team-table
+ %tr
+ %th Name
+ %th Email
+ %th Web
+ %th Git
+ %th Admin
+ - if can? current_user, :admin_team_member, @project
+ %th Actions
+ - @project.users_projects.each do |up|
+ = render(:partial => 'team_members/show', :locals => {:member => up})
+
+:javascript
+ $('.delete-team-member').live('ajax:success', function() {
+ $(this).closest('tr').fadeOut(); });
diff --git a/app/views/projects/_top_menu.html.haml b/app/views/projects/_top_menu.html.haml
new file mode 100644
index 00000000..b81ba6bb
--- /dev/null
+++ b/app/views/projects/_top_menu.html.haml
@@ -0,0 +1,24 @@
+%div.top_project_menu
+ -#%span= link_to @project.code.capitalize, @project, :class => current_page?(:controller => "projects", :action => "show", :id => @project) ? "current" : nil
+ - if @project.repo_exists?
+ %span= link_to "Tree", tree_project_path(@project), :class => current_page?(:controller => "projects", :action => "show", :id => @project) || current_page?(:controller => "projects", :action => "tree", :id => @project) ? "current" : nil
+ %span= link_to "Commits", project_commits_path(@project), :class => current_page?(:controller => "commits", :action => "index", :project_id => @project) ? "current" : nil
+ %span
+ = link_to team_project_path(@project), :class => current_page?(:controller => "projects", :action => "team", :id => @project) ? "current" : nil do
+ Team
+ - if @project.users_projects.count > 0
+ %span{ :class => "top_menu_count" }= @project.users_projects.count
+ %span
+ = link_to project_issues_path(@project), :class => (controller.controller_name == "issues") ? "current" : nil do
+ Issues
+ - if @project.issues.opened.count > 0
+ %span{ :class => "top_menu_count" }= @project.issues.opened.count
+ %span
+ = link_to wall_project_path(@project), :class => current_page?(:controller => "projects", :action => "wall", :id => @project) ? "current" : nil do
+ Wall
+ - if @project.common_notes.count > 0
+ %span{ :class => "top_menu_count" }= @project.common_notes.count
+
+ - if @commit
+ %span= link_to truncate(commit_name(@project,@commit), :length => 15), project_commit_path(@project, :id => @commit.id), :class => current_page?(:controller => "commits", :action => "show", :project_id => @project, :id => @commit.id) ? "current" : nil
+
diff --git a/app/views/projects/_tree.html.haml b/app/views/projects/_tree.html.haml
new file mode 100644
index 00000000..af3a209a
--- /dev/null
+++ b/app/views/projects/_tree.html.haml
@@ -0,0 +1,60 @@
+%h3
+ .left
+ = form_tag tree_project_path(@project), :method => :get do
+ = select_tag "branch", options_for_select(@repo.heads.map(&:name), @branch), :onchange => "this.form.submit();", :class => "", :prompt => "Branches"
+
+ .left.prepend-1
+ = form_tag tree_project_path(@project), :method => :get do
+ = select_tag "tag", options_for_select(@project.tags, @branch), :onchange => "this.form.submit();", :class => "", :prompt => "Tags"
+ = text_field_tag "ssh", @project.url_to_repo, :class => ["ssh_project_url","one_click_select"]
+ .clear
+
+%h3#tree-breadcrumbs
+ = link_to @project.name, tree_project_path(@project, :path => nil, :commit_id => @commit.try(:id)), :remote => true
+ - if params[:path]
+ - part_path = ""
+ - params[:path].split("\/").each do |part|
+ - part_path = File.join(part_path, part) unless part_path.empty?
+ - if part_path.empty?
+ - part_path = part
+ \/
+ = link_to truncate(part, :length => 40), tree_file_project_path(@project, :path => part_path, :commit_id => @commit.try(:id)), :remote => :true
+#tree-content-holder
+ - if tree.is_a?(Grit::Blob)
+ = render :partial => "projects/tree_file", :locals => { :name => tree.name, :content => tree.data, :file => tree }
+ - else
+ - contents = tree.contents
+ %table#tree-slider.round-borders
+ %tr
+ %th Name
+ %th Last Update
+ %th
+ Last commit
+ = link_to "history", project_commits_path(@project, :path => params[:path]), :class => "right"
+ - if params[:path]
+ - file = File.join(params[:path], "..")
+ %tr{ :class => "tree-item", :url => tree_file_project_path(@project, @commit.id, file) }
+ %td.tree-item-file-name
+ = image_tag "dir.png"
+ = link_to "..", tree_file_project_path(@project, @commit.id, file), :remote => :true
+ %td
+ %td
+
+ - contents.select{ |i| i.is_a?(Grit::Tree)}.each do |content|
+ = render :partial => "projects/tree_item", :locals => { :content => content }
+ - contents.select{ |i| i.is_a?(Grit::Blob)}.each do |content|
+ = render :partial => "projects/tree_item", :locals => { :content => content }
+
+:javascript
+ $(function(){
+ $('select#branch').selectmenu({style:'popup', width:200});
+ $('select#tag').selectmenu({style:'popup', width:200});
+ });
+
+- if params[:path] && request.xhr?
+ :javascript
+ $(window).unbind('popstate');
+ $(window).bind('popstate', function() {
+ if(location.pathname.search("tree") != -1) {
+ $.ajax({type: "GET", url: location.pathname, dataType: "script"})}
+ else { location.href = location.pathname;}});
diff --git a/app/views/projects/_tree_file.html.haml b/app/views/projects/_tree_file.html.haml
new file mode 100644
index 00000000..08927fc2
--- /dev/null
+++ b/app/views/projects/_tree_file.html.haml
@@ -0,0 +1,21 @@
+- require "utils"
+.view_file
+ .view_file_header
+ %strong
+ = name
+ -#= file.mime_type
+ = link_to "raw", blob_project_path(@project, :commit_id => @commit.id, :path => params[:path] ), :class => "right", :target => "_blank"
+ = link_to "history", project_commits_path(@project, :path => params[:path]), :class => "right", :style => "margin-right:10px;"
+ %br/
+ - if file.mime_type =~ /application|text/ && !Utils.binary?(file.data)
+ .view_file_content
+ - ft = handle_file_type(file.name, file.mime_type)
+ :erb
+ <%= raw Albino.colorize(content, ft, :html, 'utf-8', "linenos=True") %>
+ - elsif file.mime_type =~ /image/
+ .view_file_content_image
+ %img{ :src => "data:image/jpeg;base64,#{Base64.encode64(file.data)}"}
+ - else
+ %p
+ %center No preview for this file type
+
diff --git a/app/views/projects/_tree_item.html.haml b/app/views/projects/_tree_item.html.haml
new file mode 100644
index 00000000..25575283
--- /dev/null
+++ b/app/views/projects/_tree_item.html.haml
@@ -0,0 +1,15 @@
+- file = params[:path] ? File.join(params[:path], content.name) : content.name
+- content_commit = @repo.log(@branch, file, :max_count => 1).last
+- return unless content_commit
+%tr{ :class => "tree-item", :url => tree_file_project_path(@project, @commit.id, file) }
+ %td.tree-item-file-name
+ - if content.is_a?(Grit::Blob)
+ = image_tag "txt.png"
+ - else
+ = image_tag "dir.png"
+ = link_to truncate(content.name, :length => 40), tree_file_project_path(@project, @commit.id, file), :remote => :true
+ %td
+ = time_ago_in_words(content_commit.committed_date)
+ ago
+ %td
+ = link_to truncate(content_commit.message, :length => 40), project_commit_path(@project, content_commit)
diff --git a/app/views/projects/create.js.haml b/app/views/projects/create.js.haml
new file mode 100644
index 00000000..c457527a
--- /dev/null
+++ b/app/views/projects/create.js.haml
@@ -0,0 +1,6 @@
+- if @project.valid?
+ :plain
+ location.href = "#{project_path(@project, :notice => 'Project was successfully created.')}";
+- else
+ :plain
+ $("#new_project").replaceWith("#{escape_javascript(render('form'))}");
diff --git a/app/views/projects/edit.html.erb b/app/views/projects/edit.html.erb
new file mode 100644
index 00000000..2d343636
--- /dev/null
+++ b/app/views/projects/edit.html.erb
@@ -0,0 +1 @@
+<%= render 'form' %>
diff --git a/app/views/projects/empty.html.erb b/app/views/projects/empty.html.erb
new file mode 100644
index 00000000..a8917471
--- /dev/null
+++ b/app/views/projects/empty.html.erb
@@ -0,0 +1,49 @@
+
+
+
Git global setup:
+<% setup_str = <
+ <%= raw Albino.colorize(setup_str, :bash) %>
+
+
+ Next steps:
+<% repo_setup_str = <
+ <%= raw Albino.colorize(repo_setup_str, :bash) %>
+
+
+ Existing Git Repo?
+<% exist_repo_setup_str = <
+ <%= raw Albino.colorize(exist_repo_setup_str, :bash) %>
+
+
+ Remove this project?
+
+
+ Be careful!
+ Project cant be recovered after destroy.
+ <%= link_to 'Destroy', @project,
+ :confirm => 'Are you sure?', :method => :delete,
+ :class => "left button negative span-6", :style => "text-align:center" %>
+
+
+
+
+
diff --git a/app/views/projects/index.html.haml b/app/views/projects/index.html.haml
new file mode 100644
index 00000000..51717288
--- /dev/null
+++ b/app/views/projects/index.html.haml
@@ -0,0 +1,25 @@
+= link_to 'New Project', new_project_path, :class => "lbutton vm"
+
+%table.round-borders#projects-list
+ %tr
+ %th Name
+ %th Path
+ %th Code
+ %th Web
+ %th Git
+ %th Admin
+ %th Actions
+
+ - @projects.each do |project|
+ %tr{ :class => "project", :url => project_path(project) }
+ %td= project.name
+ %td= truncate project.url_to_repo
+ %td= project.code
+ %td= check_box_tag "read", 1, project.readers.include?(current_user), :disabled => :disabled
+ %td= check_box_tag "commit", 1, project.writers.include?(current_user), :disabled => :disabled
+ %td= check_box_tag "admin", 1, project.admins.include?(current_user), :disabled => :disabled
+ %td
+ -if can? current_user, :admin_project, project
+ = link_to 'Edit', edit_project_path(project), :class => "lbutton positive"
+%br
+
diff --git a/app/views/projects/new.html.erb b/app/views/projects/new.html.erb
new file mode 100644
index 00000000..2d343636
--- /dev/null
+++ b/app/views/projects/new.html.erb
@@ -0,0 +1 @@
+<%= render 'form' %>
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
new file mode 100644
index 00000000..0fd9c8ea
--- /dev/null
+++ b/app/views/projects/show.html.haml
@@ -0,0 +1,3 @@
+%div
+ %div#tree-holder
+ = render :partial => "tree", :locals => {:repo => @repo, :commit => @commit, :tree => @commit.tree}
diff --git a/app/views/projects/team.html.haml b/app/views/projects/team.html.haml
new file mode 100644
index 00000000..3b2c4b3e
--- /dev/null
+++ b/app/views/projects/team.html.haml
@@ -0,0 +1,3 @@
+%div
+ = render :partial => "team", :locals => {:project => @project}
+
diff --git a/app/views/projects/tree.html.erb b/app/views/projects/tree.html.erb
new file mode 100644
index 00000000..c29ed2f9
--- /dev/null
+++ b/app/views/projects/tree.html.erb
@@ -0,0 +1,5 @@
+
+
+ <%= render :partial => "tree", :locals => {:repo => @repo, :commit => @commit, :tree => @tree} %>
+
+
diff --git a/app/views/projects/tree.js.haml b/app/views/projects/tree.js.haml
new file mode 100644
index 00000000..60cbd199
--- /dev/null
+++ b/app/views/projects/tree.js.haml
@@ -0,0 +1,5 @@
+:plain
+ $("#tree-holder table").hide("slide", { direction: "left" }, 150, function(){
+ $("#tree-holder").html("#{escape_javascript(render(:partial => "tree", :locals => {:repo => @repo, :commit => @commit, :tree => @tree}))}");
+ $("#tree-holder table").show("slide", { direction: "right" }, 150);
+ });
diff --git a/app/views/projects/update.js.haml b/app/views/projects/update.js.haml
new file mode 100644
index 00000000..0188f083
--- /dev/null
+++ b/app/views/projects/update.js.haml
@@ -0,0 +1,6 @@
+- if @project.valid?
+ :plain
+ location.href = "#{project_path(@project, :notice => 'Project was successfully updated.')}";
+- else
+ :plain
+ $(".edit_project").replaceWith("#{escape_javascript(render('form'))}");
diff --git a/app/views/projects/wall.html.haml b/app/views/projects/wall.html.haml
new file mode 100644
index 00000000..479bb3cf
--- /dev/null
+++ b/app/views/projects/wall.html.haml
@@ -0,0 +1 @@
+= render "notes/notes"
diff --git a/app/views/team_members/_form.html.haml b/app/views/team_members/_form.html.haml
new file mode 100644
index 00000000..d2f7fd66
--- /dev/null
+++ b/app/views/team_members/_form.html.haml
@@ -0,0 +1,25 @@
+%div
+ = form_for @team_member, :as => :team_member, :url => project_team_members_path(@project, @team_member), :remote => "true" do |f|
+ -if @team_member.errors.any?
+ %ul
+ - @team_member.errors.full_messages.each do |msg|
+ %li= msg
+
+ .span-6.append-bottom
+ %b Name
+ %br
+ = f.select(:user_id, User.not_in_project(@project).all.collect {|p| [ p.name, p.id ] }, { :include_blank => "Select user" })
+ .span-6
+ %b Access:
+ .span-6
+ = f.check_box :read
+ Web Access
+ .span-6
+ = f.check_box :write
+ Git Access
+ .span-6.append-bottom
+ = f.check_box :admin
+ Admin
+ %hr
+ .span-6
+ = f.submit 'Save', :class => "lbutton vm"
diff --git a/app/views/team_members/_show.html.haml b/app/views/team_members/_show.html.haml
new file mode 100644
index 00000000..6d310768
--- /dev/null
+++ b/app/views/team_members/_show.html.haml
@@ -0,0 +1,18 @@
+- user = member.user
+%tr{:id => dom_id(member)}
+ %td
+ = image_tag gravatar_icon(user.email), :class => "left", :width => 40, :style => "padding:0 5px;"
+ = truncate user.name, :lenght => 16
+ %td= truncate user.email, :lenght => 16
+ - if can? current_user, :admin_project, @project
+ = form_for(member, :as => :team_member, :url => project_team_member_path(@project, member)) do |f|
+ %td= f.check_box :read, :onclick => "$(this.form).submit();"
+ %td= f.check_box :write, :onclick => "$(this.form).submit();"
+ %td= f.check_box :admin, :onclick => "$(this.form).submit();"
+ - else
+ %td= check_box_tag "read", 1, member.read, :disabled => :disabled
+ %td= check_box_tag "commit", 1, member.write, :disabled => :disabled
+ %td= check_box_tag "admin", 1, member.admin, :disabled => :disabled
+ - if can? current_user, :admin_team_member, @project
+ %td
+ = link_to 'Cancel', project_team_member_path(:project_id => @project, :id => member.id), :confirm => 'Are you sure?', :method => :delete, :class => "lbutton negative delete-team-member", :remote => true
diff --git a/app/views/team_members/create.js.haml b/app/views/team_members/create.js.haml
new file mode 100644
index 00000000..74dacc28
--- /dev/null
+++ b/app/views/team_members/create.js.haml
@@ -0,0 +1,9 @@
+- if @team_member.valid?
+ :plain
+ $("#new_tm_dialog").dialog("close");
+ $("#team-table").append("#{escape_javascript(render(:partial => 'show', :locals => {:member => @team_member} ))}");
+- else
+ :plain
+ $("#new_tm_dialog").empty();
+ $("#new_tm_dialog").append("#{escape_javascript(render('form'))}");
+ $('select#team_member_user_id').selectmenu({width:300});
diff --git a/app/views/team_members/new.js.haml b/app/views/team_members/new.js.haml
new file mode 100644
index 00000000..93bebba6
--- /dev/null
+++ b/app/views/team_members/new.js.haml
@@ -0,0 +1,15 @@
+-#$("#new-member-holder").empty();
+-#$("#new-member-holder").append("#{escape_javascript(render('form'))}");
+:plain
+ var new_tm_dialog = $("
");
+ new_tm_dialog.html("#{escape_javascript(render('form'))}");
+ $(new_tm_dialog).dialog({
+ width: 350,
+ resizable: false,
+ draggable: false,
+ title: "Add new member to project team",
+ close: function(event, ui) { $("#new_tm_dialog").remove();},
+ modal: true
+
+ });
+ $('select#team_member_user_id').selectmenu({width:300});
diff --git a/app/views/team_members/update.js.haml b/app/views/team_members/update.js.haml
new file mode 100644
index 00000000..6d7f8816
--- /dev/null
+++ b/app/views/team_members/update.js.haml
@@ -0,0 +1,6 @@
+- if @team_member.valid?
+ :plain
+ $("##{dom_id(@team_member)}").effect("highlight", {color: "#529214"}, 1000);;
+- else
+ :plain
+ $("##{dom_id(@team_member)}").effect("highlight", {color: "#D12F19"}, 1000);;
diff --git a/config.ru b/config.ru
new file mode 100644
index 00000000..5ef2a028
--- /dev/null
+++ b/config.ru
@@ -0,0 +1,4 @@
+# This file is used by Rack-based servers to start the application.
+
+require ::File.expand_path('../config/environment', __FILE__)
+run Gitlab::Application
diff --git a/config/application.rb b/config/application.rb
new file mode 100644
index 00000000..00ed1a71
--- /dev/null
+++ b/config/application.rb
@@ -0,0 +1,48 @@
+require File.expand_path('../boot', __FILE__)
+
+require 'rails/all'
+
+if defined?(Bundler)
+ # If you precompile assets before deploying to production, use this line
+ Bundler.require *Rails.groups(:assets => %w(development test))
+ # If you want your assets lazily compiled in production, use this line
+ # Bundler.require(:default, :assets, Rails.env)
+end
+
+module Gitlab
+ class Application < Rails::Application
+ # Settings in config/environments/* take precedence over those specified here.
+ # Application configuration should go into files in config/initializers
+ # -- all .rb files in that directory are automatically loaded.
+
+ # Custom directories with classes and modules you want to be autoloadable.
+ # config.autoload_paths += %W(#{config.root}/extras)
+
+ # Only load the plugins named here, in the order given (default is alphabetical).
+ # :all can be used as a placeholder for all plugins not explicitly named.
+ # config.plugins = [ :exception_notification, :ssl_requirement, :all ]
+
+ # Activate observers that should always be running.
+ # config.active_record.observers = :cacher, :garbage_collector, :forum_observer
+
+ # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
+ # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
+ # config.time_zone = 'Central Time (US & Canada)'
+
+ # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
+ # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
+ # config.i18n.default_locale = :de
+
+ # Configure the default encoding used in templates for Ruby 1.9.
+ config.encoding = "utf-8"
+
+ # Configure sensitive parameters which will be filtered from the log file.
+ config.filter_parameters += [:password]
+
+ # Enable the asset pipeline
+ config.assets.enabled = true
+
+ # Version of your assets, change this if you want to expire all your assets
+ config.assets.version = '1.0'
+ end
+end
diff --git a/config/boot.rb b/config/boot.rb
new file mode 100644
index 00000000..4489e586
--- /dev/null
+++ b/config/boot.rb
@@ -0,0 +1,6 @@
+require 'rubygems'
+
+# Set up gems listed in the Gemfile.
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
+
+require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
diff --git a/config/database.yml b/config/database.yml
new file mode 100644
index 00000000..51a4dd45
--- /dev/null
+++ b/config/database.yml
@@ -0,0 +1,25 @@
+# SQLite version 3.x
+# gem install sqlite3
+#
+# Ensure the SQLite 3 gem is defined in your Gemfile
+# gem 'sqlite3'
+development:
+ adapter: sqlite3
+ database: db/development.sqlite3
+ pool: 5
+ timeout: 5000
+
+# Warning: The database defined as "test" will be erased and
+# re-generated from your development database when you run "rake".
+# Do not set this db to the same as development or production.
+test:
+ adapter: sqlite3
+ database: db/test.sqlite3
+ pool: 5
+ timeout: 5000
+
+production:
+ adapter: sqlite3
+ database: db/production.sqlite3
+ pool: 5
+ timeout: 5000
diff --git a/config/environment.rb b/config/environment.rb
new file mode 100644
index 00000000..1c2d723e
--- /dev/null
+++ b/config/environment.rb
@@ -0,0 +1,7 @@
+# Load the rails application
+require File.expand_path('../application', __FILE__)
+
+# Initialize the rails application
+Gitlab::Application.initialize!
+
+require File.join(Rails.root, "lib", "gitosis")
diff --git a/config/environments/development.rb b/config/environments/development.rb
new file mode 100644
index 00000000..9e5bf821
--- /dev/null
+++ b/config/environments/development.rb
@@ -0,0 +1,32 @@
+Gitlab::Application.configure do
+ # Settings specified here will take precedence over those in config/application.rb
+
+ # In the development environment your application's code is reloaded on
+ # every request. This slows down response time but is perfect for development
+ # since you don't have to restart the web server when you make code changes.
+ config.cache_classes = false
+
+ # Log error messages when you accidentally call methods on nil.
+ config.whiny_nils = true
+
+ # Show full error reports and disable caching
+ config.consider_all_requests_local = true
+ config.action_controller.perform_caching = false
+
+ # Don't care if the mailer can't send
+ config.action_mailer.raise_delivery_errors = false
+
+ # Print deprecation notices to the Rails logger
+ config.active_support.deprecation = :log
+
+ # Only use best-standards-support built into browsers
+ config.action_dispatch.best_standards_support = :builtin
+
+ # Do not compress assets
+ config.assets.compress = false
+
+ # Expands the lines which load the assets
+ config.assets.debug = true
+
+ config.action_mailer.default_url_options = { :host => 'localhost:3000' }
+end
diff --git a/config/environments/production.rb b/config/environments/production.rb
new file mode 100644
index 00000000..7ebe4523
--- /dev/null
+++ b/config/environments/production.rb
@@ -0,0 +1,70 @@
+Gitlab::Application.configure do
+ # Settings specified here will take precedence over those in config/application.rb
+
+ # Code is not reloaded between requests
+ config.cache_classes = true
+
+ # Full error reports are disabled and caching is turned on
+ config.consider_all_requests_local = false
+ config.action_controller.perform_caching = true
+
+ # Disable Rails's static asset server (Apache or nginx will already do this)
+ config.serve_static_assets = false
+
+ # Compress JavaScripts and CSS
+ config.assets.compress = true
+
+ # Don't fallback to assets pipeline if a precompiled asset is missed
+ config.assets.compile = true
+
+ # Generate digests for assets URLs
+ config.assets.digest = true
+
+ # Defaults to Rails.root.join("public/assets")
+ # config.assets.manifest = YOUR_PATH
+
+ # Specifies the header that your server uses for sending files
+ # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
+ # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
+
+ # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
+ # config.force_ssl = true
+
+ # See everything in the log (default is :info)
+ # config.log_level = :debug
+
+ # Use a different logger for distributed setups
+ # config.logger = SyslogLogger.new
+
+ # Use a different cache store in production
+ # config.cache_store = :mem_cache_store
+
+ # Enable serving of images, stylesheets, and JavaScripts from an asset server
+ # config.action_controller.asset_host = "http://assets.example.com"
+
+ # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
+ # config.assets.precompile += %w( search.js )
+
+ # Disable delivery errors, bad email addresses will be ignored
+ # config.action_mailer.raise_delivery_errors = false
+
+ # Enable threaded mode
+ # config.threadsafe!
+
+ # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
+ # the I18n.default_locale when a translation can not be found)
+ config.i18n.fallbacks = true
+
+ # Send deprecation notices to registered listeners
+ config.active_support.deprecation = :notify
+
+
+ config.action_mailer.delivery_method = :sendmail
+ # Defaults to:
+ # # config.action_mailer.sendmail_settings = {
+ # # :location => '/usr/sbin/sendmail',
+ # # :arguments => '-i -t'
+ # # }
+ config.action_mailer.perform_deliveries = true
+ config.action_mailer.raise_delivery_errors = true
+end
diff --git a/config/environments/test.rb b/config/environments/test.rb
new file mode 100644
index 00000000..1e7765d9
--- /dev/null
+++ b/config/environments/test.rb
@@ -0,0 +1,42 @@
+Gitlab::Application.configure do
+ # Settings specified here will take precedence over those in config/application.rb
+
+ # The test environment is used exclusively to run your application's
+ # test suite. You never need to work with it otherwise. Remember that
+ # your test database is "scratch space" for the test suite and is wiped
+ # and recreated between test runs. Don't rely on the data there!
+ config.cache_classes = true
+
+ # Configure static asset server for tests with Cache-Control for performance
+ config.serve_static_assets = true
+ config.static_cache_control = "public, max-age=3600"
+
+ # Log error messages when you accidentally call methods on nil
+ config.whiny_nils = true
+
+ # Show full error reports and disable caching
+ config.consider_all_requests_local = true
+ config.action_controller.perform_caching = false
+
+ # Raise exceptions instead of rendering exception templates
+ config.action_dispatch.show_exceptions = false
+
+ # Disable request forgery protection in test environment
+ config.action_controller.allow_forgery_protection = false
+
+ # Tell Action Mailer not to deliver emails to the real world.
+ # The :test delivery method accumulates sent emails in the
+ # ActionMailer::Base.deliveries array.
+ config.action_mailer.delivery_method = :test
+
+ # Use SQL instead of Active Record's schema dumper when creating the test database.
+ # This is necessary if your schema can't be completely dumped by the schema dumper,
+ # like if you have constraints or database-specific column types
+ # config.active_record.schema_format = :sql
+
+ # Print deprecation notices to the stderr
+ config.active_support.deprecation = :stderr
+
+ # Allow pass debug_assets=true as a query parameter to load pages with unpackaged assets
+ config.assets.allow_debugging = true
+end
diff --git a/config/gitosis.yml b/config/gitosis.yml
new file mode 100644
index 00000000..cf76f5a2
--- /dev/null
+++ b/config/gitosis.yml
@@ -0,0 +1,4 @@
+admin_uri: git@localhost:gitosis-admin.git
+base_path: /home/git/repositories/
+host: localhost
+git_user: git
diff --git a/config/initializers/backtrace_silencers.rb b/config/initializers/backtrace_silencers.rb
new file mode 100644
index 00000000..59385cdf
--- /dev/null
+++ b/config/initializers/backtrace_silencers.rb
@@ -0,0 +1,7 @@
+# Be sure to restart your server when you modify this file.
+
+# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
+# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
+
+# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
+# Rails.backtrace_cleaner.remove_silencers!
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
new file mode 100644
index 00000000..e62db747
--- /dev/null
+++ b/config/initializers/devise.rb
@@ -0,0 +1,211 @@
+# Use this hook to configure devise mailer, warden hooks and so forth. The first
+# four configuration values can also be set straight in your models.
+Devise.setup do |config|
+ # ==> Mailer Configuration
+ # Configure the e-mail address which will be shown in Devise::Mailer,
+ # note that it will be overwritten if you use your own mailer class with default "from" parameter.
+ config.mailer_sender = "please-change-me-at-config-initializers-devise@example.com"
+
+ # Configure the class responsible to send e-mails.
+ # config.mailer = "Devise::Mailer"
+
+ # ==> ORM configuration
+ # Load and configure the ORM. Supports :active_record (default) and
+ # :mongoid (bson_ext recommended) by default. Other ORMs may be
+ # available as additional gems.
+ require 'devise/orm/active_record'
+
+ # ==> Configuration for any authentication mechanism
+ # Configure which keys are used when authenticating a user. The default is
+ # just :email. You can configure it to use [:username, :subdomain], so for
+ # authenticating a user, both parameters are required. Remember that those
+ # parameters are used only when authenticating and not when retrieving from
+ # session. If you need permissions, you should implement that in a before filter.
+ # You can also supply a hash where the value is a boolean determining whether
+ # or not authentication should be aborted when the value is not present.
+ # config.authentication_keys = [ :email ]
+
+ # Configure parameters from the request object used for authentication. Each entry
+ # given should be a request method and it will automatically be passed to the
+ # find_for_authentication method and considered in your model lookup. For instance,
+ # if you set :request_keys to [:subdomain], :subdomain will be used on authentication.
+ # The same considerations mentioned for authentication_keys also apply to request_keys.
+ # config.request_keys = []
+
+ # Configure which authentication keys should be case-insensitive.
+ # These keys will be downcased upon creating or modifying a user and when used
+ # to authenticate or find a user. Default is :email.
+ config.case_insensitive_keys = [ :email ]
+
+ # Configure which authentication keys should have whitespace stripped.
+ # These keys will have whitespace before and after removed upon creating or
+ # modifying a user and when used to authenticate or find a user. Default is :email.
+ config.strip_whitespace_keys = [ :email ]
+
+ # Tell if authentication through request.params is enabled. True by default.
+ # config.params_authenticatable = true
+
+ # Tell if authentication through HTTP Basic Auth is enabled. False by default.
+ # config.http_authenticatable = false
+
+ # If http headers should be returned for AJAX requests. True by default.
+ # config.http_authenticatable_on_xhr = true
+
+ # The realm used in Http Basic Authentication. "Application" by default.
+ # config.http_authentication_realm = "Application"
+
+ # It will change confirmation, password recovery and other workflows
+ # to behave the same regardless if the e-mail provided was right or wrong.
+ # Does not affect registerable.
+ # config.paranoid = true
+
+ # ==> Configuration for :database_authenticatable
+ # For bcrypt, this is the cost for hashing the password and defaults to 10. If
+ # using other encryptors, it sets how many times you want the password re-encrypted.
+ #
+ # Limiting the stretches to just one in testing will increase the performance of
+ # your test suite dramatically. However, it is STRONGLY RECOMMENDED to not use
+ # a value less than 10 in other environments.
+ config.stretches = Rails.env.test? ? 1 : 10
+
+ # Setup a pepper to generate the encrypted password.
+ # config.pepper = "2ef62d549c4ff98a5d3e0ba211e72cff592060247e3bbbb9f499af1222f876f53d39b39b823132affb32858168c79c1d7741d26499901b63c6030a42129924ef"
+
+ # ==> Configuration for :confirmable
+ # The time you want to give your user to confirm his account. During this time
+ # he will be able to access your application without confirming. Default is 0.days
+ # When confirm_within is zero, the user won't be able to sign in without confirming.
+ # You can use this to let your user access some features of your application
+ # without confirming the account, but blocking it after a certain period
+ # (ie 2 days).
+ # config.confirm_within = 2.days
+
+ # Defines which key will be used when confirming an account
+ # config.confirmation_keys = [ :email ]
+
+ # ==> Configuration for :rememberable
+ # The time the user will be remembered without asking for credentials again.
+ # config.remember_for = 2.weeks
+
+ # If true, a valid remember token can be re-used between multiple browsers.
+ # config.remember_across_browsers = true
+
+ # If true, extends the user's remember period when remembered via cookie.
+ # config.extend_remember_period = false
+
+ # If true, uses the password salt as remember token. This should be turned
+ # to false if you are not using database authenticatable.
+ config.use_salt_as_remember_token = true
+
+ # Options to be passed to the created cookie. For instance, you can set
+ # :secure => true in order to force SSL only cookies.
+ # config.cookie_options = {}
+
+ # ==> Configuration for :validatable
+ # Range for password length. Default is 6..128.
+ # config.password_length = 6..128
+
+ # Email regex used to validate email formats. It simply asserts that
+ # an one (and only one) @ exists in the given string. This is mainly
+ # to give user feedback and not to assert the e-mail validity.
+ # config.email_regexp = /\A[^@]+@[^@]+\z/
+
+ # ==> Configuration for :timeoutable
+ # The time you want to timeout the user session without activity. After this
+ # time the user will be asked for credentials again. Default is 30 minutes.
+ # config.timeout_in = 30.minutes
+
+ # ==> Configuration for :lockable
+ # Defines which strategy will be used to lock an account.
+ # :failed_attempts = Locks an account after a number of failed attempts to sign in.
+ # :none = No lock strategy. You should handle locking by yourself.
+ # config.lock_strategy = :failed_attempts
+
+ # Defines which key will be used when locking and unlocking an account
+ # config.unlock_keys = [ :email ]
+
+ # Defines which strategy will be used to unlock an account.
+ # :email = Sends an unlock link to the user email
+ # :time = Re-enables login after a certain amount of time (see :unlock_in below)
+ # :both = Enables both strategies
+ # :none = No unlock strategy. You should handle unlocking by yourself.
+ # config.unlock_strategy = :both
+
+ # Number of authentication tries before locking an account if lock_strategy
+ # is failed attempts.
+ # config.maximum_attempts = 20
+
+ # Time interval to unlock the account if :time is enabled as unlock_strategy.
+ # config.unlock_in = 1.hour
+
+ # ==> Configuration for :recoverable
+ #
+ # Defines which key will be used when recovering the password for an account
+ # config.reset_password_keys = [ :email ]
+
+ # Time interval you can reset your password with a reset password key.
+ # Don't put a too small interval or your users won't have the time to
+ # change their passwords.
+ config.reset_password_within = 2.hours
+
+ # ==> Configuration for :encryptable
+ # Allow you to use another encryption algorithm besides bcrypt (default). You can use
+ # :sha1, :sha512 or encryptors from others authentication tools as :clearance_sha1,
+ # :authlogic_sha512 (then you should set stretches above to 20 for default behavior)
+ # and :restful_authentication_sha1 (then you should set stretches to 10, and copy
+ # REST_AUTH_SITE_KEY to pepper)
+ # config.encryptor = :sha512
+
+ # ==> Configuration for :token_authenticatable
+ # Defines name of the authentication token params key
+ # config.token_authentication_key = :auth_token
+
+ # If true, authentication through token does not store user in session and needs
+ # to be supplied on each request. Useful if you are using the token as API token.
+ # config.stateless_token = false
+
+ # ==> Scopes configuration
+ # Turn scoped views on. Before rendering "sessions/new", it will first check for
+ # "users/sessions/new". It's turned off by default because it's slower if you
+ # are using only default views.
+ # config.scoped_views = false
+
+ # Configure the default scope given to Warden. By default it's the first
+ # devise role declared in your routes (usually :user).
+ # config.default_scope = :user
+
+ # Configure sign_out behavior.
+ # Sign_out action can be scoped (i.e. /users/sign_out affects only :user scope).
+ # The default is true, which means any logout action will sign out all active scopes.
+ # config.sign_out_all_scopes = true
+
+ # ==> Navigation configuration
+ # Lists the formats that should be treated as navigational. Formats like
+ # :html, should redirect to the sign in page when the user does not have
+ # access, but formats like :xml or :json, should return 401.
+ #
+ # If you have any extra navigational formats, like :iphone or :mobile, you
+ # should add them to the navigational formats lists.
+ #
+ # The :"*/*" and "*/*" formats below is required to match Internet
+ # Explorer requests.
+ # config.navigational_formats = [:"*/*", "*/*", :html]
+
+ # The default HTTP method used to sign out a resource. Default is :delete.
+ config.sign_out_via = :delete
+
+ # ==> OmniAuth
+ # Add a new OmniAuth provider. Check the wiki for more information on setting
+ # up on your models and hooks.
+ # config.omniauth :github, 'APP_ID', 'APP_SECRET', :scope => 'user,public_repo'
+
+ # ==> Warden configuration
+ # If you want to use other strategies, that are not supported by Devise, or
+ # change the failure app, you can configure them inside the config.warden block.
+ #
+ # config.warden do |manager|
+ # manager.failure_app = AnotherApp
+ # manager.intercept_401 = false
+ # manager.default_strategies(:scope => :user).unshift :some_external_strategy
+ # end
+end
diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb
new file mode 100644
index 00000000..9e8b0131
--- /dev/null
+++ b/config/initializers/inflections.rb
@@ -0,0 +1,10 @@
+# Be sure to restart your server when you modify this file.
+
+# Add new inflection rules using the following format
+# (all these examples are active by default):
+# ActiveSupport::Inflector.inflections do |inflect|
+# inflect.plural /^(ox)$/i, '\1en'
+# inflect.singular /^(ox)en/i, '\1'
+# inflect.irregular 'person', 'people'
+# inflect.uncountable %w( fish sheep )
+# end
diff --git a/config/initializers/load_config.rb b/config/initializers/load_config.rb
new file mode 100644
index 00000000..6c7289e0
--- /dev/null
+++ b/config/initializers/load_config.rb
@@ -0,0 +1 @@
+GITOSIS = YAML.load_file("#{Rails.root}/config/gitosis.yml")
diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb
new file mode 100644
index 00000000..72aca7e4
--- /dev/null
+++ b/config/initializers/mime_types.rb
@@ -0,0 +1,5 @@
+# Be sure to restart your server when you modify this file.
+
+# Add new mime types for use in respond_to blocks:
+# Mime::Type.register "text/richtext", :rtf
+# Mime::Type.register_alias "text/html", :iphone
diff --git a/config/initializers/rails_footnotes.rb b/config/initializers/rails_footnotes.rb
new file mode 100644
index 00000000..da9d58e4
--- /dev/null
+++ b/config/initializers/rails_footnotes.rb
@@ -0,0 +1,5 @@
+if defined?(Footnotes) && Rails.env.development?
+ Footnotes.run! # first of all
+
+ # ... other init code
+end
diff --git a/config/initializers/secret_token.rb b/config/initializers/secret_token.rb
new file mode 100644
index 00000000..6d3a9f07
--- /dev/null
+++ b/config/initializers/secret_token.rb
@@ -0,0 +1,7 @@
+# Be sure to restart your server when you modify this file.
+
+# Your secret key for verifying the integrity of signed cookies.
+# If you change this key, all old signed cookies will become invalid!
+# Make sure the secret is at least 30 characters and all random,
+# no regular words or you'll be exposed to dictionary attacks.
+Gitlab::Application.config.secret_token = '0a38e9a40ca5d66d7002a6ade0ed0f8b71058c820163f66cf65d91521ab55255ff708b9909b138008a7f13d68fec575def1dc3ff7200cd72b065896315e0bed2'
diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb
new file mode 100644
index 00000000..36c5f466
--- /dev/null
+++ b/config/initializers/session_store.rb
@@ -0,0 +1,8 @@
+# Be sure to restart your server when you modify this file.
+
+Gitlab::Application.config.session_store :cookie_store, key: '_gitlab_session'
+
+# Use the database for sessions instead of the cookie-based default,
+# which shouldn't be used to store highly confidential information
+# (create the session table with "rails generate session_migration")
+# Gitlab::Application.config.session_store :active_record_store
diff --git a/config/initializers/wrap_parameters.rb b/config/initializers/wrap_parameters.rb
new file mode 100644
index 00000000..999df201
--- /dev/null
+++ b/config/initializers/wrap_parameters.rb
@@ -0,0 +1,14 @@
+# Be sure to restart your server when you modify this file.
+#
+# This file contains settings for ActionController::ParamsWrapper which
+# is enabled by default.
+
+# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
+ActiveSupport.on_load(:action_controller) do
+ wrap_parameters format: [:json]
+end
+
+# Disable root element in JSON by default.
+ActiveSupport.on_load(:active_record) do
+ self.include_root_in_json = false
+end
diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml
new file mode 100644
index 00000000..b1826351
--- /dev/null
+++ b/config/locales/devise.en.yml
@@ -0,0 +1,58 @@
+# Additional translations at http://github.com/plataformatec/devise/wiki/I18n
+
+en:
+ errors:
+ messages:
+ expired: "has expired, please request a new one"
+ not_found: "not found"
+ already_confirmed: "was already confirmed, please try signing in"
+ not_locked: "was not locked"
+ not_saved:
+ one: "1 error prohibited this %{resource} from being saved:"
+ other: "%{count} errors prohibited this %{resource} from being saved:"
+
+ devise:
+ failure:
+ already_authenticated: 'You are already signed in.'
+ unauthenticated: 'You need to sign in or sign up before continuing.'
+ unconfirmed: 'You have to confirm your account before continuing.'
+ locked: 'Your account is locked.'
+ invalid: 'Invalid email or password.'
+ invalid_token: 'Invalid authentication token.'
+ timeout: 'Your session expired, please sign in again to continue.'
+ inactive: 'Your account was not activated yet.'
+ sessions:
+ signed_in: 'Signed in successfully.'
+ signed_out: 'Signed out successfully.'
+ passwords:
+ send_instructions: 'You will receive an email with instructions about how to reset your password in a few minutes.'
+ updated: 'Your password was changed successfully. You are now signed in.'
+ updated_not_active: 'Your password was changed successfully.'
+ send_paranoid_instructions: "If your e-mail exists on our database, you will receive a password recovery link on your e-mail"
+ confirmations:
+ send_instructions: 'You will receive an email with instructions about how to confirm your account in a few minutes.'
+ send_paranoid_instructions: 'If your e-mail exists on our database, you will receive an email with instructions about how to confirm your account in a few minutes.'
+ confirmed: 'Your account was successfully confirmed. You are now signed in.'
+ registrations:
+ signed_up: 'Welcome! You have signed up successfully.'
+ inactive_signed_up: 'You have signed up successfully. However, we could not sign you in because your account is %{reason}.'
+ updated: 'You updated your account successfully.'
+ destroyed: 'Bye! Your account was successfully cancelled. We hope to see you again soon.'
+ reasons:
+ inactive: 'inactive'
+ unconfirmed: 'unconfirmed'
+ locked: 'locked'
+ unlocks:
+ send_instructions: 'You will receive an email with instructions about how to unlock your account in a few minutes.'
+ unlocked: 'Your account was successfully unlocked. You are now signed in.'
+ send_paranoid_instructions: 'If your account exists, you will receive an email with instructions about how to unlock it in a few minutes.'
+ omniauth_callbacks:
+ success: 'Successfully authorized from %{kind} account.'
+ failure: 'Could not authorize you from %{kind} because "%{reason}".'
+ mailer:
+ confirmation_instructions:
+ subject: 'Confirmation instructions'
+ reset_password_instructions:
+ subject: 'Reset password instructions'
+ unlock_instructions:
+ subject: 'Unlock Instructions'
diff --git a/config/locales/en.yml b/config/locales/en.yml
new file mode 100644
index 00000000..f6cfb5ef
--- /dev/null
+++ b/config/locales/en.yml
@@ -0,0 +1,10 @@
+# Sample localization file for English. Add more files in this directory for other locales.
+# See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
+
+en:
+ hello: "Hello world"
+ errors:
+ messages:
+ wrong_size: "is the wrong size (should be %{file_size})"
+ size_too_small: "is too small (should be at least %{file_size})"
+ size_too_big: "is too big (should be at most %{file_size})"
diff --git a/config/routes.rb b/config/routes.rb
new file mode 100644
index 00000000..332fd8b1
--- /dev/null
+++ b/config/routes.rb
@@ -0,0 +1,46 @@
+Gitlab::Application.routes.draw do
+ namespace :admin do
+ resources :users
+ resources :projects
+ resources :team_members
+ get 'emails', :to => 'mailer#preview'
+ get 'mailer/preview_note'
+ get 'mailer/preview_user_new'
+ get 'mailer/preview_issue_new'
+ root :to => "users#index"
+ end
+
+ get "profile/password", :to => "profile#password"
+ put "profile/password", :to => "profile#password_update"
+ get "profile", :to => "profile#show"
+ #get "profile/:id", :to => "profile#show"
+
+ resources :projects, :only => [:new, :create, :index]
+ resources :keys
+ devise_for :users
+
+ resources :projects, :except => [:new, :create, :index], :path => "/" do
+ member do
+ get "tree"
+ get "blob"
+ get "team"
+ get "wall"
+
+ # tree viewer
+ get "tree/:commit_id" => "projects#tree"
+ get "tree/:commit_id/:path" => "projects#tree",
+ :as => :tree_file,
+ :constraints => {
+ :id => /[a-zA-Z0-9]+/,
+ :commit_id => /[a-zA-Z0-9]+/,
+ :path => /.*/
+ }
+
+ end
+ resources :commits
+ resources :team_members
+ resources :issues
+ resources :notes, :only => [:create, :destroy]
+ end
+ root :to => "projects#index"
+end
diff --git a/configure.rb b/configure.rb
new file mode 100644
index 00000000..27bad806
--- /dev/null
+++ b/configure.rb
@@ -0,0 +1,5 @@
+root_path = File.expand_path(File.dirname(__FILE__))
+require File.join(root_path, "install", "prepare")
+env = ARGV[0] || "development"
+
+Install.prepare(env)
diff --git a/db/fixtures/development/001_admin.rb b/db/fixtures/development/001_admin.rb
new file mode 100644
index 00000000..5020eccb
--- /dev/null
+++ b/db/fixtures/development/001_admin.rb
@@ -0,0 +1,10 @@
+# Admin account
+admin = User.create(
+ :email => "admin@local.host",
+ :name => "Administrator",
+ :password => "5iveL!fe",
+ :password_confirmation => "5iveL!fe"
+)
+
+admin.admin = true
+admin.save!
diff --git a/db/fixtures/production/001_admin.rb b/db/fixtures/production/001_admin.rb
new file mode 100644
index 00000000..a50a6936
--- /dev/null
+++ b/db/fixtures/production/001_admin.rb
@@ -0,0 +1,9 @@
+admin = User.create(
+ :email => "admin@local.host",
+ :name => "Administrator",
+ :password => "5iveL!fe",
+ :password_confirmation => "5iveL!fe"
+)
+
+admin.admin = true
+admin.save!
diff --git a/db/fixtures/test/001_repo.rb b/db/fixtures/test/001_repo.rb
new file mode 100644
index 00000000..034596f1
--- /dev/null
+++ b/db/fixtures/test/001_repo.rb
@@ -0,0 +1,8 @@
+# Clone repo
+`cp spec/seed_project.tar.gz /tmp/`
+Dir.chdir("/tmp")
+`tar -xf seed_project.tar.gz`
+3.times do |i|
+`cp -r /tmp/legit/ /tmp/legit_#{i}/`
+puts "Unpacked seed repo - /tmp/legit_#{i}"
+end
diff --git a/db/migrate/20110913200833_devise_create_users.rb b/db/migrate/20110913200833_devise_create_users.rb
new file mode 100644
index 00000000..3083e742
--- /dev/null
+++ b/db/migrate/20110913200833_devise_create_users.rb
@@ -0,0 +1,28 @@
+class DeviseCreateUsers < ActiveRecord::Migration
+ def self.up
+ create_table(:users) do |t|
+ t.database_authenticatable :null => false
+ t.recoverable
+ t.rememberable
+ t.trackable
+
+ # t.encryptable
+ # t.confirmable
+ # t.lockable :lock_strategy => :failed_attempts, :unlock_strategy => :both
+ # t.token_authenticatable
+
+
+ t.timestamps
+ end
+
+ add_index :users, :email, :unique => true
+ add_index :users, :reset_password_token, :unique => true
+ # add_index :users, :confirmation_token, :unique => true
+ # add_index :users, :unlock_token, :unique => true
+ # add_index :users, :authentication_token, :unique => true
+ end
+
+ def self.down
+ drop_table :users
+ end
+end
diff --git a/db/migrate/20110913204141_create_projects.rb b/db/migrate/20110913204141_create_projects.rb
new file mode 100644
index 00000000..45b76f95
--- /dev/null
+++ b/db/migrate/20110913204141_create_projects.rb
@@ -0,0 +1,11 @@
+class CreateProjects < ActiveRecord::Migration
+ def change
+ create_table :projects do |t|
+ t.string :name
+ t.string :path
+ t.text :description
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20110914221600_create_users_projects.rb b/db/migrate/20110914221600_create_users_projects.rb
new file mode 100644
index 00000000..a89798ae
--- /dev/null
+++ b/db/migrate/20110914221600_create_users_projects.rb
@@ -0,0 +1,13 @@
+class CreateUsersProjects < ActiveRecord::Migration
+ def change
+ create_table :users_projects do |t|
+ t.integer :user_id, :null => false
+ t.integer :project_id, :null => false
+ t.boolean :read, :default => false
+ t.boolean :write, :default => false
+ t.boolean :admin, :default => false
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20110915205627_add_private_flag_to_project.rb b/db/migrate/20110915205627_add_private_flag_to_project.rb
new file mode 100644
index 00000000..73c0b9df
--- /dev/null
+++ b/db/migrate/20110915205627_add_private_flag_to_project.rb
@@ -0,0 +1,5 @@
+class AddPrivateFlagToProject < ActiveRecord::Migration
+ def change
+ add_column :projects, :private_flag, :boolean, :default => true, :null => false
+ end
+end
diff --git a/db/migrate/20110915213352_create_keys.rb b/db/migrate/20110915213352_create_keys.rb
new file mode 100644
index 00000000..d4615b4b
--- /dev/null
+++ b/db/migrate/20110915213352_create_keys.rb
@@ -0,0 +1,9 @@
+class CreateKeys < ActiveRecord::Migration
+ def change
+ create_table :keys do |t|
+ t.integer :user_id, :null => false
+ t.text :project_id, :null => false
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20110916123731_add_name_to_user.rb b/db/migrate/20110916123731_add_name_to_user.rb
new file mode 100644
index 00000000..74142b05
--- /dev/null
+++ b/db/migrate/20110916123731_add_name_to_user.rb
@@ -0,0 +1,5 @@
+class AddNameToUser < ActiveRecord::Migration
+ def change
+ add_column :users, :name, :string
+ end
+end
diff --git a/db/migrate/20110916162511_add_key_title_to_key.rb b/db/migrate/20110916162511_add_key_title_to_key.rb
new file mode 100644
index 00000000..b2eaa51c
--- /dev/null
+++ b/db/migrate/20110916162511_add_key_title_to_key.rb
@@ -0,0 +1,7 @@
+class AddKeyTitleToKey < ActiveRecord::Migration
+ def change
+ add_column :keys, :key, :text
+ add_column :keys, :title, :string
+ remove_column :keys, :project_id
+ end
+end
diff --git a/db/migrate/20110917212932_add_identifier_to_key.rb b/db/migrate/20110917212932_add_identifier_to_key.rb
new file mode 100644
index 00000000..e5727939
--- /dev/null
+++ b/db/migrate/20110917212932_add_identifier_to_key.rb
@@ -0,0 +1,5 @@
+class AddIdentifierToKey < ActiveRecord::Migration
+ def change
+ add_column :keys, :identifier, :string
+ end
+end
diff --git a/db/migrate/20110921192501_create_issues.rb b/db/migrate/20110921192501_create_issues.rb
new file mode 100644
index 00000000..63b42ad9
--- /dev/null
+++ b/db/migrate/20110921192501_create_issues.rb
@@ -0,0 +1,13 @@
+class CreateIssues < ActiveRecord::Migration
+ def change
+ create_table :issues do |t|
+ t.string :title
+ t.text :content
+ t.integer :assignee_id
+ t.integer :author_id
+ t.integer :project_id
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20110922110156_add_code_to_project.rb b/db/migrate/20110922110156_add_code_to_project.rb
new file mode 100644
index 00000000..f54a02bd
--- /dev/null
+++ b/db/migrate/20110922110156_add_code_to_project.rb
@@ -0,0 +1,5 @@
+class AddCodeToProject < ActiveRecord::Migration
+ def change
+ add_column :projects, :code, :string
+ end
+end
diff --git a/db/migrate/20110923211333_add_status_to_issue.rb b/db/migrate/20110923211333_add_status_to_issue.rb
new file mode 100644
index 00000000..c53bf46e
--- /dev/null
+++ b/db/migrate/20110923211333_add_status_to_issue.rb
@@ -0,0 +1,5 @@
+class AddStatusToIssue < ActiveRecord::Migration
+ def change
+ add_column :issues, :closed, :boolean, :default => false, :null => false
+ end
+end
diff --git a/db/migrate/20110924214549_create_rails_admin_histories_table.rb b/db/migrate/20110924214549_create_rails_admin_histories_table.rb
new file mode 100644
index 00000000..3c743aa2
--- /dev/null
+++ b/db/migrate/20110924214549_create_rails_admin_histories_table.rb
@@ -0,0 +1,18 @@
+class CreateRailsAdminHistoriesTable < ActiveRecord::Migration
+ def self.up
+ create_table :rails_admin_histories do |t|
+ t.text :message # title, name, or object_id
+ t.string :username
+ t.integer :item
+ t.string :table
+ t.integer :month, :limit => 2
+ t.integer :year, :limit => 5
+ t.timestamps
+ end
+ add_index(:rails_admin_histories, [:item, :table, :month, :year], :name => 'index_rails_admin_histories' )
+ end
+
+ def self.down
+ drop_table :rails_admin_histories
+ end
+end
diff --git a/db/migrate/20110924215658_add_admin_field_to_user.rb b/db/migrate/20110924215658_add_admin_field_to_user.rb
new file mode 100644
index 00000000..321587e6
--- /dev/null
+++ b/db/migrate/20110924215658_add_admin_field_to_user.rb
@@ -0,0 +1,5 @@
+class AddAdminFieldToUser < ActiveRecord::Migration
+ def change
+ add_column :users, :admin, :boolean, :default => false, :null => false
+ end
+end
diff --git a/db/migrate/20110926082616_remove_admin.rb b/db/migrate/20110926082616_remove_admin.rb
new file mode 100644
index 00000000..aac9ff78
--- /dev/null
+++ b/db/migrate/20110926082616_remove_admin.rb
@@ -0,0 +1,9 @@
+class RemoveAdmin < ActiveRecord::Migration
+ def up
+ drop_table :rails_admin_histories
+ end
+
+ def down
+ raise "No rollback"
+ end
+end
diff --git a/db/migrate/20110927130352_create_notes.rb b/db/migrate/20110927130352_create_notes.rb
new file mode 100644
index 00000000..72a0e817
--- /dev/null
+++ b/db/migrate/20110927130352_create_notes.rb
@@ -0,0 +1,12 @@
+class CreateNotes < ActiveRecord::Migration
+ def change
+ create_table :notes do |t|
+ t.string :note
+ t.integer :noteable_id
+ t.string :noteable_type
+ t.integer :author_id
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20110928140106_add_project_id_for_note.rb b/db/migrate/20110928140106_add_project_id_for_note.rb
new file mode 100644
index 00000000..3e641089
--- /dev/null
+++ b/db/migrate/20110928140106_add_project_id_for_note.rb
@@ -0,0 +1,9 @@
+class AddProjectIdForNote < ActiveRecord::Migration
+ def up
+ add_column :notes, :project_id, :integer
+ end
+
+ def down
+ remove_column :notes, :project_id, :integer
+ end
+end
diff --git a/db/migrate/20110928142747_change_noteable_id_for_note.rb b/db/migrate/20110928142747_change_noteable_id_for_note.rb
new file mode 100644
index 00000000..dc9d1f01
--- /dev/null
+++ b/db/migrate/20110928142747_change_noteable_id_for_note.rb
@@ -0,0 +1,9 @@
+class ChangeNoteableIdForNote < ActiveRecord::Migration
+ def up
+ change_column :notes, :noteable_id, :string
+ end
+
+ def down
+ change_column :notes, :noteable_id, :integer
+ end
+end
diff --git a/db/migrate/20110928161328_add_attachment_to_note.rb b/db/migrate/20110928161328_add_attachment_to_note.rb
new file mode 100644
index 00000000..37d9cf10
--- /dev/null
+++ b/db/migrate/20110928161328_add_attachment_to_note.rb
@@ -0,0 +1,5 @@
+class AddAttachmentToNote < ActiveRecord::Migration
+ def change
+ add_column :notes, :attachment, :string
+ end
+end
diff --git a/db/migrate/20111005193700_add_allow_repo_creation_for_user.rb b/db/migrate/20111005193700_add_allow_repo_creation_for_user.rb
new file mode 100644
index 00000000..82bd94b8
--- /dev/null
+++ b/db/migrate/20111005193700_add_allow_repo_creation_for_user.rb
@@ -0,0 +1,9 @@
+class AddAllowRepoCreationForUser < ActiveRecord::Migration
+ def up
+ add_column :users, :allowed_create_repo, :boolean, :default => true, :null => false
+ end
+
+ def down
+ remove_column :users, :allowed_create_repo
+ end
+end
diff --git a/db/pkey.example b/db/pkey.example
new file mode 100644
index 00000000..ae045772
--- /dev/null
+++ b/db/pkey.example
@@ -0,0 +1,3 @@
+AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4
+596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4
+soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=
diff --git a/db/schema.rb b/db/schema.rb
new file mode 100644
index 00000000..eda4b805
--- /dev/null
+++ b/db/schema.rb
@@ -0,0 +1,88 @@
+# encoding: UTF-8
+# This file is auto-generated from the current state of the database. Instead
+# of editing this file, please use the migrations feature of Active Record to
+# incrementally modify your database, and then regenerate this schema definition.
+#
+# Note that this schema.rb definition is the authoritative source for your
+# database schema. If you need to create the application database on another
+# system, you should be using db:schema:load, not running all the migrations
+# from scratch. The latter is a flawed and unsustainable approach (the more migrations
+# you'll amass, the slower it'll run and the greater likelihood for issues).
+#
+# It's strongly recommended to check this file into your version control system.
+
+ActiveRecord::Schema.define(:version => 20111005193700) do
+
+ create_table "issues", :force => true do |t|
+ t.string "title"
+ t.text "content"
+ t.integer "assignee_id"
+ t.integer "author_id"
+ t.integer "project_id"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.boolean "closed", :default => false, :null => false
+ end
+
+ create_table "keys", :force => true do |t|
+ t.integer "user_id", :null => false
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.text "key"
+ t.string "title"
+ t.string "identifier"
+ end
+
+ create_table "notes", :force => true do |t|
+ t.string "note"
+ t.string "noteable_id"
+ t.string "noteable_type"
+ t.integer "author_id"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.integer "project_id"
+ t.string "attachment"
+ end
+
+ create_table "projects", :force => true do |t|
+ t.string "name"
+ t.string "path"
+ t.text "description"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.boolean "private_flag", :default => true, :null => false
+ t.string "code"
+ end
+
+ create_table "users", :force => true do |t|
+ t.string "email", :default => "", :null => false
+ t.string "encrypted_password", :limit => 128, :default => "", :null => false
+ t.string "reset_password_token"
+ t.datetime "reset_password_sent_at"
+ t.datetime "remember_created_at"
+ t.integer "sign_in_count", :default => 0
+ t.datetime "current_sign_in_at"
+ t.datetime "last_sign_in_at"
+ t.string "current_sign_in_ip"
+ t.string "last_sign_in_ip"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.string "name"
+ t.boolean "admin", :default => false, :null => false
+ t.boolean "allowed_create_repo", :default => true, :null => false
+ end
+
+ add_index "users", ["email"], :name => "index_users_on_email", :unique => true
+ add_index "users", ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true
+
+ create_table "users_projects", :force => true do |t|
+ t.integer "user_id", :null => false
+ t.integer "project_id", :null => false
+ t.boolean "read", :default => false
+ t.boolean "write", :default => false
+ t.boolean "admin", :default => false
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+end
diff --git a/db/seeds.rb b/db/seeds.rb
new file mode 100644
index 00000000..e69de29b
diff --git a/doc/README_FOR_APP b/doc/README_FOR_APP
new file mode 100644
index 00000000..fe41f5cc
--- /dev/null
+++ b/doc/README_FOR_APP
@@ -0,0 +1,2 @@
+Use this README file to introduce your application and point to useful places in the API for learning more.
+Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries.
diff --git a/install/prepare.rb b/install/prepare.rb
new file mode 100644
index 00000000..f85c01a0
--- /dev/null
+++ b/install/prepare.rb
@@ -0,0 +1,51 @@
+module Install
+ class << self
+ def prepare(env)
+ puts green " == Starting for ENV=#{env} ..."
+ puts "rvm detected" if is_rvm?
+
+ bundler
+ db(env)
+
+ puts green " == Done! Now you can start server"
+ end
+
+ def bundler
+ command 'gem install bundler'
+ command 'bundle install'
+ end
+
+ def db(env)
+ command "bundle exec rake db:setup RAILS_ENV=#{env}"
+ command "bundle exec rake db:seed_fu RAILS_ENV=#{env}"
+ end
+
+ def is_rvm?
+ `type rvm | head -1` =~ /^rvm is/
+ end
+
+ def colorize(text, color_code)
+ "\033[#{color_code}#{text}\033[0m"
+ end
+
+ def red(text)
+ colorize(text, "31m")
+ end
+
+ def green(text)
+ colorize(text, "32m")
+ end
+
+ def command(string)
+ `#{string}`
+ if $?.to_i > 0
+ puts red " == #{string} - FAIL"
+ puts red " == Error during configure"
+ exit
+ else
+ puts green " == #{string} - OK"
+ end
+ end
+ end
+end
+
diff --git a/lib/assets/.gitkeep b/lib/assets/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/lib/file_size_validator.rb b/lib/file_size_validator.rb
new file mode 100644
index 00000000..151e0ce5
--- /dev/null
+++ b/lib/file_size_validator.rb
@@ -0,0 +1,65 @@
+class FileSizeValidator < ActiveModel::EachValidator
+ MESSAGES = { :is => :wrong_size, :minimum => :size_too_small, :maximum => :size_too_big }.freeze
+ CHECKS = { :is => :==, :minimum => :>=, :maximum => :<= }.freeze
+
+ DEFAULT_TOKENIZER = lambda { |value| value.split(//) }
+ RESERVED_OPTIONS = [:minimum, :maximum, :within, :is, :tokenizer, :too_short, :too_long]
+
+ def initialize(options)
+ if range = (options.delete(:in) || options.delete(:within))
+ raise ArgumentError, ":in and :within must be a Range" unless range.is_a?(Range)
+ options[:minimum], options[:maximum] = range.begin, range.end
+ options[:maximum] -= 1 if range.exclude_end?
+ end
+
+ super
+ end
+
+ def check_validity!
+ keys = CHECKS.keys & options.keys
+
+ if keys.empty?
+ raise ArgumentError, 'Range unspecified. Specify the :within, :maximum, :minimum, or :is option.'
+ end
+
+ keys.each do |key|
+ value = options[key]
+
+ unless value.is_a?(Integer) && value >= 0
+ raise ArgumentError, ":#{key} must be a nonnegative Integer"
+ end
+ end
+ end
+
+ def validate_each(record, attribute, value)
+ raise(ArgumentError, "A CarrierWave::Uploader::Base object was expected") unless value.kind_of? CarrierWave::Uploader::Base
+
+ value = (options[:tokenizer] || DEFAULT_TOKENIZER).call(value) if value.kind_of?(String)
+
+ CHECKS.each do |key, validity_check|
+ next unless check_value = options[key]
+
+ value ||= [] if key == :maximum
+
+ value_size = value.size
+ next if value_size.send(validity_check, check_value)
+
+ errors_options = options.except(*RESERVED_OPTIONS)
+ errors_options[:file_size] = help.number_to_human_size check_value
+
+ default_message = options[MESSAGES[key]]
+ errors_options[:message] ||= default_message if default_message
+
+ record.errors.add(attribute, MESSAGES[key], errors_options)
+ end
+ end
+
+ def help
+ Helper.instance
+ end
+
+ class Helper
+ include Singleton
+ include ActionView::Helpers::NumberHelper
+ end
+end
diff --git a/lib/gitosis.rb b/lib/gitosis.rb
new file mode 100644
index 00000000..d19fb239
--- /dev/null
+++ b/lib/gitosis.rb
@@ -0,0 +1,70 @@
+require 'lockfile'
+require 'inifile'
+require 'net/ssh'
+
+class Gitosis
+
+ def pull
+ # create tmp dir
+ @local_dir = File.join(Dir.tmpdir,"gitme-gitosis-#{Time.now.to_i}")
+
+ Dir.mkdir @local_dir
+
+ # clone repo
+ `git clone #{GITOSIS['admin_uri']} #{@local_dir}/gitosis`
+ end
+
+ def push
+ # add, commit, push, and remove local tmp dir
+ `cd #{File.join(@local_dir,'gitosis')} ; git add keydir/* gitosis.conf`
+ `cd #{File.join(@local_dir,'gitosis')} ; git commit -a -m 'updated by Gitlab Gitosis'`
+ `cd #{File.join(@local_dir,'gitosis')} ; git push`
+
+ # remove local copy
+ `rm -Rf #{@local_dir}`
+ end
+
+ def configure
+ File.open(File.join(Dir.tmpdir,"gitme-gitosis.lock"), "w+") do |f|
+ f.flock(File::LOCK_EX)
+
+ pull
+ yield(self)
+ push
+
+ f.flock(File::LOCK_UN)
+ end
+ end
+
+ def destroy_project(project)
+ `rm -Rf #{project.path_to_repo}`
+
+ conf = IniFile.new(File.join(@local_dir,'gitosis','gitosis.conf'))
+
+ conf.delete_section("group #{project.path}")
+
+ conf.write
+ end
+
+ #update or create
+ def update_keys(user, key)
+ File.open(File.join(@local_dir, 'gitosis/keydir',"#{user}.pub"), 'w') {|f| f.write(key.gsub(/\n/,'')) }
+ end
+
+ def delete_key(user)
+ File.unlink(File.join(@local_dir, 'gitosis/keydir',"#{user}.pub"))
+ `cd #{File.join(@local_dir,'gitosis')} ; git rm keydir/#{user}.pub`
+ end
+
+ #update or create
+ def update_project(repo_name, name_writers)
+ # write config file
+ conf = IniFile.new(File.join(@local_dir,'gitosis','gitosis.conf'))
+
+ conf["group #{repo_name}"]['writable'] = repo_name
+ conf["group #{repo_name}"]['members'] = name_writers.join(' ')
+
+ conf.write
+ end
+
+end
diff --git a/lib/tasks/.gitkeep b/lib/tasks/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/lib/utils.rb b/lib/utils.rb
new file mode 100644
index 00000000..6e7460ed
--- /dev/null
+++ b/lib/utils.rb
@@ -0,0 +1,8 @@
+module Utils
+ def self.binary?(string)
+ string.each_byte do |x|
+ x.nonzero? or return true
+ end
+ false
+ end
+end
diff --git a/log/.gitkeep b/log/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/public/404.html b/public/404.html
new file mode 100644
index 00000000..16d8fcbc
--- /dev/null
+++ b/public/404.html
@@ -0,0 +1,25 @@
+
+
+
+ The page you were looking for doesn't exist (404)
+
+
+
+
+
+
+
404
+
The page you were looking for doesn't exist.
+
You may have mistyped the address or the page may have moved.
+
+
+
diff --git a/public/422.html b/public/422.html
new file mode 100644
index 00000000..24532664
--- /dev/null
+++ b/public/422.html
@@ -0,0 +1,25 @@
+
+
+
+ The change you wanted was rejected (422)
+
+
+
+
+
+
+
422
+
The change you wanted was rejected.
+
Maybe you tried to change something you didn't have access to.
+
+
+
diff --git a/public/500.html b/public/500.html
new file mode 100644
index 00000000..b1d54464
--- /dev/null
+++ b/public/500.html
@@ -0,0 +1,25 @@
+
+
+
+ We're sorry, but something went wrong (500)
+
+
+
+
+
+
+
500
+
We're sorry, but something went wrong.
+
We've been notified about this issue and we'll take a look at it shortly.
+
+
+
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 00000000..e69de29b
diff --git a/public/index.html.example b/public/index.html.example
new file mode 100644
index 00000000..9d9811a5
--- /dev/null
+++ b/public/index.html.example
@@ -0,0 +1,241 @@
+
+
+
+ Ruby on Rails: Welcome aboard
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Getting started
+
Here’s how to get rolling:
+
+
+
+ Use rails generate
to create your models and controllers
+ To see all available options, run it without parameters.
+
+
+
+ Set up a default route and remove public/index.html
+ Routes are set up in config/routes.rb .
+
+
+
+ Create your database
+ Run rake db:create
to create your database. If you're not using SQLite (the default), edit config/database.yml with your username and password.
+
+
+
+
+
+
+
+
+
diff --git a/public/robots.txt b/public/robots.txt
new file mode 100644
index 00000000..085187fa
--- /dev/null
+++ b/public/robots.txt
@@ -0,0 +1,5 @@
+# See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file
+#
+# To ban all spiders from the entire site uncomment the next two lines:
+# User-Agent: *
+# Disallow: /
diff --git a/script/rails b/script/rails
new file mode 100755
index 00000000..f8da2cff
--- /dev/null
+++ b/script/rails
@@ -0,0 +1,6 @@
+#!/usr/bin/env ruby
+# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
+
+APP_PATH = File.expand_path('../../config/application', __FILE__)
+require File.expand_path('../../config/boot', __FILE__)
+require 'rails/commands'
diff --git a/spec/factories.rb b/spec/factories.rb
new file mode 100644
index 00000000..457c08ba
--- /dev/null
+++ b/spec/factories.rb
@@ -0,0 +1,43 @@
+require File.join(Rails.root, 'spec', 'factory')
+
+Factory.add(:project, Project) do |obj|
+ obj.name = Faker::Internet.user_name
+ obj.path = 'legit'
+ obj.code = 'LGT'
+end
+
+Factory.add(:public_project, Project) do |obj|
+ obj.name = Faker::Internet.user_name
+ obj.path = 'legit'
+ obj.private_flag = false
+ obj.code = 'LGT'
+end
+
+Factory.add(:user, User) do |obj|
+ obj.email = Faker::Internet.email
+ obj.password = "123456"
+ obj.name = Faker::Name.name
+ obj.password_confirmation = "123456"
+end
+
+Factory.add(:admin, User) do |obj|
+ obj.email = Faker::Internet.email
+ obj.password = "123456"
+ obj.name = Faker::Name.name
+ obj.password_confirmation = "123456"
+ obj.admin = true
+end
+
+Factory.add(:issue, Issue) do |obj|
+ obj.title = Faker::Lorem.sentence
+ obj.content = Faker::Lorem.sentences
+end
+
+Factory.add(:note, Note) do |obj|
+ obj.note = Faker::Lorem.sentence
+end
+
+Factory.add(:key, Key) do |obj|
+ obj.title = "Example key"
+ obj.key = File.read(File.join(Rails.root, "db", "pkey.example"))
+end
diff --git a/spec/factory.rb b/spec/factory.rb
new file mode 100644
index 00000000..29e552b1
--- /dev/null
+++ b/spec/factory.rb
@@ -0,0 +1,29 @@
+class Factory
+ @factories = {}
+
+ class << self
+ def add(name, klass, &block)
+ @factories[name] = [klass, block]
+ end
+
+ def create(name, opts = {})
+ new(name, opts).tap(&:save!)
+ end
+
+ def new(name, opts)
+ factory = @factories[name]
+ factory[0].new.tap do |obj|
+ factory[1].call(obj)
+ end.tap do |obj|
+ opts.each do |k, opt|
+ obj.send("#{k}=", opt)
+ end
+ end
+ end
+ end
+end
+
+def Factory(name, opts={})
+ Factory.create name, opts
+end
+
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
new file mode 100644
index 00000000..23609023
--- /dev/null
+++ b/spec/models/issue_spec.rb
@@ -0,0 +1,42 @@
+require 'spec_helper'
+
+describe Issue do
+ describe "Associations" do
+ it { should belong_to(:project) }
+ it { should belong_to(:author) }
+ it { should belong_to(:assignee) }
+ end
+
+ describe "Validation" do
+ it { should validate_presence_of(:title) }
+ it { should validate_presence_of(:author_id) }
+ it { should validate_presence_of(:project_id) }
+ it { should validate_presence_of(:assignee_id) }
+ end
+
+ describe "Scope" do
+ it { Issue.should respond_to :closed }
+ it { Issue.should respond_to :opened }
+ end
+
+ it { Factory.create(:issue,
+ :author => Factory(:user),
+ :assignee => Factory(:user),
+ :project => Factory.create(:project)).should be_valid }
+
+end
+# == Schema Information
+#
+# Table name: issues
+#
+# id :integer not null, primary key
+# title :string(255)
+# content :text
+# assignee_id :integer
+# author_id :integer
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# closed :boolean default(FALSE), not null
+#
+
diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb
new file mode 100644
index 00000000..8515f19b
--- /dev/null
+++ b/spec/models/key_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe Key do
+ describe "Associations" do
+ it { should belong_to(:user) }
+ end
+
+ describe "Validation" do
+ it { should validate_presence_of(:title) }
+ it { should validate_presence_of(:key) }
+ end
+
+ describe "Methods" do
+ it { should respond_to :projects }
+ end
+
+ it { Factory.create(:key,
+ :user => Factory(:user)).should be_valid }
+end
+# == Schema Information
+#
+# Table name: keys
+#
+# id :integer not null, primary key
+# user_id :integer not null
+# created_at :datetime
+# updated_at :datetime
+# key :text
+# title :string(255)
+# identifier :string(255)
+#
+
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
new file mode 100644
index 00000000..20bd41ad
--- /dev/null
+++ b/spec/models/note_spec.rb
@@ -0,0 +1,78 @@
+require 'spec_helper'
+
+describe Note do
+ describe "Associations" do
+ it { should belong_to(:project) }
+ end
+
+ describe "Validation" do
+ it { should validate_presence_of(:note) }
+ it { should validate_presence_of(:project) }
+ end
+
+ it { Factory.create(:note,
+ :project => Factory.create(:project)).should be_valid }
+
+ describe :authorization do
+ before do
+ @p1 = Factory :project
+ @p2 = Factory :project, :code => "alien", :path => "legit_1"
+ @u1 = Factory :user
+ @u2 = Factory :user
+ @u3 = Factory :user
+ @abilities = Six.new
+ @abilities << Ability
+ end
+
+ describe :read do
+ before do
+ @p1.users_projects.create(:user => @u1, :read => false)
+ @p1.users_projects.create(:user => @u2, :read => true)
+ @p2.users_projects.create(:user => @u3, :read => true)
+ end
+
+ it { @abilities.allowed?(@u1, :read_note, @p1).should be_false }
+ it { @abilities.allowed?(@u2, :read_note, @p1).should be_true }
+ it { @abilities.allowed?(@u3, :read_note, @p1).should be_false }
+ end
+
+ describe :write do
+ before do
+ @p1.users_projects.create(:user => @u1, :write => false)
+ @p1.users_projects.create(:user => @u2, :write => true)
+ @p2.users_projects.create(:user => @u3, :write => true)
+ end
+
+ it { @abilities.allowed?(@u1, :write_note, @p1).should be_false }
+ it { @abilities.allowed?(@u2, :write_note, @p1).should be_true }
+ it { @abilities.allowed?(@u3, :write_note, @p1).should be_false }
+ end
+
+ describe :admin do
+ before do
+ @p1.users_projects.create(:user => @u1, :admin => false)
+ @p1.users_projects.create(:user => @u2, :admin => true)
+ @p2.users_projects.create(:user => @u3, :admin => true)
+ end
+
+ it { @abilities.allowed?(@u1, :admin_note, @p1).should be_false }
+ it { @abilities.allowed?(@u2, :admin_note, @p1).should be_true }
+ it { @abilities.allowed?(@u3, :admin_note, @p1).should be_false }
+ end
+ end
+end
+# == Schema Information
+#
+# Table name: notes
+#
+# id :integer not null, primary key
+# note :string(255)
+# noteable_id :string(255)
+# noteable_type :string(255)
+# author_id :integer
+# created_at :datetime
+# updated_at :datetime
+# project_id :integer
+# attachment :string(255)
+#
+
diff --git a/spec/models/project_security_spec.rb b/spec/models/project_security_spec.rb
new file mode 100644
index 00000000..8eb8ee80
--- /dev/null
+++ b/spec/models/project_security_spec.rb
@@ -0,0 +1,57 @@
+require 'spec_helper'
+
+describe Project do
+ describe :authorization do
+ before do
+ @p1 = Factory :project
+ @u1 = Factory :user
+ @u2 = Factory :user
+ @abilities = Six.new
+ @abilities << Ability
+ end
+
+ describe :read do
+ before do
+ @p1.users_projects.create(:project => @p1, :user => @u1, :read => false)
+ @p1.users_projects.create(:project => @p1, :user => @u2, :read => true)
+ end
+
+ it { @abilities.allowed?(@u1, :read_project, @p1).should be_false }
+ it { @abilities.allowed?(@u2, :read_project, @p1).should be_true }
+ end
+
+ describe :write do
+ before do
+ @p1.users_projects.create(:project => @p1, :user => @u1, :write => false)
+ @p1.users_projects.create(:project => @p1, :user => @u2, :write => true)
+ end
+
+ it { @abilities.allowed?(@u1, :write_project, @p1).should be_false }
+ it { @abilities.allowed?(@u2, :write_project, @p1).should be_true }
+ end
+
+ describe :admin do
+ before do
+ @p1.users_projects.create(:project => @p1, :user => @u1, :admin => false)
+ @p1.users_projects.create(:project => @p1, :user => @u2, :admin => true)
+ end
+
+ it { @abilities.allowed?(@u1, :admin_project, @p1).should be_false }
+ it { @abilities.allowed?(@u2, :admin_project, @p1).should be_true }
+ end
+ end
+end
+# == Schema Information
+#
+# Table name: projects
+#
+# id :integer not null, primary key
+# name :string(255)
+# path :string(255)
+# description :text
+# created_at :datetime
+# updated_at :datetime
+# private_flag :boolean default(TRUE), not null
+# code :string(255)
+#
+
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
new file mode 100644
index 00000000..8f41b525
--- /dev/null
+++ b/spec/models/project_spec.rb
@@ -0,0 +1,126 @@
+require 'spec_helper'
+
+describe Project do
+ describe "Associations" do
+ it { should have_many(:users) }
+ it { should have_many(:users_projects) }
+ end
+
+ describe "Validation" do
+ it { should validate_presence_of(:name) }
+ it { should validate_presence_of(:path) }
+ end
+
+ describe "Respond to" do
+ it { should respond_to(:readers) }
+ it { should respond_to(:writers) }
+ it { should respond_to(:gitosis_writers) }
+ it { should respond_to(:admins) }
+ it { should respond_to(:add_access) }
+ it { should respond_to(:reset_access) }
+ it { should respond_to(:update_gitosis_project) }
+ it { should respond_to(:destroy_gitosis_project) }
+ it { should respond_to(:public?) }
+ it { should respond_to(:private?) }
+ it { should respond_to(:url_to_repo) }
+ it { should respond_to(:path_to_repo) }
+ it { should respond_to(:valid_repo?) }
+ it { should respond_to(:repo_exists?) }
+ it { should respond_to(:repo) }
+ it { should respond_to(:tags) }
+ it { should respond_to(:commit) }
+ end
+
+ it "should return valid url to repo" do
+ project = Project.new(:path => "somewhere")
+ project.url_to_repo.should == "git@localhost:somewhere.git"
+ end
+
+ it "should return path to repo" do
+ project = Project.new(:path => "somewhere")
+ project.path_to_repo.should == "/tmp/somewhere"
+ end
+
+ describe :valid_repo? do
+ it "should be valid repo" do
+ project = Factory :project
+ project.valid_repo?.should be_true
+ end
+
+ it "should be invalid repo" do
+ project = Project.new(:name => "ok_name", :path => "/INVALID_PATH/", :code => "NEOK")
+ project.valid_repo?.should be_false
+ end
+ end
+
+ describe "Git methods" do
+ let(:project) { Factory :project }
+
+ describe :repo do
+ it "should return valid repo" do
+ project.repo.should be_kind_of(Grit::Repo)
+ end
+
+ it "should return nil" do
+ lambda { Project.new(:path => "invalid").repo }.should raise_error(Grit::NoSuchPathError)
+ end
+
+ it "should return nil" do
+ lambda { Project.new.repo }.should raise_error(TypeError)
+ end
+ end
+
+ describe :commit do
+ it "should return first head commit if without params" do
+ project.commit.id.should == project.repo.commits.first.id
+ end
+
+ it "should return valid commit" do
+ project.commit(ValidCommit::ID).should be_valid_commit
+ end
+
+ it "should return nil" do
+ project.commit("+123_4532530XYZ").should be_nil
+ end
+ end
+
+ describe :tree do
+ before do
+ @commit = project.commit(ValidCommit::ID)
+ end
+
+ it "should raise error w/o arguments" do
+ lambda { project.tree }.should raise_error
+ end
+
+ it "should return root tree for commit" do
+ tree = project.tree(@commit)
+ tree.contents.size.should == ValidCommit::FILES_COUNT
+ tree.contents.map(&:name).should == ValidCommit::FILES
+ end
+
+ it "should return root tree for commit with correct path" do
+ tree = project.tree(@commit, ValidCommit::C_FILE_PATH)
+ tree.contents.map(&:name).should == ValidCommit::C_FILES
+ end
+
+ it "should return root tree for commit with incorrect path" do
+ project.tree(@commit, "invalid_path").should be_nil
+ end
+ end
+ end
+end
+# == Schema Information
+#
+# Table name: projects
+#
+# id :integer not null, primary key
+# name :string(255)
+# path :string(255)
+# description :text
+# created_at :datetime
+# updated_at :datetime
+# private_flag :boolean default(TRUE), not null
+# code :string(255)
+#
+
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
new file mode 100644
index 00000000..aedfd20a
--- /dev/null
+++ b/spec/models/user_spec.rb
@@ -0,0 +1,42 @@
+require 'spec_helper'
+
+describe User do
+ describe "Associations" do
+ it { should have_many(:projects) }
+ it { should have_many(:users_projects) }
+ it { should have_many(:issues) }
+ it { should have_many(:assigned_issues) }
+ end
+
+ describe "Respond to" do
+ it { should respond_to(:is_admin?) }
+ it { should respond_to(:identifier) }
+ it { should respond_to(:name) }
+ end
+
+ it "should return valid identifier" do
+ user = User.new(:email => "test@mail.com")
+ user.identifier.should == "test_mail.com"
+ end
+end
+# == Schema Information
+#
+# Table name: users
+#
+# id :integer not null, primary key
+# email :string(255) default(""), not null
+# encrypted_password :string(128) default(""), not null
+# reset_password_token :string(255)
+# reset_password_sent_at :datetime
+# remember_created_at :datetime
+# sign_in_count :integer default(0)
+# current_sign_in_at :datetime
+# last_sign_in_at :datetime
+# current_sign_in_ip :string(255)
+# last_sign_in_ip :string(255)
+# created_at :datetime
+# updated_at :datetime
+# name :string(255)
+# admin :boolean default(FALSE), not null
+#
+
diff --git a/spec/models/users_project_spec.rb b/spec/models/users_project_spec.rb
new file mode 100644
index 00000000..d6a4d02b
--- /dev/null
+++ b/spec/models/users_project_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe UsersProject do
+ describe "Associations" do
+ it { should belong_to(:project) }
+ it { should belong_to(:user) }
+ end
+
+ describe "Validation" do
+ it { should validate_presence_of(:user_id) }
+ it { should validate_presence_of(:project_id) }
+ end
+
+ describe "Delegate methods" do
+ it { should respond_to(:user_name) }
+ it { should respond_to(:user_email) }
+ end
+end
+# == Schema Information
+#
+# Table name: users_projects
+#
+# id :integer not null, primary key
+# user_id :integer not null
+# project_id :integer not null
+# read :boolean default(FALSE)
+# write :boolean default(FALSE)
+# admin :boolean default(FALSE)
+# created_at :datetime
+# updated_at :datetime
+#
+
diff --git a/spec/monkeypatch.rb b/spec/monkeypatch.rb
new file mode 100644
index 00000000..e630cc10
--- /dev/null
+++ b/spec/monkeypatch.rb
@@ -0,0 +1,31 @@
+# Stubbing Project <-> gitosis path
+# create project using Factory only
+class Project
+ def update_gitosis_project
+ true
+ end
+
+ def update_gitosis
+ true
+ end
+
+ def path_to_repo
+ "/tmp/" + path
+ end
+end
+
+class Key
+ def update_gitosis
+ true
+ end
+
+ def gitosis_delete_key
+ true
+ end
+end
+
+class UsersProject
+ def update_gitosis_project
+ true
+ end
+end
diff --git a/spec/requests/admin/admin_projects_spec.rb b/spec/requests/admin/admin_projects_spec.rb
new file mode 100644
index 00000000..8aa311e8
--- /dev/null
+++ b/spec/requests/admin/admin_projects_spec.rb
@@ -0,0 +1,106 @@
+require 'spec_helper'
+
+describe "Admin::Projects" do
+ before do
+ @project = Factory :project,
+ :name => "LeGiT",
+ :code => "LGT"
+ login_as :admin
+ end
+
+ describe "GET /admin/projects" do
+ before do
+ visit admin_projects_path
+ end
+
+ it "should be ok" do
+ current_path.should == admin_projects_path
+ end
+
+ it "should have projects list" do
+ page.should have_content(@project.code)
+ page.should have_content(@project.name)
+ end
+ end
+
+ describe "GET /admin/projects/:id" do
+ before do
+ visit admin_projects_path
+ click_link "Show"
+ end
+
+ it "should have project info" do
+ page.should have_content(@project.code)
+ page.should have_content(@project.name)
+ end
+ end
+
+ describe "GET /admin/projects/:id/edit" do
+ before do
+ visit admin_projects_path
+ click_link "edit_project_#{@project.id}"
+ end
+
+ it "should have project edit page" do
+ page.should have_content("Name")
+ page.should have_content("Code")
+ end
+
+ describe "Update project" do
+ before do
+ fill_in "project_name", :with => "Big Bang"
+ fill_in "project_code", :with => "BB1"
+ click_button "Save"
+ @project.reload
+ end
+
+ it "should show page with new data" do
+ page.should have_content("BB1")
+ page.should have_content("Big Bang")
+ end
+
+ it "should change project entry" do
+ @project.name.should == "Big Bang"
+ @project.code.should == "BB1"
+ end
+ end
+ end
+
+ describe "GET /admin/projects/new" do
+ before do
+ visit admin_projects_path
+ click_link "New Project"
+ end
+
+ it "should be correct path" do
+ current_path.should == new_admin_project_path
+ end
+
+ it "should have labels for new project" do
+ page.should have_content("Name")
+ page.should have_content("Path")
+ page.should have_content("Description")
+ end
+ end
+
+ describe "POST /admin/projects" do
+ before do
+ visit new_admin_project_path
+ fill_in 'Name', :with => 'NewProject'
+ fill_in 'Code', :with => 'NPR'
+ fill_in 'Path', :with => '/tmp/legit_test/legit'
+ expect { click_button "Save" }.to change { Project.count }.by(1)
+ @project = Project.last
+ end
+
+ it "should be correct path" do
+ current_path.should == admin_project_path(@project)
+ end
+
+ it "should show project" do
+ page.should have_content(@project.name)
+ page.should have_content(@project.path)
+ page.should have_content(@project.description)
+ end
+ end
+end
diff --git a/spec/requests/admin/admin_users_spec.rb b/spec/requests/admin/admin_users_spec.rb
new file mode 100644
index 00000000..8d9cbcae
--- /dev/null
+++ b/spec/requests/admin/admin_users_spec.rb
@@ -0,0 +1,102 @@
+require 'spec_helper'
+
+describe "Admin::Users" do
+ before { login_as :admin }
+
+ describe "GET /admin/users" do
+ before do
+ visit admin_users_path
+ end
+
+ it "should be ok" do
+ current_path.should == admin_users_path
+ end
+
+ it "should have users list" do
+ page.should have_content(@user.email)
+ page.should have_content(@user.name)
+ end
+ end
+
+ describe "GET /admin/users/new" do
+ before do
+ @password = "123ABC"
+ visit new_admin_user_path
+ fill_in "user_name", :with => "Big Bang"
+ fill_in "user_email", :with => "bigbang@mail.com"
+ fill_in "user_password", :with => @password
+ fill_in "user_password_confirmation", :with => @password
+ end
+
+ it "should create new user" do
+ expect { click_button "Save" }.to change {User.count}.by(1)
+ end
+
+ it "should create user with valid data" do
+ click_button "Save"
+ user = User.last
+ user.name.should == "Big Bang"
+ user.email.should == "bigbang@mail.com"
+ end
+
+ it "should call send mail" do
+ Notify.should_receive(:new_user_email).and_return(stub(:deliver => true))
+ click_button "Save"
+ end
+
+ it "should send valid email to user with email & password" do
+ click_button "Save"
+ user = User.last
+ email = ActionMailer::Base.deliveries.last
+ email.subject.should have_content("Account was created")
+ email.body.should have_content(user.email)
+ email.body.should have_content(@password)
+ end
+ end
+
+ describe "GET /admin/users/:id" do
+ before do
+ visit admin_users_path
+ click_link "Show"
+ end
+
+ it "should have user info" do
+ page.should have_content(@user.email)
+ page.should have_content(@user.name)
+ page.should have_content(@user.is_admin?)
+ end
+ end
+
+ describe "GET /admin/users/:id/edit" do
+ before do
+ @simple_user = Factory :user
+ visit admin_users_path
+ click_link "edit_user_#{@simple_user.id}"
+ end
+
+ it "should have user edit page" do
+ page.should have_content("Name")
+ page.should have_content("Password")
+ end
+
+ describe "Update user" do
+ before do
+ fill_in "user_name", :with => "Big Bang"
+ fill_in "user_email", :with => "bigbang@mail.com"
+ check "user_admin"
+ click_button "Save"
+ end
+
+ it "should show page with new data" do
+ page.should have_content("bigbang@mail.com")
+ page.should have_content("Big Bang")
+ end
+
+ it "should change user entry" do
+ @simple_user.reload
+ @simple_user.name.should == "Big Bang"
+ @simple_user.is_admin?.should be_true
+ end
+ end
+ end
+end
diff --git a/spec/requests/admin/security_spec.rb b/spec/requests/admin/security_spec.rb
new file mode 100644
index 00000000..743f9f08
--- /dev/null
+++ b/spec/requests/admin/security_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+describe "Admin::Projects" do
+ describe "GET /admin/projects" do
+ it { admin_projects_path.should be_allowed_for :admin }
+ it { admin_projects_path.should be_denied_for :user }
+ it { admin_projects_path.should be_denied_for :visitor }
+ end
+
+ describe "GET /admin/users" do
+ it { admin_users_path.should be_allowed_for :admin }
+ it { admin_users_path.should be_denied_for :user }
+ it { admin_users_path.should be_denied_for :visitor }
+ end
+
+ describe "GET /admin/team_members" do
+ it { admin_team_members_path.should be_allowed_for :admin }
+ it { admin_team_members_path.should be_denied_for :user }
+ it { admin_team_members_path.should be_denied_for :visitor }
+ end
+
+ describe "GET /admin/emails" do
+ it { admin_emails_path.should be_allowed_for :admin }
+ it { admin_emails_path.should be_denied_for :user }
+ it { admin_emails_path.should be_denied_for :visitor }
+ end
+end
diff --git a/spec/requests/commits_notes_spec.rb b/spec/requests/commits_notes_spec.rb
new file mode 100644
index 00000000..522fe518
--- /dev/null
+++ b/spec/requests/commits_notes_spec.rb
@@ -0,0 +1,24 @@
+require 'spec_helper'
+
+describe "Issues" do
+ let(:project) { Factory :project }
+ let!(:commit) { project.repo.commits.first }
+
+ before do
+ login_as :user
+ project.add_access(@user, :read, :write)
+ end
+
+ describe "add new note", :js => true do
+ before do
+ visit project_commit_path(project, commit)
+ click_link "Comments" # notes tab
+ fill_in "note_note", :with => "I commented this commit"
+ click_button "Add note"
+ end
+
+ it "should conatin new note" do
+ page.should have_content("I commented this commit")
+ end
+ end
+end
diff --git a/spec/requests/commits_spec.rb b/spec/requests/commits_spec.rb
new file mode 100644
index 00000000..cd7314f9
--- /dev/null
+++ b/spec/requests/commits_spec.rb
@@ -0,0 +1,39 @@
+require 'spec_helper'
+
+describe "Commits" do
+ let(:project) { Factory :project }
+ let!(:commit) { project.repo.commits.first }
+ before do
+ login_as :user
+ project.add_access(@user, :read)
+ end
+
+ describe "GET /commits" do
+ before do
+ visit project_commits_path(project)
+ end
+
+ it "should have valid path" do
+ current_path.should == project_commits_path(project)
+ end
+
+ it "should have project name" do
+ page.should have_content(project.name)
+ end
+
+ it "should list commits" do
+ page.should have_content(commit.author)
+ page.should have_content(commit.message)
+ end
+ end
+
+ describe "GET /commits/:id" do
+ before do
+ visit project_commit_path(project, commit)
+ end
+
+ it "should have valid path" do
+ current_path.should == project_commit_path(project, commit)
+ end
+ end
+end
diff --git a/spec/requests/issues_notes_spec.rb b/spec/requests/issues_notes_spec.rb
new file mode 100644
index 00000000..95eb2f35
--- /dev/null
+++ b/spec/requests/issues_notes_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+describe "Issues" do
+ let(:project) { Factory :project }
+
+ before do
+ login_as :user
+ project.add_access(@user, :read, :write)
+
+ @issue = Factory :issue,
+ :author => @user,
+ :assignee => @user,
+ :project => project
+ end
+
+ describe "add new note", :js => true do
+ before do
+ visit project_issue_path(project, @issue)
+ fill_in "note_note", :with => "I commented this issue"
+ click_button "Add note"
+ end
+
+ it "should conatin new note" do
+ page.should have_content("I commented this issue")
+ end
+ end
+end
diff --git a/spec/requests/issues_spec.rb b/spec/requests/issues_spec.rb
new file mode 100644
index 00000000..79fdf5ef
--- /dev/null
+++ b/spec/requests/issues_spec.rb
@@ -0,0 +1,147 @@
+require 'spec_helper'
+
+describe "Issues" do
+ let(:project) { Factory :project }
+
+ before do
+ login_as :user
+ project.add_access(@user, :read, :write)
+ end
+
+ describe "GET /issues" do
+ before do
+ @issue = Factory :issue,
+ :author => @user,
+ :assignee => @user,
+ :project => project
+
+ visit project_issues_path(project)
+ end
+
+ subject { page }
+
+ it { should have_content(@issue.title) }
+ it { should have_content(@issue.project.name) }
+ it { should have_content(@issue.assignee.name) }
+
+ describe "Destroy" do
+ before do
+ # admin access to remove issue
+ @user.users_projects.destroy_all
+ project.add_access(@user, :read, :write, :admin)
+ visit project_issues_path(project)
+ end
+
+ it "should remove entry" do
+ expect {
+ click_link "destroy_issue_#{@issue.id}"
+ }.to change { Issue.count }.by(-1)
+ end
+ end
+
+ describe "statuses", :js => true do
+ before do
+ @closed_issue = Factory :issue,
+ :author => @user,
+ :assignee => @user,
+ :project => project,
+ :closed => true
+ end
+
+ it "should show only open" do
+ should have_content(@issue.title)
+ should have_no_content(@closed_issue.title)
+ end
+
+ it "should show only closed" do
+ choose "closed_issues"
+ should have_no_content(@issue.title)
+ should have_content(@closed_issue.title)
+ end
+
+ it "should show all" do
+ choose "all_issues"
+ should have_content(@issue.title)
+ should have_content(@closed_issue.title)
+ end
+ end
+ end
+
+ describe "New issue", :js => true do
+ before do
+ visit project_issues_path(project)
+ click_link "New Issue"
+ end
+
+ it "should open new issue popup" do
+ page.should have_content("Add new issue")
+ end
+
+ describe "fill in" do
+ before do
+ fill_in "issue_title", :with => "bug 345"
+ fill_in "issue_content", :with => "app bug 345"
+ click_link "Select user"
+ click_link @user.name
+ end
+
+ it { expect { click_button "Save" }.to change {Issue.count}.by(1) }
+
+ it "should add new issue to table" do
+ click_button "Save"
+
+ page.should_not have_content("Add new issue")
+ page.should have_content @user.name
+ page.should have_content "bug 345"
+ page.should have_content project.name
+ end
+
+ it "should call send mail" do
+ Notify.should_receive(:new_issue_email).and_return(stub(:deliver => true))
+ click_button "Save"
+ end
+
+ it "should send valid email to user with email & password" do
+ click_button "Save"
+ issue = Issue.last
+ email = ActionMailer::Base.deliveries.last
+ email.subject.should have_content("New Issue was created")
+ email.body.should have_content(issue.title)
+ email.body.should have_content(issue.assignee.name)
+ end
+ end
+ end
+
+ describe "Edit issue", :js => true do
+ before do
+ @issue = Factory :issue,
+ :author => @user,
+ :assignee => @user,
+ :project => project
+ visit project_issues_path(project)
+ click_link "Edit"
+ end
+
+ it "should open new issue popup" do
+ page.should have_content("Issue ##{@issue.id}")
+ end
+
+ describe "fill in" do
+ before do
+ fill_in "issue_title", :with => "bug 345"
+ fill_in "issue_content", :with => "app bug 345"
+ end
+
+ it { expect { click_button "Save" }.to_not change {Issue.count} }
+
+ it "should update issue fields" do
+ click_button "Save"
+
+ page.should_not have_content("Issue ##{@issue.id}")
+ page.should have_content @user.name
+ page.should have_content "bug 345"
+ page.should have_content project.name
+ end
+ end
+ end
+end
diff --git a/spec/requests/keys_spec.rb b/spec/requests/keys_spec.rb
new file mode 100644
index 00000000..316115f9
--- /dev/null
+++ b/spec/requests/keys_spec.rb
@@ -0,0 +1,54 @@
+require 'spec_helper'
+
+describe "Issues" do
+ before do
+ login_as :user
+ end
+
+ describe "GET /keys" do
+ before do
+ @key = Factory :key, :user => @user
+ visit keys_path
+ end
+
+ subject { page }
+
+ it { should have_content(@key.title) }
+
+ describe "Destroy" do
+ it "should remove entry" do
+ expect {
+ click_link "destroy_key_#{@key.id}"
+ }.to change { @user.keys.count }.by(-1)
+ end
+ end
+ end
+
+ describe "New key", :js => true do
+ before do
+ visit keys_path
+ click_link "Add new"
+ end
+
+ it "should open new key popup" do
+ page.should have_content("Add new public key")
+ end
+
+ describe "fill in" do
+ before do
+ fill_in "key_title", :with => "laptop"
+ fill_in "key_key", :with => "publickey234="
+ end
+
+ it { expect { click_button "Save" }.to change {Key.count}.by(1) }
+
+ it "should add new key to table" do
+ click_button "Save"
+
+ page.should_not have_content("Add new public key")
+ page.should have_content "laptop"
+ page.should have_content "publickey234="
+ end
+ end
+ end
+end
diff --git a/spec/requests/profile_spec.rb b/spec/requests/profile_spec.rb
new file mode 100644
index 00000000..07fdc4ab
--- /dev/null
+++ b/spec/requests/profile_spec.rb
@@ -0,0 +1,55 @@
+require 'spec_helper'
+
+describe "Profile" do
+ before do
+ login_as :user
+ end
+
+ describe "Show profile" do
+ before do
+ visit profile_path
+ end
+
+ it { page.should have_content(@user.name) }
+ it { page.should have_content(@user.email) }
+ end
+
+ describe "Password update" do
+ before do
+ visit profile_password_path
+ end
+
+ it { page.should have_content("Password") }
+ it { page.should have_content("Password confirmation") }
+
+ describe "change password" do
+ before do
+ @old_pwd = @user.encrypted_password
+ fill_in "user_password", :with => "777777"
+ fill_in "user_password_confirmation", :with => "777777"
+ click_button "Save"
+ @user.reload
+ end
+
+ it "should redirect to signin page" do
+ current_path.should == new_user_session_path
+ end
+
+ it "should change password" do
+ @user.encrypted_password.should_not == @old_pwd
+ end
+
+ describe "login with new password" do
+ before do
+ fill_in "user_email", :with => @user.email
+ fill_in "user_password", :with => "777777"
+ click_button "Sign in"
+ end
+
+ it "should login user" do
+ current_path.should == root_path
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/projects_security_spec.rb b/spec/requests/projects_security_spec.rb
new file mode 100644
index 00000000..a725a49c
--- /dev/null
+++ b/spec/requests/projects_security_spec.rb
@@ -0,0 +1,111 @@
+require 'spec_helper'
+
+describe "Projects" do
+ describe "GET /projects" do
+ it { projects_path.should be_allowed_for :admin }
+ it { projects_path.should be_allowed_for :user }
+ it { projects_path.should be_denied_for :visitor }
+ end
+
+ describe "GET /projects/new" do
+ it { projects_path.should be_allowed_for :admin }
+ it { projects_path.should be_allowed_for :user }
+ it { projects_path.should be_denied_for :visitor }
+ end
+
+ describe "Project" do
+ before do
+ @project = Factory :project
+ @u1 = Factory :user
+ @u2 = Factory :user
+ @u3 = Factory :user
+ # full access
+ @project.users_projects.create(:user => @u1, :read => true, :write => true, :admin => true)
+ # no access
+ @project.users_projects.create(:user => @u2, :read => false, :write => false, :admin => false)
+ # readonly
+ @project.users_projects.create(:user => @u3, :read => true, :write => false, :admin => false)
+ end
+
+ describe "GET /project_code" do
+ it { project_path(@project).should be_allowed_for @u1 }
+ it { project_path(@project).should be_allowed_for @u3 }
+ it { project_path(@project).should be_denied_for :admin }
+ it { project_path(@project).should be_denied_for @u2 }
+ it { project_path(@project).should be_denied_for :user }
+ it { project_path(@project).should be_denied_for :visitor }
+ end
+
+ describe "GET /project_code/tree" do
+ it { tree_project_path(@project).should be_allowed_for @u1 }
+ it { tree_project_path(@project).should be_allowed_for @u3 }
+ it { tree_project_path(@project).should be_denied_for :admin }
+ it { tree_project_path(@project).should be_denied_for @u2 }
+ it { tree_project_path(@project).should be_denied_for :user }
+ it { tree_project_path(@project).should be_denied_for :visitor }
+ end
+
+ describe "GET /project_code/commits" do
+ it { project_commits_path(@project).should be_allowed_for @u1 }
+ it { project_commits_path(@project).should be_allowed_for @u3 }
+ it { project_commits_path(@project).should be_denied_for :admin }
+ it { project_commits_path(@project).should be_denied_for @u2 }
+ it { project_commits_path(@project).should be_denied_for :user }
+ it { project_commits_path(@project).should be_denied_for :visitor }
+ end
+
+ describe "GET /project_code/commit" do
+ it { project_commit_path(@project, @project.commit).should be_allowed_for @u1 }
+ it { project_commit_path(@project, @project.commit).should be_allowed_for @u3 }
+ it { project_commit_path(@project, @project.commit).should be_denied_for :admin }
+ it { project_commit_path(@project, @project.commit).should be_denied_for @u2 }
+ it { project_commit_path(@project, @project.commit).should be_denied_for :user }
+ it { project_commit_path(@project, @project.commit).should be_denied_for :visitor }
+ end
+
+ describe "GET /project_code/team" do
+ it { team_project_path(@project).should be_allowed_for @u1 }
+ it { team_project_path(@project).should be_allowed_for @u3 }
+ it { team_project_path(@project).should be_denied_for :admin }
+ it { team_project_path(@project).should be_denied_for @u2 }
+ it { team_project_path(@project).should be_denied_for :user }
+ it { team_project_path(@project).should be_denied_for :visitor }
+ end
+
+ describe "GET /project_code/wall" do
+ it { wall_project_path(@project).should be_allowed_for @u1 }
+ it { wall_project_path(@project).should be_allowed_for @u3 }
+ it { wall_project_path(@project).should be_denied_for :admin }
+ it { wall_project_path(@project).should be_denied_for @u2 }
+ it { wall_project_path(@project).should be_denied_for :user }
+ it { wall_project_path(@project).should be_denied_for :visitor }
+ end
+
+ describe "GET /project_code/blob" do
+ it { blob_project_path(@project).should be_allowed_for @u1 }
+ it { blob_project_path(@project).should be_allowed_for @u3 }
+ it { blob_project_path(@project).should be_denied_for :admin }
+ it { blob_project_path(@project).should be_denied_for @u2 }
+ it { blob_project_path(@project).should be_denied_for :user }
+ it { blob_project_path(@project).should be_denied_for :visitor }
+ end
+
+ describe "GET /project_code/edit" do
+ it { edit_project_path(@project).should be_allowed_for @u1 }
+ it { edit_project_path(@project).should be_denied_for @u3 }
+ it { edit_project_path(@project).should be_denied_for :admin }
+ it { edit_project_path(@project).should be_denied_for @u2 }
+ it { edit_project_path(@project).should be_denied_for :user }
+ it { edit_project_path(@project).should be_denied_for :visitor }
+ end
+
+ describe "GET /project_code/issues" do
+ it { project_issues_path(@project).should be_allowed_for @u1 }
+ it { project_issues_path(@project).should be_allowed_for @u3 }
+ it { project_issues_path(@project).should be_denied_for :admin }
+ it { project_issues_path(@project).should be_denied_for @u2 }
+ it { project_issues_path(@project).should be_denied_for :user }
+ it { project_issues_path(@project).should be_denied_for :visitor }
+ end
+ end
+end
diff --git a/spec/requests/projects_spec.rb b/spec/requests/projects_spec.rb
new file mode 100644
index 00000000..329f0a50
--- /dev/null
+++ b/spec/requests/projects_spec.rb
@@ -0,0 +1,152 @@
+require 'spec_helper'
+
+describe "Projects" do
+ before { login_as :user }
+
+ describe "GET /projects" do
+ before do
+ visit projects_path
+ end
+
+ it "should be on projects page" do
+ current_path.should == projects_path
+ end
+
+ it "should have link to new project" do
+ page.should have_content("New Project")
+ end
+ end
+
+ describe "GET /projects/new" do
+ before do
+ visit projects_path
+ click_link "New Project"
+ end
+
+ it "should be correct path" do
+ current_path.should == new_project_path
+ end
+
+ it "should have labels for new project" do
+ page.should have_content("Name")
+ page.should have_content("Path")
+ page.should have_content("Description")
+ end
+ end
+
+ describe "POST /projects" do
+ before do
+ visit new_project_path
+ fill_in 'Name', :with => 'NewProject'
+ fill_in 'Code', :with => 'NPR'
+ fill_in 'Path', :with => '/tmp/legit_test/legit'
+ expect { click_button "Create Project" }.to change { Project.count }.by(1)
+ @project = Project.last
+ end
+
+ it "should be correct path" do
+ current_path.should == project_path(@project)
+ end
+
+ it "should show project" do
+ page.should have_content(@project.name)
+ page.should have_content(@project.path)
+ page.should have_content(@project.description)
+ end
+
+ it "should init repo instructions" do
+ page.should have_content("git remote")
+ page.should have_content(@project.url_to_repo)
+ end
+ end
+
+ describe "GET /projects/show" do
+ before do
+ @project = Factory :project
+ @project.add_access(@user, :read)
+
+ visit project_path(@project)
+ end
+
+ it "should be correct path" do
+ current_path.should == project_path(@project)
+ end
+
+ it_behaves_like :tree_view
+ end
+
+ describe "GET /projects/team" do
+ before do
+ @project = Factory :project
+ @project.add_access(@user, :read)
+
+ visit team_project_path(@project,
+ :path => ValidCommit::BLOB_FILE_PATH,
+ :commit_id => ValidCommit::ID)
+ end
+
+ it "should be correct path" do
+ current_path.should == team_project_path(@project)
+ end
+
+ it "should have as as team member" do
+ page.should have_content(@user.name)
+ end
+ end
+
+ describe "GET /projects/:id/edit" do
+ before do
+ @project = Factory :project
+ @project.add_access(@user, :admin, :read)
+
+ visit edit_project_path(@project)
+ end
+
+ it "should be correct path" do
+ current_path.should == edit_project_path(@project)
+ end
+
+ it "should have labels for new project" do
+ page.should have_content("Name")
+ page.should have_content("Path")
+ page.should have_content("Description")
+ end
+ end
+
+ describe "PUT /projects/:id" do
+ before do
+ @project = Factory :project
+ @project.add_access(@user, :admin, :read)
+
+ visit edit_project_path(@project)
+
+ fill_in 'Name', :with => 'Awesome'
+ fill_in 'Path', :with => 'legit'
+ fill_in 'Description', :with => 'Awesome project'
+ click_button "Update Project"
+ @project = @project.reload
+ end
+
+ it "should be correct path" do
+ current_path.should == project_path(@project)
+ end
+
+ it "should show project" do
+ page.should have_content("Awesome")
+ end
+
+ it_behaves_like :tree_view
+ end
+
+ #describe "DELETE /projects/:id", :js => true do
+ #before do
+ #@project = Factory :project
+ #@project.add_access(@user, :read, :admin)
+ #visit projects_path
+ #end
+
+ #it "should be correct path" do
+ #expect { click_link "Destroy" }.to change {Project.count}.by(1)
+ #end
+ #end
+end
diff --git a/spec/requests/projects_tree_spec.rb b/spec/requests/projects_tree_spec.rb
new file mode 100644
index 00000000..4e3176bb
--- /dev/null
+++ b/spec/requests/projects_tree_spec.rb
@@ -0,0 +1,92 @@
+require 'spec_helper'
+
+describe "Projects" do
+ before { login_as :user }
+
+ describe "GET /projects/tree" do
+ describe "head" do
+ before do
+ @project = Factory :project
+ @project.add_access(@user, :read)
+
+ visit tree_project_path(@project)
+ end
+
+ it "should be correct path" do
+ current_path.should == tree_project_path(@project)
+ end
+
+ it_behaves_like :tree_view
+ end
+
+ describe ValidCommit::ID do
+ before do
+ @project = Factory :project
+ @project.add_access(@user, :read)
+
+ visit tree_project_path(@project, :commit_id => ValidCommit::ID)
+ end
+
+ it "should be correct path" do
+ current_path.should == tree_project_path(@project)
+ end
+
+ it_behaves_like :tree_view
+ it_behaves_like :project_side_pane
+ end
+
+ describe "branch passed" do
+ before do
+ @project = Factory :project
+ @project.add_access(@user, :read)
+
+ visit tree_project_path(@project, :branch => "master")
+ end
+
+ it "should be correct path" do
+ current_path.should == tree_project_path(@project)
+ end
+
+ it_behaves_like :tree_view
+ it_behaves_like :project_side_pane
+ end
+
+ # TREE FILE PREVIEW
+ describe "file preview" do
+ before do
+ @project = Factory :project
+ @project.add_access(@user, :read)
+
+ visit tree_project_path(@project, :path => ".rvmrc")
+ end
+
+ it "should be correct path" do
+ current_path.should == tree_project_path(@project)
+ end
+
+ it "should contain file view" do
+ page.should have_content("rvm use 1.9.2@legit")
+ end
+ end
+ end
+
+ # RAW FILE
+ describe "GET /projects/blob" do
+ before do
+ @project = Factory :project
+ @project.add_access(@user, :read)
+
+ visit blob_project_path(@project,
+ :path => ValidCommit::BLOB_FILE_PATH,
+ :commit_id => ValidCommit::ID)
+ end
+
+ it "should be correct path" do
+ current_path.should == blob_project_path(@project)
+ end
+
+ it "raw file response" do
+ page.source.should == ValidCommit::BLOB_FILE
+ end
+ end
+end
diff --git a/spec/requests/projects_wall_spec.rb b/spec/requests/projects_wall_spec.rb
new file mode 100644
index 00000000..a7387f93
--- /dev/null
+++ b/spec/requests/projects_wall_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe "Projects", "Wall" do
+ let(:project) { Factory :project }
+
+ before do
+ login_as :user
+ project.add_access(@user, :read, :write)
+ end
+
+ describe "View notes on wall" do
+ before do
+ Factory :note, :project => project, :note => "Project specs", :author => @user
+ visit wall_project_path(project)
+ end
+
+ it { page.should have_content("Project specs") }
+ it { page.should have_content(@user.name) }
+ it { page.should have_content("less than a minute ago") }
+ end
+
+ describe "add new note", :js => true do
+ before do
+ visit wall_project_path(project)
+ fill_in "note_note", :with => "my post on wall"
+ click_button "Add note"
+ end
+
+ it "should conatin new note" do
+ page.should have_content("my post on wall")
+ end
+ end
+end
diff --git a/spec/requests/team_members_spec.rb b/spec/requests/team_members_spec.rb
new file mode 100644
index 00000000..db7513ae
--- /dev/null
+++ b/spec/requests/team_members_spec.rb
@@ -0,0 +1,46 @@
+require 'spec_helper'
+
+describe "TeamMembers" do
+ before do
+ login_as :user
+ @project = Factory :project
+ @project.add_access(@user, :read, :admin)
+ end
+
+ describe "New Team member", :js => true do
+ before do
+ @user_1 = Factory :user
+ visit team_project_path(@project)
+ click_link "Add new"
+ end
+
+ it "should open new team member popup" do
+ page.should have_content("Add new member to project")
+ end
+
+ describe "fill in" do
+ before do
+ check "team_member_read"
+ click_link "Select user"
+ click_link @user_1.name
+ #select @user_1.name, :from => "team_member_user_id"
+ end
+
+ it { expect { click_button "Save" }.to change {UsersProject.count}.by(1) }
+
+ it "should add new member to table" do
+ click_button "Save"
+
+ page.should_not have_content("Add new member")
+ page.should have_content @user_1.name
+ end
+ end
+ end
+
+ describe "Cancel membership" do
+ it "should cancel membership" do
+ visit team_project_path(@project)
+ expect { click_link "Cancel" }.to change { UsersProject.count }.by(-1)
+ end
+ end
+end
diff --git a/spec/requests/top_panel_spec.rb b/spec/requests/top_panel_spec.rb
new file mode 100644
index 00000000..bd4d2047
--- /dev/null
+++ b/spec/requests/top_panel_spec.rb
@@ -0,0 +1,34 @@
+require 'spec_helper'
+
+describe "Top Panel", :js => true do
+ before { login_as :user }
+
+ describe "Search autocomplete" do
+ before do
+ visit projects_path
+ fill_in "search", :with => "Ke"
+ sleep(2)
+ find(:xpath, "//ul[contains(@class,'ui-autocomplete')]/li/a[.=\"Keys\"]").click
+ end
+
+ it "should be on projects page" do
+ current_path.should == keys_path
+ end
+ end
+
+ describe "with project" do
+ before do
+ @project = Factory :project
+ @project.add_access(@user, :read)
+ visit project_path(@project)
+
+ fill_in "search", :with => "Commi"
+ sleep(2)
+ find(:xpath, "//ul[contains(@class,'ui-autocomplete')]/li/a[.=\"#{@project.code} / Commits\"]").click
+ end
+
+ it "should be on projects page" do
+ current_path.should == project_commits_path(@project)
+ end
+ end
+end
diff --git a/spec/requests/user_security_spec.rb b/spec/requests/user_security_spec.rb
new file mode 100644
index 00000000..3c923870
--- /dev/null
+++ b/spec/requests/user_security_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+
+describe "Users Security" do
+ describe "Project" do
+ before do
+ @u1 = Factory :user
+ end
+
+ describe "GET /login" do
+ it { new_user_session_path.should be_denied_for @u1 }
+ it { new_user_session_path.should be_denied_for :admin }
+ it { new_user_session_path.should be_denied_for :user }
+ it { new_user_session_path.should be_allowed_for :visitor }
+ end
+
+ describe "GET /keys" do
+ it { keys_path.should be_allowed_for @u1 }
+ it { keys_path.should be_allowed_for :admin }
+ it { keys_path.should be_allowed_for :user }
+ it { keys_path.should be_denied_for :visitor }
+ end
+
+ describe "GET /profile" do
+ it { profile_path.should be_allowed_for @u1 }
+ it { profile_path.should be_allowed_for :admin }
+ it { profile_path.should be_allowed_for :user }
+ it { profile_path.should be_denied_for :visitor }
+ end
+
+ describe "GET /profile/password" do
+ it { profile_password_path.should be_allowed_for @u1 }
+ it { profile_password_path.should be_allowed_for :admin }
+ it { profile_password_path.should be_allowed_for :user }
+ it { profile_password_path.should be_denied_for :visitor }
+ end
+ end
+end
diff --git a/spec/seed_project.tar.gz b/spec/seed_project.tar.gz
new file mode 100644
index 00000000..6474c324
Binary files /dev/null and b/spec/seed_project.tar.gz differ
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
new file mode 100644
index 00000000..0c2545e3
--- /dev/null
+++ b/spec/spec_helper.rb
@@ -0,0 +1,56 @@
+require 'simplecov'
+SimpleCov.start 'rails'
+
+# This file is copied to spec/ when you run 'rails generate rspec:install'
+ENV["RAILS_ENV"] ||= 'test'
+require File.expand_path("../../config/environment", __FILE__)
+require 'rspec/rails'
+require 'capybara/rails'
+require 'capybara/rspec'
+require 'capybara/dsl'
+require 'factories'
+require 'monkeypatch'
+
+
+# Requires supporting ruby files with custom matchers and macros, etc,
+# in spec/support/ and its subdirectories.
+Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
+
+RSpec.configure do |config|
+ # == Mock Framework
+ #
+ # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
+ #
+ # config.mock_with :mocha
+ # config.mock_with :flexmock
+ # config.mock_with :rr
+ config.mock_with :rspec
+
+ config.include LoginMacros
+
+ # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
+ config.fixture_path = "#{::Rails.root}/spec/fixtures"
+
+ # If you're not using ActiveRecord, or you'd prefer not to run each of your
+ # examples within a transaction, remove the following line or assign false
+ # instead of true.
+ config.use_transactional_fixtures = false
+
+ config.before :each, :type => :integration do
+ DeviseSessionMock.disable
+ end
+
+ config.before do
+ if example.metadata[:js]
+ DatabaseCleaner.strategy = :truncation
+ else
+ DatabaseCleaner.strategy = :transaction
+ end
+
+ DatabaseCleaner.start
+ end
+
+ config.after do
+ DatabaseCleaner.clean
+ end
+end
diff --git a/spec/support/js_patch.rb b/spec/support/js_patch.rb
new file mode 100644
index 00000000..0d4ab264
--- /dev/null
+++ b/spec/support/js_patch.rb
@@ -0,0 +1,6 @@
+module JsPatch
+ def confirm_js_popup
+ page.evaluate_script("window.alert = function(msg) { return true; }")
+ page.evaluate_script("window.confirm = function(msg) { return true; }")
+ end
+end
diff --git a/spec/support/login.rb b/spec/support/login.rb
new file mode 100644
index 00000000..09f64f9e
--- /dev/null
+++ b/spec/support/login.rb
@@ -0,0 +1,29 @@
+module LoginMacros
+ def login_as role
+ @user = User.create(:email => "user#{User.count}@mail.com",
+ :name => "John Smith",
+ :password => "123456",
+ :password_confirmation => "123456")
+
+ if role == :admin
+ @user.admin = true
+ @user.save!
+ end
+
+ visit new_user_session_path
+ fill_in "Email", :with => @user.email
+ fill_in "Password", :with => "123456"
+ click_button "Sign in"
+ end
+
+ def login_with(user)
+ visit new_user_session_path
+ fill_in "Email", :with => user.email
+ fill_in "Password", :with => "123456"
+ click_button "Sign in"
+ end
+
+ def logout
+ click_link "Logout" rescue nil
+ end
+end
diff --git a/spec/support/matchers.rb b/spec/support/matchers.rb
new file mode 100644
index 00000000..953b5356
--- /dev/null
+++ b/spec/support/matchers.rb
@@ -0,0 +1,46 @@
+RSpec::Matchers.define :be_valid_commit do
+ match do |actual|
+ actual != nil
+ actual.id == ValidCommit::ID
+ actual.message == ValidCommit::MESSAGE
+ actual.author.name == ValidCommit::AUTHOR_FULL_NAME
+ end
+end
+
+RSpec::Matchers.define :be_allowed_for do |user|
+ match do |url|
+ include UrlAccess
+ url_allowed?(user, url)
+ end
+end
+
+RSpec::Matchers.define :be_denied_for do |user|
+ match do |url|
+ include UrlAccess
+ url_denied?(user, url)
+ end
+end
+
+module UrlAccess
+ def url_allowed?(user, url)
+ emulate_user(user)
+ visit url
+ result = (current_path == url)
+ end
+
+ def url_denied?(user, url)
+ emulate_user(user)
+ visit url
+ result = (current_path != url)
+ end
+
+ def emulate_user(user)
+ user = case user
+ when :user then Factory(:user)
+ when :visitor then nil
+ when :admin then Factory(:admin)
+ else user
+ end
+ login_with(user) if user
+ end
+end
diff --git a/spec/support/security.rb b/spec/support/security.rb
new file mode 100644
index 00000000..e69de29b
diff --git a/spec/support/shared_examples.rb b/spec/support/shared_examples.rb
new file mode 100644
index 00000000..a2e94ac1
--- /dev/null
+++ b/spec/support/shared_examples.rb
@@ -0,0 +1,18 @@
+shared_examples_for :project_side_pane do
+ subject { page }
+ it { should have_content((@project || project).name) }
+ it { should have_content("Commits") }
+ it { should have_content("Team") }
+ it { should have_content("Tree") }
+end
+
+
+shared_examples_for :tree_view do
+ subject { page }
+
+ it "should have Tree View of project" do
+ should have_content("app")
+ should have_content("history")
+ should have_content("Gemfile")
+ end
+end
diff --git a/spec/support/valid_commit.rb b/spec/support/valid_commit.rb
new file mode 100644
index 00000000..a4d3496e
--- /dev/null
+++ b/spec/support/valid_commit.rb
@@ -0,0 +1,25 @@
+module ValidCommit
+ ID = "eaffbe556ec3a8dc84ef15892a9f12d84dde7e1d"
+ MESSAGE = "style"
+ AUTHOR_FULL_NAME = "Dmitriy Zaporozhets"
+
+ FILES = [".gitignore", ".rspec", ".rvmrc", "Gemfile", "Gemfile.lock", "LICENSE", "README.rdoc", "Rakefile", "app", "config.ru", "config", "db", "doc", "lib", "log", "public", "script", "spec", "vendor"]
+ FILES_COUNT = 19
+
+ C_FILE_PATH = "app/models"
+ C_FILES = [".gitkeep", "project.rb", "user.rb"]
+
+ BLOB_FILE = <<-blob
+
+
Tree / <%= link_to "Commits", project_commits_path(@project) %>
+ <%= render :partial => "tree", :locals => {:repo => @repo, :commit => @commit, :tree => @commit.tree} %>
+
+
+
+ <%= render "side_panel" %>
+
+blob
+
+ BLOB_FILE_PATH = "app/views/projects/show.html.erb"
+end
+
diff --git a/vendor/assets/stylesheets/.gitkeep b/vendor/assets/stylesheets/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/vendor/assets/stylesheets/blueprint/ie.css b/vendor/assets/stylesheets/blueprint/ie.css
new file mode 100644
index 00000000..f0153995
--- /dev/null
+++ b/vendor/assets/stylesheets/blueprint/ie.css
@@ -0,0 +1,36 @@
+/* -----------------------------------------------------------------------
+
+
+ Blueprint CSS Framework 1.0.1
+ http://blueprintcss.org
+
+ * Copyright (c) 2007-Present. See LICENSE for more info.
+ * See README for instructions on how to use Blueprint.
+ * For credits and origins, see AUTHORS.
+ * This is a compressed file. See the sources in the 'src' directory.
+
+----------------------------------------------------------------------- */
+
+/* ie.css */
+body {text-align:center;}
+.container {text-align:left;}
+* html .column, * html .span-1, * html .span-2, * html .span-3, * html .span-4, * html .span-5, * html .span-6, * html .span-7, * html .span-8, * html .span-9, * html .span-10, * html .span-11, * html .span-12, * html .span-13, * html .span-14, * html .span-15, * html .span-16, * html .span-17, * html .span-18, * html .span-19, * html .span-20, * html .span-21, * html .span-22, * html .span-23, * html .span-24 {display:inline;overflow-x:hidden;}
+* html legend {margin:0px -8px 16px 0;padding:0;}
+sup {vertical-align:text-top;}
+sub {vertical-align:text-bottom;}
+html>body p code {*white-space:normal;}
+hr {margin:-8px auto 11px;}
+img {-ms-interpolation-mode:bicubic;}
+.clearfix, .container {display:inline-block;}
+* html .clearfix, * html .container {height:1%;}
+fieldset {padding-top:0;}
+legend {margin-top:-0.2em;margin-bottom:1em;margin-left:-0.5em;}
+textarea {overflow:auto;}
+label {vertical-align:middle;position:relative;top:-0.25em;}
+input.text, input.title, textarea {background-color:#fff;border:1px solid #bbb;}
+input.text:focus, input.title:focus {border-color:#666;}
+input.text, input.title, textarea, select {margin:0.5em 0;}
+input.checkbox, input.radio {position:relative;top:.25em;}
+form.inline div, form.inline p {vertical-align:middle;}
+form.inline input.checkbox, form.inline input.radio, form.inline input.button, form.inline button {margin:0.5em 0;}
+button, input.button {position:relative;top:0.25em;}
\ No newline at end of file
diff --git a/vendor/assets/stylesheets/blueprint/plugins/buttons/icons/cross.png b/vendor/assets/stylesheets/blueprint/plugins/buttons/icons/cross.png
new file mode 100755
index 00000000..1514d51a
Binary files /dev/null and b/vendor/assets/stylesheets/blueprint/plugins/buttons/icons/cross.png differ
diff --git a/vendor/assets/stylesheets/blueprint/plugins/buttons/icons/key.png b/vendor/assets/stylesheets/blueprint/plugins/buttons/icons/key.png
new file mode 100755
index 00000000..a9d5e4f8
Binary files /dev/null and b/vendor/assets/stylesheets/blueprint/plugins/buttons/icons/key.png differ
diff --git a/vendor/assets/stylesheets/blueprint/plugins/buttons/icons/tick.png b/vendor/assets/stylesheets/blueprint/plugins/buttons/icons/tick.png
new file mode 100755
index 00000000..a9925a06
Binary files /dev/null and b/vendor/assets/stylesheets/blueprint/plugins/buttons/icons/tick.png differ
diff --git a/vendor/assets/stylesheets/blueprint/plugins/buttons/readme.txt b/vendor/assets/stylesheets/blueprint/plugins/buttons/readme.txt
new file mode 100644
index 00000000..aa9fe26b
--- /dev/null
+++ b/vendor/assets/stylesheets/blueprint/plugins/buttons/readme.txt
@@ -0,0 +1,32 @@
+Buttons
+
+* Gives you great looking CSS buttons, for both and .
+* Demo: particletree.com/features/rediscovering-the-button-element
+
+
+Credits
+----------------------------------------------------------------
+
+* Created by Kevin Hale [particletree.com]
+* Adapted for Blueprint by Olav Bjorkoy [bjorkoy.com]
+
+
+Usage
+----------------------------------------------------------------
+
+1) Add this plugin to lib/settings.yml.
+ See compress.rb for instructions.
+
+2) Use the following HTML code to place the buttons on your site:
+
+
+ Save
+
+
+
+ Change Password
+
+
+
+ Cancel
+
diff --git a/vendor/assets/stylesheets/blueprint/plugins/buttons/screen.css b/vendor/assets/stylesheets/blueprint/plugins/buttons/screen.css
new file mode 100644
index 00000000..bb66b215
--- /dev/null
+++ b/vendor/assets/stylesheets/blueprint/plugins/buttons/screen.css
@@ -0,0 +1,97 @@
+/* --------------------------------------------------------------
+
+ buttons.css
+ * Gives you some great CSS-only buttons.
+
+ Created by Kevin Hale [particletree.com]
+ * particletree.com/features/rediscovering-the-button-element
+
+ See Readme.txt in this folder for instructions.
+
+-------------------------------------------------------------- */
+
+a.button, button {
+ display:block;
+ float:left;
+ margin: 0.7em 0.5em 0.7em 0;
+ padding:5px 10px 5px 7px; /* Links */
+
+ border:1px solid #dedede;
+ border-top:1px solid #eee;
+ border-left:1px solid #eee;
+
+ background-color:#f5f5f5;
+ font-family:"Lucida Grande", Tahoma, Arial, Verdana, sans-serif;
+ font-size:100%;
+ line-height:130%;
+ text-decoration:none;
+ font-weight:bold;
+ color:#565656;
+ cursor:pointer;
+}
+button {
+ width:auto;
+ overflow:visible;
+ padding:4px 10px 3px 7px; /* IE6 */
+}
+button[type] {
+ padding:4px 10px 4px 7px; /* Firefox */
+ line-height:17px; /* Safari */
+}
+*:first-child+html button[type] {
+ padding:4px 10px 3px 7px; /* IE7 */
+}
+button img, a.button img{
+ margin:0 3px -3px 0 !important;
+ padding:0;
+ border:none;
+ width:16px;
+ height:16px;
+ float:none;
+}
+
+
+/* Button colors
+-------------------------------------------------------------- */
+
+/* Standard */
+button:hover, a.button:hover{
+ background-color:#dff4ff;
+ border:1px solid #c2e1ef;
+ color:#336699;
+}
+a.button:active{
+ background-color:#6299c5;
+ border:1px solid #6299c5;
+ color:#fff;
+}
+
+/* Positive */
+body .positive {
+ color:#529214;
+}
+a.positive:hover, button.positive:hover {
+ background-color:#E6EFC2;
+ border:1px solid #C6D880;
+ color:#529214;
+}
+a.positive:active {
+ background-color:#529214;
+ border:1px solid #529214;
+ color:#fff;
+}
+
+/* Negative */
+body .negative {
+ color:#d12f19;
+}
+a.negative:hover, button.negative:hover {
+ background-color:#fbe3e4;
+ border:1px solid #fbc2c4;
+ color:#d12f19;
+}
+a.negative:active {
+ background-color:#d12f19;
+ border:1px solid #d12f19;
+ color:#fff;
+}
diff --git a/vendor/assets/stylesheets/blueprint/plugins/fancy-type/readme.txt b/vendor/assets/stylesheets/blueprint/plugins/fancy-type/readme.txt
new file mode 100644
index 00000000..85f24915
--- /dev/null
+++ b/vendor/assets/stylesheets/blueprint/plugins/fancy-type/readme.txt
@@ -0,0 +1,14 @@
+Fancy Type
+
+* Gives you classes to use if you'd like some
+ extra fancy typography.
+
+Credits and instructions are specified above each class
+in the fancy-type.css file in this directory.
+
+
+Usage
+----------------------------------------------------------------
+
+1) Add this plugin to lib/settings.yml.
+ See compress.rb for instructions.
diff --git a/vendor/assets/stylesheets/blueprint/plugins/fancy-type/screen.css b/vendor/assets/stylesheets/blueprint/plugins/fancy-type/screen.css
new file mode 100644
index 00000000..127cf255
--- /dev/null
+++ b/vendor/assets/stylesheets/blueprint/plugins/fancy-type/screen.css
@@ -0,0 +1,71 @@
+/* --------------------------------------------------------------
+
+ fancy-type.css
+ * Lots of pretty advanced classes for manipulating text.
+
+ See the Readme file in this folder for additional instructions.
+
+-------------------------------------------------------------- */
+
+/* Indentation instead of line shifts for sibling paragraphs. */
+ p + p { text-indent:2em; margin-top:-1.5em; }
+ form p + p { text-indent: 0; } /* Don't want this in forms. */
+
+
+/* For great looking type, use this code instead of asdf:
+ asdf
+ Best used on prepositions and ampersands. */
+
+.alt {
+ color: #666;
+ font-family: "Warnock Pro", "Goudy Old Style","Palatino","Book Antiqua", Georgia, serif;
+ font-style: italic;
+ font-weight: normal;
+}
+
+
+/* For great looking quote marks in titles, replace "asdf" with:
+ “ asdf”
+ (That is, when the title starts with a quote mark).
+ (You may have to change this value depending on your font size). */
+
+.dquo { margin-left: -.5em; }
+
+
+/* Reduced size type with incremental leading
+ (http://www.markboulton.co.uk/journal/comments/incremental_leading/)
+
+ This could be used for side notes. For smaller type, you don't necessarily want to
+ follow the 1.5x vertical rhythm -- the line-height is too much.
+
+ Using this class, it reduces your font size and line-height so that for
+ every four lines of normal sized type, there is five lines of the sidenote. eg:
+
+ New type size in em's:
+ 10px (wanted side note size) / 12px (existing base size) = 0.8333 (new type size in ems)
+
+ New line-height value:
+ 12px x 1.5 = 18px (old line-height)
+ 18px x 4 = 72px
+ 72px / 5 = 14.4px (new line height)
+ 14.4px / 10px = 1.44 (new line height in em's) */
+
+p.incr, .incr p {
+ font-size: 10px;
+ line-height: 1.44em;
+ margin-bottom: 1.5em;
+}
+
+
+/* Surround uppercase words and abbreviations with this class.
+ Based on work by Jørgen Arnor Gårdsø Lom [http://twistedintellect.com/] */
+
+.caps {
+ font-variant: small-caps;
+ letter-spacing: 1px;
+ text-transform: lowercase;
+ font-size:1.2em;
+ line-height:1%;
+ font-weight:bold;
+ padding:0 2px;
+}
diff --git a/vendor/assets/stylesheets/blueprint/plugins/link-icons/icons/doc.png b/vendor/assets/stylesheets/blueprint/plugins/link-icons/icons/doc.png
new file mode 100644
index 00000000..834cdfaf
Binary files /dev/null and b/vendor/assets/stylesheets/blueprint/plugins/link-icons/icons/doc.png differ
diff --git a/vendor/assets/stylesheets/blueprint/plugins/link-icons/icons/email.png b/vendor/assets/stylesheets/blueprint/plugins/link-icons/icons/email.png
new file mode 100644
index 00000000..7348aed7
Binary files /dev/null and b/vendor/assets/stylesheets/blueprint/plugins/link-icons/icons/email.png differ
diff --git a/vendor/assets/stylesheets/blueprint/plugins/link-icons/icons/external.png b/vendor/assets/stylesheets/blueprint/plugins/link-icons/icons/external.png
new file mode 100644
index 00000000..cf1cfb42
Binary files /dev/null and b/vendor/assets/stylesheets/blueprint/plugins/link-icons/icons/external.png differ
diff --git a/vendor/assets/stylesheets/blueprint/plugins/link-icons/icons/feed.png b/vendor/assets/stylesheets/blueprint/plugins/link-icons/icons/feed.png
new file mode 100644
index 00000000..315c4f4f
Binary files /dev/null and b/vendor/assets/stylesheets/blueprint/plugins/link-icons/icons/feed.png differ
diff --git a/vendor/assets/stylesheets/blueprint/plugins/link-icons/icons/im.png b/vendor/assets/stylesheets/blueprint/plugins/link-icons/icons/im.png
new file mode 100644
index 00000000..79f35ccb
Binary files /dev/null and b/vendor/assets/stylesheets/blueprint/plugins/link-icons/icons/im.png differ
diff --git a/vendor/assets/stylesheets/blueprint/plugins/link-icons/icons/lock.png b/vendor/assets/stylesheets/blueprint/plugins/link-icons/icons/lock.png
new file mode 100644
index 00000000..2ebc4f6f
Binary files /dev/null and b/vendor/assets/stylesheets/blueprint/plugins/link-icons/icons/lock.png differ
diff --git a/vendor/assets/stylesheets/blueprint/plugins/link-icons/icons/pdf.png b/vendor/assets/stylesheets/blueprint/plugins/link-icons/icons/pdf.png
new file mode 100644
index 00000000..8f8095e4
Binary files /dev/null and b/vendor/assets/stylesheets/blueprint/plugins/link-icons/icons/pdf.png differ
diff --git a/vendor/assets/stylesheets/blueprint/plugins/link-icons/icons/visited.png b/vendor/assets/stylesheets/blueprint/plugins/link-icons/icons/visited.png
new file mode 100644
index 00000000..ebf206de
Binary files /dev/null and b/vendor/assets/stylesheets/blueprint/plugins/link-icons/icons/visited.png differ
diff --git a/vendor/assets/stylesheets/blueprint/plugins/link-icons/icons/xls.png b/vendor/assets/stylesheets/blueprint/plugins/link-icons/icons/xls.png
new file mode 100644
index 00000000..b977d7e5
Binary files /dev/null and b/vendor/assets/stylesheets/blueprint/plugins/link-icons/icons/xls.png differ
diff --git a/vendor/assets/stylesheets/blueprint/plugins/link-icons/readme.txt b/vendor/assets/stylesheets/blueprint/plugins/link-icons/readme.txt
new file mode 100644
index 00000000..fc4dc649
--- /dev/null
+++ b/vendor/assets/stylesheets/blueprint/plugins/link-icons/readme.txt
@@ -0,0 +1,18 @@
+Link Icons
+* Icons for links based on protocol or file type.
+
+This is not supported in IE versions < 7.
+
+
+Credits
+----------------------------------------------------------------
+
+* Marc Morgan
+* Olav Bjorkoy [bjorkoy.com]
+
+
+Usage
+----------------------------------------------------------------
+
+1) Add this line to your HTML:
+
diff --git a/vendor/assets/stylesheets/blueprint/plugins/link-icons/screen.css b/vendor/assets/stylesheets/blueprint/plugins/link-icons/screen.css
new file mode 100644
index 00000000..0cefc774
--- /dev/null
+++ b/vendor/assets/stylesheets/blueprint/plugins/link-icons/screen.css
@@ -0,0 +1,42 @@
+/* --------------------------------------------------------------
+
+ link-icons.css
+ * Icons for links based on protocol or file type.
+
+ See the Readme file in this folder for additional instructions.
+
+-------------------------------------------------------------- */
+
+/* Use this class if a link gets an icon when it shouldn't. */
+body a.noicon {
+ background:transparent none !important;
+ padding:0 !important;
+ margin:0 !important;
+}
+
+/* Make sure the icons are not cut */
+a[href^="http:"], a[href^="https:"],
+a[href^="http:"]:visited, a[href^="https:"]:visited,
+a[href^="mailto:"], a[href$=".pdf"], a[href$=".doc"], a[href$=".xls"],
+a[href$=".rss"], a[href$=".rdf"], a[href^="aim:"] {
+ padding:2px 22px 2px 0;
+ margin:-2px 0;
+ background-repeat: no-repeat;
+ background-position: right center;
+}
+
+/* External links */
+a[href^="http:"] { background-image: url(icons/external.png); }
+a[href^="https:"] { background-image: url(icons/lock.png); }
+a[href^="mailto:"] { background-image: url(icons/email.png); }
+a[href^="http:"]:visited { background-image: url(icons/visited.png); }
+
+/* Files */
+a[href$=".pdf"] { background-image: url(icons/pdf.png); }
+a[href$=".doc"] { background-image: url(icons/doc.png); }
+a[href$=".xls"] { background-image: url(icons/xls.png); }
+
+/* Misc */
+a[href$=".rss"],
+a[href$=".rdf"] { background-image: url(icons/feed.png); }
+a[href^="aim:"] { background-image: url(icons/im.png); }
diff --git a/vendor/assets/stylesheets/blueprint/plugins/rtl/readme.txt b/vendor/assets/stylesheets/blueprint/plugins/rtl/readme.txt
new file mode 100644
index 00000000..5564c402
--- /dev/null
+++ b/vendor/assets/stylesheets/blueprint/plugins/rtl/readme.txt
@@ -0,0 +1,10 @@
+RTL
+* Mirrors Blueprint, so it can be used with Right-to-Left languages.
+
+By Ran Yaniv Hartstein, ranh.co.il
+
+Usage
+----------------------------------------------------------------
+
+1) Add this line to your HTML:
+
diff --git a/vendor/assets/stylesheets/blueprint/plugins/rtl/screen.css b/vendor/assets/stylesheets/blueprint/plugins/rtl/screen.css
new file mode 100644
index 00000000..7db7eb5e
--- /dev/null
+++ b/vendor/assets/stylesheets/blueprint/plugins/rtl/screen.css
@@ -0,0 +1,110 @@
+/* --------------------------------------------------------------
+
+ rtl.css
+ * Mirrors Blueprint for left-to-right languages
+
+ By Ran Yaniv Hartstein [ranh.co.il]
+
+-------------------------------------------------------------- */
+
+body .container { direction: rtl; }
+body .column, body .span-1, body .span-2, body .span-3, body .span-4, body .span-5, body .span-6, body .span-7, body .span-8, body .span-9, body .span-10, body .span-11, body .span-12, body .span-13, body .span-14, body .span-15, body .span-16, body .span-17, body .span-18, body .span-19, body .span-20, body .span-21, body .span-22, body .span-23, body .span-24 {
+ float: right;
+ margin-right: 0;
+ margin-left: 10px;
+ text-align:right;
+}
+
+body div.last { margin-left: 0; }
+body table .last { padding-left: 0; }
+
+body .append-1 { padding-right: 0; padding-left: 40px; }
+body .append-2 { padding-right: 0; padding-left: 80px; }
+body .append-3 { padding-right: 0; padding-left: 120px; }
+body .append-4 { padding-right: 0; padding-left: 160px; }
+body .append-5 { padding-right: 0; padding-left: 200px; }
+body .append-6 { padding-right: 0; padding-left: 240px; }
+body .append-7 { padding-right: 0; padding-left: 280px; }
+body .append-8 { padding-right: 0; padding-left: 320px; }
+body .append-9 { padding-right: 0; padding-left: 360px; }
+body .append-10 { padding-right: 0; padding-left: 400px; }
+body .append-11 { padding-right: 0; padding-left: 440px; }
+body .append-12 { padding-right: 0; padding-left: 480px; }
+body .append-13 { padding-right: 0; padding-left: 520px; }
+body .append-14 { padding-right: 0; padding-left: 560px; }
+body .append-15 { padding-right: 0; padding-left: 600px; }
+body .append-16 { padding-right: 0; padding-left: 640px; }
+body .append-17 { padding-right: 0; padding-left: 680px; }
+body .append-18 { padding-right: 0; padding-left: 720px; }
+body .append-19 { padding-right: 0; padding-left: 760px; }
+body .append-20 { padding-right: 0; padding-left: 800px; }
+body .append-21 { padding-right: 0; padding-left: 840px; }
+body .append-22 { padding-right: 0; padding-left: 880px; }
+body .append-23 { padding-right: 0; padding-left: 920px; }
+
+body .prepend-1 { padding-left: 0; padding-right: 40px; }
+body .prepend-2 { padding-left: 0; padding-right: 80px; }
+body .prepend-3 { padding-left: 0; padding-right: 120px; }
+body .prepend-4 { padding-left: 0; padding-right: 160px; }
+body .prepend-5 { padding-left: 0; padding-right: 200px; }
+body .prepend-6 { padding-left: 0; padding-right: 240px; }
+body .prepend-7 { padding-left: 0; padding-right: 280px; }
+body .prepend-8 { padding-left: 0; padding-right: 320px; }
+body .prepend-9 { padding-left: 0; padding-right: 360px; }
+body .prepend-10 { padding-left: 0; padding-right: 400px; }
+body .prepend-11 { padding-left: 0; padding-right: 440px; }
+body .prepend-12 { padding-left: 0; padding-right: 480px; }
+body .prepend-13 { padding-left: 0; padding-right: 520px; }
+body .prepend-14 { padding-left: 0; padding-right: 560px; }
+body .prepend-15 { padding-left: 0; padding-right: 600px; }
+body .prepend-16 { padding-left: 0; padding-right: 640px; }
+body .prepend-17 { padding-left: 0; padding-right: 680px; }
+body .prepend-18 { padding-left: 0; padding-right: 720px; }
+body .prepend-19 { padding-left: 0; padding-right: 760px; }
+body .prepend-20 { padding-left: 0; padding-right: 800px; }
+body .prepend-21 { padding-left: 0; padding-right: 840px; }
+body .prepend-22 { padding-left: 0; padding-right: 880px; }
+body .prepend-23 { padding-left: 0; padding-right: 920px; }
+
+body .border {
+ padding-right: 0;
+ padding-left: 4px;
+ margin-right: 0;
+ margin-left: 5px;
+ border-right: none;
+ border-left: 1px solid #eee;
+}
+
+body .colborder {
+ padding-right: 0;
+ padding-left: 24px;
+ margin-right: 0;
+ margin-left: 25px;
+ border-right: none;
+ border-left: 1px solid #eee;
+}
+
+body .pull-1 { margin-left: 0; margin-right: -40px; }
+body .pull-2 { margin-left: 0; margin-right: -80px; }
+body .pull-3 { margin-left: 0; margin-right: -120px; }
+body .pull-4 { margin-left: 0; margin-right: -160px; }
+
+body .push-0 { margin: 0 18px 0 0; }
+body .push-1 { margin: 0 18px 0 -40px; }
+body .push-2 { margin: 0 18px 0 -80px; }
+body .push-3 { margin: 0 18px 0 -120px; }
+body .push-4 { margin: 0 18px 0 -160px; }
+body .push-0, body .push-1, body .push-2,
+body .push-3, body .push-4 { float: left; }
+
+
+/* Typography with RTL support */
+body h1,body h2,body h3,
+body h4,body h5,body h6 { font-family: Arial, sans-serif; }
+html body { font-family: Arial, sans-serif; }
+body pre,body code,body tt { font-family: monospace; }
+
+/* Mirror floats and margins on typographic elements */
+body p img { float: right; margin: 1.5em 0 1.5em 1.5em; }
+body dd, body ul, body ol { margin-left: 0; margin-right: 1.5em;}
+body td, body th { text-align:right; }
diff --git a/vendor/assets/stylesheets/blueprint/print.css b/vendor/assets/stylesheets/blueprint/print.css
new file mode 100644
index 00000000..bd79afde
--- /dev/null
+++ b/vendor/assets/stylesheets/blueprint/print.css
@@ -0,0 +1,29 @@
+/* -----------------------------------------------------------------------
+
+
+ Blueprint CSS Framework 1.0.1
+ http://blueprintcss.org
+
+ * Copyright (c) 2007-Present. See LICENSE for more info.
+ * See README for instructions on how to use Blueprint.
+ * For credits and origins, see AUTHORS.
+ * This is a compressed file. See the sources in the 'src' directory.
+
+----------------------------------------------------------------------- */
+
+/* print.css */
+body {line-height:1.5;font-family:"Helvetica Neue", Arial, Helvetica, sans-serif;color:#000;background:none;font-size:10pt;}
+.container {background:none;}
+hr {background:#ccc;color:#ccc;width:100%;height:2px;margin:2em 0;padding:0;border:none;}
+hr.space {background:#fff;color:#fff;visibility:hidden;}
+h1, h2, h3, h4, h5, h6 {font-family:"Helvetica Neue", Arial, "Lucida Grande", sans-serif;}
+code {font:.9em "Courier New", Monaco, Courier, monospace;}
+a img {border:none;}
+p img.top {margin-top:0;}
+blockquote {margin:1.5em;padding:1em;font-style:italic;font-size:.9em;}
+.small {font-size:.9em;}
+.large {font-size:1.1em;}
+.quiet {color:#999;}
+.hide {display:none;}
+a:link, a:visited {background:transparent;font-weight:700;text-decoration:underline;}
+a:link:after, a:visited:after {content:" (" attr(href) ")";font-size:90%;}
\ No newline at end of file
diff --git a/vendor/assets/stylesheets/blueprint/screen.css b/vendor/assets/stylesheets/blueprint/screen.css
new file mode 100644
index 00000000..fe68de6e
--- /dev/null
+++ b/vendor/assets/stylesheets/blueprint/screen.css
@@ -0,0 +1,265 @@
+/* -----------------------------------------------------------------------
+
+
+ Blueprint CSS Framework 1.0.1
+ http://blueprintcss.org
+
+ * Copyright (c) 2007-Present. See LICENSE for more info.
+ * See README for instructions on how to use Blueprint.
+ * For credits and origins, see AUTHORS.
+ * This is a compressed file. See the sources in the 'src' directory.
+
+----------------------------------------------------------------------- */
+
+/* reset.css */
+html {margin:0;padding:0;border:0;}
+body, div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, code, del, dfn, em, img, q, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, dialog, figure, footer, header, hgroup, nav, section {margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline;}
+article, aside, details, figcaption, figure, dialog, footer, header, hgroup, menu, nav, section {display:block;}
+body {line-height:1.5;background:white;}
+table {border-collapse:separate;border-spacing:0;}
+caption, th, td {text-align:left;font-weight:normal;float:none !important;}
+table, th, td {vertical-align:middle;}
+blockquote:before, blockquote:after, q:before, q:after {content:'';}
+blockquote, q {quotes:"" "";}
+a img {border:none;}
+:focus {outline:0;}
+
+/* typography.css */
+html {font-size:100.01%;}
+body {font-size:75%;color:#222;background:#fff;font-family:"Helvetica Neue", Arial, Helvetica, sans-serif;}
+h1, h2, h3, h4, h5, h6 {font-weight:normal;color:#111;}
+h1 {font-size:3em;line-height:1;margin-bottom:0.5em;}
+h2 {font-size:2em;margin-bottom:0.75em;}
+h3 {font-size:1.5em;line-height:1;margin-bottom:1em;}
+h4 {font-size:1.2em;line-height:1.25;margin-bottom:1.25em;}
+h5 {font-size:1em;font-weight:bold;margin-bottom:1.5em;}
+h6 {font-size:1em;font-weight:bold;}
+h1 img, h2 img, h3 img, h4 img, h5 img, h6 img {margin:0;}
+p {margin:0 0 1.5em;}
+.left {float:left !important;}
+p .left {margin:1.5em 1.5em 1.5em 0;padding:0;}
+.right {float:right !important;}
+p .right {margin:1.5em 0 1.5em 1.5em;padding:0;}
+a:focus, a:hover {color:#09f;}
+a {color:#06c;text-decoration:underline;}
+blockquote {margin:1.5em;color:#666;font-style:italic;}
+strong, dfn {font-weight:bold;}
+em, dfn {font-style:italic;}
+sup, sub {line-height:0;}
+abbr, acronym {border-bottom:1px dotted #666;}
+address {margin:0 0 1.5em;font-style:italic;}
+del {color:#666;}
+pre {margin:1.5em 0;white-space:pre;}
+pre, code, tt {font:1em 'andale mono', 'lucida console', monospace;line-height:1.5;}
+li ul, li ol {margin:0;}
+ul, ol {margin:0 1.5em 1.5em 0;padding-left:1.5em;}
+ul {list-style-type:disc;}
+ol {list-style-type:decimal;}
+dl {margin:0 0 1.5em 0;}
+dl dt {font-weight:bold;}
+dd {margin-left:1.5em;}
+table {margin-bottom:1.4em;width:100%;}
+th {font-weight:bold;}
+thead th {background:#c3d9ff;}
+th, td, caption {padding:4px 10px 4px 5px;}
+tbody tr:nth-child(even) td, tbody tr.even td {background:#e5ecf9;}
+tfoot {font-style:italic;}
+caption {background:#eee;}
+.small {font-size:.8em;margin-bottom:1.875em;line-height:1.875em;}
+.large {font-size:1.2em;line-height:2.5em;margin-bottom:1.25em;}
+.hide {display:none;}
+.quiet {color:#666;}
+.loud {color:#000;}
+.highlight {background:#ff0;}
+.added {background:#060;color:#fff;}
+.removed {background:#900;color:#fff;}
+.first {margin-left:0;padding-left:0;}
+.last {margin-right:0;padding-right:0;}
+.top {margin-top:0;padding-top:0;}
+.bottom {margin-bottom:0;padding-bottom:0;}
+
+/* forms.css */
+label {font-weight:bold;}
+fieldset {padding:0 1.4em 1.4em 1.4em;margin:0 0 1.5em 0;border:1px solid #ccc;}
+legend {font-weight:bold;font-size:1.2em;margin-top:-0.2em;margin-bottom:1em;}
+fieldset, #IE8#HACK {padding-top:1.4em;}
+legend, #IE8#HACK {margin-top:0;margin-bottom:0;}
+input[type=text], input[type=password], input[type=url], input[type=email], input.text, input.title, textarea {background-color:#fff;border:1px solid #bbb;color:#000;}
+input[type=text]:focus, input[type=password]:focus, input[type=url]:focus, input[type=email]:focus, input.text:focus, input.title:focus, textarea:focus {border-color:#666;}
+select {background-color:#fff;border-width:1px;border-style:solid;}
+input[type=text], input[type=password], input[type=url], input[type=email], input.text, input.title, textarea, select {margin:0.5em 0;}
+input.text, input.title {width:300px;padding:5px;}
+input.title {font-size:1.5em;}
+textarea {width:390px;height:250px;padding:5px;}
+form.inline {line-height:3;}
+form.inline p {margin-bottom:0;}
+.error, .alert, .notice, .success, .info {padding:0.8em;margin-bottom:1em;border:2px solid #ddd;}
+.error, .alert {background:#fbe3e4;color:#8a1f11;border-color:#fbc2c4;}
+.notice {background:#fff6bf;color:#514721;border-color:#ffd324;}
+.success {background:#e6efc2;color:#264409;border-color:#c6d880;}
+.info {background:#d5edf8;color:#205791;border-color:#92cae4;}
+.error a, .alert a {color:#8a1f11;}
+.notice a {color:#514721;}
+.success a {color:#264409;}
+.info a {color:#205791;}
+
+/* grid.css */
+.container {width:950px;margin:0 auto;}
+.showgrid {background:url(src/grid.png);}
+.column, .span-1, .span-2, .span-3, .span-4, .span-5, .span-6, .span-7, .span-8, .span-9, .span-10, .span-11, .span-12, .span-13, .span-14, .span-15, .span-16, .span-17, .span-18, .span-19, .span-20, .span-21, .span-22, .span-23, .span-24 {float:left;margin-right:10px;}
+.last {margin-right:0;}
+.span-1 {width:30px;}
+.span-2 {width:70px;}
+.span-3 {width:110px;}
+.span-4 {width:150px;}
+.span-5 {width:190px;}
+.span-6 {width:230px;}
+.span-7 {width:270px;}
+.span-8 {width:310px;}
+.span-9 {width:350px;}
+.span-10 {width:390px;}
+.span-11 {width:430px;}
+.span-12 {width:470px;}
+.span-13 {width:510px;}
+.span-14 {width:550px;}
+.span-15 {width:590px;}
+.span-16 {width:630px;}
+.span-17 {width:670px;}
+.span-18 {width:710px;}
+.span-19 {width:750px;}
+.span-20 {width:790px;}
+.span-21 {width:830px;}
+.span-22 {width:870px;}
+.span-23 {width:910px;}
+.span-24 {width:950px;margin-right:0;}
+input.span-1, textarea.span-1, input.span-2, textarea.span-2, input.span-3, textarea.span-3, input.span-4, textarea.span-4, input.span-5, textarea.span-5, input.span-6, textarea.span-6, input.span-7, textarea.span-7, input.span-8, textarea.span-8, input.span-9, textarea.span-9, input.span-10, textarea.span-10, input.span-11, textarea.span-11, input.span-12, textarea.span-12, input.span-13, textarea.span-13, input.span-14, textarea.span-14, input.span-15, textarea.span-15, input.span-16, textarea.span-16, input.span-17, textarea.span-17, input.span-18, textarea.span-18, input.span-19, textarea.span-19, input.span-20, textarea.span-20, input.span-21, textarea.span-21, input.span-22, textarea.span-22, input.span-23, textarea.span-23, input.span-24, textarea.span-24 {border-left-width:1px;border-right-width:1px;padding-left:5px;padding-right:5px;}
+input.span-1, textarea.span-1 {width:18px;}
+input.span-2, textarea.span-2 {width:58px;}
+input.span-3, textarea.span-3 {width:98px;}
+input.span-4, textarea.span-4 {width:138px;}
+input.span-5, textarea.span-5 {width:178px;}
+input.span-6, textarea.span-6 {width:218px;}
+input.span-7, textarea.span-7 {width:258px;}
+input.span-8, textarea.span-8 {width:298px;}
+input.span-9, textarea.span-9 {width:338px;}
+input.span-10, textarea.span-10 {width:378px;}
+input.span-11, textarea.span-11 {width:418px;}
+input.span-12, textarea.span-12 {width:458px;}
+input.span-13, textarea.span-13 {width:498px;}
+input.span-14, textarea.span-14 {width:538px;}
+input.span-15, textarea.span-15 {width:578px;}
+input.span-16, textarea.span-16 {width:618px;}
+input.span-17, textarea.span-17 {width:658px;}
+input.span-18, textarea.span-18 {width:698px;}
+input.span-19, textarea.span-19 {width:738px;}
+input.span-20, textarea.span-20 {width:778px;}
+input.span-21, textarea.span-21 {width:818px;}
+input.span-22, textarea.span-22 {width:858px;}
+input.span-23, textarea.span-23 {width:898px;}
+input.span-24, textarea.span-24 {width:938px;}
+.append-1 {padding-right:40px;}
+.append-2 {padding-right:80px;}
+.append-3 {padding-right:120px;}
+.append-4 {padding-right:160px;}
+.append-5 {padding-right:200px;}
+.append-6 {padding-right:240px;}
+.append-7 {padding-right:280px;}
+.append-8 {padding-right:320px;}
+.append-9 {padding-right:360px;}
+.append-10 {padding-right:400px;}
+.append-11 {padding-right:440px;}
+.append-12 {padding-right:480px;}
+.append-13 {padding-right:520px;}
+.append-14 {padding-right:560px;}
+.append-15 {padding-right:600px;}
+.append-16 {padding-right:640px;}
+.append-17 {padding-right:680px;}
+.append-18 {padding-right:720px;}
+.append-19 {padding-right:760px;}
+.append-20 {padding-right:800px;}
+.append-21 {padding-right:840px;}
+.append-22 {padding-right:880px;}
+.append-23 {padding-right:920px;}
+.prepend-1 {padding-left:40px;}
+.prepend-2 {padding-left:80px;}
+.prepend-3 {padding-left:120px;}
+.prepend-4 {padding-left:160px;}
+.prepend-5 {padding-left:200px;}
+.prepend-6 {padding-left:240px;}
+.prepend-7 {padding-left:280px;}
+.prepend-8 {padding-left:320px;}
+.prepend-9 {padding-left:360px;}
+.prepend-10 {padding-left:400px;}
+.prepend-11 {padding-left:440px;}
+.prepend-12 {padding-left:480px;}
+.prepend-13 {padding-left:520px;}
+.prepend-14 {padding-left:560px;}
+.prepend-15 {padding-left:600px;}
+.prepend-16 {padding-left:640px;}
+.prepend-17 {padding-left:680px;}
+.prepend-18 {padding-left:720px;}
+.prepend-19 {padding-left:760px;}
+.prepend-20 {padding-left:800px;}
+.prepend-21 {padding-left:840px;}
+.prepend-22 {padding-left:880px;}
+.prepend-23 {padding-left:920px;}
+.border {padding-right:4px;margin-right:5px;border-right:1px solid #ddd;}
+.colborder {padding-right:24px;margin-right:25px;border-right:1px solid #ddd;}
+.pull-1 {margin-left:-40px;}
+.pull-2 {margin-left:-80px;}
+.pull-3 {margin-left:-120px;}
+.pull-4 {margin-left:-160px;}
+.pull-5 {margin-left:-200px;}
+.pull-6 {margin-left:-240px;}
+.pull-7 {margin-left:-280px;}
+.pull-8 {margin-left:-320px;}
+.pull-9 {margin-left:-360px;}
+.pull-10 {margin-left:-400px;}
+.pull-11 {margin-left:-440px;}
+.pull-12 {margin-left:-480px;}
+.pull-13 {margin-left:-520px;}
+.pull-14 {margin-left:-560px;}
+.pull-15 {margin-left:-600px;}
+.pull-16 {margin-left:-640px;}
+.pull-17 {margin-left:-680px;}
+.pull-18 {margin-left:-720px;}
+.pull-19 {margin-left:-760px;}
+.pull-20 {margin-left:-800px;}
+.pull-21 {margin-left:-840px;}
+.pull-22 {margin-left:-880px;}
+.pull-23 {margin-left:-920px;}
+.pull-24 {margin-left:-960px;}
+.pull-1, .pull-2, .pull-3, .pull-4, .pull-5, .pull-6, .pull-7, .pull-8, .pull-9, .pull-10, .pull-11, .pull-12, .pull-13, .pull-14, .pull-15, .pull-16, .pull-17, .pull-18, .pull-19, .pull-20, .pull-21, .pull-22, .pull-23, .pull-24 {float:left;position:relative;}
+.push-1 {margin:0 -40px 1.5em 40px;}
+.push-2 {margin:0 -80px 1.5em 80px;}
+.push-3 {margin:0 -120px 1.5em 120px;}
+.push-4 {margin:0 -160px 1.5em 160px;}
+.push-5 {margin:0 -200px 1.5em 200px;}
+.push-6 {margin:0 -240px 1.5em 240px;}
+.push-7 {margin:0 -280px 1.5em 280px;}
+.push-8 {margin:0 -320px 1.5em 320px;}
+.push-9 {margin:0 -360px 1.5em 360px;}
+.push-10 {margin:0 -400px 1.5em 400px;}
+.push-11 {margin:0 -440px 1.5em 440px;}
+.push-12 {margin:0 -480px 1.5em 480px;}
+.push-13 {margin:0 -520px 1.5em 520px;}
+.push-14 {margin:0 -560px 1.5em 560px;}
+.push-15 {margin:0 -600px 1.5em 600px;}
+.push-16 {margin:0 -640px 1.5em 640px;}
+.push-17 {margin:0 -680px 1.5em 680px;}
+.push-18 {margin:0 -720px 1.5em 720px;}
+.push-19 {margin:0 -760px 1.5em 760px;}
+.push-20 {margin:0 -800px 1.5em 800px;}
+.push-21 {margin:0 -840px 1.5em 840px;}
+.push-22 {margin:0 -880px 1.5em 880px;}
+.push-23 {margin:0 -920px 1.5em 920px;}
+.push-24 {margin:0 -960px 1.5em 960px;}
+.push-1, .push-2, .push-3, .push-4, .push-5, .push-6, .push-7, .push-8, .push-9, .push-10, .push-11, .push-12, .push-13, .push-14, .push-15, .push-16, .push-17, .push-18, .push-19, .push-20, .push-21, .push-22, .push-23, .push-24 {float:left;position:relative;}
+div.prepend-top, .prepend-top {margin-top:1.5em;}
+div.append-bottom, .append-bottom {margin-bottom:1.5em;}
+.box {padding:1.5em;margin-bottom:1.5em;background:#e5eCf9;}
+hr {background:#ddd;color:#ddd;clear:both;float:none;width:100%;height:1px;margin:0 0 17px;border:none;}
+hr.space {background:#fff;color:#fff;visibility:hidden;}
+.clearfix:after, .container:after {content:"\0020";display:block;height:0;clear:both;visibility:hidden;overflow:hidden;}
+.clearfix, .container {display:block;}
+.clear {clear:both;}
\ No newline at end of file
diff --git a/vendor/assets/stylesheets/blueprint/src/forms.css b/vendor/assets/stylesheets/blueprint/src/forms.css
new file mode 100644
index 00000000..7ceb966e
--- /dev/null
+++ b/vendor/assets/stylesheets/blueprint/src/forms.css
@@ -0,0 +1,82 @@
+/* --------------------------------------------------------------
+
+ forms.css
+ * Sets up some default styling for forms
+ * Gives you classes to enhance your forms
+
+ Usage:
+ * For text fields, use class .title or .text
+ * For inline forms, use .inline (even when using columns)
+
+-------------------------------------------------------------- */
+
+/*
+ A special hack is included for IE8 since it does not apply padding
+ correctly on fieldsets
+ */
+label { font-weight: bold; }
+fieldset { padding:0 1.4em 1.4em 1.4em; margin: 0 0 1.5em 0; border: 1px solid #ccc; }
+legend { font-weight: bold; font-size:1.2em; margin-top:-0.2em; margin-bottom:1em; }
+
+fieldset, #IE8#HACK { padding-top:1.4em; }
+legend, #IE8#HACK { margin-top:0; margin-bottom:0; }
+
+/* Form fields
+-------------------------------------------------------------- */
+
+/*
+ Attribute selectors are used to differentiate the different types
+ of input elements, but to support old browsers, you will have to
+ add classes for each one. ".title" simply creates a large text
+ field, this is purely for looks.
+ */
+input[type=text], input[type=password], input[type=url], input[type=email],
+input.text, input.title,
+textarea {
+ background-color:#fff;
+ border:1px solid #bbb;
+ color:#000;
+}
+input[type=text]:focus, input[type=password]:focus, input[type=url]:focus, input[type=email]:focus,
+input.text:focus, input.title:focus,
+textarea:focus {
+ border-color:#666;
+}
+select { background-color:#fff; border-width:1px; border-style:solid; }
+
+input[type=text], input[type=password], input[type=url], input[type=email],
+input.text, input.title,
+textarea, select {
+ margin:0.5em 0;
+}
+
+input.text,
+input.title { width: 300px; padding:5px; }
+input.title { font-size:1.5em; }
+textarea { width: 390px; height: 250px; padding:5px; }
+
+/*
+ This is to be used on forms where a variety of elements are
+ placed side-by-side. Use the p tag to denote a line.
+ */
+form.inline { line-height:3; }
+form.inline p { margin-bottom:0; }
+
+
+/* Success, info, notice and error/alert boxes
+-------------------------------------------------------------- */
+
+.error,
+.alert,
+.notice,
+.success,
+.info { padding: 0.8em; margin-bottom: 1em; border: 2px solid #ddd; }
+
+.error, .alert { background: #fbe3e4; color: #8a1f11; border-color: #fbc2c4; }
+.notice { background: #fff6bf; color: #514721; border-color: #ffd324; }
+.success { background: #e6efc2; color: #264409; border-color: #c6d880; }
+.info { background: #d5edf8; color: #205791; border-color: #92cae4; }
+.error a, .alert a { color: #8a1f11; }
+.notice a { color: #514721; }
+.success a { color: #264409; }
+.info a { color: #205791; }
diff --git a/vendor/assets/stylesheets/blueprint/src/grid.css b/vendor/assets/stylesheets/blueprint/src/grid.css
new file mode 100755
index 00000000..dbd57381
--- /dev/null
+++ b/vendor/assets/stylesheets/blueprint/src/grid.css
@@ -0,0 +1,280 @@
+/* --------------------------------------------------------------
+
+ grid.css
+ * Sets up an easy-to-use grid of 24 columns.
+
+ By default, the grid is 950px wide, with 24 columns
+ spanning 30px, and a 10px margin between columns.
+
+ If you need fewer or more columns, namespaces or semantic
+ element names, use the compressor script (lib/compress.rb)
+
+-------------------------------------------------------------- */
+
+/* A container should group all your columns. */
+.container {
+ width: 950px;
+ margin: 0 auto;
+}
+
+/* Use this class on any .span / container to see the grid. */
+.showgrid {
+ background: url(src/grid.png);
+}
+
+
+/* Columns
+-------------------------------------------------------------- */
+
+/* Sets up basic grid floating and margin. */
+.column, .span-1, .span-2, .span-3, .span-4, .span-5, .span-6, .span-7, .span-8, .span-9, .span-10, .span-11, .span-12, .span-13, .span-14, .span-15, .span-16, .span-17, .span-18, .span-19, .span-20, .span-21, .span-22, .span-23, .span-24 {
+ float: left;
+ margin-right: 10px;
+}
+
+/* The last column in a row needs this class. */
+.last { margin-right: 0; }
+
+/* Use these classes to set the width of a column. */
+.span-1 {width: 30px;}
+
+.span-2 {width: 70px;}
+.span-3 {width: 110px;}
+.span-4 {width: 150px;}
+.span-5 {width: 190px;}
+.span-6 {width: 230px;}
+.span-7 {width: 270px;}
+.span-8 {width: 310px;}
+.span-9 {width: 350px;}
+.span-10 {width: 390px;}
+.span-11 {width: 430px;}
+.span-12 {width: 470px;}
+.span-13 {width: 510px;}
+.span-14 {width: 550px;}
+.span-15 {width: 590px;}
+.span-16 {width: 630px;}
+.span-17 {width: 670px;}
+.span-18 {width: 710px;}
+.span-19 {width: 750px;}
+.span-20 {width: 790px;}
+.span-21 {width: 830px;}
+.span-22 {width: 870px;}
+.span-23 {width: 910px;}
+.span-24 {width:950px; margin-right:0;}
+
+/* Use these classes to set the width of an input. */
+input.span-1, textarea.span-1, input.span-2, textarea.span-2, input.span-3, textarea.span-3, input.span-4, textarea.span-4, input.span-5, textarea.span-5, input.span-6, textarea.span-6, input.span-7, textarea.span-7, input.span-8, textarea.span-8, input.span-9, textarea.span-9, input.span-10, textarea.span-10, input.span-11, textarea.span-11, input.span-12, textarea.span-12, input.span-13, textarea.span-13, input.span-14, textarea.span-14, input.span-15, textarea.span-15, input.span-16, textarea.span-16, input.span-17, textarea.span-17, input.span-18, textarea.span-18, input.span-19, textarea.span-19, input.span-20, textarea.span-20, input.span-21, textarea.span-21, input.span-22, textarea.span-22, input.span-23, textarea.span-23, input.span-24, textarea.span-24 {
+ border-left-width: 1px;
+ border-right-width: 1px;
+ padding-left: 5px;
+ padding-right: 5px;
+}
+
+input.span-1, textarea.span-1 { width: 18px; }
+input.span-2, textarea.span-2 { width: 58px; }
+input.span-3, textarea.span-3 { width: 98px; }
+input.span-4, textarea.span-4 { width: 138px; }
+input.span-5, textarea.span-5 { width: 178px; }
+input.span-6, textarea.span-6 { width: 218px; }
+input.span-7, textarea.span-7 { width: 258px; }
+input.span-8, textarea.span-8 { width: 298px; }
+input.span-9, textarea.span-9 { width: 338px; }
+input.span-10, textarea.span-10 { width: 378px; }
+input.span-11, textarea.span-11 { width: 418px; }
+input.span-12, textarea.span-12 { width: 458px; }
+input.span-13, textarea.span-13 { width: 498px; }
+input.span-14, textarea.span-14 { width: 538px; }
+input.span-15, textarea.span-15 { width: 578px; }
+input.span-16, textarea.span-16 { width: 618px; }
+input.span-17, textarea.span-17 { width: 658px; }
+input.span-18, textarea.span-18 { width: 698px; }
+input.span-19, textarea.span-19 { width: 738px; }
+input.span-20, textarea.span-20 { width: 778px; }
+input.span-21, textarea.span-21 { width: 818px; }
+input.span-22, textarea.span-22 { width: 858px; }
+input.span-23, textarea.span-23 { width: 898px; }
+input.span-24, textarea.span-24 { width: 938px; }
+
+/* Add these to a column to append empty cols. */
+
+.append-1 { padding-right: 40px;}
+.append-2 { padding-right: 80px;}
+.append-3 { padding-right: 120px;}
+.append-4 { padding-right: 160px;}
+.append-5 { padding-right: 200px;}
+.append-6 { padding-right: 240px;}
+.append-7 { padding-right: 280px;}
+.append-8 { padding-right: 320px;}
+.append-9 { padding-right: 360px;}
+.append-10 { padding-right: 400px;}
+.append-11 { padding-right: 440px;}
+.append-12 { padding-right: 480px;}
+.append-13 { padding-right: 520px;}
+.append-14 { padding-right: 560px;}
+.append-15 { padding-right: 600px;}
+.append-16 { padding-right: 640px;}
+.append-17 { padding-right: 680px;}
+.append-18 { padding-right: 720px;}
+.append-19 { padding-right: 760px;}
+.append-20 { padding-right: 800px;}
+.append-21 { padding-right: 840px;}
+.append-22 { padding-right: 880px;}
+.append-23 { padding-right: 920px;}
+
+/* Add these to a column to prepend empty cols. */
+
+.prepend-1 { padding-left: 40px;}
+.prepend-2 { padding-left: 80px;}
+.prepend-3 { padding-left: 120px;}
+.prepend-4 { padding-left: 160px;}
+.prepend-5 { padding-left: 200px;}
+.prepend-6 { padding-left: 240px;}
+.prepend-7 { padding-left: 280px;}
+.prepend-8 { padding-left: 320px;}
+.prepend-9 { padding-left: 360px;}
+.prepend-10 { padding-left: 400px;}
+.prepend-11 { padding-left: 440px;}
+.prepend-12 { padding-left: 480px;}
+.prepend-13 { padding-left: 520px;}
+.prepend-14 { padding-left: 560px;}
+.prepend-15 { padding-left: 600px;}
+.prepend-16 { padding-left: 640px;}
+.prepend-17 { padding-left: 680px;}
+.prepend-18 { padding-left: 720px;}
+.prepend-19 { padding-left: 760px;}
+.prepend-20 { padding-left: 800px;}
+.prepend-21 { padding-left: 840px;}
+.prepend-22 { padding-left: 880px;}
+.prepend-23 { padding-left: 920px;}
+
+
+/* Border on right hand side of a column. */
+.border {
+ padding-right: 4px;
+ margin-right: 5px;
+ border-right: 1px solid #ddd;
+}
+
+/* Border with more whitespace, spans one column. */
+.colborder {
+ padding-right: 24px;
+ margin-right: 25px;
+ border-right: 1px solid #ddd;
+}
+
+
+/* Use these classes on an element to push it into the
+next column, or to pull it into the previous column. */
+
+
+.pull-1 { margin-left: -40px; }
+.pull-2 { margin-left: -80px; }
+.pull-3 { margin-left: -120px; }
+.pull-4 { margin-left: -160px; }
+.pull-5 { margin-left: -200px; }
+.pull-6 { margin-left: -240px; }
+.pull-7 { margin-left: -280px; }
+.pull-8 { margin-left: -320px; }
+.pull-9 { margin-left: -360px; }
+.pull-10 { margin-left: -400px; }
+.pull-11 { margin-left: -440px; }
+.pull-12 { margin-left: -480px; }
+.pull-13 { margin-left: -520px; }
+.pull-14 { margin-left: -560px; }
+.pull-15 { margin-left: -600px; }
+.pull-16 { margin-left: -640px; }
+.pull-17 { margin-left: -680px; }
+.pull-18 { margin-left: -720px; }
+.pull-19 { margin-left: -760px; }
+.pull-20 { margin-left: -800px; }
+.pull-21 { margin-left: -840px; }
+.pull-22 { margin-left: -880px; }
+.pull-23 { margin-left: -920px; }
+.pull-24 { margin-left: -960px; }
+
+.pull-1, .pull-2, .pull-3, .pull-4, .pull-5, .pull-6, .pull-7, .pull-8, .pull-9, .pull-10, .pull-11, .pull-12, .pull-13, .pull-14, .pull-15, .pull-16, .pull-17, .pull-18, .pull-19, .pull-20, .pull-21, .pull-22, .pull-23, .pull-24 {float: left; position:relative;}
+
+
+.push-1 { margin: 0 -40px 1.5em 40px; }
+.push-2 { margin: 0 -80px 1.5em 80px; }
+.push-3 { margin: 0 -120px 1.5em 120px; }
+.push-4 { margin: 0 -160px 1.5em 160px; }
+.push-5 { margin: 0 -200px 1.5em 200px; }
+.push-6 { margin: 0 -240px 1.5em 240px; }
+.push-7 { margin: 0 -280px 1.5em 280px; }
+.push-8 { margin: 0 -320px 1.5em 320px; }
+.push-9 { margin: 0 -360px 1.5em 360px; }
+.push-10 { margin: 0 -400px 1.5em 400px; }
+.push-11 { margin: 0 -440px 1.5em 440px; }
+.push-12 { margin: 0 -480px 1.5em 480px; }
+.push-13 { margin: 0 -520px 1.5em 520px; }
+.push-14 { margin: 0 -560px 1.5em 560px; }
+.push-15 { margin: 0 -600px 1.5em 600px; }
+.push-16 { margin: 0 -640px 1.5em 640px; }
+.push-17 { margin: 0 -680px 1.5em 680px; }
+.push-18 { margin: 0 -720px 1.5em 720px; }
+.push-19 { margin: 0 -760px 1.5em 760px; }
+.push-20 { margin: 0 -800px 1.5em 800px; }
+.push-21 { margin: 0 -840px 1.5em 840px; }
+.push-22 { margin: 0 -880px 1.5em 880px; }
+.push-23 { margin: 0 -920px 1.5em 920px; }
+.push-24 { margin: 0 -960px 1.5em 960px; }
+
+.push-1, .push-2, .push-3, .push-4, .push-5, .push-6, .push-7, .push-8, .push-9, .push-10, .push-11, .push-12, .push-13, .push-14, .push-15, .push-16, .push-17, .push-18, .push-19, .push-20, .push-21, .push-22, .push-23, .push-24 {float: left; position:relative;}
+
+
+/* Misc classes and elements
+-------------------------------------------------------------- */
+
+/* In case you need to add a gutter above/below an element */
+div.prepend-top, .prepend-top {
+ margin-top:1.5em;
+}
+div.append-bottom, .append-bottom {
+ margin-bottom:1.5em;
+}
+
+/* Use a .box to create a padded box inside a column. */
+.box {
+ padding: 1.5em;
+ margin-bottom: 1.5em;
+ background: #e5eCf9;
+}
+
+/* Use this to create a horizontal ruler across a column. */
+hr {
+ background: #ddd;
+ color: #ddd;
+ clear: both;
+ float: none;
+ width: 100%;
+ height: 1px;
+ margin: 0 0 17px;
+ border: none;
+}
+
+hr.space {
+ background: #fff;
+ color: #fff;
+ visibility: hidden;
+}
+
+
+/* Clearing floats without extra markup
+ Based on How To Clear Floats Without Structural Markup by PiE
+ [http://www.positioniseverything.net/easyclearing.html] */
+
+.clearfix:after, .container:after {
+ content: "\0020";
+ display: block;
+ height: 0;
+ clear: both;
+ visibility: hidden;
+ overflow:hidden;
+}
+.clearfix, .container {display: block;}
+
+/* Regular clearing
+ apply to column that should drop below previous ones. */
+
+.clear { clear:both; }
diff --git a/vendor/assets/stylesheets/blueprint/src/grid.png b/vendor/assets/stylesheets/blueprint/src/grid.png
new file mode 100644
index 00000000..d42a6c32
Binary files /dev/null and b/vendor/assets/stylesheets/blueprint/src/grid.png differ
diff --git a/vendor/assets/stylesheets/blueprint/src/ie.css b/vendor/assets/stylesheets/blueprint/src/ie.css
new file mode 100644
index 00000000..111a2eaf
--- /dev/null
+++ b/vendor/assets/stylesheets/blueprint/src/ie.css
@@ -0,0 +1,79 @@
+/* --------------------------------------------------------------
+
+ ie.css
+
+ Contains every hack for Internet Explorer,
+ so that our core files stay sweet and nimble.
+
+-------------------------------------------------------------- */
+
+/* Make sure the layout is centered in IE5 */
+body { text-align: center; }
+.container { text-align: left; }
+
+/* Fixes IE margin bugs */
+* html .column, * html .span-1, * html .span-2,
+* html .span-3, * html .span-4, * html .span-5,
+* html .span-6, * html .span-7, * html .span-8,
+* html .span-9, * html .span-10, * html .span-11,
+* html .span-12, * html .span-13, * html .span-14,
+* html .span-15, * html .span-16, * html .span-17,
+* html .span-18, * html .span-19, * html .span-20,
+* html .span-21, * html .span-22, * html .span-23,
+* html .span-24 { display:inline; overflow-x: hidden; }
+
+
+/* Elements
+-------------------------------------------------------------- */
+
+/* Fixes incorrect styling of legend in IE6. */
+* html legend { margin:0px -8px 16px 0; padding:0; }
+
+/* Fixes wrong line-height on sup/sub in IE. */
+sup { vertical-align:text-top; }
+sub { vertical-align:text-bottom; }
+
+/* Fixes IE7 missing wrapping of code elements. */
+html>body p code { *white-space: normal; }
+
+/* IE 6&7 has problems with setting proper margins. */
+hr { margin:-8px auto 11px; }
+
+/* Explicitly set interpolation, allowing dynamically resized images to not look horrible */
+img { -ms-interpolation-mode:bicubic; }
+
+/* Clearing
+-------------------------------------------------------------- */
+
+/* Makes clearfix actually work in IE */
+.clearfix, .container { display:inline-block; }
+* html .clearfix,
+* html .container { height:1%; }
+
+
+/* Forms
+-------------------------------------------------------------- */
+
+/* Fixes padding on fieldset */
+fieldset { padding-top:0; }
+legend { margin-top:-0.2em; margin-bottom:1em; margin-left:-0.5em; }
+
+/* Makes classic textareas in IE 6 resemble other browsers */
+textarea { overflow:auto; }
+
+/* Makes labels behave correctly in IE 6 and 7 */
+label { vertical-align:middle; position:relative; top:-0.25em; }
+
+/* Fixes rule that IE 6 ignores */
+input.text, input.title, textarea { background-color:#fff; border:1px solid #bbb; }
+input.text:focus, input.title:focus { border-color:#666; }
+input.text, input.title, textarea, select { margin:0.5em 0; }
+input.checkbox, input.radio { position:relative; top:.25em; }
+
+/* Fixes alignment of inline form elements */
+form.inline div, form.inline p { vertical-align:middle; }
+form.inline input.checkbox, form.inline input.radio,
+form.inline input.button, form.inline button {
+ margin:0.5em 0;
+}
+button, input.button { position:relative;top:0.25em; }
diff --git a/vendor/assets/stylesheets/blueprint/src/print.css b/vendor/assets/stylesheets/blueprint/src/print.css
new file mode 100755
index 00000000..b230b842
--- /dev/null
+++ b/vendor/assets/stylesheets/blueprint/src/print.css
@@ -0,0 +1,92 @@
+/* --------------------------------------------------------------
+
+ print.css
+ * Gives you some sensible styles for printing pages.
+ * See Readme file in this directory for further instructions.
+
+ Some additions you'll want to make, customized to your markup:
+ #header, #footer, #navigation { display:none; }
+
+-------------------------------------------------------------- */
+
+body {
+ line-height: 1.5;
+ font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
+ color:#000;
+ background: none;
+ font-size: 10pt;
+}
+
+
+/* Layout
+-------------------------------------------------------------- */
+
+.container {
+ background: none;
+}
+
+hr {
+ background:#ccc;
+ color:#ccc;
+ width:100%;
+ height:2px;
+ margin:2em 0;
+ padding:0;
+ border:none;
+}
+hr.space {
+ background: #fff;
+ color: #fff;
+ visibility: hidden;
+}
+
+
+/* Text
+-------------------------------------------------------------- */
+
+h1,h2,h3,h4,h5,h6 { font-family: "Helvetica Neue", Arial, "Lucida Grande", sans-serif; }
+code { font:.9em "Courier New", Monaco, Courier, monospace; }
+
+a img { border:none; }
+p img.top { margin-top: 0; }
+
+blockquote {
+ margin:1.5em;
+ padding:1em;
+ font-style:italic;
+ font-size:.9em;
+}
+
+.small { font-size: .9em; }
+.large { font-size: 1.1em; }
+.quiet { color: #999; }
+.hide { display:none; }
+
+
+/* Links
+-------------------------------------------------------------- */
+
+a:link, a:visited {
+ background: transparent;
+ font-weight:700;
+ text-decoration: underline;
+}
+
+/*
+ This has been the source of many questions in the past. This
+ snippet of CSS appends the URL of each link within the text.
+ The idea is that users printing your webpage will want to know
+ the URLs they go to. If you want to remove this functionality,
+ comment out this snippet and make sure to re-compress your files.
+ */
+a:link:after, a:visited:after {
+ content: " (" attr(href) ")";
+ font-size: 90%;
+}
+
+/* If you're having trouble printing relative links, uncomment and customize this:
+ (note: This is valid CSS3, but it still won't go through the W3C CSS Validator) */
+
+/* a[href^="/"]:after {
+ content: " (http://www.yourdomain.com" attr(href) ") ";
+} */
diff --git a/vendor/assets/stylesheets/blueprint/src/reset.css b/vendor/assets/stylesheets/blueprint/src/reset.css
new file mode 100755
index 00000000..b26168fa
--- /dev/null
+++ b/vendor/assets/stylesheets/blueprint/src/reset.css
@@ -0,0 +1,65 @@
+/* --------------------------------------------------------------
+
+ reset.css
+ * Resets default browser CSS.
+
+-------------------------------------------------------------- */
+
+html {
+ margin:0;
+ padding:0;
+ border:0;
+}
+
+body, div, span, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, code,
+del, dfn, em, img, q, dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, dialog, figure, footer, header,
+hgroup, nav, section {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ font-size: 100%;
+ font: inherit;
+ vertical-align: baseline;
+}
+
+/* This helps to make newer HTML5 elements behave like DIVs in older browers */
+article, aside, details, figcaption, figure, dialog,
+footer, header, hgroup, menu, nav, section {
+ display:block;
+}
+
+/* Line-height should always be unitless! */
+body {
+ line-height: 1.5;
+ background: white;
+}
+
+/* Tables still need 'cellspacing="0"' in the markup. */
+table {
+ border-collapse: separate;
+ border-spacing: 0;
+}
+/* float:none prevents the span-x classes from breaking table-cell display */
+caption, th, td {
+ text-align: left;
+ font-weight: normal;
+ float:none !important;
+}
+table, th, td {
+ vertical-align: middle;
+}
+
+/* Remove possible quote marks (") from , . */
+blockquote:before, blockquote:after, q:before, q:after { content: ''; }
+blockquote, q { quotes: "" ""; }
+
+/* Remove annoying border on linked images. */
+a img { border: none; }
+
+/* Remember to define your own focus styles! */
+:focus { outline: 0; }
diff --git a/vendor/assets/stylesheets/blueprint/src/typography.css b/vendor/assets/stylesheets/blueprint/src/typography.css
new file mode 100644
index 00000000..adef7128
--- /dev/null
+++ b/vendor/assets/stylesheets/blueprint/src/typography.css
@@ -0,0 +1,123 @@
+/* --------------------------------------------------------------
+
+ typography.css
+ * Sets up some sensible default typography.
+
+-------------------------------------------------------------- */
+
+/* Default font settings.
+ The font-size percentage is of 16px. (0.75 * 16px = 12px) */
+html { font-size:100.01%; }
+body {
+ font-size: 75%;
+ color: #222;
+ background: #fff;
+ font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
+}
+
+
+/* Headings
+-------------------------------------------------------------- */
+
+h1,h2,h3,h4,h5,h6 { font-weight: normal; color: #111; }
+
+h1 { font-size: 3em; line-height: 1; margin-bottom: 0.5em; }
+h2 { font-size: 2em; margin-bottom: 0.75em; }
+h3 { font-size: 1.5em; line-height: 1; margin-bottom: 1em; }
+h4 { font-size: 1.2em; line-height: 1.25; margin-bottom: 1.25em; }
+h5 { font-size: 1em; font-weight: bold; margin-bottom: 1.5em; }
+h6 { font-size: 1em; font-weight: bold; }
+
+h1 img, h2 img, h3 img,
+h4 img, h5 img, h6 img {
+ margin: 0;
+}
+
+
+/* Text elements
+-------------------------------------------------------------- */
+
+p { margin: 0 0 1.5em; }
+/*
+ These can be used to pull an image at the start of a paragraph, so
+ that the text flows around it (usage: Text
)
+ */
+.left { float: left !important; }
+p .left { margin: 1.5em 1.5em 1.5em 0; padding: 0; }
+.right { float: right !important; }
+p .right { margin: 1.5em 0 1.5em 1.5em; padding: 0; }
+
+a:focus,
+a:hover { color: #09f; }
+a { color: #06c; text-decoration: underline; }
+
+blockquote { margin: 1.5em; color: #666; font-style: italic; }
+strong,dfn { font-weight: bold; }
+em,dfn { font-style: italic; }
+sup, sub { line-height: 0; }
+
+abbr,
+acronym { border-bottom: 1px dotted #666; }
+address { margin: 0 0 1.5em; font-style: italic; }
+del { color:#666; }
+
+pre { margin: 1.5em 0; white-space: pre; }
+pre,code,tt { font: 1em 'andale mono', 'lucida console', monospace; line-height: 1.5; }
+
+
+/* Lists
+-------------------------------------------------------------- */
+
+li ul,
+li ol { margin: 0; }
+ul, ol { margin: 0 1.5em 1.5em 0; padding-left: 1.5em; }
+
+ul { list-style-type: disc; }
+ol { list-style-type: decimal; }
+
+dl { margin: 0 0 1.5em 0; }
+dl dt { font-weight: bold; }
+dd { margin-left: 1.5em;}
+
+
+/* Tables
+-------------------------------------------------------------- */
+
+/*
+ Because of the need for padding on TH and TD, the vertical rhythm
+ on table cells has to be 27px, instead of the standard 18px or 36px
+ of other elements.
+ */
+table { margin-bottom: 1.4em; width:100%; }
+th { font-weight: bold; }
+thead th { background: #c3d9ff; }
+th,td,caption { padding: 4px 10px 4px 5px; }
+/*
+ You can zebra-stripe your tables in outdated browsers by adding
+ the class "even" to every other table row.
+ */
+tbody tr:nth-child(even) td,
+tbody tr.even td {
+ background: #e5ecf9;
+}
+tfoot { font-style: italic; }
+caption { background: #eee; }
+
+
+/* Misc classes
+-------------------------------------------------------------- */
+
+.small { font-size: .8em; margin-bottom: 1.875em; line-height: 1.875em; }
+.large { font-size: 1.2em; line-height: 2.5em; margin-bottom: 1.25em; }
+.hide { display: none; }
+
+.quiet { color: #666; }
+.loud { color: #000; }
+.highlight { background:#ff0; }
+.added { background:#060; color: #fff; }
+.removed { background:#900; color: #fff; }
+
+.first { margin-left:0; padding-left:0; }
+.last { margin-right:0; padding-right:0; }
+.top { margin-top:0; padding-top:0; }
+.bottom { margin-bottom:0; padding-bottom:0; }
diff --git a/vendor/assets/stylesheets/jquery_ui/jquery-ui-1.8.16.custom.css b/vendor/assets/stylesheets/jquery_ui/jquery-ui-1.8.16.custom.css
new file mode 100644
index 00000000..4308e08e
--- /dev/null
+++ b/vendor/assets/stylesheets/jquery_ui/jquery-ui-1.8.16.custom.css
@@ -0,0 +1,577 @@
+/*
+ * jQuery UI CSS Framework 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Theming/API
+ */
+
+/* Layout helpers
+----------------------------------*/
+.ui-helper-hidden { display: none; }
+.ui-helper-hidden-accessible { position: absolute !important; clip: rect(1px 1px 1px 1px); clip: rect(1px,1px,1px,1px); }
+.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; }
+.ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; }
+.ui-helper-clearfix { display: inline-block; }
+/* required comment for clearfix to work in Opera \*/
+* html .ui-helper-clearfix { height:1%; }
+.ui-helper-clearfix { display:block; }
+/* end clearfix */
+.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); }
+
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-disabled { cursor: default !important; }
+
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Overlays */
+.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
+
+
+/*
+ * jQuery UI CSS Framework 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Theming/API
+ *
+ * To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana,Arial,sans-serif&fwDefault=normal&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=cccccc&bgTextureHeader=03_highlight_soft.png&bgImgOpacityHeader=75&borderColorHeader=aaaaaa&fcHeader=222222&iconColorHeader=222222&bgColorContent=ffffff&bgTextureContent=01_flat.png&bgImgOpacityContent=75&borderColorContent=aaaaaa&fcContent=222222&iconColorContent=222222&bgColorDefault=e6e6e6&bgTextureDefault=02_glass.png&bgImgOpacityDefault=75&borderColorDefault=d3d3d3&fcDefault=555555&iconColorDefault=888888&bgColorHover=dadada&bgTextureHover=02_glass.png&bgImgOpacityHover=75&borderColorHover=999999&fcHover=212121&iconColorHover=454545&bgColorActive=ffffff&bgTextureActive=02_glass.png&bgImgOpacityActive=65&borderColorActive=aaaaaa&fcActive=212121&iconColorActive=454545&bgColorHighlight=fbf9ee&bgTextureHighlight=02_glass.png&bgImgOpacityHighlight=55&borderColorHighlight=fcefa1&fcHighlight=363636&iconColorHighlight=2e83ff&bgColorError=fef1ec&bgTextureError=02_glass.png&bgImgOpacityError=95&borderColorError=cd0a0a&fcError=cd0a0a&iconColorError=cd0a0a&bgColorOverlay=aaaaaa&bgTextureOverlay=01_flat.png&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=aaaaaa&bgTextureShadow=01_flat.png&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px
+ */
+
+
+/* Component containers
+----------------------------------*/
+.ui-widget { font-family: "Helvetica Neue",Arial,Helvetica,sans-serif; font-size: 1.1em; }
+.ui-widget .ui-widget { font-size: 1em; }
+.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Verdana,Arial,sans-serif; font-size: 1em; }
+.ui-widget-content { border: 1px solid #dddddd; background: #ffffff url(images/ui-bg_flat_75_ffffff_40x100.png) 50% 50% repeat-x; color: #222222; }
+.ui-widget-content a { color: #222222; }
+.ui-widget-header { color: #222222; font-weight: bold; }
+.ui-widget-header a { color: #222222; }
+
+/* Interaction states
+----------------------------------*/
+.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #d3d3d3; background: #e6e6e6 url(images/ui-bg_glass_75_e6e6e6_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #555555; }
+.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #555555; text-decoration: none; }
+.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #999999; background: #dadada url(images/ui-bg_glass_75_dadada_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #212121; }
+.ui-state-hover a, .ui-state-hover a:hover { color: #212121; text-decoration: none; }
+.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #aaaaaa; background: #ffffff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #212121; }
+.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #212121; text-decoration: none; }
+.ui-widget :active { outline: none; }
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #fcefa1; background: #fbf9ee url(images/ui-bg_glass_55_fbf9ee_1x400.png) 50% 50% repeat-x; color: #363636; }
+.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636; }
+.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #cd0a0a; background: #fef1ec url(images/ui-bg_glass_95_fef1ec_1x400.png) 50% 50% repeat-x; color: #cd0a0a; }
+.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #cd0a0a; }
+.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #cd0a0a; }
+.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; }
+.ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; }
+.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; }
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_222222_256x240.png); }
+.ui-widget-content .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); }
+.ui-widget-header .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); }
+.ui-state-default .ui-icon { background-image: url(images/ui-icons_888888_256x240.png); }
+.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_454545_256x240.png); }
+.ui-state-active .ui-icon {background-image: url(images/ui-icons_454545_256x240.png); }
+.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_2e83ff_256x240.png); }
+.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_cd0a0a_256x240.png); }
+
+/* positioning */
+.ui-icon-carat-1-n { background-position: 0 0; }
+.ui-icon-carat-1-ne { background-position: -16px 0; }
+.ui-icon-carat-1-e { background-position: -32px 0; }
+.ui-icon-carat-1-se { background-position: -48px 0; }
+.ui-icon-carat-1-s { background-position: -64px 0; }
+.ui-icon-carat-1-sw { background-position: -80px 0; }
+.ui-icon-carat-1-w { background-position: -96px 0; }
+.ui-icon-carat-1-nw { background-position: -112px 0; }
+.ui-icon-carat-2-n-s { background-position: -128px 0; }
+.ui-icon-carat-2-e-w { background-position: -144px 0; }
+.ui-icon-triangle-1-n { background-position: 0 -16px; }
+.ui-icon-triangle-1-ne { background-position: -16px -16px; }
+.ui-icon-triangle-1-e { background-position: -32px -16px; }
+.ui-icon-triangle-1-se { background-position: -48px -16px; }
+.ui-icon-triangle-1-s { background-position: -64px -16px; }
+.ui-icon-triangle-1-sw { background-position: -80px -16px; }
+.ui-icon-triangle-1-w { background-position: -96px -16px; }
+.ui-icon-triangle-1-nw { background-position: -112px -16px; }
+.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
+.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
+.ui-icon-arrow-1-n { background-position: 0 -32px; }
+.ui-icon-arrow-1-ne { background-position: -16px -32px; }
+.ui-icon-arrow-1-e { background-position: -32px -32px; }
+.ui-icon-arrow-1-se { background-position: -48px -32px; }
+.ui-icon-arrow-1-s { background-position: -64px -32px; }
+.ui-icon-arrow-1-sw { background-position: -80px -32px; }
+.ui-icon-arrow-1-w { background-position: -96px -32px; }
+.ui-icon-arrow-1-nw { background-position: -112px -32px; }
+.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
+.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
+.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
+.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
+.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
+.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
+.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
+.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
+.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
+.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
+.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
+.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
+.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
+.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
+.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
+.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
+.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
+.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
+.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
+.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
+.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
+.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
+.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
+.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
+.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
+.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
+.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
+.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
+.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
+.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
+.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
+.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
+.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
+.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
+.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
+.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
+.ui-icon-arrow-4 { background-position: 0 -80px; }
+.ui-icon-arrow-4-diag { background-position: -16px -80px; }
+.ui-icon-extlink { background-position: -32px -80px; }
+.ui-icon-newwin { background-position: -48px -80px; }
+.ui-icon-refresh { background-position: -64px -80px; }
+.ui-icon-shuffle { background-position: -80px -80px; }
+.ui-icon-transfer-e-w { background-position: -96px -80px; }
+.ui-icon-transferthick-e-w { background-position: -112px -80px; }
+.ui-icon-folder-collapsed { background-position: 0 -96px; }
+.ui-icon-folder-open { background-position: -16px -96px; }
+.ui-icon-document { background-position: -32px -96px; }
+.ui-icon-document-b { background-position: -48px -96px; }
+.ui-icon-note { background-position: -64px -96px; }
+.ui-icon-mail-closed { background-position: -80px -96px; }
+.ui-icon-mail-open { background-position: -96px -96px; }
+.ui-icon-suitcase { background-position: -112px -96px; }
+.ui-icon-comment { background-position: -128px -96px; }
+.ui-icon-person { background-position: -144px -96px; }
+.ui-icon-print { background-position: -160px -96px; }
+.ui-icon-trash { background-position: -176px -96px; }
+.ui-icon-locked { background-position: -192px -96px; }
+.ui-icon-unlocked { background-position: -208px -96px; }
+.ui-icon-bookmark { background-position: -224px -96px; }
+.ui-icon-tag { background-position: -240px -96px; }
+.ui-icon-home { background-position: 0 -112px; }
+.ui-icon-flag { background-position: -16px -112px; }
+.ui-icon-calendar { background-position: -32px -112px; }
+.ui-icon-cart { background-position: -48px -112px; }
+.ui-icon-pencil { background-position: -64px -112px; }
+.ui-icon-clock { background-position: -80px -112px; }
+.ui-icon-disk { background-position: -96px -112px; }
+.ui-icon-calculator { background-position: -112px -112px; }
+.ui-icon-zoomin { background-position: -128px -112px; }
+.ui-icon-zoomout { background-position: -144px -112px; }
+.ui-icon-search { background-position: -160px -112px; }
+.ui-icon-wrench { background-position: -176px -112px; }
+.ui-icon-gear { background-position: -192px -112px; }
+.ui-icon-heart { background-position: -208px -112px; }
+.ui-icon-star { background-position: -224px -112px; }
+.ui-icon-link { background-position: -240px -112px; }
+.ui-icon-cancel { background-position: 0 -128px; }
+.ui-icon-plus { background-position: -16px -128px; }
+.ui-icon-plusthick { background-position: -32px -128px; }
+.ui-icon-minus { background-position: -48px -128px; }
+.ui-icon-minusthick { background-position: -64px -128px; }
+.ui-icon-close { background-position: -80px -128px; }
+.ui-icon-closethick { background-position: -96px -128px; }
+.ui-icon-key { background-position: -112px -128px; }
+.ui-icon-lightbulb { background-position: -128px -128px; }
+.ui-icon-scissors { background-position: -144px -128px; }
+.ui-icon-clipboard { background-position: -160px -128px; }
+.ui-icon-copy { background-position: -176px -128px; }
+.ui-icon-contact { background-position: -192px -128px; }
+.ui-icon-image { background-position: -208px -128px; }
+.ui-icon-video { background-position: -224px -128px; }
+.ui-icon-script { background-position: -240px -128px; }
+.ui-icon-alert { background-position: 0 -144px; }
+.ui-icon-info { background-position: -16px -144px; }
+.ui-icon-notice { background-position: -32px -144px; }
+.ui-icon-help { background-position: -48px -144px; }
+.ui-icon-check { background-position: -64px -144px; }
+.ui-icon-bullet { background-position: -80px -144px; }
+.ui-icon-radio-off { background-position: -96px -144px; }
+.ui-icon-radio-on { background-position: -112px -144px; }
+.ui-icon-pin-w { background-position: -128px -144px; }
+.ui-icon-pin-s { background-position: -144px -144px; }
+.ui-icon-play { background-position: 0 -160px; }
+.ui-icon-pause { background-position: -16px -160px; }
+.ui-icon-seek-next { background-position: -32px -160px; }
+.ui-icon-seek-prev { background-position: -48px -160px; }
+.ui-icon-seek-end { background-position: -64px -160px; }
+.ui-icon-seek-start { background-position: -80px -160px; }
+/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
+.ui-icon-seek-first { background-position: -80px -160px; }
+.ui-icon-stop { background-position: -96px -160px; }
+.ui-icon-eject { background-position: -112px -160px; }
+.ui-icon-volume-off { background-position: -128px -160px; }
+.ui-icon-volume-on { background-position: -144px -160px; }
+.ui-icon-power { background-position: 0 -176px; }
+.ui-icon-signal-diag { background-position: -16px -176px; }
+.ui-icon-signal { background-position: -32px -176px; }
+.ui-icon-battery-0 { background-position: -48px -176px; }
+.ui-icon-battery-1 { background-position: -64px -176px; }
+.ui-icon-battery-2 { background-position: -80px -176px; }
+.ui-icon-battery-3 { background-position: -96px -176px; }
+.ui-icon-circle-plus { background-position: 0 -192px; }
+.ui-icon-circle-minus { background-position: -16px -192px; }
+.ui-icon-circle-close { background-position: -32px -192px; }
+.ui-icon-circle-triangle-e { background-position: -48px -192px; }
+.ui-icon-circle-triangle-s { background-position: -64px -192px; }
+.ui-icon-circle-triangle-w { background-position: -80px -192px; }
+.ui-icon-circle-triangle-n { background-position: -96px -192px; }
+.ui-icon-circle-arrow-e { background-position: -112px -192px; }
+.ui-icon-circle-arrow-s { background-position: -128px -192px; }
+.ui-icon-circle-arrow-w { background-position: -144px -192px; }
+.ui-icon-circle-arrow-n { background-position: -160px -192px; }
+.ui-icon-circle-zoomin { background-position: -176px -192px; }
+.ui-icon-circle-zoomout { background-position: -192px -192px; }
+.ui-icon-circle-check { background-position: -208px -192px; }
+.ui-icon-circlesmall-plus { background-position: 0 -208px; }
+.ui-icon-circlesmall-minus { background-position: -16px -208px; }
+.ui-icon-circlesmall-close { background-position: -32px -208px; }
+.ui-icon-squaresmall-plus { background-position: -48px -208px; }
+.ui-icon-squaresmall-minus { background-position: -64px -208px; }
+.ui-icon-squaresmall-close { background-position: -80px -208px; }
+.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
+.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
+.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
+.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
+.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
+.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Corner radius */
+/*.ui-corner-all, .ui-corner-top, .ui-corner-left, .ui-corner-tl { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; -khtml-border-top-left-radius: 4px; border-top-left-radius: 4px; }*/
+/*.ui-corner-all, .ui-corner-top, .ui-corner-right, .ui-corner-tr { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; -khtml-border-top-right-radius: 4px; border-top-right-radius: 4px; }*/
+/*.ui-corner-all, .ui-corner-bottom, .ui-corner-left, .ui-corner-bl { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; -khtml-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; }*/
+/*.ui-corner-all, .ui-corner-bottom, .ui-corner-right, .ui-corner-br { -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; -khtml-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; }*/
+
+/* Overlays */
+.ui-widget-overlay { background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); }
+.ui-widget-shadow { margin: -8px 0 0 -8px; padding: 8px; background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); -moz-border-radius: 8px; -khtml-border-radius: 8px; -webkit-border-radius: 8px; border-radius: 8px; }/*
+ * jQuery UI Resizable 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Resizable#theming
+ */
+.ui-resizable { position: relative;}
+.ui-resizable-handle { position: absolute;font-size: 0.1px;z-index: 99999; display: block; }
+.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; }
+.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0; }
+.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0; }
+.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0; height: 100%; }
+.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0; height: 100%; }
+.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; }
+.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; }
+.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; }
+.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}/*
+ * jQuery UI Selectable 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Selectable#theming
+ */
+.ui-selectable-helper { position: absolute; z-index: 100; border:1px dotted black; }
+/*
+ * jQuery UI Accordion 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Accordion#theming
+ */
+/* IE/Win - Fix animation bug - #4615 */
+.ui-accordion { width: 100%; }
+.ui-accordion .ui-accordion-header { cursor: pointer; position: relative; margin-top: 1px; zoom: 1; border:1px solid #ddd}
+.ui-accordion .ui-accordion-li-fix { display: inline; }
+.ui-accordion .ui-accordion-header-active { border-bottom: 0 !important; }
+.ui-accordion .ui-accordion-header a { display: block; font-size: 1em; padding: .5em .5em .5em .7em; }
+.ui-accordion-icons .ui-accordion-header a { padding-left: 2.2em; }
+.ui-accordion .ui-accordion-header .ui-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; }
+.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; zoom: 1; }
+.ui-accordion .ui-accordion-content-active { display: block; }
+/*
+ * jQuery UI Autocomplete 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Autocomplete#theming
+ */
+.ui-autocomplete { position: absolute; cursor: default; }
+
+/* workarounds */
+* html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */
+
+/*
+ * jQuery UI Menu 1.8.16
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Menu#theming
+ */
+.ui-menu {
+ list-style:none;
+ padding: 2px;
+ margin: 0;
+ display:block;
+ float: left;
+}
+.ui-menu .ui-menu {
+ margin-top: -3px;
+}
+.ui-menu .ui-menu-item {
+ margin:0;
+ padding: 0;
+ zoom: 1;
+ float: left;
+ clear: left;
+ width: 100%;
+}
+.ui-menu .ui-menu-item a {
+ text-decoration:none;
+ display:block;
+ padding:.2em .4em;
+ line-height:1.5;
+ zoom:1;
+}
+.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;
+ border-radius:0px;
+ border-color:white;
+ border-bottom: 1px solid #E2EAEE;
+ border-top: 1px solid #E2EAEE;
+
+}
+/*
+ * jQuery UI Button 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Button#theming
+ */
+.ui-button { display: inline-block; position: relative; padding: 0; margin-right: .1em; text-decoration: none !important; cursor: pointer; text-align: center; zoom: 1; overflow: visible; } /* the overflow property removes extra width in IE */
+.ui-button-icon-only { width: 2.2em; } /* to make room for the icon, a width needs to be set here */
+button.ui-button-icon-only { width: 2.4em; } /* button elements seem to need a little more width */
+.ui-button-icons-only { width: 3.4em; }
+button.ui-button-icons-only { width: 3.7em; }
+
+/*button text element */
+.ui-button .ui-button-text { display: block; line-height: 1.4; }
+.ui-button-text-only .ui-button-text { padding: .4em 1em; }
+.ui-button-icon-only .ui-button-text, .ui-button-icons-only .ui-button-text { padding: .4em; text-indent: -9999999px; }
+.ui-button-text-icon-primary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 1em .4em 2.1em; }
+.ui-button-text-icon-secondary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 2.1em .4em 1em; }
+.ui-button-text-icons .ui-button-text { padding-left: 2.1em; padding-right: 2.1em; }
+/* no icon support for input elements, provide padding by default */
+input.ui-button { padding: .4em 1em; }
+
+/*button icon element(s) */
+.ui-button-icon-only .ui-icon, .ui-button-text-icon-primary .ui-icon, .ui-button-text-icon-secondary .ui-icon, .ui-button-text-icons .ui-icon, .ui-button-icons-only .ui-icon { position: absolute; top: 50%; margin-top: -8px; }
+.ui-button-icon-only .ui-icon { left: 50%; margin-left: -8px; }
+.ui-button-text-icon-primary .ui-button-icon-primary, .ui-button-text-icons .ui-button-icon-primary, .ui-button-icons-only .ui-button-icon-primary { left: .5em; }
+.ui-button-text-icon-secondary .ui-button-icon-secondary, .ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; }
+.ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; }
+
+/*button sets*/
+.ui-buttonset { margin-right: 7px; }
+.ui-buttonset .ui-button { margin-left: 0; margin-right: -.3em; }
+
+/* workarounds */
+button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra padding in Firefox */
+/*
+ * jQuery UI Dialog 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Dialog#theming
+ */
+.ui-dialog { position: absolute; padding: 0; width: 300px; overflow: hidden; }
+.ui-dialog .ui-dialog-titlebar { padding: .4em 1em; position: relative; background: #333; color:#eaeaea }
+.ui-dialog .ui-dialog-title { float: left; margin: .1em 16px .1em 0;}
+.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; background:#eaeaea}
+.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; }
+.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; }
+.ui-dialog .ui-dialog-content { position: relative; border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; }
+.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; }
+.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { float: right; }
+.ui-dialog .ui-dialog-buttonpane button { margin: .5em .4em .5em 0; cursor: pointer; }
+.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; }
+.ui-draggable .ui-dialog-titlebar { cursor: move; }
+/*
+ * jQuery UI Slider 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Slider#theming
+ */
+.ui-slider { position: relative; text-align: left; }
+.ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; }
+.ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; background-position: 0 0; }
+
+.ui-slider-horizontal { height: .8em; }
+.ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; }
+.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; }
+.ui-slider-horizontal .ui-slider-range-min { left: 0; }
+.ui-slider-horizontal .ui-slider-range-max { right: 0; }
+
+.ui-slider-vertical { width: .8em; height: 100px; }
+.ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; }
+.ui-slider-vertical .ui-slider-range { left: 0; width: 100%; }
+.ui-slider-vertical .ui-slider-range-min { bottom: 0; }
+.ui-slider-vertical .ui-slider-range-max { top: 0; }/*
+ * jQuery UI Tabs 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Tabs#theming
+ */
+.ui-tabs { position: relative; padding: 0; border:none; zoom: 1; } /* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */
+.ui-tabs .ui-tabs-nav { margin: 0; padding: .2em .2em 0; }
+.ui-tabs .ui-tabs-nav li { list-style: none; float: left; position: relative; top: 1px; margin: 0 .2em 1px 0; border-bottom: 0 !important; padding: 0; white-space: nowrap;}
+.ui-tabs .ui-tabs-nav li a { float: left; padding: .5em 1em; text-decoration: none; width:200px}
+.ui-tabs .ui-tabs-nav li.ui-tabs-selected { margin-bottom: 0; padding-bottom: 1px; }
+.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; }
+.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */
+.ui-tabs .ui-tabs-panel { display: block; border-width: 0; padding: 1em 0; background: none; }
+.ui-tabs .ui-tabs-hide { display: none !important; }
+/*
+ * jQuery UI Datepicker 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Datepicker#theming
+ */
+.ui-datepicker { width: 17em; padding: .2em .2em 0; display: none; }
+.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; }
+.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; }
+.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; }
+.ui-datepicker .ui-datepicker-prev { left:2px; }
+.ui-datepicker .ui-datepicker-next { right:2px; }
+.ui-datepicker .ui-datepicker-prev-hover { left:1px; }
+.ui-datepicker .ui-datepicker-next-hover { right:1px; }
+.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; }
+.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; }
+.ui-datepicker .ui-datepicker-title select { font-size:1em; margin:1px 0; }
+.ui-datepicker select.ui-datepicker-month-year {width: 100%;}
+.ui-datepicker select.ui-datepicker-month,
+.ui-datepicker select.ui-datepicker-year { width: 49%;}
+.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; }
+.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; }
+.ui-datepicker td { border: 0; padding: 1px; }
+.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; }
+.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; }
+.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; }
+.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; }
+
+/* with multiple calendars */
+.ui-datepicker.ui-datepicker-multi { width:auto; }
+.ui-datepicker-multi .ui-datepicker-group { float:left; }
+.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; }
+.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; }
+.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; }
+.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; }
+.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; }
+.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; }
+.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; }
+.ui-datepicker-row-break { clear:both; width:100%; font-size:0em; }
+
+/* RTL support */
+.ui-datepicker-rtl { direction: rtl; }
+.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; }
+.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; }
+.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; }
+.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; }
+.ui-datepicker-rtl .ui-datepicker-group { float:right; }
+.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
+.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
+
+/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */
+.ui-datepicker-cover {
+ display: none; /*sorry for IE5*/
+ display/**/: block; /*sorry for IE5*/
+ position: absolute; /*must have*/
+ z-index: -1; /*must have*/
+ filter: mask(); /*must have*/
+ top: -4px; /*must have*/
+ left: -4px; /*must have*/
+ width: 200px; /*must have*/
+ height: 200px; /*must have*/
+}/*
+ * jQuery UI Progressbar 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Progressbar#theming
+ */
+.ui-progressbar { height:2em; text-align: left; }
+.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; }
diff --git a/vendor/plugins/.gitkeep b/vendor/plugins/.gitkeep
new file mode 100644
index 00000000..e69de29b