Removed return statement from isValid function

--- coffee/main.coffee
+++ coffee/main.coffee
@@ -96,7 +96,7 @@

       isValid: ->
         return true if !@field.isValid
-        return @field.isValid(@$el, @model)
+        @field.isValid(@$el, @model)

       render: ->
         do (


Added an extra update of view to correct after cursor hack

--- coffee/main.coffee
+++ coffee/main.coffee
@@ -61,7 +61,7 @@
     else if mode in ['write', '']
       editor.setOption 'readOnly', no
       editor.focus()
-      updateView() if mode is ''
+      updateView true if mode is ''
   slide: (nr) -> updateView()
   theme: (v) ->
     model.theme = v
@@ -88,16 +88,16 @@
       saved = not err?
       updateTitle()

-updateView = ->
-  switch state.mode
-    when 'present'
+updateView = (force) ->
+  switch
+    when force or state.mode in ['read', 'write']
+      viewEl.innerHTML = marked editor.getValue()
+    when state.mode is 'present'
       md = editor.getValue().split /\n\s*\n\s*(?:[-*]\s*){3,}\n/
       if state.slide < md.length
         viewEl.innerHTML = marked md[state.slide || 0]
       else
         state.slide = md.length - 1
-    when 'read', 'write'
-      viewEl.innerHTML = marked editor.getValue()
     else
       cline = editor.getCursor().line
       md = editor.getValue().split '\n'
@@ -114,8 +114,7 @@
       if cursorTop < scrollTop or cursorTop > scrollTop + viewHeight - cursorHeight
         viewWrapEl.scrollTop = cursorTop - viewHeight/2

-updateTitle = ->
-  document.title = (if saved then '' else '*')+docTitle()
+updateTitle = -> document.title = (if saved then '' else '*')+docTitle()

 updateThemes = ->
   model.themes = [ 'serif', 'cv' ].map (name) ->
@@ -126,6 +125,7 @@
 nextSlide = -> state.slide = (state.slide || 0)+1
 prevSlide = -> state.slide = Math.max (state.slide || 0)-1, 0

+correctionTimer = null
 saveTimer = null
 editor = CodeMirror.fromTextArea document.getElementById('input-md'),
   mode: 'gfm'
@@ -140,6 +140,8 @@
     if saved
       saved = no
       updateTitle()
+    clearTimeout correctionTimer
+    correctionTimer = setTimeout (-> updateView true), 1000
     clearTimeout saveTimer
     saveTimer = setTimeout save, 5000
   else


Watch gruntfile.coffee

Watch gruntfile.coffee

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -14,6 +14,10 @@
       coffeeScript:
         files: ['spec/**/*.coffee']
         tasks: ['test']
+      config:
+        files: ['gruntfile.coffee']
+        options:
+          reload: true

   grunt.registerTask 'default', ['test']
   grunt.registerTask 'test', ['jasmine_nodejs:test']


DatePicker: Default to 24 hours timeframe and allow override.

--- app/assets/javascripts/pgdatagraph/datagraph.js.coffee
+++ app/assets/javascripts/pgdatagraph/datagraph.js.coffee
@@ -11,6 +11,7 @@
       { label: "last 2 weeks",  duration: "-2w" },
       { label: "last 24 hours", duration: "-1d" }
     ]
+    defaultDuration: '-1d'
     series: {}
     dateFormat: "M d, yy"
     renderer: "line"
@@ -65,9 +66,10 @@
           @datePickers.append $datePicker
           if i is 0
             $datePicker.addClass("#{@options.className}__datepicker_first")
-            @selectDatePicker $datePicker
           if i is @options.datePickers.length - 1
             $datePicker.addClass("#{@options.className}__datepicker_last")
+          if datePicker.duration == @options.defaultDuration
+            @selectDatePicker $datePicker

     instance = @
     @datePickers.on "click", ".#{@options.className}__datepicker", (event) ->


Don't change focus to the same focus.

Don't change focus to the same focus.

--- app/assets/javascripts/roofpig/EventHandlers.js.coffee
+++ app/assets/javascripts/roofpig/EventHandlers.js.coffee
@@ -5,13 +5,14 @@
 class @EventHandlers

   @set_focus: (new_focus) ->
-    @dom_handler.has_focus(false) if @focus
+    if @focus != new_focus
+      @dom_handler.has_focus(false) if @focus

-    @focus = new_focus
-    @camera = @focus.camera
-    @dom_handler = @focus.dom_handler
+      @focus = new_focus
+      @camera = @focus.camera
+      @dom_handler = @focus.dom_handler

-    @dom_handler.has_focus(true)
+      @dom_handler.has_focus(true)

   @initialize: () ->
     $("body").keydown (e) -> EventHandlers.key_down(e)


renders submitted page

renders submitted page

--- app/assets/javascripts/views/submitted_view.coffee
+++ app/assets/javascripts/views/submitted_view.coffee
@@ -9,12 +9,24 @@
     @renderFinishedMap()

   template: ->
+    map_height = $(document).height() - 350
     """

You submitted this boundary

-        http://example.com/3478357835
+

     """

   renderFinishedMap: ->
-    #TODO+    @createMap()
+    @polygon = L.polygon(@farm.points)
+    @polygon.addTo(@map)
+    @map.fitBounds(@polygon.getBounds())
+
+  createMap: ->
+    @map = L.map('map').setView([51.505, -0.09], 13)
+    osmUrl='http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
+    osmAttrib='Map data © OpenStreetMap contributors'
+    osm = new L.TileLayer(osmUrl, {attribution: osmAttrib})
+    @map.addLayer(osm)
+


adds ability to constrain directional using a given cardinal.

--- app/assets/utensils/directional/directional.coffee
+++ app/assets/utensils/directional/directional.coffee
@@ -17,6 +17,10 @@

   getPlacementAndConstrain: ->
     @constrainToViewport(@getPlacementFromCardinal())
+
+
+  getPlacementAndConstrainCardinal: (cardinal) ->
+    @constrainToViewportByCardinal(@getPlacementFromCardinal(cardinal))

   getPlacementFromCardinal: (cardinal=@cardinal) ->
@@ -69,6 +73,24 @@
     return position

+  constrainToViewportByCardinal: (position) ->
+    cardinal = position.cardinal
+    wt = @win.scrollTop()
+    wl = @win.scrollLeft()
+    ww = @win.width()
+    wh = @win.height()
+    ew = @element.outerWidth()
+    eh = @element.outerHeight()
+
+    switch cardinal
+      when 'south' then position.top = wt + wh - eh if (position.top + eh > wt + wh)
+      when 'north' then position.top = wt if (position.top < wt)
+      when 'east' then position.left = wl + ww - ew if (position.left + ew > wl + ww)
+      when 'west' then position.left = wl if (position.left < wl)
+    position
+
+
+
   getDimensions: (element) ->
     eo = element.offset()
     return {


Destroy groups in running jobs after they're emptied

--- assets/scripts/app/controllers/running_jobs.coffee
+++ assets/scripts/app/controllers/running_jobs.coffee
@@ -9,6 +9,9 @@
       @set 'sortedJobs', Em.ArrayProxy.extend(Em.SortableMixin,
         sortProperties: ['number']
       ).create(content: @get('jobs'))
+
+    willDestroy: ->
+      @get('sortedJobs').destroy()

     add: (job) ->
       @get('jobs').pushObject(job) unless @get('jobs').contains job
@@ -97,3 +100,4 @@
   removeGroup: (group) ->
     @get('groups').removeObject group
     delete @groupsBySlug[group.get('slug')]
+    group.destroy()


Proper name is just 'Question Library'

Proper name is just 'Question Library'

--- src/flux/current-user.coffee
+++ src/flux/current-user.coffee
@@ -40,7 +40,7 @@
       student: 'viewPerformanceForecast'
       teacher: 'viewTeacherPerformanceForecast'
   questions:
-    label: 'Questions Library'
+    label: 'Question Library'
     roles:
       teacher: 'viewQuestionsLibrary'
   scores:


fix(sidebar): add info@ to blacklist

fix(sidebar): add info@ to blacklist

--- src/flux/models/utils.coffee
+++ src/flux/models/utils.coffee
@@ -541,6 +541,7 @@
       "support@"
       "alert[s]?@"
       "news@"
+      "info@"
       "automated@"
       "list[s]?@"
       "distribute[s]?@"


Update specs for package.json

Update specs for package.json

--- spec/apm-cli-spec.coffee
+++ spec/apm-cli-spec.coffee
@@ -536,7 +536,7 @@
           expect(fs.existsSync(path.join(packagePath, 'spec', 'fake-package-view-spec.coffee'))).toBeTruthy()
           expect(fs.existsSync(path.join(packagePath, 'spec', 'fake-package-spec.coffee'))).toBeTruthy()
           expect(fs.existsSync(path.join(packagePath, 'stylesheets', 'fake-package.css'))).toBeTruthy()
-          expect(fs.existsSync(path.join(packagePath, 'package.cson'))).toBeTruthy()
+          expect(fs.existsSync(path.join(packagePath, 'package.json'))).toBeTruthy()

     describe "when creating a theme", ->
       it "generates the proper file structure", ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/number_extensions.coffee
+++ src/number_extensions.coffee
@@ -74,7 +74,7 @@
   Math.round(this)

 ###*
-
+@name circularPo
 ###
 Number::circularPoints = (n, block) ->
   n.times (i) ->


Remove transitionEnd event listeners to stop them building up.

--- js/flight.coffee
+++ js/flight.coffee
@@ -102,10 +102,8 @@
       window.setTimeout =>
         @targetPanel.style.webkitTransition = "#{@speed} ease"
         @targetPanel.style.webkitTransform = "translateX(100%)"
-        @currentPanel.addEventListener("webkitTransitionEnd", =>
-          @finishTransition()
-        , false);
-      , 10
+        @currentPanel.addEventListener("webkitTransitionEnd", @finishTransition, false);
+      , 5

     else
       #do forward transition
@@ -123,12 +121,10 @@

         @targetPanel.style.webkitTransition = "#{@speed} ease"
         @targetPanel.style.webkitTransform = "translateX(100%)"
-        @currentPanel.addEventListener("webkitTransitionEnd", =>
-          @finishTransition()
-        , false);
+        @currentPanel.addEventListener("webkitTransitionEnd", @finishTransition, false);
       , 5

-  finishTransition: ->
+  finishTransition: =>
     @currentPanel.style.webkitTransition = "0s ease"
     @currentPanel.style.left = "-100%"
     @currentPanel.style.display = "none"
@@ -136,6 +132,7 @@
     @addClass(@targetPanel, 'visible')
     @back = false
     @isTransitioning = false
+    @currentPanel.removeEventListener("webkitTransitionEnd", @finishTransition, false);

   fitHeightToContent: ->


Fix `noPrompt` handling

Fix `noPrompt` handling

--- lib/Shell.coffee
+++ lib/Shell.coffee
@@ -52,7 +52,12 @@
         # Start
         process.nextTick =>
             if @isShell
-                @prompt() unless @set 'noPrompt'
+                command = @set 'command'
+                noPrompt = @set 'noPrompt'
+                if command
+                    @run command
+                else if not noPrompt
+                    @prompt()
             else
                 command = @set 'command'
                 @run command


Improve get doc

Improve get doc

--- lib/Table.coffee
+++ lib/Table.coffee
@@ -309,12 +309,15 @@
             callback null, if isArray then records else records[0]

     ###
-    Retrieve a record
-    -----------------
-    If options is an array, it is considered to be an array of properties
+    Retrieve one or multiple records
+    --------------------------------
+    If options is an array, it is considered to be the list of properties to retrieve.
     Options are
-    -   properties, array of properties to fetch, all if null
+    -   properties, array of properties to fetch, all properties if not provided
     -   force, force the retrieval of properties even if already present in the record objects
+    callback arguments are
+    -   err, error object if the command failed
+    -   records, object or array of object if command succeed. Object are null if record was not found
     ###
     get: (records, options, callback) ->
         if arguments.length is 2


get  or use

get  or use

--- lib/chain.coffee
+++ lib/chain.coffee
@@ -410,10 +410,11 @@
     results.removed = control.removed if control.removed?

     # call the done function if one exists
-    control._done? result.error, results
+    error = result.error ? results.failed
+    control._done? error, results

     # emit done with the same info
-    @emit 'done', result.error, results
+    @emit 'done', error, results

     # pass back the `results` we built
     return results


Set a default for Connection's config.options.default .

--- lib/debug.coffee
+++ lib/debug.coffee
@@ -10,6 +10,7 @@
     @options = @options || {}
     @options.data = @options.data || false
     @options.payload = @options.payload || false
+    @options.packet = @options.packet || false
     @options.token = @options.token || false

     @indent = '  '


Remove logging

Remove logging

--- lib/emmet.coffee
+++ lib/emmet.coffee
@@ -12,7 +12,6 @@
   activate: (@state) ->
     unless @actionTranslation
       @actionTranslation = {}
-      console.log CSON.readFileSync(path.join(__dirname, "../keymaps/emmet.cson"))
       for selector, bindings of CSON.readFileSync(path.join(__dirname, "../keymaps/emmet.cson"))
         for key, action of bindings
           # Atom likes -, but Emmet expects _


attempt to fix path issue

attempt to fix path issue

--- lib/minjs.coffee
+++ lib/minjs.coffee
@@ -2,17 +2,17 @@
 fs = require 'fs'

 compile = () ->
-  editors = atom.workspace.getEditors()
-  for e in editors
-    path = e.getPath()
-    if path.indexOf('.js') == path.length - 3 and path.indexOf('.min.js') < 0
-      result = minjs.minify(editor.getText(), {fromString: true});
-      new_name = path.replace('.js', '.min.js')
+	editor = atom.workspace.getActiveEditor()
+	path = editor.getPath()
+
+	if path.indexOf('.js') == path.length - 3 and path.indexOf('.min.js') < 0
+	result = minjs.minify(editor.getText(), {fromString: true});
+	new_name = path.replace('.js', '.min.js')

-      fs.writeFile(new_name, result.code, (err) ->
-          if err
-            console.log("Couldn't minify js file!")
-      )
+	fs.writeFile(new_name, result.code, (err) ->
+		 if err
+			console.log("Couldn't minify js file!")
+	)

 module.exports =
   activate: (state) =>


Fix unsubscribe bug

Fix unsubscribe bug

--- lib/pages.coffee
+++ lib/pages.coffee
@@ -151,10 +151,6 @@

   reload: ->
     @unsubscribe =>
-      delete @initPage
-      @requested = {}
-      @received = {}
-      @queue = []
       @call "CountPages", (e, total) =>
         @sess "totalPages", total
         p = @currentPage()
@@ -164,6 +160,10 @@

   unsubscribe: (cb) ->
     @call "Unsubscribe", =>
+      delete @initPage
+      @requested = {}
+      @received = {}
+      @queue = []
       cb()  if cb?

   setDefaults: ->


Only look at immediate children for Signature

By searching only for immediate children instead of any descendants, we avoid getting two Signatures found for a signed Response containing a signed Assertion.

According to the SAML 2.0 Assertion schema, the `<ds:Signature>` element must be a direct child of the `<Assertion>` element, so this should be a safe change.

--- lib/saml2.coffee
+++ lib/saml2.coffee
@@ -174,7 +174,7 @@
 check_saml_signature = (xml, certificate) ->
   doc = (new xmldom.DOMParser()).parseFromString(xml)

-  signature = xmlcrypto.xpath(doc, ".//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']")
+  signature = xmlcrypto.xpath(doc, "./*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']")
   return null unless signature.length is 1
   sig = new xmlcrypto.SignedXml()
   sig.keyInfoProvider = getKey: -> format_pem(certificate, 'CERTIFICATE')


OPCE19: fixed bug in Selection.split

OPCE19: fixed bug in Selection.split

--- lib/y-rte.coffee
+++ lib/y-rte.coffee
@@ -1,5 +1,5 @@
 _ = require 'underscore'
-
+debug = true
 # Function that translates an index from start (absolute position) into a
 # relative position in word index and offset
 #
@@ -240,16 +240,15 @@

       # joke here, because Insel means island in German
       inSel = @
+
+      # order is important because outSelLeft == selection
+      # outSelRight has to be updated first
+      outSelRight.leftPos = inSel.rightPos
+      outSelRight.rightPos = selection.rightPos
+      outSelRight.bind inSel.right, selection.right
+
       outSelLeft.leftPos = selection.leftPos
       outSelLeft.rightPos = inSel.leftPos
-      outSelLeft.bind selection.left, inSel.left
-
-      outSelRight.leftPos = inSel.rightPos
-      outSelRight.rightPos = inSel.rightPos
-      outSelRight.bind inSel.right, selection.right
-
-      # order is important because outSelLeft == selection
-      outSelRight.bind inSel.right, selection.right
       outSelLeft.bind selection.left, inSel.left
     else
       console.log "Impossible to split, #{@} is not in #{selection}"
@@ -332,6 +331,9 @@
       throw new Error "Missing argument left"
     if !right
       throw new Error "Missing argument right"
+
+    @left = left
+    @right = right
     if !(@ in @left.left)
       @left.left.push @
     if !(@ in @right.right)


pass more operator tests

pass more operator tests

--- test/operators.coffee
+++ test/operators.coffee
@@ -244,9 +244,9 @@
     a = 0
     ok 1 > a++ < 1

-  #test "jashkenas/coffee-script#891: incorrect inversion of chained comparisons", ->
-  #  ok (true unless 0 > 1 > 2)
-  #  ok (true unless (NaN = 0/0) < 0/0 < NaN)
+  test "jashkenas/coffee-script#891: incorrect inversion of chained comparisons", ->
+    ok (true unless 0 > 1 > 2)
+    ok (true unless (NaN = 0/0) < 0/0 < NaN)

   test "jashkenas/coffee-script#1234: Applying a splat to :: applies the splat to the wrong object", ->
     nonce = {}
@@ -265,10 +265,10 @@
     x = 2
     eq (- --x), -1

-  #test "Regression with implicit calls against an indented assignment", ->
-  #  eq 1, a =
-  #    1
-  #  eq a, 1
+  test "Regression with implicit calls against an indented assignment", ->
+    eq 1, a =
+      1
+    eq a, 1

   test "jashkenas/coffee-script#2155: conditional assignment to a closure", ->
     x = null


fixed brain syntax

fixed brain syntax

--- src/sentiment.coffee
+++ src/sentiment.coffee
@@ -5,27 +5,27 @@
 #   sentiment - lists sentiment stats (message contents NOT stored)
 #   who's happy?
 #   who's stressed?
-#   where's the happiness?
-#   where's the stress?
+#   where's happiness?
+#   where's stress?
 #
 # Author:
 #   Michi Kono
 #   Will Barksdale
 #
-# Configuration:
-#   HUBOT_SENTIMENT_BRAIN_INIT_TIMEOUT=N - wait for N milliseconds for brain data to load from redis. (default 10000)
 # Notes:
 #   Requires underscore and sentiment NPM modules
 #
 #   Tracks scores by current month. Collects sentiment on users [sentiment:week_digit:user] and
 #   channels [sentiment:week_digit:channel] in format:
 #
-#    [{
-#      name: user_or_channe_name
-#      year: 'YYYY',
-#      score_average: 0,
-#      score_count: 0
-#    }]
+#     {
+#       data: [{
+#         name: user_or_channe_name
+#         year: 'YYYY',
+#         score_average: 0,
+#         score_count: 0
+#       }]
+#     }
 #
 # Example:
 #   [sentiment:12:user:michi] = {n:a:yyyy} where n is the number of records, and a is the average score of the n records
@@ -33,9 +33,8 @@
 #
 sentiment = require 'sentiment'
 _ = require 'underscore'
-INIT_TIMEOUT = (if process.env.HUBOT_REACT_INIT_TIMEOUT then parseInt(process.env.HUBOT_SENTIMENT_BRAIN_INIT_TIMEOUT) else 10000)
-
-start = (robot) ->
+
+module.exports = (robot) ->
   # helper method to get the week of the year in numeric form
   getWeekOfYear = (dateObj = new Date()) ->
     onejan = new Date(dateObj.getFullYear(), 0, 1)
@@ -73,7 +72,7 @@
   #      score: 0
   #    }]
   getAllEntriesForWeek = (recordType, weekNum) ->
-    (robot.brain[calculateKey(recordType, weekNum)] || {}).data || []
+    (robot.brain.get(calculateKey(recordType, weekNum)) || {}).entry_data || []

   # Description: returns keys
@@ -93,7 +92,7 @@
   #  weekNum 0-52
   #  sentimentScore: AFINN Sentiment score
   updateEntry = (recordType, recordName, weekNum, sentimentScore) ->
-    masterRecord = (robot.brain[calculateKey(recordType, weekNum)] || {}).data || []
+    masterRecord = (robot.brain.get(calculateKey(recordType, weekNum)) || {}).entry_data || []
     entry = (_.filter(masterRecord, (x) -> x.name == recordName) || [])[0]

     if !entry || !entry.name
@@ -110,7 +109,7 @@
     entry.score_average = ((entry.score_average * entry.score_count) + sentimentScore) / (entry.score_count + 1)
     entry.score_count = entry.score_count + 1

-    robot.brain[calculateKey(recordType, weekNum)] = {data: masterRecord}
+    robot.brain.set(calculateKey(recordType, weekNum), {entry_data: masterRecord})

   # helper method to get sender of the message
   getUsername = (response) ->
@@ -181,32 +180,26 @@
   robot.respond /sentiment/i, (msg) ->
     msg.send getHappyPeople(3) + getHappyChannels(3) + getSadPeople(3) + getSadChannels(3)

-  robot.respond /who( i|')s happy\??/i, (msg) ->
+  robot.respond /who[ is']* happy\??/i, (msg) ->
     # responds in the current channel
     msg.send getHappyPeople(10)

-  robot.respond /where( i|')s( the)? (happy|happiness)\??/i, (msg) ->
+  robot.respond /where[ is']*( the)? (happy|happiness)\??/i, (msg) ->
     # responds in the current channel
     msg.send getHappyChannels(10)

-  robot.respond /who( i|')s (sad|stress(ed)?)\??/i, (msg) ->
+  robot.respond /who[ is']* (sad|stress(ed)?)\??/i, (msg) ->
     # responds in the current channel
     msg.send getSadPeople(10)

-  robot.respond /where( i|')s( the)? (sadness|stress)\??/i, (msg) ->
+  robot.respond /where[ is']*( the)? (sadness|stress)\??/i, (msg) ->
     # responds in the current channel
     msg.send getSadChannels(10)

-module.exports = (robot) ->
-  loaded = _.once(->
-    console.log "starting hubot-sentiment..."
-    start robot
-    return
-  )
-  if _.isEmpty(robot.brain.data) or _.isEmpty(robot.brain.data._private)
-    robot.brain.once "loaded", loaded
-    setTimeout loaded, INIT_TIMEOUT
-  else
-    loaded()
-  return
-
+  robot.respond /what[ is'] (sadness|stress)\??/i, (msg) ->
+    # responds in the current channel
+    msg.reply "Stress is not shipping code!"
+
+  robot.respond /what[ is'] (happiness|happy)\??/i, (msg) ->
+    # responds in the current channel
+    msg.reply "Happiness at Monsoon!"


correct a bunch of misnomers in the localization code

--- library/exposure/localized.coffee
+++ library/exposure/localized.coffee
@@ -63,19 +63,18 @@
     # YAML document is treated as a translation for one language.
     # Please, never execute this method directly, since it is very
     # heavy on reasources and should be shadowed with the caching.
-    collectTranslationMessages: ->
-        assert resolve = r = require("path").join
+    compileTranslationMessages: ->
         assert sreader = require("fs").readFileSync
+        assert resolve = join = require("path").join
         identify = @constructor.identify().underline
         e = "UTF-8" # all messages files must be UTF-8
         collector = {} # will hold the translation map
-        assert locations = @constructor.messages() or []
-        locations = _.cloneDeep locations # no mutations
-        deduce = (o) -> o.f = r o.directory, o.location
-        o.blob = sreader deduce(o), e for o in locations
-        processing = (f) -> _.each locations, f; collector
-        processing (o) -> yaml.safeLoadAll o.blob, (doc) ->
-            assert loc = o.location, "got invalid location"
+        assert translations = @constructor.translation()
+        deduce = (t) -> t.f = join t.directory, t.location
+        t.blob = sreader deduce(t), e for t in translations
+        processing = (f) -> _.each translations, f; collector
+        processing (t) -> yaml.safeLoadAll t.blob, (doc) ->
+            assert loc = t.location, "got invalid location"
             message = "Get translation for %s at %s".cyan
             logger.debug message, identify, loc.underline
             noLanguage = "got incorrect translation file"
@@ -88,9 +87,9 @@
     # messages. These are the strings to use for displaying with
     # different languages. File format is YAML with the special
     # structure that embeds multiple languages in a single file.
-    @messages: (location, options={}) ->
+    @translation: (location, options={}) ->
         assert not _.isEmpty cwd = try process.cwd()
-        assert previous = @messageLocations or Array()
+        assert previous = @translations or new Array()
         return previous if arguments.length is 0 # get
         implicit = "locale" # default dir for messages
         noLocation = "the location has to be a string"
@@ -98,7 +97,7 @@
         directory = options.dir or "#{cwd}/#{implicit}"
         assert _.isString(location or 0), noLocation
         assert _.isObject(options or 0), noOptions
-        this.messageLocations = previous.concat
+        return this.translations = previous.concat
             directory: directory.toString()
             location: location.toString()
             options: options or Object()


fix the scoping

fix the scoping

--- library/membrane/bilateral.coffee
+++ library/membrane/bilateral.coffee
@@ -114,7 +114,7 @@
             mangled += "/#{nsp}" if _.isString nsp = @nsp
             assert value; return @socket.on mangled, =>
                 logger.info uplinking, reference, nsp
-                return value i(arguments)..., (params...) =>
+                value.call this, i(arguments)..., (params...) =>
                     id = @socket.sacks = (@socket.sacks ?= 0) + 1
                     ack = type: "ack", name: mangled, ack: "data"
                     assert _.extend ack, ackId: id, args: o(params)


Revert "Attempt to reduce the code pyramid"

This reverts commit 0e97a92efa87e7fb9911b80472bbe1fbcc8255c4.

--- library/membrane/bilateral.coffee
+++ library/membrane/bilateral.coffee
@@ -108,12 +108,6 @@
         assert _.isFunction i = Marshal.deserialize
         uplinking = "Uplink %s at #{@location}, nsp=%s"
         ack = "Ack an uplink %s at #{@location}, nsp=%s"
-        responder = (socket, mangled, callback) -> ->
-            return c(o(arguments)...) if c = callback
-            id = socket.sacks = (socket.sacks ?= 0) + 1
-            packet = type: "ack", name: mangled, ack: "data"
-            _.extend packet, ackId: id, args: o(arguments)
-            assert packet.ackId; socket.packet packet
         _.forIn this, (value, reference, context) =>
             return unless reference of (@uplinks or {})
             assert mangled = "#{@location}/#{reference}"
@@ -121,9 +115,13 @@
             @socket.on mangled, (args..., callback) =>
                 args.push c unless _.isFunction c = callback
                 callback = null unless _.isFunction callback
-                reply = responder @socket, mangled, callback
                 console.log uplinking, reference, nsp
-                return value i(args)..., reply
+                assert args; return value i(args)..., =>
+                    return c(o(arguments)...) if c = callback
+                    id = @socket.sacks = (@socket.sacks ?= 0) + 1
+                    packet = type: "ack", name: mangled, ack: "data"
+                    _.extend packet, ackId: id, args: o(arguments)
+                    assert packet.ackId; @socket.packet packet

     # This is a complementary part of the bilateral implementation.
     # It is invoked to produce a server side agent that is aware of
@@ -138,17 +136,14 @@
         notify = "incorrect callback for the uplink"
         assert _.isFunction o = Marshal.serialize
         assert _.isFunction i = Marshal.deserialize
-        uploader = (socket, callback, ptr) -> ->
-            predicate = (ack, key) -> ack is ptr()
-            key = _.findKey socket.acks, predicate
-            assert key, "ack"; delete socket.acks[key]
-            logger.debug responded.cyan, name.bold
-            return callback.apply this, i(arguments)
         return (parameters..., callback=(->)) =>
             assert _.isFunction(callback), notify
             assert mangled = "#{@location()}/#{name}"
             assert binder = socket.binder, noBinder
             mangled += "/#{nsp}" if nsp = binder.nsp
             logger.debug uplinking.cyan, name.bold
-            ack = uploader socket, callback, -> ack
-            socket.emit mangled, o(parameters)..., ack
+            socket.emit mangled, o(parameters)..., a = =>
+                key = _.findKey socket.acks, (x) -> x is a
+                assert key, "ack"; delete socket.acks[key]
+                logger.debug responded.cyan, name.bold
+                return callback.apply this, i(arguments)


Operation brake-check the Hop-ons:

Operation brake-check the Hop-ons:

Problem:
SceneView.getSpriteSheet doesn't cache loaded resources, and hits resource load for each invocation.  This function is called when validating that a tile sprite is ready for rendering, and must be quick.

Solution:
Don't be an idiot and cache results on the view.  Object lookup is sufficiently fast.

Result:
getSpriteSheet gets road-rash as it's knocked off the top offender's stair-car.

--- src/scene/sceneView.coffee
+++ src/scene/sceneView.coffee
@@ -33,6 +33,7 @@
     @camera = new eburp.Rect(0,0,9,9)
     @cameraScale = 1.0
     @unitSize = eburp.SceneView.UNIT
+    @_sheets = {}

   # Scene rendering interfaces
   # -----------------------------------------------------------------------------
@@ -93,8 +94,10 @@

   interpolateTick: (elapsed) ->

-  getSpriteSheet: (name, done=->) ->
-    @loader.get "/images/#{name}.png", done
+  getSpriteSheet: (name, done) ->
+    if not @_sheets[name]
+      @_sheets[name] = @loader.get "/images/#{name}.png", done
+    return @_sheets[name]

   # Scene Camera updates
   # -----------------------------------------------------------------------------


Restrict guest users from accessing the bot.

--- lib/bot.coffee
+++ lib/bot.coffee
@@ -215,7 +215,7 @@
   throw new Error("Failed to reconnect to the Slack RTM API.")

 controller.on 'ambient', (bot, message) ->
-  checkMessage(bot, message)
+  attemptExpandUrl(bot, message)

 QUERY_REGEX = '(query|q|column|bar|line|pie|scatter|map)( )?(\\w+)? (.+)'
 FIND_REGEX = 'find (dashboard|look )? ?(.+)'
@@ -235,7 +235,36 @@
     message.text = SlackUtils.stripMessageText(message.text)
     processCommand(bot, message, true)

+ensureUserAuthorized = (bot, message, callback, options = {}) ->
+
+  unless options.silent
+    context = new ReplyContext(defaultBot, bot, message)
+
+  bot.api.users.info({user: message.user}, (error, response) ->
+    user = response?.user
+    if error || !user
+      context?.replyPrivate(
+        text: "Could not fetch your user info from Slack. #{error || ""}"
+      )
+    else
+      if user.is_restricted || user.is_ultra_restricted
+        context?.replyPrivate(
+          text: "Sorry @#{user.name}, as a guest user you're not able to use this command."
+        )
+      else if user.is_bot
+        context?.replyPrivate(
+          text: "Sorry @#{user.name}, as a bot you're not able to use this command."
+        )
+      else
+        callback()
+  )
+
 processCommand = (bot, message, isDM = false) ->
+  ensureUserAuthorized(bot, message, ->
+    processCommandInternal(bot, message, isDM)
+  )
+
+processCommandInternal = (bot, message, isDM) ->

   message.text = message.text.split('“').join('"')
   message.text = message.text.split('”').join('"')
@@ -358,22 +387,27 @@
   runner = new LookFinder(context, type, query)
   runner.start()

-checkMessage = (bot, message) ->
+attemptExpandUrl = (bot, message) ->
+
   return if !message.text || message.subtype == "bot_message"

   return unless process.env.LOOKER_SLACKBOT_EXPAND_URLS == "true"

-  # URL Expansion
-  for url in getUrls(message.text).map((url) -> url.replace("%3E", ""))
-
-    for looker in lookers
-
-      # Starts with Looker base URL?
-      if url.lastIndexOf(looker.url, 0) == 0
-        context = new ReplyContext(defaultBot, bot, message)
-        context.looker = looker
-        annotateLook(context, url, message, looker)
-        annotateShareUrl(context, url, message, looker)
+  ensureUserAuthorized(bot, message, ->
+
+    # URL Expansion
+    for url in getUrls(message.text).map((url) -> url.replace("%3E", ""))
+
+      for looker in lookers
+
+        # Starts with Looker base URL?
+        if url.lastIndexOf(looker.url, 0) == 0
+          context = new ReplyContext(defaultBot, bot, message)
+          context.looker = looker
+          annotateLook(context, url, message, looker)
+          annotateShareUrl(context, url, message, looker)
+
+  , {silent: true})

 annotateLook = (context, url, sourceMessage, looker) ->
   if matches = url.match(/\/looks\/([0-9]+)$/)


try to use cdn

try to use cdn

--- server/lib/_cors.coffee
+++ server/lib/_cors.coffee
@@ -1,3 +1,8 @@
 WebApp.rawConnectHandlers.use (req, res, next) ->
 	res.setHeader("Access-Control-Allow-Origin", "*")
-	return next()+	return next()
+
+_staticFilesMiddleware = WebAppInternals.staticFilesMiddleware
+WebAppInternals._staticFilesMiddleware = (staticFiles, req, res, next) ->
+	res.setHeader("Access-Control-Allow-Origin", "*")
+	_staticFilesMiddleware(staticFiles, req, res, next)


:lipstick:

:lipstick:

--- build/tasks/compile-packages-slug-task.coffee
+++ build/tasks/compile-packages-slug-task.coffee
@@ -46,8 +46,7 @@
       packages[metadata.name] = pack

       for extension, paths of moduleCache.extensions
-        if paths.length is 0
-          delete moduleCache.extensions[extension]
+        delete moduleCache.extensions[extension] if paths.length is 0

     metadata = grunt.file.readJSON(path.join(appDir, 'package.json'))
     metadata._atomPackages = packages


add shuffle command

add shuffle command

--- src/scripts/meowziq.coffee
+++ src/scripts/meowziq.coffee
@@ -8,6 +8,7 @@
 #
 # Commands:
 #   hubot [meowziq] play - play meowziq
+#   hubot [meowziq] shuffle - shuffle meowziq
 #   hubot [meowziq] skip - skip meowziq
 #   hubot [meowziq] status - show meowziq status
 #   hubot [meowziq] stop - stop meowziq
@@ -59,6 +60,20 @@
       result = if r.statusCode is 200 then 'OK' else 'ERROR'
       res.send result

+  pattern = new RegExp prefix + 'shuffle'
+  robot.respond pattern, (res) ->
+    request config.url + '/status'
+    .then (r) ->
+      json = JSON.parse r.body
+      request
+        method: 'PATCH'
+        url: config.url + '/status'
+        form:
+          shuffle: !json.shuffle
+    .then (r) ->
+      result = if r.statusCode is 200 then 'OK' else 'ERROR'
+      res.send result
+
   pattern = new RegExp prefix + 'skip'
   robot.respond pattern, (res) ->
     message = null


Couchbase.disconnect() commented due to issues.

--- server/src/Component/Builtin/DataSource/Couchbase.coffee
+++ server/src/Component/Builtin/DataSource/Couchbase.coffee
@@ -39,6 +39,6 @@

     # Close the database connection
     destroy: ->
-        @bucket?.disconnect?()
+        # @bucket?.disconnect?()

 module.exports = Couchbase


Was afraid that 4999 would break it, it does not.

--- src/spec/roman.spec.coffee
+++ src/spec/roman.spec.coffee
@@ -37,6 +37,12 @@
                 expected = 'MCMXCIX'
                 actual = @conv.convertFromDecimal 1999
                 expect(actual).toEqual expected
+
+            it "Converts 4999 to 'MMMMCMXCIX'", ->
+                expected = 'MMMMCMXCIX'
+                actual = @conv.convertFromDecimal 4999
+                expect(actual).toEqual expected
+
         describe "Combined Values", ->
             it "Converts 7 to 'VII'", ->
                 expected = 'VII'
@@ -106,6 +112,11 @@
                 actual = @conv.convertFromRoman 'MMVIII'
                 expect(actual).toEqual expected

+            it "Handles 'MMMMCMXCIX' 4999", ->
+                expected = 4999
+                actual = @conv.convertFromRoman 'MMMMCMXCIX'
+                expect(actual).toEqual expected
+
         describe "Combined Values", ->
             it "Handles 'II' as 2", ->
                 expected = 2


new version of clone fixes collections

new version of clone fixes collections

--- lib/cli.coffee
+++ lib/cli.coffee
@@ -606,6 +606,8 @@

   # clone a site
   when 'clone'
+
+    id_mapping = {}
     src_slug = args[0]
     dst_slug = args[1]

@@ -659,15 +661,48 @@
               # insert pages
               async.forEach objects, ((object, next) ->
                 data = object.val()
+                old_id = data._id
                 delete data._id
                 data.seo = {}
                 data.site_id = dst_site.get('_id').val()
-                db.get('objects').insert data, (err) ->
+                db.get('objects').insert data, (err, new_data) ->
+                  console.log 'old', old_id, 'new', new_data.val()._id
+                  id_mapping[old_id] = new_data.val()._id
                   exit err if err
                   next()
               ), (err) ->
-                exit err if err
-                exit 'clone complete'
+
+                # clean up references
+                db.get('objects').find {
+                  site_id: dst_site.get('_id').val()
+                }, {
+                  limit: 100000
+                }, (err, objects) ->
+
+                  async.forEach objects, ((object, next) ->
+                    object_val = object.val()
+                    str_json = JSON.stringify(object_val, null, 3)
+                    replaced = false
+
+                    # replace mapping
+                    for key, value of id_mapping
+                      if str_json.indexOf(key) > -1
+                        og_key = new RegExp(key, 'g');
+                        replaced = true
+                        str_json = str_json.replace(og_key, value)
+
+                    if replaced
+                      new_json = JSON.parse(str_json)
+
+                      # set new data with updated mapping
+                      object.get('data').set new_json.data, (err) ->
+                        next()
+                    else
+                      next()
+                  ), (err) ->
+                    exit err if err
+                    exit 'clone complete may need to wait 60 seconds for cache to clear'
+

   # create a new package
   when 'create'


activity: fix the blur behavior of input view

--- client/Social/Activity/views/inputview.coffee
+++ client/Social/Activity/views/inputview.coffee
@@ -157,7 +157,8 @@
   # `contenteditable` doesn't actually remove focus. This is a workaround
   # from http://stackoverflow.com/questions/12353247/force-contenteditable-div-to-stop-accepting-input-after-it-loses-focus-under-web
   forceBlur: ->
-    $('

')
+
+    $('

')
       .prependTo 'body'
       .focus()
       .remove()


Find and use config file

Find and use config file

If the user has a project-wide config file, pass it to flake8.

--- lib/linter-flake8.coffee
+++ lib/linter-flake8.coffee
@@ -1,6 +1,7 @@
 linterPath = atom.packages.getLoadedPackage("linter").path

 Linter = require "#{linterPath}/lib/linter"
+{findFile} = require "#{linterPath}/lib/utils"

 class LinterFlake8 extends Linter
   @syntax: ['source.python']
@@ -17,6 +18,8 @@

   constructor: (editor)->
     super(editor)
+
+    @configFile = findFile @cwd, ['setup.cfg', 'tox.ini', '.pep8']

     atom.config.observe 'linter-flake8.executableDir', =>
       @executablePath = atom.config.get 'linter-flake8.executableDir'
@@ -55,6 +58,9 @@
     if maxLineLength
       cmd.push '--max-line-length', maxLineLength

+    if @configFile
+      cmd.push '--config', @configFile
+
     if errorCodes and errorCodes.length > 0
       cmd.push '--ignore', errorCodes.toString()


collaboration: s/RealTime/Realtime/

collaboration: s/RealTime/Realtime/

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -170,7 +170,7 @@
       @initPrivateMessage callback

-  getRealTimeFileName: (id) ->
+  getRealtimeFileName: (id) ->

     unless id
       if @channelId          then id = @channelId
@@ -500,7 +500,7 @@
         else
           callback no

-      @rtm.fetchFileByTitle @getRealTimeFileName id
+      @rtm.fetchFileByTitle @getRealtimeFileName id

     if @rtm then kallback()
     else
@@ -732,7 +732,7 @@
     @rtm.once 'FileCreated', (file) =>
       @loadCollaborationFile file.id

-    @rtm.createFile @getRealTimeFileName()
+    @rtm.createFile @getRealtimeFileName()

     @setMachineSharingStatus on, (err) ->

@@ -775,7 +775,7 @@
         callback null

     @mySnapshot.clear()
-    @rtm.deleteFile @getRealTimeFileName()
+    @rtm.deleteFile @getRealtimeFileName()

   getCollaborationHost: -> if @amIHost then nick() else @collaborationHost


add missing 'to' field to notifications

add missing 'to' field to notifications

--- lib/notifications.coffee
+++ lib/notifications.coffee
@@ -88,12 +88,13 @@

   @notifyMessageUpdate = (diff) ->
     msg = Messages.findOne diff.id
-    for listener in messageListeners msg
+    for to in messageListeners msg
       if canSee msg, false, listener  ## xxx what should behavior be for superuser?
         ## Coallesce past notification (if it exists) into this notification,
         ## if they regard the same message and haven't yet been seen by user.
         notification = Notifications.findOne
           type: 'messageUpdate'
+          to: to
           message: diff.id
           seen: false
         if notification?
@@ -103,6 +104,7 @@
         else
           notificationInsert
             type: 'messageUpdate'
+            to: to
             message: diff.id
             dates: [diff.updated]
             diffs: [diff._id]


collaboration: broadcast message before ending

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -678,6 +678,8 @@

   endCollaborationForHost: (callbacks) ->

+    @broadcastMessage { type: 'SessionEnded' }
+
     fileName = @getRealtimeFileName()
     realtimeHelpers.deleteCollaborationFile @rtm, fileName, (err) =>
       return callbacks.error err  if err


collaboration: controller use state machine for starting

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -199,7 +199,6 @@
     @setCollaborativeReferences()
     @addParticipant whoami()
     @registerCollaborationSessionId()
-    @bindRealtimeEvents()

     if @amIHost
     then @activateRealtimeManagerForHost()
@@ -501,39 +500,186 @@
     IDEMetrics.collect 'StatusBar.collaboration_button', 'shown'

-  setCollaborationState: (state) ->
-
-    @stateMachine.transition state
-    @emit 'change', { state }
-
-
   initCollaborationStateMachine: ->

+    @stateMachine = new CollaborationStateMachine
+      stateHandlers:
+        Loading      : => kd.utils.defer => @onCollaborationLoading()
+        Resuming     : @bound 'onCollaborationResuming'
+        NotStarted   : @bound 'onCollaborationNotStarted'
+        PreCreated   : @bound 'onCollaborationPreCreated'
+        Creating     : @bound 'onCollaborationCreating'
+        Active       : @bound 'onCollaborationActive'
+
+
+
+  onCollaborationLoading: ->
+
+    @statusBar.emit 'CollaborationLoading'
+
+    @checkSessionActivity
+      active     : => @stateMachine.transition 'Resuming'
+      notStarted : => @stateMachine.transition 'NotStarted'
+      error      : => @stateMachine.transition 'ErrorLoading'
+
+
+  checkSessionActivity: (callbacks) ->
+
+    {channelId} = @workspaceData
+
+    callMethod = (name, args...) -> callbacks[name] args...
+
+    unless @workspaceData.channelId
+      return callMethod 'notStarted'
+
+    @fetchSocialChannel (err, channel) =>
+      return callMethod 'error'  if err
+      @isRealtimeSessionActive channel.id, (isActive, file) =>
+        if isActive
+        then callMethod 'active', channel, file
+        else callMethod 'notStarted'
+
+
+  onCollaborationNotStarted: ->
+
+    @statusBar.emit 'CollaborationEnded'
+    @collectButtonShownMetric()
+
+
+  prepareChatSession: ->
+
+    socialHelpers.initChannel (err, channel) =>
+
+      return @stateMachine.transition 'ErrorCreating'  if err
+
+      @setSocialChannel channel
+      @createChatPaneView channel
+
+      envHelpers.updateWorkspace @workspaceData, { channelId : channel.id }
+        .then =>
+          @workspaceData.channelId = channel.id
+          @chat.ready =>
+            @stateMachine.transition 'PreCreated'
+        .error (err) =>
+          # @stateMachine.transition 'ErrorCreating'
+
+
+  onCollaborationPreCreated: ->
+
+    @chat.emit 'CollaborationNotInitialized'
+
+
+  startCollaborationSession: ->
+
+    switch @stateMachine.state
+      when 'PreCreated' then @stateMachine.transition 'Creating'
+
+
+  onCollaborationCreating: ->
+
+    @createCollaborationSession
+      success : (doc) =>
+        @whenRealtimeReady => @stateMachine.transition 'Active'
+        @activateRealtimeManager doc
+      error: =>
+        @stateMachine.transition 'ErrorCreating'
+
+
+  createCollaborationSession: (callbacks) ->
+
+    fileName = @getRealtimeFileName()
+    socialHelpers.sendActivationMessage @socialChannel, kd.noop
+
+    realtimeHelpers.createCollaborationFile @rtm, fileName, (err, file) =>
+      return callbacks.error err  if err
+
+      realtimeHelpers.loadCollaborationFile @rtm, file.id, (err, doc) =>
+        return callbacks.error err  if err
+
+        @rtmFileId = file.id
+        @setMachineSharingStatus on, (err) =>
+          return callbacks.error err  if err
+          callbacks.success doc
+
+
+  onCollaborationResuming: ->
+
+    successCb = (channel, doc) =>
+      @whenRealtimeReady =>
+        @setSocialChannel channel
+        @createChatPaneView channel
+
+        @chat.ready =>
+          @stateMachine.transition 'Active'
+
+      @activateRealtimeManager doc
+
+    errorCb = => # @stateMachine.transition 'ErrorResuming'
+
+    @resumeCollaborationSession
+      success : successCb
+      error   : errorCb
+
+
+  resumeCollaborationSession: (callbacks) ->
+
+    title = @getRealtimeFileName()
+    realtimeHelpers.fetchCollaborationFile @rtm, title, (err, file) =>
+      # return callbacks.error err  if err
+      realtimeHelpers.loadCollaborationFile @rtm, file.id, (err, doc) =>
+        # return callbacks.error err  if err
+        @rtmFileId = file.id
+        callbacks.success @socialChannel, doc
+
+
+  onCollaborationActive: ->
+
+    @showChatPane()
+
+    @transitionViewsToActive()
+    @collectButtonShownMetric()
+    @bindRealtimeEvents()
+    @bindSocialChannelEvents()
+
+
+  transitionViewsToActive: ->
+
+    @listChatParticipants (accounts) =>
+      @chat.settingsPane.createParticipantsList accounts
+
+    {settingsPane} = @chat
+    settingsPane.on 'ParticipantKicked', @bound 'handleParticipantKicked'
+    settingsPane.updateDefaultPermissions()
+
+    @chat.emit 'CollaborationStarted'
+    @statusBar.emit 'CollaborationStarted'
+
+
+  showChat: ->
+
+    switch @stateMachine.state
+      when 'Active'     then @showChatPane()
+      when 'NotStarted' then @prepareChatSession()
+
+
+  showChatPane: ->
+
+    @chat.showChatPane()
+    @chat.start()
+
+
+  createChatPaneView: (channel) ->
+    return throwError 'RealtimeManager is not set'  unless @rtm
+
+    @chat = new IDEChatView { @rtm, @isInSession }, channel
+    @getView().addSubView @chat

   prepareCollaboration: ->

-    @initCollaborationStateMachine()
-
-
-  startCollaborationSession: (callback) ->
-
-    return callback msg : 'no social channel'  unless @socialChannel
-
-    kallback = (err, channel) => @whenRealtimeReady => callback err, channel
-    socialHelpers.sendActivationMessage @socialChannel, kallback
-
-    @collaborationJustInitialized = yes
-
-    @rtm or= new RealtimeManager
-    title  = @getRealtimeFileName()
-
-    realtimeHelpers.createCollaborationFile @rtm, title, (err, file) =>
-      return throwError err  if err
-      @loadCollaborationFile file.id
-
-    @setMachineSharingStatus on, (err) ->
-      throwError err  if err
+    @rtm = new RealtimeManager
+    @showShareButton()
+    @rtm.ready => @initCollaborationStateMachine()

   # should clean realtime manager.


Remove functionality.

Remove functionality.

--- client/templates/items_dial/items_dial.coffee
+++ client/templates/items_dial/items_dial.coffee
@@ -6,13 +6,28 @@
   Math.sqrt a * a + b * b

 # Handle drag stop on item(s)
-dragStop = ->
+dragStop = (center_item_id) ->
   # Test if item is dropped within circle
   left = parseFloat $(this).css "left"
   top = parseFloat $(this).css "top"
   if length(left, top) < 70
     template = if Router.current().route.getName() is "projectPage" then "memberPage" else "projectPage"
     Router.go template, { _id: $(this).attr("data-id") }
+  else if length(left, top) > 200
+    if Router.current().route.getName() is "projectPage"
+      projectId = center_item_id
+      projectMembers = Projects.findOne(projectId).members
+      userId = $(this).attr("data-id")
+      projectMembers = (member for member in projectMembers when member != userId)
+      projectProperties =
+        members: projectMembers
+      Projects.update projectId, {$set: projectProperties}, (error) ->
+        if error
+          throwError error.reason
+        else
+          Router.go 'projectPage', {_id: projectId}
+    else
+      console.log("Delete project from member")
   else
     $(this).css("left", $(this).attr "data-orig-x")
     $(this).css("top", $(this).attr "data-orig-y")
@@ -25,7 +40,8 @@
     $(this).attr("data-orig-y", "" + $(this).css("top"))
   )
   $(".circular").draggable()
-  $(".circular").on("dragstop", dragStop)
+  center_item_id = Template.parentData()._id
+  $(".circular").on("dragstop", -> dragStop.apply this, [center_item_id])

 Template.items_dial.helpers
   title: ->


minor cleanup

minor cleanup

--- client/app/MainApp/ContentDisplay/ContentDisplayController.coffee
+++ client/app/MainApp/ContentDisplay/ContentDisplayController.coffee
@@ -1,32 +1,48 @@
 class ContentDisplayController extends KDController

   constructor:(options)->
+
     super
+
     @displays = {}
     @attachListeners()
     @revivedContentDisplay = no

+
   attachListeners:->
+
     @on "ContentDisplayWantsToBeShown",  (view)=> @showContentDisplay view
     @on "ContentDisplayWantsToBeHidden", (view)=> @hideContentDisplay view
     @on "ContentDisplaysShouldBeHidden",       => @hideAllContentDisplays()
     KD.getSingleton("appManager").on "ApplicationShowedAView",    => @hideAllContentDisplays()

+
   showContentDisplay:(view)->
-    contentPanel = KD.getSingleton "contentPanel"
-    wrapper = new ContentDisplay
-      domId : "content-display-wrapper" if not @revivedContentDisplay
+
+    contentPanel       = KD.getSingleton "contentPanel"
+    mainView           = KD.getSingleton "mainView"
+    mainViewController = KD.getSingleton "mainViewController"
+    wrapper            = new ContentDisplay
+      domId : "content-display-wrapper"  if not @revivedContentDisplay
+
     wrapper.bindTransitionEnd()
     @displays[view.id] = view
     wrapper.addSubView view
     contentPanel.addSubView wrapper
     @slideWrapperIn wrapper
     @revivedContentDisplay = yes
+    mainView.sidebar.navController.deselectAllItems()
+
+    # KD.utils.defer -> mainViewController.setViewState()
+
     return wrapper
+

   hideContentDisplay:(view)-> KD.getSingleton('router').back()

+
   hideAllContentDisplays:(exceptFor)->
+
     displayIds =\
       if exceptFor?
         (id for own id,display of @displays when exceptFor isnt display)
@@ -41,15 +57,21 @@

     @slideWrapperOut @displays[lastId]

+
   slideWrapperIn:(wrapper)->
+
     wrapper.setClass 'in'

+
   slideWrapperOut:(view)->
+
     wrapper = view.parent
     wrapper.once 'transitionend', => @destroyView view
     wrapper.unsetClass 'in'

+
   destroyView:(view)->
+
     wrapper = view.parent
     @emit 'ContentDisplayIsDestroyed', view
     delete @displays[view.id]


Get the User + CollectorProfile before beginning

--- components/inquiry_questionnaire/index.coffee
+++ components/inquiry_questionnaire/index.coffee
@@ -20,15 +20,9 @@
     # Try to get a location incase one doesn't exist,
     # don't wait for it though
     user.approximateLocation()
-    user.fetch
-      success: ->
-        if user.id?
-          # We have an existing anonymous session
-          # or a logged in user
-          done()
-        else
-          # Create an anonymous session before continuing
-          user.save().then done
+    user.instantiate()
+      .then -> user.related().collectorProfile.instantiate()
+      .then done

   questionnaire.state.on 'abort', ->
     modal.close()


FIX: treat "no zero suppression" as "suppress tailing zero"

Although this is not specified in the gerber file format specs, Altium does offer an option to generate gerber with no zero suppression (i.e. missing `p[2]`), which I always use to avoid problem with fab. This patch makes gerber parser accept these files.

--- src/gerber-parser.coffee
+++ src/gerber-parser.coffee
@@ -19,6 +19,10 @@
   # parse a format block
   parseFormat: (p, c) ->
     zero = if p[2] is 'L' or p[2] is 'T' then p[2] else null
+    # FIX: treat "no zero suppression" as "suppress tailing zero"
+    if not zero?
+      p = ' ' + p
+      zero = 'T'
     nota = if p[3] is 'A' or p[3] is 'I' then p[3] else null
     if p[4] is 'X' and p[7] is 'Y' and p[5..6] is p[8..9] and
     p[5] < 8 and p[6] < 8


Fixed voting after sign in.

Fixed voting after sign in.

--- core/app/backbone/models/comment_tally.coffee
+++ core/app/backbone/models/comment_tally.coffee
@@ -20,6 +20,19 @@
     @set newOpinion, @get(newOpinion)+1 if newOpinion != 'no_vote'
     @set 'current_user_opinion', newOpinion

+  # When signing in using the ReactSigninPopover after voting a race condition occurs
+  # between the get request of the user associated comments and the put request of the vote.
+  # when the put request is earlier:
+  # _setOpinionAgainAfterCollectionFetch checks whether the backbone collection is loading after setting,
+  # if the get request is still busy, and redos the users action after the get request is completed / synced.
+  # when the get request is earlier:
+  # _setOpinionAgainAfterCollectionFetch sets the currentUserOpinion directly after successful processing of the put request
+  # to override the data of the get request
+  _setOpinionAgainAfterCollectionFetch: (opinion_type) ->
+    @_setCurrentUserOpinion opinion_type
+    if @_comment.collection.loading()
+      @_comment.collection.once 'sync', => @_setCurrentUserOpinion opinion_type
+
   _saveCurrentUserOpinion: (opinion_type) ->
     @previous_opinion_type = @get('current_user_opinion')
     return if @previous_opinion_type == opinion_type
@@ -27,6 +40,7 @@
     @_setCurrentUserOpinion opinion_type
     @save {},
       success: =>
+        @_setOpinionAgainAfterCollectionFetch opinion_type
         mp_track "Comment: Vote", url: @url(), opinion: opinion_type
       error: =>
         @_setCurrentUserOpinion @previous_opinion_type


Remove extraneous spaces.

Remove extraneous spaces.


trim apikey?!

trim apikey?!

--- client/code/router/main.coffee
+++ client/code/router/main.coffee
@@ -40,5 +40,5 @@

   _setApiKey: (callback) ->
     $.get '/tpl/apikey', (data) ->
-      window.apikey = data
+      window.apikey = data.trim()
       callback()


Modified ports to be overridden by NODE_PORT environment variable.

--- src/configuration.coffee
+++ src/configuration.coffee
@@ -8,7 +8,8 @@
 	server.set 'views', public_dir 'views'

 	server.configure 'production', ->
-		this.set 'listening-port', 80
+		this.set 'listening-port', process.env.NODE_PORT || 80

 	server.configure 'development', ->
-		this.set 'listening-port', 8000
+		this.set 'listening-port', process.env.NODE_PORT || 8000
+


Workspace: Added new files to includes.

Workspace: Added new files to includes.

--- client/landing/includes.coffee
+++ client/landing/includes.coffee
@@ -735,6 +735,8 @@
   "app/CommonViews/workspace/panes/collaborativetabbededitorpane.coffee",
   "app/CommonViews/workspace/panes/collaborativeterminalpane.coffee",
   "app/CommonViews/workspace/panes/collaborativeclientterminalpane.coffee",
+  "app/CommonViews/workspace/panes/sharableterminalpane.coffee",
+  "app/CommonViews/workspace/panes/sharableclientterminalpane.coffee",
   "app/CommonViews/workspace/panes/collaborativefinderpane.coffee",
   "app/CommonViews/workspace/panes/collaborativeclientfinderpane.coffee",
   "app/CommonViews/workspace/panes/collaborativeeditorpane.coffee",


Remove log

Remove log

--- client/lib/readMessages.coffee
+++ client/lib/readMessages.coffee
@@ -20,7 +20,6 @@
 			if subscription? and (subscription.alert is true or subscription.unread > 0)
 				# Only read messages if user saw the first unread message
 				position = $('.message.first-unread').position()
-				console.log position
 				if force is true or not position? or position.top >= 0
 					Meteor.call 'readMessages', rid


better error message

better error message

--- src/scripts/pagerduty.coffee
+++ src/scripts/pagerduty.coffee
@@ -104,7 +104,7 @@
         msg.send "No open incidents"

   robot.respond /(pager|major)( me)? (?:trigger|page) ([\w\-]+)$/i, (msg) ->
-    msg.reply "Please include a user or schedule to page, like 'hubot pager ops everything is on fire'."
+    msg.reply "Please include a user or schedule to page, like 'hubot pager infrastructure everything is on fire'."

   robot.respond /(pager|major)( me)? (?:trigger|page) ([\w\-]+) (.+)$/i, (msg) ->
     msg.finish()
@@ -112,10 +112,6 @@
     userOrSchedule = msg.match[3]
     reason         = msg.match[4]
     description    = "#{reason} - @#{fromUserName}"
-
-    unless reason
-      msg.reply "Please include a user or schedule to page, like 'hubot pager ops everything is on fire'."
-      return

     # Figure out who we are
     campfireUserToPagerDutyUser msg, msg.message.user, (triggerdByPagerDutyUser) ->
@@ -356,7 +352,7 @@
       return unless userId

       if !msg.match[2] || msg.match[2] == 'me'
-        msg.reply "Please specify a schedule with 'pager me ops 60'. Use 'pager schedules' to list all schedules."
+        msg.reply "Please specify a schedule with 'pager me infrastructure 60'. Use 'pager schedules' to list all schedules."
         return

       withScheduleMatching msg, msg.match[2], (matchingSchedule) ->


Implement API to delete faults

Implement API to delete faults

--- lib/nbi.coffee
+++ lib/nbi.coffee
@@ -60,6 +60,7 @@
 DELETE_DEVICE_REGEX = /^\/devices\/([a-zA-Z0-9\-\_\%]+)\/?$/
 PROVISIONS_REGEX = /^\/provisions\/([a-zA-Z0-9\-\_\%]+)\/?$/
 VIRTUAL_PARAMETERS_REGEX = /^\/virtual_parameters\/([a-zA-Z0-9\-\_\%]+)\/?$/
+FAULTS_REGEX = /^\/faults\/([a-zA-Z0-9\-\_\%\:]+)\/?$/

 errorToString = (err) ->
@@ -249,6 +250,25 @@
         )
       else
         response.writeHead 405, {'Allow': 'POST, DELETE'}
+        response.end('405 Method Not Allowed')
+    else if FAULTS_REGEX.test(urlParts.pathname)
+      if request.method == 'DELETE'
+        faultId = querystring.unescape(FAULTS_REGEX.exec(urlParts.pathname)[1])
+        deviceId = faultId.split(':', 1)
+        channel = faultId.slice(deviceId + 1)
+        db.redisClient.del("#{deviceId}_faults", (err) ->
+          db.faultsCollection.remove({_id : faultId}, (err) ->
+            throw err if err
+            if channel.startsWith('_task_')
+              return db.tasksCollection.remove({_id : mongodb.ObjectID(channel.slice(6))}, (err) ->
+                throw err if err
+              )
+            response.writeHead(200)
+            response.end()
+          )
+        )
+      else
+        response.writeHead 405, {'Allow': 'DELETE'}
         response.end('405 Method Not Allowed')
     else if DEVICE_TASKS_REGEX.test(urlParts.pathname)
       if request.method == 'POST'
@@ -450,6 +470,11 @@
           when 'tasks'
             q = query.sanitizeQueryTypes(q, {
               _id: ((v) -> return new mongodb.ObjectID(v))
+              timestamp: ((v) -> return new Date(v))
+              retries: Number
+            })
+          when 'faults'
+            q = query.sanitizeQueryTypes(q, {
               timestamp: ((v) -> return new Date(v))
               retries: Number
             })


Choose illustration type Pick the type when you're marking the page, not later.

--- project.coffee
+++ project.coffee
@@ -144,8 +144,25 @@
       question: 'Draw rectangles around the illustrations on this page.'
       choices: [{
         type: Rectangle
-        label: 'Mark the illustrations'
-        color: '#333333'
+        label: 'drawing/painting/diagram'
+        value: 'drawing'
+        color: '#006666'
+        checked: true
+      },{
+        type: Rectangle
+        label: 'chart/table'
+        value: 'chart'
+        color: '#666666'
+      },{
+        type: Rectangle
+        label: 'photograph'
+        value: 'photograph'
+        color: '#660066'
+      },{
+        type: Rectangle
+        label: 'map'
+        value: 'map'
+        color: '#666600'
       }]
       next: 'parts'
     details:
@@ -153,17 +170,6 @@
       question: 'Add information about the illustration.'
       legend: 'Illustration'
       choices: [{
-        type: 'select'
-        key: 'type'
-        label: 'Type'
-        value: 'drawing/painting/diagram'
-        options: [
-          'drawing/painting/diagram'
-          'chart/table'
-          'photograph'
-          'map'
-        ]
-      },{
         type: 'textarea'
         key: 'keywords'
         label: 'Keywords'


make a very unlikely codepath more robust (this happens when using a sageworksheet to close a file that is also opened.... while restoring a session). very much an edge case

--- src/smc-webapp/editor.coffee
+++ src/smc-webapp/editor.coffee
@@ -710,7 +710,7 @@

         @element.show()
         # if above line reveals it, give it a bit time to do the layout first
-        @_show(opts)  # critical -- also do an intial layout!  Otherwise get a horrible messed up animation effect.
+        @_show(opts)  # critical -- also do an initial layout!  Otherwise get a horrible messed up animation effect.
         setTimeout((=> @_show(opts)), 10)
         if DEBUG
             window?.smc?.doc = @  # useful for debugging...
@@ -1740,6 +1740,9 @@

     _show_codemirror_editors: (height) =>
         # console.log("_show_codemirror_editors: #{@_layout}")
+        if not @codemirror?
+            # already closed so can't show (in syncdoc, .codemirorr is deleted on close)
+            return
         switch @_layout
             when 0
                 p = 1


increase supervisor update timeout to 1 minute

--- src/supervisor-update.coffee
+++ src/supervisor-update.coffee
@@ -55,7 +55,7 @@
 				)
 				stream.on 'end', ->
 					reject(new Error('New supervisor stopped before success message'))
-			.timeout(10000) # wait up to 10 seconds
+			.timeout(60000) # wait up to 1 minute
 		.catch (e) ->
 			container.stopAsync()
 			console.log('Container failed to start successfully. Error: ', e)


There is no need to resolve currentSupervisor again as we have already resolved it.

--- src/supervisor-update.coffee
+++ src/supervisor-update.coffee
@@ -113,11 +113,8 @@
 				stream.on('end', resolve)
 		.then ->
 			console.log('Inspecting new supervisor:', remoteImage)
-			Promise.all([
-				docker.getImage(remoteImage).inspectAsync()
-				currentSupervisor
-			])
-		.spread (localImageInfo, currentSupervisor) ->
+			docker.getImage(remoteImage).inspectAsync()
+		.then (localImageInfo) ->
 			localImageId = localImageInfo.Id or localImageInfo.id
 			if localImageId is currentSupervisor.Image
 				utils.mixpanelTrack('Supervisor up to date')


View helper methods

View helper methods

--- src/layout/header.coffee
+++ src/layout/header.coffee
@@ -1,19 +1,22 @@
 {vector} = require '../god/vector'

-exports.header = {
-  _LABEL: 'header'
-  going: vector.axis.down
-  _CHILDREN: [
+group = (label, items, direction) -> {
+  layout: direction
+  _LABEL: label
+  _CHILDREN: items
+}
+
+rows = (label, items) -> group(label, items, vector.axis.down)
+cols = (label, items) -> group(label, items, vector.axis.across)
+
+exports.header =
+  rows 'header', [
     {_LABEL: 'game', name: "Example Game", name_style: "24pt"}
     {_LABEL: 'level', name: "Move the Turtle to the Exit", name_style: "18pt"}
     {_LABEL: 'progress', name: "1 of 2"}
-    {
-      going: vector.axis.across
-      _CHILDREN: [
-        {_LABEL: 'ticks', name: "ticks: 0"}
-        {_LABEL: 'clicks', name: "clicks: 0"}
-        {_LABEL: 'bricks', name: "bricks: 0"}
-      ]
-    }
+    cols 'status', [
+      {_LABEL: 'ticks', name: "ticks: 0"}
+      {_LABEL: 'clicks', name: "clicks: 0"}
+      {_LABEL: 'bricks', name: "bricks: 0"}
+    ]
   ]
-}


Added a `return` to make sure that the simulator didn't return a value.

I'm not sure if this is necessary, but I was worried about the simulator returning a value that would then get collected up when I would loop through lots of generations.

--- src/lib/simulator.coffee
+++ src/lib/simulator.coffee
@@ -28,6 +28,7 @@
     @population.addBird(b) for b in newBirds
     @population.mortalityPass()
     @population.capToCarryingCapacity()
+    return

   getPopulation: () -> @population


Finish with the tests of us movements in the kanban

--- test/unit/controllers/kanbanControllerSpec.coffee
+++ test/unit/controllers/kanbanControllerSpec.coffee
@@ -424,10 +424,6 @@
             ctrl.scope.project.is_backlog_activated = false
             sortableScope = {status: {id: 1}}

-            ctrl.resortUserStories = ->
-                defered = $q.defer()
-                defered.resolve()
-                return defered.promise
             sinon.spy(ctrl, "resortUserStories")
             sinon.spy(ctrl, "formatUserStories")
             sinon.spy(ctrl.scope, "$broadcast")
@@ -442,7 +438,11 @@
                 (us) -> $model.make_model("userstories", us)
             )

+            httpBackend.expectPOST("#{APIURL}/userstories/bulk_update_order", {
+                    projectId: 1, bulkStories: [[3,0], [1,1], [2,2]]}).respond(200)
+
             promise = ctrl.sortableOnUpdate(uss, sortableScope)
+            httpBackend.flush()

             promise.should.be.fulfilled.then ->
                 expect(
@@ -454,57 +454,55 @@
                 expect(ctrl.resortUserStories).have.been.called.once
                 expect(ctrl.scope.$broadcast).have.been.calledWith("wipline:redraw")

-        #it "shouldn't allow to move us in a status colum if backlog is activate", inject ($model, $q) ->
-        #    ctrl.scope.project.is_backlog_activated = false
-        #    sortableScope = {status: {id: 1}}
-
-        #    ctrl.formatUserStories = ->
-        #    sinon.spy(ctrl, "resortUserStories")
-        #    sinon.spy(ctrl, "formatUserStories")
-        #    sinon.spy(ctrl.scope, "$broadcast")
-
-        #    uss = _.map(
-        #        [{id: 3, status: 1}, {id: 1, status: 1}, {id: 2, status: 1}],
-        #        (us) -> $model.make_model("userstories", us)
-        #    )
-
-        #    promise = ctrl.sortableOnUpdate(uss, sortableScope)
-        #    httpBackend.flush()
-
-        #    promise.should.be.fulfilled.then ->
-        #        expect(
-        #            _.map(ctrl.uss[1], (us) -> us.getAttrs())
-        #        ).to.be.deep.equal(
-        #            [{id: 3, status: 1}, {id: 1, status: 1}, {id: 2, status: 1}]
-        #        )
-        #        expect(ctrl.formatUserStories).have.been.called.once
-        #        expect(ctrl.resortUserStories).have.not.been.called
-        #        expect(ctrl.scope.$broadcast).have.been.calledWith("wipline:redraw")
-
-        #it "should allow to remove user story from the list of unassigned user stories", inject ($model) ->
-        #    ctrl.scope.userstories = _.map(
-        #        [{id: 1, milestone: null}, {id: 2, milestone: null}, {id: 3, milestone: null}],
-        #        (us) -> $model.make_model("userstories", us)
-        #    )
-
-        #    ctrl.getSelectedUserStories = ->
-        #        "test"
-        #    sinon.spy(ctrl, "getSelectedUserStories")
-
-        #    ctrl.calculateStoryPoints = ->
-        #        10
-        #    sinon.spy(ctrl, "calculateStoryPoints")
-
-        #    ctrl.sortableOnRemove(ctrl.scope.unassignedUs[0])
-        #    expect(
-        #        _.map(ctrl.scope.userstories, (us) -> us.getAttrs())
-        #    ).to.be.deep.equal(
-        #        [{id: 2, milestone: null}, {id: 3, milestone: null}]
-        #    )
-        #    expect(ctrl.getSelectedUserStories).have.been.called.once
-        #    expect(ctrl.calculateStoryPoints).have.been.called.once
-        #    expect(ctrl.scope.selectedUserStories).to.be.equal("test")
-        #    expect(ctrl.scope.selectedStoryPoints).to.be.equal(10)
+        it "shouldn't allow to move us in a status colum if backlog is activate", inject ($model, $q) ->
+            ctrl.scope.constants.usStatusesList = [{id: 1}]
+            ctrl.scope.project.is_backlog_activated = true
+            sortableScope = {status: {id: 1}}
+
+            sinon.spy(ctrl, "resortUserStories")
+            sinon.spy(ctrl, "formatUserStories")
+            sinon.spy(ctrl.scope, "$broadcast")
+
+            uss = _.map(
+                [{id: 3, status: 1}, {id: 1, status: 1}, {id: 2, status: 1}],
+                (us) -> $model.make_model("userstories", us)
+            )
+
+            httpBackend.expectGET("#{APIURL}/userstories?project=1").respond(200,
+                    [{id: 1, status: 1}, {id: 2, status: 1}, {id: 3, status: 1}])
+
+            promise = ctrl.sortableOnUpdate(uss, sortableScope)
+            httpBackend.flush()
+
+            promise.should.be.fulfilled.then ->
+                expect(
+                    _.map(ctrl.uss[1], (us) -> us.getAttrs())
+                ).to.be.deep.equal(
+                    [{id: 1, status: 1}, {id: 2, status: 1}, {id: 3, status: 1}]
+                )
+                expect(ctrl.formatUserStories).have.been.called.once
+                expect(ctrl.resortUserStories).have.not.been.called
+                expect(ctrl.scope.$broadcast).have.been.calledWith("wipline:redraw")
+
+        it "should allow to remove and us of a status colum of the kanban", inject ($model) ->
+            sortableScope = {status: {id: 1}}
+
+            ctrl.uss = []
+            ctrl.uss[1] = _.map(
+                [{id: 1, status: 1}, {id: 2, status: 1}, {id: 3, status: 1}],
+                (us) -> $model.make_model("userstories", us)
+            )
+
+            sinon.spy(ctrl.scope, "$broadcast")
+
+            ctrl.sortableOnRemove(ctrl.uss[1][0], sortableScope)
+            expect(
+                _.map(ctrl.uss[1], (us) -> us.getAttrs())
+            ).to.be.deep.equal(
+                [{id: 2, status: 1}, {id: 3, status: 1}],
+            )
+
+            expect(ctrl.scope.$broadcast).have.been.calledWith("wipline:redraw")

     describe "KanbanUsModalController", ->


new default background color

new default background color

--- framer/Defaults.coffee
+++ framer/Defaults.coffee
@@ -6,7 +6,7 @@

 Originals =
 	Layer:
-		backgroundColor: "rgba(0, 124, 255, 0.5)"
+		backgroundColor: "rgba(150,175,200,0.5)"
 		color: "white"
 		shadowColor: "black"
 		width: 100


shell: genericize _updateAttributesWithDefaults

_updateAttributesWithDefaults now updates all default attributes. A
shell's attribute will be updated to the current default value under the
following circumstances:
1) the attribute is undefined
2) the attribute is the same as the most recent default value
3) the attribute is an empty string and the attribute is not allowed to
   be empty

--- coffee/src/shells/shell.coffee
+++ coffee/src/shells/shell.coffee
@@ -158,14 +158,24 @@
     thumbnail: acorn.config.img.acorn

+  _attributeCanBeEmpty: =>
+    thumbnail: false
+
+
   _updateAttributesWithDefaults: =>
     # retrieve previous and current default thumbnails
     lastDefaults = @_lastDefaults ? {}
     currentDefaults = @_lastDefaults = @defaultAttributes()

-    # update model thumbnail if its value is the old default or empty/undefined
-    if @model.thumbnail() == lastDefaults.thumbnail or not @model.thumbnail()
-      @model.thumbnail currentDefaults.thumbnail
+    # update default values where appropriate
+    for attr, currentDefault of currentDefaults
+      modelVal = @model[attr]()
+      invalidEmpty = modelVal == '' and not @_attributeCanBeEmpty()[attr]
+
+      # update a model attribute if its value is the old default, is undefined,
+      # or is improperly empty
+      if modelVal == lastDefaults[attr] or not modelVal? or invalidEmpty
+        @model[attr] currentDefault


:fire: Remove unused clip parameter

:fire: Remove unused clip parameter

--- src/transform-layer.coffee
+++ src/transform-layer.coffee
@@ -44,9 +44,9 @@
     iterator.seek(position, clip)
     iterator.getSourcePosition()

-  fromSourcePosition: (sourcePosition, clip) ->
+  fromSourcePosition: (sourcePosition) ->
     iterator = @buildIterator()
-    iterator.seekToSourcePosition(sourcePosition, clip)
+    iterator.seekToSourcePosition(sourcePosition)
     iterator.getPosition()

 class TransformLayerIterator


fixed the header image

fixed the header image

--- components/layout/index.coffee
+++ components/layout/index.coffee
@@ -45,7 +45,8 @@
   incrementer = 1

   changeHeaderBackground = ->
-    $('.header').css "background-image": "url(./images/Banner_#{incrementer++}.jpg)", 1000
+    $('.header').css "background-image": "url(./images/banner-#{incrementer}.jpg)", 1000
+    incrementer = incrementer + 1
     incrementer = 1 if(incrementer == 4)

   setInterval changeHeaderBackground, 3000


bugfix

bugfix

--- src/mini-tutorial.coffee
+++ src/mini-tutorial.coffee
@@ -89,7 +89,7 @@

     for elements in [@images, @contents, @steppers]
       for element, i in elements
-        if i is index
+        if i is @index
           element.setAttribute 'data-readymade-active', true
         else
           element.removeAttribute 'data-readymade-active'


remove duplicated global.expect

remove duplicated global.expect

--- configs/test/jest.setup.coffee
+++ configs/test/jest.setup.coffee
@@ -28,12 +28,6 @@
 # bump up timeout to 30 seconds
 global.jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000

-global.expect = (actual) ->
-  originalMatchers = originalExpect(actual)
-  chaiMatchers = chai.expect(actual)
-  combinedMatchers = Object.assign(chaiMatchers, originalMatchers)
-  combinedMatchers
-
 process.on 'unhandledRejection', (reason, p) ->
   console.error("Possibly Unhandled Rejection at: Promise ", p, " reason: ", reason)


make startup message consistent

make startup message consistent


trendline accounting for today

trendline accounting for today

--- src/modules/graph.coffee
+++ src/modules/graph.coffee
@@ -79,6 +79,10 @@
     'trendline': (actual, created_at, due_on) ->
         start = +actual[0].date

+        # Now is an actual point too.
+        last = actual[actual.length - 1]
+        actual.push { 'date': new Date(), 'points': last.points }
+
         values = _.map actual, ({ date, points }) ->
             [ +date - start, points ]


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/active_bounds.coffee
+++ src/active_bounds.coffee
@@ -13,7 +13,7 @@
     bullet.include ActiveBounds

     # => Now the bullet will be removed when it goes outside of the
-
+         game

 ActiveBounds module
 @name ActiveBounds


Account Settings: Credentials: Use thin arrow

--- client/account/lib/accountcredentiallistwrapper.coffee
+++ client/account/lib/accountcredentiallistwrapper.coffee
@@ -66,7 +66,7 @@
     list          = []
     { providers } = globals.config

-    Object.keys(providers).forEach (provider) =>
+    Object.keys(providers).forEach (provider) ->

       list.push
         key   : provider


activity: test: set scrollPosition test added

--- client/activity/lib/flux/test/channelflagsstore.coffee
+++ client/activity/lib/flux/test/channelflagsstore.coffee
@@ -86,3 +86,17 @@

       expect(storeState.mockChannelFlagsForSuccessId.reachedFirstMessage).to.eql no

+
+  describe 'handleSetScrollPosition', ->
+
+    it 'sets scrollPosition flag to channel', ->
+
+      @reactor.dispatch actionTypes.SET_CHANNEL_SCROLL_POSITION, {
+        channelId: 42
+        position: 177
+      }
+
+      storeState = @reactor.evaluateToJS ['ChannelFlagsStore']
+
+      expect(storeState[42].scrollPosition).to.eql 177
+


StackTemplateList: showItemContent helper to show dump of the stack template

--- client/admin/lib/views/stacks/stacktemplatelist.coffee
+++ client/admin/lib/views/stacks/stacktemplatelist.coffee
@@ -41,3 +41,25 @@
     overlay.once 'click',                   modal.bound   'destroy'

     return modal
+
+
+  showItemContent: (item) ->
+
+    stackTemplate = item.getData()
+
+    try
+      content = JSON.stringify stackTemplate, null, 2
+    catch e
+      kd.warn e; kd.log data
+      return new KDNotificationView
+        title: "An error occurred"
+
+    cred = hljs.highlight('json', content).value
+
+    new KDModalView
+      title          : stackTemplate.title
+      subtitle       : stackTemplate.modifiedAt
+      cssClass       : 'has-markdown'
+      overlay        : yes
+      overlayOptions : cssClass : 'second-overlay'
+      content        : "
#{cred}
"


Fix fetchAppsFromFS for os kite

Fix fetchAppsFromFS for os kite

--- client/landing/app/MainApp/kodingappscontroller.coffee
+++ client/landing/app/MainApp/kodingappscontroller.coffee
@@ -58,48 +58,43 @@
   fetchAppsFromFs:(callback)->

     path = "/home/#{KD.whoami().profile.nickname}/Applications"
-
-    throw "Please adapt to new os kite interface."
-
-    @kiteController.run "ls #{escapeFilePath path} -lpva", \
-      KD.utils.getTimedOutCallback (err, response)=>
-        if err
-          @putAppsToAppStorage {}
-          warn err, response
-          callback err
-        else
-          files = FSHelper.parseLsOutput [path], response
-          apps  = []
-          stack = []
-
-          files.forEach (file)->
-            if /\.kdapp$/.test file.name
-              apps.push file
-
-          apps.forEach (app)=>
-            stack.push (cb)=>
-              manifestFile = if app.type is "folder" then \
-                FSHelper.createFileFromPath "#{app.path}/.manifest" else app
-              manifestFile.fetchContents (err, response)->
-                # shadowing the error is intentional here
-                # to not to break the results of the stack
-                cb null, response
-
-          manifests = @constructor.manifests
-          async.parallel stack, (err, results)=>
-            warn err if err
-            results.forEach (rawManifest)->
-              if rawManifest
-                try
-                  manifest = JSON.parse rawManifest
-                  manifests["#{manifest.name}"] = manifest
-                catch e
-                  console.warn "Manifest file is broken", e
-            @putAppsToAppStorage manifests
-            callback? null, manifests
-      , ->
-        log "Timeout reached for kite request"
+    appDir = FSHelper.createFileFromPath path
+    appDir.fetchContents KD.utils.getTimedOutCallback (files)=>
+      if not Array.isArray files or files.length is 0
+        @putAppsToAppStorage {}
         callback()
+      else
+        apps  = []
+        stack = []
+
+        files.forEach (file)->
+          if /\.kdapp$/.test file.name
+            apps.push file
+
+        apps.forEach (app)=>
+          stack.push (cb)=>
+            manifestFile = if app.type is "folder" then \
+              FSHelper.createFileFromPath "#{app.path}/.manifest" else app
+            manifestFile.fetchContents (err, response)->
+              # shadowing the error is intentional here
+              # to not to break the results of the stack
+              cb null, response
+
+        manifests = @constructor.manifests
+        async.parallel stack, (err, results)=>
+          warn err if err
+          results.forEach (rawManifest)->
+            if rawManifest
+              try
+                manifest = JSON.parse rawManifest
+                manifests["#{manifest.name}"] = manifest
+              catch e
+                console.warn "Manifest file is broken", e
+          @putAppsToAppStorage manifests
+          callback? null, manifests
+    , ->
+      log "Timeout reached for kite request"
+      callback()

   fetchAppsFromDb:(callback)->
     return unless @appStorage


Fixed calendar render logic

Fixed calendar render logic

--- src/wave-datepicker.coffee
+++ src/wave-datepicker.coffee
@@ -23,13 +23,13 @@

-
-
+
+                    <button class="btn btn-small js-wdp-prev"></button>

-
-
+
+                    <button class="btn btn-small js-wdp-next"></button>

@@ -92,8 +92,8 @@
       @_initShortcuts()
       @_initEvents()

-    render: ->
-      @_updateDateInUI()
+    render: =>
+      @_updateMonthAndYear()
       @_fill()
       return this

@@ -142,20 +142,19 @@
     _initEvents: ->
       # Show and hide picker
       @$el.on('focus', @show).on('blur', @hide)
-      @$el.on 'datechange', @_updateDateInUI
-
-      @$datepicker.on 'click', @_onClick
+      @$el.on 'datechange', @render
+
       @$datepicker.on 'mousedown', @_cancelEvent
       @$datepicker.on 'click', '.js-wdp-shortcut', @_onShortcutClick
+      @$datepicker.on 'click', '.js-wdp-prev', @prev
+      @$datepicker.on 'click', '.js-wdp-next', @next

     # Updates the picker with the current date.
-    _updateDateInUI: =>
-      monthAndYear = moment(@date).format('MMMM YYYY')
-      @$el.val @_formatDate(@date)
+    _updateMonthAndYear: =>
+      date = new Date(@_state.year, @_state.month, 1)
+      monthAndYear = moment(date).format('MMMM YYYY')
+      @$el.val @_formatDate(date)
       @$monthAndYear.text monthAndYear
-      @_state.year = @date.getFullYear()
-      @_state.month = @date.getMonth()
-      @_fill()

     # Sets the Date object for this widget and update `<input>` field.
     setDate: (date) ->
@@ -197,7 +196,8 @@

       # 0 == Sun, 1 == Mon, ..., 6 == Sat
       firstDateDay = startOfMonth.day() - 1
-      lastDateDay = endOfMonth.day()
+      lastDateDay = endOfMonth.day() - 1
+      paddingStart = 0

       # If start date is not Sun then padd beginning of calendar.
       if firstDateDay isnt 0
@@ -209,6 +209,7 @@
           d = prevMonth.add('days', -1).date()
           # + 1 because element at index zero is the

           html[6 - i + 1] = "#{d}	"
+          paddingStart++

       # Fill in dates for this month.
       for i in [1..daysInMonth]
@@ -219,9 +220,8 @@
       # If end date is not Sat then padd the end of calendar.
       if lastDateDay isnt 6
         nextMonth = endOfMonth.clone()
-        n = endOfMonth.date() + firstDateDay
-
-        for i in [lastDateDay+1..6]
+        n = paddingStart + daysInMonth
+        while (n++) % 7 isnt 0
           d = nextMonth.add('days', 1).date()
           if (index++) % 7 is 0
             html.push '

'
@@ -231,8 +231,23 @@

       @$tbody.html html.join ''

-    # Click event from datepicker.
-    _onClick: (e) =>
+    # Navigate to prev month.
+    prev: =>
+      if @_state.month is 1
+        @_state.month = 12
+        @_state.year -= 1
+      else
+        @_state.month -= 1
+      @render()
+
+    # Navigate to next month.
+    next: =>
+      if @_state.month is 12
+        @_state.month = 1
+        @_state.year += 1
+      else
+        @_state.month += 1
+      @render()

     _cancelEvent: (e) => e.stopPropagation(); e.preventDefault()


Try to remove SQL users

Try to remove SQL users


Added missing extention of download file


xmpp/connection: use send() instead of specifying from jid

--- src/xmpp/connection.coffee
+++ src/xmpp/connection.coffee
@@ -94,9 +94,8 @@

     subscribePresence: (jid) ->
         unless @onlineResources.hasOwnProperty(jid)
-            @conn.send new xmpp.Element("presence",
+            @send new xmpp.Element("presence",
                 to: jid
-                from: @conn.jid
                 type: "subscribe"
             )

@@ -118,7 +117,7 @@
         switch presence.attrs.type
             when "subscribe"
                 # User subscribes to us
-                @conn.send new xmpp.Element("presence",
+                @send new xmpp.Element("presence",
                     from: presence.attrs.to
                     to: presence.attrs.from
                     id: presence.attrs.id
@@ -126,7 +125,7 @@
                 )
             when "unsubscribe"
                 # User unsubscribes from us
-                @conn.send new xmpp.Element("presence",
+                @send new xmpp.Element("presence",
                     from: presence.attrs.to
                     to: presence.attrs.from
                     id: presence.attrs.id
@@ -134,7 +133,7 @@
                 )
             when "probe"
                 # We are always available
-                @conn.send new xmpp.Element("presence",
+                @send new xmpp.Element("presence",
                     from: presence.attrs.to
                     to: presence.attrs.from
                     id: presence.attrs.id


Naming consistency

Naming consistency

--- Web/gruntfile.coffee
+++ Web/gruntfile.coffee
@@ -16,14 +16,14 @@
 				dest: 'dist/'
 				expand:  true
 				flatten: true
-			lib:
+			jslib:
 				src:  'build/jslibs.min.js'
 				dest: 'dist/jslibs.min.js'
 			index:
 				src:  'index.htm'
 				dest: 'dist/'
 		concat:
-			libs:
+			jslib:
 				src:  'jslib/*.min.js'
 				dest: 'build/jslibs.min.js'
 		clean:
@@ -43,7 +43,7 @@
 	grunt.loadNpmTasks 'grunt-myth'
 	grunt.registerTask 'default', () ->
 		fs.mkdirSync 'build' unless fs.existsSync 'build'
-		grunt.task.run 'concat:libs' unless fs.existsSync 'build/jslibs.min.js'
+		grunt.task.run 'concat:jslib' unless fs.existsSync 'build/jslibs.min.js'
 		grunt.task.run [
 			'clean:dist'
 			'copy'


Revert to older version of cache URL

Revert to older version of cache URL

--- API/src/scraper.coffee
+++ API/src/scraper.coffee
@@ -185,7 +185,7 @@
 # get a pet that was not posted yet
 get_notPostedPet = ->
     new Promise (f, r) ->
-        filename = 'posted_pets.json'
+        filename = '/var/cache/petschemnitz/posted_pets.json'
         try
             if fs.existsSync(filename)
                 postedPets = JSON.parse fs.readFileSync filename, 'utf8'


rollback fc12520

rollback fc12520

--- src/parser/parser.coffee
+++ src/parser/parser.coffee
@@ -23,21 +23,17 @@

   parse: (commit) ->
     conventions = []
-    try
-      commit = JSON.parse commit if 'string' is helpers.extractType commit
-      commit.files.forEach (file) ->
-        ext = path.extname file.filename
-        if isSupportExt(ext) and file.patch?
-          convention = {lang: ext.substr(1)}
-          psr = getParser ext
-          lines = parser.parseAdditionTokens file.patch
-          lines.forEach (line) ->
-            convention = psr.parse line, convention, commit.html_url
-          conventions.push convention
-      conventions
-    catch err
-      logger.error 'parsing', {err: err}
-      []
+    commit = JSON.parse commit if 'string' is helpers.extractType commit
+    commit.files.forEach (file) ->
+      ext = path.extname file.filename
+      if isSupportExt(ext) and file.patch?
+        convention = {lang: ext.substr(1)}
+        psr = getParser ext
+        lines = parser.parseAdditionTokens file.patch
+        lines.forEach (line) ->
+          convention = psr.parse line, convention, commit.html_url
+        conventions.push convention
+    conventions

 # private
 supportExts = [


update player directive

update player directive

--- static_source/app/app.coffee
+++ static_source/app/app.coffee
@@ -17,7 +17,7 @@
     'ui.pagination'
     'ngDialog'
     'ngSanitize'
-    'ui.audio-player'
+    'ui.player'
   ])

 angular.module('app')


keymap for tabs. ⌘-Shift-[, ⌘-Shift-], ⌘-1, ⌘-2, etc

--- plugins/tabs/tabs.coffee
+++ plugins/tabs/tabs.coffee
@@ -14,6 +14,17 @@

   keymap:
     'Command-W': 'closeActiveTab'
+    'Command-Shift-[': 'prevTab'
+    'Command-Shift-]': 'nextTab'
+    'Command-1': -> @switchToTab 1
+    'Command-2': -> @switchToTab 2
+    'Command-3': -> @switchToTab 3
+    'Command-4': -> @switchToTab 4
+    'Command-5': -> @switchToTab 5
+    'Command-6': -> @switchToTab 6
+    'Command-7': -> @switchToTab 7
+    'Command-8': -> @switchToTab 8
+    'Command-9': -> @switchToTab 9

   initialize: ->
     @editor = activeWindow.document
@@ -55,6 +66,12 @@
     $('#tabs').parents('.pane').remove()
     $('#tabs-style').remove()

+  nextTab: ->
+    @switchToTab $('#tabs ul .active').next()
+
+  prevTab: ->
+    @switchToTab $('#tabs ul .active').prev()
+
   showTabs: ->
     activeWindow.addPane this
     $('#tabs').parents('.pane').css height: 'inherit'
@@ -62,6 +79,8 @@
     $('head').append css

   switchToTab: (tab) ->
+    tab = $('#tabs ul li').get(tab - 1) if _.isNumber tab
+    return if tab.length is 0
     $('#tabs ul .active').removeClass()
     $(tab).addClass 'active'
     @editor.switchToSession $(tab).data 'path'


fixes #144 for good

fixes #144 for good

--- src/languages/jsx.coffee
+++ src/languages/jsx.coffee
@@ -16,7 +16,8 @@
     Supported extensions
     ###
     extensions: [
-        "jsx"
+        "jsx",
+        "js"
     ]

 }


Better hound responses

Better hound responses

--- src/scripts/hound.coffee
+++ src/scripts/hound.coffee
@@ -127,8 +127,20 @@
                 hound status for the morning"
     Logger.logToChannel response, 'ibizan-diagnostics'

-  robot.respond /hound (.*)/i, (res) ->
+  robot.respond /hound\s*(.*)?$/i, (res) ->
+    if res.message.user.name is 'hubot'
+      return
+    user = Organization.getUserBySlackName res.message.user.name
+    if not user
+      res.reply "#{res.message.user.name} is not a valid user"
+      Logger.addReaction 'x', res.message
+      return
+
     command = res.match[1]
+    if not command
+      user.directMessage "Change hounding settings using `hound (scope) (command)`! Try something like `hound (self/org) (on/off/pause/reset/status/X hours)`", Logger
+      Logger.addReaction 'dog2', res.message
+      return
     comps = command.split(' ') || []
     scope = comps[0] || 'self'
     if scope is Organization.name
@@ -147,11 +159,7 @@
     action = comps[1] || 'unknown'

     if scope is 'self'
-      user = Organization.getUserBySlackName res.message.user.name
-      if not user
-        user.directMessage "#{res.message.user.name} is not a valid user", Logger
-        Logger.addReaction 'x', res.message
-      else if match = action.match /((0+)?(?:\.+[0-9]*) hours?)|(0?1 hour)|(1+(?:\.+[0-9]*)? hours)|(0?[2-9]+(?:\.+[0-9]*)? hours)|([1-9][0-9]+(?:\.+[0-9]*)? hours)/i
+      if match = action.match /((0+)?(?:\.+[0-9]*) hours?)|(0?1 hour)|(1+(?:\.+[0-9]*)? hours)|(0?[2-9]+(?:\.+[0-9]*)? hours)|([1-9][0-9]+(?:\.+[0-9]*)? hours)/i
         block_str = match[0].replace('hours', '').replace('hour', '').trimRight()
         block = parseFloat block_str
         user.settings.fromSettings {
@@ -236,5 +244,6 @@
         user.directMessage "I couldn't understand you. Try something like `hound (self/org) (on/off/pause/reset/status/X hours)`", Logger
         Logger.addReaction 'x', res.message
     else
+      Logger.debug "Hound could not parse #{command}"
       user.directMessage "I couldn't understand you. Try something like `hound (self/org) (on/off/pause/reset/status/X hours)`", Logger
       Logger.addReaction 'x', res.message


Only out-hound after active hours

Only out-hound after active hours

--- src/scripts/hound.coffee
+++ src/scripts/hound.coffee
@@ -100,7 +100,7 @@
               user.hound houndMessage('out'), Logger
           else if lastPunch.mode is 'in' and user.isInactive()
             Logger.debug "Considering hounding #{user.slack} because lastPunch is in and it's outside of their active period"
-            if timeSinceEnd >= 0.5
+            if now.isAfter(end) and timeSinceEnd >= 0.5
               user.hound houndMessage('out'), Logger
           else if lastPunch.mode is 'out' and not passive
             Logger.debug "Considering hounding #{user.slack} because lastPunch is out during active period"


Fix load projects in link to epic

Fix load projects in link to epic

--- app/coffee/modules/common/lightboxes.coffee
+++ app/coffee/modules/common/lightboxes.coffee
@@ -1132,7 +1132,7 @@

 debounceLeading = @.taiga.debounceLeading

-RelateToEpicLightboxDirective = ($rootScope, $confirm, lightboxService, tgCurrentUserService
+RelateToEpicLightboxDirective = ($rootScope, $confirm, lightboxService, $tgCurrentUserService
 tgResources, $tgResources, $epicsService, tgAnalytics) ->
     link = ($scope, $el, $attrs) ->
         us = null
@@ -1147,11 +1147,7 @@

         loadProjects = ->
             if $scope.projects == null
-                $tgResources.projects.list({
-                    blocked_code: 'null',
-                    is_epics_activated: true
-                }).then (data) ->
-                    $scope.projects = data
+                $scope.projects = $tgCurrentUserService.projects.get("all")

         filterEpics = (selectedProjectId, filterText) ->
             tgResources.epics.listInAllProjects(


test if we got data

test if we got data

--- src/test/template.coffee
+++ src/test/template.coffee
@@ -8,8 +8,13 @@
     simple: (æ) ->
         xml = streamify new Template ->
             @$tag('test')
-        xml.on 'end', æ.done
-        xml.on 'data', (tag) -> æ.equal "<test/>", tag
+        check = "no data"
+        xml.stream.on 'data', (tag) ->
+            æ.equal "<test/>", tag
+            check = "got data"
+        xml.stream.on 'end', ->
+            æ.equal check, "got data"
+            æ.done()

     html5: (æ) ->


remove console.log

remove console.log

--- ng-tether.coffee
+++ ng-tether.coffee
@@ -12,7 +12,6 @@
   link: (scope, element, attrs) ->
     tetherOptions = {}

-    console.log attrs
     # pluck out relevant keys from the attributes and remove prefix
     for own key, value of attrs
       if key isnt prefix and (key.indexOf prefix) isnt -1


Toggle quotes back

Toggle quotes back

--- src/theme-package.coffee
+++ src/theme-package.coffee
@@ -5,7 +5,7 @@
 class ThemePackage extends Package
   getType: -> 'theme'

-  getStylesheetType: -> "theme"
+  getStylesheetType: -> 'theme'

   enable: ->
     atom.config.unshiftAtKeyPath('core.themes', @name)


some spelling issues in code documentation

--- client/application.coffee
+++ client/application.coffee
@@ -48,7 +48,7 @@
 		if method is "read"
 			if (data = @cache.get(cacheKey)) isnt null
 				# If the cache data is available, call appropriate success
-				# callbacks wit hthe data
+				# callbacks with the data
 				model.trigger("sync", model, data, options)
 				options.success(data)
 				# Return true as the function was successful
@@ -59,7 +59,7 @@
 			# Delete the key from the cache
 			@cache.remove(cacheKey)

-		# If the item is not cache use the original backbone.sync
+		# If the item is not cached use the original backbone.sync
 		@BackboneSync.apply(this, [method, model, options])

 	# Override backbone.sync functionality to insert cached data when appropriate


Fix clothes.add js

Fix clothes.add js

--- public/assets/coffee/clothes-add.coffee
+++ public/assets/coffee/clothes-add.coffee
@@ -7,6 +7,7 @@
     $('#form-clothes button').show()
     $(@).addClass('active')
     category = $(@).data('category')
+    $('#category').val(category)
     types = switch category
       when 'jacket'    then [ 'bust', 'arm', 'topbelly', 'belly'         ]
       when 'pants'     then [ 'waist', 'hip', 'thigh', 'length'          ]
@@ -19,11 +20,15 @@
       when 'belt'      then [ 'length'                                   ]
       when 'shoes'     then [ 'foot'                                     ]
       else []
-    for type in types
-      $("##{type}").show()
+    $("##{type}").show() for type in types

   $('.label-category').click ->
     color = $(@).data('color')
     $('.label-category').removeClass('active')
     $(@).addClass('active')
     $("#color").val(color)
+
+  $('#btn-discard').click (e) ->
+    e.preventDefault()
+    $('#discard').val('1')
+    $(@).closest('form').submit()


improve best bond into greater regex matches.

--- scripts/bond.coffee
+++ scripts/bond.coffee
@@ -5,5 +5,5 @@
 ]

 module.exports = (robot) ->
-  robot.respond /best bond/i, (msg) ->
+  robot.hear /best bond/i, (msg) ->
     msg.send "http://media.economist.com/sites/default/files/imagecache/full-width/images/2012/10/articles/body/20121020_bkc276_0.png"


Added jerk as part of robot.respond message response functionality.

--- scripts/jerk.coffee
+++ scripts/jerk.coffee
@@ -1,5 +1,6 @@
 module.exports = (robot) ->
-  robot.hear /^!jerk/i, (msg) ->
+  robot.respond /(jerk)( me)?/i, (msg) ->
+    console.log(msg)
     msg
       .http("https://www.reddit.com/r/circlejerk/.json")
       .query


remove base64 prefix before creating image

--- src/ui/dispatcher.coffee
+++ src/ui/dispatcher.coffee
@@ -224,6 +224,7 @@
 handle 'onpasteimage', ->
     element = document.getElementById 'preview-img'
     element.src = clipboard.readImage().toDataURL()
+    element.src = element.src.replace /image\/png/, 'image/gif'
     document.querySelector('#preview-container').classList.add('open')

 handle 'uploadpreviewimage', ->


Fix changelanguage handler not working if we have an invalid locale.


Second level in certificates

Second level in certificates

--- lib/zen.coffee
+++ lib/zen.coffee
@@ -232,7 +232,7 @@

 __server = ->
   if ZEN.protocol is "https"
-    certificates = __dirname + "/../../../certificates/"
+    certificates = (ZEN.path or "#{__dirname}/../../..") + "/certificates/"
     https.createServer
       cert  : fs.readFileSync("#{certificates}#{ZEN.cert}")
       key   : fs.readFileSync("#{certificates}#{ZEN.key}")


adds tray options to View menu

adds tray options to View menu

--- src/ui/views/menu.coffee
+++ src/ui/views/menu.coffee
@@ -394,11 +394,30 @@
         }
         { type: 'separator' }
         {
-            label: i18n.__('menu.view.tray.show_tray:Show tray icon')
-            type: 'checkbox'
-            enabled: not viewstate.hidedockicon
-            checked:  viewstate.showtray
-            click: -> action 'toggleshowtray'
+            label: i18n.__('menu.view.tray.main:Tray icon')
+            submenu: [
+                {
+                    label: i18n.__('menu.view.tray.show_tray:Show tray icon')
+                    type: 'checkbox'
+                    enabled: not viewstate.hidedockicon
+                    checked:  viewstate.showtray
+                    click: -> action 'toggleshowtray'
+                }
+                {
+                  label: i18n.__ "menu.view.tray.start_minimize:Start minimized to tray"
+                  type: "checkbox"
+                  enabled: viewstate.showtray
+                  checked: viewstate.startminimizedtotray
+                  click: -> action 'togglestartminimizedtotray'
+                }
+                {
+                    label: i18n.__ "menu.view.tray.close:Close to tray"
+                    type: "checkbox"
+                    enabled: viewstate.showtray
+                    checked: viewstate.closetotray
+                    click: -> action 'toggleclosetotray'
+                }
+            ]
         }
         {
           label: i18n.__('menu.view.escape.title:Escape key behavior')


Ignore various unimportant python linting errors.

Signed-off-by: Jason Lynch <68c46a606457643eab92053c1c05574abb26f861@calindora.com>

--- topics/atom.symlink/config.cson
+++ topics/atom.symlink/config.cson
@@ -25,6 +25,7 @@
     clangDefaultCFlags: "-Weverything"
     clangDefaultCppFlags: "-std=c++1z -Weverything -Wno-c++98-compat -Wno-padded -Wno-c++98-compat-pedantic"
   "linter-pylama":
+    ignoreErrorsAndWarnings: "D100,D101,D102,D103,D105,E265,E501,W191"
     useIsort: true
   "linter-stylelint":
     disableWhenNoConfig: false