Don't check for matching Systems when adding Ozaria Components

Ozaria has different systems and doesn't track relationships between Components and Systems there.

--- app/views/editor/component/ThangComponentsEditView.coffee
+++ app/views/editor/component/ThangComponentsEditView.coffee
@@ -10,6 +10,7 @@
 AddThangComponentsModal = require './AddThangComponentsModal'
 nodes = require '../level/treema_nodes'
 require 'lib/setupTreema'
+utils = require 'core/utils'

 ThangType = require 'models/ThangType'
 CocoCollection = require 'collections/CocoCollection'
@@ -337,6 +338,7 @@

   checkForMissingSystems: ->
     return unless @level
+    return if utils.isOzaria  # Ozaria has different systems and doesn't track relationships between Components and Systems there
     extantSystems =
       (@supermodel.getModelByOriginalAndMajorVersion LevelSystem, sn.original, sn.majorVersion).attributes.name.toLowerCase() for idx, sn of @level.get('systems')


User updater for migrating old referral to rewards, not ready yet wip

--- scripts/user-updater/main.coffee
+++ scripts/user-updater/main.coffee
@@ -1,5 +1,6 @@

-Bongo     = require 'bongo'
+Bongo = require 'bongo'
+{Relationship} = require 'jraphical'

 { join: joinPath } = require 'path'

@@ -19,33 +20,83 @@

 koding.once 'dbClientReady', ->

-  JAccount     = rekuire 'account.coffee'
-  JDomainAlias = rekuire 'domainalias.coffee'
+  JAccount = rekuire 'account.coffee'
+  JReward  = rekuire 'rewards/index.coffee'

-  gskip  = 0
+  fetchUser = (acc, callback)->
+    JUser   = rekuire 'user/index.coffee'
+
+    selector = { targetId: acc._id, as: 'owner', sourceName: 'JUser' }
+    Relationship.one selector, (err, rel) ->
+      return callback err   if err
+      return callback null  unless rel
+      JUser.one {_id: rel.sourceId}, callback
+
+
+  gskip = 0

   JAccount.count {type:'registered'}, (err, total)->
     return console.warn err  if err?
+
     limit = 100

+    console.log "Total #{total} users found, updating..."
+
     fetcher = (skip)->
+
       dindex = 0
       index  = skip
-      JAccount.each {type:'registered'}, {}, {skip, limit}, (err, account)->
+
+      JAccount.each { type:'registered' }, {}, {skip, limit}, (err, account)->
+
         return console.warn err  if err?
         return  unless account?

         index++
         console.log "Working on #{index}. user"  if index % 10 is 0
-        JDomainAlias.ensureTopDomainExistence account, null, (err)->
+
+        originId         = account._id
+        referrerUsername = account.profile.nickname
+        type             = "disk"
+        unit             = "MB"
+        amount           = 500
+        sourceCampaign   = "register"
+
+        JAccount.each { referrerUsername }, {}, { limit: 40 }, (err, referrer)->
+
           console.warn err  if err?
+
+          unless referrer
+            options = { unit, type, originId }
+            console.log "updating rewards for #{referrerUsername}"
+            JReward.calculateAndUpdateEarnedAmount options, (err)->
+              console.log "rewards updated for #{referrerUsername}", err
+            return
+
+          providedBy = referrer._id
+
+          fetchUser referrer, (err, user)->
+
+            console.log "Found user:", user.username, user.status
+            return  unless user.status is 'confirmed'
+
+            reward = new JReward {
+              type, unit, amount, sourceCampaign, providedBy, originId
+            }
+
+            reward.save (err)->
+              console.warn err  if err?
+
           dindex++
+
           if dindex is limit
             console.log "#{index} users updated."
             console.log "Moving to next batch."
             fetcher skip + limit
+
           else if skip >= total
             console.log "ALL DONE"
             process.exit 0

+
     fetcher gskip


remove white space

remove white space

--- js/outer/about.coffee
+++ js/outer/about.coffee
@@ -41,7 +41,6 @@
       bio: ["Jenneviere moved to the Bay Area in 2010 after spending a few months there, singing and dancing, as the lead in a rock opera. She then worked as an extra on the movie Twixt, and when she realized that Mr. Coppola wasn't going to cast her as the lead in his next film, she began drowning her sorrows in hoppy IPAs and the soothing click of knitting needles. She took the black and works the Gate at That Thing In The Desert, and has only written a single line of code, ever.",
         "Drawing from her many years' experience in her previous position as the Customer Amazement Specialist, Operations Manager, Returns Siren, and Retail Store Maven for a certain large utility kilt manufacturer, Jenneviere brings her skills as a professional pottymouth, dabbler in inappropriate and snarky humor, and cat wrangling to the team at Circle, and spends most of her time trying to keep everyone well-groomed and hairball free."]
     ,
-
       name: "Daniel Woelfel"
       photo: "daniel"
       visible: true
@@ -62,7 +61,6 @@
       bio: ["Mahmood loves car camping, which might explain why he also likes moving as much as he does. He and his family have moved 6 times in the last 6 years, though his baby boy has been completely lazy and hasn't offered to carry a single box yet. In between relocation to new abodes, Mahmood spends the nicer Boston weather exploring bike trails and learning computer languages. He's determined that his son will know Java before he can talk.",
         "During his time at MIT, Mahmood spoke at a few Java research conferences including Devoxx, and has pushed code to Java 8--and Java 7--compilers. He's been a speaker at OOPSLA/Splash, and is active in the open-source community. He enjoys being reminded of his in-production projects, like java-apns, every time he gets support tickets. He acknowledges his own fallibility, and aims to help the Circle team continue to bring reliable test automation to all developers. His Achilles Heel is his pair of permanently underachieving glasses."]
     ,
-
       name: "Gordon Syme"
       photo: "gordon"
       visible: true
@@ -73,7 +71,6 @@
       bio: ["Gordon has been racing dinghies right through the winter off the coast of Ireland for the past 16-odd years, and as his oft-showcased cabinet of prize coffee mugs will tell you, he's damn good at it. When he's not busy bludgeoning a piano with his ham-fisted fingers, he spends his free time rolling down mountains on a bicycle… sometimes even the right way up. In his down time, he's a board game shark, though you won't catch him trying to buy Boardwalk or get out of jail free.",
         "Gordon joins the team after spending significant time at Amazon building tools to monitor the entire network (he built their DWDM monitoring from the ground up). He also built a JVM bytecode recompiler to enable running applications on a clustered VM without needing re-programming effort, and has grand plans for how he's going to make waves at Circle. We're pretty sure they include company Power Grid tournaments."]
     ,
-
       name: "Danny King"
       photo: "danny"
       visible: true


be heavy handed with clearing keys

be heavy handed with clearing keys

--- src/components/exercise/step-card.cjsx
+++ src/components/exercise/step-card.cjsx
@@ -66,10 +66,11 @@
     stepState = @getStepState(@props)

   componentWillMount: ->
-    keymaster('enter', @onContinue) if @props.allowKeyNext
+    @clearKeys()
+    @startKeys() if @props.allowKeyNext

   componentWillUnmount: ->
-    keymaster.unbind('enter') if @props.allowKeyNext
+    @clearKeys()

   shouldComponentUpdate: (nextProps, nextState) ->
     not (_.isEqual(@props, nextProps) and
@@ -85,10 +86,15 @@
       @updateKeyBind(nextProps.allowKeyNext)

   updateKeyBind: (allowKeyNext) ->
-    if allowKeyNext
-      keymaster('enter', @onContinue)
-    else
-      keymaster.unbind('enter')
+    if allowKeyNext then @startKeys() else @clearKeys()
+
+  startKeys: ->
+    keymaster('enter', 'multiple-choice', @onContinue)
+    keymaster.setScope('multiple-choice')
+
+  clearKeys: ->
+    keymaster.unbind('enter', 'multiple-choice')
+    keymaster.deleteScope('multiple-choice')

   getStepState: (props) ->
     {step} = props


fixed interaction with autocomplete

fixed interaction with autocomplete

--- keymaps/vim-mode.cson
+++ keymaps/vim-mode.cson
@@ -15,6 +15,8 @@
   'ctrl-u': 'editor:delete-to-beginning-of-line'
   'left': 'vim-mode:move-left-insert'
   'right': 'vim-mode:move-right-insert'
+
+'atom-text-editor.vim-mode.insert-mode:not(.autocomplete-active)':
   'up': 'vim-mode:move-up-insert'
   'down': 'vim-mode:move-down-insert'


Add inline docs for options

Add inline docs for options

--- js/counter.coffee
+++ js/counter.coffee
@@ -13,7 +13,9 @@
   pluginName = "counter"
   # Available options
   defaults =
-    autoStart: true
+    # Start counting automatically on plugin call.
+    autoStart: true # true/false
+    # Duration of the counter
     duration: 1500
     # If the given element contains a value larger than countTo it set on
     # countFrom
@@ -21,10 +23,16 @@
     # If the given element contains a value larger than countFrom it set on
     # countTo
     countTo: undefined # Defaults to 0 if not set
-    runOnce: false
+    # Run the counter only once
+    runOnce: false # true/false
+    # Placeholder to use before the counter has started.
+    # Most useful with autoStart: false
     placeholder: undefined
+    # Easing used by jQuery.animate
     easing: "easeOutQuad"
+    # Callback called when the counter has started. No arguments.
     onStart: ->
+    # Callback called when the counter is completed. No arguments.
     onComplete: ->
     # Format the displayed number with this function.
     # The given value is the number counted to at every step.


improve the logger for debugging #13

improve the logger for debugging #13

--- lib/Logger.coffee
+++ lib/Logger.coffee
@@ -12,7 +12,7 @@
           throw err

   formatCommand: (cmd) ->
-    utils.formatSexp cmd
+    utils.serialize(cmd)

   logIncomingCommand: (str) ->
     @logText "< #{str}\n"


=fixing commit command

=fixing commit command

--- lib/commit.coffee
+++ lib/commit.coffee
@@ -18,6 +18,6 @@
 				# do not create a commit, but show a list of paths that are to be committed.
 				when 'dryrun' then "--dry-run"
 				when 'all' then "-a"
-				when 'message' then "-m=#{_.str.quote(v)}"
+				when 'message' then "--message=#{_.str.quote(v, '\'')}"
 				else ''
 	.filter _.identity


Fix error when on preferences page Small performance optimizations and code cleanup

--- lib/format.coffee
+++ lib/format.coffee
@@ -23,7 +23,7 @@
       @subscribeToEvents()

   format: (state) ->
-    editor = atom.workspace.getActivePaneItem()
+    editor = atom.workspace.getActiveTextEditor()
     if !editor
       return

@@ -75,24 +75,24 @@
     editorSettings = atom.config.get('editor')

     opts = atom.config.get('jsformat')
-
     opts.indent_size = editorSettings.tabLength
     opts.wrap_line_length = editorSettings.preferredLineLength

-    beautifiedText = jsbeautify(editor.getText(), opts)
+    editorText = editor.getText()
+    beautifiedText = jsbeautify(editorText, opts)

-    if (editor.getText() != beautifiedText)
+    if (editorText != beautifiedText)
       if @selectionsAreEmpty(editor)
-        editor.setText(jsbeautify(editor.getText(), opts))
+        editor.setText(beautifiedText)

       else
         for selection in editor.getSelections()
-          selection.insertText(jsbeautify(selection.getText(), opts), {select:true})
+          selection.insertText(jsbeautify(selection.getText(), opts), { select: true })

   selectionsAreEmpty: (editor) ->
     for selection in editor.getSelections()
       return false unless selection.isEmpty()
-    true
+    return true

   subscribeToEvents: (state) ->
     if atom.config.get('jsformat.format_on_save') ? @configDefaults['format_on_save']


Remove resize/scroll callbacks when the view closes

Leftover resize/scroll callbacks were firing when the view was gone,
causing some undefined errors.

--- src/scripts/modules/media/media.coffee
+++ src/scripts/modules/media/media.coffee
@@ -88,7 +88,6 @@

       $pinnable = @regions.pinnable.$el
       pinnableTop = $pinnable.offset().top
-      $titleArea = -> mediaTitleView.$el.find('.media-title')
       $toc = tocView.$el
       isPinned = false
       setTocHeight = ->
@@ -98,27 +97,33 @@
         $toc.css('top', "#{tocTop}px")
         newHeight = window.innerHeight - tocTop
         $toc.height("#{newHeight}px")
+      Backbone.on('window:optimizedResize', setTocHeight)
+      @on('closing', ->
+        Backbone.off('window:optimizedResize', setTocHeight)
+      )
+
       adjustMainMargin = (height) ->
         mainPage.regions.main.$el.css('margin-top', "#{height}px")
+
+      $titleArea = mediaTitleView.$el.find('.media-title')
       pinNavBar = ->
         $pinnable.addClass('pinned')
-        $titleArea().addClass('compact')
+        $titleArea.addClass('compact')
         $toc.addClass('pinned')
         isPinned = true
         adjustMainMargin($pinnable.height())
       unpinNavBar = ->
         $pinnable.removeClass('pinned')
-        $titleArea().removeClass('compact')
+        $titleArea.removeClass('compact')
         $toc.removeClass('pinned')
         isPinned = false
         adjustMainMargin(0)
         pinnableTop = $pinnable.offset().top
       mediaTitleView.on('render', ->
-        if isPinned
-          $titleArea().addClass('compact')
-      )
-
-      Backbone.on('window:optimizedResize', setTocHeight)
+        $titleArea = mediaTitleView.$el.find('.media-title')
+        $titleArea.addClass('compact') if isPinned
+      )
+
       handleHeaderViewPinning = ->
         top = $(window).scrollTop()
         if top > pinnableTop
@@ -127,8 +132,11 @@
         else if isPinned
           unpinNavBar()
         setTocHeight()
-
       Backbone.on('window:optimizedScroll', handleHeaderViewPinning)
+      @on('closing', ->
+        Backbone.off('window:optimizedScroll', handleHeaderViewPinning)
+      )
+
       navView.on('tocIsOpen', (whether) ->
         windowWithSidebar.open(whether)
         # On small screens, when the contents is opened,
@@ -148,6 +156,9 @@
         $(window).scrollTop(scrollTo)
       )

+    close: ->
+      @trigger('closing')
+
     updateSummary: () ->
       abstract = @model.get('abstract')
       if abstract


Some clean up.

Some clean up.

--- src/spec/orderdistribution.spec.coffee
+++ src/spec/orderdistribution.spec.coffee
@@ -188,9 +188,9 @@
       expect(_.size(orders)).toBe 1
       expect(@distribution.masterClient._rest.GET).toHaveBeenCalledWith(/orders/, jasmine.any(Function))
       done()
-    .fail (msg) ->
-      console.error msg
-      done(msg)
+    .fail (err) ->
+      console.error err
+      done err

 describe '#getRetailerProductByMasterSKU', ->
   beforeEach ->
@@ -204,9 +204,9 @@
       uri = "/product-projections/search?staged=true&lang=de&filter=variants.attributes.mastersku%3A%22foo%22"
       expect(@distribution.retailerClient._rest.GET).toHaveBeenCalledWith(uri, jasmine.any(Function))
       done()
-    .fail (msg) ->
-      console.error msg
-      done(msg)
+    .fail (err) ->
+      console.error err
+      done err

 describe '#addSyncInfo', ->
   beforeEach ->
@@ -228,6 +228,6 @@
         ]
       expect(@distribution.masterClient._rest.POST).toHaveBeenCalledWith("/orders/x", JSON.stringify(expectedAction), jasmine.any(Function))
       done()
-    .fail (msg) ->
-      console.error msg
-      done(msg)
+    .fail (err) ->
+      console.error err
+      done err


Remove unnecessary code from parser

Remove unnecessary code from parser

--- lib/parser.coffee
+++ lib/parser.coffee
@@ -38,7 +38,5 @@
 tr = through (data) ->
   if parsed = parse_tweet(data)
     @emit 'data', parsed
-, ->
-  @emit 'end'

 module.exports = tr


Do not hide the controlbar

Do not hide the controlbar

--- src/videojs.chromecastComponent.coffee
+++ src/videojs.chromecastComponent.coffee
@@ -1,5 +1,6 @@
 class vjs.ChromecastComponent extends vjs.Button
   buttonText: "Chromecast"
+  inactivityTimeout: 2000

   apiInitialized: false
   apiSession: null
@@ -94,8 +95,12 @@

     @paused = false
     @player_.loadTech "ChromecastTech", {}
+    @casting = true
+
+    # Always show the controlbar
+    @inactivityTimeout = @player_.options_.inactivityTimeout
+    @player_.options_.inactivityTimeout = 0
     @player_.userActive true
-    @casting = true

   onSessionUpdate: (isAlive) ->
     return unless @apiMedia
@@ -198,6 +203,9 @@
     # Hide the default HTML5 player controls.
     @player_.tech.setControls(false)

+    # Enable user activity timeout
+    @player_.options_.inactivityTimeout = @inactivityTimeout
+
     @player_.ready ->
       @player_.currentTime @currentMediaTime
       @player_.play() unless @paused


:art: :hammer: snippet name and prefix

:art: :hammer: snippet name and prefix

--- lib/snippets.cson
+++ lib/snippets.cson
@@ -12,8 +12,8 @@
       }$5
     """

-  'Atom Snippet Keys':
-    prefix: 'snipk'
+  'Atom Snippet With No Selector':
+    prefix: 'snipns'
     body: """
       "${1:Snippet Name}": {
         "prefix": "${2:Snippet Trigger}",
@@ -41,12 +41,12 @@
           'body': '${4:Hello World!}'$5
     """

-  'Atom Snippet Keys':
-    prefix: 'snipk'
+  'Atom Snippet With No Selector':
+    prefix: 'snipns'
     body: """
       '${1:Snippet Name}':
         'prefix': '${2:Snippet Trigger}'
-        'body': '${3:Hello World!}'$5
+        'body': '${3:Hello World!}'$4
     """

   'Atom Keymap':


Fix HTML Sorting

Fix HTML Sorting

--- lib/sorter.coffee
+++ lib/sorter.coffee
@@ -58,32 +58,38 @@
 					{
 						find: '="'
 						join: '" '
-						split: '" '
+						split: /" |"$/gm
+						end: '"'
 					}
 					{
 						find: ';'
 						join: '; '
 						split: ';'
+						end: ''
 					}
 					{
 						find: '='
 						join: ' = '
 						split: '='
+						end: ''
 					}
 					{
 						find: ','
 						join: ', '
 						split: ','
+						end: ''
 					}
 					{
 						find: ':'
 						join: ': '
 						split: ':'
+						end: ''
 					}
 					{
 						find: ' '
 						join: ' '
 						split: ' '
+						end: ''
 					}
 				]

@@ -97,9 +103,13 @@
 				# skip unknown separations
 				return if separationIndex == -1

+				# add end
+				end += separations[separationIndex].end
+
 				# trim whitespace from each element
-				things = text.split(separations[separationIndex].split).map (x) ->
-					x.trim()
+				things = text
+					.split(separations[separationIndex].split)
+					.filter((x) -> !!x).map((x) -> x.trim())

 				# sort & merge the elements
 				sorted = things.sort() unless sortingFunction


incorporate functionality from auxis

incorporate functionality from auxis

--- lib/theory.coffee
+++ lib/theory.coffee
@@ -59,6 +59,17 @@
   [naturalName, accidentals] = match[1...]
   pitch = SharpNoteNames.indexOf(naturalName.toUpperCase())
   pitch += AccidentalValues[c] for c in accidentals
+  return pitch
+
+midi2name = (number) ->
+  "#{NoteNames[(number + 12) % 12]}#{Math.floor((number - 12) / 12)}"
+
+name2midi = (name) ->
+  throw new Error "#{name} is not a note name" unless m = name.toUpperCase().match(/^([A-G])([♯#♭b𝄪𝄫]*)(\d+)/)
+  [noteName, accidentals, octave] = m.slice(1)
+  pitch = NoteNames.indexOf(noteName)
+  pitch += AccidentalValues[c] for c in accidentals
+  pitch += 12 * Number(octave)
   return pitch

@@ -195,17 +206,21 @@
     @abbrs ?= [@abbr]
     @abbrs = @abbrs.split(/s/) if typeof @abbrs == 'string'
     @abbr ?= @abbrs[0]
-    if @rootPitch?
-      @rootName or= NoteNames[@rootPitch]
+
+    @rootName or= NoteNames[@rootPitch] if @rootPitch?
     if @rootName?
-      @rootPitch ?= parsePitchClass(@rootName)
+      @rootPitch ?=
+        if @rootName.match(/\d/) then pitchFromScientificNotation(@rootName) else parsePitchClass(@rootName)
+      @pitches = (pitch + @rootPitch for pitch in @pitchClasses)
       rootlessAbbr = @abbr
       rootlessFullName = @fullName
       Object.defineProperty this, 'name', get: -> "#{@rootName}#{rootlessAbbr}"
       Object.defineProperty this, 'fullName', get: -> "#{@rootName} #{rootlessFullName}"
+
     degrees = (1 + 2 * i for i in [0..@pitchClasses.length])
     degrees[1] = {'Sus2':2, 'Sus4':4}[@name] || degrees[1]
     degrees[3] = 6 if @name.match /6/
+
     @components = for pc, pci in @pitchClasses
       name = IntervalNames[pc]
       degree = degrees[pci]
@@ -242,11 +257,16 @@
     return this

   @find: (name) ->
-    match = name.match(/^([a-gA-G][#b♯♭]*)(.*)$/)
+    chord = Chords[name]
+    return chord if chord
+    match = name.match(/^([a-gA-G][#b♯♭𝄪𝄫]*(?:\d*))?(.*)$/)
     throw new Error("#{name} is not a chord name") unless match
     [noteName, chordName] = match[1...]
-    throw new Error("#{name} is not a chord name") unless Chords[chordName]
-    return Chords[chordName].at(noteName)
+    chordName or= 'Major'
+    chord = Chords[chordName]
+    throw new Error("#{name} is not a chord name") unless chord
+    chord = chord.at(noteName) if noteName
+    return chord

   @fromPitches: (pitches) ->
     root = pitches[0]
@@ -258,8 +278,7 @@
     throw new Error("Couldn''t find chord with pitch classes #{pitchClasses}") unless chord
     return chord

-
-ChordDefinitions = [
+Chords = [
   {name: 'Major', abbrs: ['', 'M'], pitchClasses: '047'},
   {name: 'Minor', abbr: 'm', pitchClasses: '037'},
   {name: 'Augmented', abbrs: ['+', 'aug'], pitchClasses: '048'},
@@ -278,21 +297,17 @@
   {name: 'Minor-Major 7th', abbrs: ['min/maj7', 'min(maj7)'], pitchClasses: '037e'},
   {name: '6th', abbrs: ['6', 'M6', 'M6', 'maj6'], pitchClasses: '0479'},
   {name: 'Minor 6th', abbrs: ['m6', 'min6'], pitchClasses: '0379'},
-]
-
-# Chords is an array of chord classes
-Chords = ChordDefinitions.map (spec) ->
-  spec.fullName = spec.name
-  spec.name = spec.name
+].map ({name, abbr, abbrs, pitchClasses}) ->
+  fullName = name
+  name = name
     .replace(/Major(?!$)/, 'Maj')
     .replace(/Minor(?!$)/, 'Min')
     .replace('Dominant', 'Dom')
     .replace('Diminished', 'Dim')
-  spec.abbrs or= [spec.abbr]
-  spec.abbrs = spec.abbrs.split(/s/) if typeof spec.abbrs == 'string'
-  spec.abbr or= spec.abbrs[0]
-  spec.pitchClasses = spec.pitchClasses.match(/./g).map (c) -> {'t':10, 'e':11}[c] or Number(c)
-  new Chord spec
+  abbrs ?= [abbr]
+  abbr ?= abbrs[0]
+  pitchClasses = pitchClasses.match(/./g).map (c) -> {'t':10, 'e':11}[c] ? Number(c)
+  new Chord {name, fullName, abbr, abbrs, pitchClasses}

 # `Chords` is also indexed by chord names and abbreviations, and by pitch classes
 do ->
@@ -300,6 +315,41 @@
     {name, fullName, abbrs} = chord
     Chords[key] = chord for key in [name, fullName].concat(abbrs)
     Chords[chord.pitchClasses] = chord
+
+
+#
+# Chord Progressions
+#
+
+Chord.progression = (tonic, chords) ->
+  scale = [0, 2, 4, 5, 7, 9, 11]
+  romanNumerals = 'I II III IV V VI VII'.split(/\s+/)
+  tonic = name2midi(tonic) if typeof tonic == 'string'
+  for name in chords.split(/[\s+\-]+/)
+    cr = name.replace(/[♭67°ø\+bcd]/g, '')
+    i = romanNumerals.indexOf(cr.toUpperCase())
+    if i >= 0
+      acc = 0
+      acc = -1 if name.match /♭/
+      chordRoot = midi2name(tonic + scale[i] + acc)
+      chordType = "Major"
+      chordType = "Minor" if cr == cr.toLowerCase()
+      chordType = "aug" if name.match /\+/
+      chordType = "dim" if name.match /°/
+      chordType = "maj6" if name.match /6/
+      chordType = "dom7" if name.match /7/
+      chordType = "+7" if name.match /\+7/
+      chordType = "°7" if name.match /°7/
+      chordType = "ø7" if name.match /ø7/
+      # TODO 9, 13, sharp, natural
+      chord = Chord.find(chordType).at(chordRoot)
+      chord.inversion = 1 if name.match /b/
+      chord.inversion = 2 if name.match /c/
+      chord.inversion = 3 if name.match /d/
+      chord.pitches = chord.pitches[chord.inversion...].concat chord.pitches[...chord.inversion] if chord.inversion?
+      chord
+    else
+      name

 #
@@ -318,9 +368,11 @@
   ScaleDegreeNames
   Scales
   SharpNoteNames
+  getPitchClassName
   getPitchName
-  getPitchClassName
   intervalClassDifference
+  midi2name
+  name2midi
   normalizePitchClass
   pitchFromScientificNotation
   pitchNameToNumber: parsePitchClass


Change decision to solution

Change decision to solution

--- server/materials/data-en/level_about.coffee
+++ server/materials/data-en/level_about.coffee
@@ -78,7 +78,7 @@

If, for objective unforeseen reasons, you could not study in the course for more than 5 days in a row (broke your arm, etc.), then I will refund you the money for the entire period when you could not study. Predictable things like trips, exams, etc. are not counted here.

If for whatever reason you have not been engaged for more than 10 days in a row, then I will refund you half of the money for this period.

If you warn me in advance that you will not be able to study for more than 10 days in a row, then I can refund you money for this period.

-
Of course, if for some reason I could not check your decisions and respond to your emails for more than 5 days in a row, then I will refund you for the entire period.

+
Of course, if for some reason I could not check your solutions and respond to your emails for more than 5 days in a row, then I will refund you for the entire period.

""", {id: "pay"})

@@ -194,7 +194,7 @@

But in general, try to check the problem before sending it to the system (and we will learn this separately). Treat failed attempts as a real failure, and try to make as few failed attempts as possible. In general, at many programming contests you will have only one opportunity to submit the problem, and if it does not pass some tests, then you will no longer have the opportunity to improve!

Accepting and ignoring submits, comments on submits

-
I will review all your decisions: if I have time, all submissions in general, including unsuccessful ones, or if I have little time, and there are a lot of submissions, then only submissions with the OK status.

+
I will review all your solutions: if I have time, all submissions in general, including unsuccessful ones, or if I have little time, and there are a lot of submissions, then only submissions with the OK status.

In successful submissions, I will review your code for the following:

@@ -204,7 +204,7 @@

If I have any comments or comments on your program, I will write them and you will be able to see them. Firstly, they will appear on the site in the right column in the "Comments" section; secondly, in each submission, in the window opened by clicking the submit link, there is a special "comments" tab; finally, there is a separate Comments section in the lefthand menu. Accordingly, as soon as I comment on your package, you will see a comment. Usually I try to view all the submissions during the day, although sometimes there are long delays, up to several days.

In addition to actually writing a comment, I make a decision on each of your submissions — either I accept this submit (albeit with small comments), or I will require you to rewrite this submit taking into account my comments. In the first case, I change the status of the submission to "Accepted" (i.e. you will see exactly "Accepted" instead of "OK" in the table with your submissions), in the second case I change the status to "Ignored". Accordingly, in the first case, just take note of the comments that I wrote (and it happens that I do not write any comments), although you can rewrite the solution if you want. In the second case, be sure to rewrite the solution taking into account these comments.

-
(Read more about why I ignore decisions (in Russian).)

+
(Read more about why I ignore solutions (in Russian).)

In the summary table, the "OK" solutions (i.e., which I haven't looked at yet) are displayed with a yellow background, the "Accepted" solutions (i.e., which I have accepted) are displayed with a green background, ignored solutions are not taken into account in the total number of problems you have solved, just the corresponding problem in the summary table is displayed with a blue background.

@@ -304,7 +304,7 @@

When can I start studying? When does the "next group" start?

You can join the course at any time. All the same, students have quite different levels of training, so there is no single program like "this week we are going through this, next week this, and if you missed or learned about the course too late, then wait for the next group or next year".

-
The only thing is that during the holidays or in the summer I may have limited access to the Internet, so I may not respond to your letters so quickly, look at your decisions, etc.

+
The only thing is that during the holidays or in the summer I may have limited access to the Internet, so I may not respond to your letters so quickly, look at your solutions, etc.

And who are you anyway?

I am Petr Kalinin, Ph.D., Senior developer at Yandex, graduate of the Nizhny Novgorod State University. At one time, I actively participated in various programming contests and olympiads: bronze medalist of the International Olympiad in Informatics (IOI) in 2001, gold medalist of the IOI in 2002; as part of the UNN team, I twice participated in the finals of the ACM International Collegiate Programming Contest (ACM ICPC). From 2017 to 2019, I taught at the Yandex School of Data Analysis. I am a student of V.D. Lelyukh.


Pass builder to callback so user can cleanup

The test is in a tmp dir that will be destroyed so it doesn't want the
build calling cleanup

--- lib/commands/build.coffee
+++ lib/commands/build.coffee
@@ -10,19 +10,18 @@
   builder.build()
     .then (hash) ->
       copyDereferenceSync(hash.directory, outputDir)
-    .finally ->
-      builder.cleanup()
-    .then ->
-      cb(null)
+      cb(null, builder)
+
     .catch (err) ->
-      # Should show file and line/col if present
       console.error('File: ' + err.file) if err.file?
       console.error(err.stack)
       console.error('\nBuild failed')
-      cb(err)
+      cb(err, builder)

-defaultCb = (err) ->
-  if err
-    process.exit(1)
-  else
-    process.exit(0)
+
+defaultCb = (err, builder) ->
+  builder.cleanup().finally ->
+    if err
+      process.exit(1)
+    else
+      process.exit(0)


Bugfix

Bugfix

--- server/publish/user.coffee
+++ server/publish/user.coffee
@@ -3,6 +3,6 @@
     Meteor.users.find _id: @userId
   children: [
     find: (user) ->
-      _id = user.profile.picture or null
+      _id = user.profile?.picture or null
       ProfilePictures.find _id: _id
-    ]+    ]


dont depend on relative path

dont depend on relative path

--- gist-meta.coffee
+++ gist-meta.coffee
@@ -39,7 +39,7 @@
 # using the "since" date. If no date is specified it gets the blocks since the
 # start of time
 getGistMetaData = ->
-  usersString = fs.readFileSync('data/usables.csv').toString()
+  usersString = fs.readFileSync(__dirname + '/data/usables.csv').toString()
   users = d3.csv.parse usersString
   allGists = []
   async.eachLimit users, 5, (user, userCb) ->


Remove unused router

Remove unused router

--- server/routes/about.coffee
+++ server/routes/about.coffee
@@ -1,6 +0,0 @@
-#
-# * GET home page.
-#
-exports.about = (req, res) ->
-  res.render "about",
-    title: "Express"


remove null check on mailchimp at top of file

I didn't like the if statment being after the require, mailchimp
could also be null and called elsewhere by acident

--- services/web/app/coffee/Features/Newsletter/NewsletterManager.coffee
+++ services/web/app/coffee/Features/Newsletter/NewsletterManager.coffee
@@ -3,7 +3,6 @@
 Settings = require 'settings-sharelatex'
 crypto = require('crypto')
 Mailchimp = require('mailchimp-api-v3')
-mailchimp = new Mailchimp(Settings.mailchimp?.api_key) if Settings.mailchimp?

 module.exports =
 	subscribe: (user, callback = () ->)->
@@ -12,6 +11,7 @@
 			return callback()
 		options = buildOptions(user, true)
 		logger.log options:options, user:user, email:user.email, "trying to subscribe user to the mailing list"
+		mailchimp = new Mailchimp(Settings.mailchimp?.api_key)
 		mailchimp.request options, (err)->
 			if err?
 				logger.err err:err, "error subscribing person to newsletter"
@@ -25,6 +25,7 @@
 			return callback()
 		logger.log user:user, email:user.email, "trying to unsubscribe user to the mailing list"
 		options = buildOptions(user, false)
+		mailchimp = new Mailchimp(Settings.mailchimp?.api_key)
 		mailchimp.request options, (err)->
 			if err?
 				logger.err err:err, "error unsubscribing person to newsletter"


Make intermediate argument signature clearer

--- services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee
+++ services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee
@@ -150,11 +150,11 @@
 			DocstoreManager.getDoc project_id, doc_id, options, callback

 	addDoc: (project_or_id, folder_id, docName, docLines, userId, callback = (error, doc, folder_id) ->)=>
-		ProjectEntityHandler.addDocWithoutUpdatingHistory project_or_id, folder_id, docName, docLines, userId, (error, doc, folder_id, result) ->
+		ProjectEntityHandler.addDocWithoutUpdatingHistory project_or_id, folder_id, docName, docLines, userId, (error, doc, folder_id, path) ->
 			return callback(error) if error?
 			newDocs = [
 				doc: doc
-				path: result?.path?.fileSystem
+				path: path
 				docLines: docLines.join('\n')
 			]
 			project_id = project_or_id._id or project_or_id
@@ -177,7 +177,7 @@
 				return callback(err)
 			ProjectEntityHandler._addDocWithProject project, folder_id, docName, docLines, userId, callback

-	_addDocWithProject: (project, folder_id, docName, docLines, userId, callback = (error, doc, folder_id, result) ->)=>
+	_addDocWithProject: (project, folder_id, docName, docLines, userId, callback = (error, doc, folder_id, path) ->)=>
 		project_id = project._id
 		logger.log project_id: project_id, folder_id: folder_id, doc_name: docName, "adding doc to project with project"
 		confirmFolder project, folder_id, (folder_id)=>
@@ -197,7 +197,7 @@
 						rev:          0
 					}, (err) ->
 						return callback(err) if err?
-						callback(null, doc, folder_id, result)
+						callback(null, doc, folder_id, result?.path?.fileSystem)

 	restoreDoc: (project_id, doc_id, name, callback = (error, doc, folder_id) ->) ->
 		# getDoc will return the deleted doc's lines, but we don't actually remove
@@ -206,7 +206,7 @@
 			return callback(error) if error?
 			ProjectEntityHandler.addDoc project_id, null, name, lines, callback

-	addFileWithoutUpdatingHistory: (project_id, folder_id, fileName, path, userId, callback = (error, fileRef, folder_id, result, fileStoreUrl) ->)->
+	addFileWithoutUpdatingHistory: (project_id, folder_id, fileName, path, userId, callback = (error, fileRef, folder_id, path, fileStoreUrl) ->)->
 		ProjectGetter.getProjectWithOnlyFolders project_id, (err, project) ->
 			if err?
 				logger.err project_id:project_id, err:err, "error getting project for add file"
@@ -225,13 +225,13 @@
 							return callback(err)
 						tpdsUpdateSender.addFile {project_id:project._id, file_id:fileRef._id, path:result?.path?.fileSystem, project_name:project.name, rev:fileRef.rev}, (err) ->
 							return callback(err) if err?
-							callback(null, fileRef, folder_id, result, fileStoreUrl)
-
-	addFile:  (project_id, folder_id, fileName, path, userId, callback = (error, fileRef, folder_id) ->)->
-		ProjectEntityHandler.addFileWithoutUpdatingHistory project_id, folder_id, fileName, path, userId, (error, fileRef, folder_id, result, fileStoreUrl) ->
+							callback(null, fileRef, folder_id, result?.path?.fileSystem, fileStoreUrl)
+
+	addFile:  (project_id, folder_id, fileName, fsPath, userId, callback = (error, fileRef, folder_id) ->)->
+		ProjectEntityHandler.addFileWithoutUpdatingHistory project_id, folder_id, fileName, fsPath, userId, (error, fileRef, folder_id, path, fileStoreUrl) ->
 			newFiles = [
 				file: fileRef
-				path: result?.path?.fileSystem
+				path: path
 				url: fileStoreUrl
 			]
 			DocumentUpdaterHandler.updateProjectStructure project_id, userId, {newFiles}, (error) ->


Change compiler

Change compiler

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -29,10 +29,10 @@
         'source/core/*.coffee'
         'source/class/*.coffee']
       app: [
-        'extensions/app/source/*.coffee'
-        'extensions/app/source/atom/*.coffee'
-        'extensions/app/source/molecule/*.coffee'
-        'extensions/app/source/organism/*.coffee']
+        'extensions/app/*.coffee'
+        'extensions/app/atom/*.coffee'
+        'extensions/app/molecule/*.coffee'
+        'extensions/app/organism/*.coffee']
       spec  : [
         'spec/*.coffee']
       example:


Compress icons

Compress icons

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -101,7 +101,7 @@
         options: compress: false, import: [ '__init']
         files: '<%=meta.bower%>/<%=pkg.name%>.app.theme.css': '<%=source.stylus.theme%>'
       icons:
-        options: compress: false
+        options: compress: true
         files: '<%=meta.extensions%>/icons/<%=pkg.name%>.icons.css': '<%=source.stylus.icons%>'


[master] login

[master] login

--- server/wechat/index.coffee
+++ server/wechat/index.coffee
@@ -2,8 +2,11 @@
 xml2js = require 'xml2js'
 Q = require 'q'
 utils = require '../utils'
+database = require '../database'

 logger = utils.logging.newConsoleLogger module.filename
+
+redisConnection = database.getConnection()

 authenticationHandler = (req, res, next) ->
   token = 'EastIndiaCompany'
@@ -44,8 +47,21 @@
   .done()

 rpcCall = (tag, payload, sendResponse) ->
-  if tag == 'GET'
+  if tag == 'echo'
     sendResponse "echo #{payload.request}"
+  else if tag == 'login'
+    if payload.request.match /[^0-9a-z]/i
+      sendResponse 'only letters and digits allowed'
+    else
+      Q.ninvoke redisConnection, 'hset', 'login', request.fromUser, payload.request
+      sendResponse 'OK'
+  else if tag == 'summary'
+    Q.ninvoke redisConnection, 'hget', 'login', request.fromUser
+    .then (id) ->
+      if not id
+        sendResponse 'please login first'
+      else
+        sendResponse "http://myap.ml/?id=#{id}"
   else
     sendResponse "invalid tag #{tag}"

@@ -53,14 +69,14 @@
   console.dir req.body
   Q.ninvoke xml2js, 'parseString', req.body
   .then (parsed) ->
-    toUser = parsed.xml['FromUserName'][0]
-    fromUser = parsed.xml['ToUserName'][0]
+    fromUser = parsed.xml['FromUserName'][0]
+    toUser = parsed.xml['ToUserName'][0]
     createTime = (new Date()).getTime() // 1000
     sendResponse = (content) ->
       ret = "
         <xml>
-          <ToUserName><![CDATA[#{toUser}]]></ToUserName>
-          <FromUserName><![CDATA[#{fromUser}]]></FromUserName>
+          <ToUserName><![CDATA[#{fromUser}]]></ToUserName>
+          <FromUserName><![CDATA[#{toUser}]]></FromUserName>
           <CreateTime>#{createTime}</CreateTime>
           <MsgType><![CDATA[text]]></MsgType>
           <Content><![CDATA[#{content}]]></Content>
@@ -77,8 +93,8 @@
       tag = content[..separatorIndex - 1]
       request = content[separatorIndex + 1..]
       payload =
-        toUser: fromUser
-        fromUser: toUser
+        toUser: toUser
+        fromUser: fromUser
         createTime: createTime
         request: request
       rpcCall tag, payload, sendResponse


Make cache worker off for dev

Make cache worker off for dev

--- config/main.dev.coffee
+++ config/main.dev.coffee
@@ -85,7 +85,7 @@
     login       : 'prod-social'
     watch       : yes
     queueName   : socialQueueName+'cache'
-    run         : yes
+    run         : no
   feeder        :
     queueName   : "koding-feeder"
     exchangePrefix: "followable-"


User-importer: tidy up logs

User-importer: tidy up logs

--- scripts/user-updater/main.coffee
+++ scripts/user-updater/main.coffee
@@ -35,6 +35,9 @@

   # Helpers
   # -------
+
+  logError = (err, index)->
+    console.log "ERROR on #{index}. >", err  if err?

   iterate = (cursor, func, index, callback)->
     cursor.nextObject (err, obj)->
@@ -129,18 +132,18 @@
     fetchDamn referral, (err, res) ->

       if err
-        console.log "ERROR>", err
+        logError err, index
         return callback null

       {referrer, referred} = res

       createMissingReward referral, {referrer, referred}, (err)->
-        console.log "ERROR>", err  if err
+        logError err, index

         [referrer, referred] = [referred, referrer]

         createMissingReward referral, {referrer, referred}, (err)->
-          console.log "ERROR>", err  if err
+          logError err, index

           callback null


initial commit

initial commit

--- assets/data.coffee
+++ assets/data.coffee
@@ -7,7 +7,7 @@
         links:
           github:"https://github.com/morrow/instacss"
           mdn:"https://developer.mozilla.org/en/CSS"
-          'a(href="/html/partial/all.html" class="source")':'Full HTML'
+          'a(href="/html/partial.html" class="source")':'Full HTML'
       sidepane:
         search:
           history:''


addition to collection functions to allow distinct counts of unique values easily

--- server/api/lib/collection.coffee
+++ server/api/lib/collection.coffee
@@ -159,8 +159,24 @@
       res = this.search qy
   return res.hits.total

-API.collection.prototype.count = (q) ->
-  return this.search(q).hits.total
+API.collection.prototype.count = (q,key) ->
+  if key?
+    # TODO could check for a hash mapping for the key, and if available use that instead
+    # is there any benefit to default to doing on the exacts rather than the inexact?
+    # https://www.elastic.co/guide/en/elasticsearch/guide/1.x/cardinality.html
+    qy = API.collection._translate(q)
+    qy.aggs = {
+      "keycard" : {
+        "cardinality" : {
+          "field" : key,
+          "precision_threshold": 40000 # this is high precision and will be very memory-expensive in high cardinality keys, with lots of different values going in to memory
+        }
+      }
+    }
+    res = API.es.call 'POST', this._route + '/_search', qy
+    return res.aggregations.keycard.value
+  else
+    return this.search(q).hits.total

 API.collection.prototype.terms = (key, size=100, counts=false, q) ->
   key = key.replace('.exact','')+'.exact' if true # TODO should check the mapping to see if .exact is relevant for key, and have a way for user to decide
@@ -336,6 +352,10 @@
           qry.query.filtered.filter.bool.must.push tobj
         else if typeof q[y] in ['number','boolean']
           qry.query.filtered.query.bool.must.push {query_string:{query:y + ':' + q[y]}}
+        else if typeof q[y] is 'object'
+          qobj = {}
+          qobj[y] = q[y]
+          qry.query.filtered.filter.bool.must.push qobj
         else if q[y]?
           qry.query.filtered.filter.bool.must.push q[y]
   else if typeof q is 'string'


changed ordering on static assets path, just tidying.

--- services/web/app/coffee/infrastructure/ExpressLocals.coffee
+++ services/web/app/coffee/infrastructure/ExpressLocals.coffee
@@ -10,6 +10,10 @@

 fingerprints = {}
 Path = require 'path'
+
+
+imgPath = "/img/"
+cssPath = "/stylesheets/"
 jsPath =
 	if Settings.useMinifiedJs
 		"/minjs/"
@@ -17,14 +21,6 @@
 		"/js/"

-imgPath = "/img/"
-cssPath = "/stylesheets/"
-
-if Settings.cdn?.web?.host?
-	jsPath = "#{Settings.cdn?.web?.host}#{jsPath}"
-	imgPath = "#{Settings.cdn?.web?.host}#{imgPath}"
-	cssPath = "#{Settings.cdn?.web?.host}#{cssPath}"
-
 logger.log "Generating file fingerprints..."
 for path in [
 	"#{jsPath}libs/require.js",
@@ -46,7 +42,14 @@
 		fingerprints[path] = hash
 	else
 		logger.log filePath:filePath, "file does not exist for fingerprints"
-
+
+logger.log "Finished generating file fingerprints"
+
+if Settings.cdn?.web?.host?
+	jsPath = "#{Settings.cdn?.web?.host}#{jsPath}"
+	imgPath = "#{Settings.cdn?.web?.host}#{imgPath}"
+	cssPath = "#{Settings.cdn?.web?.host}#{cssPath}"
+

 module.exports = (app, webRouter, apiRouter)->
 	webRouter.use (req, res, next)->


Fix error reporting in the weboob controller + fix dev mode.

--- server/controllers/weboob.coffee
+++ server/controllers/weboob.coffee
@@ -2,16 +2,23 @@
 h = require './helpers'

 module.exports.status = (req, res) ->
+    # Short-circuit, in dev mode:
+    if not process.env.NODE_ENV? or process.env.NODE_ENV is 'development'
+        res.send 200,
+            isInstalled: true
+            log: 'no log'
+        return
+
     Config.byName 'weboob-installed', (err, pair) ->
         if err?
-            h.sendErr res
+            h.sendErr err
             return

         isInstalled = pair? and pair.value == 'true'

         Config.byName 'weboob-log', (err, pair) ->
             if err?
-                h.sendErr res
+                h.sendErr err
                 return

             log = if not pair then 'no log' else pair.value


Update results always after submitSubmits

--- server/cron/submitSubmits.coffee
+++ server/cron/submitSubmits.coffee
@@ -59,6 +59,9 @@
         registeredUser = await RegisteredUser.findByKey(submit.user)
         await testSystem.submitWithObject(registeredUser, submit.problem, {source: submit.sourceRaw, language: getLanguage(submit.language)})
         await Submit.remove({_id: submit._id})
+        dirtyResults = {}
+        await setDirty(submit, dirtyResults, {})
+        await User.updateUser(submit.user, dirtyResults)
         logger.info "Successfully submitted pending submit #{submit.user} #{submit.problem} attempt #{submitProcess.attempts}"
     catch e
         logger.info "Can not submit pending submit #{submit.user} #{submit.problem} attempt #{submitProcess.attempts}"


Add  "permanent-delete" package to Atom

Add  "permanent-delete" package to Atom

Because Atom can't delete a file without OS trash. :(

--- atom/packages.cson
+++ atom/packages.cson
@@ -23,6 +23,7 @@
   "minimap"
   "omni-ruler"
   "package-sync"
+  "permanent-delete"
   "pigments"
   "platformio-ide-terminal"
   "pretty-json"


Add 'deep equal' assertion snippets

Add 'deep equal' assertion snippets

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -271,6 +271,14 @@
     'prefix': 'nottoequal'
     'body': 'not.to.equal($1);$2'

+  'to deep equal':
+    'prefix': 'todeepequal'
+    'body': 'to.deep.equal($1);$2'
+
+  'not to deep equal':
+    'prefix': 'nottodeepequal'
+    'body': 'not.to.deep.equal($1);$2'
+
   'to have length':
     'prefix': 'tohavelen'
     'body': 'to.have.length($1);$2'


Add new PHP inline snippets; rename some prefixes

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -242,7 +242,7 @@
 '.source.php':

   'echo':
-    'prefix': 'e'
+    'prefix': 'echo'
     'body': "echo $1;"

   'print_r':
@@ -289,6 +289,10 @@
     'prefix': 'def'
     'body': "define('$1', ${2:''});"

+  'define constant conditionally':
+    'prefix': 'ifndef'
+    'body': "if ( ! defined( '$1' ) ) {\n\tdefine( '$1', ${2:false} );\n}"
+
   'display errors':
     'prefix': 'derr'
     'body': "ini_set('display_errors', '1');"
@@ -299,24 +303,36 @@

   'PHP Block (inline)':
     'prefix': 'php'
-    'body': "<?php $1 ?>"
+    'body': "<?php $1 ?>$2"

   'PHP Block (multiline)':
-    'prefix': 'phpb'
-    'body': "<?php\n\t$1\n?>"
+    'prefix': 'phpblock'
+    'body': "<?php\n\t$1\n?>$2"

   'echo (inline)':
-    'prefix': 'pe'
-    'body': "<?php echo $1; ?>"
+    'prefix': 'pecho'
+    'body': "<?php echo $1; ?>$2"

   'if (inline)':
     'prefix': 'pif'
-    'body': "<?php if ( ${1:condition} ): ?>\n\t$2\n<?php endif; ?>"
+    'body': "<?php if ( ${1:condition} ): ?>$2"
+
+  'else (inline)':
+    'prefix': 'pelse'
+    'body': "<?php else$1: ?>$2"
+
+  'else (inline)':
+    'prefix': 'pelseif'
+    'body': "<?php elseif: ?>$2"

   'for (inline)':
     'prefix': 'pfor'
-    'body': "<?php for ( ${1:\$i} = ${2:0}; ${1:\$i} < ${3:end}; ${1:\$i} += ${4:1} ): ?>\n\t$5\n<?php endfor; ?>"
+    'body': "<?php for ( ${1:\$i} = ${2:0}; ${1:\$i} < ${3:end}; ${1:\$i} += ${4:1} ): ?>$5"

   'foreach (inline)':
     'prefix': 'pforeach'
-    'body': "<?php foreach ( ${1:object} as ${2:value} ): ?>\n\t$3 \n<?php endforeach; ?>"
+    'body': "<?php foreach ( ${1:\$object} as ${2:\$value} ): ?>$3"
+
+  'end block (inline)':
+    'prefix': 'pend'
+    'body': "<?php end$1; ?>$2"


Fix _.string to _.str on recent server trim check

--- server/middleware/contact.coffee
+++ server/middleware/contact.coffee
@@ -11,7 +11,7 @@
       email_id: sendwithus.templates.coppa_deny_parent_signup
       recipient:
         address: req.body.parentEmail
-    if /@codecombat.com/.test(context.recipient.address) or not _.string.trim context.recipient.address
+    if /@codecombat.com/.test(context.recipient.address) or not _.str.trim context.recipient.address
       console.error "Somehow sent an email with bogus recipient? #{context.recipient.address}"
       return next(new errors.InternalServerError("Error sending email. Need a valid recipient."))
     sendwithus.api.send context, (err, result) ->


STL/fx close #18 slide out fx fix

STL/fx close #18 slide out fx fix

--- stl/fx/src/fx/slide.coffee
+++ stl/fx/src/fx/slide.coffee
@@ -46,6 +46,11 @@
   if how is 'out'
     style[if vertical then 'height' else 'width'] = '0px'

+    if vertical
+      element_style.height = size.y + 'px'
+    else
+      element_style.width = size.y + 'px'
+
     if to_right
       style.marginLeft = margin_left + size.x+'px'
     else if to_bottom


wallapatta print css

wallapatta print css

--- js/static.coffee
+++ js/static.coffee
@@ -1,6 +1,11 @@
 Mod.require 'Wallapatta.Parser', (Parser) ->
  RATIO = 0
  PAGE_HEIGHT = PAGE_WIDTH = 0
+
+ if (window.location.href.indexOf 'print') isnt -1
+  PRINT = true
+ else
+  PRINT = false

  renderWeb = (render) ->
   render.mediaLoaded ->
@@ -42,7 +47,7 @@
    main: main
    sidebar: sidebar
   window.requestAnimationFrame ->
-   if (window.location.href.indexOf 'print') isnt -1
+   if PRINT
     renderPrint render
    else
     renderWeb render
@@ -51,8 +56,6 @@
   docs = document.getElementsByClassName 'wallapatta'
   for doc, i in docs
    process i, doc
-
- PRINT = true

  if PRINT
   docs = document.getElementsByClassName 'wallapatta-container'


spec: Window state events

spec: Window state events

--- spec/api-browser-window-spec.coffee
+++ spec/api-browser-window-spec.coffee
@@ -183,3 +183,24 @@
         assert.equal frameName, 'target'
         done()
       w.loadUrl "file://#{fixtures}/pages/target-name.html"
+
+  describe 'maximize event', ->
+    it 'emits when window is maximized', (done) ->
+      w.once 'maximize', -> done()
+      w.maximize()
+
+  describe 'unmaximize event', ->
+    it 'emits when window is unmaximized', (done) ->
+      w.once 'unmaximize', -> done()
+      w.unmaximize()
+
+  describe 'minimize event', ->
+    it 'emits when window is minimized', (done) ->
+      w.once 'minimize', -> done()
+      w.minimize()
+
+  describe 'restore event', ->
+    it 'emits when window is restored', (done) ->
+      w.once 'restore', -> done()
+      w.minimize()
+      w.restore()


Replace invisible chars (space, tab, newline) w/ visible chars in specs

--- spec/beautify-languages-spec.coffee
+++ spec/beautify-languages-spec.coffee
@@ -152,7 +152,18 @@

                         beautifyCompleted = false
                         completionFun = (text) ->
-                          console.log(text, expectedContents)
+                          # Replace Newlines
+                          text = text.replace(/(?:\r\n|\r|\n)/g, '⏎\n')
+                          expectedContents = expectedContents\
+                            .replace(/(?:\r\n|\r|\n)/g, '⏎\n')
+                          # Replace tabs
+                          text = text.replace(/(?:\t)/g, '↹')
+                          expectedContents = expectedContents\
+                            .replace(/(?:\t)/g, '↹')
+                          # Replace spaces
+                          text = text.replace(/(?:\ )/g, '␣')
+                          expectedContents = expectedContents\
+                            .replace(/(?:\ )/g, '␣')
                         #   logger.verbose(expectedTestPath, text) if ext is ".less"
                           expect(text instanceof Error).not.toEqual(true, text)
                           return beautifyCompleted = true if text instanceof Error


Remove debugger statement

Remove debugger statement

--- spec/unpoly/feedback_spec.js.coffee
+++ spec/unpoly/feedback_spec.js.coffee
@@ -174,7 +174,6 @@
         nav = fixture('div[up-nav]')
         link = e.affix(nav, 'a[href="/foo"]')
         up.hello(link)
-        debugger
         expect(link).toHaveClass('up-current')

       describe 'updating .up-current marks when the URL changes', ->


Cleaner output

Cleaner output

--- spec/MicroFloPortalLights.coffee
+++ spec/MicroFloPortalLights.coffee
@@ -28,7 +28,8 @@
       runtime = new Runtime
       runtime.setDefinition def
       checkPorts = () ->
-        console.log runtime.inPorts
+        console.log Object.keys runtime.inPorts.ports
+        console.log Object.keys runtime.outPorts.ports
         chai.expect(runtime.inPorts.ports['pixel']).to.be.an 'object'
         chai.expect(runtime.inPorts.ports['show']).to.be.an 'object'
         chai.expect(runtime.outPorts.ports['shown']).to.be.an 'object'


:green_heart: Still got some failure due to nextAnimationFrame mock

--- spec/minimap-element-spec.coffee
+++ spec/minimap-element-spec.coffee
@@ -496,7 +496,7 @@
         it 'scrolls the editor gradually to the line below the mouse', ->
           # wait until all animations run out
           waitsFor ->
-            nextAnimationFrame()
+            nextAnimationFrame isnt noAnimationFrame and nextAnimationFrame()
             editor.getScrollTop() is 400

       describe 'dragging the visible area', ->


squirrel-update test on desktop shortcut groups too many assertions

Every test on desktop shortcut now tends to have 1 assertion. They have been rearranged to show more clearly what is the actual test intent. Expectation already checked somewhere else have been removed.

--- spec/squirrel-update-spec.coffee
+++ spec/squirrel-update-spec.coffee
@@ -5,25 +5,25 @@
 temp = require 'temp'
 SquirrelUpdate = require '../src/browser/squirrel-update'

-describe "Windows squirrel updates", ->
+describe "Windows Squirrel Update", ->
   tempHomeDirectory = null
+  originalSpawn = ChildProcess.spawn
+
+  harmlessSpawn = ->
+    # Just spawn something that won't actually modify the host
+    if process.platform is 'win32'
+      originalSpawn('dir')
+    else
+      originalSpawn('ls')

   beforeEach ->
-    # Prevent the actually home directory from being manipulated
+    # Prevent the actual home directory from being manipulated
     tempHomeDirectory = temp.mkdirSync('atom-temp-home-')
     spyOn(fs, 'getHomeDirectory').andReturn(tempHomeDirectory)

     # Prevent any commands from actually running and affecting the host
-    originalSpawn = ChildProcess.spawn
     spyOn(ChildProcess, 'spawn').andCallFake (command, args) ->
-      if path.basename(command) is 'Update.exe' and args?[0] is '--createShortcut'
-        fs.writeFileSync(path.join(tempHomeDirectory, 'Desktop', 'Atom.lnk'), '')
-
-      # Just spawn something that won't actually modify the host
-      if process.platform is 'win32'
-        originalSpawn('dir')
-      else
-        originalSpawn('ls')
+      harmlessSpawn()

   it "ignores errors spawning Squirrel", ->
     jasmine.unspy(ChildProcess, 'spawn')
@@ -67,28 +67,55 @@
     runs ->
       expect(SquirrelUpdate.handleStartupEvent(app, '--not-squirrel')).toBe false

-  it "keeps the desktop shortcut deleted on updates if it was previously deleted after install", ->
-    desktopShortcutPath = path.join(tempHomeDirectory, 'Desktop', 'Atom.lnk')
-    expect(fs.existsSync(desktopShortcutPath)).toBe false
-
-    app = quit: jasmine.createSpy('quit')
-    expect(SquirrelUpdate.handleStartupEvent(app, '--squirrel-install')).toBe true
-
-    waitsFor ->
-      app.quit.callCount is 1
-
-    runs ->
-      app.quit.reset()
-      expect(fs.existsSync(desktopShortcutPath)).toBe true
-      fs.removeSync(desktopShortcutPath)
+  describe "Desktop shortcut", ->
+    desktopShortcutPath = '/non/existing/path'
+
+    beforeEach ->
+      desktopShortcutPath = path.join(tempHomeDirectory, 'Desktop', 'Atom.lnk')
+
+      jasmine.unspy(ChildProcess, 'spawn')
+      spyOn(ChildProcess, 'spawn').andCallFake (command, args) ->
+        if path.basename(command) is 'Update.exe' and args?[0] is '--createShortcut'
+          fs.writeFileSync(path.join(tempHomeDirectory, 'Desktop', 'Atom.lnk'), '')
+          harmlessSpawn()
+        else
+          throw new Error("API not mocked")
+
+    it "does not exist before install", ->
       expect(fs.existsSync(desktopShortcutPath)).toBe false
-      expect(SquirrelUpdate.handleStartupEvent(app, '--squirrel-updated')).toBe true
-
-    waitsFor ->
-      app.quit.callCount is 1
-
-    runs ->
-      expect(fs.existsSync(desktopShortcutPath)).toBe false
+
+    describe "on install", ->
+      beforeEach ->
+        app = quit: jasmine.createSpy('quit')
+        SquirrelUpdate.handleStartupEvent(app, '--squirrel-install')
+        waitsFor ->
+          app.quit.callCount is 1
+
+      it "creates desktop shortcut", ->
+        expect(fs.existsSync(desktopShortcutPath)).toBe true
+
+      describe "when shortcut is deleted and then app is updated", ->
+        beforeEach ->
+          fs.removeSync(desktopShortcutPath)
+          expect(fs.existsSync(desktopShortcutPath)).toBe false
+
+          app = quit: jasmine.createSpy('quit')
+          SquirrelUpdate.handleStartupEvent(app, '--squirrel-updated')
+          waitsFor ->
+            app.quit.callCount is 1
+
+        it "does not recreate shortcut", ->
+          expect(fs.existsSync(desktopShortcutPath)).toBe false
+
+      describe "when shortcut is kept and app is updated", ->
+        beforeEach ->
+          app = quit: jasmine.createSpy('quit')
+          SquirrelUpdate.handleStartupEvent(app, '--squirrel-updated')
+          waitsFor ->
+            app.quit.callCount is 1
+
+        it "still has desktop shortcut", ->
+          expect(fs.existsSync(desktopShortcutPath)).toBe true

   describe ".restartAtom", ->
     it "quits the app and spawns a new one", ->


Troubleshoot: on "healthCheckStarted" event system status is rerendered

--- client/Main/avatararea/avatarareatroubleshootmodal.coffee
+++ client/Main/avatararea/avatarareatroubleshootmodal.coffee
@@ -111,6 +111,9 @@
       @loader.hide()
       @render()

+    KD.singleton("troubleshoot").on "healthCheckStarted", =>
+      @render()
+

   viewAppended: ->
     JView::viewAppended.call this


Updated test for new regex

Updated test for new regex

--- spec/supported-files-spec.coffee
+++ spec/supported-files-spec.coffee
@@ -67,7 +67,11 @@
   describe "when getting the regular expression for a valid file type", ->
     result = null
     expectedResult = ///
-      function \s+ TEST \s* \( | TEST \s* (=|:) \s* function \s* \(
+      function \s+ TEST \s* \(
+        |
+      TEST \s* (=|:) \s* function \s* \(
+        |
+      TEST \s* ([=:])? \s* \(? \s* \( ([\,\s\w]*)? \)? \s* =>
     ///

     beforeEach ->


Support legacy title-changed emitting

Support legacy title-changed emitting

--- lib/file-info-view.coffee
+++ lib/file-info-view.coffee
@@ -15,13 +15,21 @@
     @subscribeToActiveItem()

   subscribeToActiveItem: ->
-    activeItem = @getActiveItem()
+    @modifiedSubscription?.dispose()
+    @titleSubscription?.dispose()

-    @titleSubscription?.dispose()
-    @titleSubscription = activeItem?.onDidChangeTitle? => @update()
+    if activeItem = @getActiveItem()
+      @updateCallback ?= => @update()

-    @modifiedSubscription?.dispose()
-    @modifiedSubscription = activeItem?.onDidChangeModified? => @update()
+      if typeof activeItem.onDidChangeTitle is 'function'
+        @titleSubscription = activeItem.onDidChangeTitle(@updateCallback)
+      else if typeof activeItem.on is 'function'
+        #TODO Remove once title-changed event support is removed
+        activeItem.on('title-changed', @updateCallback)
+        @titleSubscription = dispose: =>
+          activeItem.off?('title-changed', @updateCallback)
+
+      @modifiedSubscription = activeItem.onDidChangeModified?(@updateCallback)

     @update()


Fix construction of `Object`

Fix construction of `Object`

--- lib/Ripper.coffee
+++ lib/Ripper.coffee
@@ -51,7 +51,6 @@
     )

   @find: ({ symbol, references }, targetLocationData) ->
-    # console.log '-------------------------------'
     target = @findSymbol symbol, targetLocationData
     return [] unless target?
     _.uniq @findReferences(references, target)[0]
@@ -111,8 +110,6 @@
       if child instanceof Code
         isContains = Ripper.isContains child, target
         isDeclared = Ripper.isDeclared child, target, parent
-        # console.log inspect { isDeclaredInParent, isDeclared, isContains }
-
         [ childDests, isChildFixed ] = Ripper.findReferences child, target, isDeclaredInParent or isDeclared

         if isContains
@@ -129,7 +126,6 @@
           if isDeclared
             return true
           if isDeclaredInParent
-            # console.log inspect childDests
             dests = dests.concat childDests
             return true
         return true
@@ -137,7 +133,7 @@
       child.scope = parent.scope

       # Skip object key access
-      if child.asKey and child.unfoldedSoak isnt false
+      if child.asKey and parent.soak is false and child.unfoldedSoak isnt false
         return true

       # Skip key in object literal
@@ -178,7 +174,7 @@
         o = bare: true
         root.compileRoot o
         root.scope = o.scope
-      variables = root.scope.declaredVariables()
+      variables = Ripper.declaredSymbols root.scope
       return variables.indexOf(target.value) isnt -1
     catch err
     false
@@ -195,21 +191,20 @@
         o =
           scope: parent?.scope
           indent: ''
-          bare: true
         code.compileNode o
         code.scope = o.scope
-      # variables = o.scope.declaredVariables()
-      variables = Ripper.declaredVariables code.scope
-      # console.log inspect code.scope.declaredVariables()
-      # console.log inspect variables
+      variables = Ripper.declaredSymbols code.scope
       return variables.indexOf(target.value) isnt -1
     catch err
+      console.error err
     false

-  @declaredVariables: (scope) ->
-    _.filter (_.pluck scope.variables, 'name'), (name) ->
-      _.isString(name) and name isnt 'arguments'
-
+  @declaredSymbols: (scope) ->
+    realVars = []
+    tempVars = []
+    for { name, type } in scope.variables when (type is 'var' or type is 'param') and _.isString name
+      (if name.charAt(0) is '_' then tempVars else realVars).push name
+    realVars.sort().concat tempVars.sort()

   ###


Greatly improve dynamic generation of comparators


adds support for checking option types

adds support for checking option types

--- lib/bebopt.coffee
+++ lib/bebopt.coffee
@@ -141,6 +141,10 @@
 # has no arg, then it causes bebopt to exit
 # non-zero and prints an error message.
   _optTypeError: (optName, optArg, nofDashes) =>
+    # XXX this function MUST be called after we've already
+    # taken the optargs passed in the `--OPTION ARG' fashion
+    # and smooshed them in their respective
+    # arg objects.
     [
       {
         dashes: 1,
@@ -164,12 +168,28 @@
         type = @[list.name][optName].type
         switch type
           when 'optarg'
+            @[list.name][optName].arg = optArg
+          when 'arg'
+            if optArg is undefined
+              err = "#{@app}: "
+              if name is '_short'
+                err += "option requires an argument -- '#{optName}'"
+              else
+                dashes = if name is '_half' then '-' else '--'
+                err += "option '#{dashes}#{optName}' requires an argument"
+              console.error(err)
+              process.exit(1)
+            else
+              @[list.name][optName].arg = optArg
+          when 'flag'
             if optArg isnt undefined
-              @[list.name][optName].arg = optArg
-
-
-      when 1
-        switch @_short
+              err = "#{@app}"
+              dashes = if name is '_half' then '-' else '--'
+              err += "option '#{dashes}#{optName}' doesn't allow an argument"
+              console.error(err)
+              process.exit(1)
+            else
+              @[list.name][optName].arg = true)

   _resolveOpts: () =>
     @_opts.forEach((elem, ind) =>
@@ -180,24 +200,6 @@
         when 1
           @_short(elem.arg)
     )
-        #parent = null
-        #switch len
-          #when 1
-            #opt = opt.replace(/^-/, '')
-            #if opt.length < 2 # short
-              #ref = @_short[opt]
-              #parent = 'short'
-            #else
-              #ref = @_half[opt]
-              #parent = 'half'
-          #when 2
-            #opt = opt.replace(/^--/, '')
-            #ref = @_long[opt]
-            #parent = 'long'
-        #console.log opt
-        #@_optError(parent, opt)
-      #else
-        #return ref)

   _log: (y) ->
     console.log(util.inspect(y, { colors: true, depth: null }))


:bug: Fix CountTotal in bottom

:bug: Fix CountTotal in bottom

--- lib/bottom.coffee
+++ lib/bottom.coffee
@@ -23,9 +23,8 @@
   update: (Messages)->
     CountTotal = Messages.length
     CountFile = Messages.filter( (Message) -> Message.CurrentFile).length
-    CountProject = CountTotal - CountFile
     @BarCurrent.Child.textContent = CountFile.toString()
-    @BarProject.Child.textContent = CountProject.toString()
+    @BarProject.Child.textContent = CountTotal.toString()
     if CountTotal
       @BarStatus.Root.classList.remove 'linter-success'
       @BarStatus.Root.classList.add 'linter-error'


fix for exists

fix for exists

--- lib/clever.coffee
+++ lib/clever.coffee
@@ -60,8 +60,8 @@
         val = true
       else if arguments.length is 1
         if _(path).isBoolean()
+          val = path
           path = @_curr_path
-          val = true
         else
           val = true
       path_conds = @_conditions[path] or (@_conditions[path] = {})


refactor obtaining of sync tokens

refactor obtaining of sync tokens

--- lib/clever.coffee
+++ lib/clever.coffee
@@ -6,6 +6,8 @@
 QueryStream = require "#{__dirname}/querystream"
 _.mixin(require 'underscore.deep')

+API_BASE = 'https://api.clever.com'
+
 handle_errors = (resp, body, cb) ->
   return cb null, resp, body if resp.statusCode is 200
   err = new Error "received statusCode #{resp.statusCode} instead of 200"
@@ -20,7 +22,7 @@
     http_opts.headers ?= {}
     _(http_opts.headers).extend Authorization: "Bearer #{auth.token}"

-Clever = module.exports = (auth, url_base='https://api.clever.com', options={}) ->
+Clever = module.exports = (auth, url_base=API_BASE, options={}) ->
   throw new Error 'Must provide auth' if not auth
   auth = {api_key: auth} if _.isString auth
   clever =
@@ -266,35 +268,36 @@
 module.exports.handle_errors = handle_errors
 Clever.handle_errors = handle_errors

-Clever.Token = class Token
-  @url_base: 'https://clever.com'
-  @api_base: 'https://api.clever.com'
-  @path: '/oauth/tokens'
-
-  @find: (client_id, client_secret, owner_type..., cb) ->
-    owner_type = owner_type[0]
-    owner_type ?= 'district'
-    opts =
-      method: 'get'
-      ca: certs
-      json: true
-      auth: "#{client_id}:#{client_secret}"
-      uri: "#{@url_base}#{@path}?owner_type=#{owner_type}"
-    quest opts, (err, resp, body) ->
-      handle_errors resp, body, (err, resp, body) ->
-        return cb?(err) if err
-        cb?(err, body?.data)
-
-  @details: (token, cb) ->
-    auth = {token: token} if _.isString(token)
-    auth ?= {token: token.access_token}
-    opts =
-      method: 'get'
-      ca: certs
-      json: true
-      uri: "#{@api_base}/me"
-    apply_auth auth, opts
-    quest opts, (err, resp, body) ->
-      handle_errors resp, body, (err, resp, body) ->
-        return cb?(err) if err
-        cb?(err, body?.data)
+Clever.me = (token, url_base..., cb) ->
+  url_base = url_base[0]
+  url_base ?= API_BASE
+  auth = {token: token} if _.isString(token)
+  auth ?= {token: token.access_token}
+  opts =
+    method: 'get'
+    ca: certs
+    json: true
+    uri: "#{url_base}/me"
+  apply_auth auth, opts
+  quest opts, (err, resp, body) ->
+    handle_errors resp, body, (err, resp, body) ->
+      return cb?(err) if err
+      cb?(err, body?.data)
+
+Clever.oauth_tokens = (client_id, client_secret, optional..., cb) ->
+  owner_type = optional?.owner_type
+  owner_type ?= 'district'
+  url_base = options?.url_base
+  url_base ?= 'https://clever.com'
+  path = optional?.path
+  path ?= '/oauth/tokens'
+  opts =
+    method: 'get'
+    ca: certs
+    json: true
+    auth: "#{client_id}:#{client_secret}"
+    uri: "#{url_base}#{path}?owner_type=#{owner_type}"
+  quest opts, (err, resp, body) ->
+    handle_errors resp, body, (err, resp, body) ->
+      return cb?(err) if err
+      cb?(err, body?.data)


OVirtApiNode: move defaults to prototype.

--- src/ApiNodes/OVirtApiNode.coffee
+++ src/ApiNodes/OVirtApiNode.coffee
@@ -58,6 +58,15 @@
     '$resourceLinks'
   ]

+  # Defaults
+  _$actions: {}
+  _$attributes: {}
+  _$collections: {}
+  _$connection: null
+  _$owner: null
+  _$properties: {}
+  _$resourceLinks: {}
+
   #
   # @property [Object<ApiNodes.OVirtAction>]
   #   actions that belongs to current API node
@@ -152,15 +161,6 @@
     @_$resourceLinks = resourceLinks

   constructor: (options = {}) ->
-    # Set instance defaults
-    @_$actions = {}
-    @_$attributes = {}
-    @_$collections = {}
-    @_$connection = null
-    @_$owner = null
-    @_$properties = {}
-    @_$resourceLinks = {}
-
     # Setup properties
     @setupExistedProperties options


Separate archive-specific CSS into archive.css (shared by all files)

--- imports/download.coffee
+++ imports/download.coffee
@@ -20,6 +20,19 @@
 files = {}
 internalFiles = {}
 subthreads = false
+
+downloadAux = (zip, group) ->
+  ## Hide interactive buttons
+  zip.file "#{group}/css/archive.css", """
+    .group-right-buttons,
+    .superButton, .usersButton, .settingsButton, .searchText,
+    .rawButton, .historyButton, .editButton, .replaceButton, .actionButton,
+    .message-reply-buttons, .emojiButtons,
+    .dropdown-toggle .caret, .dropdown-menu,
+    .pdfButtons, .pdfStatus, .pdfBox,
+    .foldButton, #{if subthreads then '' else '.focusButton,'}
+    .footer { display: none !important }
+  """

 savePage = (zip, group, filename) ->
   pageUrl = document.URL
@@ -123,19 +136,10 @@
   ## Remove scripts
   html = html.replace //////g, ''
   ## Hide interactive buttons
-  html = html.replace //////i, (match) -> """
-
-      #{match}
-    """
+  html = html.replace //////i, """
+    <link rel="stylesheet" href="css/archive.css">
+    $&
+  """
   ## Link to original
   html = """
     <!DOCTYPE html>
@@ -154,6 +158,7 @@
   internalFiles = {}
   JSZip = (await import('jszip')).default
   zip = new JSZip
+  downloadAux zip, group
   ## Group page
   await untilReady()
   console.assert Router.current().route.getName() == 'group',


Reorganized the specs, moving generally applicable specificatinos into an 'any draggable' section.

--- spec/draggable_spec.coffee
+++ spec/draggable_spec.coffee
@@ -41,16 +41,57 @@
       it 'should possess the supplied dragging class', ->
         expect(@$draggable).toHaveClass options.alternateDraggingClass

+  describe 'any draggable', ->
+
+    beforeEach ->
+      loadFixtures 'draggable.html'
+      @$draggable = $('#draggable').draggable()
+
+    describe 'when clicked on', ->
+
+      beforeEach ->
+        spyOnEvent @$draggable, 'mousedown'
+        SpecHelper.mouseDownInCenterOf @$draggable
+
+      it 'should not possess the default dragging class', ->
+        expect(@$draggable).not.toHaveClass $.draggable::defaults['draggingClass']
+
+      it 'should capture the mousedown event', ->
+        expect('mousedown').toHaveBeenPreventedOn(@$draggable)
+
+    describe 'while in mid-drag', ->
+
+      beforeEach ->
+        center = SpecHelper.mouseDownInCenterOf @$draggable
+
+        # Move it by the prescribed amount, without lifting the mouse button
+        $(document).simulate 'mousemove',
+          clientX: center.x + options.dragDistance
+          clientY: center.y + options.dragDistance
+
+      it 'should possess the default dragging class', ->
+        expect(@$draggable).toHaveClass $.draggable::defaults['draggingClass']
+
+    describe 'after having been dragged', ->
+
+      beforeEach ->
+        # Drag the draggable a standard distance
+        @$draggable.simulate 'drag',
+          moves: 1
+          dx: options.dragDistance
+          dy: options.dragDistance
+
+      it 'should not possess the default dragging class', ->
+        expect(@$draggable).not.toHaveClass $.draggable::defaults['draggingClass']
+
   describe 'a statically positioned draggable', ->

     beforeEach ->
       loadFixtures 'draggable.html'
       @$draggable = $('#draggable').draggable()

-    it 'should be positioned statically', ->
-      expect(@$draggable).toHaveCss { position: 'static' }
+    describe 'when clicked on', ->

-    describe 'when clicked on', ->
       beforeEach ->
         spyOnEvent @$draggable, 'mousedown'
         SpecHelper.mouseDownInCenterOf @$draggable
@@ -58,13 +99,8 @@
       it 'should be positioned statically', ->
         expect(@$draggable).toHaveCss { position: 'static' }

-      it 'should not possess the default dragging class', ->
-        expect(@$draggable).not.toHaveClass $.draggable::defaults['draggingClass']
+    describe 'while in mid-drag', ->

-      it 'should capture the click event', ->
-        expect('mousedown').toHaveBeenPreventedOn(@$draggable)
-
-    describe 'while in mid-drag', ->
       beforeEach ->
         center = SpecHelper.mouseDownInCenterOf @$draggable

@@ -76,10 +112,8 @@
       it 'should be positioned relatively', ->
         expect(@$draggable).toHaveCss { position: 'relative' }

-      it 'should possess the default dragging class', ->
-        expect(@$draggable).toHaveClass $.draggable::defaults['draggingClass']
+    describe 'after having been dragged', ->

-    describe 'after having been dragged a standard amount', ->
       beforeEach ->
         # Drag the draggable a standard distance
         @$draggable.simulate 'drag',
@@ -90,9 +124,9 @@
       it 'should be positioned relatively', ->
         expect(@$draggable).toHaveCss { position: 'relative' }

-      it 'should find itself a standard distance from its original top', ->
+      it 'should find itself the drag distance from its original top', ->
         expect(@$draggable).toHaveCss { top: "#{options.dragDistance}px" }

-      it 'should find itself a standard distance from its original left', ->
+      it 'should find itself the drag distance from its original left', ->
         expect(@$draggable).toHaveCss { left: "#{options.dragDistance}px" }


[Bug fix] Protected files in / cause error

On windows when typing / or \ the fs.statSync fails due to the file c:/hiberfil.sys, or others similar, not being readable.
This causes a fatal error.
Putting in this try catch statement allows for this to go wrong and will just move on regardless
Fix for issue #13

--- lib/paths-provider.coffee
+++ lib/paths-provider.coffee
@@ -47,7 +47,10 @@
       resultPath = path.resolve directory, result

       # Check for type
-      stat = fs.statSync resultPath
+      try
+        stat = fs.statSync resultPath
+      catch e
+        continue
       if stat.isDirectory()
         label = "Dir"
         result += "/"


one more merge conflict fix

one more merge conflict fix

--- noddy/service/v2/find.coffee
+++ noddy/service/v2/find.coffee
@@ -213,12 +213,7 @@
     delete metadata.id
   options.url = options.url[0] if _.isArray options.url

-<<<<<<< HEAD
-  try _get_formatted_crossref(crossref) if typeof crossref is 'object'
-  try _get_formatted_europepmc(europepmc) if typeof europepmc is 'object'
-
-=======
->>>>>>> origin/develop
+
   metadata.doi ?= options.doi.replace('doi:','').replace('doi.org/','').trim() if typeof options.doi is 'string'
   metadata.title ?= options.title.trim() if typeof options.title is 'string'
   metadata.pmid ?= options.pmid if options.pmid


refactor(productImport): increase concurrency to 30

--- lib/product-import.coffee
+++ lib/product-import.coffee
@@ -150,7 +150,7 @@
           .then ({ body: { results } }) ->
             resolve(results)
           .catch (err) -> reject(err)
-      , { concurrency: 5 })
+      , { concurrency: 30 })
       .then (results) ->
         debug 'Fetched products: %j', results
         resolve(_.flatten(results))


getDetails -> getDetail

getDetails -> getDetail

--- spec/tree-view-spec.coffee
+++ spec/tree-view-spec.coffee
@@ -1414,7 +1414,7 @@
             expect(fs.existsSync(filePath)).toBeFalsy()

       describe "when pasting the files fails", ->
-        it "shows a notification", ->
+        fit "shows a notification", ->
           spyOn(fs, 'writeFileSync').andCallFake ->
             writeError = new Error("ENOENT: no such file or directory, open '#{filePath}'")
             writeError.code = 'ENOENT'
@@ -1428,7 +1428,7 @@
           atom.commands.dispatch(treeView.element, "tree-view:paste")

           expect(atom.notifications.getNotifications()[0].getMessage()).toContain 'Unable to paste paths'
-          expect(atom.notifications.getNotifications()[0].getDetails()).toContain 'ENOENT: no such file or directory'
+          expect(atom.notifications.getNotifications()[0].getDetail()).toContain 'ENOENT: no such file or directory'

     describe "tree-view:add-file", ->
       [addPanel, addDialog] = []


Start tracking stats before graph is opened

This allows the graph to open with data available
since the editor was launched.

--- spec/tree-view-spec.coffee
+++ spec/tree-view-spec.coffee
@@ -761,7 +761,7 @@

           expect(addDialog.miniEditor.getText().length).toBe 0

-    fdescribe "tree-view:move", ->
+    describe "tree-view:move", ->
       describe "when a file is selected", ->
         moveDialog = null


Bug fix for destroying many relationships

--- lib/relations/many.coffee
+++ lib/relations/many.coffee
@@ -11,8 +11,7 @@
   constructor: (@model_type, @key, options) ->
     @[key] = value for key, value of options
     @ids_accessor or= "#{inflection.singularize(@key)}_ids"
-    # @foreign_key = inflection.foreign_key(@as or @model_type.model_name) unless @foreign_key
-    @foreign_key = inflection.foreign_key(@model_type.model_name) unless @foreign_key
+    @foreign_key = inflection.foreign_key(@as or @model_type.model_name) unless @foreign_key
     @collection_type = Backbone.Collection unless @collection_type

   initialize: ->
@@ -257,28 +256,27 @@

   _clearRelation: (model, callback) ->
     (query = {})[@foreign_key] = model.attributes.id
+
+    # clear in memory collection
+    collection = @_ensureCollection(model)
+    related_model.set(@foreign_key, null) for related_model in _.clone(collection.models)
+
     if @reverse_relation.type is 'hasMany'
-      @join_table.destroy query, (err, json) =>
-        callback()
+      @join_table.destroy(query, callback)
     else
-      if (collection = @_ensureCollection(model))?.length
-        @_clearHasOneRelatedModels(collection.models, callback)
-      else
-        @cursor(model, @key).toModels (err, related_models) =>
-          return callback(err) if err
-          @_clearHasOneRelatedModels(related_models, callback)
+      @cursor(model, @key).toModels (err, related_models) =>
+        return callback(err) if err
+        @_clearHasOneRelatedModels(related_models, callback)

   _clearHasOneRelatedModels: (related_models, callback) ->
     return callback() unless related_models.length
     (update = {})[@foreign_key] = null
     queue = new Queue()
     for related_model in related_models
-      if related_model
-        do (related_model) =>
-          queue.defer (callback) =>
-            related_model.save update, Utils.bbCallback (err, saved_model) =>
-              callback(err) if err
-              if cache = @reverse_model_type.cache()
-                cache.set(saved_model.id, saved_model)
-              callback()
+      continue unless related_model
+      do (related_model) => queue.defer (callback) =>
+        related_model.save update, Utils.bbCallback (err, saved_model) =>
+          callback(err) if err
+          cache.set(saved_model.id, saved_model) if cache = @reverse_model_type.cache()
+          callback()
     queue.await callback


resolved problem with config rendering

resolved problem with config rendering

--- lib/config.coffee
+++ lib/config.coffee
@@ -170,17 +170,18 @@
       'array':(name,value)=>@parseArrayChild name,value,
       'color':(name,value)=>@parseColorChild  name,value
     }
-    console.log 'parsing child tab',name
+    console.log 'parsing child tab',name,level
     if not value.enum?
-      parsers[value.type] name,value
-    else
-      @parseEnumChild name,value
-
-  makeTabs:(name,obj,level)->
+      parsers[value.type] name,value,level+1
+    else
+      @parseEnumChild name,value,level+1
+
+  makeTabs:(name,obj)->
     cleanName = @getChildCleanName name,obj
     props = obj.properties
     tabs = Object.keys(props)
     console.log 'tabs',tabs
+    level = 0
     html = "
"
     index = 0
     for tab in tabs
@@ -194,13 +195,14 @@
       do (key,value) =>
         console.log 'parsing tab content',key
         html += "
"
-        html += @parseTabChild key,value,level+1
+        html += @parseObjectChild key,value,1
         html += "
"
     html += "
"
     html

   parseObjectChild:(name,obj,level)->
-    console.log 'name,obj',name,obj
+    if !level? then level = 0
+    console.log 'parsing object child',name,obj,level
     if level > 10
       console.error 'too much levels... :/'
       throw new Error('something goes terribly wrong... I\'m going out of here')
@@ -212,7 +214,8 @@
       props = obj.properties
       for key,value of props
         do (key,value)=>
-          html += @parseTabChild key,value,level++
+          html += @parseTabChild key,value,level+1
+    html

   loadSettings:->
     @settings = {}


get config from sails.config instead of env vars

--- test/bootstrap.test.coffee
+++ test/bootstrap.test.coffee
@@ -3,32 +3,16 @@
 oauth2 = require 'oauth2_client'
 fs = require 'fs'
 config = JSON.parse fs.readFileSync './.sailsrc'
-assert = require 'assert'
-
-[
-  'USER_ID'
-  'USER_SECRET'
-  'PASS_CLIENT_ID'
-  'PASS_CLIENT_SECRET'
-  'TOKENURL'
-  'SCOPE'
-].map (name) ->
-  assert name of process.env, "process.env.#{name} not yet defined"

 before ->
-  user =
-    id: process.env.USER_ID.split(',')[0]
-    secret: process.env.USER_SECRET.split(',')[0]
-  client =
-    id: process.env.PASS_CLIENT_ID
-    secret: process.env.PASS_CLIENT_SECRET
-  scope = process.env.SCOPE.split ','
-  oauth2
-    .token process.env.TOKENURL, client, user, scope
+  Sails
+    .liftAsync config
+    .then ->
+      {url, client, user, scope} = sails.config.oauth2
+      oauth2
+        .token url.token, client, user, scope
     .then (token) ->
       global.token = token
-    .then ->
-      Sails.liftAsync config

 after ->
   Sails.lowerAsync()


probably fixes #12, but not the underlying issue

--- lib/ensime.coffee
+++ lib/ensime.coffee
@@ -194,17 +194,19 @@
     @ensimeServerPid?.kill()
     @ensimeServerPid = null

-    @messages.clear()
-    @messages.close()
+    @messages?.clear()
+    @messages?.close()
     @messages = null #GC now?

-    @statusbarView.destroy()
+    @statusbarView?.destroy()
     @statusbarView = null

     @deleteEditorControllers()

-    @client.destroy()
+    @client?.destroy()
     @client = null
+
+    #atom.packages.deactivatePackage('Ensime')


Pass current options to loadState

Pass current options to loadState

--- src/browser/atom-application.coffee
+++ src/browser/atom-application.coffee
@@ -87,7 +87,7 @@
     if options.pathsToOpen?.length > 0 or options.urlsToOpen?.length > 0 or options.test
       @openWithOptions(options)
     else
-      @loadState() or @openPath(options)
+      @loadState(options) or @openPath(options)

   openWithOptions: ({pathsToOpen, executedFrom, urlsToOpen, test, pidToKillWhenClosed, devMode, safeMode, newWindow, logFile, profileStartup, timeout}) ->
     if test
@@ -441,15 +441,15 @@
     if states.length > 0 or allowEmpty
       @storageFolder.store('application.json', states)

-  loadState: ->
+  loadState: (options) ->
     if (states = @storageFolder.load('application.json'))?.length > 0
       for state in states
-        @openWithOptions({
+        @openWithOptions(_.extend(options, {
           pathsToOpen: state.initialPaths
           urlsToOpen: []
           devMode: @devMode
           safeMode: @safeMode
-        })
+        }))
       true
     else
       false


Express timeout in seconds

Express timeout in seconds

--- src/browser/atom-application.coffee
+++ src/browser/atom-application.coffee
@@ -497,12 +497,12 @@
     if resourcePath isnt @resourcePath and not fs.existsSync(resourcePath)
       resourcePath = @resourcePath

-    timeout = Number.parseFloat(timeout)
-    unless Number.isNaN(timeout)
+    timeoutInSeconds = Number.parseFloat(timeout)
+    unless Number.isNaN(timeoutInSeconds)
       timeoutHandler = ->
-        console.log "The test suite has timed out because it has been running for more than #{timeout} minutes."
+        console.log "The test suite has timed out because it has been running for more than #{timeoutInSeconds} seconds."
         process.exit(124) # Use the same exit code as the UNIX timeout util.
-      setTimeout(timeoutHandler, timeout * 60 * 1000)
+      setTimeout(timeoutHandler, timeoutInSeconds * 1000)

     try
       windowInitializationScript = require.resolve(path.resolve(@devResourcePath, 'src', 'initialize-test-window'))


Add a little delay when there's nothing to load

Give the app time to register any handler to the locales:loaded event

--- lib/utilities/i18n.coffee
+++ lib/utilities/i18n.coffee
@@ -125,7 +125,10 @@
         App.channel.trigger 'locales:loaded'
     else
       # nothing to load
-      App.channel.trigger 'locales:loaded'
+      setTimeout(->
+        App.channel.trigger 'locales:loaded'
+      , 100)
+

   ###


Re-format method.

Re-format method.

--- src/coffee/orderstatusimport.coffee
+++ src/coffee/orderstatusimport.coffee
@@ -35,11 +35,9 @@

   _parcelExists: (order, trackingId) ->
     result = _.chain(order.shippingInfo.deliveries)
-    .map (delivery) ->
-      delivery.parcels
+    .map (delivery) -> delivery.parcels
     .flatten()
-    .find (parcel) ->
-      parcel.trackingData.trackingID is trackingId
+    .find (parcel) -> parcel.trackingData.trackingID is trackingId
     .some()
     .value()


Update drag/drop example.

Update drag/drop example.

--- examples/dragdrop.coffee
+++ examples/dragdrop.coffee
@@ -10,9 +10,10 @@
     src: (src) -> src != ''

 ImgView = Draggable View
+  # If you des
   model: Img
-  # Cheap to completely mutate on change, no need to describe how to render
-  # each model property.
+  # Cheap to completely mutate on change, no need to describe bindings, just
+  # re-render entire view on any model changes.
   render: ->
     @el[prop] = @model[prop] for prop in @model
   template: -> ''
@@ -23,15 +24,13 @@

 DropzoneView = Droppable View
   model: Dropzone
+  # Expensive to re-render entire view, so we'll declare bindings to describe
+  # how to re-render on model changes.
+  render: ->
+    for d in @model.drops
+      @el.appendChild d.el

-  # Render can either be a single function (when it's inexpensive) or a complex
-  # object describing initial state and all state changes of each property
-  render:
-    # Initial state.
-    drops: (drops) ->
-      for d in drops
-        @el.appendChild d.el
-
+  bindings:
     # Various state changes
     'drops:add': (d) ->
       @el.appendChild d.el
@@ -39,17 +38,34 @@
     'drops:remove': (d) ->
       @el.removeChild d.el

-  # Events update the model, but should not update the DOM (render does that).
+  # Events update the model, but should not update the DOM, that should be left
+  # to render/bindings.
   events:
     drop: (d) ->
       @model.drops.push d

+  # You should never mutate the DOM manually, just update the model and let
+  # render/bindings do the work!
+  removeDrop: (idx) ->
+    @model.drops.pop idx
+
+  addDrop: (d) ->
+    @model.drops.push d
+
   template: -> '

'

 # Create some dropzones
-dropzone1 = DropzoneView drops: [ImgView(), ImgView()]
+dropzone1 = DropzoneView
+  drops: [
+    ImgView src: 'image1.jpg',
+    ImgView src: 'image2.jpg',
+  ]
 dropzone2 = DropzoneView()

+# Render them
+document.body.appendChild dropzone1.render().el
+document.body.appendChild dropzone2.render().el
+
 # Programmatically move a drop
-drop = dropzone1.model.drops.pop()
-dropzone2.model.drops.push drop
+drop = dropzone1.removeDrop()
+dropzone2.addDrop drop


added tests

added tests

--- tests/api/basic/test.traversing.coffee
+++ tests/api/basic/test.traversing.coffee
@@ -45,6 +45,10 @@
       child = $('#lorem').children('span')
       child.should.have.length 1
       child[0].name.should.equal "span"
+
+      child = $('#fruits').children('.pear')
+      child.should.have.length 1
+      child.text().should.equal "Pear"

     'with selector (no results)' : ($) ->
       child = $('#lorem').children('em')


Fix coffeescript tab issues so Pages will render (#6972)

--- src/desktop/apps/page/routes.coffee
+++ src/desktop/apps/page/routes.coffee
@@ -12,6 +12,6 @@
     return res.redirect(301, "/consign")
   else
     return new Page(id: req.params.id).fetch
-    cache: true
-    success: (page) -> res.render 'template', page: page
-    error: res.backboneError
+      cache: true
+      success: (page) -> res.render 'template', page: page
+      error: res.backboneError


Fixed profileTests.coffee

Fixed profileTests.coffee

--- tests/mocha/client/profileTests.coffee
+++ tests/mocha/client/profileTests.coffee
@@ -11,20 +11,20 @@
       )

     it 'Attempt to update his profile info', (done) ->
-    Meteor.users.update(
+      Meteor.users.update(
         {_id:Meteor.users.findOne({username:'tester'})._id}
         {$set:
             firstName:"test firstName"
             lastName:"test lastName"
             age:30
         }
-      ),
-        try
-          updatedUser=Meteor.users.findOne({username:'tester'})
-          chai.assert.equal updatedUser.firstName, "test firstName"
-          done()
-        catch
-          done()
+      )
+      try
+        updatedUser=Meteor.users.findOne({username:'tester'})
+        chai.assert.equal updatedUser.firstName, "test firstName"
+        done()
+      catch
+        done()

     it 'Attempt to remove user from client and receive not permitted error', (done)->
       try


payment: update buttonPartial conditional

--- client/Main/payment/form.coffee
+++ client/Main/payment/form.coffee
@@ -90,13 +90,12 @@
       cssClass : 'success-msg hidden'
       partial  : ''

-    if @state.subscriptionState is 'expired'
-      buttonPartial = 'CONTINUE'
-    else
-      buttonPartial = switch operation
-        when  1 then 'UPGRADE YOUR PLAN'
-        when  0 then 'MAKE CHANGE'
-        when -1 then 'DOWNGRADE'
+    buttonPartial = if @state.subscriptionState is 'expired'
+    then 'CONTINUE'
+    else switch operation
+      when  1 then 'UPGRADE YOUR PLAN'
+      when  0 then 'MAKE CHANGE'
+      when -1 then 'DOWNGRADE'

     @submitButton = new KDButtonView
       style     : 'solid medium green'


Teamwork: Remove unused Facebook classes from TW includes.

--- client/Teamwork/includes.coffee
+++ client/Teamwork/includes.coffee
@@ -3,7 +3,7 @@
   # coffee block
   "Views/teamworktabview.coffee",
   "Views/teamworkmarkdownmodal.coffee",
-  "Views/facebookteamworkinstructionsmodal.coffee",
+  # "Views/facebookteamworkinstructionsmodal.coffee",
   "Views/teamworkinvitemodal.coffee",
   "Views/teamworkexportmodal.coffee",
   "Views/teamworksharemodal.coffee",
@@ -16,7 +16,7 @@
   "Views/teamworkavatartabview.coffee",
   "Views/playgroundteamwork.coffee",
   "Views/teamworkapp.coffee",
-  "Views/facebookteamwork.coffee",
+  # "Views/facebookteamwork.coffee",
   "AppView.coffee",
   "AppController.coffee",


builder: fix typo

builder: fix typo

--- client/builder/lib/index.coffee
+++ client/builder/lib/index.coffee
@@ -363,12 +363,12 @@
         timeEnd 'styles: kd: copy ' + outfile
         msg = 'copied kd.css to ' + outfile
         console.log msg
-        if opts.watchJs
+        if opts.watchCss
           notify 'styles', msg

         callback?()

-      if opts.watchJs and not watchingKd
+      if opts.watchCss and not watchingKd
         watchingKd = yes
         w = chokidar.watch kdCssFile, persistent: yes
         w.on 'change', copyKd


A proxy is only blank if it doesn't contain an id

--- client/lanes/models/Base.coffee
+++ client/lanes/models/Base.coffee
@@ -309,7 +309,7 @@
     isBlank: (name) ->
         return _.isEmpty(this.serialize()) if _.isUndefined(name)
         value = this.get(name)
-        _.isBlank(value) || value.isProxy
+        _.isBlank(value) || (value.isProxy && value.isNew())

     # sets the model from a user interaction


* Fixed select after filter issue

* Fixed select after filter issue

--- src/bootstrap-dual-select.coffee
+++ src/bootstrap-dual-select.coffee
@@ -138,8 +138,8 @@
     unless $unselectedSelect? and $selectedSelect?
       [$unselectedSelect, $selectedSelect] = getSelects($instance)
     $buttons.prop('disabled', yes)
-    unselectedOptionsCount = $unselectedSelect.children().size()
-    selectedOptionsCount = $selectedSelect.children().size()
+    unselectedOptionsCount = $unselectedSelect.find('option:visible').size()
+    selectedOptionsCount = $selectedSelect.find('option:visible').size()
     $instance.find('div[data-area="unselected"] .count').text unselectedOptionsCount
     $instance.find('div[data-area="selected"] .count').text selectedOptionsCount
     if unselectedOptionsCount > 0
@@ -155,11 +155,14 @@
       $selectedSelect.children().prop('selected', no)

   refreshSelectedOptions = ($select, $selectedSelect) ->
+    # Update orignal select values
     selectedValues = $selectedSelect.children().map (i, el) ->
        $(el).val()
     $select.children().prop('selected', no).filter((i, el) ->
       $(el).val() in selectedValues
     ).prop('selected', yes)
+    # Trigger original select change event
+    $select.trigger('change')

   # Listen events and do the actions
   addEventsListener = ($select, $instance, options) ->
@@ -204,18 +207,18 @@
         refreshSelectedOptions($select, $selectedSelect)
       'keyup input.filter': (evt) ->
         $el = $(evt.currentTarget)
-        $select = null
+        $instanceSelect = null
         refreshCount = ->
-          $el.parent().find('.count').text $select.find('option:visible').size()
+          $el.parent().find('.count').text $instanceSelect.find('option:visible').size()

         delay options.timeout, ->
           value = $el.val().trim().toLowerCase()
           area = $el.parents('.dual-select-container').data('area')
-          $select = $instance.find(selectors["#{area}Select"])
+          $instanceSelect = $instance.find(selectors["#{area}Select"])
           if value is ''
-            $select.children().show()
+            $instanceSelect.children().show()
             return refreshCount()
-          $select.children().hide().filter((i, option) ->
+          $instanceSelect.children().hide().filter((i, option) ->
             $option = $(option)
             $option.text().toLowerCase().indexOf(value) >= 0 or $option.val() is value
           ).show()