sankey: further simplification

sankey: further simplification

--- app/assets/javascripts/d3/sankey.coffee
+++ app/assets/javascripts/d3/sankey.coffee
@@ -232,7 +232,7 @@
     # returns an array of the other nodes that belong to the same column. This
     # is used by the +y_offset+ method to calculate the right node position
     siblings: =>
-      items = _.groupBy(@view.node_list.models, (node) -> node.get('column'))
+      items = _.groupBy(@view.node_list, (node) -> node.get('column'))
       items[@get 'column']

     label: => @get('label') || @get('id')
@@ -241,8 +241,8 @@
     initialize: =>
       @view = D3.sankey.view
       @series = @view.series
-      @left = @view.node_list.get @get('left')
-      @right = @view.node_list.get @get('right')
+      @left  = @view.node_map[@get('left')]
+      @right = @view.node_map[@get('right')]
       if @get('gquery')
         @gquery = new ChartSerie
           gquery_key: @get('gquery')
@@ -326,7 +326,11 @@
         @use_present = true
       else
         k = @key
-      @node_list = new namespace.NodeList(namespace.charts[k].data.nodes)
+      @node_map = {}
+      @node_list = _.map namespace.charts[k].data.nodes, (n) =>
+        node = new D3.sankey.Node(n)
+        @node_map[n.id] = node
+        node
       @link_list = _.map namespace.charts[k].data.links, (l) -> new D3.sankey.Link(l)
       @initialize_defaults()

@@ -382,7 +386,7 @@
     draw_nodes: =>
       colors = d3.scale.category20()
       nodes = @svg.selectAll("g.node")
-        .data(@node_list.models, (d) -> d.get('id'))
+        .data(@node_list, (d) -> d.get('id'))
         .enter()
         .append("g")
         .attr("class", "node")
@@ -447,14 +451,14 @@

     # this method is called every time we're updating the chart
     refresh: =>
-      max_height = @node_list.max_column_value()
+      max_height = @max_column_value()

       # update the scaling function
       @y.domain([0, max_height * 1.25])
         .range([0, @height * .90])

       # update the node label
-      @nodes.data(@node_list.models, (d) -> d.get('id'))
+      @nodes.data(@node_list, (d) -> d.get('id'))
         .attr("data-tooltip", (d) =>
           h = "<strong>
             #{d.label()}: #{Metric.autoscale_value d.value(), 'PJ', 2}
@@ -487,14 +491,11 @@
           if link.value() == 0.0 then 'none' else 'inline'
         )

-class D3.sankey.NodeList extends Backbone.Collection
-  model: D3.sankey.Node
-
-  # returns the height of the tallest column
-  max_column_value: =>
-    sums = {}
-    @each (n) ->
-      column = n.get 'column'
-      sums[column] = sums[column] || 0
-      sums[column] += n.value()
-    _.max _.values(sums)
+    # returns the height of the tallest column
+    max_column_value: =>
+      sums = {}
+      for n in @node_list
+        column = n.get 'column'
+        sums[column] = sums[column] || 0
+        sums[column] += n.value()
+      _.max _.values(sums)


live => on, por actualización de jquery a 1.9

--- app/assets/javascripts/layout.js.coffee
+++ app/assets/javascripts/layout.js.coffee
@@ -5,8 +5,7 @@
     return false

   # Maneja la respuesta del controlador que pregunta xhr?
-  $('#paginacion')
-    .live('ajax:success', (evt, data, status, xhr) ->
+  $(document).on('ajax:success', '#paginacion', (evt, data, status, xhr) ->
       $('#lista').replaceWith(data)
       $('body').css('cursor', 'auto') )


Fixed event not being resolved to window.event in firefox by using the jQuery provided event instead

--- app/assets/javascripts/sheets.js.coffee
+++ app/assets/javascripts/sheets.js.coffee
@@ -62,8 +62,8 @@
       window.location = $($(this).data('target')).attr('action') + '.' + $(this).data('format') + '?' + $($(this).data('target')).serialize()
       false
     )
-    .on('click', "[data-link]", () ->
-      if nonStandardClick(event)
+    .on('click', "[data-link]", (e) ->
+      if nonStandardClick(e)
         window.open($(this).data("link"))
         return false
       else


alt strict label filtering

alt strict label filtering

--- app/assets/javascripts/zvg/point.coffee
+++ app/assets/javascripts/zvg/point.coffee
@@ -185,14 +185,14 @@
     labels.exit().remove()

     if @_strict_scale
-      data = (({ value: value, label: label }) for value, label of @_strict_scale)
+      data = (({ value: value, label: label }) for value, label of @_strict_scale when "#{value}" isnt label)
       keys = @y_scale.selectAll('text.key_label').data(data)
       keys.enter()
         .append('text').attr('class', 'key_label')
         .style('fill', ZVG.flatUIColors['CONCRETE'])
       keys.attr('x', @x_offset + 5)
         .attr('y', (d) => @value_domain(d.value))
-        .text((d) -> if "#{d.value}" is d.label then "" else d.label)
+        .text((d) -> d.label)
       keys.exit().remove()


scrolliness is a made up word but should be spelled correctly

--- app/components/shibe-transaction.coffee
+++ app/components/shibe-transaction.coffee
@@ -4,7 +4,7 @@
   classNames: ['shibe-transaction']
   classNameBindings: ['isCredit:credit:debit', 'isPending:pending']
   didInsertElement: ->
-    @send 'assignScrollyness'
+    @send 'assignScrolliness'
   personField: (->
     if @get('transaction.status') == Transaction.STATUS.DEPOSIT
       "deposit"
@@ -57,8 +57,8 @@
   ).property 'isCredit'

   actions:
-    assignScrollyness: ->
-      scrollynessAssignment = =>
+    assignScrolliness: ->
+      scrollinessAssignment = =>
         @get('scrollElements').forEach (element) ->
           if element.outerWidth() < element[0].scrollWidth
             element.addClass('scroll-active')
@@ -68,7 +68,7 @@
       @$('.scroll-content').each ->
         scrollElements.push $(this)
       @set 'scrollElements', scrollElements
-      scrollynessAssignment()
-      $(window).bind 'resize', scrollynessAssignment
+      scrollinessAssignment()
+      $(window).bind 'resize', scrollinessAssignment

 `export default ShibeTransaction`


Update HumanReadableLogsRules.coffee

Update HumanReadableLogsRules.coffee

--- services/web/public/coffee/ide/human-readable-logs/HumanReadableLogsRules.coffee
+++ services/web/public/coffee/ide/human-readable-logs/HumanReadableLogsRules.coffee
@@ -20,7 +20,7 @@
 		regexToMatch: /Missing [{$] inserted./
 		extraInfoURL: "https://www.sharelatex.com/learn/Errors/Missing_$_inserted"
 		humanReadableHint: """
-			Check that your $'s match around math expressions. If they do, then you've probably used a symbol in normal text that needs to be in math mode. Symbols such as subscripts ( _ ), integrals ( \\int ), Greek letters ( \\alpha, \\beta, \\delta ), and modifiers (\\vec{x}, \\tilde{x} ) must be written in math mode. See the full list here.If you intended to use mathematics mode, then use $ \u2026 $ for 'inline math mode', $$ \u2026 $$ for 'display math mode' or alternatively \begin{math} \u2026 \end{math}.
+			Check that your $'s match around math expressions. If they do, then you've probably used a symbol in normal text that needs to be in math mode. Symbols such as subscripts ( _ ), integrals ( \\int ), Greek letters ( \\alpha, \\beta, \\delta ), and modifiers (\\vec{x}, \\tilde{x} ) must be written in math mode. See the full list here.If you intended to use mathematics mode, then use $ \u2026 $ for 'inline math mode', $$ \u2026 $$ for 'display math mode' or alternatively \\begin{math} \u2026 \\end{math}.
 		"""
 	,
 		regexToMatch: /(undefined )?[rR]eference(s)?.+(undefined)?/


Added bookmarklet script

Added bookmarklet script

--- app/js/loader/loader_bookmarklet.coffee
+++ app/js/loader/loader_bookmarklet.coffee
@@ -1 +1,12 @@
+window.FactlinkConfig ?= {}
+window.FactlinkConfig.api ?= 'https://factlink.com:443'
+window.FactlinkConfig.lib ?= 'https://static.factlink.com:443/lib/dist'
+window.FactlinkConfig.srcPath ?= '/factlink.core.min.js'
+window.FactlinkConfig.client = 'bookmarklet'
+
+console.info window.FACTLINK_LOADED
+
 window.FACTLINK_START_LOADER()
+
+window.FACTLINK.startHighlighting()
+window.FACTLINK.startAnnotating()


fix: remove unnecessary code

fix: remove unnecessary code

--- app/routes/authenticated/project.coffee
+++ app/routes/authenticated/project.coffee
@@ -1,7 +1,7 @@
 `import Ember from 'ember'`
-`import ScrollTopMixin from 'irene/mixins/scroll-top'`

-AuthenticatedProjectRoute = Ember.Route.extend ScrollTopMixin,
+
+AuthenticatedProjectRoute = Ember.Route.extend

   model: (params)->
     @store.findRecord "project", params.projectId


:lipstick: Use @constructor to refer to class name.

--- src/browser/atom-window.coffee
+++ src/browser/atom-window.coffee
@@ -21,7 +21,7 @@
     global.atomApplication.addWindow(this)

     @setupNodePath(@resourcePath)
-    @browserWindow = new BrowserWindow show: false, title: 'Atom', icon: AtomWindow.iconPath
+    @browserWindow = new BrowserWindow show: false, title: 'Atom', icon: @constructor.iconPath
     @browserWindow.restart = _.wrap _.bind(@browserWindow.restart, @browserWindow), (restart) =>
       @setupNodePath(@resourcePath)
       restart()


Add sftp function to get stats

Add sftp function to get stats

--- src/coffee/helpers/sftp.coffee
+++ src/coffee/helpers/sftp.coffee
@@ -30,6 +30,22 @@
           sftp.close handle

     deferred.promise
+
+  ###*
+   * {@link https://github.com/mscdex/ssh2#stats}
+   * Get directory statistics (useful to check if file is directory or file)
+   * @param {Object} sftp SFTP handle
+   * @param {String} path Path to the file where to get the stats from
+   * @return {Promise} A promise, fulfilled with an {Array} or rejected with an error
+  ###
+  stats: (sftp, path) ->
+    d = Q.defer()
+    sftp.stat path, (err, stats) ->
+      if err
+        d.reject err
+      else
+        d.resolve stats
+    d.promise

   readFile: (fileName) ->
     # TODO


Usa html() caso passe a propriedade content.html

--- src/coffee/vtex-message.coffee
+++ src/coffee/vtex-message.coffee
@@ -85,11 +85,18 @@
 		$(@domElement).addClass(@prefixClassForType+@type+" "+@id+" "+@classes.MESSAGEINSTANCE)
 		$(@domElement).hide()
 		$(@domElement).data('vtex-message', @)
-		if @content.title and @content.title isnt ''
-			$(@classes.TITLE, @domElement).text(@content.title)
-		else
-			$(@classes.TITLE, @domElement).hide()
-		$(@classes.DETAIL, @domElement).text(@content.detail)
+		if @content.html
+			if @content.title and @content.title isnt ''
+				$(@classes.TITLE, @domElement).html(@content.title)
+			else
+				$(@classes.TITLE, @domElement).hide()
+			$(@classes.DETAIL, @domElement).html(@content.detail)
+		else
+			if @content.title and @content.title isnt ''
+				$(@classes.TITLE, @domElement).text(@content.title)
+			else
+				$(@classes.TITLE, @domElement).hide()
+			$(@classes.DETAIL, @domElement).text(@content.detail)

 		if @usingModal
 			$(@domElement).on 'hidden', => @visible = false
@@ -269,8 +276,9 @@
 			messageObj =
 				type: 'fatal'
 				content:
-					title: globalError
+					title: globalError
 					detail: errorMessage
+					html: true
 				close: globalClose

 			@addMessage(messageObj, true)


Don't show poll tooltips forever/too often/too soon

--- app/views/play/CampaignView.coco.coffee
+++ app/views/play/CampaignView.coco.coffee
@@ -1216,6 +1216,7 @@
       return @promptForSubscription campaignSlug, 'premium campaign switch clicked'

   loadUserPollsRecord: ->
+    return if storage.load 'ignored-poll'
     url = "/db/user.polls.record/-/user/#{me.id}"
     @userPollsRecord = new UserPollsRecord().setURL url
     onRecordSync = ->
@@ -1238,7 +1239,8 @@
       return if @destroyed
       tempLoadingPoll.url = -> '/db/poll/' + @id
       @poll = tempLoadingPoll
-      _.delay (=> @activatePoll?(forceShowPoll)), 1000
+      delay = if forceShowPoll then 1000 else 5000  # Wait a little bit before showing the poll
+      _.delay (=> @activatePoll?(forceShowPoll)), delay
     onPollError = (poll, response, request) ->
       if response.status is 404
         console.log 'There are no more polls left.'
@@ -1259,6 +1261,11 @@
       @showPoll()
     else
       $pollButton.tooltip 'show'
+      _.delay (=>
+        $pollButton?.tooltip 'hide'
+        unless @destroyed
+          storage.save 'ignored-poll', true, 5  #  Don't show again in next N minutes
+      ), 20000  # Don't leave the poll open forever

   showPoll: ->
     return false unless @shouldShow 'poll'


oneOfType takes an array as argument

oneOfType takes an array as argument

Silences "undefined checker type" warning

--- src/components/task-plan/chapter-section.cjsx
+++ src/components/task-plan/chapter-section.cjsx
@@ -5,10 +5,10 @@
 module.exports = React.createClass
   displayName: 'ChapterSection'
   propTypes:
-    section: React.PropTypes.oneOfType(
+    section: React.PropTypes.oneOfType([
       React.PropTypes.array
       React.PropTypes.string
-    ).isRequired
+    ]).isRequired

   componentWillMount: ->
     @setState(skipZeros: false)


don't return falsy values from event listeners, this prevents other listeners from running

--- tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/zone-refresh.coffee
+++ tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/zone-refresh.coffee
@@ -34,11 +34,14 @@

       # Whenever the zone updates, we can clear the executing flag.

-      zone.on events.zone.didUpdate, -> executing = false
+      zone.on events.zone.didUpdate, ->
+        executing = false
+        return

       cleanUp = ->
         window.clearInterval intervalId
         zone = null
+        return

       handler = ->
         # Don't clog things up if the response rate is too slow


Don't fetch external resources on server side

--- nodejs/palsu.coffee
+++ nodejs/palsu.coffee
@@ -20,7 +20,7 @@
         require: [ 'jquery-browserify' ]

 jsdom.defaultDocumentFeatures =
-    FetchExternalResources: ['script'],
+    FetchExternalResources: false,
     ProcessExternalResources: false

 # Serve the list of meetings for /


Use the selection box's background color for the outline if given

--- src/core/canvasRenderer.coffee
+++ src/core/canvasRenderer.coffee
@@ -90,7 +90,7 @@
         shape._br.width + shape.margin * 2,
         shape._br.height + shape.margin * 2)
     ctx.lineWidth = 1
-    ctx.strokeStyle = '#000'
+    ctx.strokeStyle = shape.backgroundColor or '#000'
     ctx.setLineDash([2, 4])
     ctx.strokeRect(
       shape._br.x - shape.margin, shape._br.y - shape.margin,


fixed bleed issues

fixed bleed issues

--- oridomi.coffee
+++ oridomi.coffee
@@ -682,11 +682,11 @@
           when 'left'
             '0, 0, 0)'
           when 'right'
-            "-#{ @vPanels * .5 }px, 0, 0)"
+            "-#{ @_settings.vPanels * 1 }px, 0, 0)"
           when 'top'
             '0, 0, 0)'
           when 'bottom'
-            "0, #{ @hPanels * .5 }px, 0)"
+            "0, -#{ (@_settings.hPanels + 2) * 1 }px, 0)"

   _stageReset: (anchor, cb) =>


course may be null in specs

course may be null in specs


fix: correct error message for form message

--- app/scripts/app.coffee
+++ app/scripts/app.coffee
@@ -50,11 +50,13 @@
       $(element).parent().removeClass('has-additional')

     errorPlacement: (error, element) ->
-      $wrap = $('
')
-      $wrap.html(error).insertAfter(element);
+      $wrap = $('
');
+      $icon = $('').appendTo( $wrap );
+      error.appendTo($wrap);
+      $wrap.insertAfter(element);

     messages:
-      message: 'We need to know what would you like our help you with.'
+      message: 'Please tells us what would you like our help with.'
       name: 'Please tell us your name so we know what to call you.'
       email:
         required: 'Please tell us your email address so we can get in touch.'


Restrict serilizer duplicate checking to strings.  This was incorrectly triggered by other object types.

--- dl/src/chain/serializer_types.coffee
+++ dl/src/chain/serializer_types.coffee
@@ -228,10 +228,10 @@
     validate: (array)->
         dup_map = {}
         for o in array
-            o_str = JSON.stringify(o)
-            if dup_map[o_str] isnt undefined
-                throw new Error "duplicate"
-            dup_map[o_str] = on
+            if typeof o is 'string'
+                if dup_map[o] isnt undefined
+                    throw new Error "duplicate"
+                dup_map[o] = on
         array.sort(st_operation.compare)
     fromByteBuffer:(b)->
         size = b.readVarint32()
@@ -442,9 +442,10 @@
         for o in array
             unless o.length is 2
                 throw new Error "expecting two elements"
-            if dup_map[o[0]] isnt undefined
-                throw new Error "duplicate"
-            dup_map[o[0]] = on
+            if typeof o[0] is 'string'
+                if dup_map[o[0]] isnt undefined
+                    throw new Error "duplicate"
+                dup_map[o[0]] = on
         array

     fromByteBuffer:(b)->


expand forums' search text input whenever the field receives focus.

JIRA: FOR-203

--- common/static/coffee/src/discussion/views/discussion_thread_list_view.coffee
+++ common/static/coffee/src/discussion/views/discussion_thread_list_view.coffee
@@ -5,6 +5,7 @@
       "click .home": "goHome"
       "click .browse": "toggleTopicDrop"
       "keydown .post-search-field": "performSearch"
+      "focus .post-search-field": "showSearch"
       "click .sort-bar a": "sortThreads"
       "click .browse-topic-drop-menu": "filterTopic"
       "click .browse-topic-drop-search-input": "ignoreClick"
@@ -209,7 +210,7 @@

       @$(".search").addClass('is-open')
       @$(".browse").removeClass('is-open')
-      setTimeout (-> @$(".post-search-field").focus()), 200
+      setTimeout (-> @$(".post-search-field").focus()), 200 unless @$(".post-search-field").is(":focus")

     goHome: ->
       @template = _.template($("#discussion-home").html())


Generic Controller: forward modal size small and veryLarge

--- app/assets/javascripts/app/controllers/_application_controller_generic.coffee
+++ app/assets/javascripts/app/controllers/_application_controller_generic.coffee
@@ -214,7 +214,9 @@
       pageData:      @pageData
       genericObject: @genericObject
       container:     @container
+      small:         @small
       large:         @large
+      veryLarge:     @veryLarge
     )

   new: (e) ->
@@ -223,7 +225,9 @@
       pageData:      @pageData
       genericObject: @genericObject
       container:     @container
+      small:         @small
       large:         @large
+      veryLarge:     @veryLarge
     )

   import: (e) ->


need to force DST data to last until I fix data loading

--- brunch-config.coffee
+++ brunch-config.coffee
@@ -15,6 +15,9 @@
         before: [
           'app/src/config/configs.js'
         ]
+        after: [
+          'app/src/data/dst_latest.js'
+        ]
     stylesheets:
       joinTo:
         'css/vendor.css': /^(bower_components|vendor)/


fix: attempt to use gobin when provided and fall back to gopath

--- src/hooks-worker-client.coffee
+++ src/hooks-worker-client.coffee
@@ -148,12 +148,20 @@
       return callback(new Error(msg))

     else if @language == 'go'
-      gopath = process.env.GOPATH
-      @handlerCommand = "#{gopath}/bin/goodman"
+      # https://golang.org/cmd/go/
+      # "you can add DIR/bin to your PATH to get at the installed commands.
+      # If the GOBIN environment variable is set, commands are installed to the
+      # directory it names instead of DIR/bin. GOBIN must be an absolute path."
+      # Use the gobin if provided, otherwise fall back to gopath
+      gobin = process.env.GOBIN
+      if !gobin
+        gobin = "#{process.env.GOPATH}/bin"
+
+      @handlerCommand = "#{gobin}/goodman"
       @handlerCommandArgs = []
       unless which.which @handlerCommand
         msg = '''\
-          Go hooks handler command not found in $GOPATH/bin
+          Go hooks handler command not found in $GOBIN or $GOPATH/bin
           Install go hooks handler by running:
           $ go get github.com/snikch/goodman/cmd/goodman
         '''


Fix repeated edits on inline threads.

Fix repeated edits on inline threads.

--- lms/static/coffee/src/discussion/views/discussion_thread_view_inline.coffee
+++ lms/static/coffee/src/discussion/views/discussion_thread_view_inline.coffee
@@ -36,6 +36,11 @@
         @renderResponses()
       @
     createShowView: () ->
+
+      if @editView?
+        @editView.undelegateEvents()
+        @editView.$el.empty()
+        @editView = null
       @showView = new DiscussionThreadInlineShowView(model: @model)
       @showView.bind "thread:delete", @delete
       @showView.bind "thread:edit", @edit
@@ -111,4 +116,5 @@
     createEditView: () ->
       super()
       @editView.bind "thread:update", @expandPost
+      @editView.bind "thread:update", @abbreviateBody
       @editView.bind "thread:cancel_edit", @expandPost


Increase TDD default test timeout to 30 seconds and add Munit.wrap and Munit.lastError to save exceptions during tests

--- Munit.coffee
+++ Munit.coffee
@@ -1,109 +1,120 @@
-class Munit
-
-
-  @run:(testSuite)->
-    chai.expect(testSuite).to.be.an('object')
-
-    suiteName = testSuite.name || testSuite.constructor.name
-    suiteSetup = _getSuiteFunction(testSuite,'suiteSetup')
-    suiteTearDown = _getSuiteFunction(testSuite,'suiteTearDown')
-    testSetup = _getSuiteFunction(testSuite,"setup")
-    testTearDown = _getSuiteFunction(testSuite,"tearDown")
-    arrSuiteTests = _getSuiteTests(testSuite)
-
-    if arrSuiteTests.length is 0
-      return
-
-    firstTest = arrSuiteTests.shift() # get first element of an array
-    lastTest = arrSuiteTests.pop()# get last element of an array
-
-    addFirstTest = ->
-      lvTestAsyncMulti "#{suiteName} - #{firstTest.name}",firstTest.timeout,[
-        suiteSetup
-        testSetup
-        firstTest.func
-        testTearDown
-      ]
-
-    addLastTest = ->
-      lvTestAsyncMulti "#{suiteName} - #{lastTest.name}",lastTest.timeout,[
-        testSetup
-        lastTest.func
-        testTearDown
-        suiteTearDown
-      ]
-
-    if firstTest and not lastTest
-      lvTestAsyncMulti "#{suiteName} - #{firstTest.name}",firstTest.timeout,[
-        suiteSetup
-        testSetup
-        firstTest.func
-        testTearDown
-        suiteTearDown
-      ]
-    else if firstTest and lastTest and arrSuiteTests.length is 0
-      addFirstTest()
-      addLastTest()
-
-    else
-      addFirstTest()
-      for test in arrSuiteTests
-        lvTestAsyncMulti "#{suiteName} - #{test.name}",test.timeout,[
-          testSetup
-          test.func
-          testTearDown
-        ]
-      addLastTest()
-
-
-  _getSuiteFunction = (testSuite,nameFunc)->
-    func = testSuite[nameFunc]
-    return func ?= -> # create an empty function if no exists
-
-  _getSuiteTests = (testSuite)->
-    arrTests = []
-    suiteTests = testSuite['tests']
-    if suiteTests
-
-      # Convert an object declaration into the standard array format.
-      if not _.isArray(suiteTests) and _.isObject(suiteTests)
-        suiteTests = []
-        for key, test of testSuite['tests']
-          test = { func:test } if _.isFunction(test)
-          test.name ?= key
-          suiteTests.push(test)
-        testSuite['tests'] = suiteTests
-
-      chai.expect(suiteTests).to.be.an('array')
-      for test in suiteTests
-        chai.expect(test).to.have.property('name')
-        chai.expect(test).to.have.property('func')
-        if not test.skip
-          test.timeout = test.timeout || 5000
-          if test.type is "client"
-            if Meteor.isClient
-              arrTests.push test
-          else if test.type is "server"
-            if Meteor.isServer
-              arrTests.push test
-          else
-            arrTests.push test
-
-    for key,func of testSuite
-      if key isnt "tests" and key.indexOf("test") is 0
-        expect(func).to.be.a('function')
-        arrTests.push name:key,func:func,timeout:5000
-
-      else if key.indexOf("clientTest") is 0
-        if Meteor.isClient
-          expect(func).to.be.a('function')
-          suiteTestName = key.replace("client","")
-          arrTests.push name:suiteTestName,func:func,timeout:5000
-
-      else if key.indexOf("serverTest") is 0
-        if Meteor.isServer
-          expect(func).to.be.a('function')
-          suiteTestName = key.replace("server","")
-          arrTests.push name:suiteTestName,func:func,timeout:5000
-
-    return arrTests
+class Munit
+
+  @lastError: null
+
+  @wrap: (func)->
+    chai.expect(func).to.be.a('function')
+    return ->
+      Munit.lastError = null
+      try
+        func()
+      catch ex
+        Munit.lastError = ex
+        throw ex
+
+  @run:(testSuite)->
+    chai.expect(testSuite).to.be.an('object')
+
+    suiteName = testSuite.name || testSuite.constructor.name
+    suiteSetup = _getSuiteFunction(testSuite,'suiteSetup')
+    suiteTearDown = _getSuiteFunction(testSuite,'suiteTearDown')
+    testSetup = _getSuiteFunction(testSuite,"setup")
+    testTearDown = _getSuiteFunction(testSuite,"tearDown")
+    arrSuiteTests = _getSuiteTests(testSuite)
+
+    if arrSuiteTests.length is 0
+      return
+
+    firstTest = arrSuiteTests.shift() # get first element of an array
+    lastTest = arrSuiteTests.pop()# get last element of an array
+
+    addFirstTest = ->
+      lvTestAsyncMulti "#{suiteName} - #{firstTest.name}",firstTest.timeout,[
+        suiteSetup
+        testSetup
+        firstTest.func
+        testTearDown
+      ]
+
+    addLastTest = ->
+      lvTestAsyncMulti "#{suiteName} - #{lastTest.name}",lastTest.timeout,[
+        testSetup
+        lastTest.func
+        testTearDown
+        suiteTearDown
+      ]
+
+    if firstTest and not lastTest
+      lvTestAsyncMulti "#{suiteName} - #{firstTest.name}",firstTest.timeout,[
+        suiteSetup
+        testSetup
+        firstTest.func
+        testTearDown
+        suiteTearDown
+      ]
+    else if firstTest and lastTest and arrSuiteTests.length is 0
+      addFirstTest()
+      addLastTest()
+
+    else
+      addFirstTest()
+      for test in arrSuiteTests
+        lvTestAsyncMulti "#{suiteName} - #{test.name}",test.timeout,[
+          testSetup
+          test.func
+          testTearDown
+        ]
+      addLastTest()
+
+
+  _getSuiteFunction = (testSuite,nameFunc)->
+    func = testSuite[nameFunc]
+    return func ?= -> # create an empty function if no exists
+
+  _getSuiteTests = (testSuite)->
+    arrTests = []
+    suiteTests = testSuite['tests']
+    if suiteTests
+
+      # Convert an object declaration into the standard array format.
+      if not _.isArray(suiteTests) and _.isObject(suiteTests)
+        suiteTests = []
+        for key, test of testSuite['tests']
+          test = { func:test } if _.isFunction(test)
+          test.name ?= key
+          suiteTests.push(test)
+        testSuite['tests'] = suiteTests
+
+      chai.expect(suiteTests).to.be.an('array')
+      for test in suiteTests
+        chai.expect(test).to.have.property('name')
+        chai.expect(test).to.have.property('func')
+        if not test.skip
+          test.timeout = test.timeout || 30000
+          if test.type is "client"
+            if Meteor.isClient
+              arrTests.push test
+          else if test.type is "server"
+            if Meteor.isServer
+              arrTests.push test
+          else
+            arrTests.push test
+
+    for key,func of testSuite
+      if key isnt "tests" and key.indexOf("test") is 0
+        expect(func).to.be.a('function')
+        arrTests.push name:key,func:func,timeout:5000
+
+      else if key.indexOf("clientTest") is 0
+        if Meteor.isClient
+          expect(func).to.be.a('function')
+          suiteTestName = key.replace("client","")
+          arrTests.push name:suiteTestName,func:func,timeout:5000
+
+      else if key.indexOf("serverTest") is 0
+        if Meteor.isServer
+          expect(func).to.be.a('function')
+          suiteTestName = key.replace("server","")
+          arrTests.push name:suiteTestName,func:func,timeout:5000
+
+    return arrTests


Split HT URL on semicolon

Split HT URL on semicolon

--- js/book-aligner.coffee
+++ js/book-aligner.coffee
@@ -72,7 +72,7 @@
 process_ht = (identifier_string) ->
   console.log 'process_ht'
   match = identifier_string.match(HT_REGEX)
-  ht_id = match[1].split('&')[0]
+  ht_id = match[1].split(';')[0]
   process_ht_id(ht_id, 100)
   ht_query(ht_id)


don't switch to dev mode quite so much

don't switch to dev mode quite so much

--- projects.wme03370.cson
+++ projects.wme03370.cson
@@ -103,7 +103,6 @@
       "~/Devel/atom/atom-symbol-quick-pane"
     ]
     icon: "atom-icon"
-    devMode: true
   }
   {
     title: "Site Setup"
@@ -186,9 +185,9 @@
     group: "Atom"
     paths: [
       "~/Devel/atom/pkg-groups"
-    ]
-    icon: "atom-icon"
-    devMode: true
+      "~/Devel/atom/pkg-groups/node_modules/etch"
+    ]
+    icon: "atom-icon"
   }
   {
     title: "Bootstrap4 Experiments"


forgot .json extension

forgot .json extension

--- src/businesscat.coffee
+++ src/businesscat.coffee
@@ -20,7 +20,7 @@
   robot.hear regex, (msg) ->
     msg.send msg.random images

-images = require 'images'
+images = require 'images.json'

 jargon = [
   'action items',


Removed unused function

Removed unused function

--- core/app/backbone/models/fact.coffee
+++ core/app/backbone/models/fact.coffee
@@ -12,12 +12,6 @@
     return '' unless fact_url

     new Backbone.Factlink.Url(fact_url).host()
-
-  justCreated: ->
-    milliseconds_ago = Date.now() - new Date(@get('created_at'))
-    minutes_ago = milliseconds_ago/1000/60
-
-    minutes_ago < 5

   friendly_fact_url: ->
     Factlink.Global.core_url + '/f/' + @id


completing the extraction of the socket out of the transform method.

--- gulp-espruino/src/gulp-espruino.coffee
+++ gulp-espruino/src/gulp-espruino.coffee
@@ -4,12 +4,12 @@
 extend = require 'extend'
 Q = require 'q'

-output = (->
+createOutput = ->
   consumed = ''

   append: (content) -> consumed += content
   all: -> consumed
-)()
+

 createPublisher = (readableStream, readableStreamDone) ->
   published = false
@@ -33,20 +33,24 @@
     clearTimeout(timeout)
     timeout = setTimeout(done, timeoutInMillis)

-createEspruino = (port) ->
+createEspruino = (port, options) ->
   serialPort = new SerialPort(port, { baudrate: 9600 })
+  output = createOutput()
   commandExecuted = Q.defer()

-  serialPort: serialPort
+  finishCommand = createTimer(options.idleReadTimeBeforeClose, -> commandExecuted.resolve())

+  serialPort.on 'data', (data) ->
+    output.append(data.toString())
+    finishCommand()
+
+  log: -> output.all()
+  close: (callback) -> serialPort.close(callback)
   send: (command, onError) ->
+    serialPort.on('error', onError)
     commandExecuted = Q.defer()
     serialPort.write command, (error) -> onError(error) if error
     commandExecuted.promise
-
-  sendFinished: -> commandExecuted.resolve()
-
-  close: (callback) -> serialPort.close(callback)

 module.exports =
   deploy: (port, overrides = {}) ->
@@ -57,29 +61,19 @@
       reset: true
       save: true

-    espruino = createEspruino(port)
-
     options = extend({}, defaults, overrides)
-    serialPort = espruino.serialPort
+    espruino = createEspruino(port, options)

     onTransform = (chunk, encoding, callback) ->
       publish = createPublisher(@, callback)
       onError = (error) -> publish.error(error)
-      finishCommand = createTimer(options.idleReadTimeBeforeClose, -> espruino.sendFinished())

-      serialPort.on 'data', (data) ->
-        output.append(data.toString())
-        finishCommand()
-
-      serialPort.on('error', onError)
-
-      Q()
-        .then(-> espruino.send("reset();\n", onError) if options.reset)
-        .then(-> espruino.send("echo(0);\n", onError) if options.echoOff)
-        .then(-> espruino.send("{ #{chunk.contents.toString()} }\n", onError))
-        .then(-> espruino.send("echo(1);\n", onError) if options.echoOff)
-        .then(-> espruino.send("save();\n", onError) if options.save)
-        .then(-> publish.content(output.all()))
+      Q().then(-> espruino.send("reset();\n", onError) if options.reset)
+         .then(-> espruino.send("echo(0);\n", onError) if options.echoOff)
+         .then(-> espruino.send("{ #{chunk.contents.toString()} }\n", onError))
+         .then(-> espruino.send("echo(1);\n", onError) if options.echoOff)
+         .then(-> espruino.send("save();\n", onError) if options.save)
+         .then(-> publish.content(espruino.log()))

     onFlush = (callback) ->
       self = this


implement --checkstyle by copying jslinreporter

--- src/commandline.coffee
+++ src/commandline.coffee
@@ -211,6 +211,46 @@

         msg

+class CheckstyleReporter extends Reporter
+
+    publish : () ->
+        @print "<?xml version=\"1.0\" encoding=\"utf-8\"?><checkstyle version=\"4.3\">"
+
+        for path, errors of @errorReport.paths
+            if errors.length
+                @print "<file name=\"#{path}\">"
+
+                for e in errors
+                    @print """
+                    <error line="#{e.lineNumber}"
+                            severity="#{@escape(e.level)}"
+                            message="#{@escape(e.message)}; context:#{@escape(e.context)}""
+                            source="coffeelint"/>
+                    """
+                @print "</file>"
+
+        @print "</checkstyle>"
+
+    escape : (msg) ->
+        # Force msg to be a String
+        msg = "" + msg
+        unless msg
+            return
+        # Perhaps some other HTML Special Chars should be added here
+        # But this are the XML Special Chars listed in Wikipedia
+        replacements = [
+            [/&/g, "&"]
+            [/"/g, """]
+            [/</g, "<"]
+            [/>/g, ">"]
+            [/'/g, "'"]
+            ]
+
+        for r in replacements
+            msg = msg.replace r[0], r[1]
+
+        msg
+
 # Return an error report from linting the given paths.
 lintFiles = (paths, config) ->
     errorReport = new ErrorReport()
@@ -255,6 +295,8 @@
         new JSLintReporter(errorReport)
     else if options.argv.csv
         new CSVReporter(errorReport)
+    else if options.argv.checkstyle
+        new CheckstyleReporter(errorReport)
     else
         colorize = not options.argv.nocolor
         new Reporter(errorReport, colorize)
@@ -281,6 +323,7 @@
             .describe("r", "Recursively lint .coffee files in subdirectories.")
             .describe("csv", "Use the csv reporter.")
             .describe("jslint", "Use the JSLint XML reporter.")
+            .describe("checkstyle", "Use the checkstyle XML reporter.")
             .describe("nocolor", "Don't colorize the output")
             .describe("s", "Lint the source from stdin")
             .describe("q", "Only print errors.")
@@ -288,6 +331,7 @@
                 "Used with --stdin to process as Literate CoffeeScript")
             .boolean("csv")
             .boolean("jslint")
+            .boolean("checkstyle")
             .boolean("nocolor")
             .boolean("noconfig")
             .boolean("makeconfig")


add missing function literal

add missing function literal

--- workers/social/testhelper/models/computeproviders/computeproviderhelper.coffee
+++ workers/social/testhelper/models/computeproviders/computeproviderhelper.coffee
@@ -14,7 +14,7 @@
 ComputeProvider = require '../../../../social/lib/social/models/computeproviders/computeprovider'

-# this helper registers a new user and creates request models
+# this helper registers a new user and creates requested models
 withConvertedUserAnd = (models, options, callback) ->

   [options, callback] = [callback, options]  unless callback
@@ -152,7 +152,7 @@
         when 'StackTemplate'    then _createStackTemplate
         when 'Stack'            then _createStack
         when 'ComputeProvider'  then _createComputeProvider
-        else                         callback 'invalid request'
+        else                      -> callback 'invalid request'

       queue.push ->
         fn client, options, (data_) ->


replace two regexes with one

replace two regexes with one

--- src/core/editor.coffee
+++ src/core/editor.coffee
@@ -109,7 +109,7 @@

   _insertAt: (index, text, formatting = {}) ->
     @selection.shiftAfter(index, text.length, =>
-      text = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n')
+      text = text.replace(/\r\n?/g, '\n')
       lineTexts = text.split('\n')
       [line, offset] = @doc.findLineAt(index)
       _.each(lineTexts, (lineText, i) =>


Extract Default Modules

Extract Default Modules

--- src/game_object.coffee
+++ src/game_object.coffee
@@ -164,7 +164,8 @@
   "Cooldown"
   "Drawable"
   "Expirable"
-  "Follow, Metered, Movable, Rotatable, TimedEvents, Tween]
+  "Follow"
+  , Metered, Movable, Rotatable, TimedEvents, Tween]

 ###*
 Construct an object instance from the given entity data.


OHM-24: added unique tag to contactGroups collection

--- src/model/contactGroups.coffee
+++ src/model/contactGroups.coffee
@@ -7,7 +7,7 @@
   "maxAlerts":  { type: String, enum: ["no max", "1 per hour", "1 per day" ], default: "no max" }

 ContactGroupSchema = new Schema
-  "group":   { type: String, required: true }
+  "group":   { type: String, required: true, unique: true }
   "users":   [ ContactUserSchema ]

 exports.ContactUser = mongoose.model 'ContactUser', ContactUserSchema


fixing gauge from being derp

fixing gauge from being derp

--- kurea.base/client/scripts/gauge.coffee
+++ kurea.base/client/scripts/gauge.coffee
@@ -291,7 +291,8 @@
     @percentColors = null
     if typeof G_vmlCanvasManager != 'undefined'
       @canvas = window.G_vmlCanvasManager.initElement(@canvas)
-    @ctx = @canvas.getContext('2d')
+    try
+      @ctx = @canvas.getContext('2d')
     @gp = [new GaugePointer(@)]
     @setOptions()
     @render()


add scroll top to src/jscrollpane.coffee

--- src/jscrollpane.coffee
+++ src/jscrollpane.coffee
@@ -20,6 +20,12 @@
 					$timeout(fn, $scope.$eval($attrs.scrollTimeout))
 				else
 					$timeout(fn, 0)
+				$scope.$watch (->
+					$attrs.scrollAlwaysTop
+				), (newVal, oldVal) ->
+					if newVal and $scope.pane
+						$scope.pane.scrollToY 0
+					return
 				$scope.$on("reinit-pane", (event, id) ->
 					if id is $attrs.id and $scope.pane
 						console.log("Reinit pane #{id}")


Unit test for `finishLogin`

Unit test for `finishLogin`

--- services/web/test/unit/coffee/Authentication/AuthenticationControllerTests.coffee
+++ services/web/test/unit/coffee/Authentication/AuthenticationControllerTests.coffee
@@ -579,3 +579,56 @@
 			@AuthenticationController._clearRedirectFromSession(@req)
 			expect(@req.session.postLoginRedirect).to.equal undefined

+
+	describe 'finishLogin', ->
+		# - get redirect
+		# - async handlers
+		# - afterLoginSessionSetup
+		# - clear redirect
+		# - issue redir, two ways
+		beforeEach ->
+			@AuthenticationController._getRedirectFromSession = sinon.stub().returns '/some/page'
+			@AuthenticationController._loginAsyncHandlers = sinon.stub()
+			@AuthenticationController.afterLoginSessionSetup = sinon.stub().callsArgWith(2, null)
+			@AuthenticationController._clearRedirectFromSession = sinon.stub()
+			@req.headers = {accept: 'application/json, whatever'}
+			@res.json = sinon.stub()
+			@res.redirect = sinon.stub()
+
+		it 'should extract the redirect from the session', () ->
+			@AuthenticationController.finishLogin(@user, @req, @res, @next)
+			expect(@AuthenticationController._getRedirectFromSession.callCount).to.equal 1
+			expect(@AuthenticationController._getRedirectFromSession.calledWith(@req)).to.equal true
+
+		it 'should call the async handlers', () ->
+			@AuthenticationController.finishLogin(@user, @req, @res, @next)
+			expect(@AuthenticationController._loginAsyncHandlers.callCount).to.equal 1
+			expect(@AuthenticationController._loginAsyncHandlers.calledWith(@req, @user)).to.equal true
+
+		it 'should call afterLoginSessionSetup', () ->
+			@AuthenticationController.finishLogin(@user, @req, @res, @next)
+			expect(@AuthenticationController.afterLoginSessionSetup.callCount).to.equal 1
+			expect(@AuthenticationController.afterLoginSessionSetup.calledWith(@req, @user)).to.equal true
+
+		it 'should clear redirect from session', () ->
+			@AuthenticationController.finishLogin(@user, @req, @res, @next)
+			expect(@AuthenticationController._clearRedirectFromSession.callCount).to.equal 1
+			expect(@AuthenticationController._clearRedirectFromSession.calledWith(@req)).to.equal true
+
+		it 'should issue a json response with a redirect', () ->
+			@AuthenticationController.finishLogin(@user, @req, @res, @next)
+			expect(@res.json.callCount).to.equal 1
+			expect(@res.redirect.callCount).to.equal 0
+			expect(@res.json.calledWith({ redir: '/some/page' })).to.equal true
+
+		describe 'with a non-json request', ->
+			beforeEach ->
+				@req.headers = {}
+				@res.json = sinon.stub()
+				@res.redirect = sinon.stub()
+
+			it 'should issue a plain redirect', () ->
+				@AuthenticationController.finishLogin(@user, @req, @res, @next)
+				expect(@res.json.callCount).to.equal 0
+				expect(@res.redirect.callCount).to.equal 1
+				expect(@res.redirect.calledWith('/some/page')).to.equal true


Use mixin over updateAttributes

Use mixin over updateAttributes

--- src/model/model.coffee
+++ src/model/model.coffee
@@ -146,7 +146,7 @@
   @findOrCreate: (attrs, callback) ->
     record = @_loadIdentity(attrs[@primaryKey])
     if record
-      record.updateAttributes(attrs)
+      record.mixin(attrs)
       callback(undefined, record)
     else
       record = new this(attrs)


#234 Use user layer name in shader, still need to sanitize


fixed function call

fixed function call

--- lib/atom-ternjs.coffee
+++ lib/atom-ternjs.coffee
@@ -83,7 +83,7 @@

   registerEvents: ->
     @disposables.push atom.workspace.onDidOpen (e) =>
-      if isGrammarInGrammars(e.item)
+      if @isGrammarInGrammars(e.item)
         @startServer()
     @disposables.push atom.workspace.onDidChangeActivePaneItem =>
       @setCurrentProvider()
@@ -127,6 +127,7 @@
     @disposables = []

   startServer: ->
+    console.log 'startserver'
     return unless !@server?.process
     return unless atom.project.getRootDirectory()
     @server = new TernServer()


tip over network

tip over network

--- src/smc-webapp/app_shared.cjsx
+++ src/smc-webapp/app_shared.cjsx
@@ -153,7 +153,10 @@
         if @props.connection_status == 'connected'
             <div>
                 <Icon name='wifi' style={marginRight: 8, fontSize: '13pt', display: 'inline'} />
-                {<Tip title={'Most recently recorded roundtrip time to message the server.'}>
+                {<Tip
+                    title     = {'Most recently recorded roundtrip time to the server.'}
+                    placement = {'left'}
+                    >
                     {Math.floor(@props.avgping)}ms
                 </Tip> if @props.avgping?}
             </div>


Rearrange the commit panel

Rearrange the commit panel

--- src/smc-webapp/editor_git.cjsx
+++ src/smc-webapp/editor_git.cjsx
@@ -401,7 +401,23 @@
         window.refs = @refs
         head =

-                Commit
+
+
+
+                    Select the tracked files to commit
+                    <Space/> <Space/>
+                    <Button onClick={=>@props.actions.get_changed_tracked_files()}>
+                        Refresh
+                    </Button>
+
+

+                {<FileListing
+                    listing             = {@props.git_changed_tracked_files}
+                    listing_type        = 'tracked'
+                    checked_files       = {@props.checked_files}
+                    actions             = {@props.actions} /> if @props.git_changed_tracked_files}
+

+                Write your commit message
                 <Input
                     ref         = 'commit_message'
                     type        = 'text'
@@ -410,18 +426,6 @@
                     onChange    = {=>@props.actions.setState(commit_message:@refs.commit_message.getValue())}
                     onKeyDown   = {@handle_commit_message_keypress}
                 />
-
-                    Changed tracked files
-                    <Space/> <Space/>
-                    <Button onClick={=>@props.actions.get_changed_tracked_files()}>
-                        Refresh
-                    </Button>
-
-                {<FileListing
-                    listing             = {@props.git_changed_tracked_files}
-                    listing_type        = 'tracked'
-                    checked_files       = {@props.checked_files}
-                    actions             = {@props.actions} /> if @props.git_changed_tracked_files}
                 <Button
                     onClick  = {=>@props.actions.run_git_commit()} >
                     Commit the selected changed tracked files


TEST: I don't feel we need this,dramatically improves performance on windows systems, adresses #229 , #174

--- src/ui/models/viewstate.coffee
+++ src/ui/models/viewstate.coffee
@@ -84,12 +84,12 @@
     setSize: (size) ->
         localStorage.size = JSON.stringify(size)
         @size = size
-        updated 'viewstate'
+        # updated 'viewstate'

     setPosition: (pos) ->
         localStorage.pos = JSON.stringify(pos)
         @pos = pos
-        updated 'viewstate'
+        # updated 'viewstate'

     setLeftSize: (size) ->
         return if @leftSize == size


Fixing placement of forum poll timestamp

--- scripts/forum-poll.coffee
+++ scripts/forum-poll.coffee
@@ -31,13 +31,12 @@
   constructor: (@robot) ->

   getNewThreads: (callback) ->
-    LAST_POLLED_AT = new Date()
     @robot.http(HOST_URL+API_URI).query(take: 3).get() (err, res, body) =>
       try
         threads = JSON.parse(body)
         result = threads.filter (thread) => @isMoreRecentThanLastPoll(thread.updated_at)

-
+        LAST_POLLED_AT = new Date()
         # need to set last polled date/time
       catch error
         console.log "Parsing error when fetching feeds from forum", error


backend_pubsub: reply() must also copy 'replay' field on MaxStanzaSizeExceeded

--- src/xmpp/backend_pubsub.coffee
+++ src/xmpp/backend_pubsub.coffee
@@ -85,18 +85,15 @@
                 @conn.send notification.toStanza(@conn.jid, listener)
         catch e
             if e.constructor is errors.MaxStanzaSizeExceeded and opts.length > 1
-                # FIXME: a notification may have been sent already
+                # FIXME: a notification may have been sent already to a previous shorter listener jid
+                copyProps = (opts_) ->
+                    ['type', 'node', 'user', 'listener', 'replay'].forEach (prop) ->
+                        opts_[prop] = opts[prop]
                 pivot = Math.floor(opts.length / 2)
                 opts1 = opts.slice(0, pivot)
-                opts1.type = opts.type
-                opts1.node = opts.node
-                opts1.user = opts.user
-                opts1.listener = opts.listener
+                copyProps opts1
                 opts2 = opts.slice(pivot, opts.length)
-                opts2.type = opts.type
-                opts2.node = opts.node
-                opts2.user = opts.user
-                opts2.listener = opts.listener
+                copyProps opts2
                 logger.warn "MaxStanzaSizeExceeded: split notification from #{opts.length} into #{opts1.length}+#{opts2.length}"
                 @notify opts1
                 @notify opts2


Remove deprecated test AMIs

Remove deprecated test AMIs

--- scripts/test-instance/instance-ami.coffee
+++ scripts/test-instance/instance-ami.coffee
@@ -3,10 +3,6 @@

   # Ordered by creation date
   list: [
-    'ami-10465678'
-    'ami-3e415256'
-    'ami-bc5e4cd4'
-    'ami-193fd572'
     'ami-a7986fcc'
     'ami-b920d7d2'
     'ami-9b02f0f0'


Image gallery no long breaks when the browser is resized

--- static/js/image-gallery.coffee
+++ static/js/image-gallery.coffee
@@ -16,6 +16,11 @@

     @nextButton.addEventListener 'click', @moveToNextImage
     @prevButton.addEventListener 'click', @moveToPreviousImage
+
+    # Recalculate translateX distance when the browser is resized to avoid
+    # layout issues
+    window.addEventListener 'resize', =>
+      @setTranslateXProperty @imageContainerRule, -@imageGalleryElement.offsetWidth * @currentIndex

   moveToNextImage: =>
     if @numberOfImages <= @currentIndex + 1


fixed js error @el -> $(@el)

fixed js error @el -> $(@el)


Dont update tag again and again

Dont update tag again and again

--- client/app/Applications/Activity.kdapplication/widgets/codesnippetwidget.coffee
+++ client/app/Applications/Activity.kdapplication/widgets/codesnippetwidget.coffee
@@ -114,7 +114,6 @@
       @tagController.addItemToSubmitQueue @tagController.getNoItemFoundView(syntax)

   submit:=>
-    debugger
     @addCustomData "code", @ace.getContents()
     @once "FormValidationPassed", => @reset()
     super
@@ -168,7 +167,7 @@
       @ace.setTheme()
       @ace.setSyntax "javascript"
       @ace.editor.getSession().on 'change', => @refreshEditorView()
-      @updateSyntaxTag 'javascript'
+      @once @updateSyntaxTag 'javascript'
       @emit "codeSnip.aceLoaded"

   refreshEditorView:->


Save config on clean

Save config on clean

--- lib/ide-backend.coffee
+++ lib/ide-backend.coffee
@@ -40,12 +40,13 @@
     buildDir = @getConfigOpt 'buildDir'

     if cabalFile?
-      cabalArgs =
-        switch cmd
-          when 'build', 'test'
-            [cmd, '--only', '--builddir=' + buildDir]
-          else
-            [cmd, '--builddir=' + buildDir]
+      cabalArgs = [cmd]
+      switch cmd
+        when 'build', 'test'
+          cabalArgs.push '--only'
+        when 'clean'
+          cabalArgs.push '--save-configure'
+      cabalArgs.push '--builddir=' + buildDir
       cabalArgs.push target if target? and cmd is 'build'
       cabalProcess = new CabalProcess 'cabal', cabalArgs, @spawnOpts(cabalRoot), opts
     else


Add ViewModel.bindObjects test

Add ViewModel.bindObjects test

--- tests/viewmodel.coffee
+++ tests/viewmodel.coffee
@@ -178,6 +178,20 @@
       instanceStub.returns templateInstance
       ret = ViewModel.bindHelper()
       assert.equal ret[ViewModel.bindIdAttribute], 99
+
+    it "adds the binding to ViewModel.bindObjects", ->
+      viewmodel = new ViewModel()
+      instanceStub = sinon.stub Template, 'instance'
+      parseBindStub = sinon.stub ViewModel, 'parseBind'
+      bindObject =
+        text: 'name'
+      parseBindStub.returns bindObject
+      templateInstance =
+        viewmodel: viewmodel
+        '$': -> "X"
+      instanceStub.returns templateInstance
+      ViewModel.bindHelper("text: name")
+      assert.equal ViewModel.bindObjects[99], bindObject

     it "binds the view model when the view is ready", ->
       viewmodel = new ViewModel()


dig for real package directory to fix #21

--- plugin/browserify.coffee
+++ plugin/browserify.coffee
@@ -82,12 +82,32 @@
   #  3. when altPackageName exists and it's an app file, it's used as the packageFolderName
   getRoot: (altPackageName) ->

+    # use the cached value when it exists
+    if this.___root? then return this.___root
+
+    # we can tell if we're processing an app file or a package file based on this value
+    # null means app file
     root = @getPackageName()
+
     if root?
-      index = root.indexOf(':') + 1
-      root = 'packages/' + root[index...]
+      # if there is a deeply hidden property containing keys with the actual directory name...
+      if this?._resourceSlot?.packageSourceBatch?.unibuild?.watchSet?.files?
+        # get one of the keys
+        break for watchedFile of this._resourceSlot.packageSourceBatch.unibuild.watchSet.files
+        # get path relative to the app's directory
+        relativePath = path.relative (path.resolve '.'), watchedFile
+        # strip off tail, only keep the 'packages/packageDirectory' portion
+        root = relativePath[...relativePath.indexOf('/', 10)]
+        # cache this value so we don't have to calculate it again
+        this.___root = root
+      else
+        # use the 'meteor way' of having a package's directory be its name without the username
+        index = root.indexOf(':') + 1
+        root = 'packages/' + root[index...]
+
     else if altPackageName?
       root = 'packages/' + altPackageName
+
     else
       root = ''


JAccount: Remove domain stuff from JAccount

--- workers/social/lib/social/models/account.coffee
+++ workers/social/lib/social/models/account.coffee
@@ -207,8 +207,6 @@
           (signature Object, Function)
         fetchRelatedUsersFromGraph:
           (signature Object, Function)
-        fetchDomains:
-          (signature Function)
         unlinkOauth:
           (signature String, Function)
         changeUsername: [
@@ -1420,40 +1418,6 @@
     client.context.group = 'koding'
     oldAddTags.call this, client, tags, options, callback

-  fetchUserDomains: (client, callback) ->
-    {connection: {delegate}} = client
-    {group} = client.context
-
-    JDomain = require './domain'
-    delegate.fetchDomains (err, domains) ->
-      return callback err  if err
-      domainList = []
-      domainList = filterDomains domains, delegate, group  if domains
-
-      callback null, domainList
-
-  # filters domains such as shared-x/vm-x.groupSlug.kd.io
-  # or x.koding.kd.io. Also shows only group related
-  # domains to users
-  filterDomains = (domains, account, group)->
-    domainList = []
-    domainList = domains.filter (domain)->
-      {domain} = domain
-      re = if group is "koding" then new RegExp(account.profile.nickname + '\.kd\.io$') \
-           else new RegExp('(.*)\.' + group + '\.kd\.io$')
-      isVmAlias         = (/^shared|vm[\-]?([0-9]+)?/.test domain)
-      isKodingSubdomain = (/(.*)\.(koding|guests)\.kd\.io$/.test domain)
-      isGroupAlias      = re.test domain
-      not isVmAlias and not isKodingSubdomain and isGroupAlias
-
-  fetchDomains$: permit
-    advanced: [
-      { permission: 'list own domains', validateWith: Validators.own }
-    ]
-    success: (client, callback) ->
-      @fetchUserDomains client, callback
-
-
   {Member, OAuth} = require "./graph"

   fetchMyFollowingsFromGraph: secure (client, options, callback)->


Creating link wrapper around bookmarklet. Need to make configurable.

Also need to check quoting in the uglify code to avoid issues with quotes.

--- lib/bookmarklet.coffee
+++ lib/bookmarklet.coffee
@@ -7,8 +7,10 @@

   activate: (state) ->
     @bookmarkletView = new BookmarkletView(state.bookmarkletViewState)
-    @header = 'javascript:(function(){';
-    @footer = '})();';
+    @linkPrefix = 'Click Me'
+    @header = 'javascript:(function(){'
+    @footer = '})();'
     atom.workspaceView.command "bookmarklet:create", => @create()

   deactivate: ->
@@ -26,7 +28,7 @@

       # Call uglify to get rid of spaces, new lines, etc.
       ug = uglify.minify(content, { fromString: true })
-      out = @header + ug.code + @footer;
+      out = @linkPrefix + @header + ug.code + @footer + @linkSuffix;

       # Copy the text to the clipboard
       atom.clipboard.write(out)


Add support to a logbook format

Add support to a logbook format

--- views/components/akashic-advanced-module.cjsx
+++ views/components/akashic-advanced-module.cjsx
@@ -201,6 +201,41 @@
         retData.push logItem[11]
         retData.push logItem[12]
         retData.push logItem[13]
+        retData
+      data = data.filter (log) ->
+        log.length is 12
+    when "No.,日付,海域,マス,出撃,ランク,敵艦隊,ドロップ艦種,ドロップ艦娘,ドロップアイテム,大破艦,旗艦,旗艦(第二艦隊),MVP,MVP(第二艦隊)"
+      logType = CONST.typeList.attack
+      data = logs.slice(1).map (logItem) ->
+        logItem = logItem.split ','
+        if logItem.length isnt 15
+          return []
+        retData = []
+        retData.push (new Date(logItem[1].replace(/-/g, "/"))).getTime()
+        tmpArray = logItem[3].match(/:\d+(-\d+)?/g)
+        retData.push "#{logItem[2]}(#{tmpArray[0].substring(1)})"
+        if logItem[4] is "ボス"
+          tmp = "Boss点"
+        else
+          tmp = "道中"
+        retData.push "#{tmpArray[1].substring(1)}(#{tmp})"
+        if logItem[4] is "出撃"
+          tmp = "出撃"
+        else
+          tmp = "進撃"
+        retData.push tmp
+        retData.push logItem[5]
+        retData.push logItem[6]
+        retData.push "#{logItem[8]}#{if logItem[8] != '' and logItem[9] != '' then ' & ' else ''}#{logItem[9]}"
+        if logItem[10] is ""
+          tmp = "无"
+        else
+          tmp = "有"
+        retData.push tmp
+        retData.push logItem[11]
+        retData.push logItem[12]
+        retData.push logItem[13]
+        retData.push logItem[14]
         retData
       data = data.filter (log) ->
         log.length is 12


Added new page to admin

Added new page to admin

--- client/router.coffee
+++ client/router.coffee
@@ -34,4 +34,4 @@
   'isAdmin': (page) ->
     if Meteor.user()?.profile.admin then page else 'adminLogin'

-Meteor.Router.filter 'isAdmin', only: ['adminIndex', 'list_view']
+Meteor.Router.filter 'isAdmin', only: ['admin', 'list_view', 'document_view']


cleaning up transform

cleaning up transform

--- test/drawable.coffee
+++ test/drawable.coffee
@@ -1,26 +1,25 @@
 module "Drawable"

 test "should apply hflip and vflip transformations", ->
-  window.canvas = $("<canvas width=10 height=10} />").powerCanvas()
-
-  horizontal_obj = GameObject
+  object = GameObject
     hflip: true

-  horizontal_obj.draw(canvas)
+  transform = object.getTransform()

-  equal horizontal_obj.I.transform.a, Matrix.HORIZONTAL_FLIP.a
-  equal horizontal_obj.I.transform.b, Matrix.HORIZONTAL_FLIP.b
-  equal horizontal_obj.I.transform.c, Matrix.HORIZONTAL_FLIP.c
-  equal horizontal_obj.I.transform.d, Matrix.HORIZONTAL_FLIP.d
+  equal transform.a, Matrix.HORIZONTAL_FLIP.a
+  equal transform.b, Matrix.HORIZONTAL_FLIP.b
+  equal transform.c, Matrix.HORIZONTAL_FLIP.c
+  equal transform.d, Matrix.HORIZONTAL_FLIP.d

-  vertical_obj = GameObject
-    vflip: true
+  object.I.hflip = false
+  object.I.vflip = true

-  vertical_obj.draw(canvas)
+  transform = object.getTransform()

-  equal vertical_obj.I.transform.a, Matrix.VERTICAL_FLIP.a
-  equal vertical_obj.I.transform.b, Matrix.VERTICAL_FLIP.b
-  equal vertical_obj.I.transform.c, Matrix.VERTICAL_FLIP.c
-  equal vertical_obj.I.transform.d, Matrix.VERTICAL_FLIP.d
+  equal transform.a, Matrix.VERTICAL_FLIP.a
+  equal transform.b, Matrix.VERTICAL_FLIP.b
+  equal transform.c, Matrix.VERTICAL_FLIP.c
+  equal transform.d, Matrix.VERTICAL_FLIP.d

-module()+module()
+


using correct .tar now

using correct .tar now

--- web/doppio-jvm/scripts/demo/doppio-api.coffee
+++ web/doppio-jvm/scripts/demo/doppio-api.coffee
@@ -42,7 +42,7 @@
             Loads the compressed pre-compiled java classes for Doppio
         ###
         try
-            data = node.fs.readFileSync("/home/doppio/scripts/demo/mini-rt.tar")
+            data = node.fs.readFileSync("/home/doppio/preload.tar")
         catch e
             console.error e
         if data == null


Get element for cursor from document directly

--- lib/cute-cursor.coffee
+++ lib/cute-cursor.coffee
@@ -1,14 +1,13 @@
 COLORS = 'red green blue yellow purple magenta cyan'.split(' ')
 COLORSIZE = COLORS.length
+CUSOR_SELECTOR = 'atom-text-editor.editor .cursors .cursor'

 module.exports =
   activate: (state) ->
-    view = atom.workspaceView
     colorIndex = 0
-
+    cursor = document.querySelector(CUSOR_SELECTOR)
     setInterval(->
-      cursor = view.getActivePaneView().find('.cursor')
-      cursor.css('background-color', COLORS[colorIndex])
+      cursor.style['background-color'] = COLORS[colorIndex]
       colorIndex += 1
       if colorIndex >= COLORSIZE
         colorIndex = 0


all tests pass... gutted out tons of junk

--- lib/precompiler.coffee
+++ lib/precompiler.coffee
@@ -4,7 +4,6 @@
 path = require "path"
 _ = require "underscore"
 mkdirp = require "mkdirp"
-async = require 'async'
 compressor = require './utils/compressor'

 # @api private
@@ -16,246 +15,60 @@
   root = path.join(global.options.templates, "/")
   output_path = path.normalize("public/js/templates.js")
   mkdirp.sync path.dirname(output_path)
-  fs.writeFileSync output_path, ""
-  files = _.map(fs.readdirSync(
-    path.join(process.cwd(), root)),
-    (f) -> path.basename f, ".jade"
+
+  precompiler = new Precompiler(
+    source: path.normalize(process.cwd() + '/' + root)
+    output: output_path
+    templates: _.map(fs.readdirSync(
+      path.join(process.cwd(), root)),
+      (f) -> path.basename f, ".jade"
+    )
   )
-  settings =
-    inline: false
-    debug: false
-    namespace: "templates"
-    window: false
-    source: root
-    output: output_path
-    templates: files
-
-  precompile settings, process.cwd()
+  buf = precompiler.compile()
+  buf = compressor buf, 'js'
+  fs.writeFileSync output_path, buf

+class Precompiler
+  constructor: (options = {}) ->
+    defaults =
+      inline: false
+      debug: false
+      namespace: "templates"
+      source: ''
+      output: ''
+      templates: undefined

-###
-Namespacer(settings)
-Description: Creates a Namespacer instance for processing namespace data
-
-settings:
-  "namespace": String(Required), namespace object when including templates to browser
-  "templates": Array(Required), names of templates to be precompiled
-###
-
-class Namespacer
-
-  ###
-  constructor
-  Description: Bind settings to object
-  ###
-  constructor: (settings) ->
-    if settings.namespace? then @groupNamespace = settings.namespace
-    else return 'Error: \'namespace\' is not configured'
-
-    if settings.templates? then @templates = settings.templates
-    else return 'Error: \'templates\' is not configured'
-
-    if settings.skiproot? then @skiproot = settings.skiproot
-    console.log @skiproot
-
-    if @groupNamespace? and @templates?
-      @namespaces = []
-      @result = []
-      @init()
-
-  init: ->
-    async.auto({
-      checkGroupNamespace: (callback) -> @checkGroupNamespace(callback)
-      checkTemplateNamespaces: (callback) -> @checkTemplateNamespaces(callback)
-      createNamespaces: ['checkGroupNamespace', 'checkTemplateNamespaces', (callback) ->
-        @createNamespaces(callback)
-      ]
-    }, (err) ->
-      if err?
-        return err
-      else
-        return [null, @result]
-    )
-
-  ###
-  checkGroupNamespace
-  Description: Checks group namespace if needed to be splitted
-  ###
-  checkGroupNamespace : (callback) ->
-    # Checks if needed to split namespace
-    if @groupNamespace.indexOf('.') > 0
-      @splitNamespace(@groupNamespace, true)
-      callback(null)
-    else
-      @namespaces.push(@groupNamespace)
-      callback(null)
-
-  ###
-  checkTemplateNamespaces
-  Description: Checks template namespaces if needed to be splitted
-  ###
-  checkTemplateNamespaces : (callback) ->
-    counter = 0
-
-    next = ->
-      counter++
-      if counter is @templates.length
-        arr = []
-        for i in [0...@namespaces.length]
-          unless arr.indexOf(@namespaces[i]) > 0
-            arr.push(@namespaces[i])
-          if i is @namespaces.length-1
-            @namespaces = arr
-            callback(null)
-
-    for templateName in @templates
-      if templateName.indexOf('/') > 0
-        @splitNamespace(templateName)
-        next()
-      else next()
-
-
-  ###
-  createNamespaces
-  Description: Prepends required namespace declarations for the browser
-  ###
-  createNamespaces : (callback) ->
-
-    # Get the maximum index for group namespaces in the namespaces array
-    groupNamespaceLength = (@groupNamespace).split('.').length
-
-    # Appends the group namespace declarations
-    for g in [0...groupNamespaceLength]
-      if g > 0 or not @skiproot
-        @result.push "window.#{@namespaces[g]} = window.#{@namespaces[g]} || {};"
-
-    if groupNamespaceLength is @namespaces.length
-      callback null
-    else
-      for t in [groupNamespaceLength...@namespaces.length]
-        @result.push "#{@groupNamespace}.#{@namespaces[t]} = #{@groupNamespace}.#{@namespaces[t]} || {};"
-        if t is @namespaces.length-1
-          callback null
-
-  ###
-  splitNamespace(name, isGroupNamespace)
-  Description: Helper, splits each string and adds it into the 'namespaces' array for later processing
-
-  Params:
-    name: Namespace name
-    isGroupNamespace: boolean value to determine if the current iteration is for group
-  ###
-  splitNamespace: (name, isGroupNamespace) ->
-    arr = name.split(/\.|\//) # Split template into array
-
-    # Determine the maximum depth to compile
-    if isGroupNamespace then max = arr.length else max = arr.length - 1
-
-    @namespaces.push arr[0] # Push base namespace
-
-    str = arr[0] # Set base namespace
-
-    for i in [1...max]
-      str += ".#{arr[i]}" # Appends additional levels of namespacing
-      @namespaces.push str
-
-
-
-
-
-
-
-
-###
-Precompiler(groupSettings)
-Description: Creates a Precompiler instance for executing precompiling work
-
-settings:
-  "namespace": String(Required), namespace object when including templates to browser
-  "source": String(Required), relative path to source directory
-  "output": String, relative path to output directory
-  "templates": Array(Required), names of templates to be precompiled
-  "compileDebug": Boolean(default: false), whether to compile Jade debugging
-  "inline": Boolean(default: false), whether to inline Jade runtime functions
-
-function callback(err, res) {}
-(Optional) For Javascript API. If specified "res" will be the String of compiled templates
-of this group.
-
-Note: Either one or both of "callback"/"output" must be present, or there will be no output
-channel and the module will throw an error.
-###
-class Precompiler
-  ###
-  Binds settings, checks for dependencies and throw errors
-  ###
-  constructor: (groupSettings) ->
-    @settings = groupSettings
-
-    if @settings.source
-      @settings.source = path.normalize(globalSettings.dir + '/' + @settings.source)
-    else
-      throw 'ERR: No source directory defined for ' + groupSettings.namespace
-
-      @settings.output = path.normalize(globalSettings.dir + '/' + @settings.output)
-
-
-  ###
-  getNamespaces()
-  Description: get and return all the namespace declarations
-  ###
-  getNamespaces: (cb) ->
-    Namespacer @settings, (err, res) =>
-      if err? then throw err
-      else
-        cb(res.join('\n') + '\n')
+    _.extend @, defaults, options

   ###
   compile()
   Description: Flow control and execution for the compilation
   ###
   compile: ->
-    @getNamespaces (namespaces) =>
+    buf = []
+    buf.push """
+    (function(){
+      window.#{@namespace} = window.#{@namespace} || {};
+      #{@helpers() if @helpers isnt false and @inline isnt true}
+    """

-      {templates, inline, helpers, output} = @settings
-      buf = []
-
-      buf.push "(function(){"
+    for template in @templates
+      buf.push @compileTemplate(template).toString()

-      buf.push namespaces if namespaces isnt false
-
-      buf.push @helpers() if helpers isnt false and inline isnt true
-
-      for template in @settings.templates
-        buf.push @compileTemplate(template).toString()
-
-      buf.push "})();"
-
-      buf = buf.join("")
-
-      buf = compressor(buf, 'js')
-
-      if output?
-        fs.writeFileSync @settings.output, buf
-        console.log ('Saved and Uglified').bold + ':' + output if @settings.verbose
+    buf.push '})();'
+    return buf.join ''

   ###
   compileTemplate()
   Description: Compiles individual templates and returns them to compile()
   ###
   compileTemplate : (template) ->
-    {source, namespace, compileDebug, inline} = @settings
-
     templateNamespace = template.replace(/\//g, '.') # Replaces '/' with '.'
-
-    if @settings.verbose
-      console.log "Compiling #{namespace}.#{templateNamespace} from #{source+template}"
-
-    sourceFile = source + template + '.jade'
+    sourceFile = @source + template + '.jade'
     data = fs.readFileSync(sourceFile, 'utf8')

-    namespace + '.' + templateNamespace + ' = ' + jade.compile(data, { compileDebug: compileDebug || false, inline: inline || false, client: true }) + ';\n'
+    "#{@namespace}.#{templateNamespace} = #{jade.compile(data, { compileDebug: @debug || false, inline: @inline || false, client: true })};\n"

   ###
   helpers()
@@ -267,47 +80,20 @@
     escape = jade.runtime.escape.toString()
     rethrow = jade.runtime.rethrow.toString()

-    if @settings.compileDebug
-      obj = """
+    if @debug
+      obj = '
         var jade = {
           attrs: attrs,
           escape: escape,
           rethrow: rethrow
         };\n
-      """
+      '
       [attrs, escape, rethrow, obj].join('\n')
     else
-      obj = """
+      obj = '
         var jade = {
           attrs: attrs,
           escape: escape
         };\n
-      """
+      '
       [attrs, escape, obj].join('\n')
-
-
-extend = (main, sub) ->
-  for prop of sub
-    main[prop] = sub[prop] if sub[prop]?
-  main
-
-# Global settings
-globalSettings = {}
-
-###
-precompile(settings, dir)
-Description: Main precompile function
-
-Params:
-  settings(object): Global settings object for tmpl-precompile
-    "verbose": Boolean(default:false), if should output compile info on console
-    "relative": Boolean(default:true), if paths to each template is relative to settings file
-  dir(string): Main execution directory
-###
-precompile = (settings,dir) ->
-  globalSettings = settings
-  globalSettings.dir = dir
-
-  precompiler = new Precompiler(settings)
-  precompiler.compile()
-


[ #316199 ] Delay initialization of the loading indicator until it is needed

--- src/resources-packaged/ops/javascript/orbeon/xforms/LoadingIndicator.coffee
+++ src/resources-packaged/ops/javascript/orbeon/xforms/LoadingIndicator.coffee
@@ -21,19 +21,18 @@

 class LoadingIndicator

-    constructor: (form) ->
+    constructor: (@form) ->
         @shownCounter = 0
-        # Span for loading indicator is a child of the form element
-        loadingSpan = _.detect form.childNodes, (node) -> YD.hasClass node, "xforms-loading-loading"
-        # On scroll, we might need to move the overlay
-        loadingSpan.style.display = "block"
-        @initialRight = YD.getViewportWidth() - YD.getX loadingSpan
-        @initialTop = YD.getY loadingSpan
+
+        # For now hide the loading indicator
+        @loadingSpan = _.detect @form.childNodes, (node) -> YD.hasClass node, "xforms-loading-loading"
+        @loadingSpan.style.display = "none"
+
+        # Differ creation of the overlay to the first time we need it,
+        @loadingOverlay = null
+        # On scroll or resize, move the overlay so it stays visible
         Overlay.windowScrollEvent.subscribe => @_updateLoadingPosition()
-        # Create a YUI overlay after showing the span
-        @loadingOverlay = new YAHOO.widget.Overlay loadingSpan, { visible: false, monitorresize: true }
-        Utils.overlayUseDisplayHidden @loadingOverlay
-        loadingSpan.style.right = "auto"
+        Overlay.windowResizeEvent.subscribe => @_updateLoadingPosition()

         # When an Ajax call starts, we might want to show the indicator
         Connect.startEvent.subscribe =>
@@ -73,6 +72,7 @@

     # Actually shows the loading indicator (no delay or counter)
     show: (message) ->
+        @_initLoadingOverlay()
         message ?= DEFAULT_LOADING_TEXT
         OD.setStringValue @loadingOverlay.element, message
         @loadingOverlay.show()
@@ -83,7 +83,20 @@
         if not Globals.loadingOtherPage
             @loadingOverlay.cfg.setProperty "visible", false

+    _initLoadingOverlay: () ->
+        # Initialize @loadingOverlay if not done already
+        if not @loadingOverlay?
+            # On scroll, we might need to move the overlay
+            @loadingSpan.style.display = "block"
+            @initialRight = YD.getViewportWidth() - YD.getX @loadingSpan
+            @initialTop = YD.getY @loadingSpan
+            # Create a YUI overlay after showing the span
+            @loadingOverlay = new YAHOO.widget.Overlay @loadingSpan, { visible: false, monitorresize: true }
+            Utils.overlayUseDisplayHidden @loadingOverlay
+            @loadingSpan.style.right = "auto"
+
     _updateLoadingPosition: () ->
+        @_initLoadingOverlay()
         @loadingOverlay.cfg.setProperty "x", do =>
             # Keep the distance to the right border of the viewport the same
             scrollX = YD.getDocumentScrollLeft()


base route fix

base route fix

--- templates/skeleton/app/assets/javascripts/admin/routes/base_route.js.coffee
+++ templates/skeleton/app/assets/javascripts/admin/routes/base_route.js.coffee
@@ -1,6 +1,4 @@
-Admin.BaseRoute = Ember.Route.extend Barbecue.AjaxMixin, Barbecue.FlashMixin, Barbecue.CrudMixin
-
+Admin.BaseRoute = Ember.Route.extend Barbecue.AjaxMixin, Barbecue.FlashMixin, Barbecue.CrudMixin,
   activate: ->
     @_super()
     window.scrollTo(0,0)
-


Avoid tokens shim in isBufferRowCommented

--- src/text-editor.coffee
+++ src/text-editor.coffee
@@ -2471,9 +2471,8 @@
   # Extended: Determine if the given row is entirely a comment
   isBufferRowCommented: (bufferRow) ->
     if match = @lineTextForBufferRow(bufferRow).match(/\S/)
-      scopeDescriptor = @tokenForBufferPosition([bufferRow, match.index]).scopes
       @commentScopeSelector ?= new TextMateScopeSelector('comment.*')
-      @commentScopeSelector.matches(scopeDescriptor)
+      @commentScopeSelector.matches(@scopeDescriptorForBufferPosition([bufferRow, match.index]).scopes)

   logCursorScope: ->
     scopeDescriptor = @getLastCursor().getScopeDescriptor()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/text_effect.coffee
+++ src/text_effect.coffee
@@ -24,7 +24,7 @@

 @name draw
 @methodOf TextEffect#
-@params
+@params {Pix}
 @event
 ###
 TextEffect = (I={}) ->


levels and default game states

levels and default game states

--- src/text_screen.coffee
+++ src/text_screen.coffee
@@ -7,7 +7,7 @@
   self = GameState(I).extend
     centerText: (canvas, options={}) ->
       font = options.font || I.font
-      size = options.size || I.font
+      size = options.size || I.fontSize
       canvas.font "#{size}px #{font}"

       canvas.centerText


require.paths is gone

require.paths is gone

--- test/test_eco.coffee
+++ test/test_eco.coffee
@@ -125,9 +125,7 @@
     test.done()

   "requiring eco templates as modules": (test) ->
-    require.paths.unshift __dirname + "/fixtures"
-
-    hello = require "hello.eco"
+    hello = require __dirname + "/fixtures/hello.eco"
     test.ok typeof hello is "function"
     test.same fixture("hello.out.1"), hello name: "Sam"


Remove reference to spec-launcher, which no longer exists

--- lib/spec-runner.coffee
+++ lib/spec-runner.coffee
@@ -1,4 +1,3 @@
-SpecLauncher = require './spec-launcher'
 SpecRunnerView = require './spec-runner-view'

 configUri = "atom://spec-runner"


Fix error on tab with no title. Closes #10

--- lib/tab-watcher.coffee
+++ lib/tab-watcher.coffee
@@ -55,7 +55,7 @@
     item.emitter.emit "did-change-title", item.getTitle()

   getEmberPodName: (item) =>
-    filePath = item.getPath().replace(/\\/g, '/')
+    filePath = item.getPath()?.replace(/\\/g, '/')
     pieces = filePath?.split("/")

     return false if !pieces || !pieces.length


form workflow: readability tweak

form workflow: readability tweak

--- client/landing/app/CommonViews/formworkflow.coffee
+++ client/landing/app/CommonViews/formworkflow.coffee
@@ -25,6 +25,6 @@
     @forms[form]?.hide() for form in forms

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


Tiny additions to finger tree code.

Tiny additions to finger tree code.

--- lib/finger_tree.coffee
+++ lib/finger_tree.coffee
@@ -3,6 +3,13 @@
 #
 # Copyright (c) 2011 Olaf Delgado-Friedrichs (odf@github.com)
 # --------------------------------------------------------------------
+
+
+if typeof(require) != 'undefined'
+  require.paths.unshift('#{__dirname}/../lib')
+  { Sequence } = require('sequence')
+else
+  { Sequence } = pazy

 # A node.
@@ -140,7 +147,8 @@
   reduceLeft  = (op) -> (z, x) -> x.reduceLeft(z, op)
   reduceRight = (op) -> (x, z) -> x.reduceRight(op, z)

-  asTree = (s) -> reduceLeft((a, b) -> a.before b) Empty, s
+  asTree = (s) -> s.reduceLeft Empty, (a, b) -> a.before b
+  asSeq  = (s) -> s.reduceRight ((a, b) -> Sequence.conj a, -> b), null

   constructor: (left, mid, right) ->
     @l = left
@@ -192,6 +200,16 @@
     else
       new Deep @l, @m, @r.init()

+  nodes = (n, s) ->
+    if n == 0
+      null
+    else if n == 2 or n % 3 == 1
+      Sequence.conj new Tree2(s.take(3).into([])...), -> nodes n-2, s.drop 2
+    else if n >= 3
+      Sequence.conj new Tree3(s.take(3).into([])...), -> nodes n-3, s.drop 3
+    else
+      raise new Error "this should not happen"
+

 # --------------------------------------------------------------------
 # Exporting.
@@ -199,4 +217,4 @@

 exports ?= this.pazy ?= {}

-exports.Empty       = Empty
+exports.Empty = Empty


Trigger "modified" event again with a short delay to allow the CSS to redraw and the overlay to update correctly

--- plugins/image.coffee
+++ plugins/image.coffee
@@ -99,6 +99,10 @@
                 document.execCommand "insertImage", null, $(".halloimage-activeImage").attr('src')
                 img = document.getSelection().anchorNode.firstChild
                 jQuery(img).attr "alt", jQuery(".caption").value
+
+                triggerModified = () ->
+                    widget.element.trigger "hallomodified"
+                window.setTimeout triggerModified, 100
                 widget._closeDialog()

             @options.dialog.find(".halloimage-activeImage, ##{widget.options.uuid}-#{widget.widgetName}-addimage").click insertImage


fix typo

fix typo

--- lib/insert-mode.coffee
+++ lib/insert-mode.coffee
@@ -32,7 +32,7 @@
 class CopyFromLineAbove extends InsertMode
   @extend()
   complete: true
-  rowTransration: -1
+  rowTranslation: -1

   getTextInScreenRange: (range) ->
     @editor.getTextInBufferRange(@editor.bufferRangeForScreenRange(range))
@@ -41,14 +41,14 @@
     @editor.transact =>
       for cursor in @editor.getCursors()
         {row, column} = cursor.getScreenPosition()
-        row += @rowTransration
+        row += @rowTranslation
         continue if row < 0 # No line to copy from.
         range = [[row, column], [row, column+1]]
         cursor.selection.insertText @getTextInScreenRange(range)

 class CopyFromLineBelow extends CopyFromLineAbove
   @extend()
-  rowTransration: +1
+  rowTranslation: +1

 module.exports = {
   CopyFromLineAbove,


Fix pm tests

Fix pm tests

--- test/processManagerTest.coffee
+++ test/processManagerTest.coffee
@@ -10,6 +10,9 @@

   it 'should be have #start', ->
     pm.start.should.be.a.Function
+
+  it 'should be have #stop', ->
+    pm.stop.should.be.a.Function

   describe '#start', ->
     node = pm.startNode()
@@ -27,17 +30,15 @@
     it '#startNode should return number pid', ->
       node.pid.should.be.a.Number

+  ###
   describe '#stop', ->
-    it 'should be a function', ->
-      pm.stop.should.be.a.Function
-    ###
     it 'should return true', ->
       pm.stop (callback) ->
         callback.should.be.true

     it 'should not return error', ->
       pm.stop.should.not.throw()
-    ###
+  ###

   describe '#writePid', ->
     it 'should be a function', ->


Added hints

Added hints

--- src/trivia-game.coffee
+++ src/trivia-game.coffee
@@ -18,6 +18,7 @@
 #   !skip - skip the current question
 #   !answer <answer> or !a <answer> - provide an answer
 #   !score <player> - check the score of the player
+#   !hint - hints for trivia question
 #
 # Author:
 #   yincrash
@@ -70,6 +71,7 @@

 class Game
   @currentQ = null
+  @hintLength = null

   constructor: (@robot, @scoreKeeper) ->
     buffer = Fs.readFileSync(Path.resolve('./res', 'questions.json'))
@@ -80,6 +82,7 @@
     unless @currentQ # set current question
       index = Math.floor(Math.random() * @questions.length)
       @currentQ = @questions[index]
+      @hintLength = 1
       @robot.logger.debug "Answer is #{@currentQ.answer}"
       # remove optional portions of answer that are in parens
       @currentQ.validAnswer = @currentQ.answer.replace /\(.*\)/, ""
@@ -96,6 +99,7 @@
     if @currentQ
       resp.send "The answer is #{@currentQ.answer}."
       @currentQ = null
+      @hintLength = null
       @askQuestion(resp)
     else
       resp.send "There is no active question!"
@@ -122,10 +126,28 @@
         if newScore? then resp.send "#{user} has #{newScore} points."

         @currentQ = null
+        @hintLength = null
       else
         resp.send "#{guess} is incorrect."
     else
       resp.send "There is no active question!"
+
+  hint: (resp) ->
+    if @currentQ
+      # Check if the hintLength is greater than answerLength/2
+      if @hintLength > answer.length/2
+        resp.send "You have run out of hints for the active question"
+        return
+
+      answer = @currentQ.validAnswer
+      hint = answer.substr(0,@hintLength) + answer.substr(@hintLength,(answer.length + @hintLength)).replace(/./g, ".")
+
+      if @hintLength <= answer.length
+        @hintLength += 1
+
+      resp.send hint
+    else
+      resp.send "There is no active question!"

   checkScore: (resp, name) ->
     name = name.toLowerCase().trim()
@@ -159,5 +181,7 @@

   robot.hear /!t (top|bottom)( \d+)?/i, (resp) ->
     amount = parseInt(resp.match[2])
-    amount = if amount? amount else 10
     game.leaderBoard(resp, resp.match[1], amount)
+
+  robot.hear /^!hint/i, (resp) ->
+    game.hint(resp)


round xy values early

round xy values early

--- src/ui/features.coffee
+++ src/ui/features.coffee
@@ -68,13 +68,17 @@
         point = app.along(segment.geometry.coordinates, interval * (n + 0.5))
         point.properties = {symbols: segment.properties.symbols}
         if app.inside(point.geometry.coordinates, db.bbox)
-          [x, y] = map.projection(point.geometry.coordinates)
+          xyproj = map.projection(point.geometry.coordinates)
+          x = d3.round(xyproj[0])
+          y = d3.round(xyproj[1])
           if app.inside([x, y], [0, 0, map.width, map.height])
-            segmentSymbols.push(point)
+            symbol = {properties: point.properties, x: x, y: y}
+            segmentSymbols.push(symbol)

     symbols.selectAll('.segmentSymbol').data(segmentSymbols)
       .enter().append('g')
         .attr('class', 'symbol segmentSymbol')
+        .attr('transform', (d) -> "translate(#{d.x},#{d.y})")
         .each (d) ->
           return unless d.properties.symbols
           for i in d3.range(0, d.properties.symbols.length)
@@ -85,20 +89,20 @@

     poiSymbols = []
     for poi in poiLayer
-      [x, y] = map.projection(poi.geometry.coordinates)
+      xyproj = map.projection(poi.geometry.coordinates)
+      x = d3.round(xyproj[0])
+      y = d3.round(xyproj[1])
       if x > 0 and x < map.width and y > 0 and y < map.height
-        poiSymbols.push(poi)
+        symbol = {properties: poi.properties, x: x, y: y}
+        poiSymbols.push(symbol)

     symbols.selectAll('.poiSymbol').data(poiSymbols)
       .enter().append('g')
         .attr('class', 'symbol poiSymbol')
+        .attr('transform', (d) -> "translate(#{d.x},#{d.y})")
         .each (poi) ->
           if (drawSymbol = app.symbol[poi.properties.type])?
             drawSymbol(d3.select(@))
-
-    symbols.selectAll('.symbol')
-        .attr 'transform', (d) ->
-          "translate(#{d3.round(d) for d in map.projection(d.geometry.coordinates)})"

   map.dispatch.on 'redraw.features', ->
     render()


Re stub time controller methods

Re stub time controller methods

--- controllers/time_entries.coffee
+++ controllers/time_entries.coffee
@@ -1,63 +1,25 @@
-crypto = require 'crypto'
-_ = require 'underscore'
 async = require 'async'
-
-Singleton = require 'singleton'
-Riak = require "#{__dirname}/../db/riak"
+sql = require 'sql'
+BaseController = require "#{__dirname}/base"
 TimeEntry = require "#{__dirname}/../models/time_entry"

-class TimeEntriesController extends Singleton
-  bucket: 'time_entries'
-  idGen: ()->
-    sha = crypto.createHash 'sha1'
-    _.each arguments, (d)->
-      sha.update JSON.stringify d
-    sha.digest 'hex'
+class TimeEntriesController extends BaseController
+  time_entry: sql.define
+    name: 'time_entries'
+    columns: (new TimeEntry).columns()

   create: (time_entry, callback)->
-    if time_entry.userId and time_entry.projectId
-      time_entry.id = @idGen time_entry
+    if time_entry.validate()

-      handler = (err, obj, meta) ->
-        if err
-          callback err
-        else
-          callback null, time_entry
-
-      Riak.getClient().save @bucket, time_entry.id, time_entry,
-        {
-          index:
-            userId: new String time_entry.userId
-            projectId: new String time_entry.projectId
-        }, handler
     else
-      callback new Error "User or Project Id not provided"
+      callback new Error "Time Entry did not pass validation"

   getOne: (key, callback)->
-    Riak.getClient().get @bucket, key, (err, te, meta)->
-      if err
-        return callback err
-      else
-        callback null, new TimeEntry te

   deleteOne: (key, callback)->
-    Riak.getClient().remove @bucket, key, callback

   deleteForProject: (projectId, callback)->
-    client = Riak.getClient()
-    client.query @bucket, {projectId: new String projectId}, (err, keys)=>
-      if err
-        callback err
-      else if keys.length is 0
-        callback()
-      else
-        async.eachLimit keys, 100, (key,cb)=>
-          client.remove @bucket, key, cb
-        , callback

   getForProject: (projectId, callback)->
-    Riak.getClient().mapreduce.add(
-      bucket: @bucket, index: "projectId_bin", key: new String projectId
-    ).map('Riak.mapValuesJson').run callback

 module.exports = TimeEntriesController.get()


Make sure fancybox updates after things ajax in

--- app/assets/javascripts/replace_self_on_load.js.coffee
+++ app/assets/javascripts/replace_self_on_load.js.coffee
@@ -4,3 +4,4 @@
     $(el).removeAttr('data-replace-self-on-load')
     $.get url, (html) ->
       $(el).replaceWith(html)
+      $.fancybox.update()


Fix basket setting from a previous buy

Fix basket setting from a previous buy

--- app/assets/javascripts/services/basket_service.coffee
+++ app/assets/javascripts/services/basket_service.coffee
@@ -14,7 +14,7 @@
         newAmount = currentAmount + amount
         $location.search(name, newAmount).replace()

-    empty = ->
+    emptyBasketAndRemoveBuyer = ->
       searchObj = $location.search()
       delete searchObj.buyer
       delete searchObj[entry.product.name] for entry in products
@@ -73,22 +73,26 @@
         return buyer if buyer?.then?
         buyer = User.find(username).then (user)-> buyer = user

-    setFromBuy = (buy)->
+
+    setSearch = (buy)->
       search = $location.search()
       search.buyer = buy.buyer.username
       search.basket = "true"
       for entry in buy.consistsOfProducts
         search[entry.product.name] = entry.amount
+      search
+
+    setFromBuy = (buy)->
       if $location.path() == '/buy'
-        empty()
-        $location.search(search).replace()
+        emptyBasketAndRemoveBuyer()
+        $location.search(setSearch(buy)).replace()
       else
-        $location.search(search).path('/buy')
+        $location.search(setSearch(buy)).path('/buy')

     #return api-object with methods/objects accessible from outside
     exports = {
       changeAmount: changeAmount
-      empty: empty
+      empty: emptyBasketAndRemoveBuyer
       productCount: productCount
       price: price
       isBuyable: isBuyable


Improve events in demo steps

Improve events in demo steps

--- app/frontend/javascripts/visualization-demo.js.coffee
+++ app/frontend/javascripts/visualization-demo.js.coffee
@@ -59,7 +59,7 @@
       $('#visualization-add-node-btn').one 'click', =>
         @addNextPopover()
     else if @current_step == 2
-      $('.popover-demo .popover-content a').click (e) =>
+      $('.popover-demo .popover-content a').one 'click', (e) =>
         e.preventDefault()
         @addNextPopover()
     else if @current_step == 3 || @current_step == 5
@@ -93,12 +93,12 @@
       @nodes.once 'change:node_type', =>
         @addNextPopover()
     else if @current_step == 15
-      $('.popover-demo .popover-content a').click (e) =>
+      $('.popover-demo .popover-content a').one 'click', (e) =>
         e.preventDefault()
         $(@steps[@current_step].selector).popover('hide').popover('destroy')
         Backbone.trigger 'visualization.demo.loadData'
     else if @current_step == 16
-      $('.visualization-graph-menu-actions .btn-configure').click (e) =>
+      $('.visualization-graph-menu-actions .btn-configure').one 'click', =>
         @addNextPopover()
     else if @current_step == 17
       Backbone.once 'visualization.config.updateNodesSize', =>


#190 enabled pagination

#190 enabled pagination

--- app/assets/javascripts/admin/views/team/team_list_view.coffee
+++ app/assets/javascripts/admin/views/team/team_list_view.coffee
@@ -72,11 +72,9 @@

     @listenTo(app.vent, "paginationView:filter", @filter)

-    @collection = new TeamCollection()
     @collection.fetch(
-      data:
-        "isEditable=true"
-      silent: true
+      data : "isEditable=true"
+      silent : true
     ).done( =>
       @collection.goTo(1)
     )


Boyscouting coffeescript

Boyscouting coffeescript

--- core/app/backbone/collections/notifications.coffee
+++ core/app/backbone/collections/notifications.coffee
@@ -7,12 +7,7 @@
       (item)-> !item
     unreads.length

-  markAsRead: (opts = {}) ->
-
-    $.ajax({
-      url: @url() + "/mark_as_read",
-      type: "POST",
-      success: -> opts.success.apply(this, arguments) if ( typeof opts.success == "function" )
-      error: -> opts.error.apply(this, arguments) if ( typeof opts.error == "function" )
-    });
-
+  markAsRead: (options = {}) ->
+    $.ajax _.extend {}, options,
+      url: @url() + "/mark_as_read"
+      type: "POST"


more button work on standard buttons

more button work on standard buttons

--- Framework/Views/Controls/Button_Framework.coffee
+++ Framework/Views/Controls/Button_Framework.coffee
@@ -1,36 +1,48 @@
 root.Button_Framework = class Button
   constructor:(options = {}) ->
-
+    # Settings
     @settings = root._.extend({
+      borderWidth: 1
+      borderRadius: 5
+      bottom: null
       enabled: true
-      borderRadius: 3
-      opacity: 1.0
+      fontSize: 12
+      height: 30
+      left: null
+      right: null
+      text: "Button"
+      top: null
+      width: Ti.UI.SIZE
       onClick: () => Ti.API.info("button clicked")
     }, options)
+
     @settings.style = root._.extend({
       borderColor: "#CCC"
-      gradient: ["#EEE", "#DDD"]
+      gradient: ["#DDD", "#CCC"]
       labelColor: "#FFF"
       labelShadowColor: "#CCC"
-      height: 30
-      width: Ti.UI.FILL
-      bottom: null
-      top: null
-      left: null
-      right: null
-      labelText: "Button"
-      labelFontSize: 12
     }, options.style)
+
     @settings.onClickStyle = root._.extend({}, options.onClickStyle)
+    # /Settings

     @view = Ti.UI.createView({
-      height: @startStyle("height")
-      width: @startStyle("width")
-      bottom: @startStyle("bottom")
-      top: @startStyle("top")
-      left: @startStyle("left")
-      right: @startStyle("right")
+      bottom: @settings.bottom
+      height: @settings.height
+      left: @settings.left
+      right: @settings.right
+      top: @settings.top
+      width: @settings.width
     })
+
+    @disabledOverlay = Ti.UI.createView({
+      height: Ti.UI.FILL
+      width: Ti.UI.FILL
+      backgroundColor: "#EEE"
+      opacity: 0.5
+      zIndex: 10
+    })
+    @view.add(@disabledOverlay)

     @view.addEventListener("singletap", () => @settings.onClick() if @view.enabled)
     @view.addEventListener("touchstart", () => @onTouchStart() if @view.enabled)
@@ -40,17 +52,15 @@
       height: Ti.UI.FILL
       width: Ti.UI.FILL
       borderRadius: @settings.borderRadius
-      borderWidth: 1
-      borderColor: @startStyle("borderColor")
+      borderWidth: @settings.borderWidth
+      borderColor: @settings.style.borderColor
       backgroundGradient: {
         type: 'linear'
         startPoint: { x: 0, y: 0 }
         endPoint: { x: 0, y: "100%" }
-        colors: @startStyle("gradient")
+        colors: @settings.style.gradient
         backfillStart: false
       }
-      opacity: @settings.opacity
-      zIndex: 1
     })
     @view.add(@button)

@@ -59,20 +69,19 @@
       width: Ti.UI.SIZE
       height: Ti.UI.SIZE
       backgroundColor: "transparent"
-      zIndex: 2
     })

-    if @startStyle("iconSettings")
-      @icon = Ti.UI.createImageView(@startStyle("iconSettings"))
+    if @settings.iconSettings
+      @icon = Ti.UI.createImageView(@settings.iconSettings)
       @content.add(@icon)

-    if @startStyle("labelText") && @startStyle("labelText") != ""
+    if @settings.text && @settings.text != ""
       @label = Ti.UI.createLabel({
-        color: @startStyle("labelColor")
-        text: @startStyle("labelText")
-        font: { fontSize: @startStyle("labelFontSize"), fontWeight: "bold" }
+        color: @settings.style.labelColor
+        text: @settings.text
+        font: { fontSize: @settings.fontSize, fontWeight: "bold" }
         shadowOffset: { x: 1, y: 1 }
-        shadowColor: @startStyle("labelShadowColor")
+        shadowColor: @settings.style.labelShadowColor
         textAlign: "center"
         height: Ti.UI.SIZE
         width: Ti.UI.SIZE
@@ -86,62 +95,44 @@

   onTouchStart: =>
     @button.updateLayout({
-      borderColor: @clickStyle("borderColor")
+      borderColor: @settings.onClickStyle.borderColor
       backgroundGradient: {
         type: 'linear'
         startPoint: { x: 0, y: 0 }
         endPoint: { x: 0, y: "100%" }
-        colors: @clickStyle("gradient")
+        colors: @settings.onClickStyle.gradient
         backfillStart: false
       }
     })

     if @label
       @label.updateLayout({
-        color: @clickStyle("labelColor")
-        text: @clickStyle("labelText")
-        font: @clickStyle("labelFont")
-        shadowColor: @clickStyle("labelShadowColor")
+        color: @settings.onClickStyle.labelColor
+        shadowColor: @settings.onClickStyle.labelShadowColor
       })

   onTouchEnd: =>
     @button.updateLayout({
-      borderColor: @startStyle("borderColor")
+      borderColor: @settings.style.borderColor
       backgroundGradient: {
         type: 'linear'
         startPoint: { x: 0, y: 0 }
         endPoint: { x: 0, y: "100%" }
-        colors: @startStyle("gradient")
+        colors: @settings.style.gradient
         backfillStart: false
       }
     })

     if @label
       @label.updateLayout({
-        color: @startStyle("labelColor")
-        text: @startStyle("labelText")
-        font: @startStyle("labelFont")
-        shadowColor: @startStyle("labelShadowColor")
+        color: @settings.style.labelColor
+        shadowColor: @settings.style.labelShadowColor
       })
-
-
-  startStyle: (param) =>
-    if @settings.style[param] == null
-      null
-    else
-      @settings.style[param]
-
-  clickStyle: (param) =>
-    if @settings.onClickStyle[param] == null
-      if @settings.style[param]
-        @settings.style[param]
-      else
-        null
-    else
-      @settings.onClickStyle[param]

   setEnabled: (enabled) =>
     if enabled
       @view.enabled = true
+      @disabledOverlay.hide()
     else
       @view.enabled = false
+      @disabledOverlay.show()


test that task plan sets periods as changed

--- tutor/specs/flux/task-plan.spec.coffee
+++ tutor/specs/flux/task-plan.spec.coffee
@@ -22,8 +22,13 @@
     TaskPlanActions.createClonedPlan( newId, {
       planId: PLAN.id, courseId: COURSE_ID, due_at: moment()
     })
-    clone = TaskPlanStore.get(newId)
+    clone = TaskPlanStore.getChanged(newId)
     for attr in ['title', 'description', 'type', 'settings', 'is_feedback_immediate']
       expect(clone[attr]).to.deep.equal(PLAN[attr])

+    expect(clone.cloned_from_id).to.equal(PLAN.id)
+    for period in CourseStore.get(COURSE_ID).periods
+      tasking_plan = _.find(clone.tasking_plans, target_id: period.id)
+      expect(tasking_plan).to.exist
+
     undefined


remove console.log

remove console.log

--- tutor/src/components/qa/view-book.cjsx
+++ tutor/src/components/qa/view-book.cjsx
@@ -51,7 +51,7 @@
     {ecosystemId, section} = Router.currentParams()
     section = section or ReferenceBookStore.getFirstSection(ecosystemId).join('.')
     contentComponent = if @state.isShowingBook then QAContent else QAExercises
-    console.log 'Render section', section
+
     <ReferenceBook
       pageNavRouterLinkTarget='QAViewBookSection'
       menuRouterLinkTarget='QAViewBookSection'