diff --git a/assets/scripts/app/controllers/build.coffee b/assets/scripts/app/controllers/build.coffee index ba99f348..6e61ab6f 100644 --- a/assets/scripts/app/controllers/build.coffee +++ b/assets/scripts/app/controllers/build.coffee @@ -2,7 +2,6 @@ Travis.BuildController = Ember.Controller.extend needs: ['repo'] repoBinding: 'controllers.repo.repo' commitBinding: 'build.commit' - lineNumberBinding: 'controllers.repo.lineNumber' currentUserBinding: 'controllers.repo.currentUser' tabBinding: 'controllers.repo.tab' diff --git a/assets/scripts/app/controllers/job.coffee b/assets/scripts/app/controllers/job.coffee index 148dbad7..7365d06d 100644 --- a/assets/scripts/app/controllers/job.coffee +++ b/assets/scripts/app/controllers/job.coffee @@ -4,7 +4,6 @@ Travis.JobController = Em.Controller.extend jobBinding: 'controllers.repo.job' repoBinding: 'controllers.repo.repo' commitBinding: 'job.commit' - lineNumberBinding: 'controllers.repo.lineNumber' currentUserBinding: 'controllers.repo.currentUser' tabBinding: 'controllers.repo.tab' diff --git a/assets/scripts/app/routes.coffee b/assets/scripts/app/routes.coffee index 14cfd682..711111a8 100644 --- a/assets/scripts/app/routes.coffee +++ b/assets/scripts/app/routes.coffee @@ -1,5 +1,4 @@ require 'travis/location' -require 'travis/line_number_parser' Ember.Router.reopen location: (if testMode? then Ember.NoneLocation.create() else Travis.Location.create()) @@ -79,12 +78,6 @@ Ember.Route.reopen Travis.storeAfterSignInPath(path) @transitionTo('auth') -Travis.Router.reopen - transitionTo: -> - this.container.lookup('controller:repo').set('lineNumber', null) - - @_super.apply this, arguments - Travis.Router.map -> @resource 'index', path: '/', -> @route 'current', path: '/' @@ -108,12 +101,6 @@ Travis.Router.map -> @route 'index', path: '/' @route 'profile', path: '/profile' -Travis.ApplicationRoute = Ember.Route.extend Travis.LineNumberParser, - setupController: -> - @_super.apply this, arguments - - this.controllerFor('repo').set('lineNumber', @fetchLineNumber()) - Travis.SetupLastBuild = Ember.Mixin.create setupController: -> @repoDidLoad() diff --git a/assets/scripts/app/views/log.coffee b/assets/scripts/app/views/log.coffee index 2bacdfe2..631765b3 100644 --- a/assets/scripts/app/views/log.coffee +++ b/assets/scripts/app/views/log.coffee @@ -1,4 +1,5 @@ require 'log' +require 'travis/lines_selector' Log.DEBUG = false Log.LIMIT = 10000 @@ -26,12 +27,12 @@ Travis.reopen console.log 'log view: did insert' if Log.DEBUG @_super.apply this, arguments @createEngine() - @lineNumberDidChange() willDestroyElement: -> console.log 'log view: will destroy' if Log.DEBUG parts = @get('log.parts') parts.removeArrayObserver(@, didChange: 'partsDidChange', willChange: 'noop') + @lineSelector?.willDestroy() versionDidChange: (-> @rerender() if @get('state') == 'inDOM' @@ -46,8 +47,8 @@ Travis.reopen console.log 'log view: create engine' if Log.DEBUG @scroll = new Log.Scroll @engine = Log.create(limit: Log.LIMIT, listeners: [@scroll]) + @lineSelector = new Travis.LinesSelector(@$().find('#log'), @scroll, window.location) @observeParts() - @numberLineOnHover() observeParts: -> parts = @get('log.parts') @@ -63,10 +64,6 @@ Travis.reopen @engine.set(part.number, part.content) @propertyDidChange('limited') - lineNumberDidChange: (-> - @scroll.set(number) if !@get('isDestroyed') && number = @get('controller.lineNumber') - ).observes('controller.lineNumber') - limited: (-> @engine?.limit?.limited ).property() @@ -79,24 +76,11 @@ Travis.reopen Travis.tailing.toggle() event.preventDefault() - numberLineOnHover: -> - $('#log').on 'mouseenter', 'a', -> - $(@).attr('href', '#L' + ($("#log p:visible").index(@parentNode) + 1)) - click: (event) -> - if (href = $(event.target).attr('href')) && matches = href?.match(/#L(\d+)$/) - @lineNumberClicked(matches[1]) - event.stopPropagation() - false - else - target = $(event.target) + target = $(event.target) + if target.prop('tagName') == 'P' target.closest('.fold').toggleClass('open') - lineNumberClicked: (number) -> - path = "#{window.location.pathname}#L#{number}" - window.history.pushState({ path: path }, null, path); - @set('controller.lineNumber', number) - actions: toTop: () -> $(window).scrollTop(0) @@ -105,25 +89,14 @@ Travis.reopen Log.Scroll = -> Log.Scroll.prototype = $.extend new Log.Listener, - set: (number) -> - return unless number - @number = number - @tryScroll() - insert: (log, data, pos) -> - @tryScroll() if @number + @tryScroll() if @numbers true tryScroll: -> - if element = $("#log p:visible")[@number - 1] + if element = $("#log p:visible.highlight:first") $('#main').scrollTop(0) - $('html, body').scrollTop($(element).offset()?.top) # weird, html works in chrome, body in firefox - @highlight(element) - @number = undefined - - highlight: (element) -> - $('#log p.highlight').removeClass('highlight') - $(element).addClass('highlight') + $('html, body').scrollTop(element.offset()?.top) # weird, html works in chrome, body in firefox # Log.Logger = -> # Log.Logger.prototype = $.extend new Log.Listener, diff --git a/assets/scripts/lib/travis/line_number_parser.coffee b/assets/scripts/lib/travis/line_number_parser.coffee deleted file mode 100644 index 2abd54df..00000000 --- a/assets/scripts/lib/travis/line_number_parser.coffee +++ /dev/null @@ -1,3 +0,0 @@ -Travis.LineNumberParser = Ember.Mixin.create - fetchLineNumber: -> - match[1] if match = document.location.hash.match(/#L(\d+)$/) diff --git a/assets/scripts/lib/travis/lines_selector.coffee b/assets/scripts/lib/travis/lines_selector.coffee new file mode 100644 index 00000000..e49a456e --- /dev/null +++ b/assets/scripts/lib/travis/lines_selector.coffee @@ -0,0 +1,55 @@ +class Travis.LinesSelector + element: null + scroll: null + location: null + last_selected_line: null + + constructor: (@element, @scroll, @location) -> + Ember.run.scheduleOnce 'afterRender', this, -> + @last_selected_line = @getSelectedLines()?.first + @highlightLines() + + @element.on 'click', 'a', (event) => + element = $(event.target).parent('p') + @loadLineNumbers(element, event.shiftKey) + + event.preventDefault() + false + + willDestroy: -> + @location.hash = '' + + loadLineNumbers: (element, multiple) -> + @setHashValueWithLine(element, multiple) + @highlightLines() + + highlightLines: -> + @removeAllHighlights() + + if lines = @getSelectedLines() + @element.find('p:visible').slice(lines.first - 1, lines.last).addClass('highlight') + @scroll.tryScroll() + + setHashValueWithLine: (line, multiple) -> + line_number = @getLineNumberFromElement(line) + + if multiple && @last_selected_line? + lines = [line_number, @last_selected_line].sort (a,b) -> a - b + hash = "#L#{lines[0]}-L#{lines[1]}" + else + hash = "#L#{line_number}" + + @last_selected_line = line_number + @location.hash = hash + + getLineNumberFromElement: (element) -> + @element.find('p:visible').index(element) + 1 + + removeAllHighlights: -> + @element.find('p.highlight').removeClass('highlight') + + getSelectedLines: -> + if match = @location.hash.match(/#L(\d+)(-L(\d+))?$/) + first = match[1] + last = match[3] || match[1] + {first: first, last: last} diff --git a/assets/scripts/spec/unit/line_selector_spec.coffee b/assets/scripts/spec/unit/line_selector_spec.coffee new file mode 100644 index 00000000..cbd4f12b --- /dev/null +++ b/assets/scripts/spec/unit/line_selector_spec.coffee @@ -0,0 +1,92 @@ +fakeLocation = {} +fakeScroll = + tryScroll: sinon.spy() + +element = jQuery('
+

first line

+

second line

+

third line

+
') + +module "Travis.LinesSelector", + setup: -> + fakeLocation.hash = '' + jQuery('body').append(element) + + teardown: -> + element.remove() + +test "defaults to no line selected", -> + Ember.run -> + new Travis.LinesSelector(element, fakeScroll, fakeLocation) + + wait().then -> + equal($('#fakeLog p.highlight').length, 0) + +test "defaults to a single line selected", -> + fakeLocation.hash = '#L2' + Ember.run -> + new Travis.LinesSelector(element, fakeScroll, fakeLocation) + + wait().then -> + equal($('#fakeLog p.highlight').length, 1) + equal($('#fakeLog p:nth-child(2)').hasClass('highlight'), true) + +test "defaults to multiple lines selected", -> + fakeLocation.hash = '#L2-L3' + Ember.run -> + new Travis.LinesSelector(element, fakeScroll, fakeLocation) + + wait().then -> + equal($('#fakeLog p.highlight').length, 2) + equal($('#fakeLog p:nth-child(2)').hasClass('highlight'), true) + equal($('#fakeLog p:nth-child(3)').hasClass('highlight'), true) + +test "selects a single line", -> + Ember.run -> + new Travis.LinesSelector(element, fakeScroll, fakeLocation) + + wait().then -> + equal($('#fakeLog p.highlight').length, 0) + $('#fakeLog p:first a').click() + equal($('#fakeLog p.highlight').length, 1) + equal($('#fakeLog p:nth-child(1)').hasClass('highlight'), true) + equal('#L1', fakeLocation.hash) + +test "selects multiple lines", -> + fakeLocation.hash = '#L2' + Ember.run -> + new Travis.LinesSelector(element, fakeScroll, fakeLocation) + + wait().then -> + equal($('#fakeLog p.highlight').length, 1) + + event = jQuery.Event('click') + event.shiftKey = true + $('#fakeLog p:first a').trigger(event) + + equal($('#fakeLog p.highlight').length, 2) + equal($('#fakeLog p:nth-child(1)').hasClass('highlight'), true) + equal($('#fakeLog p:nth-child(2)').hasClass('highlight'), true) + equal('#L1-L2', fakeLocation.hash) + +test "uses the last selected line as second selection line", -> + selector = null + Ember.run -> + selector = new Travis.LinesSelector(element, fakeScroll, fakeLocation) + + wait().then -> + $('#fakeLog p:last a').click() + equal($('#fakeLog p.highlight').length, 1) + equal(3, selector.last_selected_line) + + event = jQuery.Event('click') + event.shiftKey = true + $('#fakeLog p:first a').trigger(event) + + equal($('#fakeLog p.highlight').length, 3) + equal($('#fakeLog p:nth-child(1)').hasClass('highlight'), true) + equal($('#fakeLog p:nth-child(2)').hasClass('highlight'), true) + equal($('#fakeLog p:nth-child(3)').hasClass('highlight'), true) + equal('#L1-L3', fakeLocation.hash) + equal(1, selector.last_selected_line) diff --git a/assets/styles/_mixins/colors.sass b/assets/styles/_mixins/colors.sass index e54bf4fa..ec6e5313 100644 --- a/assets/styles/_mixins/colors.sass +++ b/assets/styles/_mixins/colors.sass @@ -23,6 +23,7 @@ $yellow-light-2: #fffcf4 $gray-dark-1: #333 $gray-dark-2: #444 $gray-dark-3: #666 +$gray-dark-4: #777 $gray-medium-1: #999 $gray-medium-2: #aaa $gray-medium-3: #c4cbcc @@ -65,6 +66,7 @@ $color-bg-log: #222222 $color-bg-log-fold: $gray-dark-1 $color-bg-log-hover: $gray-dark-2 $color-bg-log-highlight: $gray-dark-3 +$color-bg-log-fold-highlight: $gray-dark-4 $color-bg-slider: $slate-blue-3 $color-bg-left: $gray-light-4 $color-bg-list-odd: $white diff --git a/assets/styles/main/log.sass b/assets/styles/main/log.sass index 2e0ad5ff..d5793a54 100644 --- a/assets/styles/main/log.sass +++ b/assets/styles/main/log.sass @@ -57,6 +57,8 @@ pre#log // &.active p:first-of-type background: $color-bg-log-fold inline-image('ui/log.fold.open.2.png') no-repeat 8px 3px + &.highlight + background-color: $color-bg-log-fold-highlight &:not(.open) p:first-of-type visibility: visible