Scroll to top

Scroll to top

--- app/assets/javascripts/_site.js.coffee
+++ app/assets/javascripts/_site.js.coffee
@@ -54,6 +54,26 @@
     outer.remove()

     result
+
+  scrollToTop: ->
+    element = null
+    if feedbin.panel == 1
+      element = $(".feeds")
+    else if feedbin.panel == 2
+      element = $(".entries")
+    else if feedbin.panel == 3
+      element = $(".entry-content")
+
+    if element
+      element.css
+        "-webkit-overflow-scrolling": "auto"
+      element.animate {scrollTop: 0}, {
+        duration: 150,
+        complete: ()->
+          element.css
+            "-webkit-overflow-scrolling": "touch"
+      }
+

   toggleDiff: ->
     $('[data-behavior~=diff_view_changes]').toggleClass("hide")
@@ -1892,6 +1912,10 @@
       $(document).on 'input', '[data-behavior~=autosubmit]', (event) ->
         throttled($(@))

+    statsBarTouched: ->
+      $(document).on 'feedbin:native:statusbartouched', (event) ->
+        feedbin.scrollToTop()
+
     linkActions: ->
       $(document).on 'click', '[data-behavior~=view_link]', (event) ->
         href = $(@).parents("a:first").attr('href')


troll text changed

troll text changed

--- client/app/Applications/Activity.kdapplication/ListItems/ActivityListItem.coffee
+++ client/app/Applications/Activity.kdapplication/ListItems/ActivityListItem.coffee
@@ -147,7 +147,7 @@

     if superAdmin
       menu[0].items = [
-        { title : 'Report troll', id : 1,  parentId : null, callback : => @getSingleton('mainController').emit 'TrollReported', data  }
+        { title : 'MARK USER AS TROLL', id : 1,  parentId : null, callback : => @getSingleton('mainController').emit 'TrollReported', data  }
         { title : 'Delete', id : 2,  parentId : null, callback : => @confirmDeletePost data  }
       ]


change sockets for heroku

change sockets for heroku

--- lib/config/socketio.coffee
+++ lib/config/socketio.coffee
@@ -8,10 +8,10 @@
   return Math.floor(Math.random()*(max-min+1)+min)

 module.exports = (io) ->
-
   io.configure ->
     io.set('log level', config.socketio.log_level)
-
+    io.set("transports", ["xhr-polling"]);
+    io.set("polling duration", 10);
     io.set "authorization", (handshakeData, accept) ->

       # Check whether cookies were passed


range slicing

range slicing

--- hexaflip.coffee
+++ hexaflip.coffee
@@ -5,7 +5,7 @@
 # Copyright 2013, MIT License

 baseName = 'hexaFlip'
-className = baseName[0].toUpperCase() + baseName.slice 1
+className = baseName[0].toUpperCase() + baseName[1...]
 prefixList = ['webkit', 'Moz', 'O', 'ms']

 prefixProp = (prop) ->


quick fix to remove toolbars on subplots in gridplots

--- bokehjs/src/coffee/common/grid_plot.coffee
+++ bokehjs/src/coffee/common/grid_plot.coffee
@@ -42,6 +42,7 @@
       childmodels = []
       for row in @mget('children')
         for plot in row
+          plot.set('toolbar_location', null)
           childmodels.push(plot)
       build_views(@childviews, childmodels, {})
       @set_child_view_states()


fix load order

fix load order

--- app/assets/javascripts/bixby.js.coffee
+++ app/assets/javascripts/bixby.js.coffee
@@ -1,6 +1,5 @@

 #= require "./init"
-#= require "./login"

 #= require_tree "./templates"
 #= require_tree "./models"
@@ -9,6 +8,8 @@
 #= require_tree "./inventory"
 #= require_tree "./monitoring"

+#= require "./login"
+
 # finally, start the app
 jQuery ->
   Bixby.app.start()


Notification: print error message

Notification: print error message

--- client/Main/CommonViews/messagesList.coffee
+++ client/Main/CommonViews/messagesList.coffee
@@ -198,6 +198,7 @@

   click:->
     showPost = (err, post)->
+      warn err  if err
       if post
         internalApp = if post.constructor.name is "JNewApp" then "Apps" else "Activity"
         groupSlug = if post.group is "koding" then "" else "/#{post.group}"


Init first language switch buttons

Init first language switch buttons

--- app/assets/javascripts/pages.js.coffee
+++ app/assets/javascripts/pages.js.coffee
@@ -4,12 +4,13 @@

 initLanguageSelectors = () ->
   $('.language-sel a').click ->
-     window.location = window.location.pathname + "?locale=#{$(this).attr 'hreflang'}"
-     return false
+    window.location = window.location.pathname + "?locale=#{$(this).attr 'hreflang'}"
+    return false

 $(document).ready ->
+  initLanguageSelectors()
   initDom('body')
-  initLanguageSelectors()
+

 ###
 openNewOrganDialog = ->


Start watching after dom init

Start watching after dom init

--- app/assets/javascripts/ekylibre.js.coffee
+++ app/assets/javascripts/ekylibre.js.coffee
@@ -30,7 +30,8 @@
             listener.callback $element if $element.length
         return

-    _observe: () -> @observer.observe(@element, {childList: true, subtree: true})
+    _observe: () ->
+      $ => @observer.observe(@element, {childList: true, subtree: true})

   #
   # AJAX error handler


Fixed Compiler's warning

Fixed Compiler's warning

--- wzk/ui/form/AjaxForm.coffee
+++ wzk/ui/form/AjaxForm.coffee
@@ -48,7 +48,7 @@
   ###
   uploadFiles: (form) ->
     iframeIO = new goog.net.IframeIo()
-    iframeIO.sendFromForm(form)
+    iframeIO.sendFromForm (`/** @type {HTMLFormElement} */`) form
     iframeIO.listen goog.net.EventType.SUCCESS, @uploadSuccess
     iframeIO.listen goog.net.EventType.ERROR, @uploadError


Changed condition to be positive instead of negative.

--- app/assets/javascripts/selector.js.coffee
+++ app/assets/javascripts/selector.js.coffee
@@ -52,7 +52,7 @@
         @element.removeAttr "name"
         if @element.attr("required") is "true"
           @valueField.attr "required", "true"
-        unless @valueField.val()? and @valueField.val() != ''
+        if !@valueField.val()? or @valueField.val() == ''
           @valueField.attr("value", @element.val())
       this._on @element,
         keypress: "_keypress"


fix chrome

fix chrome

--- app/assets/jax/controllers/oz3d.js.coffee
+++ app/assets/jax/controllers/oz3d.js.coffee
@@ -13,7 +13,7 @@
     @context.activeCamera.lookAt [0, 4.5, 0]

   mouse_rolled: (e) ->
-    radius += e.wheelDeltaY / e.wheelDelta * 0.25
+    radius += e.wheelDeltaY * 0.25
     radius = 0.25 if radius < 0.25

   mouse_dragged: (e) ->


moved to cloudmade

moved to cloudmade

--- app/assets/javascripts/labs.js.coffee
+++ app/assets/javascripts/labs.js.coffee
@@ -1,5 +1,3 @@
-# http://cloudmade.com/documentation/map-tiles
-
 window.labs = []
 window.map = null
 window.showingContacts = false
@@ -40,15 +38,6 @@
     $(this).toggleClass 'active'

-  # if $('body').hasClass 'a-home'
-  #   map = L.map('homepage-map', { scrollWheelZoom: false, zoomControl: false }).setView([50, 0], 2 )
-  #   L.tileLayer('https://ssl_tiles.cloudmade.com/384aceabcd0942189d0e93cf0e98cd31/90734/256/{z}/{x}/{y}.png').addTo(map)
-  #   $(window).resize _.debounce((-> map.invalidateSize()),500)
-  #   # navigator.geolocation.getCurrentPosition((position)->
-  #   #   map.setView([position.coords.latitude, position.coords.longitude], 13)
-  #   # )
-
-
   $('[data-toggle=offcanvas]').click ->
     $('.row-offcanvas').toggleClass('active')

@@ -56,7 +45,7 @@
     location = [$('#lab-map').data('latitude'), $('#lab-map').data('longitude')]
     labmap = L.map('lab-map', { scrollWheelZoom: false, zoomControl: false, loadingControl: true }).setView(location, 14 )
     new L.Control.Zoom({ position: 'topright' }).addTo(labmap)
-    L.tileLayer('https://ssl_tiles.cloudmade.com/d8b794cdcd1e4e37bd83addfd40b7c68/110755/256/{z}/{x}/{y}.png', { attribution: osmAttrib }).addTo(labmap)
+    L.tileLayer('https://{s}.tiles.mapbox.com/v3/examples.map-9ijuk24y/{z}/{x}/{y}.png', { attribution: osmAttrib }).addTo(labmap)
     icon = L.icon({
       iconUrl: window.mapIcons[$('#lab-map').data('kind-name')]
       iconSize:     [35, 35]
@@ -104,7 +93,7 @@

     window.map = map
     new L.Control.Zoom({ position: 'topleft' }).addTo(map)
-    L.tileLayer('https://ssl_tiles.cloudmade.com/d8b794cdcd1e4e37bd83addfd40b7c68/110755/256/{z}/{x}/{y}.png', { attribution: osmAttrib, maxZoom: 14 }).addTo(map)
+    L.tileLayer('https://{s}.tiles.mapbox.com/v3/examples.map-9ijuk24y/{z}/{x}/{y}.png', { attribution: osmAttrib, maxZoom: 14 }).addTo(map)
     navigator.geolocation.getCurrentPosition((position)->
       map.setView([position.coords.latitude, position.coords.longitude], 4)
     )


wtf indentation

wtf indentation

--- app/assets/javascripts/modules.coffee
+++ app/assets/javascripts/modules.coffee
@@ -15,10 +15,10 @@
         handle = ".dot-" + self.parents(".panel").eq(0).attr('id')
         if response.complete
         	self.addClass('completed')
-          $(handle).removeClass(uncompletedClass).addClass(completedClass)
+        	$(handle).removeClass(uncompletedClass).addClass(completedClass)
         else
         	self.removeClass('completed')
-          $(handle).removeClass(completedClass).addClass(uncompletedClass)
+        	$(handle).removeClass(completedClass).addClass(uncompletedClass)

 $(document).ready(init_modules)
 $(document).on('page:load', init_modules)


preventDefault when search icon clicked

preventDefault when search icon clicked

--- app/assets/javascripts/reasons.coffee
+++ app/assets/javascripts/reasons.coffee
@@ -26,8 +26,9 @@
       text = $(this).text().replace(/\s+/g, ' ').toLowerCase();
       return !~text.indexOf(val);
     ).hide();
-  $(document).on 'click', '#search ~ li a', () ->
+  $(document).on 'click', 'li.search-icon a', (e) ->
     $('#search').focus()
+    e.preventDefault()
   setTimeout ->
     $('#search').addClass 'ready'
   , 100


Add null guards.

Add null guards.

--- app/javascripts/filters.coffee
+++ app/javascripts/filters.coffee
@@ -6,11 +6,11 @@
                          'qiprofile.tnm', 'qiprofile.image']

     filters.filter 'capitalize', ->
-      (s) -> _s.capitalize(s)
+      (s) -> _s.capitalize(s) if s?

     filters.filter 'underscore', ->
-      (s) -> _s.underscored(s)
+      (s) -> _s.underscored(s) if s?

     filters.filter 'romanize', ['StringHelper', (StringHelper) ->
@@ -18,12 +18,12 @@
       # @returns a string with the leading digit sequence converted
       #    to a roman numeral
       (value) ->
-        value.toString().replace(/^\d+/, roman.romanize)
+        value.toString().replace(/^\d+/, roman.romanize) if value?
     ]

     filters.filter 'moment', ->
-      (s) -> moment(s).format('MM/DD/YYYY')
+      (s) -> moment(s).format('MM/DD/YYYY') if s?

     filters.filter 'notSpecified', ->
@@ -52,10 +52,11 @@

     filters.filter 'percent', ->
-      (value) -> value * 100
+      (value) -> value * 100 if value?

     filters.filter 'imageContainerTitle', ->
-      # If the image container name is
+      # If the image container name ends with _reg, then it is a registation.
+      # Otherwise, it is a scan.
       (value) ->
         if value.startsWith('reg')
           'Registration ' + value


Fix typo, close #29

Fix typo, close #29

--- en-us.coffee
+++ en-us.coffee
@@ -35,7 +35,7 @@

   tutorial:
     welcome: 'Penguin Watch is a citizen science website trying to understand the lives of penguins. We want you to help us mark images taken from nesting sites around Antarctica.'
-    marking: 'Individually mark penguins, chicks, and eggs in the image by clicking on the center of each one\'s visible area. Sometimes just a head or tail is showing, other times you\'ll be able to mark the center of the chest/torso. We sometimes need you to mark up to 25 of each — and you can more more if you want to.'
+    marking: 'Individually mark penguins, chicks, and eggs in the image by clicking on the center of each one\'s visible area. Sometimes just a head or tail is showing, other times you\'ll be able to mark the center of the chest/torso. We sometimes need you to mark up to 25 of each — and you can mark more if you want to.'
     removing: 'Remove marks by clicking them and using the black-and-white cross.'
     other: 'Mark any other animals too so we can see how often they are near the penguin nest.'
     talk: 'You can discuss the project with the science team and other volunteers by visiting Talk.'


IDE: Fix auto remove pane issue

IDE: Fix auto remove pane issue

--- client/ide/lib/views/tabview/ideview.coffee
+++ client/ide/lib/views/tabview/ideview.coffee
@@ -128,7 +128,7 @@
     appStorage = kd.getSingleton('appStorageController').storage 'Ace', '1.0.1'
     paneLength = tabHandle.getDelegate().panes.length

-    return  if paneLength > 1 # remove pane when there is only one pane left
+    return  if paneLength >= 1  # remove pane when there is only one pane left

     appStorage.ready =>


Add axis labels

Add axis labels

--- cistrans/cistrans.coffee
+++ cistrans/cistrans.coffee
@@ -43,6 +43,10 @@
   darkGray = d3.rgb(200, 200, 200)
   pink = "#E9CFEC"
   purple = "#8C4374"
+  # bgcolor = "black"
+  labelcolor = "black"   # "white"
+  titlecolor = "blue"    # "Wheat"
+  maincolor = "darkblue" # "Wheat"

   # calculate X and Y scales, using cM positions
   totalChrLength = 0;
@@ -133,6 +137,54 @@
            .attr("fill", darkGray)
            .style("pointer-events", "none")

+  # chromosome labels
+  axislabels = svg.append("g").attr("id", "axislabels").style("pointer-events", "none")
+  axislabels.append("g").attr("id", "topleftX").selectAll("empty")
+     .data(data.chrnames)
+     .enter()
+     .append("text")
+     .text((d) -> d)
+     .attr("x", (d) -> (data.chr[d].start_Xpixel + data.chr[d].end_Xpixel)/2)
+     .attr("y", bottom[0] + pad.bottom*0.3)
+     .attr("fill", labelcolor)
+  axislabels.append("g").attr("id", "topleftY")
+     .selectAll("empty")
+     .data(data.chrnames)
+     .enter()
+     .append("text")
+     .text((d) -> d)
+     .attr("x", left[0] - pad.left*0.15)
+     .attr("y", (d) -> (data.chr[d].start_Ypixel + data.chr[d].end_Ypixel)/2)
+     .style("text-anchor", "end")
+     .attr("fill", labelcolor)
+  axislabels.append("g").attr("id", "bottomX").selectAll("empty")
+     .data(data.chrnames)
+     .enter()
+     .append("text")
+     .text((d) -> d)
+     .attr("x", (d) -> (data.chr[d].start_lowerXpixel + data.chr[d].end_lowerXpixel)/2)
+     .attr("y", bottom[1] + pad.bottom*0.3)
+     .attr("fill", labelcolor)
+  axislabels.append("text")
+     .text("Marker position (cM)")
+     .attr("x", (left[0] + right[0])/2)
+     .attr("y", bottom[0] + pad.bottom*0.75)
+     .attr("fill", titlecolor)
+  axislabels.append("text")
+     .text("Position (cM)")
+     .attr("x", (left[1] + right[1])/2)
+     .attr("y", bottom[1] + pad.bottom*0.75)
+     .attr("fill", titlecolor)
+  xloc = left[0] - pad.left*0.65
+  yloc = (top[0] + bottom[0])/2
+  axislabels.append("text")
+     .text("Probe position (cM)")
+     .attr("x", xloc)
+     .attr("y", yloc)
+     .attr("transform", "rotate(270,#{xloc},#{yloc})")
+     .style("text-anchor", "middle")
+     .attr("fill", titlecolor)
+
   # maximum lod score
   maxlod = d3.max(data.peaks, (d) -> d.lod)

@@ -153,7 +205,6 @@
                  .text((z) -> "#{z.probe} (LOD = #{d3.format('.2f')(z.lod)})")
                  .attr("class", "d3-tip")
                  .attr("id", "eqtltip")
-

   # create indices to lod scores, split by chromosome
   cur = 0
@@ -199,12 +250,15 @@
          .text(titletext)
          .attr("x", (left[1]+right[1])/2)
          .attr("y", top[1] - pad.top/2)
+         .attr("fill", maincolor)
+         .style("font-size", "18px")
     else
       probeaxes.append("text")
          .text(titletext)
          .attr("x", (left[1]+right[1])/2)
          .attr("y", top[1] - pad.top/2)
-
+         .attr("fill", maincolor)
+         .style("font-size", "18px")

   # circles at eQTL peaks
   peaks = svg.append("g").attr("id", "peaks")


also pad signature

also pad signature

--- zechat/static/crypto.coffee
+++ zechat/static/crypto.coffee
@@ -1,7 +1,10 @@
+zc.pad_base64 = (text) ->
+  while text.length % 4 != 0
+    text = text + '='
+  return text
+
 zc.format_pem = (key, title) ->
   rv = "-----BEGIN " + title + "-----\n"
-  while key.length % 4 != 0
-    key = key + '='
   while key
     rv += key.slice(0, 64) + "\n"
     key = key.slice(64)
@@ -17,14 +20,14 @@
   k = new RSAKey()
   k.generate(size, '10001')
   key = k.privateKeyToPkcs1PemString()
-  _.defer(callback, zc.format_pem(key, "RSA PRIVATE KEY"))
+  _.defer(callback, zc.format_pem(zc.pad_base64(key), "RSA PRIVATE KEY"))

 zc.get_public_key = (private_key) ->
   key = new RSAKey()
   key.readPrivateKeyFromPEMString(private_key)
   public_key = key.publicKeyToX509PemString()
-  return zc.format_pem(public_key, "PUBLIC KEY")
+  return zc.format_pem(zc.pad_base64(public_key), "PUBLIC KEY")

 class zc.Crypto
@@ -47,7 +50,7 @@
   sign: (data, callback) ->
     @create_crypt (crypt) ->
       crypt.sign data, (_, signed) ->
-        callback(signed.signature)
+        callback(zc.pad_base64(signed.signature))

   verify: (data, signature, callback) ->
     @create_crypt (crypt) ->


teams: tests: fix settings page test

teams: tests: fix settings page test

--- client/test/lib/helpers/teamshelpers.coffee
+++ client/test/lib/helpers/teamshelpers.coffee
@@ -152,8 +152,8 @@
       .waitForElementVisible  teamSettingsLinkItem, 20000
       .click                  teamSettingsLinkItem
       .waitForElementVisible  teamDashboard, 20000 # Assertion
-      .pause                  200 # Wait for team dashboard
-      .assert.containsText    teamDashboard, 'Team Dashboard' # Assertion
+      .pause                  200 # Wait for team settings
+      .assert.containsText    teamDashboard, 'Team Settings' # Assertion

   seeTeamNameOnsideBar: (browser, name) ->


Fixed calling callback when unhovering button

--- app/js/jail_iframe/classes/buttons.coffee
+++ app/js/jail_iframe/classes/buttons.coffee
@@ -51,13 +51,22 @@
     </div>
   """

-  constructor: (dom_events={}) ->
+  constructor: (callbacks) ->
     super
-    @$el.on 'mouseleave', => @_removeClass 'fl-button-state-hovered'
-    $(document).on 'mousemove', => @_removeClass 'fl-button-state-hovered'

-    for event, callback of dom_events
-      @$el.on event, callback
+    @_callbacks = callbacks
+    @$el.on 'mouseenter', @_callbacks.mouseenter
+    @$el.on 'click', @_callbacks.click
+    @$el.on 'mouseleave', @_onMouseLeave
+    $(document).on 'mousemove', @_onMouseLeave
+
+  destroy: ->
+    super
+    $(document).off 'mousemove', @_onMouseLeave
+
+  _onMouseLeave: =>
+    @_removeClass 'fl-button-state-hovered'
+    @_callbacks.mouseleave()

   _textContainer: ($el) ->
     for el in $el.parents()


Fix merge.

Fix merge.

--- app/views/settings/data.coffee
+++ app/views/settings/data.coffee
@@ -18,16 +18,8 @@
   subscriptions:
     'source:dataReceived': 'updateValidSourceTools'

-  initialize: (options) ->
-    @channel = @model.get('channel')
-    @sourceType = @model.get('data_source').get('source_type') or false
-    @selectedSource = Manager.get('sources').get(@model.get('data_source').get('source')) if typeof @model.get('data_source').get('source') isnt 'undefined'
+  initialize: ->
     @updateValidSourceTools()
-
-    @searchTypeView = new SearchTypeView()
-    @params = @model.get('data_source').get('params')
-    @paramsView = new ParamsView @params
-
     @searchTypeView = new SearchTypeView({model: @model.get('data_source')})
     @paramsView = new ParamsView @model.get('data_source').get('params')


ok remove double duty specs

ok remove double duty specs

--- components/artwork_item/test/artwork.coffee
+++ components/artwork_item/test/artwork.coffee
@@ -27,18 +27,6 @@
     it 'can render a specified size', ->
       $ = cheerio.load render('artwork')({ artwork: @artwork, artworkSize: 'large' })
       $('.artwork-item-image').attr('src').should.containEql 'large'
-
-    it 'displays missing image if the size is not available', ->
-      @artwork.set images: fabricate('artwork_image', image_versions: [])
-      $ = cheerio.load render('artwork')({ artwork: @artwork, artworkSize: 'banana' })
-      $('.artwork-item-image').attr('src').should.not.containEql 'banana'
-      $('.artwork-item-image').attr('src').should.containEql 'missing'
-
-    it 'displays a fallback nothing available', ->
-      @artwork.set images: fabricate('artwork_image', image_versions: ['small'])
-      $ = cheerio.load render('artwork')({ artwork: @artwork, artworkSize: 'banana' })
-      $('.artwork-item-image').attr('src').should.not.containEql 'banana'
-      $('.artwork-item-image').attr('src').should.containEql 'small'

     it 'renders with a fixed with', ->
       $ = cheerio.load render('artwork')({ artwork: @artwork, imageWidth: 500 })


Removed TODO.

Removed TODO.

--- client/publication.coffee
+++ client/publication.coffee
@@ -23,8 +23,6 @@
         $canvas = $('<canvas/>').addClass('display-canvas').addClass('display-canvas-loading')
         $loading = $('<div/>').addClass('loading').text("Page #{ pageNumber }")
         $pageDisplay = $('<div/>').addClass('display-page').append($canvas).append($loading).appendTo('#viewer .display-wrapper')
-
-        # TODO: Add pending page number + loading animation to the page

         do ($canvas, $pageDisplay) =>
           # TODO: Handle errors as well


Improving behavior on Chrome.

Improving behavior on Chrome.

--- client/publication.coffee
+++ client/publication.coffee
@@ -1,9 +1,17 @@
 class @Publication extends @Publication
+  constructor: (args...) ->
+    super args...
+
+    @_pages = null
+
   _viewport: (page) =>
     scale = 1.25
     page.page.getViewport scale

   _progressCallback: (progressData) =>
+    # Maybe this instance has been destroyed in meantime
+    return if @_pages is null
+
     @_progressData = progressData if progressData

     documentHalf = _.min [(@_progressData.loaded / @_progressData.total) / 2, 0.5]
@@ -12,31 +20,39 @@
     Session.set 'currentPublicationProgress', documentHalf + pagesHalf

   show: =>
-    @destroy()
+    console.debug "Showing publication #{ @_id }"
+
+    assert.strictEqual @_pages, null

     @_pagesDone = 0
     @_pages = []

-    # TODO: Handle errors as well
     PDFJS.getDocument(@url(), null, null, @_progressCallback).then (@_pdf) =>
+      # To make sure we are starting with empty slate
+      $('#viewer .display-wrapper').empty()
+
       for pageNumber in [1..@_pdf.numPages]
         $canvas = $('<canvas/>').addClass('display-canvas').addClass('display-canvas-loading')
         $loading = $('<div/>').addClass('loading').text("Page #{ pageNumber }")
-        $pageDisplay = $('<div/>').addClass('display-page').append($canvas).append($loading).appendTo('#viewer .display-wrapper')
-
-        do ($canvas, $pageDisplay) =>
-          # TODO: Handle errors as well
+        $('<div/>').addClass('display-page').attr('id', "display-page-#{ pageNumber }").append($canvas).append($loading).appendTo('#viewer .display-wrapper')
+
+        do (pageNumber) =>
           @_pdf.getPage(pageNumber).then (page) =>
+            # Maybe this instance has been destroyed in meantime
+            return if @_pages is null
+
+            assert.equal pageNumber, page.pageNumber
+
             viewport = @_viewport
               page: page # Dummy page object

+            $canvas = $("#display-page-#{ pageNumber } canvas")
             $canvas.removeClass('display-canvas-loading').attr
               height: viewport.height
               width: viewport.width

-            @_pages[page.pageNumber - 1] =
-              $canvas: $canvas
-              $pageDisplay: $pageDisplay
+            @_pages[pageNumber - 1] =
+              pageNumber: pageNumber
               page: page
               rendering: false
             @_pagesDone++
@@ -46,41 +62,76 @@
             # Check if new page should be maybe rendered?
             @checkRender()

-    $(window).on 'scroll.publication', @checkRender
-    $(window).on 'resize.publication', @checkRender
+          , (args...) =>
+            # TODO: Handle errors better (call destroy?)
+            console.error "Error getting page #{ pageNumber }", args...
+
+      $(window).on 'scroll.publication', @checkRender
+      $(window).on 'resize.publication', @checkRender
+
+    , (args...) =>
+      # TODO: Handle errors better (call destroy?)
+      console.error "Error showing #{ @_id }", args...

   checkRender: =>
-    for page in @_pages
+    for page in @_pages or []
       continue if page.rendering

-      canvasTop = page.$canvas.offset().top
-      canvasBottom = canvasTop + page.$canvas.height()
+      $canvas = $("#display-page-#{ page.pageNumber } canvas")
+
+      canvasTop = $canvas.offset().top
+      canvasBottom = canvasTop + $canvas.height()
       # Add 100px so that we start rendering early
       if canvasTop - 100 <= $(window).scrollTop() + $(window).height() and canvasBottom + 100 >= $(window).scrollTop()
         @renderPage page

   destroy: =>
-    for page in @_pages or []
+    console.debug "Destroying publication #{ @_id }"
+
+    pages = @_pages or []
+    @_pages = null # To remove references to pdf.js elements to allow cleanup, and as soon as possible as this disables other callbacks
+
+    $(window).off 'scroll.publication'
+    $(window).off 'resize.publication'
+
+    for page in pages
       page.page.destroy()
     @_pdf.destroy() if @_pdf
-    @_pages = null # To remove references to canvas elements
-
-    $(window).off 'scroll.publication'
-    $(window).off 'resize.publication'

   renderPage: (page) =>
     return if page.rendering
     page.rendering = true

+    $canvas = $("#display-page-#{ page.pageNumber } canvas")
+
+    # Redo canvas resize to make sure it is the right size
+    # It seems sometimes already resized canvases are being deleted and replaced with initial versions
+    viewport = @_viewport page
+    $canvas.attr
+      height: viewport.height
+      width: viewport.width
+
     renderContext =
-      canvasContext: page.$canvas.get(0).getContext '2d'
+      canvasContext: $canvas.get(0).getContext '2d'
       viewport: @_viewport page

     console.debug "Rendering page #{ page.page.pageNumber }"

-    # TODO: Handle errors as well
     page.page.render(renderContext).then =>
-      page.$pageDisplay.find('.loading').hide()
+      console.debug "Rendering page #{ page.page.pageNumber } complete"
+
+      $("#display-page-#{ page.pageNumber } .loading").hide()
+
+    , (args...) =>
+      # TODO: Handle errors better (call destroy?)
+      console.error "Error rendering page #{ page.page.pageNumber }", args...
+
+  # Fields needed when showing (rendering) the publication: those which are needed for PDF URL to be available
+  # TODO: Verify that it works after support for filtering fields on the client will be released in Meteor
+  @SHOW_FIELDS: ->
+    fields:
+      foreignId: 1
+      source: 1

 Deps.autorun ->
   if Session.get 'currentPublicationId'
@@ -88,7 +139,8 @@
     Meteor.subscribe 'annotations-by-publication', Session.get 'currentPublicationId'

 Deps.autorun ->
-  publication = Publications.findOne Session.get 'currentPublicationId'
+  # TODO: Limit only to fields necessary to display publication so that it is not rerun on field changes
+  publication = Publications.findOne Session.get('currentPublicationId'), Publication.SHOW_FIELDS()

   return unless publication


Remove a log

Remove a log

--- app/lib/surface/SingularSprite.coffee
+++ app/lib/surface/SingularSprite.coffee
@@ -79,7 +79,6 @@
         @baseScaleY *= @camera.y2x * 0.85
       else
         @baseScaleY *= @camera.y2x / 0.85
-      console.log 'it is a cliff!', actionName, @baseScaleX, @baseScaleY
     @currentAnimation = actionName
     return


Simplify setting current menu

Simplify setting current menu

--- resources/assets/coffee/_classes/menu.coffee
+++ resources/assets/coffee/_classes/menu.coffee
@@ -64,14 +64,11 @@
     e.preventDefault()
     timeout = parseInt(link.dataset.menuShowDelay ? @menuTimeout, 10)

-    Timeout.clear @refreshTimeout
-    @refreshTimeout = Timeout.set timeout, =>
-      @currentMenu =
-        if @currentMenu == target
-          @closestMenuId $target
-        else
-          target
-      @refresh()
+    @setMenu timeout, =>
+      if @currentMenu == target
+        @closestMenuId $target
+      else
+        target

@@ -79,28 +76,18 @@
     link = e.currentTarget
     timeout = parseInt(link.dataset.menuShowDelay ? @menuTimeout, 10)

-    Timeout.clear @refreshTimeout
-    @refreshTimeout = Timeout.set timeout, =>
-      @currentMenu = link.dataset.menuTarget
-      @currentMenu ?= @closestMenuId $(link)
-      @refresh()
-
+    @setMenu timeout, =>
+      link.dataset.menuTarget ? @closestMenuId($(link))

   onMouseLeave: (e) =>
     $target = $(e.currentTarget)

-    Timeout.clear @refreshTimeout
-    @refreshTimeout = Timeout.set @menuTimeout, =>
-      @currentMenu = @parentsMenuId $target
-      @refresh()
+    @setMenu null, => @parentsMenuId($target)

   hideMenu: =>
-    Timeout.clear @refreshTimeout
-    @refreshTimeout = Timeout.set @menuTimeout, =>
-      @currentMenu = null
-      @refresh()
+    @setMenu()

   refresh: =>
@@ -120,3 +107,12 @@
         Fade.in menu
         @$menuLink(menuId).addClass('js-menu--active')
         $(menu).trigger 'menu:showing'
+
+
+  setMenu: (delay, menuFunc) =>
+    delay ?= @menuTimeout
+    Timeout.clear @refreshTimeout
+
+    @refreshTimeout = Timeout.set delay, =>
+      @currentMenu = menuFunc?()
+      @refresh()


update notification to be cleaner with subscriptions to event

--- app/assets/javascripts/application/services/notification.coffee
+++ app/assets/javascripts/application/services/notification.coffee
@@ -6,6 +6,7 @@
     channel_name = @element.data('channel')
     event_name   = @element.data('event')

+    console.log
     subscribed_channels = []
     unless jQuery.isEmptyObject(AHP.channels)
       for key, value of AHP.channels.channels
@@ -13,8 +14,14 @@

     unless channel_name in subscribed_channels
       channel = AHP.subscribe(channel_name)
+    else
+      channel = AHP.channel(channel_name)
+
+    channel.bind 'pusher:subscription_succeeded', =>
       channel.bind event_name, (data) =>
         @getDelta(data) unless data.who == gon.who
+
+

   getDelta: (data) ->
     $.ajax


Fixes #10. Fix bug where the session user might be disposed too soon.

The process of registering consists of creating the user (via API), then setting
a user into Chaplin.mediator.user. Previously we were setting the registered
user object directly, which broke because when we left the registration page
we would dispose that user, clearing it's properties. The fix is to always create
a new user to be the "session user", copying attributes from the user object
given. This way we know our "session user" isn't attached to any other views
or controllers.

--- app/assets/javascripts/controllers/session_controller.js.coffee
+++ app/assets/javascripts/controllers/session_controller.js.coffee
@@ -40,9 +40,13 @@
         @publishEvents()

     createSession: (user) ->
-      Chaplin.mediator.user = user
+      # We are creating a new user and copying over the
+      # attributes as the user param likely refers to a view's
+      # model somewhere, which might later be disposed.
+      Chaplin.mediator.user = new User
+      Chaplin.mediator.user.set(user.getAttributes())
       @publishEvents()
-      utils.sessionStorage('authentication_token', user.get('authentication_token'))
+      utils.sessionStorage('authentication_token', Chaplin.mediator.user.get('authentication_token'))

     logout: =>
       @destroySession()


Update the timestamp of the last data refresh once for each refresh, instead of once for every page of feeds requested from the server.

--- app/assets/javascripts/ng-services/ng-FeedsFoldersSvc.js.coffee
+++ app/assets/javascripts/ng-services/ng-FeedsFoldersSvc.js.coffee
@@ -22,9 +22,6 @@

     # Indicate that AJAX request/response cycle is busy so no more calls are done until finished
     feedsPaginationSvc.set_busy true
-
-    # Reset the 1-minute timer until the next feeds refresh
-    $rootScope.last_data_refresh = Date.now()

     page += 1
     $http.get("/api/feeds.json?include_read=#{$rootScope.show_read}&page=#{page}")
@@ -160,6 +157,8 @@
   # PRIVATE FUNCTION: Load feeds and folders.
   #--------------------------------------------
   load_data = ->
+    # Reset the 1-minute timer until the next data refresh
+    $rootScope.last_data_refresh = Date.now()
     load_feeds()
     load_folders()


Don't use of deprecated methods internally

--- src/workspace-view.coffee
+++ src/workspace-view.coffee
@@ -151,7 +151,7 @@

     @command 'pane:reopen-closed-item', => @reopenItemSync()

-    @command 'core:close', => if @getActivePaneItem()? then @destroyActivePaneItem() else @destroyActivePane()
+    @command 'core:close', => if @getModel().getActivePane().getActiveItem()? then @destroyActivePaneItem() else @destroyActivePane()
     @command 'core:save', => @saveActivePaneItem()
     @command 'core:save-as', => @saveActivePaneItemAs()

@@ -182,8 +182,8 @@
               detailedMessage: "The shell commands `atom` and `apm` are installed."

   handleFocus: ->
-    if @getActivePane()
-      @getActivePane().focus()
+    if @getActivePaneView()
+      @getActivePaneView().focus()
       false
     else
       @updateTitle()
@@ -205,7 +205,7 @@
   # Updates the application's title, based on whichever file is open.
   updateTitle: ->
     if projectPath = atom.project.getPath()
-      if item = @getActivePaneItem()
+      if item = @getModel().getActivePane().getActiveItem()
         @setTitle("#{item.getTitle?() ? 'untitled'} - #{projectPath}")
       else
         @setTitle(projectPath)
@@ -272,7 +272,7 @@
   #
   # Returns a {PaneView}.
   getActivePaneView: ->
-    @panes.getActivePane()
+    @panes.getActivePaneView()

   # Public: Get the view associated with the active pane item.
   #


activity: show TopicChatPaneView if react is enabled

--- client/activity/lib/activityappview.coffee
+++ client/activity/lib/activityappview.coffee
@@ -16,7 +16,10 @@
 isChannelCollaborative = require 'app/util/isChannelCollaborative'
 isKoding               = require 'app/util/isKoding'
 isGroup                = require 'app/util/isGroup'
+isReactEnabled         = require 'app/util/isReactEnabled'
 ChatSearchModal        = require 'app/activity/sidebar/chatsearchmodal'
+
+TopicChatPaneView = require './components/topicchatpane/view'

 module.exports = class ActivityAppView extends KDView
@@ -136,6 +139,7 @@
             global.location.href = location
             return

+          console.log {type_, slug}
           router.handleNotFound router.getCurrentPath()
         else
           { typeConstant } = data
@@ -197,10 +201,13 @@
     type      = data.typeConstant

     paneClass = switch type
-      when 'topic'          then TopicMessagePane
       when 'bot'            then KodingBotMessagePane
       when 'privatemessage' then PrivateMessagePane
       when 'post'           then SingleActivityPane
+      when 'topic'
+        if isReactEnabled()
+        then TopicChatPaneView
+        else TopicMessagePane
       else
         if name is 'announcement-changelog'
         then AnnouncementPane


Add comments

Add comments

--- app/assets/javascripts/components/moving_records/new_moving_record_form.js.coffee
+++ app/assets/javascripts/components/moving_records/new_moving_record_form.js.coffee
@@ -9,27 +9,28 @@
     description: ""

   componentDidMount: ->
-    @updateAutocomplete()
+    @updateAutocomplete()  # Initialize the jQuery UI autocomplete.

   componentDidUpdate: ->
-    @updateAutocomplete()
+    @updateAutocomplete()  # Update the jQuery UI autocomplete.

   componentWillUnmount: ->
-    # get rid of the extra HTML that jQuery UI autocomplete creates.
+    # Remove the extra HTML that jQuery UI autocomplete creates.
     $(React.findDOMNode(@refs.room)).autocomplete('destroy')
     $(React.findDOMNode(@refs.category)).autocomplete('destroy')

+  # Sets up the jQuery UI autocomplete on the real DOM with the provided data.
+  # When an autocomplete item is selected, updates the value accordingly.
   updateAutocomplete: ->
     $(React.findDOMNode(@refs.room)).autocomplete
       source: @props.roomSuggestions
-      select: (e, ui) =>
-        @setState room: ui.item.value
+      select: (e, ui) => @setState room: ui.item.value

     $(React.findDOMNode(@refs.category)).autocomplete
       source: @props.categorySuggestions
-      select: (e, ui) =>
-        @setState category: ui.item.value
+      select: (e, ui) => @setState category: ui.item.value

+  # Updates the active element's value based on user's input.
   handleChange: (e) ->
     name = e.target.name
     @setState "#{ name }": e.target.value
@@ -59,24 +60,17 @@
   handleClear: (e) ->
     @setState @getInitialState()  # Restore component's initial UI.

-  # field validations
+  # Field validations
   valid: ->
     @validName() && @validVolume() && @validQuantity() &&
     @validRoom() && @validCategory() && @validDescription()
-  validName: ->
-    @state.name && @state.name.length <= 50
-  validVolume: ->
-    @state.volume && @state.volume.length <= 10
-  validQuantity: ->
-    @state.quantity && @state.quantity.length <= 10
-  validRoom: ->
-    @state.room && @state.room.length <= 50
-  validCategory: ->
-    @state.category && @state.category.length <= 50
-  validDescription: ->
-    @state.description.length <= 200
-  fieldColor: (validator) ->
-    if validator then 'has-success' else 'has-warning'
+  validName:        -> @state.name && @state.name.length <= 50
+  validVolume:      -> @state.volume && @state.volume.length <= 10
+  validQuantity:    -> @state.quantity && @state.quantity.length <= 10
+  validRoom:        -> @state.room && @state.room.length <= 50
+  validCategory:    -> @state.category && @state.category.length <= 50
+  validDescription: -> @state.description.length <= 200
+  fieldColor: (validator) -> if validator then 'has-success' else 'has-warning'

   capitalize: (string) ->
     string.charAt(0).toUpperCase() + string.slice(1)
@@ -123,7 +117,7 @@
           R.input
             type:        'text'
             className:   'form-control'
-            ref:         'category'  # for autocompletion
+            ref:         'category'  # for Autocomplete to access the DOM
             name:        'category'  # for @handleChange
             placeholder: 'Category'
             value:       @state.category
@@ -133,7 +127,7 @@
           R.input
             type:        'text'
             className:   'form-control'
-            ref:         'room'  # for autocompletion
+            ref:         'room'  # for Autocomplete to access the DOM
             name:        'room'  # for @handleChange
             placeholder: 'Room'
             value:       @state.room


home/integrations: make sure app secret is being shown

--- client/home/lib/integrations/components/gitlab/container.coffee
+++ client/home/lib/integrations/components/gitlab/container.coffee
@@ -35,14 +35,20 @@

   componentDidMount: ->

-    return  unless getGroup().config?.gitlab?
+    group = getGroup()

-    { gitlab } = getGroup().config
+    return  unless group.config?.gitlab?

-    @setState
-      enabled: gitlab.enabled
-      url: gitlab.url
-      applicationId: gitlab.applicationId
+
+    group.fetchDataAt 'gitlab.applicationSecret', (err, applicationSecret) =>
+
+      { gitlab } = group.config
+
+      @setState
+        enabled: gitlab.enabled
+        url: gitlab.url
+        applicationId: gitlab.applicationId
+        applicationSecret: applicationSecret

   onInputChange: (field) -> (event) =>
@@ -103,8 +109,7 @@

         newState = {}
         newState.err = err  if err
-        newState = _.pick options, 'url', 'applicationId'
-        newState.applicationSecret = ''
+        newState = _.pick options, 'url', 'applicationId', 'applicationSecret'
         newState.err = err
         newState.isSaving = no


Team parser: Parse Hp as hp.

Team parser: Parse Hp as hp.

--- client/app/js/concerns/team_parsing.coffee
+++ client/app/js/concerns/team_parsing.coffee
@@ -105,6 +105,7 @@

 statsHash =
   'hp'   : 'hp'
+  'Hp'   : 'hp'
   'HP'   : 'hp'
   'Atk'  : 'attack'
   'Def'  : 'defense'


minor ui updates

minor ui updates

--- app/assets/js/main.coffee
+++ app/assets/js/main.coffee
@@ -262,6 +262,10 @@
           $scope.rows = response.plan.rows
           $scope.$emit(UPDATE_TIMEOUT, response.timeout)

+       updateAndClearSelected = (response) ->
+         update(response)
+         $scope.clearSelected()
+
       $scope.claim = (seat) ->
         payload = {target: {name:seat}}
         if ($scope.selected.length > 0)
@@ -269,12 +273,10 @@
             angular.forEach($scope.selected, (seat) ->
                 payload.seats.push({name: seat})
             )
-            $http.post(jsRoutes.controllers.Orders.ajaxMove($scope.showId, $scope.orderId).url, payload).success(update).error(errorHandler)
-        $scope.clearSelected()
+            $http.post(jsRoutes.controllers.Orders.ajaxMove($scope.showId, $scope.orderId).url, payload).success(updateAndClearSelected).error(errorHandler)

       $scope.$on(MOVE_BEST, () ->
-        $http.post(jsRoutes.controllers.Orders.ajaxMoveBest($scope.showId, $scope.orderId).url).success(update).error(errorHandler)
-        $scope.clearSelected()
+        $http.post(jsRoutes.controllers.Orders.ajaxMoveBest($scope.showId, $scope.orderId).url).success(updateAndClearSelected).error(errorHandler)
       )

       $scope.selected = []
@@ -365,7 +367,7 @@
       if ($scope.quantity > 0)
         $scope.quantity -= 1
     $scope.validQuantity = ->
-      $scope.quantity > 0 && $scope.quantity <= $scope.max
+      parseInt($scope.quantity) > 0 && parseInt($scope.quantity) <= parseInt($scope.max)
     $scope.$on(UPDATE_DISABLED_SEAT_TYPE, (event, addDisabledSeatType) -> $scope.updateDisabledSeatType(addDisabledSeatType))
     $scope.updateDisabledSeatType = (addDisabledSeatType) ->
       idx = $scope.seatTypesArray.indexOf("disabled")


bubbles: using [conf.nodeIdAttr] instead of .code

--- app/charts/bubbles.coffee
+++ app/charts/bubbles.coffee
@@ -552,7 +552,7 @@

     bubble = svg.selectAll("g.bubble")
-      .data(nodes, (d) -> d.code)
+      .data(nodes, (d) -> d[conf.nodeIdAttr])

     # update subnodes data
     bubble.selectAll("circle").datum((d) -> this.parentNode.__data__)
@@ -571,7 +571,7 @@
       .attr("y", 5)
       .attr("font-size", 9)
       .attr("text-anchor", "middle")
-      .text((d)-> if d.code.length < 7 then d.code else d.code.substring(0,5)+".." )
+      .text((d)-> c = d[conf.nodeIdAttr]; if c.length < 7 then c else c.substring(0,5)+".." )

     bubbleEnter.on 'click', (d, i) ->
       if selectedNode == this


Skip Talk link on mock data

Skip Talk link on mock data

--- app/classifier/index.cjsx
+++ app/classifier/index.cjsx
@@ -111,7 +111,8 @@
       <hr />

       <nav className="task-nav">
-        <Link to="project-talk-subject" params={owner: @props.owner.login, name: @props.project.slug, id: @props.subject.id} className="talk standard-button">Talk</Link>
+        {if @props.owner? and @props.project?
+          <Link to="project-talk-subject" params={owner: @props.owner.login, name: @props.project.slug, id: @props.subject.id} className="talk standard-button">Talk</Link>}
         <button type="button" className="continue major-button" onClick={@props.onClickNext}>Next</button>
       </nav>
     </div>


changed api

changed api

--- index.coffee
+++ index.coffee
@@ -8,30 +8,26 @@
 app = express()
 server = null

-app.get ['/', '/rss/'], (req, res) ->
-  prefix = "#{server.address().address}:#{server.address().port}"
+app.get ['/'], (req, res) ->
+  str = "#{server.address().address}:#{server.address().port}"
+  #https://api.soundcloud.com/resolve?url=https%3A//soundcloud.com/david-buezas/sets/sport&client_id=02gUJC0hH2ct1EGOcYXQIzRFU91c72Ea
   if process.env.OPENSHIFT_NODEJS_IP?
-    prefix = "#{process.env.OPENSHIFT_NODEJS_IP}:#{process.env.OPENSHIFT_NODEJS_PORT}/"
-  prefix = "http://#{prefix}/rss/[username]/"
-  posfix = '?client_id=[your_client_id(optional)]'
-  res.send "usage:

-    #{prefix}tracks#{posfix}

-    #{prefix}favorites#{posfix}

-    #{prefix}playlists/[playlist_name]#{posfix}
-    "
+    str = "#{process.env.OPENSHIFT_NODEJS_IP}:#{process.env.OPENSHIFT_NODEJS_PORT}/"
+  str = "http://#{prefix}/rss?url=[soundcloud-url]&client_id=[your_client_id(optional)]"
+  res.send "usage:
#{str}"

-app.get '/rss/*', (req, res) ->
-  client_id_replaced = req.query.client_id
-  client_id_replaced ?= client_id
-
+app.get '/rss', (req, res) ->
+  unless req.query.url?
+    return res.redirect '/'
   try
-    route = req.originalUrl.substr 5
+    route = req.params[0]
     feed = new Podcast
       title: route
       itunesImage:'http://www.jasonmasi.com/sites/default/files/public/images/soundcloud-icon.png'
-
+    req.query.client_id ?= client_id
     request
-      url: "http://api.soundcloud.com/users/#{route}?client_id=#{client_id}"
+      url: "http://api.soundcloud.com/resolve"
+      qs: req.query
       json: yes
     , (err, res1, songs) ->
       return res.send err if err?


Require model into index

Require model into index

--- index.coffee
+++ index.coffee
@@ -3,6 +3,7 @@
 webserver = require './lib/webserver.coffee'
 webserver.listen 4001
 util = require './lib/util.coffee'
+model = require './lib/model.coffee'

 lock = false
 check = ->


The helper for comic links I forgot

The helper for comic links I forgot

--- index.coffee
+++ index.coffee
@@ -32,7 +32,10 @@

 # Set up handlebars
 app.set 'view engine', 'handlebars'
-app.engine 'handlebars', handlebars(defaultLayout: 'main')
+app.engine 'handlebars', handlebars
+  defaultLayout: 'main'
+  helpers:
+    comicUrl: (at) -> "/at/#{at}/"

 # View and static paths on disk
 app.set 'views', path.resolve(root, 'views')


Add CLI Help, More Lazy Data Retrieval

Add CLI Help, More Lazy Data Retrieval

--- index.coffee
+++ index.coffee
@@ -1,23 +1,33 @@
+optimist = require('optimist')
+  .usage('Amazon Trade In Price Check')
+  .alias('o', 'out')
+  .describe('o', 'Write data to JSON file. Usage: "-o data.json"')
+  .alias('t', 'table')
+  .describe('t', 'Output results as CLI table')
+
+argv = optimist.argv
+
 yaml = require('js-yaml')
-argv = require('optimist').argv

-books = require './books.yml'
+books = require('./books.yml')

 retrieve = require('./src/retrieve')

 do ->
+  if argv.h or argv.help
+    return optimist.showHelp()
+
   console.log "Using books.yml with #{books.books.length} ISBNs"
-  r = retrieve(books.books)

-  if argv.table
+  if argv.t or argv.table
     output = require('./src/tableOutput')
-    output r
+    output retrieve(books.books)

   out = argv.o or argv.out
   if typeof out is "string"
     fs = require('fs')

-    r.then (data) ->
+    retrieve(books.books).then (data) ->
       fs.writeFile out, JSON.stringify(data, null, 2), (err) ->
         return console.log "Error writing file #{out}: #{err}" if err
         console.log "Wrote data to #{out}"


activity: don't assign Reactivity class by default

--- client/activity/lib/activityappview.coffee
+++ client/activity/lib/activityappview.coffee
@@ -16,7 +16,6 @@
 isChannelCollaborative = require 'app/util/isChannelCollaborative'
 isKoding               = require 'app/util/isKoding'
 isGroup                = require 'app/util/isGroup'
-isReactivityEnabled    = require 'app/util/isReactivityEnabled'
 ChatSearchModal        = require 'app/activity/sidebar/chatsearchmodal'
 isSuggestionEnabled    = require 'activity/util/isSuggestionEnabled'

@@ -34,9 +33,6 @@

     options.cssClass   = 'content-page activity clearfix'
     options.domId      = 'content-page-activity'
-
-    if isReactivityEnabled()
-      options.cssClass = kd.utils.curry 'Reactivity', options.cssClass

     super options, data


form workflow: add requireData and collectData interfaces

--- client/app/CommonViews/formworkflow.coffee
+++ client/app/CommonViews/formworkflow.coffee
@@ -3,16 +3,36 @@
   constructor: (options = {}, data) ->
     super options, data
     @forms = {}
+    @requiredData = {}
+    @collectedData = {}
+
+  requireData: (fields) ->
+    for field in fields
+      @requiredData[field] = no
+    return this
+
+  collectData: (data) ->
+    # collect those data
+    @collectedData[field] = value  for own field, value of data
+    # remove the requirement to collect those data
+    delete @requiredData[field]
+    # return if there are more data required to collect
+    return this  for own _ of @requiredData
+    # otherwise, we are done, emit the "DataCollected" event
+    @emit 'DataCollected', @collectedData
+    return this

   addForm: (formName, form) ->
     @forms[formName] = form
     @addSubView form
     form.hide()
+    return this

   removeForm: (form) ->
     form = @getForm form
     @removeSubView form
     delete @forms[form]
+    return this

   getForm: (form) ->
     if 'string' is typeof form
@@ -23,8 +43,10 @@

   hideForms: (forms = @getFormNames()) ->
     @forms[form]?.hide() for form in forms
+    return this

   showForm: (form) ->
     @hideForms()
     form = @getForm form
-    form.show()+    form.show()
+    return this


client: add explicit return statement

client: add explicit return statement

--- client/app/lib/flux/payment/actions.coffee
+++ client/app/lib/flux/payment/actions.coffee
@@ -6,7 +6,7 @@

 loadStripeClient = ({ dispatch, evaluate }) -> ->

-  new Promise (resolve, reject) ->
+  return new Promise (resolve, reject) ->

     flags = evaluate getters.paymentValues

@@ -35,7 +35,7 @@
     exp_year  : options.cardYear
     name      : options.cardName

-  new Promise (resolve, reject) ->
+  return new Promise (resolve, reject) ->
     loadStripeClient({ dispatch, evaluate })().then ->
       Stripe.card.createToken tokenOptions, (status, response) ->
         if err = response.error


removed class which was never used

removed class which was never used

--- core/app/backbone/views/ndp_interacting_users_names_view.coffee
+++ core/app/backbone/views/ndp_interacting_users_names_view.coffee
@@ -1,6 +1,6 @@
 class NDPInteractingUsersNameView extends Backbone.Marionette.Layout
   tagName: 'span'
-  className: 'ndp-interacting-users-name separator-list-item'
+  className: 'separator-list-item'
   template: 'fact_relations/interactor_name'

 class window.NDPInteractingUsersNamesView extends Backbone.Marionette.CompositeView


Un pequeño test para el autocompletado javascript.

--- spec/javascripts/jquery.autocomplete_form_spec.coffee
+++ spec/javascripts/jquery.autocomplete_form_spec.coffee
@@ -6,14 +6,14 @@

 describe 'Autocomplete form', ->
   beforeEach ->
+    loadFixtures 'jquery.autocomplete_form.html'
+    $("form").autocompleteForm()
+
+  it "calls autocomplete on the input", ->
+    expect($("form input")).toHaveClass "ui-autocomplete-input"
     # Lo ideal sería hacer un spyOn sobre el input
     # pero no parece posible por no ser el mismo
     # objeto en los tests que al ejecutarse.
     # En jQuery:
     # jQuery(jQuery(this)[0]) != jQuery(this)
-    loadFixtures 'jquery.autocomplete_form.html'
-    $('form.filter').autocompleteForm()
-
-  it 'calls autocomplete on the input', ->
-    # expect(input.autocomplete).toHaveBeenCalled()
-
+    # expect(input.autocomplete).toHaveBeenCalled(), with url...


Errata.

Errata.

--- spec/javascripts/jquery.autocomplete_form_spec.coffee
+++ spec/javascripts/jquery.autocomplete_form_spec.coffee
@@ -11,7 +11,7 @@
     # objeto en los tests que al ejecutarse.
     # En jQuery:
     # jQuery(jQuery(this)[0]) != jQuery(this)
-    loadFixtures 'autocomplete_form.html'
+    loadFixtures 'jquery.autocomplete_form.html'
     $('form.filter').autocompleteForm()

   it 'calls autocomplete on the input', ->


getArtistList returns a sorted list

getArtistList returns a sorted list

--- src/mpd.coffee
+++ src/mpd.coffee
@@ -3,6 +3,26 @@
   MPD_INIT = /^OK MPD .+\n$/
   MPD_SENTINEL = /OK\n$/
   MPD_ACK = /^ACK \[\d+@\d+\].*\n$/
+
+  startsWith = (string, str) -> string.substring(0, str.length) == str
+  stripPrefixes = ['the ', 'a ', 'an ']
+  sortableTitle = (title) ->
+    t = title.toLowerCase()
+    for prefix in stripPrefixes
+      if startsWith(t, prefix)
+        t = t.substring(prefix.length)
+        break
+    t
+
+  titleSort = (a,b) ->
+    _a = sortableTitle(a)
+    _b = sortableTitle(b)
+    if _a < _b
+      -1
+    else if _a > _b
+      1
+    else
+      0

   constructor: ->
     @socket = io.connect "http://localhost"
@@ -37,7 +57,9 @@
     switch cmd
       when 'list artist'
         # remove the 'Artist: ' text from every line and convert to array
-        cb (line.substring(8) for line in msg.split('\n')) for cb in callbacks
+        list = (line.substring(8) for line in msg.split('\n'))
+        list.sort titleSort
+        cb list for cb in callbacks
       else
         alert "unhandled command: " + cmd


Fixed the TNM score concatenation.

Fixed the TNM score concatenation.

--- app/javascripts/services.coffee
+++ app/javascripts/services.coffee
@@ -464,9 +464,9 @@

     # Extend the subject encounters.
     for enc in subject.encounters
-      # Construst the TNM score as a single string: TxNxMx.
+      # Construst the TNM score as a single string: TxNxMxGx.
       tnm = enc.outcome.tnm
-      tnm_score = 'T'.concat(tnm.grade.toString(), 'N', tnm.lymph_status.toString(), 'M', TNM_METASTASIS[tnm.metastasis])
+      tnm_score = tnm.size.concat('N', tnm.lymph_status.toString(), 'M', TNM_METASTASIS[tnm.metastasis], 'G', tnm.grade.toString())
       _.extend enc, tnm_score: tnm_score
       # Add accordion control boolean values to subject encounters.
       _.extend enc, accordion_open: true


properly trigger route

properly trigger route

--- client/apps/monoliths/client/view.coffee
+++ client/apps/monoliths/client/view.coffee
@@ -55,7 +55,7 @@

     route = $(e.currentTarget).find('.route-value').val()

-    @pusher.triggerEvent "command", "route:#{route}", monolithId,
+    @pusher.triggerEvent "command", "route", { name: monolithId, route: route }

 module.exports.init = ->
   init()


moar defense

moar defense

--- client/apps/verticals/edit_client.coffee
+++ client/apps/verticals/edit_client.coffee
@@ -16,7 +16,7 @@
         el: $(el)
         src: (
           if $(el).attr('data-name').match 'featured_links'
-            @vertical.get('featured_links')?[$(el).attr('data-index')].thumbnail_url
+            @vertical.get('featured_links')?[$(el).attr('data-index')]?.thumbnail_url
           else
             @vertical.get $(el).attr 'data-name'
         )


Only send a subject_set_id if a workflow is grouped

--- app/pages/project/classify.cjsx
+++ app/pages/project/classify.cjsx
@@ -91,9 +91,15 @@
   createNewClassification: (project, workflowID) ->
     workflow = @getWorkflow project, workflowID
     subject = workflow.then (workflow) =>
-      workflow.get('subject_sets').then (subjectSets) =>
-        randomIndex = Math.floor Math.random() * subjectSets.length
-        subjectSet = subjectSets[randomIndex]
+      # A subject set is only specified if the workflow is grouped.
+      getSubjectSet = if workflow.grouped
+        workflow.get('subject_sets').then (subjectSets) =>
+          randomIndex = Math.floor Math.random() * subjectSets.length
+          subjectSets[randomIndex]
+      else
+        Promise.resolve()
+
+      getSubjectSet.then (subjectSet) =>
         @getNextSubject project, workflow, subjectSet

     Promise.all([workflow, subject]).then ([workflow, subject]) ->
@@ -141,8 +147,9 @@
       # console.log 'Fetching subjects'
       subjectQuery =
         workflow_id: workflow.id
-        subject_set_id: subjectSet.id
         sort: 'queued' unless SKIP_CELLECT
+      if subjectSet?
+        subjectQuery.subject_set_id = subjectSet.id

       fetchSubjects = apiClient.type('subjects').get subjectQuery
         .catch (error) ->


Add utc_offset to classification metadata

--- app/pages/project/classify.cjsx
+++ app/pages/project/classify.cjsx
@@ -111,6 +111,7 @@
           started_at: (new Date).toISOString()
           user_agent: navigator.userAgent
           user_language: counterpart.getLocale()
+          utc_offset: ((new Date).getTimezoneOffset() * 60).toString() # In seconds
         links:
           project: project.id
           workflow: workflow.id


stop mixing in props

stop mixing in props

--- client/lanes/components/shared/Icon.cjsx
+++ client/lanes/components/shared/Icon.cjsx
@@ -34,7 +34,7 @@
             'clickable'  : @props.tooltip and @props.tooltipProps.trigger is 'click'

         icon =
-            <i {...@props} style={@props.style} className={classes} />
+            <i style={@props.style} className={classes} />

         if @props.tooltip
             props = _.extend({}, @props.tooltipProps, DEFAULT_TOOLTIP_PROPS)


Tweak level schema to get WebStorm to stop thinking it's bad syntax

--- app/schemas/models/level.coffee
+++ app/schemas/models/level.coffee
@@ -244,7 +244,7 @@
   links: [{rel: 'db', href: '/db/article/{(original)}/version/{(majorVersion)}'}]
 },
   original: c.objectId(title: 'Original', description: 'A reference to the original Article.')#, format: 'hidden')  # hidden?
-  majorVersion: {title: 'Major Version', description: 'Which major version of the Article is being used.', type: 'integer', minimum: 0}#, format: 'hidden'}  # hidden?
+  majorVersion: {title: 'Major Version', description: 'Which major version of the Article is being used.', type: 'integer', minimum: 0} #, format: 'hidden'}  # hidden?

 LevelSchema = c.object {
   title: 'Level'


Zh han traditional update (#3690)

Zh han traditional update (#3690)

* Update zh-HANT.coffee

* Update zh-HANT.coffee

* Update zh-HANT.coffee

* Update zh-HANT.coffee

--- app/locale/zh-HANT.coffee
+++ app/locale/zh-HANT.coffee
@@ -279,8 +279,8 @@
     recovery_sent: "密碼重置的信件已寄出"

   items:
-    primary: "主要武器"
-    secondary: "次要武器"
+    primary: "主手裝備"
+    secondary: "副手裝備"
     armor: "裝甲"
     accessories: "飾品"
     misc: "輔助"
@@ -344,10 +344,10 @@
     confirm_password: "確認密碼"
     message: "訊息"
     code: "程式碼"
-    ladder: "升級比賽"
-    when: "當"
+    ladder: "天梯"
+    when: "何時"
     opponent: "對手"
-    rank: "階級"
+    rank: "排名"
     score: "分數"
     win: "獲勝"
     loss: "失敗"
@@ -472,7 +472,7 @@
     tip_reticulating: "網格狀鋸齒(指卡頓現象)"
     tip_harry: "巫師，"
     tip_great_responsibility: "隨著擁有強大的編程技巧，除錯的責任將越大。"
-    tip_munchkin: "如果您不吃掉您的蔬菜，那小矮人將在您沉睡時找到您。"
+    tip_munchkin: "如果您不吃掉您的蔬菜，那些矮人將在您睡覺時找到您。"
     tip_binary: "只有2種人在這世上：那些懂2進位的，和那些不懂的。"
     tip_commitment_yoda: "一個程式員必須擁有強烈的責任感和一顆認真的心。 ~ Yoda《星際大戰》"
     tip_no_try: "做，或者不做。這邊不存在嘗試的選項。 - Yoda《星際大戰》"
@@ -510,7 +510,7 @@
     tip_life_and_polymorphism: "開放源碼就像是一個完全多樣異質的結構：海納百川。"#"Open Source is like a totally polymorphic heterogeneous structure: All types are welcome."
     tip_mistakes_proof_of_trying: "程式碼中的錯誤證明了你曾經嘗試過。"#"Mistakes in your code are just proof that you are trying."
     tip_adding_orgres: "包圍敵人!"#"Rounding up ogres."
-    tip_sharpening_swords: "磨煉造就寶劍。"#"Sharpening the swords."
+    tip_sharpening_swords: "磨利劍鋒。"#"Sharpening the swords."
     tip_ratatouille: "你不需要讓任何人以你的出身決定你的極限，你唯一的限制就是你的心靈。廚神Gusteau《料理鼠王》 "#"You must not let anyone define your limits because of where you come from. Your only limit is your soul. - Gusteau, Ratatouille"
     tip_nemo: "想知道當生活使你消沉時，你必須做甚麼嗎？一直游，一直游就對了。多莉《海底總動員》"#"When life gets you down, want to know what you've gotta do? Just keep swimming, just keep swimming. - Dory, Finding Nemo"
     tip_internet_weather: "搬來網路世界吧，這裡十分不錯。我們住的地方天氣總是很好。 - John Green"#"Just move to the internet, it's great here. We get to live inside where the weather is always awesome. - John Green"
@@ -725,37 +725,37 @@
     editor_config_behaviors_description: "自動填入小括號、大括號以及引號。"

   about:
-#    main_title: "If you want to learn to program, you need to write (a lot of) code."
-#    main_description: "At CodeCombat, our job is to make sure you're doing that with a smile on your face."
-#    mission_link: "Mission"
-#    team_link: "Team"
-#    story_link: "Story"
-#    press_link: "Press"
-#    mission_title: "Our mission: make programming accessible to every student on Earth."
-#    mission_description_1: "Programming is magic. It's the ability to create things from pure imagination. We started CodeCombat to give learners the feeling of wizardly power at their fingertips by using typed code."
-#    mission_description_2: "As it turns out, that enables them to learn faster too. WAY faster. It's like having a conversation instead of reading a manual. We want to bring that conversation to every school and to every student, because everyone should have the chance to learn the magic of programming."
-#    team_title: "Meet the CodeCombat team"
-#    team_values: "We value open and respectful dialog, where the best idea wins. Our decisions are grounded in customer research and our process is focused on delivering tangible results for them. Everyone is hands-on, from our CEO to our Github contributors, because we value growth and learning in our team."
+    main_title: "如果您想要學編程，你需要寫(一大堆)程式碼。"#"If you want to learn to program, you need to write (a lot of) code."
+    main_description: "在CodeCombat中，我們的職責是確保您笑著玩遊戲。"    #"At CodeCombat, our job is to make sure you're doing that with a smile on your face."
+    mission_link: "任務"#"Mission"
+    team_link: "隊伍"#"Team"
+    story_link: "故事"#"Story"
+    press_link: "按"#"Press"
+    mission_title: "我們的任務為：讓世上每個學生都有機會能學會編程。"    #"Our mission: make programming accessible to every student on Earth."
+    mission_description_1: "編程是一種魔法。它是一種從純粹的想像中創造東西的能力。我們營運CodeCombat來讓學習者可以透過鍵入程式碼在指尖感覺到巫師般的力量。"    #"Programming is magic. It's the ability to create things from pure imagination. We started CodeCombat to give learners the feeling of wizardly power at their fingertips by using typed code."
+    mission_description_2: "它的出現也讓使用者學得更快，就像是與它對話而不是閱讀手冊一般。我們想將這種對話帶給每一個學校以及每一位學生，因為每個人都應該要有機會體會編程的魔力。 "    #"As it turns out, that enables them to learn faster too. WAY faster. It's like having a conversation instead of reading a manual. We want to bring that conversation to every school and to every student, because everyone should have the chance to learn the magic of programming."
+    team_title: "認識CodeCombat團隊。"#"Meet the CodeCombat team"
+    team_values: "我們重視開放且互相尊重的對話，好的想法才會勝出。我們的抉擇建立於客戶調查上，且過程注重於將具體的結果交給客戶。從我們的CEO到我們的Github貢獻者，每一個人都會動手參與，因為我們傾向在團隊中學習成長。"    #"   We value open and respectful dialog, where the best idea wins. Our decisions are grounded in customer research and our process is focused on delivering tangible results for them. Everyone is hands-on, from our CEO to our Github contributors, because we value growth and learning in our team."
     nick_title: "程式員" # {change}
-    nick_blurb: "亢奮的Guru"
+    nick_blurb: "亢奮的咕嚕"
     matt_title: "共同創辦人" # {change}
     cat_title: "首席開卡設計師" # {change}
     cat_blurb: "氣宗"
     scott_title: "共同創辦人" # {change}
     scott_blurb: "理性至上"
-#    maka_title: "Customer Advocate"
+    maka_title: "客戶代言人" #"Customer Advocate"
     maka_blurb: "說書人"
     rob_title: "編譯工程師" # {change}
     rob_blurb: "編寫一些的程式碼"
     josh_c_title: "遊戲設計師"
     josh_c_blurb: "設計遊戲"
-#    robin_title: "UX Design & Research"
-#    robin_blurb: "Scaffolding"
+    robin_title: "使用者體驗研究與設計"#"UX Design & Research"
+    robin_blurb: "鷹架"#"Scaffolding"
     josh_title: "遊戲設計師"
     josh_blurb: "地面是熔岩"
-#    phoenix_title: "Software Engineer"
-#    nolan_title: "Territory Manager"
-#    elliot_title: "Partnership Manager"
+    phoenix_title: "軟體工程師"#"Software Engineer"
+    nolan_title: "區域經理"#"Territory Manager"
+    elliot_title: "合作經裡"#"Partnership Manager"
     retrostyle_title: "插畫師"
     retrostyle_blurb: "復古風格的遊戲"
     jose_title: "音樂"
@@ -1566,41 +1566,41 @@

   ladder:
     please_login: "在參與對戰前請先登入。"
-    my_matches: "我的對手"
+    my_matches: "我的對戰"
     simulate: "模擬"
-    simulation_explanation: "通過模擬遊戲，您可以使您的遊戲更快得到評分！"
+    simulation_explanation: "通過模擬遊戲，您可以使您的遊戲更快得到評定排名！"
     simulation_explanation_leagues: "你主要會為你的部落或者課程的同伴幫忙模擬遊戲。"
     simulate_games: "模擬遊戲！"
     games_simulated_by: "您模擬過的次數："
     games_simulated_for: "替您模擬的次數："
     games_in_queue: "遊戲正在列隊中："
-    games_simulated: "遊戲已模擬"
+    games_simulated: "遊戲模擬次數"
     games_played: "玩過的遊戲"
-    ratio: "通過率"
+    ratio: "比率"
     leaderboard: "排行榜"
     battle_as: "我要扮演 "
     summary_your: "您的 "
     summary_matches: "對手 - "
     summary_wins: " 勝利，"
     summary_losses: " 失敗"
-    rank_no_code: "沒有新程式碼可評分"
-    rank_my_game: "對我的遊戲評分！"
+    rank_no_code: "沒有新程式碼可評定排名"
+    rank_my_game: "對我的遊戲評定排名！"
     rank_submitting: "上傳中…"
-    rank_submitted: "已上傳以求評分"
-    rank_failed: "評分失敗"
-    rank_being_ranked: "已評分"
+    rank_submitted: "已上傳以求評定排名"
+    rank_failed: "評定失敗"
+    rank_being_ranked: "已評定"
     rank_last_submitted: "已上傳 "
     help_simulate: "幫我們模擬遊戲？"
-    code_being_simulated: "您的新程式碼正在被其他人模擬評分中。分數將隨每次新的配對而更新。"
+    code_being_simulated: "您的新程式碼正在被其他人模擬評定中。分數將隨每次新的配對而更新。"
     no_ranked_matches_pre: "對這個隊伍尚未有評分過的配對！"
-    no_ranked_matches_post: " 在別人的戰場上扮演對手並且回到這使您的程式碼接受評分。"
+    no_ranked_matches_post: " 在別人的戰場上扮演對手並且回到這使您的程式碼接受評定排名。"
     choose_opponent: "選擇對手"
     select_your_language: "選擇您的語言！"
     tutorial_play: "教學"
     tutorial_recommended: "如果您尚未玩過，建議先嘗試教學"
     tutorial_skip: "略過教學"
     tutorial_not_sure: "不確定發生啥事？"
-    tutorial_play_first: "先嘗試教學"
+    tutorial_play_first: "先試試教學"
     simple_ai: "簡單人工智慧" # {change}
     warmup: "熱身"
     friends_playing: "與朋友連線"
@@ -1609,7 +1609,7 @@
     invite_friends_to_battle: "邀請您的朋友加入此戰鬥！"
     fight: "戰鬥！"
     watch_victory: "觀看您的勝利"
-    defeat_the: "擊敗"
+    defeat_the: "敗於"
     watch_battle: "觀看戰役"
     tournament_started: "，錦標賽已開始"
     tournament_ends: "錦標賽結束"


Update zh-HANT.coffee

Update zh-HANT.coffee

--- app/locale/zh-HANT.coffee
+++ app/locale/zh-HANT.coffee
@@ -66,7 +66,7 @@
     want_coco: "想要在您的學校進行CodeCombat嗎？"

   nav:
-#    map: "Map"
+    map: "地圖"
     play: "返回地圖" # The top nav bar entry where players choose which levels to play
     community: "社群"
     courses: "課程"


update zh hant (#5759)

update zh hant (#5759)

--- app/locale/zh-HANT.coffee
+++ app/locale/zh-HANT.coffee
@@ -572,10 +572,10 @@
 #    concept_challenge_unlocked: "Concept Challenge Unlocked"
 #    concept_challenge_complete: "Concept Challenge Complete!"
 #    combo_challenge_complete: "Combo Challenge Complete!"
-#    combo_challenge_complete_body: "Great job, it looks like you're well on your way to understanding __concept__!"
+    combo_challenge_complete_body: "太棒了。看來你正在掌握甚麼是 __concept__!"
 #    replay_level: "Replay Level"
 #    combo_concepts_used: "__complete__/__total__ Concepts Used"
-#    combo_all_concepts_used: "You used all concepts possible to solve the challenge. Great job!"
+    combo_all_concepts_used: "你運用了所有所學的概念來完成這個挑戰，太棒了。"
 #    combo_not_all_concepts_used: "You used __complete__ out of the __total__ concepts possible to solve the challenge. Try to get all __total__ concepts next time!"
 #    start_challenge: "Start Challenge"
     next_game: "下一個遊戲"
@@ -730,8 +730,8 @@
 #    view_gallery: "View Gallery"
 #    project_published_noty: "Your level has been published!"
 #    keep_editing: "Keep Editing"
-#    learn_new_concepts: "Learn new concepts"
-#    watch_a_video: "Watch a video on __concept_name__"
+    learn_new_concepts: "學習新概念"
+    watch_a_video: "觀看一條有關 __concept_name__ 的影片"
 #    concept_unlocked: "Concept Unlocked"
 #    use_at_least_one_concept: "Use at least one concept: "
 #    command_bank: "Command Bank"
@@ -1163,7 +1163,7 @@
     screenshots: "螢幕截圖"
     character_art: "角色美術作品"
     download_all: "下載全部"
-    previous: "上一個"
+    previous: "上一步"
     location_title: "我們位於城鎮 SF："

   teachers:
@@ -2613,9 +2613,9 @@
     trees: "資料結構 - 樹狀結構"
     variables: "變數"
     vectors: "向量"
-    while_condition_loops: "While(當) 含條件迴圈 "
-    while_loops_simple: "While(當) 迴圈"
-    while_loops_nested: "巢狀 While(當) 迴圈"
+    while_condition_loops: "While 含條件循環 "
+    while_loops_simple: "While 循環"
+    while_loops_nested: "巢狀 While 循環"
     xy_coordinates: "Cartesian Coordinates(笛卡爾座標) "
     advanced_strings: "進階字串" # Rest of concepts are deprecated
     algorithms: "算法"


Fix discuss link in summary

Fix discuss link in summary

--- app/partials/summary.cjsx
+++ app/partials/summary.cjsx
@@ -38,7 +38,7 @@

           Want to discuss this with other volunteers?

-
+
           Discuss on Talk


remove old codes

remove old codes

--- app/scripts/editor.coffee
+++ app/scripts/editor.coffee
@@ -206,12 +206,6 @@
 		# apply link URL
 		$('#toolpad').delegate 'li:first-child', 'click', (e)->
 			self.openLinkInput()
-			# if self.linkRange
-			# a = document.createElement 'a'
-			# a.setAttribute 'href', '#'
-			# self.lastLinkElement = a
-			# self.linkRange.surroundContents a
-			# document.execCommand('createlink', '#')
 		.delegate 'li:first-child', 'mouseenter', (e)->
 			self.linkRange = self.selection().getRangeAt 0
 		.delegate 'input', 'keydown', (e)->


Make ForkModal handle empty tasks

Make ForkModal handle empty tasks

--- app/views/editor/ForkModal.coco.coffee
+++ app/views/editor/ForkModal.coco.coffee
@@ -32,8 +32,9 @@
     newModel.unset 'i18nCoverage'
     newModel.set 'commitMessage', "Forked from #{@model.get('name')}"
     newModel.set 'name', @$el.find('#fork-model-name').val()
-    newModel.set 'tasks', newModel.get('tasks').map (task)=>
-      {name: task.name, complete:false}
+    if newModel.get('tasks')
+      newModel.set 'tasks', newModel.get('tasks').map (task) ->
+        {name: task.name, complete: false}
     if @model.schema().properties.permissions
       newModel.set 'permissions', [access: 'owner', target: me.id]
     newPathPrefix = "editor/#{@editorPath}/"


added disable click in only tabs factura

--- app/assets/javascripts/app.js.coffee
+++ app/assets/javascripts/app.js.coffee
@@ -1,15 +1,19 @@
-PreventOpeningInNewWindow = {
-  init: ->
-    $(document).on "click", (e) ->
-      if e.ctrlKey or e.metaKey
-        e.preventDefault()
-        $(e.target).trigger("click")
-        false
-    $(document).on "contextmenu", (e) ->
-      e.preventDefault()
-      false
-}
+# PreventOpeningInNewWindow = {
+#   init: ->
+#     $(document).on "click", (e) ->
+#       if e.ctrlKey or e.metaKey
+#         e.preventDefault()
+#         $(e.target).trigger("click")
+#         false
+#     $(document).on "contextmenu", (e) ->
+#       e.preventDefault()
+#       false
+# }

-window.Helpers = {}
+# window.Helpers = {}

-PreventOpeningInNewWindow.init()
+# PreventOpeningInNewWindow.init()
+
+$(document).ready ->
+  $("#tab_ventas").bind "contextmenu", (e) ->
+  	false


Trigger popover on click event

Trigger popover on click event

Focus is not supported on iOS touch devices

--- app/assets/javascripts/navbar.coffee
+++ app/assets/javascripts/navbar.coffee
@@ -41,4 +41,3 @@
       container: $(this)
       template: HandlebarsTemplates['notifications/base'](popoverData)
       title: I18n.t("javascript.notifications.#{notificationsType}.title")
-      trigger: 'focus'


handle error when listing bucket

handle error when listing bucket

--- app/coffee/S3PersistorManager.coffee
+++ app/coffee/S3PersistorManager.coffee
@@ -104,6 +104,9 @@
 			secret: settings.filestore.s3.secret
 			bucket: bucketName
 		s3Client.list prefix:key, (err, data)->
+			if err?
+				logger.err err:err, bucketName:bucketName, key:key, "something went wrong listing prefix in aws"
+				return callback(err)
 			keys = _.map data.Contents, (entry)->
 				return entry.Key
 			s3Client.deleteMultiple keys, callback


Workspace: Fixes for presence.

Workspace: Fixes for presence.

--- client/CollaborativeWorkspace/collaborativeworkspace.coffee
+++ client/CollaborativeWorkspace/collaborativeworkspace.coffee
@@ -81,7 +81,9 @@
       @usersRef.child(username).child("status").on "value", (snapshot) =>
         status = snapshot.val()
         if status is "online"
-          @activeUsers[username] = @users[username]  if @users[username]
+          @once "WorkspaceUsersFetched", =>
+            @activeUsers[username] = @users[username]
+
           @emit "SomeoneJoinedToTheSession", username
           @setPresenceHandler()  unless @presenceInterval
           if @amIHost()
@@ -89,7 +91,8 @@
             @addToHistory { message, by: KD.nick() }
         else if status is "offline"
           delete @activeUsers[username]
-          @killPresenceTimer()  if Object.keys(@activeUsers).length is 0
+          if Object.keys(@activeUsers).length is 0 and @amIHost()
+            @killPresenceTimer()
           @emit "SomeoneHasLeftTheSession", username
           if @amIHost()
             message = "#{username} has left the session"
@@ -345,6 +348,7 @@

   killPresenceTimer: ->
     @utils.killRepeat @presenceInterval
+    @presenceInterval = null

   showNotification: (message) ->
     return if @notification


Salva o keyCode do esc em uma constante

Salva o keyCode do esc em uma constante

--- src/coffee/portal-template-as-modal.coffee
+++ src/coffee/portal-template-as-modal.coffee
@@ -1,3 +1,5 @@
+KEYCODE_ESC = 27
+
 doBind = ->
 	$('.to-bind-modal').each ->	$(this).removeClass('to-bind-modal').on('click', openModalFromTemplate)

@@ -18,14 +20,14 @@
 	$overlay = getOverlay()
 	$container = $(containerTemplate)

-	#showModal
 	$overlay.appendTo($("body")).fadeIn()
 	$container.appendTo($("body")).fadeIn()
+
 	hideModal = ->
 		$overlay.fadeOut()
 		$container.remove()
 	hideModalOnEscapeKey = (e) ->
-		hideModal() if e.keyCode is 27 # esc
+		hideModal() if e.keyCode is KEYCODE_ESC
 		$(document).off "keyup", hideModalOnEscapeKey

 	$overlay.on "click", hideModal


radio button fix when setValue is called without a value (which should probably not happen)

--- client/Framework/components/inputs/KDInputRadioGroup.coffee
+++ client/Framework/components/inputs/KDInputRadioGroup.coffee
@@ -51,7 +51,7 @@
     # @getDomElement().find("input[value='#{value}']").parent().addClass('checked')
     @getDomElement().find("input[value='#{value}']").attr "checked","checked"
     @getDomElement().find(".kd-radio-holder").removeClass 'active'
-    @getDomElement().find(".kd-radio-holder.#{value}").addClass 'active'
+    @getDomElement().find(".kd-radio-holder.#{value}").addClass 'active' if value

   getInputElements:->
     @getDomElement().find('input')


activity: added chat footer class for the private thread pane

--- client/activity/lib/components/privatechatpane/index.coffee
+++ client/activity/lib/components/privatechatpane/index.coffee
@@ -25,7 +25,7 @@
         onInviteOthers = {@bound 'onInviteOthers'}
         onLoadMore     = { @bound 'onLoadMore' }
       />
-      <footer className='PrivateChatPane-footer'>
+      <footer className='PrivateChatPane-footer ChatPaneFooter'>
         <ChatInputWidget
           onSubmit         = { @bound 'onSubmit' }
           onCommand        = { @bound 'onCommand' }


Client: TeamSettings: Members: Implemented KodingListController instead of KdListViewController and deleted unnecessary lines.

--- client/admin/lib/views/members/teammemberscommonview.coffee
+++ client/admin/lib/views/members/teammemberscommonview.coffee
@@ -1,13 +1,12 @@
 kd                   = require 'kd'
 KDView               = kd.View
+KDSelectBox          = kd.SelectBox
+KDCustomHTMLView     = kd.CustomHTMLView
+KDHitEnterInputView  = kd.HitEnterInputView
 whoami               = require 'app/util/whoami'
-KDSelectBox          = kd.SelectBox
 MemberItemView       = require './memberitemview'
-KDCustomHTMLView     = kd.CustomHTMLView
-KDListViewController = kd.ListViewController
-KDHitEnterInputView  = kd.HitEnterInputView
 remote               = require('app/remote').getInstance()
-
+KodingListController = require 'app/kodinglist/kodinglistcontroller'

 module.exports = class TeamMembersCommonView extends KDView

@@ -16,9 +15,8 @@
     options.cssClass                 = 'members-commonview'
     options.itemLimit               ?= 10
     options.fetcherMethod          or= 'fetchMembersWithEmail'
-    options.noItemFoundWidget      or= new KDCustomHTMLView
-    options.listViewItemClass      or= MemberItemView
     options.listViewItemOptions    or= {}
+    options.listViewItemClass      or= null
     options.searchInputPlaceholder or= 'Find by name/username'
     options.showSearchFieldAtFirst or= no
     options.sortOptions            or= [
@@ -33,7 +31,6 @@

     @createSearchView()
     @createListController()
-    @fetchMembers()

   createSearchView: ->
@@ -54,15 +51,15 @@
       callback      : @bound 'search'

     @searchContainer.addSubView @searchInput = new KDHitEnterInputView
-      type        : 'text'
-      placeholder : @getOptions().searchInputPlaceholder
-      callback    : @bound 'search'
+      type          : 'text'
+      placeholder   : @getOptions().searchInputPlaceholder
+      callback      : @bound 'search'

     @searchContainer.addSubView @searchClear = new KDCustomHTMLView
-      tagName     : 'span'
-      partial     : 'clear'
-      cssClass    : 'clear-search hidden'
-      click       : =>
+      tagName       : 'span'
+      partial       : 'clear'
+      cssClass      : 'clear-search hidden'
+      click         : =>
         @searchInput.setValue ''
         @search()
         @searchClear.hide()
@@ -70,20 +67,21 @@

   createListController: ->

-    { listViewItemClass, noItemFoundWidget, listViewItemOptions } = @getOptions()
-
-    @listController       = new KDListViewController
+    { noItemFoundText, listViewItemOptions, fetcherMethod, listViewItemClass } = @getOptions()
+    group = @getData()
+
+    @listController       = new KodingListController
+      noItemFoundText     : noItemFoundText
+      lazyLoadThreshold   : .99
+      sort                : { timestamp: -1 }
+      itemClass           : listViewItemClass or MemberItemView
       viewOptions         :
         wrapper           : yes
-        itemClass         : listViewItemClass
         itemOptions       : listViewItemOptions
-      noItemFoundWidget   : noItemFoundWidget
-      useCustomScrollView : yes
-      startWithLazyLoader : yes
-      lazyLoadThreshold   : .99
-      lazyLoaderOptions   :
-        spinnerOptions    :
-          size            : { width: 28 }
+      fetcherMethod       : (query, fetchOptions, callback) =>
+        group[fetcherMethod] query, fetchOptions, (err, members) -> callback err, members
+
+    @listController.addListItems = @bound 'listMembers'

     @addSubView @listController.getView()

@@ -256,7 +254,6 @@

   handleSearchResult: (accounts) ->

-
     usernames = (profile.nickname for { profile } in accounts)

     # Send a request to back-end for user emails.


Admin: close Teams Settings modal when opening VM settings modal

--- client/admin/lib/views/resources/resourcemachineitem.coffee
+++ client/admin/lib/views/resources/resourcemachineitem.coffee
@@ -73,3 +73,18 @@

     @sidebarToggle = new kd.CustomHTMLView

+
+  destroyModal: ->
+
+    if modal = helper.findParentModal @parent
+      modal.destroy()
+
+
+  helper =
+
+    findParentModal: (view) ->
+
+      return  unless view
+      return view  if view instanceof kd.ModalView
+
+      helper.findParentModal view.parent


sidebar: render new stack icon

sidebar: render new stack icon

Signed-off-by: Umut Sirin <4055abbc0e40a1254e715b952ccd414607e0b365@gmail.com>

--- client/app/lib/sidebar/components/ownedresourceslist.coffee
+++ client/app/lib/sidebar/components/ownedresourceslist.coffee
@@ -142,7 +142,6 @@
     menuItems = {}
     { stack, template } = resource

-
     if !!resource.unreadCount
       menuItems['Update'] = { callback }

@@ -228,6 +227,12 @@
     menuOptions = { cssClass: 'SidebarMenu', x: 36, y: top + 31 }
     MENU = new kd.ContextMenu menuOptions, menuItems
     MENU.once 'KDObjectWillBeDestroyed', -> kd.utils.wait 50, -> MENU = null
+
+
+  onNewStack: (event) ->
+
+    kd.utils.stopDOMEvent event
+    kd.singletons.router.handleRoute '/Stack-Editor/New'

   renderSectionHeaderAtIndex: (sectionIndex) ->
@@ -271,7 +276,7 @@
   render: ->

-      <SectionHeader />
+      <SectionHeader onNewStack={@bound 'onNewStack'} />
       <List
         sectionClassName='SidebarSection SidebarStackSection'
         rowClassName='SidebarSection-body'
@@ -284,8 +289,9 @@

-SectionHeader = ({ children }) ->
+SectionHeader = ({ onNewStack }) ->

   <Link className='SidebarSection-headerTitle' href='/Home/stacks'>
     STACKS
+
   </Link>


activity: add cache busting mechanism to mapActivity

--- client/app/lib/socialapicontroller.coffee
+++ client/app/lib/socialapicontroller.coffee
@@ -111,17 +111,24 @@
     realtime.unsubscribeChannel channel

-  mapActivity = (data) ->
+  unbindMessageListeners = (message) ->
+
+    message._events = {}
+
+
+  mapActivity = (data, invalidateCache = no) ->

     return  unless data
     return  unless plain = data.message

-    {accountOldId, replies, interactions} = data
-    {createdAt, deletedAt, updatedAt, typeConstant}     = plain
+    {accountOldId, replies, interactions}           = data
+    {createdAt, deletedAt, updatedAt, typeConstant} = plain

     cachedItem = kd.singletons.socialapi.retrieveCachedItem typeConstant, plain.id

-    return cachedItem  if cachedItem
+    if cachedItem
+      return  cachedItem  unless invalidateCache
+      unbindMessageListeners cachedItem

     plain._id = plain.id


Update placeholder text

Update placeholder text

--- client/apps/settings/client/curations/venice_section.coffee
+++ client/apps/settings/client/curations/venice_section.coffee
@@ -238,7 +238,7 @@
             label {}, 'Email Author'
             input {
               className: 'bordered-input'
-              placeholder: 'Enter a description'
+              placeholder: 'Enter an author'
               defaultValue: @state.email_author
               onChange: @onInputChange
               name: 'email_author'


payment: remove paymentController#makePayment

This isn't going to work.  Removing for now; will reimplement soon

--- client/landing/app/MainApp/payment/paymentcontroller.coffee
+++ client/landing/app/MainApp/payment/paymentcontroller.coffee
@@ -67,28 +67,6 @@
       else
         JPaymentSubscription.fetchUserSubscriptions (err, subs) ->
           findActiveSubscription subs, planCode, callback
-
-  makePayment: (paymentMethodId, plan, amount) ->
-    vmController = KD.getSingleton('vmController')
-
-    if amount is 0
-      vmController.createGroupVM type, plan.code or plan
-
-    else if 'string' is typeof plan
-      @fetchPlanByCode plan, (err, planObj) =>
-        return  if KD.showError err
-        @makePayment paymentMethodId, planObj, amount
-
-    # else if type in ['group', 'expensed']
-    #   paymentMethod = { plan: plan.code, multiple: yes }
-    #   getGroup().makePayment paymentMethod, (err, result)->
-    #     return KD.showError err  if err
-    #     vmController.createGroupVM type, plan.code
-
-    else
-      plan.subscribe paymentMethodId, multiple: yes, (err, result)->
-        return KD.showError err  if err
-        vmController.createGroupVM type, plan.code

   deleteVM: (vmInfo, callback) ->
     type  =


Fixed intro item update text for child items.

--- client/landing/app/MainApp/sidebar/modals/adminmodal.coffee
+++ client/landing/app/MainApp/sidebar/modals/adminmodal.coffee
@@ -706,9 +706,16 @@

     super options, data

-    @utils.defer => @setAceEditor() if @getOptions().type is "Item"
-    if @getOptions().type is "Group"
+    {type, actionType} = @getOptions()
+
+    if type is "Group"
       @setVisibilityOfOverlayWarning @parentData.overlay is "no" # wtf "no"
+
+    if type is "Item"
+      @utils.defer => @setAceEditor()
+      if actionType is "Update"
+        partial = "You're updating this item from: #{@parentData.introductionItem.getData().title}"
+        @inputs.GroupName.updatePartial partial

   save: ->
     options      = @getOptions()


Fix tutorial

Fix tutorial

--- app/controllers/classify_page.coffee
+++ app/controllers/classify_page.coffee
@@ -101,11 +101,11 @@
   activate: ->
     super
     html.addClass 'on-classify'
-    @tutorial.dialog.open() if Subject.current?.metadata.tutorial
+    @tutorial.dialog.el.show() if Subject.current?.metadata.tutorial

   deactivate: ->
     super
     html.removeClass 'on-classify'
-    @tutorial.dialog.close() if Subject.current?.metadata.tutorial
+    @tutorial.dialog.el.hide() if Subject.current?.metadata.tutorial

 module.exports = ClassifyPage


interval update new node

interval update new node

--- assets/scripts/controllers/task.coffee
+++ assets/scripts/controllers/task.coffee
@@ -190,7 +190,7 @@
         from: data.from
         active: true
         visible: false
-      jobInfo.flushDom data.from
+        isNew: true

   socket.on 'workerOut', (data) ->
     return unless jobInfo.workers[data.from]
@@ -201,6 +201,9 @@
     state: document.getElementsByClassName("tstate")[0]
     count: document.getElementsByClassName("tsourceNum")[0]
   setInterval (->
+    for w,x of jobInfo.workers when x.isNew
+      delete x.isNew
+      jobInfo.flushDom w
     nodeCnt = (w for w,x of jobInfo.workers when x.active).length
     headBar.count.innerHTML = nodeCnt
     activeCnt = (w for w,x of jobInfo.workers when x.active).length


Inlined variables

Inlined variables

--- core/app/backbone/views/wheel/interactive_wheel_view.coffee
+++ core/app/backbone/views/wheel/interactive_wheel_view.coffee
@@ -6,7 +6,7 @@
       @toggleActiveOpinionType opinion_type
       if @model.isUserOpinion(opinion_type)
         $.ajax
-          url: "/facts/" + fact_id + "/opinion/" + opinion_type + "s.json"
+          url: "/facts/#{fact_id}/opinion/#{opinion_type}s.json"
           type: "POST"
           success: (data) =>
             @model.updateTo data.authority, data.opinion_types
@@ -21,7 +21,7 @@
       else
         $.ajax
           type: "DELETE"
-          url: "/facts/" + fact_id + "/opinion.json"
+          url: "/facts/#{fact_id}/opinion.json"
           success: (data) =>
             @model.updateTo data.authority, data.opinion_types
             mp_track "Factlink: De-opinionate",


[node_modules_koding][broker] added durable parameter into exchange creation options

--- node_modules_koding/broker/lib/broker.coffee
+++ node_modules_koding/broker/lib/broker.coffee
@@ -211,7 +211,7 @@
   emit: (channel, event, payload, options = {}) ->
     {durable, autoDelete} = options
     exchangeOptions =
-      durable     : no  #unless durable? then true else durable
+      durable     : unless durable? then no else durable
       autoDelete  : if autoDelete? then autoDelete else yes
       type        : 'fanout'


fixed a description of the method del()

fixed a description of the method del()

--- _src/lib/node_cache.coffee
+++ _src/lib/node_cache.coffee
@@ -156,11 +156,11 @@

 	# ## del
 	#
-	# remove a key
-	#
-	# **Parameters:**
-	#
-	# * `key` ( String | String[] ): cache key to delete or a array of cache keys
+	# remove keys
+	#
+	# **Parameters:**
+	#
+	# * `keys` ( String | String[] ): cache key to delete or a array of cache keys
 	# * `[cb]` ( Function ): Callback function
 	#
 	# **Return**
@@ -171,8 +171,8 @@
 	#
 	#     myCache.del( "myKey" )
 	#
-	#     myCache.del( "myKey", ( err, success )->
-	#       console.log( err, success )
+	#     myCache.del( "myKey", ( err, delCount )->
+	#       console.log( err, delCount )
 	#
 	del: ( keys, cb )=>
 		# convert a string to an array of one key


Add existential test

Add existential test

--- admin/static/coffee/router.coffee
+++ admin/static/coffee/router.coffee
@@ -127,16 +127,19 @@
         namespace = namespaces.get(id)

         @current_view.destroy()
-        if namespace? then @current_view = new NamespaceView.Container model:namespace
-        else @current_view = new NamespaceView.NotFound id
+        if namespace?
+            @current_view = new NamespaceView.Container model:namespace
+        else
+            @current_view = new NamespaceView.NotFound id

         if tab?
             @$container.html @current_view.render(tab).el
         else
             @$container.html @current_view.render().el
-
-        @current_view.overview.render_data_repartition()
-        @current_view.overview.render_data_in_memory()
+
+        if namespace?
+            @current_view.overview.render_data_repartition()
+            @current_view.overview.render_data_in_memory()

         @sidebar.set_type_view()


Remove version endpoint. There is a better way to do that.

--- kills.coffee
+++ kills.coffee
@@ -2,7 +2,6 @@
 #
 # OscarRobo losses - Ask about recent losses by your corp
 # OscarRobo kills - Ask about recent kills by your corp
-# OscarRobo version - Ask Oscar about what version he is running
 # I hate <target> - Oscar will insult <target>
 # (and in random conversation) - Oscar will otherwise snark in channel, spam with image macros, and othwerwise be a twit until banned

@@ -58,9 +57,6 @@
       )()
   robot.hear /^kills$|^losses$/i, (msg) ->
     msg.send "you didn't say Simon Says!"
-
-  robot.respond /version/i, (msg) ->
-    msg.send "I'm running version #{version}"

   robot.hear /damn|fucking/i, (msg) ->
     msg.send msg.random schadenfreude


videoLinkShell: add loops-button class to subview

--- coffee/src/shells/videolink.shell.coffee
+++ coffee/src/shells/videolink.shell.coffee
@@ -234,6 +234,7 @@
       buttonName: 'loops:'
       data: loopsButtonData
       initialView: initialView
+      extraClasses: ['loops-button']

   render: =>


mainly fix bug that prevented find/replace/goto line from working

--- src/smc-webapp/code-editor/actions.coffee
+++ src/smc-webapp/code-editor/actions.coffee
@@ -97,8 +97,10 @@
         return

     set_active_id: (active_id) =>
-        @tree_ops = tree_ops
         local = @store.get('local_view_state')
+        if local?.get('active_id') == active_id
+            # already set -- nother more to do
+            return
         if tree_ops.is_leaf_id(local?.get('frame_tree'), active_id)
             @setState(local_view_state : @store.get('local_view_state').set('active_id', active_id))
             @_save_local_view_state()
@@ -240,9 +242,9 @@
         if explicit and @redux.getStore('account')?.getIn(['editor_settings', 'strip_trailing_whitespace'])
             @delete_trailing_whitespace()
         @_do_save =>
-            if @_syncstring?.has_unsaved_changes()
-                # do it again...
-                @_do_save()
+            # do it again...
+            setTimeout((=>if @_syncstring?.has_unsaved_changes() then @_do_save()), 3000)
+
         if explicit
             @_active_cm()?.focus()


Fix daft typos

Fix daft typos

--- server-ce/settings.coffee
+++ server-ce/settings.coffee
@@ -462,7 +462,7 @@
 			bindDn: process.env["SHARELATEX_LDAP_BIND_DN"]
 			bindCredentials: process.env["SHARELATEX_LDAP_BIND_CREDENTIALS"]
 			bindProperty: process.env["SHARELATEX_LDAP_BIND_PROPERTY"]
-			searchBase: process.env["SHARELATEX_LDAP_SEARCHBASE"]
+			searchBase: process.env["SHARELATEX_LDAP_SEARCH_BASE"]
 			searchScope: process.env["SHARELATEX_LDAP_SEARCH_SCOPE"]
 			searchFilter: process.env["SHARELATEX_LDAP_SEARCH_FILTER"]
 			searchAttributes: (
@@ -502,7 +502,7 @@
 					try
 						parseInt(_ldap_connect_timeout)
 					catch e
-						console.error "Cannot parse SHARELATEX_CONNECTLDAP_TIMEOUT"
+						console.error "Cannot parse SHARELATEX_LDAP_CONNECT_TIMEOUT"
 				else
 					undefined
 			)


default to all permissions

default to all permissions

--- components/ManageSteps/ManageSteps.coffee
+++ components/ManageSteps/ManageSteps.coffee
@@ -21,7 +21,7 @@
       projectId: projectId
       stepIds: stepsByProject?.items || []
       fetching: stepsByProject?.isFetching
-      permissions: permissions || []
+      permissions: permissions || ['CREATE', 'UPDATE', 'DELETE']

     React.createElement ManageStepsView, props


Update view when title changes

Update view when title changes

--- scripts/views/workspace/sidebar/toc.coffee
+++ scripts/views/workspace/sidebar/toc.coffee
@@ -43,3 +43,4 @@
     changeTitle: ->
       title = prompt('Enter a new Title', @model.get('title'))
       if title then @model.set('title', title)
+      @render()


make files only available for members (fixes #24)

--- server/collections.coffee
+++ server/collections.coffee
@@ -23,7 +23,7 @@
 	insert: (userId, doc) -> Roles.userIsInRole userId, 'admin', doc.projectId
 	update: (userId, doc) -> Roles.userIsInRole userId, 'admin', doc.projectId
 	remove: (userId, doc) -> Roles.userIsInRole userId, 'admin', doc.projectId
-	download: -> true
+	download: (userId, doc) -> Roles.userIsInRole userId, Permissions.member, doc.projectId

 Pictures.allow
 	insert: validUserOrTeamOrMeeting


add to_s method to date parser

add to_s method to date parser

--- lib/assets/javascripts/dateparser.coffee
+++ lib/assets/javascripts/dateparser.coffee
@@ -2,7 +2,11 @@
 class root.DateParser
   constructor: (object)->
     @object = object
-  evaluate: ->
+  formatDate: (date)->
+    date.getFullYear() + '-' + ('0' + (date.getMonth() + 1))[-2..-1] + '-' + ( '0' + date.getDate())[-2..-1]
+  to_s: ->
+    @evaluate(true)
+  evaluate: (string_flag)->
     for i of @object
       if @object[i] != null and typeof @object[i] == 'object'
         new DateParser(@object[i]).evaluate()
@@ -10,7 +14,8 @@
         if !!@object[i].match(/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/)
           time = new Date(@object[i])
           time.setTime( time.getTime() + time.getTimezoneOffset()*60*1000 ) #offset timezone
-          @object[i] = time
+          @object[i] = if string_flag then @formatDate(time) else time
         else if !!@object[i].match(/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])T[0-9]{2}\:[0-9]{2}\:[0-9]{2}\.[0-9]{3}[A-Z]$/)
-          @object[i] = new Date(@object[i])
+          time = new Date(@object[i])
+          @object[i] = if string_flag then @formatDate(time) else time
     return


Bug fix

Bug fix

--- lib/test_generators/backbone_rest.coffee
+++ lib/test_generators/backbone_rest.coffee
@@ -152,7 +152,7 @@
           .end (err, res) ->
             assert.ok(!err, "no errors: #{err}")
             assert.equal(res.status, 200, "status not 200. Status: #{res.status}. Body: #{util.inspect(res.body)}")
-            assert.deepEqual(expected = _.map(MODELS_JSON, (item) -> item['name']), actual = sortO(res.body, 'name'), "Expected: #{util.inspect(expected)}. Actual: #{util.inspect(actual)}")
+            assert.deepEqual(expected = sortA(_.map(MODELS_JSON, (item) -> item['name'])), actual = sortA(res.body), "Expected: #{util.inspect(expected)}. Actual: #{util.inspect(actual)}")
             done()

       it 'should select requested values by single key (in array)', (done) ->


Fix drawer behvaior selector

Fix drawer behvaior selector

--- assets/javascripts/application.coffee
+++ assets/javascripts/application.coffee
@@ -45,6 +45,10 @@
 $('#add-symbol').click ->
   $('.add-symbol-wrapper').toggleClass('visible')

+$('.stock-list li').click ->
+  console.log 'hi'
+  $(@).toggleClass('show-drawer')
+
 window.bindEvents =  ->

   $(document).on 'click', '.remove', (e) ->
@@ -53,9 +57,6 @@
     window.stocks = _.reject(window.stocks, (symbol) -> symbol is stock)
     localStorage.setItem('stocks', window.stocks)
     $('li[name=' + stock + ']').remove()
-
-  $('.stock-list li').click ->
-    $(this).toggleClass('show-drawer')

   $('#add-symbol-form').submit (event) ->
     event.preventDefault()


Set proper alpha on nonselection glyph

Set proper alpha on nonselection glyph

--- bokehjs/src/coffee/api/plotting.coffee
+++ bokehjs/src/coffee/api/plotting.coffee
@@ -156,11 +156,11 @@
   _default_color: "#1f77b4"
   _default_alpha: 1.0

-  _pop_colors_and_alpha: (cls, attrs, prefix="") ->
+  _pop_colors_and_alpha: (cls, attrs, prefix="", default_color=@_default_color, default_alpha=@_default_alpha) ->
       result = {}

-      color = _with_default(attrs[prefix + "color"], @_default_color)
-      alpha = _with_default(attrs[prefix + "alpha"], @_default_alpha)
+      color = _with_default(attrs[prefix + "color"], default_color)
+      alpha = _with_default(attrs[prefix + "alpha"], default_alpha)

       delete attrs[prefix + "color"]
       delete attrs[prefix + "alpha"]
@@ -210,7 +210,7 @@
     has_hglyph = _.any(_.keys(attrs), (key) -> key.startsWith("hover_"))

     glyph_ca   = @_pop_colors_and_alpha(cls, attrs)
-    nsglyph_ca = @_pop_colors_and_alpha(cls, attrs, "nonselection_")
+    nsglyph_ca = @_pop_colors_and_alpha(cls, attrs, "nonselection_", undefined, 0.1)
     sglyph_ca  = if has_sglyph then @_pop_colors_and_alpha(cls, attrs, "selection_") else {}
     hglyph_ca  = if has_hglyph then @_pop_colors_and_alpha(cls, attrs, "hover_") else {}


Use ENV.config instead of injected config

--- assets/scripts/app/routes/main.coffee
+++ assets/scripts/app/routes/main.coffee
@@ -1,6 +1,7 @@
 require 'routes/route'

 TravisRoute = Travis.Route
+config = ENV.config

 Route = TravisRoute.extend
   renderTemplate: ->
@@ -17,7 +18,7 @@

   activate: ->
     # subscribe to pusher only if we're at a main route
-    if @config.pusher.channels
-      @get('pusher').subscribeAll(@config.pusher.channels)
+    if config.pusher.channels
+      @get('pusher').subscribeAll(config.pusher.channels)

 Travis.MainRoute = Route


Modify grid-graph to support uneven lines

--- examples/grid-graph/path.coffee
+++ examples/grid-graph/path.coffee
@@ -40,6 +40,7 @@
      marker-start={start} marker-end={end}/>
   </symbol>

+'': blank
 ' ': blank
 O: vertex
 o: vertex


Convert authors in results.items to author objects

--- src/scripts/models/search-results.coffee
+++ src/scripts/models/search-results.coffee
@@ -19,3 +19,16 @@
       @query = options.query or ''
       @fetch
         success: () => @set('loaded', true)
+
+    parse: (response, options) ->
+      response = super(arguments...)
+
+      authors = new Backbone.Collection()
+      _.each response.results.limits, (limit) ->
+        if limit.author then authors.add(limit.author)
+
+      _.each response.results.items, (item) ->
+        _.each item.authors, (author, index) ->
+          item.authors[index] = authors.get(author).toJSON()
+
+      return response


Explicitly sort fairs chronologically by start_at date

--- src/mobile/apps/fair_organizer/routes.coffee
+++ src/mobile/apps/fair_organizer/routes.coffee
@@ -42,6 +42,7 @@
   request = fairs.fetch
     data:
       fair_organizer_id: fairOrg.id
+      sort: "-start_at"
     success: (models, response, options)->
       articles = new Articles()


select using incorrect parameter to limit fields

--- src/tower/model/cursor/operations.coffee
+++ src/tower/model/cursor/operations.coffee
@@ -174,7 +174,7 @@
   #
   # @return [Array] returns the fields for this cursor.
   select: ->
-    @_fields = _.flatten _.args(fields)
+    @_fields = _.flatten _.args(arguments)
     @invalidate()
     @


auto assigned uuids should respect idAttribute

--- subtree/bokehjs/static/coffee/base.coffee
+++ subtree/bokehjs/static/coffee/base.coffee
@@ -222,9 +222,9 @@
     super(attrs, options)
     @properties = {}
     @property_cache = {}
-    if not _.has(attrs, 'id')
+    if not _.has(attrs, @idAttribute)
       this.id = _.uniqueId(this.type)
-      this.attributes['id'] = this.id
+      this.attributes[@idAttribute] = this.id
     _.defer(() =>
       if not @inited
         @dinitialize(attrs, options))