el._browser_* is now an object (instanceof BrowserState)

--- src/browser.coffee
+++ src/browser.coffee
@@ -3,6 +3,8 @@
   cancelable_and_retrivable_callbacks,
   removed } = require './util'
 { isArray } = Array
+
+SHARED = ['parent_done', 'insert', 'replace', 'done']

 EVENTS = [
     'add', 'end'
@@ -14,6 +16,29 @@
 defaultfn = {}
 EVENTS.forEach (e) ->
     defaultfn[e] = -> throw new Error "no specific fn for #{e} defined" # dummy
+
+
+class BrowserState
+    initialize: (prev) ->
+        @manip ?= cancelable_and_retrivable_callbacks()
+        @done  ?= deferred_callbacks()
+        @manip.reset()
+
+    mergeInto: (state) ->
+        for key in SHARED
+            state[key] ?= this[key]
+            this[key] = null
+        state
+
+    destroy: (opts) ->
+        @manip?.cancel()
+        @done?.reset()
+        if opts.soft
+            this[key] = null for key in SHARED
+        else
+            delete this[key] for key in SHARED
+            delete manip
+        this

 class BrowserAdapter
@@ -41,17 +66,17 @@
         do @listen
         # prefill builder with state
         @make(@builder) # create a dom object
-        @builder._browser_done = deferred_callbacks()
-        do @builder._browser_done.callback() # builder is allways done
+        (@builder._browser ?= new BrowserState).done = deferred_callbacks()
+        do @builder._browser.done.callback() # builder is allways done
         # register ready handler
         @template.register 'ready', (tag, next) ->
+            tag._browser ?= new BrowserState
             # when tag is already in the dom its fine,
             #  else wait until it is inserted into dom
-            if tag._browser_ready is yes
+            if tag._browser.ready is yes
                 next(tag)
             else
-                tag._browser_ready = ->
-                    next(tag)
+                tag._browser.ready = next
         return this

     use: (plugin) ->
@@ -72,71 +97,68 @@
         return if removed el
         @make(el) # create a dom object
         that = this
-        el._browser_manip    ?= cancelable_and_retrivable_callbacks()
-        el._browser_done     ?= deferred_callbacks()
+        (el._browser ?= new BrowserState).initialize()
         parent._browser_done ?= deferred_callbacks()
-        el._browser_manip.reset()
-        while (cb = el._browser_manip.callbacks.shift())?
+        while (cb = el._browser.manip.callbacks.shift())?
             @animation.push(cb)

-        ecb = el._browser_done.callback()
-        pcb = parent._browser_done.callback()
+        ecb = el._browser.done.callback()
+        pcb = parent._browser.done.callback()
         if el is el.builder then ecb() else el.ready(ecb)
         if parent is parent.builder then pcb() else parent.ready(pcb)

-        el._browser_insert ?= singlton_callback el, ->
+        el._browser.insert ?= singlton_callback el, ->
             return if removed this
             that.insert_callback(this)
-        el._browser_insert.replace?(el)
-
-        el._browser_parent_done ?= singlton_callback el, ->
+        el._browser.insert.replace?(el)
+
+        el._browser.parent_done ?= singlton_callback el, ->
             return if removed this
             that.parent_done_callback(this)
-        el._browser_parent_done.replace(el)
-        parent._browser_done(el._browser_parent_done)
+        el._browser.parent_done.replace(el)
+        parent._browser.done(el._browser.parent_done)

     onreplace: (oldtag, newtag) ->
         return if removed(oldtag) or removed(newtag)
-        newtag._browser_parent_done ?= oldtag._browser_parent_done
-        newtag._browser_insert      ?= oldtag._browser_insert
-        newtag._browser_done        ?= oldtag._browser_done
-        oldtag._browser_parent_done  = null
-        oldtag._browser_insert       = null
-        oldtag._browser_done         = null
+        newtag._browser ?= new BrowserState
+        oldtag._browser?.mergeInto(newtag._browser)

         @onadd(oldtag.parent, newtag)

-        oldtag._browser_manip?.cancel?()
-        newtag._browser_manip.reset()
+        oldtag._browser?.destroy(soft:yes)
+        newtag._browser.manip.reset()
         # if manip left from last time run them
-        while (cb = newtag._browser_manip.callbacks.shift())?
+        while (cb = newtag._browser.manip.callbacks.shift())?
             @animation.push(cb)

-        if newtag._browser_insert is true
+        if newtag._browser.insert is true
             that = this
-            newtag._browser_replace ?= oldtag._browser_replace
-            oldreplacerequest = newtag._browser_replace?
-            newtag._browser_replace ?= singlton_callback newtag, ->
+            newtag._browser.replace ?= oldtag._browser?.replace
+            oldreplacerequest = newtag._browser.replace?
+            newtag._browser.replace ?= singlton_callback newtag, ->
                 return if removed this
                 that.replace_callback(oldtag, this)
-            newtag._browser_replace.replace?(newtag)
-            oldtag._browser_replace = null
+            newtag._browser.replace.replace(newtag)
+            oldtag._browser?.replace = null
             unless oldreplacerequest
-                @animation.push(newtag._browser_replace)
+                @animation.push(newtag._browser.replace)

     ontext: (el, text) ->
-        el._browser_manip ?= cancelable_and_retrivable_callbacks(yes)
-        @animation.push el._browser_manip =>
+        (el._browser ?= new BrowserState).manip ?=
+            cancelable_and_retrivable_callbacks(yes)
+        @animation.push el._browser.manip =>
             @fn.text(el, text)

     onraw: (el, html) ->
-        el._browser_manip ?= cancelable_and_retrivable_callbacks(yes)
-        @animation.push el._browser_manip =>
+        (el._browser ?= new BrowserState).manip ?=
+            cancelable_and_retrivable_callbacks(yes)
+        @animation.push el._browser.manip =>
             @fn.raw(el, html)

     onattr: (el, key, value) ->
-        el._browser_manip ?= cancelable_and_retrivable_callbacks(yes)
-        @animation.push el._browser_manip =>
+        (el._browser ?= new BrowserState).manip ?=
+            cancelable_and_retrivable_callbacks(yes)
+        @animation.push el._browser.manip =>
             @fn.attr(el, key, value)

     onshow: (el) ->
@@ -147,35 +169,28 @@

     onremove: (el, opts) ->
         @fn.remove(el, opts)
-        el._browser_parent_done?.replace(null)
-        el._browser_done?.reset()
-        el._browser_manip?.cancel()
-        delete el._browser_parent_done
-        delete el._browser_replace
-        delete el._browser_insert
-        unless opts.soft
-            delete el._browser_manip
-            delete el._browser_done
+        el._browser?.destroy(opts)
+        delete el._browser unless opts.soft

     # ready callbacks

     insert_callback: (el) ->
         @fn.add(el.parent, el)
-        el._browser_ready?()
-        el._browser_ready = yes
-        el._browser_insert = yes
+        el._browser.ready?(el)
+        el._browser.ready = yes
+        el._browser.insert = yes

     parent_done_callback: (el) ->
         if el.parent is el.parent.builder
             bool = (not el.parent.parent? or
                        (el.parent.parent is el.parent.parent?.builder and # FIXME recursive?
-                        el.parent.parent?._browser_insert is true))
-            if bool and el.parent._browser_insert is true
-                @animation.push(el._browser_insert)
+                        el.parent.parent?._browser?.insert is true))
+            if bool and el.parent._browser?.insert is true
+                @animation.push(el._browser.insert)
             else
-                el._browser_insert?()
+                el._browser.insert?()
         else
-            @animation.push(el._browser_insert)
+            @animation.push(el._browser.insert)

     replace_callback: (oldtag, newtag) ->
         @fn.replace(oldtag, newtag)


reactivity: emoji selector: scroll to top fix

--- client/activity/lib/components/emojiselector/scrollablelist.coffee
+++ client/activity/lib/components/emojiselector/scrollablelist.coffee
@@ -96,7 +96,7 @@
     { items, sectionIndex, query } = @props

     index    = if query then 0 else sectionIndex
-    category = items.get(index).get 'category'
+    category = if index is -1 then '' else items.get(index).get 'category'

     {category}

@@ -107,7 +107,7 @@

       { @renderFixedCategoryHeader() }
-      <Scroller className="EmojiSelector-list Dropbox-resizable" ref='scroller' onScrollY={@bound 'onScroll'}>
+      <Scroller className="EmojiSelector-list Dropbox-resizable" ref='scroller' onScroll={@bound 'onScroll'}>
         <input className='EmojiSelector-searchInput' placeholder='Search' value={query} onChange={@bound 'onSearch'} />
         <List
           items            = { items }


Added more tests for delaunay.coffee.

Added more tests for delaunay.coffee.

--- spec/examples/delaunay_spec.coffee
+++ spec/examples/delaunay_spec.coffee
@@ -40,29 +40,55 @@
   t = triangulation()

   it "should produce an empty sequence of triangles", ->
-    expect(Sequence.empty t).toBe true
+    expect(Sequence.empty t).toBeTruthy()

-  it "should return anything on find", ->
-    expect(t.find 1, 2, 3).toBe undefined
+  it "shouldn't return anything on find", ->
+    expect(t.find 1, 2, 3).toBeUndefined()

   it "should produce a non-empty triangulation when a triangle is added", ->
-    expect(Sequence.empty t.plus 1, 2, 3).toBe false
+    expect(Sequence.empty t.plus 1, 2, 3).toBeFalsy()

 describe "A triangulation with one triangle", ->
-  t = triangulation([1,2,3])
+  t = triangulation([1, 2, 3])

   it "should not be empty", ->
-    expect(Sequence.empty t).toBe false
+    expect(Sequence.empty t).toBeFalsy()

   it "should have one triangle", ->
     expect(Sequence.size t).toBe 1
+
+  it "should find that triangle again", ->
+    expect(t.find(1, 2, 3).equals [1, 2, 3]).toBeTruthy()
+
+  it "should find that triangle with its vertices cyclically permuted", ->
+    expect(t.find(2, 3, 1).equals [1, 2, 3]).toBeTruthy()
+    expect(t.find(3, 1, 2).equals [1, 2, 3]).toBeTruthy()
+
+  it "should not find the same triangle when the orientation is reversed", ->
+    expect(t.find 1, 3, 2).toBeUndefined()
+    expect(t.find 3, 2, 1).toBeUndefined()
+    expect(t.find 2, 1, 3).toBeUndefined()
+
+  it "should find the three edges included in that triangle", ->
+    expect(t.find(1, 2).equals [1, 2, 3]).toBeTruthy()
+    expect(t.find(2, 3).equals [1, 2, 3]).toBeTruthy()
+    expect(t.find(3, 1).equals [1, 2, 3]).toBeTruthy()
+
+  it "should not find the edges in that triangle with reversed orientation", ->
+    expect(t.find 1, 3).toBeUndefined()
+    expect(t.find 3, 2).toBeUndefined()
+    expect(t.find 2, 1).toBeUndefined()
+
+  it "should not find unrelated triangles or edges", ->
+    expect(t.find 1, 2, 3.01).toBeUndefined()
+    expect(t.find 'a', 'b').toBeUndefined()

   it "should produce a sequence containing only the original triangle", ->
     expect(t.toSeq().toString()).toEqual "((1, 2, 3))"

   it "should return something when find is called with the original triangle", ->
-    expect(t.find 1, 2, 3).toNotBe undefined
+    expect(t.find 1, 2, 3).toBeDefined()

   it "should not change when we add the same triangle again", ->
     expect(t.plus 1, 2, 3).toEqual t


fixed "show more" logic

fixed "show more" logic

--- client/landing/app/CommonViews/opinions/opinionlistitemview.coffee
+++ client/landing/app/CommonViews/opinions/opinionlistitemview.coffee
@@ -131,7 +131,7 @@
     maxHeight = 300

     if @markup.height()>maxHeight
-      @textMaxHeight = @getHeight()
+      @textMaxHeight = @markup.height()
       @markup.css {maxHeight}
       @larger.show()


error fields always present, even when empty

--- app/lib/analytics.coffee
+++ app/lib/analytics.coffee
@@ -13,6 +13,8 @@
   eventData['type'] = type
   eventData['relatedID'] = related_id
   eventData['experiment'] = Experiments.currentExperiment
+  eventData['errorCode'] = ""
+  eventData['errorDescription'] = ""
   eventData['cohort'] = Experiments.currentCohorts[eventData['experiment']]
   if typeof eventData['cohort'] == 'undefined'
     cohortRetriever = Experiments.getCohortRetriever()


Fixed error box text for dialog

Fixed error box text for dialog

fixes CNVS-22107

Test Plan:
Compose a new message
Type some random text into the "to" field
enter other required fields and click submit
notice validation text is more informative

Change-Id: Idd751f42e3503ac9b243927398746c3781638875
Reviewed-on: https://gerrit.instructure.com/62905
Reviewed-by: Joel Hough <4816dabf8db1bc6cac35b3a24cab2ff844b5b0c7@instructure.com>
Tested-by: Jenkins
QA-Review: Adrian Russell <5867b55c34ec258a06bcc4d9b6dbda2c79313b40@instructure.com>
Product-Review: Steven Burnett <a0b450117c5d20a22d5c4d52bb8ae13adee003d8@instructure.com>

--- app/coffeescripts/views/conversations/MessageFormDialog.coffee
+++ app/coffeescripts/views/conversations/MessageFormDialog.coffee
@@ -278,7 +278,7 @@
         disableWhileLoading: true
         required: ['body']
         property_validations:
-          token_capture: => I18n.t('errors.field_is_required', "This field is required") if @recipientView and !@recipientView.tokens.length
+          token_capture: => I18n.t("Invalid recipient name.") if @recipientView and !@recipientView.tokens.length
         handle_files: (attachments, data) ->
           data.attachment_ids = (a.attachment.id for a in attachments)
           data


possible fix for #197

possible fix for #197

--- src/node_modules/bucket-item/bucket-item-link.coffee
+++ src/node_modules/bucket-item/bucket-item-link.coffee
@@ -61,8 +61,11 @@
             # HACK for firefox micro-blurring on focus
             scope.model.focusHack = true
             debug("focusing input", input)
-            input.select() if not input.is(':focus')
+            input.focus() if not input.is(':focus')
             scope.model.focusHack = false
+
+            # select input on next tick
+            $timeout -> input.select()

       saveContribution:
         value: (myContribution) ->


Slowly automating the release procedure

Slowly automating the release procedure

--- gulp.coffee
+++ gulp.coffee
@@ -191,3 +191,49 @@
 gulp.task('test', ['chained-test-examples'], (cb) ->
   cb()
 )
+
+gulp.task('dist', (taskCb) -> #['chained-complete'], (cb) ->
+  fs = require('fs')
+  git = require('git-cli')
+
+  cfgNpm = require('./package.json')
+  cfgBower = require('./bower.json')
+  cfgBower.version = cfgNpm.version
+
+  fs.writeFileSync('./bower.json', JSON.stringify(cfgBower, null, '  '))
+
+  git.Repository.init('./', (err, repo) ->
+    repo.add(['bower.json'], (err) ->
+      repo.add(
+        [
+          'dist/to-json.coffee',
+          'dist/to-json.node.js',
+          'dist/to-json.umd.js',
+          'dist/to-json.umd.min.js',
+          'dist/to-json.web.js',
+          'dist/to-json.web.min.js'
+        ],
+        { f: true },
+        (err) ->
+          repo.commit("Version #{cfgBower.version} for distribution", (err) ->
+            taskCb()
+          )
+      )
+    )
+  )
+
+
+
+  #console.log('To publish, run:');
+  #console.log('    git add bower.json');
+  #console.log('    git add -f ' + distConfig.debug);
+  #console.log('    git add -f ' + distConfig.min);
+  #console.log('    git checkout head');
+  #console.log('    git commit -m \'Version ' + version + ' for distribution\'');
+  #console.log('    git tag -a v' + version + ' -m \'Add tag v' + version + '\'');
+  #console.log('    git checkout master');
+  #console.log('    git push origin --tags');
+
+  #cb()
+  return
+)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/random.coffee
+++ test/random.coffee
@@ -16,6 +16,6 @@
   ok Random.angleBetween

   a = Random.angleBetween(0.25.turns, 0.5.turns)
-  ok a
+  ok a <

 module()


Remove build step which copied themes folder

--- tasks/build-task.coffee
+++ tasks/build-task.coffee
@@ -54,7 +54,6 @@
     cp 'spec', path.join(appDir, 'spec')
     cp 'src', path.join(appDir, 'src'), filter: /.+\.(cson|coffee|less)$/
     cp 'static', path.join(appDir, 'static'), filter: /.+\.less$/
-    cp 'themes', path.join(appDir, 'themes'), filter: /.+\.(cson|less)$/

     grunt.file.recurse path.join('resources', 'mac'), (sourcePath, rootDirectory, subDirectory='', filename) ->
       unless /.+\.plist/.test(sourcePath)


Cleaning up method, reducing indentations.

--- src/toaster/filetype/coffee/optimizer/vendors.coffee
+++ src/toaster/filetype/coffee/optimizer/vendors.coffee
@@ -33,23 +33,23 @@
     return unless @config.browser.vendors?

     for vname, vurl of @config.browser.vendors
-      unless /^http/m.test vurl
+      continue if /^http/m.test vurl

-        continue if all is false and vurl isnt specific
+      continue if all is false and vurl isnt specific

-        release_path = path.join @config.release_dir, "#{vname}.js"
-        fsu.cp vurl, release_path
+      release_path = path.join @config.release_dir, "#{vname}.js"
+      fsu.cp vurl, release_path

-        from = vurl.replace @toaster.basepath, ''
-        to = release_path.replace @toaster.basepath, ''
+      from = vurl.replace @toaster.basepath, ''
+      to = release_path.replace @toaster.basepath, ''

-        from = from.substr 1 if from[0] is path.sep
-        to = to.substr 1 if to[0] is path.sep
+      from = from.substr 1 if from[0] is path.sep
+      to = to.substr 1 if to[0] is path.sep

-        # date for CLI notifications
-        now = ("#{new Date}".match /[0-9]{2}\:[0-9]{2}\:[0-9]{2}/)[0]
+      # date for CLI notifications
+      now = ("#{new Date}".match /[0-9]{2}\:[0-9]{2}\:[0-9]{2}/)[0]

-        msg = if log_time then "[#{now}] " else ""
-        msg += "#{'✓ Vendor copied: '.bold}#{from} -> #{to}"
+      msg = if log_time then "[#{now}] " else ""
+      msg += "#{'✓ Vendor copied: '.bold}#{from} -> #{to}"

-        log msg.green+      log msg.green


added request renderer change event to dynamic_image_renderer

--- bokehjs/src/coffee/renderer/tile/dynamic_image_renderer.coffee
+++ bokehjs/src/coffee/renderer/tile/dynamic_image_renderer.coffee
@@ -6,6 +6,10 @@
 {logger} = require "../../common/logging"

 class DynamicImageView extends PlotWidget
+
+  initialize: (options) ->
+    super(options)
+    @listenTo(@model, 'change', @renderer.request_render)

   get_extent: () ->
     return [@x_range.get('start'), @y_range.get('start'), @x_range.get('end'), @y_range.get('end')]


fix bug with histogram when there aren't other opinions around

--- @client/histogram_layout.coffee
+++ @client/histogram_layout.coffee
@@ -213,14 +213,16 @@
             max_density = area
           densities.push area
           stance += avg_inc
-        for area,idx in densities
-          densities[idx] /= max_density
+        if max_density > 0
+          for area,idx in densities
+            densities[idx] /= max_density

     if !body.opinion_density_at_target
       density = 0
       for window_size, densities of opinion_density
         density += densities[ Math.round(body.x_target / (width - body.radius) * (densities.length - 1)) ]
-      body.opinion_density_at_target = density /= window_sizes.length
+      density /= window_sizes.length
+      body.opinion_density_at_target = density

     return body.opinion_density_at_target


sidebar: listen invitation events on individual machines

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

--- client/app/lib/components/sidebarmachineslistitem/index.coffee
+++ client/app/lib/components/sidebarmachineslistitem/index.coffee
@@ -31,6 +31,7 @@
     activeMachine : EnvironmentFlux.getters.activeMachine
     activeLeavingMachine : EnvironmentFlux.getters.activeLeavingSharedMachineId
     activeInvitationMachineId : EnvironmentFlux.getters.activeInvitationMachineId
+    requiredInvitationMachine : EnvironmentFlux.getters.requiredInvitationMachine

   constructor: (props) ->
@@ -51,6 +52,7 @@
   componentDidMount: ->

     { computeController } = kd.singletons
+    { setActiveInvitationMachineId } = actions

     # Shared/Collab or Managed machines does not have any stacks ~ GG
     if stackId = @props.stack?.get '_id'
@@ -62,6 +64,29 @@
     kd.utils.defer =>
       actions.setMachineListItem @machine('_id'), this

+      if @state.requiredInvitationMachine
+        if @state.requiredInvitationMachine.get('_id') is @machine '_id'
+          if @state.activeInvitationMachineId isnt @machine '_id'
+            # to show invitation widget for both on initial load and after a
+            # realtime event. This is probably an anti pattern, to do this in
+            # this component here. I will fix it with the restructure of
+            # sidebar. ~Umut
+            setActiveInvitationMachineId { machine: @props.machine }
+            @setCoordinates()
+
+      if @state.activeInvitationMachineId
+        @setCoordinates()
+
+
+  setCoordinates: ->
+
+    return  unless @refs.sidebarMachinesListItem
+
+    coordinates = getBoundingClientReact @refs.sidebarMachinesListItem
+
+    debug 'setcoordinates', coordinates
+    @setState { coordinates: coordinates }
+

   componentWillUnmount: ->

@@ -78,13 +103,7 @@
       actions.unsetMachineListItem @machine('_id'), this

-  componentWillReceiveProps: ->
-
-    return  unless @refs.sidebarMachinesListItem
-
-    coordinates = getBoundingClientReact @refs.sidebarMachinesListItem
-    @setState { coordinates: coordinates }
-
+  componentWillReceiveProps: -> @setCoordinates()

   machine: (key) ->

@@ -194,8 +213,14 @@

   renderInvitationWidget: ->

+    debug 'machine id', @props.machine.get '_id'
+    debug 'invitaion id', @state.activeInvitationMachineId
+    debug 'coordinates', @state.coordinates
+
     return null  unless @state.coordinates
     return null  unless @props.machine.get('_id') is @state.activeInvitationMachineId
+
+    debug 'invited widget is about to be rendered'

     <SharingMachineInvitationWidget
       key="InvitationWidget-#{@props.machine.get '_id'}"


Test parse error in eco rendering

Test parse error in eco rendering

--- test/render.coffee
+++ test/render.coffee
@@ -52,14 +52,6 @@
         rendered.should.eql 0
         next()

-  it 'complain if source doesnt exist', (next) ->
-    mecano.render
-      source: "oups"
-      destination: "#{scratch}/render.eco"
-    , (err, rendered) ->
-      err.message.should.eql 'Invalid source, got "oups"'
-      next()
-
   it 'accept destination as a callback', (next) ->
     content = null
     mecano.render
@@ -71,5 +63,24 @@
       content.should.eql 'Hello you'
       next()

+  describe 'error', ->
+
+    it 'when source doesnt exist', (next) ->
+      mecano.render
+        source: "oups"
+        destination: "#{scratch}/render.eco"
+      , (err, rendered) ->
+        err.message.should.eql 'Invalid source, got "oups"'
+        next()
+
+    it.only 'when syntax is incorrect', (next) ->
+      mecano.render
+        content: '<%- @host ->'
+        destination: "#{scratch}/render.eco"
+        context: toto: 'lulu'
+      , (err, rendered) ->
+        err.should.eql 'Parse error on line 1: unexpected end of template'
+        next()

+


FIX: Issue with filter due to missing tutor details

--- src/app/common/filters/filters.coffee
+++ src/app/common/filters/filters.coffee
@@ -171,7 +171,7 @@
             project = task.project()
             (task.definition.abbreviation.toLowerCase().indexOf(matchText) >= 0) ||
               (task.definition.name.toLowerCase().indexOf(matchText) >= 0) ||
-              (project? && ( project.student_id.indexOf(matchText) >= 0 || project.name.toLowerCase().indexOf(matchText) >= 0 || (project.tutorial? && (project.tutorial.abbreviation.toLowerCase().indexOf(matchText) >= 0 || project.tutorName().toLowerCase().indexOf(matchText) >= 0))))
+              (project? && ( project.student_id.indexOf(matchText) >= 0 || project.name.toLowerCase().indexOf(matchText) >= 0 || (project.tutorial? && (project.tutorial.abbreviation.toLowerCase().indexOf(matchText) >= 0 || (project.tutorName()? && project.tutorName().toLowerCase().indexOf(matchText) >= 0)))))
           else
             false
       else
@@ -183,7 +183,7 @@
     if _.isString text
       matchText = text.toLowerCase()
       if input
-        _.filter  input, (project) -> (project?) && ( project.student_id.indexOf(matchText) >= 0 || project.name.toLowerCase().indexOf(matchText) >= 0 || (project.tutorial? && (project.tutorial.abbreviation.toLowerCase().indexOf(matchText) >= 0 || project.tutorName().toLowerCase().indexOf(matchText) >= 0)) )
+        _.filter  input, (project) -> (project?) && ( project.student_id.indexOf(matchText) >= 0 || project.name.toLowerCase().indexOf(matchText) >= 0 || (project.tutorial? && (project.tutorial.abbreviation.toLowerCase().indexOf(matchText) >= 0 || (project.tutorName()? && project.tutorName().toLowerCase().indexOf(matchText) >= 0))) )
       else
         input
 )


Allow suggested indent level to be less than the current indent level

--- src/app/language-mode.coffee
+++ src/app/language-mode.coffee
@@ -169,7 +169,7 @@
     return desiredIndentLevel unless decreaseIndentRegex = @decreaseIndentRegexForScopes(scopes)
     desiredIndentLevel -= 1 if decreaseIndentRegex.test(currentLine)

-    Math.max(desiredIndentLevel, currentIndentLevel)
+    desiredIndentLevel

   # Indents all the rows between two buffer row numbers.
   #


started implementing delete route

started implementing delete route

--- app/routes/router.coffee
+++ app/routes/router.coffee
@@ -188,6 +188,9 @@

     sendResponse res, err, response

+router.delete '/algorithms/:identifier', (req, res) ->
+  logger.log 'info', 'removing algorithm: ' + req.params.identifier
+
 #Info routes
 router.get '/info/inputs', (req, res) ->
   ioHelper = new IoHelper()


fix test

fix test

--- tests/batman/model/instance_manipulation_test.coffee
+++ tests/batman/model/instance_manipulation_test.coffee
@@ -377,10 +377,8 @@
         ok false, "success callback isn't called"
       , (err) ->
         ok err?, "error is passed to error handler"
-
-  setTimeout ->
-      QUnit.start()
-    , 30
+    .then ->
+      QUnit.start()

 asyncTest "model instances should be accept options for destruction", 1, ->
   product = new @Product(10)


add app_id saving in variable

add app_id saving in variable

--- assets/js/app.coffee
+++ assets/js/app.coffee
@@ -170,8 +170,8 @@
     get_new_photos()

   VK.init (data) ->
-    app_id = data.api_id
-    console.log app_id
+    #app_id = data.api_id
+    console.log data
     VK.api 'storage.get', {key: 'score'}, (data) ->
       if data.response
         if data.response == ''


Use args.resourcePath instead of args.devResourcePath

--- src/browser/main.coffee
+++ src/browser/main.coffee
@@ -21,8 +21,7 @@
     return
   else if args.test and args.mainProcess
     console.log = previousConsoleLog
-    resourcePath = if fs.existsSync(args.devResourcePath) then args.devResourcePath else require.resolve('../..')
-    require(path.join(args.devResourcePath, 'spec/browser/mocha-test-runner'))(args.pathsToOpen)
+    require(path.join(args.resourcePath, 'spec/browser/mocha-test-runner'))(args.pathsToOpen)
     return

   # NB: This prevents Win10 from showing dupe items in the taskbar


Fix autoscroll test

Fix autoscroll test

--- src/coffee/bootstrap-tour.spec.coffee
+++ src/coffee/bootstrap-tour.spec.coffee
@@ -205,6 +205,7 @@
       next: 2
       prev: -1
       animation: false
+      autoscroll: true
       container: 'body'
       backdrop: false
       backdropPadding: 0


Corrected little typos in the coffee script

--- src/coffee/bootstrap.offcanvas.coffee
+++ src/coffee/bootstrap.offcanvas.coffee
@@ -169,14 +169,14 @@
                 @target = $(target)

                 # Target must be available before running
-                if @target.length and !@target.hasClass 'js-offcanas-done'
+                if @target.length and !@target.hasClass 'js-offcanvas-done'
                     # Add class to element to say it already has events
                     @element.addClass 'js-offcanvas-has-events'

                     # Get the location of the offcanvas menu
                     @location = if @target.hasClass "navbar-offcanvas-right" then "right" else "left"

-                    @target.addClass if transform then "offcanvas-transform js-offcanas-done" else "offcanvas-position js-offcanas-done"
+                    @target.addClass if transform then "offcanvas-transform js-offcanvas-done" else "offcanvas-position js-offcanvas-done"

                     # Add some data
                     @target.data 'offcanvas', @


move variables to somewhere they can actually be used

--- cf/spell.coffee
+++ cf/spell.coffee
@@ -1,7 +1,4 @@
 $ ->
-
-  @current = ""
-  @fail = 0

   $("#question, #answer").hide()
   words = $.getJSON("spellings.json")
@@ -18,6 +15,8 @@
     $("#question, #answer").show()
     update_question(data)
     @data = data
+    @current = ""
+    @fail = 0

   random_question = (data) ->
     choice = Math.floor(Math.random() * data['words'].length)


reword

reword

--- cf/spell.coffee
+++ cf/spell.coffee
@@ -36,7 +36,7 @@
     size = 0
     for word in words
       size += word.length
-    $("dd#hint_stat").empty().append("This entry is " + size + " letters long. It is also " + words.length + " word(s) long.")
+    $("dd#hint_stat").empty().append("This entry is " + size + " letters long and " + words.length + " word(s) long.")
     $("dd#hint_define").empty().append(entry['hint'])
     $("dd#hint_sentence").empty().append(entry['example'])


Use label to switch wording depending on course type

--- src/components/task-plan/builder.cjsx
+++ src/components/task-plan/builder.cjsx
@@ -4,6 +4,7 @@
 moment = require 'moment-timezone'
 BS = require 'react-bootstrap'

+PeriodSectionLabel = require '../period-section-label'
 PlanMixin = require './plan-mixin'
 BindStoreMixin = require '../bind-store-mixin'

@@ -340,7 +341,9 @@
     choiceLabel = <BS.Row>
       <BS.Col md=12>
         {radio}
-        <label className="period" htmlFor='show-periods-radio'>Individual Periods</label>
+        <label className="period" htmlFor='show-periods-radio'>
+          Individual <PeriodSectionLabel courseId={@props.courseId} plural />
+        </label>
       </BS.Col>
     </BS.Row>


Don’t crash on solarcity failures

Don’t crash on solarcity failures

--- lib/adapters/solarcity/SolarCityAdapter.coffee
+++ lib/adapters/solarcity/SolarCityAdapter.coffee
@@ -55,5 +55,9 @@
         @log 'warn', "Request (#{type}) failed: #{err}"
         deferred.reject err
       else
-        deferred.resolve JSON.parse(body)
+        try
+          deferred.resolve JSON.parse(body)
+        catch err
+          @log 'warn', "Request (#{type}) resulted in JSON parse error: #{err}"
+          deferred.reject err
     deferred.promise


Revert gridster

Revert gridster

--- lib/assets/javascripts/dashing.gridster.coffee
+++ lib/assets/javascripts/dashing.gridster.coffee
@@ -6,10 +6,26 @@
   Dashing.customGridsterLayout = true
   positions = positions.replace(/^"|"$/g, '')
   positions = $.parseJSON(positions)
-  widgets = $("[data-row^=]")
+  widgets = $("[data-row]")
+  maxRow = 1
+  maxCol = 1
+  numColumns = Dashing.numColumns || 1
   for widget, index in widgets
-    $(widget).attr('data-row', positions[index].row)
-    $(widget).attr('data-col', positions[index].col)
+    if positions? and index < positions.length
+      $(widget).attr('data-row', positions[index].row)
+      $(widget).attr('data-col', positions[index].col)
+      if positions[index].row > maxRow
+        maxRow = positions[index].row
+        maxCol = positions[index].col + 1
+      else if positions[index].row == maxRow and positions[index].col >= maxCol
+        maxCol = positions[index].col + 1
+    else
+      if maxCol > numColumns
+        maxRow += 1
+        maxCol = 1
+      $(widget).attr('data-row', maxRow)
+      $(widget).attr('data-col', maxCol)
+      maxCol += 1

 Dashing.getWidgetPositions = ->
   $(".gridster ul:first").gridster().data('gridster').serialize()
@@ -24,7 +40,9 @@
       <script type='text/javascript'>\n
       $(function() {\n
       \ \ Dashing.gridsterLayout('#{JSON.stringify(Dashing.currentWidgetPositions)}')\n
-      });\n</script>")
+      });\n
+      </script>
+    ")

 $ ->
   $('#save-gridster').leanModal()


Item List: Fixed a problem with the option selection by keyword input. See #62652

--- src/elements/ItemList/ItemList.coffee
+++ src/elements/ItemList/ItemList.coffee
@@ -354,7 +354,7 @@
 			@__preActivateButtonByIndex(nextIndex)
 			return

-		for item, index in @__body?.DOM.children
+		for item, index in @__getButtonItems()
 			element = CUI.dom.data(item, "element")
 			if elementMatches(element)
 				@__preActivateButtonByIndex(index)
@@ -411,7 +411,7 @@
 		return

 	__getButtonItems: ->
-		return Array::filter.call(@__body.DOM.children, (item) =>
+		return Array::filter.call(@__body?.DOM.children, (item) =>
 			element = CUI.dom.data(item, "element")
 			return element instanceof CUI.Button and element.isEnabled()
 		)


Redux dev tools

Redux dev tools

--- src/client/index.coffee
+++ src/client/index.coffee
@@ -25,4 +25,4 @@

 riot.mixin "context", ContextMixin
 riot.mixin "subscribe", SubscribeMixin
-riot.mount tag, store: Redux.createStore reducer
+riot.mount tag, store: Redux.createStore reducer, undefined, if window?.devToolsExtension? then window.devToolsExtension() else undefined


much better variable/function names

much better variable/function names

--- src/coffee/tools.coffee
+++ src/coffee/tools.coffee
@@ -6,6 +6,7 @@
   this.Bokeh = Bokeh
 safebind = Continuum.safebind

+
 class PanToolView_ extends Bokeh.PlotWidget
   # draggin2 is used because having a variable named @dragging causes some sort of naming conflict.
   # I need to look through this and figure out what is going on
@@ -13,7 +14,7 @@
   initialize : (options) ->
     super(options)
     @draggin2 = false
-    @started_dragging = false
+    @basepoint_set = false
     @button_activated = false

   bind_events : (plotview) ->
@@ -21,9 +22,8 @@
     @plotview.moveCallbacks.push((e, x, y) =>
       if not @draggin2
         return
-      if not @started_dragging
-        @_start_drag(e, x, y)
-        @started_dragging = true
+      if not @basepoint_set
+        @_set_base_point(e, x, y)
       else
         @_drag(e.foo, e.foo, e, x, y)
         e.preventDefault()
@@ -56,15 +56,14 @@
         @button_activated = true)

   _start_drag2 : ->
-    if not @draggin2
-      @started_dragging = false
+    if not @draggin2
       @draggin2 = true
       if not @button_activated
         @pan_button.addClass('active')

   _stop_drag2 : ->
+    @basepoint_set = false
     if @draggin2
-      @started_dragging = false
       @draggin2 = false
       if not @button_activated
         @pan_button.removeClass('active')
@@ -73,9 +72,9 @@
     [x_, y_] = [@plot_model.rxpos(x), @plot_model.rypos(y)]
     return [x_, y_]

-  #_set_base_point
-  _start_drag : (e, x, y) ->
+  _set_base_point : (e, x, y) ->
     @draggin2 = true
+    @basepoint_set = true
     [@x, @y] = @mouse_coords(e, x, y)
     xmappers = (@model.resolve_ref(x) for x in @mget('xmappers'))
     ymappers = (@model.resolve_ref(x) for x in @mget('ymappers'))


Add data: mapspot for 2015 autumn

Add data: mapspot for 2015 autumn


add missing  east start point

add missing  east start point


Add a missing return statement

Add a missing return statement

--- src/configfinder.coffee
+++ src/configfinder.coffee
@@ -14,7 +14,7 @@
 findFile = (name, dir) ->
     dir = dir or process.cwd()
     filename = path.normalize(path.join(dir, name))
-    findFileResults[filename]  if findFileResults[filename]
+    return findFileResults[filename]  if findFileResults[filename]
     parent = path.resolve(dir, "../")
     if fs.existsSync(filename)
         findFileResults[filename] = filename


Added +balance command

Added +balance command

--- bot.iced
+++ bot.iced
@@ -165,6 +165,15 @@
 					bot.sendMessage chatterID, "Balance was not paid within 30 minutes so the transaction was cancelled"
 				else
 					bot.sendMessage chatterID, "Your payment is currently '#{body.status}'"
+		when "+balance"
+			await checkIfRegistered chatterID, defer(registered, user)
+			if registered is undefined
+				return bot.sendMessage chatterID, "The database ran into an error"
+			unless registered
+				return bot.sendMessage chatterID, "You must register before you can add funds. Do this by sending '+register'."
+
+			balance = user.funds
+			bot.sendMessage chatterID, "You currently have #{balance} DOGE to tip with"

 bot.on "friend", (steamID, Relationship) ->
 	if pendingInvites.indexOf(steamID) isnt -1


Added +stats command for the group chat

Added +stats command for the group chat

--- bot.iced
+++ bot.iced
@@ -45,11 +45,22 @@
 	cb registered, user

 bot.on "chatMsg", (sourceID, message, type, chatterID) ->
-	if message is "Me"
-		bot.sendMessage DogeTipGroupID, bot.users[chatterID].playerName
-		bot.sendMessage DogeTipGroupID, chatterID
 	if message[0] is "+"
-		bot.sendMessage DogeTipGroupID, "I won't respond to commands on the group chat. Open up a private message by double clicking on my name in the sidebar to send me commands."
+		switch message
+			when "+me"
+				bot.sendMessage DogeTipGroupID, bot.users[chatterID].playerName
+				bot.sendMessage DogeTipGroupID, chatterID
+			when "+stats"
+				Users_collection.find().toArray (err, users) ->
+					userCount = users.length
+					tipCount = 0
+					for user in users
+						for transaction in user.history
+							if transaction.type is "sent tip" then tipCount++
+					totalUserCount = undefined
+					bot.sendMessage DogeTipGroupID, "#{userCount} users have registered and tipped other shibes #{tipCount} times"
+			else
+				bot.sendMessage DogeTipGroupID, "I won't respond to commands on the group chat. Open up a private message by double clicking on my name in the sidebar to send me commands."
 bot.on "friendMsg", (chatterID, message, type) ->
 	# Private messages
 	return if message is ""


The test runner should look for unit, integration and system tests.

--- test/runner.coffee
+++ test/runner.coffee
@@ -1,13 +1,27 @@
 path = require "path"
+wrench = require "wrench"
 Mocha = require "mocha"
+_ = require "lodash"
+specs = path.join __dirname, "..", "test"
+
+find = (spec_type) ->
+  f = wrench
+    .readdirSyncRecursive path.join(specs, spec_type)
+    .filter (p) -> /\.js/.test p
+    .map (p) -> path.resolve (path.join specs, spec_type, p)
+
+all_specs = ->
+  find("unit")
+    .concat find("integration")
+    .concat find("system")

 run = (type) ->
   runner = new Mocha
     ui: "bdd"
-    reporter: "dot"
+    reporter: "spec"
     timeout: 10000

-  runner.files = [ path.join __dirname, "spec.js" ]
+  runner.files = all_specs()

   runner.run process.exit


Rename `callbacks` to `listeners`

Rename `callbacks` to `listeners`

--- src/emitter.coffee
+++ src/emitter.coffee
@@ -8,24 +8,24 @@
     this

   off: (event, fn) ->
-    callbacks = @_callbacks[event]
-    return this unless callbacks
+    listeners = @_callbacks[event]
+    return this unless listeners

-    # remove all handlers
+    # remove the listener
     if 1 is arguments.length
       delete @_callbacks[event]
       return this

     # remove specific handler
-    i = callbacks.indexOf(fn._off or fn)
-    callbacks.splice i, 1 if ~i
+    i = listeners.indexOf(fn._off or fn)
+    listeners.splice i, 1 if ~i
     this

   emit: (event, args...) ->
-    callbacks = @_callbacks[event]
-    if callbacks
-      for callback in callbacks.slice 0
-        callback.apply(this, args)
+    listeners = @_callbacks[event]
+    if listeners
+      for listener in listeners.slice 0
+        listener.apply(this, args)
     this

 Emitter.addEventListener = Emitter.on


Update atom config

Update atom config

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -1,8 +1,9 @@
 "*":
   editor:
     invisibles: {}
-    fontFamily: "Source Code Pro"
     showInvisibles: true
+    fontFamily: "Office Code Pro"
+    fontSize: 15
   core:
     ignoredNames: [
       ".bundle"
@@ -25,5 +26,7 @@
     ]
   welcome:
     showOnStartup: false
-  "atom-lint": {}
-  linter: {}
+  "autocomplete-plus":
+    confirmCompletion: "tab and enter"
+  "tree-view":
+    showOnRightSide: true


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/sprite.coffee
+++ test/sprite.coffee
@@ -9,5 +9,5 @@

   sprite = Sprite.load "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAIAAAACDbGyAAAAAXNSR0IArs4c6QAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9oMCRUiMrIBQVkAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAADElEQVQI12NgoC4AAABQAAEiE+h1AAAAAElFTkSuQmCC"

-  sprite = Sprite.load
+  sprite = Sprite.load
 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/sprite.coffee
+++ test/sprite.coffee
@@ -9,7 +9,7 @@

   callback = ->
     sprite2 = Sprite.load spriteUrl, (s) ->
-      ok
+      ok s

   sprite = Sprite.load spriteUrl, callback


minor perf optimization order of space removal.

--- js-library/app/js/jail_iframe/classes/paragraph_buttons.coffee
+++ js-library/app/js/jail_iframe/classes/paragraph_buttons.coffee
@@ -7,7 +7,7 @@
     $clonedEl = $(el).clone()
     $clonedEl.find('a').remove() # Strip links

-    textLength = $clonedEl.text().replace(/\s+/g, ' ').trim().length
+    textLength = $clonedEl.text().trim().replace(/\s\s+/g, ' ').length
     $clonedEl.remove()

     textLength >= 50


Stub user in route tests

Stub user in route tests

--- api/apps/articles/test/routes.coffee
+++ api/apps/articles/test/routes.coffee
@@ -17,7 +17,7 @@
       params: {}
       get: ->
       user: _.extend(fixtures().users, _id: ObjectId '5086df098523e60002000012')
-    @res = { send: sinon.stub(), err: sinon.stub() }
+    @res = { send: sinon.stub(), err: sinon.stub(), user: sinon.stub() }
     @next = sinon.stub()

   afterEach ->


KDDiaScene: Add fakeConnection support KDDiaScene which keeps a seperate list of connections

--- client/Framework/components/dia/kddiascene.coffee
+++ client/Framework/components/dia/kddiascene.coffee
@@ -16,10 +16,11 @@

     super

-    @containers    = []
-    @connections   = []
-    @activeDias    = []
-    @activeJoints  = []
+    @containers      = []
+    @connections     = []
+    @activeDias      = []
+    @activeJoints    = []
+    @fakeConnections = []

   diaAdded:(container, diaObj)->
     diaObj.on "JointRequestsLine",   @bound "handleLineRequest"
@@ -182,56 +183,68 @@
     @connections.push {source, target}
     @highlightLines target.dia

+  resetScene:->
+    @fakeConnections = []
+    @updateScene()
+
   updateScene:->

     @cleanup @realCanvas
-
+    @drawConnectionLine connection for connection in @connections
+    @drawConnectionLine connection for connection in @fakeConnections
+
+  drawConnectionLine:({source, target, options})->
+
+    return unless source or target
+
+    options    or= {}
     activeColor  = @getOption 'lineColorActive'
     lineDashes   = @getOption 'lineDashes'
-    defaultColor = @getOption 'lineColor'
-
-    for connection in @connections
-
-      @realContext.beginPath()
-
-      {source, target} = connection
-
-      if source.dia in @activeDias
-        lineColor = (source.dia.getOption 'colorTag')   or activeColor
-        lineDashes  = (source.dia.getOption 'lineDashes') or lineDashes
-      else if target.dia in @activeDias
-        lineColor = (target.dia.getOption 'colorTag')   or activeColor
-        lineDashes  = (target.dia.getOption 'lineDashes') or lineDashes
-      else
-        lineColor = defaultColor
-
-      sJoint = source.dia.getJointPos source.joint
-      tJoint = target.dia.getJointPos target.joint
-
-      @realContext.strokeStyle = lineColor
-      @realContext.setLineDash lineDashes  if lineDashes.length > 0
-
-      @realContext.moveTo sJoint.x, sJoint.y
-
-      cd = @getOption 'curveDistance'
-      [sx, sy, tx, ty] = [0, 0, 0, 0]
-      if source.joint in ["top", "bottom"]
-        sy = if source.joint is "top" then -cd else cd
-      else if source.joint in ["left", "right"]
-        sx = if source.joint is "left" then -cd else cd
-      if target.joint in ["top", "bottom"]
-        ty = if target.joint is "top" then -cd else cd
-      else if target.joint in ["left", "right"]
-        tx = if target.joint is "left" then -cd else cd
-
-      @realContext.bezierCurveTo(sJoint.x + sx, sJoint.y + sy, \
-                                 tJoint.x + tx, tJoint.y + ty, \
-                                 tJoint.x, tJoint.y)
-
-      @realContext.lineWidth = @getOption 'lineWidth'
-      @realContext.stroke()
+    lineColor    = @getOption 'lineColor'
+
+    @realContext.beginPath()
+
+    activeDia = if source.dia in @activeDias then source \
+                else if target.dia in @activeDias then target
+
+    if activeDia
+      lineColor  = options.lineColor  or \
+                   (activeDia.dia.getOption 'colorTag') or activeColor
+      lineDashes = options.lineDashes or \
+                   (activeDia.dia.getOption 'lineDashes') or lineDashes
+
+    sJoint = source.dia.getJointPos source.joint
+    tJoint = target.dia.getJointPos target.joint
+
+    @realContext.strokeStyle = lineColor
+    @realContext.setLineDash lineDashes  if lineDashes.length > 0
+
+    @realContext.moveTo sJoint.x, sJoint.y
+
+    cd = @getOption 'curveDistance'
+    [sx, sy, tx, ty] = [0, 0, 0, 0]
+    if source.joint in ["top", "bottom"]
+      sy = if source.joint is "top" then -cd else cd
+    else if source.joint in ["left", "right"]
+      sx = if source.joint is "left" then -cd else cd
+    if target.joint in ["top", "bottom"]
+      ty = if target.joint is "top" then -cd else cd
+    else if target.joint in ["left", "right"]
+      tx = if target.joint is "left" then -cd else cd
+
+    @realContext.bezierCurveTo(sJoint.x + sx, sJoint.y + sy, \
+                               tJoint.x + tx, tJoint.y + ty, \
+                               tJoint.x, tJoint.y)
+
+    @realContext.lineWidth = @getOption 'lineWidth'
+    @realContext.stroke()
+
+  addFakeConnection:(connection)->
+    @drawConnectionLine connection
+    @fakeConnections.push connection

   createCanvas:->
+
     @realCanvas?.destroy()
     @fakeCanvas?.destroy()

@@ -239,6 +252,11 @@
       tagName    : "canvas"
       attributes : @getSceneSize()
     @realContext = @realCanvas.getElement().getContext "2d"
+
+    # Fallback function for Firefox and Safari
+    # which does not support lineDashes correctly ~ GG
+    @realContext.setLineDash = noop  unless @realContext.setLineDash?
+
     @addSubView @fakeCanvas = new KDCustomHTMLView
       tagName    : "canvas"
       cssClass   : "fakeCanvas"


Fix for channel lazy loading on modal overlay

--- client/Social/Activity/sidebar/searchmodal.coffee
+++ client/Social/Activity/sidebar/searchmodal.coffee
@@ -73,11 +73,11 @@
     @fetch {}, @bound 'populate'

-  populate: (items) ->
+  populate: (items, options) ->

     return  unless items?.length?

-    @listController.removeAllItems()
+    @listController.removeAllItems()  unless options.skip

     @listController.addItem itemData for itemData in items

@@ -116,6 +116,8 @@

     options      = @getSearchOptions val
     options.name = val
+
+    @reachedEndOfTheList = no  unless options.limit

     @setClass 'search-active'
     @searchActive = yes
@@ -137,6 +139,7 @@

   reset: ->

+    @reachedEndOfTheList = no
     @searchActive = no
     @lastTerm = ''

@@ -164,7 +167,7 @@

       @reachedEndOfTheList = yes  unless items.length

-      callback items
+      callback items, options

   fetch: (options = {}, callback) ->
@@ -183,7 +186,7 @@

       return  if err

-      callback items
+      callback items, options
       @reachedEndOfTheList = yes  unless items.length


Fix filter redirect

Fix filter redirect

--- client/app/components/toolbar_messageslist.coffee
+++ client/app/components/toolbar_messageslist.coffee
@@ -33,11 +33,8 @@

         # change here if we add an UI for sorting
         # @props.queryParams is the current value
-        before = '-'
-        after = '-'
-        flag = '-'
+
         type = params.type
-        sort = '-date'

         switch type
             when 'from', 'dest'
@@ -47,7 +44,11 @@

             when 'date'
                 if params.range
+                    # Filter by date
                     [before, after] = params.range
+                else
+                    # Reset date filter
+                    type = null

             when 'flag'
                 if params.value
@@ -59,7 +60,7 @@
             action: 'account.mailbox.messages'
             parameters: [
                 @props.accountID, @props.mailboxID,
-                sort, type, flag, before, after
+                '-date', type, flag, before, after
             ]


ComputeController: remove unused method

ComputeController: remove unused method

--- client/app/lib/providers/computecontroller.coffee
+++ client/app/lib/providers/computecontroller.coffee
@@ -1156,19 +1156,6 @@

   ###*
-   * Fetch given stack's README from the stackTemplate which
-   * is generated from.
-  ###
-
-  fetchStackReadme: (stack, callback = kd.noop) ->
-
-    return callback null, ''  unless stack?.baseStackId
-
-    @fetchBaseStackTemplate stack, (err, template) ->
-      return callback err, template?.description ? ''
-
-
-  ###*
    * Fetch given stack's stackTemplate which is generated from.
   ###


hiding loginScreen should set -winHeight, not -winWidth

--- client/landing/app/MainApp/login/loginview.coffee
+++ client/landing/app/MainApp/login/loginview.coffee
@@ -279,7 +279,7 @@

   hide:(callback)->

-    @setY -@getSingleton('windowController').winWidth
+    @setY -@getSingleton('windowController').winHeight

     cb = =>
       @emit "LoginViewHidden"


Not functioning at the moment, items not correct for category

--- app/assets/javascripts/orders.coffee
+++ app/assets/javascripts/orders.coffee
@@ -38,7 +38,8 @@

 populateItems = (category_id) ->
   i = 0
-  items = data.categories[id: category_id].items
+  logger
+  items = data.categories[category_id].items
   items_html = []
   while i < items.length
     item = items[i]


Tests: update clickAddKodingVMButton helper for new environments modal

--- client/test/lib/helpers/environmenthelpers.coffee
+++ client/test/lib/helpers/environmenthelpers.coffee
@@ -66,8 +66,11 @@

     sidebarTitle = '[testpath=main-sidebar] .activity-sidebar .vms .sidebar-title'
     browser
-      .waitForElementVisible   '.more-modal.more-vms', 20000 # Assertion
-      .click                   '.kdbutton.create-koding-vm'
+      .waitForElementVisible   '.environments-modal', 20000 # Assertion
+      .waitForElementVisible   '.kdbutton.add-vm-button', 20000 # Assertion
+      .click                   '.kdbutton.add-vm-button'
+      .waitForElementVisible   '.kdcontextmenu.add-new-vm-menu', 20000 # Assertion
+      .click                   '.kdlistitemview.create-koding-vm'

   seeUpgradeModal: (browser) ->


system status: add signature annotations

--- workers/social/lib/social/models/systemstatus.coffee
+++ workers/social/lib/social/models/systemstatus.coffee
@@ -1,4 +1,4 @@
-{Model, Base, secure, daisy} = require 'bongo'
+{Model, Base, secure, daisy, signature} = require 'bongo'
 KodingError = require '../error'

 module.exports = class JSystemStatus extends Model
@@ -6,9 +6,18 @@
   @share()

   @set
+
     sharedMethods    :
-      static         : ['getCurrentSystemStatus','create'
-                        'stopCurrentSystemStatus','forceReload']
+      static         :
+        getCurrentSystemStatus:
+          (signature Function)
+        create:
+          (signature Object, Function)
+        stopCurrentSystemStatus:
+          (signature Function)
+        forceReload:
+          (do signature)
+
     sharedEvents     :
       static         : ['forceReload']
       instance       : ['restartCanceled', 'restartScheduled']


copyFileVersion spec modified because of code changes

--- src/datafluxSpec.coffee
+++ src/datafluxSpec.coffee
@@ -67,6 +67,7 @@
       df = new dataflux srcFolder, fluxFolder
       df.log.noLog = true
       df.fse = jasmine.createSpyObj "fse", ["copySync"]
+      df.fileExists = -> return true
       df.addTimestamp = (filePath) -> filePath
       df.copyFileVersion myFile
       expect(df.fse.copySync).toHaveBeenCalledWith(myFile, fluxFile)


Break a long line.

Break a long line.

--- app/javascripts/imageSequence.coffee
+++ app/javascripts/imageSequence.coffee
@@ -2,19 +2,23 @@
   imageSequence = ng.module('qiprofile.imagesequence',
                             ['qiprofile.timeseries', 'qiprofile.volume'])

-  imageSequence.factory 'ImageSequence', ['TimeSeries', 'Volume', (TimeSeries, Volume) ->
-    # @param imageSequence the scan or registration ImageSequence
-    #   to extend
-    # @returns the extended ImageSequence
-    extend: (imageSequence) ->
-      # Extend the time series.
-      if imageSequence.timeSeries?
-        TimeSeries.extend(imageSequence.timeSeries)
-      # Extend each volume.
-      if imageSequence.volumes?
-        for volume, index in imageSequence.volumes.images
-          Volume.extend(volume, imageSequence, index + 1)
+  imageSequence.factory 'ImageSequence', [
+    'TimeSeries', 'Volume', (TimeSeries, Volume) ->
+      # Recursively extends the image sequence time series and
+      # volumes.
+      #
+      # @param imageSequence the scan or registration REST object
+      #   to extend
+      # @returns the extended REST image sequence object
+      extend: (imageSequence) ->
+        # Extend the time series.
+        if imageSequence.timeSeries?
+          TimeSeries.extend(imageSequence.timeSeries)
+        # Extend each volume.
+        if imageSequence.volumes?
+          for volume, index in imageSequence.volumes.images
+            Volume.extend(volume, imageSequence, index + 1)

-      # Return the augmented image sequence.
-      imageSequence
+        # Return the augmented image sequence.
+        imageSequence
   ]


Formatting / whitespace

Formatting / whitespace

--- src/bootstrap-dialogs.coffee
+++ src/bootstrap-dialogs.coffee
@@ -33,12 +33,11 @@
       $btn.click(handler) if handler instanceof Function
       $btn

-    $el = $('
')
-      .html([
-        $('
').html(title)
-        body
-        $('
').html(buttons)
-      ])
+    $el = $('
').html([
+      $('
').html(title)
+      body
+      $('
').html(buttons)
+    ])

     promise = $.Deferred()
     promise.el = $el[0]
@@ -49,7 +48,6 @@
       $el.remove()

     $el.modal(backdrop: 'static')
-
     promise

   prompt: (title='Please enter a value', body) ->


Fix gps coordinates checking

Fix gps coordinates checking

--- controllers/game_controller.coffee
+++ controllers/game_controller.coffee
@@ -139,7 +139,7 @@
             @getGameUser game, user, (gameUser) =>
                 # Check if coordinates are right
                 if useGPS
-                    if @checkCoordinates(lat, lng, gamePoint.lat, gamePoint.lng, 15000) == false
+                    if @checkCoordinates(lat, lng, point.dataValues.lat, point.dataValues.lng, 200) == false
                         console.log "Rejecting checkin of point #{point.id} with coords #{lat} #{lng} by user #{user.id}"
                         return
                 @getGameTeamForUser game, user, (gameTeam) ->
@@ -170,10 +170,6 @@
     checkCoordinates: (uLat, uLng, tLat, tLng, delta) ->
         return false unless delta > 0
         earthRadius = 6372.8 # Km
-        uLat =  parseFloat (uLat).replace(',', '.')
-        uLng = parseFloat (uLng).replace(',', '.')
-        tLat = parseFloat (tLat).replace(',', '.')
-        tLng = parseFloat (tLng).replace(',', '.')
         uLat = uLat/180 * Math.PI
         uLng = uLng/180 * Math.PI
         tLat = tLat/180 * Math.PI


Allow methods bank subSection to contain capital letters (#182)

--- app/schemas/models/level.ozar.coffee
+++ app/schemas/models/level.ozar.coffee
@@ -424,7 +424,7 @@
     properties: {
       name: c.shortString(title: 'Name'),
       section: c.shortString(title: 'Methods Bank Section', pattern: '^[a-z]+$'),
-      subSection: c.shortString(title: 'Methods Bank Sub-Section', pattern: '^[a-z]+$'),
+      subSection: c.shortString(title: 'Methods Bank Sub-Section', pattern: '^[a-zA-Z]+$'),
       componentName: c.shortString(title: 'Level Component Name', description: 'Level Component to use for documentation in case there are multiple components with same property\'s documentation'),
     }
   }


fixed one last bug for castling

fixed one last bug for castling

--- app/scripts/chess/pieces/king.coffee
+++ app/scripts/chess/pieces/king.coffee
@@ -11,22 +11,24 @@

 		# castling
 		unless @hasMoved
-			# left side
-			if board.clearStraight(x,y,0,y)
-				if not board.get(0,y).hasMoved and board.get(0,y) instanceof Rook
-					# cannot castle through a check
-					unless board.inCheck(1, y, @isWhite) ||
-					board.inCheck(2, y, @isWhite) ||
-					board.inCheck(3, y, @isWhite)
-						moves.push [2,y, "castle"]
+			# king can't be in check
+			unless board.inCheck(4, y, @isWhite)
+				# left side
+				if board.clearStraight(x,y,0,y)
+					if not board.get(0,y).hasMoved and board.get(0,y) instanceof Rook
+						# cannot castle through a check
+						unless board.inCheck(1, y, @isWhite) ||
+						board.inCheck(2, y, @isWhite) ||
+						board.inCheck(3, y, @isWhite)
+							moves.push [2,y, "castle"]

-			# right side
-			if board.clearStraight(x,y,7,y)
-				if not board.get(0,y).hasMoved and board.get(0,y) instanceof Rook
-					# cannot castle through a check
-					unless board.inCheck(5, y, @isWhite) ||
-					board.inCheck(6, y, @isWhite)
-						moves.push [6,y, "castle"]
+				# right side
+				if board.clearStraight(x,y,7,y)
+					if not board.get(0,y).hasMoved and board.get(0,y) instanceof Rook
+						# cannot castle through a check
+						unless board.inCheck(5, y, @isWhite) ||
+						board.inCheck(6, y, @isWhite)
+							moves.push [6,y, "castle"]

 		# regular moves
 		for j in [0..7]


do not use String.startWith

do not use String.startWith

--- app/scripts/filters/highlight.coffee
+++ app/scripts/filters/highlight.coffee
@@ -39,7 +39,7 @@
                 matches = [ date.toString 'yyyy' ]

         _.map matches, (match) =>
-            match = if match.startsWith '0' then [(match.substr 1, match.length), match] else [match]
+            match = if match[0] is '0' then [(match.substr 1, match.length), match] else [match]
             _.map match, (match) =>
                 match = do match.toLowerCase
                 i = 0


shallow clone ajax response

shallow clone ajax response

--- src/coffee/async/ajax.coffee
+++ src/coffee/async/ajax.coffee
@@ -7,7 +7,7 @@
   # define a standard on success handler, write out the request stats to a table
   config.onSuccess = (opts, data, url) ->
     response = {}
-    OJ.extend response, data, true
+    OJ.extend response, data
     opts.onSuccess response
     if OJ.LOG_ALL_AJAX
       OJ.console.table [


Add delete_class method.

Add delete_class method.

--- src/coffee/classifier.coffee
+++ src/coffee/classifier.coffee
@@ -73,6 +73,12 @@
          delete @klass_count[from]
          @klass_count[to] = if @klass_count.hasOwnProperty(to) then @klass_count[to] + count else count

+     delete_class: (klass) ->
+         for feature, record of @feature_count
+             if record.hasOwnProperty(klass)
+                 delete record[klass]
+         delete @klass_count[from]
+
      documents_in_class_count: (klass)->
          if klass of @klass_count
              @klass_count[klass]


Add location protocol to m_redirect_url

Add location protocol to m_redirect_url

--- public/assets/coffee/order-id-payment.coffee
+++ public/assets/coffee/order-id-payment.coffee
@@ -54,7 +54,7 @@
           buyer_tel:    $info.data('phone')
           buyer_addr:   $info.data('address1')
           vbank_due:    moment().add(2, 'hours').format('YYYYMMDDHHmm')
-          m_redirect_url: "//#{location.host}/payments/#{data.id}"
+          m_redirect_url: "#{location.protocol}//#{location.host}/payments/#{data.id}"
         imp_params.notice_url = CONFIG.iamport.notice_url if CONFIG.iamport.notice_url?

         IMP.request_pay imp_params, (res) ->


Fixed storage.next and storage.prev

Fixed storage.next and storage.prev

--- src/storage.coffee
+++ src/storage.coffee
@@ -31,13 +31,13 @@
     return @images[cid]

   next: (current) ->
-    cid = ++current.cid
-    cid = 0 if @repeat and not @images[cid]
+    cid = current.cid + 1
+    cid = 0 if not @images[cid] and @repeat
     return @images[cid]

   prev: (current) ->
-    cid = --current.cid
-    cid = @length() if @repeat and cid < 0
+    cid = current.cid - 1
+    cid = @length() if cid < 0 and @repeat
     return @images[cid]

   length: ->
@@ -47,4 +47,4 @@
     for own key, image of @images
       return image if "##{image.hashbang}" is hash

-    return false+    return false


More case of passing 'undefined'

More case of passing 'undefined'

--- resources/assets/coffee/react/_components/comment-show-more.coffee
+++ resources/assets/coffee/react/_components/comment-show-more.coffee
@@ -50,7 +50,7 @@
       commentable_type: @props.parent?.commentable_type ? @props.commentableType
       commentable_id: @props.parent?.commentable_id ? @props.commentableId
       parent_id: @props.parent?.id ? ''
-      after: @props.after
+      after: @props.after ? ''

     $.get laroute.route('comments.index', params)
     .done (data) =>


Don't try reconnect if connected

Don't try reconnect if connected

--- services/web/public/coffee/ide/connection/ConnectionManager.coffee
+++ services/web/public/coffee/ide/connection/ConnectionManager.coffee
@@ -105,6 +105,7 @@

 		tryReconnect: () ->
 			@cancelReconnect()
+			return if @connected
 			@$scope.connection.reconnecting = true
 			delete @$scope.connection.reconnection_countdown
 			@ide.socket.socket.reconnect()


specifify visible enterprises in tests

specifify visible enterprises in tests

--- spec/javascripts/unit/darkswarm/services/enterprise_spec.js.coffee
+++ spec/javascripts/unit/darkswarm/services/enterprise_spec.js.coffee
@@ -5,9 +5,9 @@
     {id: 1, name: "test"}
   ]
   enterprises = [
-    {id: 1, is_distributor: true, is_primary_producer: false, producers: [{id: 2}], taxons: [{id: 1}]},
-    {id: 2, is_distributor: false, is_primary_producer: true, hubs: [{id: 1}]},
-    {id: 3, is_distributor: false, is_primary_producer: true, hubs: [{id: 1}]}
+    {id: 1, visible: true, is_distributor: true, is_primary_producer: false, producers: [{id: 2}], taxons: [{id: 1}]},
+    {id: 2, visible: true, is_distributor: false, is_primary_producer: true, hubs: [{id: 1}]},
+    {id: 3, visible: true, is_distributor: false, is_primary_producer: true, hubs: [{id: 1}]}
   ]
   beforeEach ->
     module 'Darkswarm'


Add a skeleton of a custom insertMatch function

--- services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee
+++ services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee
@@ -5,6 +5,7 @@
 	"ace/ext-language_tools"
 ], (SuggestionManager, SnippetManager) ->
 	Range = ace.require("ace/range").Range
+	aceSnippetManager = ace.require('ace/snippets').snippetManager

 	getLastCommandFragment = (lineUpToCursor) ->
 		if m = lineUpToCursor.match(/(\\[^\\]+)$/)
@@ -154,6 +155,25 @@
 					if this.completions.filterText.match(/^\\begin\{/) and nextChar == "}"
 						editor.session.remove(range)

+					if !data?
+						completions = this.completions
+						popup = editor.completer.popup
+						data = popup.getData(popup.getRow())
+						data.completer =
+							insertMatch: (editor, matchData) ->
+								console.log ">> custom insertMatch"
+								console.log ">> data", data
+								ranges = editor.selection.getAllRanges()
+								for range in ranges
+									range.start.column -= completions.filterText.length;
+									editor.session.remove(range);
+								if matchData.snippet
+									console.log ">> snippet"
+									aceSnippetManager.insertSnippet(editor, matchData.snippet);
+								else
+									console.log ">> string", matchData
+									editor.execCommand("insertstring", matchData.value || matchData);
+
 					Autocomplete::_insertMatch.call this, data

 				# Overwrite this to set autoInsert = false and set font size


Call new GET /db/user/:id/course-instances route

Previously, was using a similar, older GET /db/user/:id/course_instances
route that was missing new logic.

--- app/views/courses/CoursesView.coffee
+++ app/views/courses/CoursesView.coffee
@@ -58,7 +58,7 @@
     super()

     @classCodeQueryVar = utils.getQueryVariable('_cc', false)
-    @courseInstances = new CocoCollection([], { url: "/db/user/#{me.id}/course_instances", model: CourseInstance})
+    @courseInstances = new CocoCollection([], { url: "/db/user/#{me.id}/course-instances", model: CourseInstance})
     @courseInstances.comparator = (ci) -> return parseInt(ci.get('classroomID'), 16) + utils.orderedCourseIDs.indexOf ci.get('courseID')
     @listenToOnce @courseInstances, 'sync', @onCourseInstancesLoaded
     @supermodel.loadCollection(@courseInstances, { cache: false })


add some template

add some template

--- scripts/befool.blog.coffee
+++ scripts/befool.blog.coffee
@@ -9,9 +9,11 @@
 # tweets
 tweet_msg = (post) ->
     # templates of the tweet, would be select in random
-    template = ['befoolのブログ更新したよ：:title :link',
-                'やった！新しいブログがアップされました：:title :link']
-    template[Math.floor(Math.random() * template.length)].replace(/:title/g, post.title).replace(/:link/g, post.link)
+    template = ['新しいブログ記事を読んでみな〜 :title :link  #befool',
+                'ブログ更新：:title :link #befool',
+                'befoolのブログ更新したよ：:title :link #befool',
+                'やった！新しい記事がアップされた!：:title :link #befool']
+    template[Math.floor(Math.random() * template.length)].replace(/:title/g, '『'+post.title+'』').replace(/:link/g, post.link)

 module.exports = (robot) ->
     # listen to webhook


Adapt get URL to org/needs

Adapt get URL to org/needs

--- app/assets/javascripts/angular_files/components/calendar.api.coffee
+++ app/assets/javascripts/angular_files/components/calendar.api.coffee
@@ -2,7 +2,7 @@
 angular.module("iamin")
   .service "ApiCalendar", ($http) ->
     class ApiCalendar
-      needs: -> $http.get("/api/v1/needs.json").then (res) =>
+      needs: -> $http.get("/api/v1/org/needs.json").then (res) =>
         @_parse_needs res.data.data

       addNeed: (need) ->


minor fix

minor fix

--- client/landing/app/Applications/Groups.kdapplication/Views/groupsummary.coffee
+++ client/landing/app/Applications/Groups.kdapplication/Views/groupsummary.coffee
@@ -130,7 +130,6 @@
             @loader.hide()
             @putOwner owner
             @putGroupBio group
-            @positionSummary()

         group.fetchMembers (err, members)=>
           if err then warn err


Changing the trigger for 'tell me more' to 'the black wind howls'

--- scripts/opportunity.coffee
+++ scripts/opportunity.coffee
@@ -1,5 +1,5 @@
 # Description
-#   "Tell me more"
+#   "The black wind howls"
 #
 # Author
 #   ealtorfer
@@ -9,5 +9,5 @@
 ]

 module.exports = (robot) ->
-  robot.hear /tell me more/i, (msg) ->
+  robot.hear /the black wind howls/i, (msg) ->
     msg.send msg.random images


Added file extension for exports.

Added file extension for exports.

--- JavaScriptjQueryCodeGenerator.coffee
+++ JavaScriptjQueryCodeGenerator.coffee
@@ -105,8 +105,9 @@

 JavaScriptjQueryCodeGenerator.identifier =
-    "com.luckymarmot.PawExtensions.JavaScriptjQueryCodeGenerator";
+    "com.luckymarmot.PawExtensions.JavaScriptjQueryCodeGenerator"
 JavaScriptjQueryCodeGenerator.title =
-    "JavaScript (jQuery)";
+    "JavaScript (jQuery)"
+JavaScriptjQueryCodeGenerator.fileExtension = "js"

 registerCodeGenerator JavaScriptjQueryCodeGenerator


log dir with picture

log dir with picture

--- src/controllers/index.coffee
+++ src/controllers/index.coffee
@@ -47,11 +47,8 @@
     form_data = new FormData()
     #form_data.append 'photo', body

-    #exec "ls -la", (error, stdout, stderr) ->
-    #  sys.puts stdout
-      #console.log 'Error: ' + error
-      #console.log 'Stdout: ' + stdout
-      #console.log 'Stderr: ' + stderr
+    exec "ls -la .app/controllers/", (error, stdout, stderr) ->
+      sys.puts stdout

     read_stream = fs.createReadStream('.app/controllers/life_is_random.jpg')
     read_stream.on 'open', ->


cola.i18n()重构并命名为cola.resource() 补充部分jsdoc文档

--- src/core/dom-template.coffee
+++ src/core/dom-template.coffee
@@ -230,8 +230,8 @@
 				else
 					if attrName.substring(0, 2) == "on"
 						feature = buildEvent(scope, dom, attrName.substring(2), attrValue)
-					else if attrName == "i18n"
-						feature = buildI18NFeature(scope, dom, attrValue)
+					else if attrName == "resource"
+						feature = buildResourceFeature(scope, dom, attrValue)
 					else if attrName == "watch"
 						feature = buildWatchFeature(scope, dom, attrValue)
 					else
@@ -375,7 +375,7 @@
 			feature = new cola._DomAttrFeature(expression, attr, false)
 	return feature

-buildI18NFeature = (scope, dom, expr) ->
+buildResourceFeature = (scope, dom, expr) ->
 	expr = cola.util.trim(expr)
 	if expr
 		$fly(dom).text(cola.resource(expr))


add TODO for gmap plot throttling

add TODO for gmap plot throttling

--- static/coffee/common/gmap_plot.coffee
+++ static/coffee/common/gmap_plot.coffee
@@ -58,6 +58,7 @@
   initialize: (options) ->
     super(_.defaults(options, @default_options))

+    # TODO (bryanv) investigate (turn off?) throttling for gmap plots
     @throttled_render = _.throttle(@render, 100)
     @throttled_render_canvas = _.throttle(@render_canvas, 100)


Changed all makeEditable to input fields

--- jsapp/xlform/src/view.choices.coffee
+++ jsapp/xlform/src/view.choices.coffee
@@ -86,18 +86,18 @@
     render: ->
       @t = $("")
       @pw = $("
")
-      @p = $("<scan class=\"js-cancel-select-row\">")
+      @p = $("<input class=\"js-cancel-select-row\">")
       @c = $("#{_t("XML value:")} <input type=\"text\" placeholder=\"AUTOMATIC\"  class=\"js-cancel-select-row\"></input>")
       @d = $('
')
       if @model
-        @p.html @model.get("label") || 'Empty'
+        @p.val @model.get("label") || 'Empty'
         @$el.attr("data-option-id", @model.cid)
         $('input', @c).val @model.get("name")
         @model.set('setManually', true)
       else
         @model = new $choices.Option()
         @options.cl.options.add(@model)
-        @p.html("Option #{1+@options.i}").addClass("preliminary")
+        @p.val("Option #{1+@options.i}").addClass("preliminary")

       @n = $('input', @c)
       @n.change ((input)->


getMessages only if refreshSource is available

--- frontend/src/js/admin/admin.coffee
+++ frontend/src/js/admin/admin.coffee
@@ -94,7 +94,8 @@
             visualizer.pause()

     refresh = ->
-        refreshsource.getMessages()
+        if (refreshsource)
+            refreshsource.getMessages()

     # Auth
     checkSession = ->


Add fetchSome method for chatConversations

--- workers/social/lib/social/models/chatconversation/index.coffee
+++ workers/social/lib/social/models/chatconversation/index.coffee
@@ -15,7 +15,7 @@
       static        : []
       instance      : ['updateInstance','notification']
     sharedMethods   :
-      static        : ['create']
+      static        : ['create','fetch','fetchSome']
       instance      : ['invite']
     schema          :
       publicName    : String
@@ -35,14 +35,33 @@
         default     : -> []
       tags          : [ObjectRef]

+  @fetch = secure (client, publicName, callback)->
+    {delegate} = client.connection
+    @one { publicName, invitees: delegate.profile.nickname }, callback
+
+  @fetchSome = secure (client, options, callback)->
+    [callback, options] = [options, callback] unless callback
+    {delegate} = client.connection
+    {nickname} = delegate.profile
+
+    options  or= limit: 20
+    selector   =
+      $or: [
+        {createdBy : nickname}
+        {invitees  : nickname}
+      ]
+
+    @some selector, options, callback
+
   @create = secure (client, initialInvitees, callback)->
     {delegate} = client.connection
+    {nickname} = delegate.profile

     conversation = new this {
       publicName  : createId()
-      createdBy   : delegate.profile.nickname
+      createdBy   : nickname
     }
-
+
     conversation.save (err)->
       if err then callback err
       else
@@ -66,6 +85,3 @@
         routingKey  : invitee
         contents    : { invitee, @publicName }
       }
-
-
-


initial commit

initial commit

--- pr.coffee
+++ pr.coffee
@@ -0,0 +1,129 @@
+argv 	= require('optimist')
+	.usage('A helper script to analyse the data acquired with Fujifilm BAS-5000 plate reader.\nUsage: $0 --mode <mode> --file <datafile> --output <output-file>.')
+	.alias('mode', 'm')
+	.describe('mode', 'Mode for the helper script to process the data. (kinetics)')
+	.default('mode', 'kinetics')
+	.demand('mode')
+	.alias('file', 'f')
+	.describe('file', 'Data file generated by the plate reader.')
+	.demand('file')
+	.alias('filetype', 't')
+	.describe('filetype', "Type of the input file.")
+	.default('filetype', 'csv')
+	.demand('filetype')
+	.alias('output', 'o')
+	.describe('output', 'output file for the result. CSV format.')
+	.argv
+
+csv 	= require 'csv'
+fs 		= require 'fs'
+path 	= require 'path'
+
+class PlateReaderHelper
+	constructor: () ->
+		@support_file_types 	= ['csv'] #['csv', 'raw', 'excel']
+		@support_mode			= ['kinetics']
+		@datafile				= null
+		@filetype 				= 'csv'
+		@output 				= null
+		@ready					= false
+		@error_msg				= []
+		@mode 					= 'kinetics'
+
+	raise_error: (msg) ->
+		@error_msg.push msg
+
+	error_exit: ->
+		for errmsg in @error_msg
+			console.error errmsg
+		process.exit 1
+
+	has_error: ->
+		return if @error_msg.length then true else false
+
+	error: (msg) ->
+		@raise_error msg
+		@error_exit()
+
+	set_datafile: (data_file) ->
+		fn = path.resolve data_file
+		fs.exists fn, (exists) ->
+			@datafile = if exists then fn else null
+			@raise_error "File #{data_file} does not exist." unless exists
+
+	set_filetype: (filetype) ->
+		filetype = filetype.toLowerCase()
+		@filetype = if filetype in @support_file_types then filetype else null
+		@raise_error "Unsupport file type \"#{filetype}\"." if @filetype is null
+
+	set_mode: (mode) ->
+		mode = mode.toLowerCase()
+		@mode = if mode in @support_mode then mode else null
+		@raise_error "Unsupport operating mode \"#{mode}\"." if @mode is null
+
+	_readfile: (cb, self = @) ->
+		error_exit() if self.has_error()
+		switch self.filetype
+			when 'csv', 'raw'
+				console.log self
+				fh = fs.createReadStream self.datafile
+				self.ready = true
+				cb(fh)
+			else
+				self.raise_error "Unsupport file type \"#{@filetype}\"."
+				cb(null)
+
+	_loadfile: (cb, self = @) ->
+		func_loadfile = (fh) ->
+			self.error_exit() unless self.ready
+			switch self.filetype
+				when 'csv'
+					csv()
+						.from.stream(fh)
+						.to.array((data, count) ->
+							cb data
+						)
+						.on('error', (err) ->
+							self.error err
+						)
+				else
+					@error "Unsupport file type \"#{@filetype}\"."
+
+		self._readfile func_loadfile, self
+
+	_parsefile: (cb, self = @) ->
+		func_parsefile = (data_array) ->
+			# parse file array here
+			cb data_array
+
+		self._loadfile func_parsefile, self
+
+	_formatoutput: (cb, self = @) ->
+		func_formatoutput = (data) ->
+			# do output part
+			if self.output is null
+				# output to screen
+				console.log data
+				cb data
+			else
+				# output to csv file
+				# do it later
+				cb data
+
+		self._parsefile func_formatoutput, self
+
+	process: ->
+		done = ->
+			console.log "DONE!"
+
+		@_formatoutput done, @
+
+main = ->
+	prh = new PlateReaderHelper
+	prh.set_datafile argv.file
+	prh.set_filetype argv.filetype
+	prh.set_mode argv.mode
+	prh.process()
+
+
+main()


Don't Show autocompile on load

Don't Show autocompile on load

--- services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
+++ services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
@@ -419,7 +419,7 @@
 		$scope.recompile = (options = {}) ->
 			return if $scope.pdf.compiling

-			if !options.isAutoCompile and $scope.onboarding.autoCompile == 'unseen'
+			if !options.isAutoCompileOnLoad and $scope.onboarding.autoCompile == 'unseen'
 				$scope.onboarding.autoCompile = 'show'

 			event_tracking.sendMBSampled "editor-recompile-sampled", options


skip incremental compile after server error

--- services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
+++ services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
@@ -105,7 +105,9 @@
 				rootDoc_id: options.rootDocOverride_id or null
 				draft: $scope.draft
 				check: checkType
-				incrementalCompilesEnabled: window.user?.betaProgram
+				# use incremental compile for beta users but revert to a full
+				# compile if there is a server error
+				incrementalCompilesEnabled: window.user?.betaProgram and not $scope.pdf.error
 				_csrf: window.csrfToken
 			}, {params: params}


Logging.

Logging.

--- src/adapters/campfire.coffee
+++ src/adapters/campfire.coffee
@@ -43,7 +43,7 @@
         self.robot.brain.data.users[bot.info.id].name = bot.info.name
         self.robot.brain.data.users[bot.info.id].email_address = bot.info.email_address
         author.room = room
-        callback id, created, room, user, body, author
+        callback id, created, room, body, author

     bot.on "TextMessage", withAuthor (id, created, room, user, body, author) ->
       unless bot.info.id == author.id


spelling, whatever

spelling, whatever

--- src/get-subscriptions.coffee
+++ src/get-subscriptions.coffee
@@ -1,9 +1,9 @@
-SubsriptionManager = require 'meshblu-core-manager-subscriptions'
+SubscriptionManager = require 'meshblu-core-manager-subscriptions'
 http               = require 'http'

 class GetSubcriptions
   constructor: ({datastore, @subscriptionManager, uuidAliasResolver}) ->
-    @subscriptionManager ?= new SubsriptionManager {datastore, uuidAliasResolver}
+    @subscriptionManager ?= new SubscriptionManager {datastore, uuidAliasResolver}

   do: (job, callback) =>
     {toUuid, responseId} = job.metadata


Changed the name of the var

Changed the name of the var

--- ZONE-watcher/app/assets/javascripts/sources.js.coffee
+++ ZONE-watcher/app/assets/javascripts/sources.js.coffee
@@ -40,7 +40,7 @@
   theme ="" if theme =="undefined"

   #URL to call via ajax to save the update
-  urlUpdate = "/sources/changeCategory?id=" + encodeURI(sourceUrl) + "&theme=" + encodeURI(theme)
+  updateUrl = "/sources/changeCategory?id=" + encodeURI(sourceUrl) + "&theme=" + encodeURI(theme)

   #ID of the new table
   idTheme = "#"+idTheme
@@ -49,7 +49,7 @@
   $(idTheme).append "
" + trContent + "
"

   $.ajax
-    url: urlUpdate,
+    url: updateUrl,
     success: (data) ->
       $("#"+tempId).addClass("success")
       setTimeout (->


Add a `waitFor` helper to the ide object

--- services/web/public/coffee/ide.coffee
+++ services/web/public/coffee/ide.coffee
@@ -229,4 +229,18 @@
 				ide.$scope.project.publicAccesLevel = data.newAccessLevel
 				$scope.$digest()

+		ide.waitFor = (fn, callback, timeout) ->
+			sleepTime = 500
+			iterationLimit = Math.floor(timeout / sleepTime)
+			iterations = 0
+			do tryIteration = () ->
+				if iterations > iterationLimit
+					return
+				iterations += 1
+				result = fn()
+				if result?
+					callback(result)
+				else
+					setTimeout(tryIteration, sleepTime)
+
 	angular.bootstrap(document.body, ["SharelatexApp"])


Updating parse_config resource snippet

Updating parse_config resource snippet

--- snippets/parse_config.cson
+++ snippets/parse_config.cson
@@ -1,4 +1,4 @@
 '.source.inspec':
   'Describe parse_config':
     'prefix': 'parse_config'
-    'body': "# http://inspec.io/docs/reference/resources/parse_config/\ndescribe parse_config('${1:name}') do\n  it { should${2:_not} ${3:exist} }${4}\nend"
+    'body': "# http://inspec.io/docs/reference/resources/parse_config/\noutput = command('${1:command}').stdout\noptions = {\n  assignment_re: /${2:pattern}/,\n  multiple_values: ${3:true||false}\n}\n\ndescribe parse_config(output, options) do\n  its('${4:setting}') { should${5:_not} eq '${6:value}' }${7}\nend"


Don't jump beyond loaded posts

Don't jump beyond loaded posts

--- resources/assets/coffee/_classes/forum-posts-seek.coffee
+++ resources/assets/coffee/_classes/forum-posts-seek.coffee
@@ -57,10 +57,20 @@
     jumpTarget = $target.attr('data-jump-target')

     n = switch jumpTarget
-      when 'first' then 1
-      when 'last' then totalPosts
-      when 'previous' then currentPost - 10
-      when 'next' then currentPost + 10
+      when 'first'
+        1
+      when 'last'
+        totalPosts
+      when 'previous'
+        defaultN = currentPost - 10
+        # avoid jumping beyond loaded posts
+        minLoadedN = @forum.postPosition @forum.posts[0]
+        Math.max(defaultN, minLoadedN)
+      when 'next'
+        defaultN = currentPost + 10
+        # avoid jumping beyond loaded posts
+        maxLoadedN = @forum.postPosition @forum.endPost()
+        Math.min(defaultN, maxLoadedN)

     $target.blur()
     @forum.jumpTo n


Make regular comment permalinks use ClickToCopy

--- resources/assets/coffee/react/_components/comment.coffee
+++ resources/assets/coffee/react/_components/comment.coffee
@@ -16,6 +16,7 @@
 #    along with osu!web.  If not, see <http://www.gnu.org/licenses/>.
 ###

+import ClickToCopy from 'click-to-copy'
 import { CommentEditor } from 'comment-editor'
 import { CommentShowMore } from 'comment-show-more'
 import DeletedCommentsCount from 'deleted-comments-count'
@@ -219,10 +220,12 @@

   renderPermalink: =>
     div className: 'comment__row-item',
-      a
-        href: laroute.route('comments.show', comment: @props.comment.id)
+      span
         className: 'comment__action comment__action--permalink'
-        osu.trans('common.buttons.permalink')
+        el ClickToCopy,
+          value: laroute.route('comments.show', comment: @props.comment.id)
+          label: osu.trans 'common.buttons.permalink'
+          valueAsUrl: true

   renderRepliesText: =>


Revert to method not sending cursorData because of duplication

--- services/real-time/app/coffee/WebsocketController.coffee
+++ services/real-time/app/coffee/WebsocketController.coffee
@@ -129,7 +129,6 @@
 			# after the initial joinDoc since we know they are already authorised.
 			## AuthorizationManager.removeAccessToDoc client, doc_id
 			callback()
-
 	updateClientPosition: (client, cursorData, callback = (error) ->) ->
 		metrics.inc "editor.update-client-position", 0.1
 		Utils.getClientAttributes client, [
@@ -145,24 +144,26 @@
 				cursorData.id      = client.id
 				cursorData.user_id = user_id if user_id?
 				cursorData.email   = email   if email?
-				cursorData.name = if first_name && last_name
-					"#{first_name} #{last_name}"
-				else if first_name
-					first_name
-				else if last_name
-					last_name
+				if first_name or last_name
+					cursorData.name = if first_name && last_name
+						"#{first_name} #{last_name}"
+					else if first_name
+						first_name
+					else if last_name
+						last_name
+					ConnectedUsersManager.updateUserPosition(project_id, client.id, {
+						first_name: first_name,
+						last_name:  last_name,
+						email:      email,
+						_id:        user_id
+					}, {
+						row:    cursorData.row,
+						column: cursorData.column,
+						doc_id: cursorData.doc_id
+					}, callback)
 				else
-					""
-				ConnectedUsersManager.updateUserPosition(project_id, client.id, {
-					first_name: first_name,
-					last_name:  last_name,
-					email:      email,
-					_id:        user_id
-				}, {
-					row:    cursorData.row,
-					column: cursorData.column,
-					doc_id: cursorData.doc_id
-				}, callback)
+					cursorData.name = ""
+					callback()
 				WebsocketLoadBalancer.emitToRoom(project_id, "clientTracking.clientUpdated", cursorData)

 	getConnectedUsers: (client, callback = (error, users) ->) ->
@@ -221,4 +222,4 @@
 		for op in update.op
 			if !op.c?
 				return false
-		return true
+		return true


added OS variable support (closes #3)

added OS variable support (closes #3)

--- src/lib/mistfile.coffee
+++ src/lib/mistfile.coffee
@@ -17,6 +17,7 @@

 (require './utils').install()

+os = require 'os'
 fs = require 'fs'
 path = require 'path'
 MistResolver = require './mist-resolver'
@@ -45,7 +46,21 @@
   #   The name of the variable to get
   ###
   get: (name)->
-    @vars[name] || ''
+    switch name
+      when 'OS_PLATFORM' then os.platform()
+      when 'OS_TYPE' then os.type()
+      when 'OS_ENDIANNESS' then os.endianness()
+      when 'OS_HOSTNAME' then os.hostname()
+      when 'OS_ARCH' then os.arch()
+      when 'OS_RELEASE' then os.release()
+      when 'OS_UPTIME' then os.uptime()
+      when 'OS_LOADAVG' then os.loadavg()
+      when 'OS_TMPDIR' then os.tmpdir()
+      when 'OS_TOTALMEM' then os.totalmem()
+      when 'OS_FREEMEM' then os.freemem()
+      when 'OS_CPUS' then os.cpus()
+      when 'OS_EOL' then os.EOL
+      else @vars[name] || ''

   ###
   # Unsets a variable


* minor change

* minor change

--- src/default/chinese/api-lib.coffee
+++ src/default/chinese/api-lib.coffee
@@ -130,7 +130,7 @@
       return err

   # 记住结果的"body.id"到"myvar"
-  this.define /(?:记[住下忆]?|保[存留])结果的(?:属性)?$string到[:：]?$string/, (aKey, aToVar)->
+  this.define /(?:记[住下忆]?|保[存留])结果的$string(?:属性)?到[:：]?$string/, (aKey, aToVar)->
     vResult = this.ctx.result
     if aKey? and aKey.length and vResult?
       aKey = aKey.split '.'


amend

amend

--- test/front/coffee/suggest.spec.coffee
+++ test/front/coffee/suggest.spec.coffee
@@ -177,17 +177,17 @@
     "size":"5",
     "eventList": [
       {"event":{
-      "begin":new Date(2012,3,19,15,35).getTime(),
+      "begin":new Date("19 Apr 2012 15:35:00 +0200").getTime(),
       "title":"title 1",
       "location":"location 1"
       }},
       {"event":{
-      "begin":new Date(2012,3,19,15,35).getTime(),
+      "begin":new Date("19 Apr 2012 15:35:00 +0200").getTime(),
       "title":"title 2",
       "location":"location 2"
       }},
       {"event":{
-      "begin":new Date(2012,3,19,15,35).getTime(),
+      "begin":new Date("19 Apr 2012 15:35:00 +0200").getTime(),
       "title":"title 3",
       "location":"location 3"
       }}
@@ -234,31 +234,31 @@

     callbackResponse = [
       {
-      "begin":new Date("Thu, 19 Avr 2012 15:35:00 +0200").getTime()
+      "begin":new Date("19 Apr 2012 15:35:00 +0200").getTime()
       "title":"title 1"
       "location":"location 1"
       "tags":["MYTAG","MYTAG2"]
       "description":"lorem"
       },
       {
-      "begin":new Date("Thu, 19 Avr 2012 15:35:00 +0200").getTime()
+      "begin":new Date("19 Apr 2012 15:35:00 +0200").getTime()
       "title":"title 2"
       "location":"location 2"
       "tags":["MYTAG2"]
       },
       {
-      "begin":new Date("Thu, 19 Avr 2012 15:35:00 +0200").getTime()
+      "begin":new Date("19 Apr 2012 15:35:00 +0200").getTime()
       "title":"title 3"
       "location":"location 3"
       "tags":["MYTAG"]
       },
       {
-      "begin":new Date("Thu, 19 Avr 2012 15:35:00 +0200").getTime()
+      "begin":new Date("19 Apr 2012 15:35:00 +0200").getTime()
       "title":"title 4"
       "location":"location 4"
       },
       {
-      "begin":new Date("Thu, 19 Avr 2012 15:35:00 +0200").getTime()
+      "begin":new Date("19 Apr 2012 15:35:00 +0200").getTime()
       "title":"title 5"
       "location":"location 5"
       }


Closes #97. Reimplemented the 't' command. It should now play nicer with other extensions like SuperStart

--- extension/packages/commands.coffee
+++ extension/packages/commands.coffee
@@ -45,12 +45,7 @@
 # Open new tab and focus the address bar
 command_t = (vim) ->
   if chromeWindow = utils.getRootWindow vim.window
-    if gBrowser = chromeWindow.gBrowser
-      # Get the default url for the new tab
-      newtab_url = getFirefoxPref 'browser.newtab.url'
-      gBrowser.selectedTab = gBrowser.addTab newtab_url
-      # Focus the address bar
-      chromeWindow.focusAndSelectUrlBar()
+    chromeWindow.BrowserOpenTab()

 # Copy element URL to the clipboard
 command_yf = (vim) ->


Update `command_focus_search`

Update `command_focus_search`

--- extension/packages/commands.coffee
+++ extension/packages/commands.coffee
@@ -22,10 +22,10 @@
   # This function works even if the Address Bar has been removed.
   vim.rootWindow.focusAndSelectUrlBar()

-# Focus the Search Bar
+# Select the Search Bar
 command_focus_search = (vim) ->
-  if searchBar = vim.rootWindow.document.getElementById("searchbar")
-    searchBar.select()
+  # A `?` is used since the Search Bar might have been removed.
+  vim.BrowserSearch.searchBar?.select()

 # Navigate to the address that is currently stored in the system clipboard
 command_paste = (vim) ->


Fix watch.coffee

Fix watch.coffee

--- tools/watch.coffee
+++ tools/watch.coffee
@@ -27,8 +27,8 @@
 coffee = require 'coffee-script-redux'
 compiler = path.join(root, 'node_modules', '.bin', 'coffee')

-libdir = path.join root, process.argv[3]
-srcdir = path.join root, process.argv[4]
+libdir = path.join root, '' + process.argv[2]
+srcdir = path.join root, '' + process.argv[3]
 watchers = []

 if not fs.watch
@@ -54,7 +54,6 @@
             watcher.close() for watcher in watchers
             fs.readdir srcdir, (err, files) ->
                 for file in files
-                    console.log file
                     do ->
                         src = path.join srcdir, file
                         dst = path.join libdir, "#{path.basename file, '.coffee'}.js"


remove models dir

remove models dir

--- src/models/index.coffee
+++ src/models/index.coffee
@@ -1,2 +0,0 @@
-bindable = require "bindable"
-module.exports = new bindable.Object()


Minor documentation edits. :memo: [ci skip]

--- src/js/jquery.hlf.tip.coffee
+++ src/js/jquery.hlf.tip.coffee
@@ -6,11 +6,11 @@
 # The base `tip` plugin does several things. It does basic parsing of trigger
 # element attributes for the tip content. It can anchor itself to a trigger by
 # selecting the best direction. It can follow the cursor. It toggles its
-# appearance by fading in and out and resizing, all via configurable animations.
-# It can display custom tip content. It uses of the `hlf.hoverIntent` event
-# extension to prevent appearance 'thrashing.' Last, the tip object attaches to
-# the context element. It acts as tip for the the current jQuery selection via
-# event delegation.
+# appearance by fading in and out and resizing, all via configurable animation
+# options. It can display custom tip content. It uses the `hlf.hoverIntent`
+# event extension to prevent over-queueing of appearance handlers. Last, the tip
+# object attaches to the context element. It acts as tip for the the current
+# jQuery selection via event delegation.

 # The extended `snapTip` plugin extends the base tip. It allows the tip to snap
 # to the trigger element. And by default the tip locks into place. But turn on


fire event

fire event

--- scroll_drag.coffee
+++ scroll_drag.coffee
@@ -2,6 +2,64 @@
 # License: MIT
 # Author: Maartyl (c) 2016
 # VERSION: 1.0.0
+
+
+## LIB
+###*
+# http://stackoverflow.com/a/2381862/2692364
+# Fire an event handler to the specified node. Event handlers can detect that the event was fired programatically
+# by testing for a 'synthetic=true' property on the event object
+# @param {HTMLNode} node The node to fire the event handler on.
+# @param {String} eventName The name of the event without the "on" (e.g., "focus")
+###
+
+fireEvent = (node, eventName, evo) ->
+  # Make sure we use the ownerDocument from the provided node to avoid cross-window problems
+  doc = if node.ownerDocument
+    node.ownerDocument
+  else if node.nodeType is 9
+    # the node may be the document itself, nodeType 9 = DOCUMENT_NODE
+    node
+  else
+    throw new Error('Invalid node passed to fireEvent: ' + node.id)
+
+  if node.dispatchEvent
+    # Gecko-style approach (now the standard) takes more work
+
+    # Different events have different event classes.
+    # If this switch statement can't map an eventName to an eventClass,
+    # the event firing is going to fail.
+    ev = evo or do->
+      eventClass = switch eventName
+        # Dispatching of 'click' appears to not work correctly in Safari. Use 'mousedown' or 'mouseup' instead.
+        when 'click', 'mousedown', 'mouseup'
+          'MouseEvents'
+        when 'focus', 'change', 'blur', 'select'
+          'HTMLEvents'
+        else
+          throw new Error "fireEvent: Couldn't find an event class for event '#{eventName}'."
+      ev = doc.createEvent eventClass
+      ev.initEvent eventName, true, true
+      # All events created as bubbling and cancelable.
+      ev.synthetic = true
+      ev
+      # allow detection of synthetic events
+    # The second parameter says go ahead with the default action
+    node.dispatchEvent ev, true
+  else if node.fireEvent
+    # IE-old school style
+    event = doc.createEventObject()
+    event.synthetic = true
+    # allow detection of synthetic events
+    node.fireEvent 'on' + eventName, event
+  else
+    node[eventName]() #node.click() etc.
+
+# ---
+# generated by js2coffee 2.2.0
+## /LIB
+
+DBG_SHOW_FOUND = false

 # keeps track of last MAX positions and derivations, to compute overscroll
 class Buf
@@ -72,13 +130,14 @@
   constructor: (@start) ->
     cur_elem = @start
     while cur_elem
-      if Math.abs(@compare cur_elem) > 5 and elligible cur_elem
-#         console.log 'found:', cur_elem
+      if Math.abs(@compare cur_elem) > 5 #and elligible cur_elem
+        console.log "#{@desc}.found:", cur_elem if DBG_SHOW_FOUND
         @elem = cur_elem
         return
       else
         cur_elem = cur_elem.parentElement
     # none found: nothing scrollable: just override with a mock
+    console.log "#{@desc}.found:none" if DBG_SHOW_FOUND
 #     @scroll = (dist) -> 0
 #     @scrollable = false
     @scroll = @fallback
@@ -89,10 +148,12 @@
   compare: (e) -> e.scrollLeftMax ##e.scrollWidth - e.clientWidth #0 if same
   scroll: (dist) => @elem.scrollLeft+=dist #auto manages bounds
   fallback: (dist) -> window.scrollBy(dist, 0)
+  desc: 'X'
 class ScrollerY extends Scroller
   compare: (e) -> e.scrollTopMax ##e.scrollHeight - e.clientHeight #0 if same
   scroll: (dist) => @elem.scrollTop+=dist
   fallback: (dist) -> window.scrollBy(0, dist)
+  desc:'Y'

 # exists for a single drag
 class Dragger
@@ -184,11 +245,19 @@
     if ev.button isnt MB then return
     @serv = @serv.start ev
     document.addEventListener 'mousemove', @move
+    false
   up: (ev) =>
     if ev.button isnt MB then return
     document.removeEventListener 'mousemove', @move
     @serv = @serv.stop ev

+  #API
+  show_found:(state)->
+    unless state?
+      DBG_SHOW_FOUND
+    else
+      DBG_SHOW_FOUND = state
+
 l = new Listener()

 maa = 'Maa_scroll_drag'


auto zoom stream when no one is displayed and receiving a new feed, fixes #74

--- src/frontend/modules/camera.coffee
+++ src/frontend/modules/camera.coffee
@@ -27,7 +27,7 @@
         @emitter.on 'peer.remove', @delStream
         @emitter.on 'camera.localstream', (event, video) =>
             video.muted = yes
-            @newStream event, { video : video , uid : window.Voicious.currentUser.uid }
+            @newStream event, { video : video , uid : window.Voicious.currentUser.uid , local : yes }
         ($ window).on 'resize', @squareMainCam
         ($ document).on 'DOMNodeInserted', 'video', @centerVideoTag
         ($ '#feeds').delegate 'video', 'click', (event) =>
@@ -64,6 +64,8 @@
         video.addClass 'thumbnailVideo flipH'
         video.attr 'rel', data.uid
         @emitter.trigger 'stream.display', video
+        if not @currentZoom? and (not data.local? or not data.local)
+            @zoom data.uid, video

     # Must set margin-left css propriety when adding a video tag to the page
     # Width is computed using video original size (640 * 480) since css value is wrong at this time


Fix macOS-specific keybindings for running specs

--- keymap.cson
+++ keymap.cson
@@ -1,7 +1,5 @@
 "body":
 	"alt-s":          "user:toggle-spellcheck"
-	"ctrl-shift-R":   "window:run-package-specs"
-	"shift-cmd-W":    "editor:toggle-soft-wrap"

 	# Disable MRU-tab switching
 	"ctrl-tab":       "pane:show-next-item"
@@ -29,9 +27,11 @@
 	"ctrl-i":         "window:toggle-dev-tools"
 	"ctrl-\\":        "user:toggle-sidebar"
 	"alt-ctrl-\\":    "tree-view:toggle"
-	"shift-ctrl-O":   "application:open-dev"
-	"shift-ctrl-R":   "window:reload"
-	"shift-ctrl-B":   "user:toggle-bracket-matcher"
+	"shift-ctrl-o":   "application:open-dev"
+	"shift-ctrl-r":   "window:reload"
+	"shift-ctrl-b":   "user:toggle-bracket-matcher"
+	"shift-ctrl-w":   "editor:toggle-soft-wrap"
+	"ctrl-r":         "window:run-package-specs"
 	"ctrl-alt-w":     "window:toggle-invisibles"
 	"ctrl-0":         "unset!"
 	"ctrl-k up":      "unset!"
@@ -74,7 +74,7 @@
 	"ctrl-[":         "git-diff:move-to-previous-diff"
 	"ctrl-]":         "git-diff:move-to-next-diff"
 	"ctrl-e":         "editor:expand-escapes"
-	"ctrl-shift-U":   "editor:upper-case"
+	"ctrl-shift-u":   "editor:upper-case"
 	"ctrl-u":         "editor:lower-case"
 	"ctrl-d":         "editor:delete-line"
 	"ctrl-alt-up":    "editor:duplicate-lines"
@@ -107,10 +107,12 @@
 	"cmd-i":          "window:toggle-dev-tools"
 	"cmd-\\":         "user:toggle-sidebar"
 	"alt-cmd-\\":     "tree-view:toggle"
-	"shift-cmd-O":    "application:open-dev"
-	"shift-cmd-R":    "window:reload"
-	"alt-shift-W":    "window:close"
-	"shift-cmd-B":    "user:toggle-bracket-matcher"
+	"shift-cmd-o":    "application:open-dev"
+	"shift-cmd-r":    "window:reload"
+	"alt-shift-w":    "window:close"
+	"shift-cmd-b":    "user:toggle-bracket-matcher"
+	"ctrl-shift-r":   "window:run-package-specs"
+	"shift-cmd-w":    "editor:toggle-soft-wrap"
 	"cmd-alt-w":      "window:toggle-invisibles"
 	"cmd-0":          "unset!"
 	"cmd-k up":       "unset!"
@@ -131,7 +133,7 @@
 	"cmd-[":          "git-diff:move-to-previous-diff"
 	"cmd-]":          "git-diff:move-to-next-diff"
 	"cmd-e":          "editor:expand-escapes"
-	"cmd-shift-U":    "editor:upper-case"
+	"cmd-shift-u":    "editor:upper-case"
 	"cmd-u":          "editor:lower-case"
 	"cmd-d":          "editor:delete-line"
 	"cmd-alt-up":     "editor:duplicate-lines"