From c18222ea51993d594c71ea191fd443d4cf274122 Mon Sep 17 00:00:00 2001
From: Piotr Sarnacki <drogus@gmail.com>
Date: Tue, 1 Oct 2013 10:22:54 +0200
Subject: [PATCH 01/11] Settings pane

This commit contains a settings pane implementation. There are a couple
of things here, which are not used yet, like advanced form helpers. I'm
leaving them here, because the plan is to add support for more settings
soon (like: include/exclude branch patterns), which will need these
helpers.

There is also tabs support, although in the current version there is
only one tab (initially it was created for supporting general tab and
notifications tab).
---
 assets/scripts/app/auth.coffee                |   2 +-
 assets/scripts/app/controllers.coffee         |  18 ++
 assets/scripts/app/helpers/handlebars.coffee  | 286 ++++++++++++++++++
 assets/scripts/app/models/repo.coffee         |   7 +
 assets/scripts/app/routes.coffee              |  51 +++-
 .../scripts/app/templates/layouts/profile.hbs |   2 +-
 assets/scripts/app/templates/profile/repo.hbs |   3 +
 .../app/templates/profile/repo/settings.hbs   |  29 ++
 assets/scripts/app/templates/repos/show.hbs   |   2 +-
 assets/scripts/app/views.coffee               |   1 +
 assets/scripts/lib/travis/ajax.coffee         |   3 +-
 .../spec/unit/settings_helpers_spec.coffee    |  28 ++
 assets/styles/app/loading.sass                |   2 +-
 13 files changed, 419 insertions(+), 15 deletions(-)
 create mode 100644 assets/scripts/app/templates/profile/repo.hbs
 create mode 100644 assets/scripts/app/templates/profile/repo/settings.hbs
 create mode 100644 assets/scripts/spec/unit/settings_helpers_spec.coffee

diff --git a/assets/scripts/app/auth.coffee b/assets/scripts/app/auth.coffee
index 565a18c1..b8116df7 100644
--- a/assets/scripts/app/auth.coffee
+++ b/assets/scripts/app/auth.coffee
@@ -74,7 +74,7 @@
         try
           router.send('afterSignIn')
         catch e
-          throw e unless e =~ /There are no active handlers/
+          throw e unless e =~ /There are no active handlers/ || e =~ /Can't trigger action "afterSignIn/
         @refreshUserData(data.user)
 
   refreshUserData: (user) ->
diff --git a/assets/scripts/app/controllers.coffee b/assets/scripts/app/controllers.coffee
index f1b4f520..bcec80fd 100644
--- a/assets/scripts/app/controllers.coffee
+++ b/assets/scripts/app/controllers.coffee
@@ -48,6 +48,24 @@ Travis.FirstSyncController = Em.Controller.extend
 
   isSyncing: Ember.computed.alias('user.isSyncing')
 
+Travis.ProfileRepoController = Em.ObjectController.extend()
+Travis.ProfileRepoSettingsTabController = Em.ObjectController.extend()
+
+Travis.ProfileRepoSettingsController = Em.Controller.extend
+  needs: ['profileRepoSettingsTab', 'profileRepo']
+  tab: Ember.computed.alias('controllers.profileRepoSettingsTab.model.tab')
+  repo: Ember.computed.alias('controllers.profileRepo.content')
+
+  submit: ->
+    @set('saving', true)
+    self = this
+    @get('repo').saveSettings(@get('settings')).then ->
+      self.set('saving', false)
+      Travis.flash(success: 'Settings were saved successfully')
+    , ->
+      self.set('saving', false)
+      Travis.flash(error: 'There was an error while saving settings. Please try again.')
+
 require 'controllers/accounts'
 require 'controllers/build'
 require 'controllers/builds'
diff --git a/assets/scripts/app/helpers/handlebars.coffee b/assets/scripts/app/helpers/handlebars.coffee
index 8dfda033..63e6cc15 100644
--- a/assets/scripts/app/helpers/handlebars.coffee
+++ b/assets/scripts/app/helpers/handlebars.coffee
@@ -3,6 +3,292 @@ require 'ext/ember/bound_helper'
 safe = (string) ->
   new Handlebars.SafeString(string)
 
+Travis.Tab = Ember.Object.extend
+  show: ->
+    @get('tabs').forEach( (t) -> t.hide() )
+    @set('visible', true)
+
+  hide: ->
+    @set('visible', false)
+
+Travis.TabsView = Ember.View.extend
+  tabBinding: 'controller.tab'
+  tabsBinding: 'controller.tabs'
+
+  tabDidChange: (->
+    @activateTab(@get('tab'))
+  ).observes('tab')
+
+  tabsDidChange: (->
+    tab = @get('tab')
+    if tab
+      @activateTab(tab)
+    else if @get('tabs.length')
+      @activateTab(@get('tabs.firstObject.id'))
+  ).observes('tabs.length', 'tabs')
+
+  activateTab: (tabId) ->
+    tab = @get('tabs').findBy('id', tabId)
+
+    return unless tab
+
+    tab.show() unless tab.get('visible')
+
+  layout: Ember.Handlebars.compile(
+    '<ul class="tabs">' +
+    '  {{#each tab in tabs}}' +
+    '    <li {{bindAttr class="tab.visible:active"}}>' +
+    '      <h5>{{#linkTo "profile.repo.settings.tab" tab.id}}{{tab.name}}{{/linkTo}}</h5>' +
+    '    </li>' +
+    '  {{/each}}' +
+    '</ul>' +
+    '{{yield}}')
+
+Travis.TabView = Ember.View.extend
+  attributeBindings: ['style']
+
+  style: (->
+    if !@get('tab.visible')
+      'display: none'
+  ).property('tab.visible')
+
+Ember.Handlebars.registerHelper('travis-tab', (id, name, options) ->
+  controller = this
+  controller.set('tabs', []) unless controller.get('tabs')
+
+  tab = Travis.Tab.create(id: id, name: name, tabs: controller.get('tabs'))
+
+  view = Travis.TabView.create(
+    controller: this
+    tab: tab
+  )
+
+  controller = this
+  Ember.run.schedule('afterRender', ->
+    if controller.get('tabs.length') == 0
+      tab.show()
+    controller.get('tabs').pushObject(tab)
+  )
+
+  Ember.Handlebars.helpers.view.call(this, view, options)
+)
+
+
+Ember.Handlebars.registerHelper('travis-tabs', (options) ->
+  template   = options.fn
+  delete options.fn
+
+  @set('tabs', [])
+
+  view = Travis.TabsView.create(
+    controller: this
+    template: template
+  )
+
+  Ember.Handlebars.helpers.view.call(this, view, options)
+)
+
+Travis.SettingsMultiplierView = Ember.CollectionView.extend()
+
+createObjects = (path, offset) ->
+  segments = path.split('.')
+  if segments.length > offset
+    for i in [1..(segments.length - offset)]
+      path = segments.slice(0, i).join('.')
+      if Ember.isNone(Ember.get(this, path))
+        Ember.set(this, path, {})
+
+  return segments
+
+Ember.Handlebars.registerHelper('settings-multiplier', (path, options) ->
+  template   = options.fn
+  delete options.fn
+
+  parentsPath = getSettingsPath(options.data.view)
+  if parentsPath && parentsPath != ''
+    path = parentsPath + '.' + path
+
+  createObjects.call(this, path, 1)
+
+  if Ember.isNone(@get(path))
+    collection = [{}]
+    @set(path, collection)
+
+
+  itemViewClass = Ember.View.extend(
+    template: template,
+    controller: this,
+    tagName: 'li',
+    multiplier: true
+  )
+
+  view = Travis.SettingsMultiplierView.create(
+    contentBinding: 'controller.' + path
+    controller: this
+    tagName: 'ul'
+    itemViewClass: itemViewClass
+    fields: []
+    settingsPath: path
+  )
+
+  view.addObserver('content.length', ->
+    if @get('content.length') == 0
+      @get('content').pushObject({})
+  )
+
+  Ember.Handlebars.helpers.view.call(this, view, options)
+)
+
+Travis.FormSettingsView = Ember.View.extend Ember.TargetActionSupport,
+  target: Ember.computed.alias('controller')
+  actionContext: Ember.computed.alias('context'),
+  action: 'submit'
+  tagName: 'form'
+  submit: (event) ->
+    event.preventDefault()
+    @triggerAction()
+
+
+Ember.Handlebars.registerHelper('settings-form', (path, options) ->
+  if arguments.length == 1
+    options = path
+    path = 'settings'
+
+  view = Travis.FormSettingsView.create(
+    template: options.fn
+    controller: this
+    settingsPath: path
+  )
+
+  delete options.fn
+
+  Ember.Handlebars.helpers.view.call(this, view, options)
+)
+
+Ember.Handlebars.helper('settings-select', (options) ->
+  view = options.data.view
+  optionValues = options.hash.options
+  delete options.hash.options
+
+  originalPath = options.hash.value
+
+  parentsPath = getSettingsPath(view)
+  #TODO: such checks should also check parents, not only current context view
+  if !view.get('multiplier') && parentsPath && parentsPath != ''
+    originalPath = parentsPath + '.' + originalPath
+
+  fullPath = originalPath
+
+  if view.get('multiplier')
+    fullPath = 'view.content.' + fullPath
+
+  createObjects.call(this, fullPath, 1)
+
+  # TODO: setting a value here does not work and we still need
+  #       a valueBinding in the view, I'm not sure why
+  options.hash.value = fullPath
+
+  selectView = Ember.Select.create(
+    content: [''].pushObjects(optionValues.split(','))
+    controller: this
+    valueBinding: 'controller.' + fullPath
+  )
+
+  Ember.Handlebars.helpers.view.call(this, selectView, options)
+)
+
+
+
+Ember.Handlebars.helper('settings-remove-link', (options) ->
+  view = Ember.View.extend(
+    tagName: 'a'
+    attributeBindings: ['href', 'style']
+    href: '#'
+    style: (->
+      # TODO: do not assume that we're direct child
+      if @get('parentView.parentView.content.length') == 1
+        'display: none'
+    ).property('parentView.parentView.content.length')
+    template: Ember.Handlebars.compile('remove')
+    controller: this
+    click: (event) ->
+      event.preventDefault()
+
+      if content = @get('parentView.content')
+        @get('parentView.parentView.content').removeObject(content)
+  ).create()
+
+  Ember.Handlebars.helpers.view.call(this, view, options)
+)
+
+Ember.Handlebars.registerHelper('settings-add-link', (path, options) ->
+  parentsPath = getSettingsPath(options.data.view)
+  if parentsPath && parentsPath != ''
+    path = parentsPath + '.' + path
+
+  view = Ember.View.create(
+    tagName: 'a'
+    attributeBindings: ['href']
+    href: '#'
+    template: options.fn
+    controller: this
+    click: (event) ->
+      event.preventDefault()
+
+      if collection = @get('controller.' + path)
+        collection.pushObject({})
+  )
+
+  Ember.Handlebars.helpers.view.call(this, view, options)
+)
+
+getSettingsPath = (view) ->
+  settingsPaths = []
+  if settingsPath = view.get('settingsPath')
+    settingsPaths.pushObject settingsPath
+
+  parent = view
+  while parent = parent.get('parentView')
+    if settingsPath = parent.get('settingsPath')
+      settingsPaths.pushObject settingsPath
+
+  return settingsPaths.reverse().join('.')
+
+Ember.Handlebars.helper('settings-input', (options) ->
+  view = options.data.view
+
+  if options.hash.type == 'checkbox'
+    originalPath = options.hash.checked
+  else
+    originalPath = options.hash.value
+
+  parentsPath = getSettingsPath(view)
+  #TODO: such checks should also check parents, not only current context view
+  if !view.get('multiplier') && parentsPath && parentsPath != ''
+    originalPath = parentsPath + '.' + originalPath
+
+  fullPath = originalPath
+
+  if view.get('multiplier')
+    fullPath = 'view.content.' + fullPath
+
+  if options.hash.type != 'password'
+    createObjects.call(this, fullPath, 1)
+  else
+    createObjects.call(view, fullPath, 2)
+    content = view.get('content')
+    fullPath += ".value"
+    if Ember.isNone(Ember.get(content, originalPath))
+      Ember.set(content, originalPath, {})
+    Ember.set(content, originalPath + ".type", 'password')
+
+  if options.hash.type == 'checkbox'
+    options.hash.checked = fullPath
+  else
+    options.hash.value = fullPath
+  Ember.Handlebars.helpers.input.call(this, options)
+)
+
 Handlebars.registerHelper 'tipsy', (text, tip) ->
   safe '<span class="tool-tip" original-title="' + tip + '">' + text + '</span>'
 
diff --git a/assets/scripts/app/models/repo.coffee b/assets/scripts/app/models/repo.coffee
index f582fb10..ca098348 100644
--- a/assets/scripts/app/models/repo.coffee
+++ b/assets/scripts/app/models/repo.coffee
@@ -105,6 +105,13 @@ require 'travis/model'
   regenerateKey: (options) ->
     Travis.ajax.ajax '/repos/' + @get('id') + '/key', 'post', options
 
+  fetchSettings: ->
+    Travis.ajax.ajax('/repos/' + @get('id') + '/settings', 'get', forceAuth: true).then (data) ->
+      data['settings']
+
+  saveSettings: (settings) ->
+    Travis.ajax.ajax('/repos/' + @get('id') + '/settings', 'patch', data: { settings: settings })
+
 @Travis.Repo.reopenClass
   recent: ->
     @find()
diff --git a/assets/scripts/app/routes.coffee b/assets/scripts/app/routes.coffee
index 0f8186c1..ba390fb9 100644
--- a/assets/scripts/app/routes.coffee
+++ b/assets/scripts/app/routes.coffee
@@ -27,10 +27,10 @@ Ember.Route.reopen
         authController.set('redirected', true)
         @transitionTo('auth')
       else
-        throw(error)
+        @_super(error)
 
     renderNoOwnedRepos: ->
-      @render('no_owned_repos', outlet: 'main')
+      @render('no_owned_repos')
 
     renderFirstSync: ->
       @renderFirstSync()
@@ -41,6 +41,7 @@ Ember.Route.reopen
     afterSignOut: ->
       @afterSignOut()
 
+
   afterSignIn: ->
     if transition = Travis.auth.get('afterSignInTransition')
       Travis.auth.set('afterSignInTransition', null)
@@ -86,6 +87,7 @@ Travis.Router.reopen
 
     @_super.apply this, arguments
 
+
 Travis.Router.map ->
   @resource 'index', path: '/', ->
     @route 'current', path: '/'
@@ -103,6 +105,10 @@ Travis.Router.map ->
   @route 'auth', path: '/auth'
   @route 'notFound', path: '/not-found'
 
+  @resource 'profile.repo', path: '/profile/:owner/:name', ->
+    @resource 'profile.repo.settings', path: 'settings', ->
+      @route 'tab', path: ':tab'
+
   @resource 'profile', path: '/profile', ->
     @route 'index', path: '/'
     @resource 'account', path: '/:login', ->
@@ -125,7 +131,7 @@ Travis.SetupLastBuild = Ember.Mixin.create
     repo = @controllerFor('repo').get('repo')
     if repo && repo.get('isLoaded') && !repo.get('lastBuildId')
       Ember.run.next =>
-        @render('builds/not_found', outlet: 'pane', into: 'repo')
+        @render('builds/not_found', into: 'repo')
 
 Travis.GettingStartedRoute = Ember.Route.extend
   setupController: ->
@@ -158,7 +164,7 @@ Travis.FirstSyncRoute = Ember.Route.extend
 Travis.IndexCurrentRoute = Ember.Route.extend Travis.SetupLastBuild,
   renderTemplate: ->
     @render 'repo'
-    @render 'build', outlet: 'pane', into: 'repo'
+    @render 'build', into: 'repo'
 
   setupController: ->
     @_super.apply this, arguments
@@ -175,7 +181,7 @@ Travis.IndexCurrentRoute = Ember.Route.extend Travis.SetupLastBuild,
 
 Travis.AbstractBuildsRoute = Ember.Route.extend
   renderTemplate: ->
-    @render 'builds', outlet: 'pane', into: 'repo'
+    @render 'builds', into: 'repo'
 
   setupController: ->
     @controllerFor('repo').activate(@get('contentType'))
@@ -200,7 +206,7 @@ Travis.BranchesRoute = Travis.AbstractBuildsRoute.extend(contentType: 'branches'
 
 Travis.BuildRoute = Ember.Route.extend
   renderTemplate: ->
-    @render 'build', outlet: 'pane', into: 'repo'
+    @render 'build', into: 'repo'
 
   serialize: (model, params) ->
     id = if model.get then model.get('id') else model
@@ -221,7 +227,7 @@ Travis.BuildRoute = Ember.Route.extend
 
 Travis.JobRoute = Ember.Route.extend
   renderTemplate: ->
-    @render 'job', outlet: 'pane', into: 'repo'
+    @render 'job', into: 'repo'
 
   serialize: (model, params) ->
     id = if model.get then model.get('id') else model
@@ -248,7 +254,7 @@ Travis.RepoIndexRoute = Ember.Route.extend Travis.SetupLastBuild,
     @controllerFor('repo').activate('current')
 
   renderTemplate: ->
-    @render 'build', outlet: 'pane', into: 'repo'
+    @render 'build', into: 'repo'
 
 Travis.RepoRoute = Ember.Route.extend
   renderTemplate: ->
@@ -267,7 +273,6 @@ Travis.RepoRoute = Ember.Route.extend
 
   model: (params) ->
     slug = "#{params.owner}/#{params.name}"
-
     Travis.Repo.fetchBySlug(slug)
 
   actions:
@@ -324,7 +329,7 @@ Travis.ProfileRoute = Ember.Route.extend
     @render 'top', outlet: 'top'
     @render 'accounts', outlet: 'left'
     @render 'flash', outlet: 'flash'
-    @render 'profile'
+    @_super.apply(this, arguments)
 
 Travis.ProfileIndexRoute = Ember.Route.extend
   setupController: ->
@@ -392,3 +397,29 @@ Travis.AuthRoute = Ember.Route.extend
 
   deactivate: ->
     @controllerFor('auth').set('redirected', false)
+
+Travis.ProfileRepoRoute = Travis.ProfileRoute.extend
+  setupController: (controller, model) ->
+    # TODO: if repo is just a data hash with id and slug load it
+    #       as incomplete record
+    model = Travis.Repo.find(model.id) if model && !model.get
+    @_super(controller, model)
+
+    controller.set('content', model)
+
+  serialize: (repo) ->
+    slug = if repo.get then repo.get('slug') else repo.slug
+    [owner, name] = slug.split('/')
+    { owner: owner, name: name }
+
+  model: (params) ->
+    slug = "#{params.owner}/#{params.name}"
+    Travis.Repo.fetchBySlug(slug)
+
+Travis.ProfileRepoSettingsRoute = Ember.Route.extend
+  setupController: (controller, model) ->
+    controller.set('settings', model)
+
+  model: ->
+   repo = @modelFor('profileRepo')
+   repo.fetchSettings()
diff --git a/assets/scripts/app/templates/layouts/profile.hbs b/assets/scripts/app/templates/layouts/profile.hbs
index 6ce4e370..e149cc94 100644
--- a/assets/scripts/app/templates/layouts/profile.hbs
+++ b/assets/scripts/app/templates/layouts/profile.hbs
@@ -8,7 +8,7 @@
 
 <div id="main">
   {{outlet flash}}
-  {{outlet main}}
+  {{outlet}}
 </div>
 
 <div id="right">
diff --git a/assets/scripts/app/templates/profile/repo.hbs b/assets/scripts/app/templates/profile/repo.hbs
new file mode 100644
index 00000000..43d6cf6c
--- /dev/null
+++ b/assets/scripts/app/templates/profile/repo.hbs
@@ -0,0 +1,3 @@
+<h1>Settings: {{slug}}</h1>
+
+{{outlet}}
diff --git a/assets/scripts/app/templates/profile/repo/settings.hbs b/assets/scripts/app/templates/profile/repo/settings.hbs
new file mode 100644
index 00000000..c16eee7e
--- /dev/null
+++ b/assets/scripts/app/templates/profile/repo/settings.hbs
@@ -0,0 +1,29 @@
+{{#travis-tabs}}
+  {{#travis-tab "general" "General Settings"}}
+    {{#settings-form}}
+      <p>
+        {{settings-input checked=builds_only_with_travis_yml type="checkbox" id="builds-only-with-travis-yml"}}
+          <label for="builds-only-with-travis-yml">Build only commits with .travis.yml file</label>
+      </p>
+
+      <p>
+        {{settings-input checked=build_pushes type="checkbox" id="build-pushes"}}
+          <label for="build-pushes">Build pushes</label>
+      </p>
+
+      <p>
+        {{settings-input checked=build_pull_requests type="checkbox" id="build-pull-requests"}}
+          <label for="build-pull-requests">Build pull requests</label>
+      </p>
+
+      <p>
+        {{#if saving}}
+          <span class="saving">Saving</span>
+        {{else}}
+          <button type="submit">Submit</button>
+        {{/if}}
+      </p>
+    {{/settings-form}}
+  {{/travis-tab}}
+{{/travis-tabs}}
+
diff --git a/assets/scripts/app/templates/repos/show.hbs b/assets/scripts/app/templates/repos/show.hbs
index 7d7d3cf7..0c35e126 100644
--- a/assets/scripts/app/templates/repos/show.hbs
+++ b/assets/scripts/app/templates/repos/show.hbs
@@ -17,7 +17,7 @@
       {{/with}}
 
       <div class="tab">
-        {{outlet pane}}
+        {{outlet}}
       </div>
     {{else}}
       <div class="loading"><span>Loading</span></div>
diff --git a/assets/scripts/app/views.coffee b/assets/scripts/app/views.coffee
index 7ddf2bc8..e9017b1c 100644
--- a/assets/scripts/app/views.coffee
+++ b/assets/scripts/app/views.coffee
@@ -45,6 +45,7 @@ Travis.FirstSyncView = Travis.View.extend
         )
       , Travis.config.syncingPageRedirectionTime
 
+
 require 'views/accounts'
 require 'views/application'
 require 'views/build'
diff --git a/assets/scripts/lib/travis/ajax.coffee b/assets/scripts/lib/travis/ajax.coffee
index 00c32f01..fcd82eed 100644
--- a/assets/scripts/lib/travis/ajax.coffee
+++ b/assets/scripts/lib/travis/ajax.coffee
@@ -23,13 +23,14 @@ Travis.ajax = Em.Object.create
     !result
 
   ajax: (url, method, options) ->
+    method = method || "GET"
     method = method.toUpperCase()
 
     endpoint = Travis.config.api_endpoint || ''
     options = options || {}
 
     token = Travis.sessionStorage.getItem('travis.token')
-    if token && Travis.ajax.needsAuth(method, url)
+    if token && (Travis.ajax.needsAuth(method, url) || options.forceAuth)
       options.headers ||= {}
       options.headers['Authorization'] ||= "token #{token}"
 
diff --git a/assets/scripts/spec/unit/settings_helpers_spec.coffee b/assets/scripts/spec/unit/settings_helpers_spec.coffee
new file mode 100644
index 00000000..12616e75
--- /dev/null
+++ b/assets/scripts/spec/unit/settings_helpers_spec.coffee
@@ -0,0 +1,28 @@
+view = null
+
+module "Handlebars helpers - settings-input",
+  setup: ->
+    Ember.run -> Travis.advanceReadiness()
+
+test "settings input allows to bind to nested objects", ->
+  controller = Ember.Object.create()
+  view = Ember.View.create(
+    controller: controller
+    template: Ember.Handlebars.compile("{{input value=foo}} {{controller}}")
+  )
+
+  Ember.run ->
+    view.append()
+
+
+  Ember.run ->
+    controller.set('foo', 'bar')
+
+  Ember.run.sync()
+
+  input = view.$('input')
+  input.val('a value').change()
+  Ember.run.sync()
+
+  console.log controller.get('foo')
+
diff --git a/assets/styles/app/loading.sass b/assets/styles/app/loading.sass
index 8278162e..2f2a2584 100644
--- a/assets/styles/app/loading.sass
+++ b/assets/styles/app/loading.sass
@@ -11,7 +11,7 @@
   .loading
     display: none
 
-span.loading
+span.loading, span.saving
   padding: 0 25px 0 0
   font-size: $font-size-small
   color: $color-text-lighter

From ac6b394ec4c5e18c5855dc8e5d58d38a5a6afd1b Mon Sep 17 00:00:00 2001
From: Piotr Sarnacki <drogus@gmail.com>
Date: Wed, 11 Dec 2013 12:39:52 +0100
Subject: [PATCH 02/11] Display settings link in the cog menu

---
 assets/scripts/app/templates/repos/show/tools.hbs | 6 ++++++
 assets/scripts/app/views/repo/show.coffee         | 9 +++++++++
 2 files changed, 15 insertions(+)

diff --git a/assets/scripts/app/templates/repos/show/tools.hbs b/assets/scripts/app/templates/repos/show/tools.hbs
index b2541b3c..f858248e 100644
--- a/assets/scripts/app/templates/repos/show/tools.hbs
+++ b/assets/scripts/app/templates/repos/show/tools.hbs
@@ -11,6 +11,12 @@
         </a>
       </li>
     {{/if}}
+    {{#if view.displaySettingsLink}}
+      <li>
+        {{#linkTo "profile.repo.settings" view.repo}}Settings{{/linkTo}}
+      </li>
+    {{/if}}
+
   </ul>
   {{#if view.displayStatusImages}}
   <a href="#" id="status-image-popup" name="status-images" class="open-popup" {{action "statusImages" target="view"}}>
diff --git a/assets/scripts/app/views/repo/show.coffee b/assets/scripts/app/views/repo/show.coffee
index a27a0b29..1d008c24 100644
--- a/assets/scripts/app/views/repo/show.coffee
+++ b/assets/scripts/app/views/repo/show.coffee
@@ -115,6 +115,11 @@ Travis.reopen
         permissions.contains parseInt(@get('repo.id'))
     ).property('currentUser.permissions.length', 'repo.id')
 
+    hasPushPermission: (->
+      if permissions = @get('currentUser.pushPermissions')
+        permissions.contains parseInt(@get('repo.id'))
+    ).property('currentUser.pushPermissions.length', 'repo.id')
+
     hasAdminPermission: (->
       if permissions = @get('currentUser.adminPermissions')
         permissions.contains parseInt(@get('repo.id'))
@@ -124,6 +129,10 @@ Travis.reopen
       Travis.Urls.statusImage(@get('slug'))
     ).property('slug')
 
+    displaySettingsLink: (->
+      @get('hasPushPermission')
+    ).property('hasPushPermission')
+
     displayStatusImages: (->
       @get('hasPermission')
     ).property('hasPermission')

From 8482c4c2863f3bfbfe2cc28d83f315814a67c1f7 Mon Sep 17 00:00:00 2001
From: Piotr Sarnacki <drogus@gmail.com>
Date: Wed, 11 Dec 2013 13:34:06 +0100
Subject: [PATCH 03/11] Move settings out of the profile page

---
 assets/scripts/app/controllers.coffee         | 14 +++++------
 assets/scripts/app/helpers/handlebars.coffee  |  3 ++-
 assets/scripts/app/routes.coffee              | 25 ++++++++-----------
 .../templates/{profile => }/repo/settings.hbs |  2 ++
 assets/scripts/app/templates/repos/show.hbs   |  1 -
 .../app/templates/repos/show/tools.hbs        |  2 +-
 6 files changed, 22 insertions(+), 25 deletions(-)
 rename assets/scripts/app/templates/{profile => }/repo/settings.hbs (96%)

diff --git a/assets/scripts/app/controllers.coffee b/assets/scripts/app/controllers.coffee
index bcec80fd..5a267beb 100644
--- a/assets/scripts/app/controllers.coffee
+++ b/assets/scripts/app/controllers.coffee
@@ -48,18 +48,16 @@ Travis.FirstSyncController = Em.Controller.extend
 
   isSyncing: Ember.computed.alias('user.isSyncing')
 
-Travis.ProfileRepoController = Em.ObjectController.extend()
-Travis.ProfileRepoSettingsTabController = Em.ObjectController.extend()
-
-Travis.ProfileRepoSettingsController = Em.Controller.extend
-  needs: ['profileRepoSettingsTab', 'profileRepo']
-  tab: Ember.computed.alias('controllers.profileRepoSettingsTab.model.tab')
-  repo: Ember.computed.alias('controllers.profileRepo.content')
+Travis.RepoSettingsTabController = Em.ObjectController.extend()
+Travis.RepoSettingsController = Em.ObjectController.extend
+  needs: ['repoSettingsTab']
+  tab: Ember.computed.alias('controllers.repoSettingsTab.model.tab')
+  settings: Ember.computed.alias('model.settings')
 
   submit: ->
     @set('saving', true)
     self = this
-    @get('repo').saveSettings(@get('settings')).then ->
+    @get('model').saveSettings(@get('settings')).then ->
       self.set('saving', false)
       Travis.flash(success: 'Settings were saved successfully')
     , ->
diff --git a/assets/scripts/app/helpers/handlebars.coffee b/assets/scripts/app/helpers/handlebars.coffee
index 63e6cc15..1074da90 100644
--- a/assets/scripts/app/helpers/handlebars.coffee
+++ b/assets/scripts/app/helpers/handlebars.coffee
@@ -34,11 +34,12 @@ Travis.TabsView = Ember.View.extend
 
     tab.show() unless tab.get('visible')
 
+  # TODO: remove hardcoded link
   layout: Ember.Handlebars.compile(
     '<ul class="tabs">' +
     '  {{#each tab in tabs}}' +
     '    <li {{bindAttr class="tab.visible:active"}}>' +
-    '      <h5>{{#linkTo "profile.repo.settings.tab" tab.id}}{{tab.name}}{{/linkTo}}</h5>' +
+    '      <h5>{{#linkTo "repo.settings.tab" tab.id}}{{tab.name}}{{/linkTo}}</h5>' +
     '    </li>' +
     '  {{/each}}' +
     '</ul>' +
diff --git a/assets/scripts/app/routes.coffee b/assets/scripts/app/routes.coffee
index ba390fb9..fedc4127 100644
--- a/assets/scripts/app/routes.coffee
+++ b/assets/scripts/app/routes.coffee
@@ -99,16 +99,17 @@ Travis.Router.map ->
       @resource 'pullRequests', path: '/pull_requests'
       @resource 'branches', path: '/branches'
 
+    # this can't be nested in repo, because we want a set of different
+    # templates rendered for settings (for example no "current", "builds", ... tabs)
+    @resource 'repo.settings', path: '/:owner/:name/settings', ->
+      @route 'tab', path: ':tab'
+
   @route 'getting_started'
   @route 'first_sync'
   @route 'stats', path: '/stats'
   @route 'auth', path: '/auth'
   @route 'notFound', path: '/not-found'
 
-  @resource 'profile.repo', path: '/profile/:owner/:name', ->
-    @resource 'profile.repo.settings', path: 'settings', ->
-      @route 'tab', path: ':tab'
-
   @resource 'profile', path: '/profile', ->
     @route 'index', path: '/'
     @resource 'account', path: '/:login', ->
@@ -398,15 +399,13 @@ Travis.AuthRoute = Ember.Route.extend
   deactivate: ->
     @controllerFor('auth').set('redirected', false)
 
-Travis.ProfileRepoRoute = Travis.ProfileRoute.extend
+Travis.RepoSettingsRoute = Ember.Route.extend
   setupController: (controller, model) ->
     # TODO: if repo is just a data hash with id and slug load it
     #       as incomplete record
     model = Travis.Repo.find(model.id) if model && !model.get
     @_super(controller, model)
 
-    controller.set('content', model)
-
   serialize: (repo) ->
     slug = if repo.get then repo.get('slug') else repo.slug
     [owner, name] = slug.split('/')
@@ -416,10 +415,8 @@ Travis.ProfileRepoRoute = Travis.ProfileRoute.extend
     slug = "#{params.owner}/#{params.name}"
     Travis.Repo.fetchBySlug(slug)
 
-Travis.ProfileRepoSettingsRoute = Ember.Route.extend
-  setupController: (controller, model) ->
-    controller.set('settings', model)
-
-  model: ->
-   repo = @modelFor('profileRepo')
-   repo.fetchSettings()
+  afterModel: (repo) ->
+    # I'm using afterModel to fetch settings, because model is not always called.
+    # If link-to already provides a model, it will be just set as a route context.
+    repo.fetchSettings().then (settings) ->
+      repo.set('settings', settings)
diff --git a/assets/scripts/app/templates/profile/repo/settings.hbs b/assets/scripts/app/templates/repo/settings.hbs
similarity index 96%
rename from assets/scripts/app/templates/profile/repo/settings.hbs
rename to assets/scripts/app/templates/repo/settings.hbs
index c16eee7e..fcab8474 100644
--- a/assets/scripts/app/templates/profile/repo/settings.hbs
+++ b/assets/scripts/app/templates/repo/settings.hbs
@@ -1,3 +1,5 @@
+<h1>Settings: {{slug}}</h1>
+
 {{#travis-tabs}}
   {{#travis-tab "general" "General Settings"}}
     {{#settings-form}}
diff --git a/assets/scripts/app/templates/repos/show.hbs b/assets/scripts/app/templates/repos/show.hbs
index 0c35e126..07e62096 100644
--- a/assets/scripts/app/templates/repos/show.hbs
+++ b/assets/scripts/app/templates/repos/show.hbs
@@ -24,4 +24,3 @@
     {{/if}}
   {{/if}}
 </div>
-
diff --git a/assets/scripts/app/templates/repos/show/tools.hbs b/assets/scripts/app/templates/repos/show/tools.hbs
index f858248e..1dba6858 100644
--- a/assets/scripts/app/templates/repos/show/tools.hbs
+++ b/assets/scripts/app/templates/repos/show/tools.hbs
@@ -13,7 +13,7 @@
     {{/if}}
     {{#if view.displaySettingsLink}}
       <li>
-        {{#linkTo "profile.repo.settings" view.repo}}Settings{{/linkTo}}
+        {{#linkTo "repo.settings" view.repo}}Settings{{/linkTo}}
       </li>
     {{/if}}
 

From 3a15b037da9daaa77c95fe18f7228cf8f9037855 Mon Sep 17 00:00:00 2001
From: Piotr Sarnacki <drogus@gmail.com>
Date: Thu, 12 Dec 2013 14:55:01 +0100
Subject: [PATCH 04/11] Link to repo settings from a hooks page

---
 assets/scripts/app/models/hook.coffee               | 10 ++++++++++
 assets/scripts/app/templates/profile/tabs/hooks.hbs |  2 +-
 assets/styles/profile/hooks.sass                    |  4 ++--
 3 files changed, 13 insertions(+), 3 deletions(-)

diff --git a/assets/scripts/app/models/hook.coffee b/assets/scripts/app/models/hook.coffee
index f35166ac..58fff636 100644
--- a/assets/scripts/app/models/hook.coffee
+++ b/assets/scripts/app/models/hook.coffee
@@ -27,3 +27,13 @@ require 'travis/model'
     return if @get('isSaving')
     @set 'active', !@get('active')
     @save()
+
+  repo: (->
+    # I don't want to make an ajax request for each repository showed in profile,
+    # especially, because most of them does not have any builds anyway. That's why
+    # I add an info which we have here to the store - this will allow to display
+    # a link to the repo and if more info is needed, it will be requested when the
+    # link is used
+    Travis.loadOrMerge(Travis.Repo, @getProperties('id', 'slug', 'name', 'ownerName'), skipIfExists: true)
+    Travis.Repo.find(@get('id'))
+  ).property('id')
diff --git a/assets/scripts/app/templates/profile/tabs/hooks.hbs b/assets/scripts/app/templates/profile/tabs/hooks.hbs
index dc920183..0cae8362 100644
--- a/assets/scripts/app/templates/profile/tabs/hooks.hbs
+++ b/assets/scripts/app/templates/profile/tabs/hooks.hbs
@@ -23,7 +23,7 @@
           <p class="description">{{hook.description}}</p>
 
           <div class="controls">
-            <a {{bindAttr href="hook.urlGithubAdmin"}} class="github-admin tool-tip" title="Github service hooks admin page"></a>
+            {{#link-to "repo.settings" hook.repo class="repo-settings-icon tool-tip" title="Repository settings"}}{{/link-to}}
             <a {{action "toggle" hook}} class="switch">
               {{#if hook.active}}
                 ON
diff --git a/assets/styles/profile/hooks.sass b/assets/styles/profile/hooks.sass
index 5566b966..6c003d4a 100644
--- a/assets/styles/profile/hooks.sass
+++ b/assets/styles/profile/hooks.sass
@@ -57,14 +57,14 @@
         float: left
         display: block
 
-    .github-admin
+    .repo-settings-icon
       // Overriding an earlier definition above, which is probably
       // obsolete. TODO: Remove if so.
       position: relative
       height: 20px
       width: 20px
       padding-right: 0
-      background: inline-image('ui/github-admin.png') no-repeat 3px 4px
+      background: inline-image('ui/repo-settings.png') no-repeat 3px 4px
 
     .switch
       position: relative

From c8575b3f636f3abea1649ba42efaaaa9e2230595 Mon Sep 17 00:00:00 2001
From: Piotr Sarnacki <drogus@gmail.com>
Date: Thu, 12 Dec 2013 14:55:43 +0100
Subject: [PATCH 05/11] Forgot to add repo-settings.png

---
 assets/images/ui/repo-settings.png | Bin 0 -> 231 bytes
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 assets/images/ui/repo-settings.png

diff --git a/assets/images/ui/repo-settings.png b/assets/images/ui/repo-settings.png
new file mode 100644
index 0000000000000000000000000000000000000000..96fe3fa5e0cfe6dd32fab112f8922132d865bc48
GIT binary patch
literal 231
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbL!L4Z$)>#}9bwr}6QWy_YGJ9pl?
zb?eflOLy+vdGO%D@#Dw$@8AFI*|S%#UY$F4?*IS)D^{$ycI{fl1;Ix^y?iA>e!)ON
z1YmGIaOVV2Cc@LjF+}2W?fKJu4F()87jH8y-L~le|LyO783ayKPm$=j{()I*Ut`j5
zw?*zhm}cJ6@i$;N#lSr2#R-ds;yHU&neD1H4m{l)u`SHgJGlRav`foE=^azv=gaR9
W;977mIp#Ic4hBzGKbLh*2~7Z_VQ<<1

literal 0
HcmV?d00001


From 8aafb8d4e6c1a9be8c1f6d781ef19de63dd2198e Mon Sep 17 00:00:00 2001
From: Piotr Sarnacki <drogus@gmail.com>
Date: Thu, 12 Dec 2013 15:24:30 +0100
Subject: [PATCH 06/11] Bring back 'outlet pane' and 'outlet pane'

For some reason (I haven't had time to debug it) when we don't use named
outlet rendering "into" does not work in certain circumstances (for
example in index current view, where repos are changed automatically).
---
 assets/scripts/app/routes.coffee                 | 14 +++++++-------
 assets/scripts/app/templates/layouts/profile.hbs |  2 +-
 assets/scripts/app/templates/repos/show.hbs      |  2 +-
 3 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/assets/scripts/app/routes.coffee b/assets/scripts/app/routes.coffee
index fedc4127..06a20676 100644
--- a/assets/scripts/app/routes.coffee
+++ b/assets/scripts/app/routes.coffee
@@ -30,7 +30,7 @@ Ember.Route.reopen
         @_super(error)
 
     renderNoOwnedRepos: ->
-      @render('no_owned_repos')
+      @render('no_owned_repos', outlet: 'main')
 
     renderFirstSync: ->
       @renderFirstSync()
@@ -132,7 +132,7 @@ Travis.SetupLastBuild = Ember.Mixin.create
     repo = @controllerFor('repo').get('repo')
     if repo && repo.get('isLoaded') && !repo.get('lastBuildId')
       Ember.run.next =>
-        @render('builds/not_found', into: 'repo')
+        @render('builds/not_found', into: 'repo', outlet: 'pane')
 
 Travis.GettingStartedRoute = Ember.Route.extend
   setupController: ->
@@ -165,7 +165,7 @@ Travis.FirstSyncRoute = Ember.Route.extend
 Travis.IndexCurrentRoute = Ember.Route.extend Travis.SetupLastBuild,
   renderTemplate: ->
     @render 'repo'
-    @render 'build', into: 'repo'
+    @render 'build', into: 'repo', outlet: 'pane'
 
   setupController: ->
     @_super.apply this, arguments
@@ -182,7 +182,7 @@ Travis.IndexCurrentRoute = Ember.Route.extend Travis.SetupLastBuild,
 
 Travis.AbstractBuildsRoute = Ember.Route.extend
   renderTemplate: ->
-    @render 'builds', into: 'repo'
+    @render 'builds', into: 'repo', outlet: 'pane'
 
   setupController: ->
     @controllerFor('repo').activate(@get('contentType'))
@@ -207,7 +207,7 @@ Travis.BranchesRoute = Travis.AbstractBuildsRoute.extend(contentType: 'branches'
 
 Travis.BuildRoute = Ember.Route.extend
   renderTemplate: ->
-    @render 'build', into: 'repo'
+    @render 'build', into: 'repo', outlet: 'pane'
 
   serialize: (model, params) ->
     id = if model.get then model.get('id') else model
@@ -228,7 +228,7 @@ Travis.BuildRoute = Ember.Route.extend
 
 Travis.JobRoute = Ember.Route.extend
   renderTemplate: ->
-    @render 'job', into: 'repo'
+    @render 'job', into: 'repo', outlet: 'pane'
 
   serialize: (model, params) ->
     id = if model.get then model.get('id') else model
@@ -255,7 +255,7 @@ Travis.RepoIndexRoute = Ember.Route.extend Travis.SetupLastBuild,
     @controllerFor('repo').activate('current')
 
   renderTemplate: ->
-    @render 'build', into: 'repo'
+    @render 'build', into: 'repo', outlet: 'pane'
 
 Travis.RepoRoute = Ember.Route.extend
   renderTemplate: ->
diff --git a/assets/scripts/app/templates/layouts/profile.hbs b/assets/scripts/app/templates/layouts/profile.hbs
index e149cc94..6ce4e370 100644
--- a/assets/scripts/app/templates/layouts/profile.hbs
+++ b/assets/scripts/app/templates/layouts/profile.hbs
@@ -8,7 +8,7 @@
 
 <div id="main">
   {{outlet flash}}
-  {{outlet}}
+  {{outlet main}}
 </div>
 
 <div id="right">
diff --git a/assets/scripts/app/templates/repos/show.hbs b/assets/scripts/app/templates/repos/show.hbs
index 07e62096..3b804bba 100644
--- a/assets/scripts/app/templates/repos/show.hbs
+++ b/assets/scripts/app/templates/repos/show.hbs
@@ -17,7 +17,7 @@
       {{/with}}
 
       <div class="tab">
-        {{outlet}}
+        {{outlet pane}}
       </div>
     {{else}}
       <div class="loading"><span>Loading</span></div>

From c48f70a38bc995bbeca1fc0f60d62e0b450e9364 Mon Sep 17 00:00:00 2001
From: Piotr Sarnacki <drogus@gmail.com>
Date: Wed, 18 Dec 2013 01:55:13 +0100
Subject: [PATCH 07/11] Finish test for settings-input

---
 .../spec/unit/settings_helpers_spec.coffee    | 20 +++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/assets/scripts/spec/unit/settings_helpers_spec.coffee b/assets/scripts/spec/unit/settings_helpers_spec.coffee
index 12616e75..bea181a1 100644
--- a/assets/scripts/spec/unit/settings_helpers_spec.coffee
+++ b/assets/scripts/spec/unit/settings_helpers_spec.coffee
@@ -8,21 +8,21 @@ test "settings input allows to bind to nested objects", ->
   controller = Ember.Object.create()
   view = Ember.View.create(
     controller: controller
-    template: Ember.Handlebars.compile("{{input value=foo}} {{controller}}")
+    template: Ember.Handlebars.compile("{{settings-input value=foo.bar.baz}}")
   )
 
   Ember.run ->
-    view.append()
+    view.appendTo($("#ember-testing")[0])
 
 
-  Ember.run ->
-    controller.set('foo', 'bar')
-
-  Ember.run.sync()
-
   input = view.$('input')
-  input.val('a value').change()
-  Ember.run.sync()
 
-  console.log controller.get('foo')
+  Ember.run ->
+    input.val('a value').change()
 
+  equal(controller.get('foo.bar.baz'), 'a value')
+
+  Ember.run ->
+    controller.set('foo.bar.baz', 'a new value')
+
+  equal(input.val(), 'a new value')

From cc026b75a8d483878a2241943b15b2c279606d13 Mon Sep 17 00:00:00 2001
From: Piotr Sarnacki <drogus@gmail.com>
Date: Wed, 18 Dec 2013 11:19:06 +0100
Subject: [PATCH 08/11] Make settings header link to repo page

---
 .../scripts/app/templates/repo/settings.hbs   | 55 ++++++++++---------
 1 file changed, 28 insertions(+), 27 deletions(-)

diff --git a/assets/scripts/app/templates/repo/settings.hbs b/assets/scripts/app/templates/repo/settings.hbs
index fcab8474..e721c254 100644
--- a/assets/scripts/app/templates/repo/settings.hbs
+++ b/assets/scripts/app/templates/repo/settings.hbs
@@ -1,31 +1,32 @@
-<h1>Settings: {{slug}}</h1>
+<div id="repo">
+  <h3>Settings: {{#linkTo "repo" this}}{{slug}}{{/linkTo}}</h3>
 
-{{#travis-tabs}}
-  {{#travis-tab "general" "General Settings"}}
-    {{#settings-form}}
-      <p>
-        {{settings-input checked=builds_only_with_travis_yml type="checkbox" id="builds-only-with-travis-yml"}}
-          <label for="builds-only-with-travis-yml">Build only commits with .travis.yml file</label>
-      </p>
+  {{#travis-tabs}}
+    {{#travis-tab "general" "General Settings"}}
+      {{#settings-form}}
+        <p>
+          {{settings-input checked=builds_only_with_travis_yml type="checkbox" id="builds-only-with-travis-yml"}}
+            <label for="builds-only-with-travis-yml">Build only commits with .travis.yml file</label>
+        </p>
 
-      <p>
-        {{settings-input checked=build_pushes type="checkbox" id="build-pushes"}}
-          <label for="build-pushes">Build pushes</label>
-      </p>
+        <p>
+          {{settings-input checked=build_pushes type="checkbox" id="build-pushes"}}
+            <label for="build-pushes">Build pushes</label>
+        </p>
 
-      <p>
-        {{settings-input checked=build_pull_requests type="checkbox" id="build-pull-requests"}}
-          <label for="build-pull-requests">Build pull requests</label>
-      </p>
-
-      <p>
-        {{#if saving}}
-          <span class="saving">Saving</span>
-        {{else}}
-          <button type="submit">Submit</button>
-        {{/if}}
-      </p>
-    {{/settings-form}}
-  {{/travis-tab}}
-{{/travis-tabs}}
+        <p>
+          {{settings-input checked=build_pull_requests type="checkbox" id="build-pull-requests"}}
+            <label for="build-pull-requests">Build pull requests</label>
+        </p>
 
+        <p>
+          {{#if saving}}
+            <span class="saving">Saving</span>
+          {{else}}
+            <button type="submit">Submit</button>
+          {{/if}}
+        </p>
+      {{/settings-form}}
+    {{/travis-tab}}
+  {{/travis-tabs}}
+</div>

From c8cc13df5906780161151cce1f1c455c73b26e3b Mon Sep 17 00:00:00 2001
From: Piotr Sarnacki <drogus@gmail.com>
Date: Wed, 8 Jan 2014 12:17:57 +0100
Subject: [PATCH 09/11] Change submit to save

---
 assets/scripts/app/templates/repo/settings.hbs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/assets/scripts/app/templates/repo/settings.hbs b/assets/scripts/app/templates/repo/settings.hbs
index e721c254..80737cf4 100644
--- a/assets/scripts/app/templates/repo/settings.hbs
+++ b/assets/scripts/app/templates/repo/settings.hbs
@@ -23,7 +23,7 @@
           {{#if saving}}
             <span class="saving">Saving</span>
           {{else}}
-            <button type="submit">Submit</button>
+            <button type="submit">Save</button>
           {{/if}}
         </p>
       {{/settings-form}}

From 357b176f93085bf55311301fa2c07e675dda1e09 Mon Sep 17 00:00:00 2001
From: Piotr Sarnacki <drogus@gmail.com>
Date: Tue, 21 Jan 2014 18:46:36 +0100
Subject: [PATCH 10/11] Use switches on settings pane

---
 assets/scripts/app/components.coffee          | 16 ++++++++++++---
 assets/scripts/app/controllers.coffee         |  2 +-
 .../templates/components/travis-switch.hbs    |  2 +-
 .../scripts/app/templates/repo/settings.hbs   | 20 ++++++-------------
 4 files changed, 21 insertions(+), 19 deletions(-)

diff --git a/assets/scripts/app/components.coffee b/assets/scripts/app/components.coffee
index da423edd..09a177fb 100644
--- a/assets/scripts/app/components.coffee
+++ b/assets/scripts/app/components.coffee
@@ -1,9 +1,19 @@
 Travis.TravisSwitchComponent = Ember.Component.extend
   tagName: 'a'
   classNames: ['travis-switch']
-  classNameBindings: ['active']
+  classNameBindings: ['_active:active']
 
-  activeBinding: 'target.active'
+  # TODO: how to handle overriding properties to
+  #       avoid naming it _action?
+  _active: (->
+    @get('target.active') || @get('active')
+  ).property('target.active', 'active')
 
   click: ->
-    @sendAction('action', @get('target'))
+    if target = @get('target')
+      @set('target.active', !@get('target.active'))
+    else
+      @set('active', !@get('active'))
+    # allow for bindings to propagate
+    Ember.run.next this, ->
+      @sendAction('action', target)
diff --git a/assets/scripts/app/controllers.coffee b/assets/scripts/app/controllers.coffee
index 5a267beb..84fa90e7 100644
--- a/assets/scripts/app/controllers.coffee
+++ b/assets/scripts/app/controllers.coffee
@@ -54,7 +54,7 @@ Travis.RepoSettingsController = Em.ObjectController.extend
   tab: Ember.computed.alias('controllers.repoSettingsTab.model.tab')
   settings: Ember.computed.alias('model.settings')
 
-  submit: ->
+  save: ->
     @set('saving', true)
     self = this
     @get('model').saveSettings(@get('settings')).then ->
diff --git a/assets/scripts/app/templates/components/travis-switch.hbs b/assets/scripts/app/templates/components/travis-switch.hbs
index e7b20837..18617602 100644
--- a/assets/scripts/app/templates/components/travis-switch.hbs
+++ b/assets/scripts/app/templates/components/travis-switch.hbs
@@ -1,4 +1,4 @@
-{{#if active}}
+{{#if _active}}
   ON
 {{else}}
   OFF
diff --git a/assets/scripts/app/templates/repo/settings.hbs b/assets/scripts/app/templates/repo/settings.hbs
index 80737cf4..db445f93 100644
--- a/assets/scripts/app/templates/repo/settings.hbs
+++ b/assets/scripts/app/templates/repo/settings.hbs
@@ -5,26 +5,18 @@
     {{#travis-tab "general" "General Settings"}}
       {{#settings-form}}
         <p>
-          {{settings-input checked=builds_only_with_travis_yml type="checkbox" id="builds-only-with-travis-yml"}}
-            <label for="builds-only-with-travis-yml">Build only commits with .travis.yml file</label>
+          {{travis-switch action="save" active=settings.builds_only_with_travis_yml}}
+          Build only commits with .travis.yml file
         </p>
 
         <p>
-          {{settings-input checked=build_pushes type="checkbox" id="build-pushes"}}
-            <label for="build-pushes">Build pushes</label>
+          {{travis-switch action="save" active=settings.build_pushes}}
+          Build pushes
         </p>
 
         <p>
-          {{settings-input checked=build_pull_requests type="checkbox" id="build-pull-requests"}}
-            <label for="build-pull-requests">Build pull requests</label>
-        </p>
-
-        <p>
-          {{#if saving}}
-            <span class="saving">Saving</span>
-          {{else}}
-            <button type="submit">Save</button>
-          {{/if}}
+          {{travis-switch action="save" active=settings.build_pull_requests}}
+          Build pull requests
         </p>
       {{/settings-form}}
     {{/travis-tab}}

From 7d9db8cbaef33e3aa9bfcc263a8d52b23025a962 Mon Sep 17 00:00:00 2001
From: Justine Arreche <justine@neo.com>
Date: Fri, 24 Jan 2014 10:04:12 -0500
Subject: [PATCH 11/11] restyled general settings

---
 assets/scripts/app/templates/repo/settings.hbs | 12 ++++++------
 assets/styles/components/travis-switch.sass    |  6 +++++-
 2 files changed, 11 insertions(+), 7 deletions(-)

diff --git a/assets/scripts/app/templates/repo/settings.hbs b/assets/scripts/app/templates/repo/settings.hbs
index db445f93..1e515d4d 100644
--- a/assets/scripts/app/templates/repo/settings.hbs
+++ b/assets/scripts/app/templates/repo/settings.hbs
@@ -4,19 +4,19 @@
   {{#travis-tabs}}
     {{#travis-tab "general" "General Settings"}}
       {{#settings-form}}
-        <p>
-          {{travis-switch action="save" active=settings.builds_only_with_travis_yml}}
+        <p class="settings-row">
           Build only commits with .travis.yml file
+          {{travis-switch action="save" active=settings.builds_only_with_travis_yml}}
         </p>
 
-        <p>
-          {{travis-switch action="save" active=settings.build_pushes}}
+        <p class="settings-row">
           Build pushes
+          {{travis-switch action="save" active=settings.build_pushes}}
         </p>
 
-        <p>
-          {{travis-switch action="save" active=settings.build_pull_requests}}
+        <p class="settings-row">
           Build pull requests
+          {{travis-switch action="save" active=settings.build_pull_requests}}
         </p>
       {{/settings-form}}
     {{/travis-tab}}
diff --git a/assets/styles/components/travis-switch.sass b/assets/styles/components/travis-switch.sass
index 342aaac6..5b7ea12e 100644
--- a/assets/styles/components/travis-switch.sass
+++ b/assets/styles/components/travis-switch.sass
@@ -1,6 +1,10 @@
+.settings-row
+  margin-top: 20px
+
 .travis-switch
   position: relative
-  display: block
+  display: inline-block
+  float: left
   width: 60px
   height: 18px
   margin: 0 10px 0 15px