ahsan is replacing prem

ahsan is replacing prem

--- scripts/offshore.coffee
+++ scripts/offshore.coffee
@@ -14,6 +14,7 @@
 #   hubot who will prevail
 #   hubot who will unleash righteous vengeance upon evil
 #   hubot who will be triumphant
+#   hubot when Prem is gone and we are lost in darkness
 #   hubot what is Chris doing today
 #   hubot Chris excuse
 #   hubot goodbye Prem
@@ -81,3 +82,6 @@

   robot.respond /when all hope is lost and darkness is upon us/i, (msg)->
     msg.send "The offshore team shall know no fear! :ahsan: :sachin: :prem: :kostas: :chris: :drmatt:"
+
+  robot.respond /when Prem is gone and we are lost in darkness/i, (msg)->
+    msg.send "The offshore team shall know no fear! The offshore team will be triumphant! :ahsan: :sachin: :prem: :kostas: :chris: :drmatt:"


fix indent

fix indent

--- scripts/totsuzen.coffee
+++ scripts/totsuzen.coffee
@@ -27,9 +27,9 @@
     return until message.length
     length = Math.floor eastasianwidth.length(message) / 2

-  suddendeath = [
-    "＿#{strpad '人', length + 2}＿"
-    "＞　#{message}　＜"
-    "￣Y#{strpad '^Y', length}￣"
-  ]
-  msg.send suddendeath.join "\n"
+    suddendeath = [
+      "＿#{strpad '人', length + 2}＿"
+      "＞　#{message}　＜"
+      "￣Y#{strpad '^Y', length}￣"
+    ]
+    msg.send suddendeath.join "\n"


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/controllable.coffee
+++ src/controllable.coffee
@@ -16,7 +16,7 @@
   # update the player
   player.update()

-  # the p
+  # the player is moved
   player.I.x
   # => 2.5 # half the player's width


Guard against null data in response

Guard against null data in response

--- src/course/model.coffee
+++ src/course/model.coffee
@@ -87,6 +87,7 @@
     @channel.emit('change')

   _onConfirmed:  (response) ->
+    throw new Error("response is empty in onConfirmed") if _.isEmpty(response)
     {data} = response
     if data?.to
       _.extend(@, data.to.course)
@@ -107,9 +108,10 @@
     @channel.emit('change')

   _onRegistered: (response) ->
+    throw new Error("response is empty in onRegistered") if _.isEmpty(response)
     {data} = response
-    _.extend(@, data)
-    @errors = data.errors
+    _.extend(@, data) if data
+    @errors = data?.errors
     # a freshly registered course doesn't contain the is_concept_coach flag
     @is_concept_coach = true
     response.stopErrorDisplay = true if @errors


load config before local modules...

load config before local modules...

--- server.coffee
+++ server.coffee
@@ -5,6 +5,12 @@
 request  = require 'request'
 { exec } = require 'child_process'
 program  = require 'commander'
+
+## Load config
+try
+    config = JSON.parse fs.readFileSync './config.json', 'utf8'
+catch e
+    config = {}

 florinda = require './system'
 brain    = require './brains'
@@ -20,12 +26,6 @@

 if program.cli
     program.nochat = program.silent = true
-
-## Load config
-try
-    config = JSON.parse fs.readFileSync './config.json', 'utf8'
-catch e
-    config = {}

 ## Make main objects global
 # yeah, I could keep passing stuff around...


remove troublesome compression requirement

--- server.coffee
+++ server.coffee
@@ -3,7 +3,7 @@
 express = require 'express'
 cookies = require 'cookie-parser'
 morgan = require 'morgan'
-compression = require 'compression'
+# compression = require 'compression'
 path = require 'path'
 winston = require 'winston'
 parser = require 'body-parser'


make bin compound compliant

make bin compound compliant

--- server.coffee
+++ server.coffee
@@ -1,8 +1,16 @@
 #!/usr/bin/env coffee

-app = module.exports = require('railway').createServer()
+app = module.exports = (params) ->
+    params = params || {}
+    # specify current dir as default root of server
+    params.root = params.root || __dirname
+    return require('compound').createServer(params)

 if not module.parent
-    port = process.env.PORT or 9202
-    app.listen port, "127.0.0.1"
-    console.log "Railway server listening on port %d within %s environment", port, app.settings.env
+    port = process.env.PORT || 9202
+    host = process.env.HOST || "127.0.0.1"
+    server = app()
+    server.listen port, host, ->
+        console.log(
+            "Compound server listening on %s:%d within %s environment",
+            host, port, server.set('env'))


timestamp in server log

timestamp in server log

--- server.coffee
+++ server.coffee
@@ -287,4 +287,4 @@
   process.on(signal, terminate)

 server.listen app.get('port'), app.get('ipaddr'), ->
-  console.log("Express server listening on port " + app.get('port'))
+  console.log(new Date().toString() + ": Express server listening on port " + app.get('port'))


check

check

--- server.coffee
+++ server.coffee
@@ -105,7 +105,7 @@
   shell.exec 'git add -A'
   shell.exec 'git commit -a -m "initial commit"'
   shell.exec 'git remote add origin git@github.com:communities/' + repo + '.git'
-  shell.exec 'git push origin master'
+  shell.exec 'sudo git push origin master'

 createGitRepo = (repo, callback) ->


debug ip binding

debug ip binding

--- server.coffee
+++ server.coffee
@@ -14,8 +14,8 @@
 app = module.exports = require('railway').createServer()

 if not module.parent
-  port = process.env.PORT or 9203, "127.0.0.1"
-  app.listen port
+  port = process.env.PORT or 9203
+  app.listen port, "127.0.0.1"
   console.log "CozyMail server listening on port %d within %s environment", port, app.settings.env

   # setup KUE


Repair in score registration

Repair in score registration

--- server.coffee
+++ server.coffee
@@ -187,6 +187,8 @@

 	# Handle points and statistics
 	# Modify user and team scores
+	client = args.players.split(', ')[0]
+	log "[onCapture()] " + client
 	beaconValue = beacon.get('captureValue')
 	modifyScore client, beaconValue
 	Db.shared.modify 'game', 'teams', nextOwner, 'captured', (v) -> v+1


Encode URI components on search querys

Encode URI components on search querys

- Closes #1268

--- app/talk/search.cjsx
+++ app/talk/search.cjsx
@@ -14,9 +14,9 @@

   for key, val of object
     if Array.isArray(val)
-      params.push "#{key}[]=#{val}"
+      params.push "#{key}[]=#{window.encodeURIComponent val}"
     else
-      params.push "#{key}=#{val}"
+      params.push "#{key}=#{window.encodeURIComponent val}"

   return params.join '&'


Don't write/convert unchanged .svg/.tex files (#70)

--- src/svgtiler.coffee
+++ src/svgtiler.coffee
@@ -776,6 +776,15 @@
   else
     'xlink:href'

+maybeWrite = (filename, data) ->
+  ## Writes data to filename, unless the file is already identical to data.
+  ## Returns whether the write actually happened.
+  try
+    if data == fs.readFileSync filename, encoding: 'utf8'
+      return false
+  fs.writeFileSync filename, data
+  true
+
 class Drawing extends Input
   hrefAttr: -> hrefAttr @settings
   load: (data) ->
@@ -804,7 +813,11 @@
           j--
     @data = data
   writeSVG: (mappings, styles, filename) ->
-    ## Default filename is the input filename with extension replaced by .svg
+    ## Generates SVG and writes to filename.
+    ## Default filename is the input filename with extension replaced by .svg.
+    ## Returns generated .svg filename, but only if it actually changed
+    ## (and thus needs to be converted to other desired formats);
+    ## otherwise returns null.
     unless filename?
       filename = path.parse @filename
       if filename.ext == '.svg'
@@ -814,9 +827,12 @@
       if (outputDir = @getOutputDir '.svg')?
         filename.dir = outputDir
       filename = path.format filename
-    console.log '->', filename
-    fs.writeFileSync filename, @renderSVG mappings, styles
-    filename
+    if maybeWrite filename, @renderSVG mappings, styles
+      console.log '->', filename
+      filename
+    else
+      console.log '->', filename, '(unchanged)'
+      null
   renderSVGDOM: (mappings, styles) ->
     ###
     Main rendering engine, returning an xmldom object for the whole document.
@@ -1090,9 +1106,12 @@
          outputDir?
         relativeDir = path.relative (outputDir ? '.'), (graphicDir ? '.')
       filename = path.format filename
-    console.log ' &', filename
-    fs.writeFileSync filename, @renderTeX filename, relativeDir
-    filename
+    if maybeWrite filename, @renderTeX filename, relativeDir
+      console.log ' &', filename
+      filename
+    else
+      console.log ' &', filename, '(unchanged)'
+      null

 class ASCIIDrawing extends Drawing
   @title: "ASCII drawing (one character per symbol)"
@@ -1150,7 +1169,9 @@
     path.format filename2
   writeSVG: (mappings, styles, filename) ->
     for drawing in @drawings
-      drawing.writeSVG mappings, styles, @subfilename '.svg', drawing
+      filename = drawing.writeSVG mappings, styles, @subfilename '.svg', drawing
+      continue unless filename?  # don't list unchanged .svg files
+      filename
   writeTeX: (filename) ->
     for drawing in @drawings
       subfilename = @subfilename '.svg_tex', drawing
@@ -1446,7 +1467,8 @@
           filenames = input.writeSVG mappings, styles
           input.writeTeX() if settings.texText
           ## Convert to any additional formats
-          convert filenames, formats, settings
+          if filenames? and filenames.length
+            convert filenames, formats, settings
         else if input instanceof SVGFile
           convert input.filename, formats, settings
   unless files


xss fix styling

xss fix styling

--- workers/social/lib/social/error.coffee
+++ workers/social/lib/social/error.coffee
@@ -1,10 +1,10 @@
+encoder = require 'htmlencode'
+
 # Includes message, type pairs
 KnownErrors =
   'Access denied' : 'AccessDenied'

 module.exports = class KodingError extends Error
-
-  encoder   = require 'htmlencode'

   constructor: (message, name, errorObject) ->


add comment why we have to set opts.view to null

--- src/template.coffee
+++ src/template.coffee
@@ -31,10 +31,8 @@
     constructor: (opts = {}, template) ->
         # options
         [template, opts] = [opts, {}] if typeof opts is 'function'
-        view = null
-        if typeof(opts.view) is not "undefined"
-            view = opts.view
-            opts.view = null
+        view = opts.view
+        opts.view = null # prevent infinite recursion
         # defaults
         opts.encoding ?= 'utf-8'
         opts.doctype ?= off
@@ -57,7 +55,7 @@
         # instantiate
         @xml = new ExtendedBuilder opts
         @xml.template = this
-        @xml.view     = view
+        @xml.view     = view
         # override query
         old_query = @xml.query
         @xml.query = (type, tag, key) ->


print figure editor json to javascript console (for demo)

--- app/assets/javascripts/dances.coffee
+++ app/assets/javascripts/dances.coffee
@@ -165,6 +165,7 @@
   $fig_ed = $(dom_ed).closest(".figure_edit")
   json = JSON.stringify( figure_editor_get_figure( $fig_ed ))
   id = trailing_number_from_string( $fig_ed.attr('id') )
+  console.log("figure #{id} has #{json}");
   $form = $fig_ed.closest("form").find(".figure_hidden_#{id}")
   $form.val(json)

@@ -173,14 +174,7 @@
   $(".who_edit").change((e) -> manage_who_change e.target)
   $(".move_edit").change((e) -> manage_move_change e.target)
   $(".figure_edit").each((i) -> initialize_figure_editor( this, i ))
+  # cool! change() gets called on contailers when children get edited!
   $(".figure_edit").change((e) -> sync_figure_editor(e.target))
-  # cool! change() gets called on contailers when children get edited!
-  #$(".figure_edit").change((e) ->
-    # log whats going on
-    # console.log( "editor string: "+JSON.stringify( figure_editor_get_figure( $(e.target).closest(".figure_edit") )))
-    #to debug the editor, uncomment the following line. It sets the last editor to be
-    # the value of the last editor to accept a change. It tests JSON and whatnot.
-    #figure_editor_set_figure($("#figure_edit_8"), figure_editor_get_figure( $(e.target).closest(".figure_edit") ))
-    # )


popularpost: fix bug where most recent returns duplicate posts

--- client/Social/Activity/views/messagepane.coffee
+++ client/Social/Activity/views/messagepane.coffee
@@ -33,15 +33,7 @@

     @fakeMessageMap = {}

-    @currentFilter  = MOST_LIKED
-    @skipItemCount  = 0
-
-    # returns created at timestamp of item, this is not an instance
-    # method due to edge case when we switch from most liked to most
-    # recent filter we want to get posts from top and not the usual
-    # pagination behavior of getting posts that happened after last
-    # post on the feed
-    @getItemTimestampFn = getItemTimestampFn
+    @setFilterOptions MOST_LIKED

     {socialapi} = KD.singletons
     @once 'ChannelReady', @bound 'bindChannelEvents'
@@ -63,6 +55,21 @@
       else
         @listController.getListView().on 'ItemWasAdded', @bound 'scrollUp'

+  setFilterOptions: (filter)->
+    @skipItemCount  = 0
+
+    if filter is MOST_LIKED
+      @currentFilter      = MOST_LIKED
+      @getItemTimestampFn = ->
+    else
+      @currentFilter      = MOST_RECENT
+
+      # returns created at timestamp of item, this is not an instance
+      # method due to edge case when we switch from most liked to most
+      # recent filter we want to get posts from top and not the usual
+      # pagination behavior of getting posts that happened after last
+      # post on the feed
+      @getItemTimestampFn = getItemTimestampFn

   bindInputEvents: ->

@@ -206,7 +213,9 @@
       @listController.removeAllItems()
       @listController.showLazyLoader()

-      @currentFilter = if data.active then MOST_LIKED else MOST_RECENT
+      filter = if data.active then MOST_LIKED else MOST_RECENT
+      @setFilterOptions filter
+
       @skipItemCount = 0

       @populate()
@@ -328,6 +337,7 @@
     if type is 'post'
     then KD.utils.defer -> callback null, [data]
     else appManager.tell 'Activity', 'fetch', options, (err, items)=>
+      @getItemTimestampFn = getItemTimestampFn

       # when we run out of posts in most liked list, we switch
       # to getting most recent posts in DESC order
@@ -348,15 +358,13 @@

     return  unless last

-    from = @getItemTimestampFn(last)
+    from            = @getItemTimestampFn(last)
     @skipItemCount += ITEMS_COUNT

     @fetch {from, skip:@skipItemCount}, (err, items=[])=>
       @listController.hideLazyLoader()

       return KD.showError err  if err
-
-      @getItemTimestampFn = getItemTimestampFn

       items.forEach @lazyBound 'loadMessage'


video: add and export enableVideo helper

--- client/app/lib/videocollaboration/helper.coffee
+++ client/app/lib/videocollaboration/helper.coffee
@@ -190,6 +190,15 @@

 ###*
+ * Enable given channel's video.
+ *
+ * @param {SocialChannel} channel
+ * @param {function(err: (object|null), result: SocialChannel) callback
+###
+enableVideo = (channel, callback) -> setVideoState channel, yes, callback
+
+
+###*
  * Default error signal.
  *
  * @param {object} error
@@ -206,5 +215,6 @@
   subscribeToStream
   createPublisher
   subscribeToAudioChanges
+  enableVideo
   _errorSignal
 }


query to get all crashreports

query to get all crashreports

--- server/query-api.coffee
+++ server/query-api.coffee
@@ -42,6 +42,10 @@
     return stackframes_to_similarity_value(parse_stacktrace(crashreport["stack-trace"].crashstack))

+get_crashreports = (cb) ->
+    DB_CRASHREPORTS.find().run (err,arr) ->
+        return if err? then cb err, null else cb null, arr
+
 get_crashreport_by_id = (id, cb) ->
         DB_CRASHREPORTS.find({"id":id}).run (err,arr) ->
             return cb err,null if err?


repair ship button active mode

repair ship button active mode

--- views/components/ship/index.cjsx
+++ views/components/ship/index.cjsx
@@ -258,7 +258,7 @@
         <ButtonGroup>
         {
           for i in [0..3]
-            <Button key={i} bsSize="small" activeKey={@state.activeDeck}
+            <Button key={i} bsSize="small"
                             bsStyle={getStyle @state.states[i]}
                             onClick={@handleClick.bind(this, i)}
                             className={if @state.activeDeck == i then 'active' else ''}>


Fix up constants

Fix up constants

--- picksel.coffee
+++ picksel.coffee
@@ -1,3 +1,8 @@
+# Constants.
+USER_PATH = './.pickselacc'
+CONFIG_PATH = './picksel.json'
+
+
 # Load core dependencies.
 https = require 'https'
 fs = require 'fs'
@@ -14,9 +19,7 @@

 # Space for config files.
-userPath = './.pickselacc'
 user = null
-configPath = './picksel.json'
 config = null

@@ -97,12 +100,12 @@

 # Persists the currently loaded configuration settings
 #
-persistConfig = () -> persist config, configPath
+persistConfig = () -> persist config, CONFIG_PATH

 # Persists the currently loaded user settings.
 #
-persistUser = () -> persist user, userPath
+persistUser = () -> persist user, USER_PATH

 # Returns true if a request to the Pixabay API was successful.
@@ -237,16 +240,16 @@
 # Attempts to load the user and dependency configuration files.
 #
 loadConfig = () ->
-  if existsFile.sync userPath
-    user = jsonfile.readFileSync userPath
+  if existsFile.sync USER_PATH
+    user = jsonfile.readFileSync USER_PATH
   else
-    log.error "Couldn't find your '#{userPath}' file to get your API key." \
+    log.error "Couldn't find your '#{USER_PATH}' file to get your API key." \
       + ' You should probably run \'picksel auth\' to set one up.'
     return false
-  if existsFile.sync configPath
-    config = jsonfile.readFileSync configPath
+  if existsFile.sync CONFIG_PATH
+    config = jsonfile.readFileSync CONFIG_PATH
   else
-    log.error "Couldn't find your '#{configPath}' file with all your" \
+    log.error "Couldn't find your '#{CONFIG_PATH}' file with all your" \
       + ' dependencies. You should probably run \'picksel init\' to set one' \
       + ' up.'
     return false
@@ -258,7 +261,7 @@
 init = () ->

   # Check we're not going to overwrite an existing project file.
-  if existsFile.sync configPath
+  if existsFile.sync CONFIG_PATH
     log.warning 'Looks like this project has already been initialized for' \
       + ' Picksel.'
       return false
@@ -277,7 +280,7 @@
   config = newConfig
   persistConfig()

-  log.info "New file created at '#{configPath}' for holding your" \
+  log.info "New file created at '#{CONFIG_PATH}' for holding your" \
     + ' asset dependencies. Feel free to check this file in to source control.'

@@ -286,7 +289,7 @@
 auth = () ->

   # Check we're not going to overwrite an existing user file.
-  if existsFile.sync userPath
+  if existsFile.sync USER_PATH
     log.warning 'Looks like authentication is already set up for this project.'
     return false

@@ -304,8 +307,8 @@
   user = newUser
   persistUser()

-  log.info "New file created at '#{userPath}' containing your API key. DON'T" \
-    + " CHECK THIS FILE IN TO SOURCE CONTROL."
+  log.info "New file created at '#{USER_PATH}' containing your API key." \
+    + " DON'T CHECK THIS FILE IN TO SOURCE CONTROL."

 # Prints usage information for the application.


allow File to behave like normal backbone models

we had written some code for the backbone File model
that assumes that you would always have a 'file'
attribute (that is an actual filesytem object representing
the bytes of the file), so you couldn't save
or toJSON it if you were wanting to save it's other
attributes (like name or parent_folder_id)

this just fixes that.

test plan:
make sure that files still work as
intended in quizzes or other places we use files.

Change-Id: I4d9b7283b2d9d7160fd5b95819d9b7093f4c1cf0
Reviewed-on: https://gerrit.instructure.com/39916
Reviewed-by: Jason Madsen <bcb5fff571a20f07c35d17ba9c364855c776dce7@instructure.com>
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
Product-Review: Ryan Shaw <ea3cd978650417470535f3a4725b6b5042a6ab59@instructure.com>
QA-Review: Ryan Shaw <ea3cd978650417470535f3a4725b6b5042a6ab59@instructure.com>

--- app/coffeescripts/models/File.coffee
+++ app/coffeescripts/models/File.coffee
@@ -31,6 +31,7 @@
       super

     save: (attrs = {}, options = {}) ->
+      return super unless @get('file')
       @set attrs
       dfrd = $.Deferred()
       el = @get('file')
@@ -61,6 +62,7 @@
           options.error?(error)

     toJSON: ->
+      return super unless @get('file')
       _.pick(@attributes, 'file', _.keys(@uploadParams ? {})...)

     present: ->


Print VM start progress log

Print VM start progress log

--- client/test/lib/helpers/waitforvmrunning.coffee
+++ client/test/lib/helpers/waitforvmrunning.coffee
@@ -20,14 +20,23 @@
         .element 'css selector', buildingLabel, (result) =>
           if result.status is 0
             console.log ' ✔ VM is building, waiting to finish'
+
+            logProgress = setInterval ->
+              console.log '   VM is still building'
+            , 30000
+
             browser
               .waitForElementNotPresent  modalSelector, 600000
               .pause                     5000 # wait for sidebar redraw
               .waitForElementVisible     vmSelector, 20000
-              .pause 10000
+              .pause 10000, -> clearInterval logProgress

           else
             console.log ' ✔ VM turn on button is clicked, waiting to turn on'
+
+            logProgress = setInterval ->
+              console.log '   VM is still turning on'
+            , 30000

             browser
               .waitForElementVisible     turnOnButtonSelector, 100000
@@ -35,4 +44,4 @@
               .waitForElementNotPresent  modalSelector, 600000
               .pause                     5000 # wait for sidebar redraw
               .waitForElementVisible     vmSelector, 20000
-              .pause 10000
+              .pause 10000, -> clearInterval logProgress


Nah, we are gonna index it that way

Nah, we are gonna index it that way

--- scripts/rc.coffee
+++ scripts/rc.coffee
@@ -53,6 +53,7 @@
     @storage.multi([
       ["ZADD", @key("latest"), now, @who],
       ["ZADD", @key("#{@who}:scores"), score, now],
+      ["ZADD", @key("#{@who}:times"), now, now],
     ]).exec (err, replies) =>
       return cb(err, this) if err
       [@score, @timestamp] = [score, now]


make container optional, use Modal.Body

make container optional, use Modal.Body

--- coach/src/concept-coach/error-notification.cjsx
+++ coach/src/concept-coach/error-notification.cjsx
@@ -11,7 +11,7 @@
 ErrorNotification = React.createClass

   propTypes:
-    container: React.PropTypes.object.isRequired
+    container: React.PropTypes.object
     close: React.PropTypes.func.isRequired
     course: React.PropTypes.instanceOf(Course)

@@ -64,9 +64,9 @@
         <BS.Modal.Title>{dialog.title}</BS.Modal.Title>
       </BS.Modal.Header>

-

+      <BS.Modal.Body>
         {dialog.body}
-

+      </BS.Modal.Body>

         {dialog.buttons}


Include RNH and RUN file extensions

Include RNH and RUN file extensions

RUN appears to be one of the file extensions used by the original RUNOFF
program. RNH appears to signify a help file (supposedly "RUNOFF HELP"?).

Sources:

* http://web.mit.edu/Saltzer/www/publications/starring/starring.run
* http://odl.sysworks.biz/disk$vaxdocdec953/decw$book/dy4yaaa7.decw$book
* https://github.com/endlesssoftware/mmk/blob/master/mmk_help.rnh
* https://github.com/LuaDist/unzip/blob/master/vms/unzip_def.rnh

--- grammars/roff.cson
+++ grammars/roff.cson
@@ -20,8 +20,10 @@
 	"ms",
 	"mom",
 	"n",
+	"rnh",
 	"rno",
 	"roff",
+	"run",
 	"t",
 	"tr"
 ]


Identify matched commands in scope names


components-gtk: GladeConstructor: filter out widgets starting with _

--- components-gtk/GladeConstructorComponent.coffee
+++ components-gtk/GladeConstructorComponent.coffee
@@ -17,7 +17,9 @@
       builder.add_from_file(file.get_path())
       for object in builder.get_objects()
         try
-          objects[object.get_name()] = object
+          name = object.get_name()
+          continue if /^_.*$/.test name
+          objects[name] = object
         catch e
       if additionals
         for objectName in additionals


Replace callbacks with an API

Replace callbacks with an API

--- core/app/assets/javascripts/intermediate.coffee
+++ core/app/assets/javascripts/intermediate.coffee
@@ -2,22 +2,22 @@
 last_created_text = null

 window.remote = Factlink.createFrameProxyObject window.parent,
-  ['hide', 'show', 'highlightNewFactlink', 'stopHighlightingFactlink',
+  ['hide', 'onModalReady', 'highlightNewFactlink', 'stopHighlightingFactlink',
     'createdNewFactlink', 'trigger', 'setFeatureToggles'
   ]

 local =
-    showFactlink: (id, successFn) ->
+    showFactlink: (id) ->
       url = "/client/facts/#{id}"
-      showUrl url, successFn
+      showUrl url

-    prepareNewFactlink: (text, siteUrl, siteTitle, guided, successFn) ->
+    prepareNewFactlink: (text, siteUrl, siteTitle, guided) ->
       url = "/facts/new" +
               "?fact=" + encodeURIComponent(text) +
               "&url=" + encodeURIComponent(siteUrl) +
               "&title=" + encodeURIComponent(siteTitle) +
               "&guided=" + encodeURIComponent(guided)
-      showUrl url, successFn
+      showUrl url
       last_created_text = text

 Factlink.listenToWindowMessages null, local
@@ -26,11 +26,7 @@
   if last_created_text == text
     remote.highlightNewFactlink(text, id)

-showUrl = (url, successFn) ->
-  window.onModalReady = ->
-    window.onModalReady = -> # nothing
-    successFn()
-
+showUrl = (url) ->
   backbone = showFrame.contentWindow.Backbone
   history = backbone?.history
   if history && backbone.History.started
@@ -41,7 +37,7 @@

     history.navigate url, trigger: true
   else
-    showFrame.onload = -> window.onModalReady()
+    showFrame.onload = -> window.remote.onModalReady()
     showFrame.src = url

 showFrame.src = '/client/blank'


fix x axis popover position relative to anchor just in the middle

--- app/assets/javascripts/dante/popover.js.coffee
+++ app/assets/javascripts/dante/popover.js.coffee
@@ -25,10 +25,20 @@
   #display & copy original link
   displayAt: (ev)->
     @cancelHide()
+    #debugger
     target = $(ev.currentTarget)
+    $(@el).find(".popover-inner a").text( target.attr('href') ).attr('href', target.attr("href") )
+
     pos    = target.position()
-    $(@el).find(".popover").css("top", pos.top + 20).css("left", pos.left).show()
-    $(@el).find(".popover-inner a").text( target.attr('href') ).attr('href', target.attr("href") )
+    offset = target.offset()
+    rect   = ev.currentTarget.getBoundingClientRect()
+    popover_w = $(@el).find(".popover").width()
+    utils.log pos
+    utils.log rect
+    utils.log popover_w
+    $(@el).find(".popover")
+    .css("top", pos.top + rect.height)
+    .css("left",  pos.left + (rect.width/2) - (popover_w/2) ).show()
     $(@el).find(".popover--tooltip").css("pointer-events", "auto")
     $(@el).show()


fix loader

fix loader

--- app/assets/javascripts/gnoibox/admin.js.coffee
+++ app/assets/javascripts/gnoibox/admin.js.coffee
@@ -31,7 +31,7 @@
       Gnoibox.s3bucket.upload params, (err, data)->
         if data.Location
           container = file_input.parents('.gn-form-column-image')
-          $('.spinner-div', container).addClass("loader")
+          $('.spinner-div', container).addClass("spinner")
           $('.gn-remote-image-url', container).val(data.Location)
           file_input.val(null)

@@ -49,6 +49,6 @@
       file_input.val(data.filename)

       container = file_input.parents('.gn-form-column-image')
-      $('.spinner-div', container).addClass("loader")
+      $('.spinner-div', container).addClass("spinner")
       $('img', container).attr('src', data.url).show()
       $('.gn-remote-image-url', container).val(null)


fix(swimlanes): refresh project after a new swimlane

--- app/coffee/modules/admin/project-values.coffee
+++ app/coffee/modules/admin/project-values.coffee
@@ -136,10 +136,11 @@
         "$tgRepo",
         "$translate"
         "$tgConfirm",
-        "$tgResources"
+        "$tgResources",
+        "tgProjectService"
     ]

-    constructor: (@scope, @rootscope, @repo, @translate, @confirm, @rs) ->
+    constructor: (@scope, @rootscope, @repo, @translate, @confirm, @rs, @projectService) ->
         unwatch = @scope.$watch "resource", (resource) =>
             if resource
                 @.loadSwimlanes()
@@ -179,6 +180,8 @@
             @.loadSwimlanes()

     loadSwimlanes: =>
+        @projectService.fetchProject()
+
         return @rs[@scope.resource].list(@scope.projectId).then (values) =>
             @scope.values = values


Rewritten destroyIfContainsFactlink to _valid, to allow for other conditions also

--- app/js/jail_iframe/classes/icon_buttons.coffee
+++ app/js/jail_iframe/classes/icon_buttons.coffee
@@ -95,12 +95,13 @@
     FactlinkJailRoot.off 'updateIconButtons', @_update

   _update: =>
-    @_updatePosition()
-    @_destroyIfContainsFactlink()
+    if @_valid()
+      @_updatePosition()
+    else
+      @destroy()

-  _destroyIfContainsFactlink: =>
-    if @$paragraph.find('.factlink').length > 0
-      @destroy()
+  _valid: =>
+    @$paragraph.find('.factlink').length <= 0

   _updatePosition: ->
     contentBox = FactlinkJailRoot.contentBox(@$paragraph[0])


already an array

already an array

--- apps/artwork/client/setup_artwork_rails.coffee
+++ apps/artwork/client/setup_artwork_rails.coffee
@@ -94,7 +94,7 @@

           view.carouselPromise

-        Q.all([carouselPromises]).then ->
+        Q.all(carouselPromises).then ->
           _.delay =>
             $('#artwork-rails').attr 'data-state', 'fade-in'
           , 200


Fix small bug

Fix small bug

--- admin/static/coffee/namespaces/namespace.coffee
+++ admin/static/coffee/namespaces/namespace.coffee
@@ -231,7 +231,7 @@
             json = @model.toJSON()
             json = _.extend json, namespace_status

-            if namespace_status.reachability == 'Live'
+            if namespace_status.reachability? and namespace_status.reachability is 'Live'
                 json.reachability = true
             else
                 json.reachability = false


not sure what this does but meeeeh they give me an api why not yknow?

--- app/assets/javascripts/console-ish-input.coffee
+++ app/assets/javascripts/console-ish-input.coffee
@@ -25,7 +25,7 @@
   previousWhitespaceIndex = beforeCurrent.lastIndexOf(" ")
   previousWhitespaceIndex = 0 if previousWhitespaceIndex == -1
   valueWithWordRemoved = currentValue.slice(0, previousWhitespaceIndex) + currentValue.slice(currentIndex)
-  input.val(valueWithWordRemoved)
+  TS.utility.populateInput(input, valueWithWordRemoved)
   input.setCursorPosition(previousWhitespaceIndex)

 input.bind "keydown", (e) ->
@@ -34,7 +34,7 @@

   currentIndex = input.getCursorPosition()
   currentValue = input.val()
-  input.val(currentValue.slice(0, currentIndex))
+  TS.utility.populateInput(input, currentValue.slice(0, currentIndex))
   input.setCursorPosition(currentIndex)

 input.bind "keydown", (e) ->
@@ -43,5 +43,5 @@

   currentIndex = input.getCursorPosition()
   currentValue = input.val()
-  input.val(currentValue.slice(currentIndex))
+  TS.utility.populateInput(input, currentValue.slice(currentIndex))
   input.setCursorPosition(0)


tweak star rating initial input

tweak star rating initial input

--- app/assets/javascripts/initialization.js.coffee
+++ app/assets/javascripts/initialization.js.coffee
@@ -12,6 +12,9 @@

   $(".alert").delay(5000).fadeOut("slow")

+  selected = $(".radio-collection span").index($(".radio-collection input:checked").closest("span"))
+  for s in [0..selected]
+    $(".radio-collection span:eq("+s+") input").addClass("active")
   $(".radio-collection input").on("change", -> (
     selected = $(".radio-collection span").index($(this).closest("span"))
     $(".radio-collection input.active").removeClass("active")


converter detail: handle non-numeric values

--- app/assets/javascripts/lib/models/metric.coffee
+++ app/assets/javascripts/lib/models/metric.coffee
@@ -164,7 +164,11 @@
     return false unless value?
     value.toFixed(precision)

+  # This is used only in the converter info popup
   converter_detail_format: (x, unit) ->
     x *= 100.0 if unit == '%'
-    @round_number x, 2
+    if _.isNumber(x)
+      @round_number x, 2
+    else
+      x


trigger store form submit on tos click

trigger store form submit on tos click

--- app/assets/javascripts/mobile/_mobile.js.coffee
+++ app/assets/javascripts/mobile/_mobile.js.coffee
@@ -203,12 +203,15 @@
                 $redirect_to_order_form.click -> redirect_to_order_form()

 if gon.page is 'stores_new' or gon.page is 'stores_edit' or gon.page is 'stores_proxy' or gon.page is 'stores_create' or gon.page is 'stores_update'
-    $ -> ($ '.mobile-form')
-        .isHappy(store_form_validation_rules)
-        .submit((e) ->
-            if ($ @).find('.unhappy').length
-                e.preventDefault()
-                scroll_to_error(($ @)))
+    $ ->
+        ($ '.mobile-form')
+            .isHappy(store_form_validation_rules)
+            .submit((e) ->
+                if ($ @).find('.unhappy').length
+                    e.preventDefault()
+                    scroll_to_error(($ @)))
+        ($ '#store_terms_of_service').click -> ($ '.mobile-form').trigger('submit')
+

 if gon.page is 'orders_new' or gon.page is 'orders_edit' or gon.page is 'orders_create'
     $ ->


don't open help modal when typing ? into a textbox; fixes #349

--- app/assets/javascripts/oxalis/controller.coffee
+++ app/assets/javascripts/oxalis/controller.coffee
@@ -197,6 +197,10 @@

     $(document).keypress (event) ->

+      if $(event.target).is("input")
+        # don't summon help modal when the user types into an input field
+        return
+
       if event.shiftKey && event.which == 63
         $("#help-modal").modal('toggle')


Update simditor.coffee

Update simditor.coffee

限制外链图片显示不超过文本框

--- app/assets/javascripts/simditor/simditor.coffee
+++ app/assets/javascripts/simditor/simditor.coffee
@@ -3182,6 +3182,7 @@
       @srcEl.val '正在上传'
     else
       @srcEl.val $img.attr('src')
+      $img.css 'max-width', '100%'

 Simditor.Toolbar.addButton(ImageButton)


Fix link in credits for map icons.

Fix link in credits for map icons.

--- app/assets/javascripts/views/map.view.js.coffee
+++ app/assets/javascripts/views/map.view.js.coffee
@@ -29,7 +29,7 @@
   options:
     maxZoom: 19
     minZoom: 2
-    attribution: 'Data: © OpenStreetMap contributors, Icons: CC-By-SA Nicolas Mollet'
+    attribution: 'Data: © OpenStreetMap contributors, Icons: CC-By-SA Maps Icons Collection'

 Wheelmap.MarkerLayer = EmberLeaflet.Layer.extend
   mapBinding: 'parentLayer'


fixing bar bubble

fixing bar bubble

Former-commit-id: 6b7e223505119fd3e0423b0139c8556331523676 [formerly d13fa0c443b93277e57c87819c3266e2b56305b2] [formerly 95142c4dbaf10e10ef19c920e89eff96ae786d4b [formerly 95142c4dbaf10e10ef19c920e89eff96ae786d4b [formerly 91fea76500b7189a0c981a8cd7a5697f536d5223]]]
Former-commit-id: dbd555a07b1da71cb86b196d82a2026db3dc2437 [formerly f16993200d933f5dcf6bf5cf98f0453c8a887ca5]
Former-commit-id: 9743c9ce58136422e7317138d04457481ca97480
Former-commit-id: 0d534f4b56bf4296c6efed91c7d5f7ac2f1b4574

--- app/assets/javascripts/views/resultsview.coffee
+++ app/assets/javascripts/views/resultsview.coffee
@@ -243,9 +243,13 @@
     hard_select = ev.type == 'click'

     if @$el.find('#expanded').length == 0 && ( hard_select || @$histogram.find('.m-bar-is-hard-selected').length == 0 )
+
+
+      $bar = $target.closest('.m-histogram-bar')
+      bubble_offset = $bar.offset().top - @$el.find('.l-message-body').offset().top + 2
+
       @$el.hide()

-      $bar = $target.closest('.m-histogram-bar')
       bucket = 6 - $bar.attr('bucket')
       $('.m-bar-is-selected', @$histogram).removeClass('m-bar-is-selected m-bar-is-hard-selected m-bar-is-soft-selected')
       $bar.addClass("m-bar-is-selected #{if hard_select then 'm-bar-is-hard-selected' else 'm-bar-is-soft-selected'}")
@@ -279,9 +283,11 @@
         .show()

       @$el.find('.t-bubble').hide()
+      @$el.find('.t-bubble-bar').remove()
+

       $bar_bubble = $('

▶

')
-      $bar_bubble.css('top', $bar.position().top + 2)
+      $bar_bubble.css('top', bubble_offset)
       @$el.find('.l-message-body').append($bar_bubble)

       _.each @views, (vw) ->


Add basic test

Add basic test

--- test/browser.coffee
+++ test/browser.coffee
@@ -2,5 +2,7 @@
 request = httprequest

 describe 'httprequest', ->
-  it 'passes a test', ->
-    assert true
+  it 'performs a get request', (done) ->
+    request.get 'http://localhost:4001/getjson', (err, res) ->
+      assert.equal res.text, JSON.stringify(hello: 'world')
+      done()


extract: test over ssh

extract: test over ssh

--- test/extract.coffee
+++ test/extract.coffee
@@ -1,15 +1,17 @@

 lib = if process.env.MECANO_COV then 'lib-cov' else 'lib'
 mecano = require "../#{lib}"
+they = require 'ssh2-they'
 test = require './test'

 describe 'extract', ->

   scratch = test.scratch @

-  it 'should see extension .tgz', (next) ->
+  they 'should see extension .tgz', (ssh, next) ->
     # Test a non existing extracted dir
     mecano.extract
+      ssh: ssh
       source: "#{__dirname}/../resources/a_dir.tgz"
       destination: scratch
     , (err, extracted) ->
@@ -19,6 +21,7 @@
       # Note, there is no way for us to know which directory
       # it is in advance
       mecano.extract
+        ssh: ssh
         source: "#{__dirname}/../resources/a_dir.tgz"
         destination: scratch
       , (err, extracted) ->
@@ -26,9 +29,10 @@
         extracted.should.be.ok
         next()

-  it 'should see extension .zip', (next) ->
+  they 'should see extension .zip', (ssh, next) ->
     # Test a non existing extracted dir
     mecano.extract
+      ssh: ssh
       source: "#{__dirname}/../resources/a_dir.zip"
       destination: scratch
     , (err, extracted) ->
@@ -38,27 +42,18 @@
       # Note, there is no way for us to know which directory
       # it is in advance
       mecano.extract
+        ssh: ssh
         source: "#{__dirname}/../resources/a_dir.zip"
         destination: scratch
       , (err, extracted) ->
         return next err if err
         extracted.should.be.ok
         next()
-
-  it 'should work accross ssh', (next) ->
-    # Test a non existing extracted dir
-    mecano.extract
-      ssh: host: 'localhost'
-      source: "#{__dirname}/../resources/a_dir.tgz"
-      destination: scratch
-    , (err, extracted) ->
-      return next err if err
-      extracted.should.be.ok
-      next()

-  it 'should validate a created file', (next) ->
+  they 'should validate a created file', (ssh, next) ->
     # Test with invalid creates option
     mecano.extract
+      ssh: ssh
       source: "#{__dirname}/../resources/a_dir.tgz"
       destination: scratch
       creates: "#{scratch}/oh_no"
@@ -66,6 +61,7 @@
       err.message.should.eql "Failed to create 'oh_no'"
       # Test with valid creates option
       mecano.extract
+        ssh: ssh
         source: "#{__dirname}/../resources/a_dir.tgz"
         destination: scratch
         creates: "#{scratch}/a_dir"
@@ -74,9 +70,10 @@
         extracted.should.be.ok
         next()

-  it 'should # option # not_if_exists', (next) ->
+  they 'should # option # not_if_exists', (ssh, next) ->
     # Test with invalid creates option
     mecano.extract
+      ssh: ssh
       source: "#{__dirname}/../resources/a_dir.tgz"
       destination: scratch
       not_if_exists: __dirname
@@ -85,15 +82,17 @@
       extracted.should.not.be.ok
       next()

-  it 'should pass error for invalid extension', (next) ->
+  they 'should pass error for invalid extension', (ssh, next) ->
     mecano.extract
+      ssh: ssh
       source: __filename
     , (err, extracted) ->
       err.message.should.eql 'Unsupported extension, got ".coffee"'
       next()

-  it 'should pass error for missing source file', (next) ->
+  they 'should pass error for missing source file', (ssh, next) ->
     mecano.extract
+      ssh: ssh
       source: '/does/not/exist.tgz'
     , (err, extracted) ->
       err.message.should.eql 'File does not exist: /does/not/exist.tgz'


Metered changing max attribute

Metered changing max attribute

--- test/metered.coffee
+++ test/metered.coffee
@@ -26,7 +26,7 @@

   obj.meter 'health'

-  equals obj.I.maxHealth, 150
+  equals obj.I.healthMax, 150

 test "should set both <attribute> and max<attribute> if they aren't present in the including object", ->
   obj = GameObject()


movable velocity and acceleration getters

--- test/movable.coffee
+++ test/movable.coffee
@@ -38,7 +38,7 @@
     acceleration: Point(1, -0.3)

   60.times ->
-    particle.update(4)
+    particle.update(1/60)

   equals particle.I.velocity.x, 11
   equals particle.I.velocity.y, 2.8


tests for shell method

tests for shell method

--- test/placebo.coffee
+++ test/placebo.coffee
@@ -63,3 +63,20 @@

     it 'has a stop method', ->
       should.exist placebo.stop
+
+    it 'has a sh method', ->
+      should.exist placebo.sh
+
+    it 'returns stdout when you run shell commands', ->
+      placebo.sh('echo Hello').should.equal ['Hello', '']
+
+    it 'returns stderr when you run shell commands', ->
+      command = 'cat this_file_does_not_exist'
+      result = ['', 'cat: foo: input file is output file']
+      placebo.sh(command).should.equal result
+
+    it 'throws an exception when shell commands exit nonzero', ->
+      placebo.sh('exit 1').should.throw
+
+    it 'does not throw an exception when shell commands exit zero', ->
+      placebo.sh('exit 0').should.not.throw


Don't die immediately on fail (was for debugging only).

--- tests/common.coffee
+++ tests/common.coffee
@@ -9,7 +9,7 @@

     casper.test.on 'fail', ->
         casper.capture 'casperjs.png'
-        casper.die()
+        #casper.die()

 exports.selectFirstMatch = (select2_selector, search_text) =>


fix strip

fix strip

--- app/server/plugins/docs/index.coffee
+++ app/server/plugins/docs/index.coffee
@@ -115,7 +115,7 @@

   getLink = (text) ->
-    String(getCleanTitle(stripHTML(fileName = text))).toLowerCase()
+    String(getCleanTitle(stripHTML(text))).toLowerCase()


show hit rate to 2dp only

show hit rate to 2dp only

--- services/spelling/app/coffee/ASpell.coffee
+++ services/spelling/app/coffee/ASpell.coffee
@@ -38,7 +38,7 @@
 			for k, v of addToCache
 				cache.set(k, v)

-			logger.log hits: hits, total: words.length, hitrate: hits/words.length, "cache hit rate"
+			logger.log hits: hits, total: words.length, hitrate: (hits/words.length).toFixed(2), "cache hit rate"
 			callback null, results

 	getSuggestions: (language, output) ->


:lipstick:

:lipstick:

--- src/module-cache.coffee
+++ src/module-cache.coffee
@@ -11,8 +11,8 @@
 nativeModules = process.binding('natives')

 cache =
+  builtins: {}
   debug: false
-  builtins: {}
   dependencies: {}
   extensions: {}
   folders: {}


:racehorse: Extend range to memoize matched versions

--- src/module-cache.coffee
+++ src/module-cache.coffee
@@ -1,6 +1,19 @@
 Module = require 'module'
 path = require 'path'
 semver = require 'semver'
+
+# Extend semver.Range to memoize matched versions for speed
+class Range extends semver.Range
+  constructor: ->
+    super
+    @matchedVersions = new Set()
+
+  test: (version) ->
+    return true if @matchedVersions.has(version)
+
+    matches = super
+    @matchedVersions.add(version) if matches
+    matches

 nativeModules = process.binding('natives')

@@ -91,7 +104,7 @@

 satisfies = (version, rawRange) ->
   unless parsedRange = cache.ranges[rawRange]
-    parsedRange = new semver.Range(rawRange)
+    parsedRange = new Range(rawRange)
     cache.ranges[rawRange] = parsedRange
   parsedRange.test(version)


Method re-organisation.

Method re-organisation.

--- lib/provider_virtualbox.coffee
+++ lib/provider_virtualbox.coffee
@@ -31,35 +31,6 @@
 		return @manifest.nodes[node_name] if @manifest.nodes? and @manifest.nodes[node_name]?
 		throw new Error "node #{node_name} does not exist"

-	extract_namespace: (node_name) ->
-		###
-		Extracts a namespace by looking it up in the node itself and upper layers of the manifest
-		###
-
-		try
-			node = @get_node node_name
-		catch
-			node = null
-
-		return node.namespace if node?.namespace?
-		return @manifest.namespace if @manifest.namespace?
-
-	get_node_handle: (node_name) ->
-		###
-		Creates a VirtualBox friendlier name out of a node name. The method take into account the namespace.
-		###
-
-		namespace = @extract_namespace node_name
-
-		return (if namespace then namespace + ':' else '') + node_name
-
-	get_share_handle: (share_name) ->
-		###
-		Creates a VirtualBox friendlier name out of a share name.
-		###
-
-		return share_name.replace(/[^\w]+/, '_').replace(/_+/, '_')
-
 	extract_property: (property_name, node_name) ->
 		###
 		Extracts a property by looking into a node and upper layers of the manifest.
@@ -88,6 +59,35 @@
 	#
 	#

+	extract_namespace: (node_name) ->
+		###
+		Extracts a namespace by looking it up in the node itself and upper layers of the manifest
+		###
+
+		try
+			node = @get_node node_name
+		catch
+			node = null
+
+		return node.namespace if node?.namespace?
+		return @manifest.namespace if @manifest.namespace?
+
+	get_node_handle: (node_name) ->
+		###
+		Creates a VirtualBox friendlier name out of a node name. The method take into account the namespace.
+		###
+
+		namespace = @extract_namespace node_name
+
+		return (if namespace then namespace + ':' else '') + node_name
+
+	get_share_handle: (share_name) ->
+		###
+		Creates a VirtualBox friendlier name out of a share name.
+		###
+
+		return share_name.replace(/[^\w]+/, '_').replace(/_+/, '_')
+
 	schedule_import: (vm_url, vm_id, callback) ->
 		###
 		Schedules import operation. The function will check if the vm_id exists before execution.


Cmd+Y is a standard preview button on macOS

--- keymaps/atom-scad-preview.cson
+++ keymaps/atom-scad-preview.cson
@@ -8,4 +8,4 @@
 # For more detailed documentation see
 # https://atom.io/docs/latest/behind-atom-keymaps-in-depth
 'atom-workspace':
-  'ctrl-alt-o': 'atom-scad-preview:toggle'
+  'cmd-y': 'atom-scad-preview:toggle'


fab10 update

fab10 update

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -41,7 +41,5 @@
   $('*[data-limit]').each ->
     $(this).limit parseInt($(this).data('limit')), $(this).data('counter')

-
 document.addEventListener "page:load", initEvents
 $(document).ready initEvents
-# jQuery -> initEvents


fixed XHRequest had been sent to the server when the branch node was clicked.

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -116,8 +116,12 @@
       vis.panEnabled(true)

       vis.addListener("click", "nodes",  (e) ->
+
         node = e.target
         sha_1 = node.data.sha_1
+        if not sha_1
+          return
+
         detail = $('div#detail')
         x = e.mouseX


uh oh jquery

uh oh jquery

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -37,7 +37,7 @@
   $form.attr 'action', '/forge'
   $form.submit()

-j(document).on 'blur', '#city, #street_name', ->
+jQuery(document).on 'blur', '#city, #street_name', ->
   city = j('#city').val()
   city = null if city is ''
   street = j('#street_name').val()
@@ -54,9 +54,9 @@
       building.val current_value

 buildingNamed = no
-j(document).on 'ready', ->
+jQuery(document).on 'ready', ->
   buildingNamed = j('#building_name').val()
-j(document).on 'change', '#building_address_house_number, #building_address_street_prefix, #building_address_street_name, #building_address_street_suffix', ->
+jQuery(document).on 'change', '#building_address_house_number, #building_address_street_prefix, #building_address_street_name, #building_address_street_suffix', ->
   unless buildingNamed
     buildingName = [j('#building_address_house_number').val(), j('#building_address_street_prefix').val(), j('#building_address_street_name').val(), j('#building_address_street_suffix').val()].join(' ')
     j('#building_name').val(buildingName)


Percentage support in the frontend

Percentage support in the frontend

--- app/assets/javascripts/evaluations.js.coffee
+++ app/assets/javascripts/evaluations.js.coffee
@@ -20,6 +20,7 @@
         $redBelow   = $("#evaluation_red_below")
         $greenAbove = $("#evaluation_green_above")

+        $redMin     = $("#evaluation-red-min")
         $redMax     = $("#evaluation-red-max")
         $greenMin   = $("#evaluation-green-min")
         $greenMax   = $("#evaluation-green-max")
@@ -31,20 +32,27 @@
             $stanineMax.val(value)

         $redBelow.on "change", ->
-            $redMax.val(
-                Math.max(
-                    intify($redBelow.val(), -1),
-                    0
-                )
-            )
+            value  = $redBelow.val()
+            suffix = if /%/.test(value) then "%" else ""
+            value  = Math.max(intify(value, -1), 0)
+
+            $redMin.val("0" + suffix)
+            $redMax.val(value.toString() + suffix)

         $greenAbove.on "change", ->
-            $greenMin.val(
-                Math.min(
-                    intify($greenAbove.val(), 1),
-                    intify($maxResult.val())
-                )
-            )
+            value = $greenAbove.val()
+
+            if /%/.test(value)
+                suffix = "%"
+                max = 100
+            else
+                suffix = ""
+                max = intify($maxResult.val())
+
+            value = Math.min(intify(value, 1), max)
+
+            $greenMin.val(value.toString() + suffix)
+            $greenMax.val(max.toString() + suffix)

         $maxResult.trigger("change")
         $redBelow.trigger("change")


Form submission on autocomplete select

Form submission on autocomplete select

--- app/assets/javascripts/site_search.js.coffee
+++ app/assets/javascripts/site_search.js.coffee
@@ -31,7 +31,8 @@
                   }
         minLength: 2
         select: (event, ui) ->
-          document.location = $("#full-search").attr('action') + '?q=' + unescape(ui.item.value)
+          $searchField.val(ui.item.value)
+          $("#full-search").submit()
       .data( "ui-autocomplete" )._renderItem = (ul, item) ->
         return $("

")
         .data("ui-autocomplete-item", item)


Cleanup

Cleanup

--- src/scripts/load.coffee
+++ src/scripts/load.coffee
@@ -23,8 +23,8 @@
 		@load.image 'button-pause', 'images/button-pause.png'

 		# load spritesheets
-		@load.spritesheet('candy', 'images/candy.png', 82, 98);
-		@load.spritesheet('monster-idle', 'images/monster-idle.png', 103, 131);
-		@load.spritesheet('button-start', 'images/button-start.png', 401, 143);
+		@load.spritesheet 'candy', 'images/candy.png', 82, 98
+		@load.spritesheet 'monster-idle', 'images/monster-idle.png', 103, 131
+		@load.spritesheet 'button-start', 'images/button-start.png', 401, 143

 module.exports = Preloader


Improved error handling for Pipl API

Improved error handling for Pipl API

--- src/scripts/pipl.coffee
+++ src/scripts/pipl.coffee
@@ -26,47 +26,46 @@
       request_url = api_url + "/search/v3/json/?email=#{encodeURIComponent target_email}&exact_name=0&query_params_mode=and&key=#{PIPL_API_KEY}"

       request_response = robot.http(request_url)
+        .get() (err, res, body) ->

-      .get() (err, res, body) ->
+          if res.statusCode is 200
+            pipl_json = JSON.parse(body)

-        if res.statusCode is 200
-          pipl_json = JSON.parse(body)
+            person_sources = "Person:\n"
+            records_source = "Records:\n"

-          person_sources = "Person:\n"
-          records_source = "Records:\n"
-
-          if pipl_json.error?
-            msg.send "Pipl Error: #{pipl_json.error}"
-          else
-            ## person
-            if pipl_json.person.sources?
-              person_sources += """ - #{person_source.name}:   #{person_source.url}\n""" for person_source in pipl_json.person.sources
+            if pipl_json.error?
+              msg.send "Pipl Error: #{pipl_json.error}"
             else
-              person_sources += """ - No information found.\n"""
+              ## person
+              if pipl_json.person.sources?
+                person_sources += """ - #{person_source.name}:   #{person_source.url}\n""" for person_source in pipl_json.person.sources
+              else
+                person_sources += """ - No information found.\n"""

-            ## related_urls (really noisy)
-            # for related_url in pipl_json.related_urls
-            #   msg.send related_url
+              ## related_urls (really noisy)
+              # for related_url in pipl_json.related_urls
+              #   msg.send related_url

-            ## records
-            if pipl_json.records?
-              records_source += """ - #{record.source.name}: #{record.source.url}\n""" for record in pipl_json.records
-            else
-              records_source += """ - No information found.\n"""
+              ## records
+              if pipl_json.records?
+                records_source += """ - #{record.source.name}: #{record.source.url}\n""" for record in pipl_json.records
+              else
+                records_source += """ - No information found.\n"""

-            pipl_summary = """
-            Pipl Profile for Email: #{target_email}
-            ------------------------------------------------
-            Total Records: #{pipl_json["@records_count"]}
+              pipl_summary = """
+              Pipl Profile for Email: #{target_email}
+              ------------------------------------------------
+              Total Records: #{pipl_json["@records_count"]}

-            #{person_sources}
-            #{records_source}
-            """
+              #{person_sources}
+              #{records_source}
+              """

-            msg.send pipl_summary
-        else
-          msg.send "Error: Couldn't access #{api_url}."
+              msg.send pipl_summary
+          else
+            msg.send "Error: Couldn't access #{api_url}."

     else
       msg.send "Pipl API key not configured. Get one at http://dev.pipl.com/"


Add DOM element tree generation functionality

--- src/scripts/tael.coffee
+++ src/scripts/tael.coffee
@@ -7,27 +7,36 @@
 tiles = [
     new ref
         type: 'container'
+        content:
+            dom:
+                ($ '.tael-container')
+                .attr 'id', 'tael-node-0'
         child: undefined
 ]

 newTile = (tiles, parent_id) ->
     # Add a new tile to the `tiles` array.
-    pushTile = ->
+    pushTile = (parent) ->
         # Push a new tile to the `tiles` array and return its index.
-        (
+        index = (
             tiles.push new ref
                 type: 'leaf'
                 content:
-                    # To be added to
-                    dom: null
-                    data: 'foo'
+                    dom:
+                        ($ '<div>')
+                        .addClass 'tael-node-leaf'
+                        .text 'foo'
+                        .appendTo parent.tile.value.content.dom
+                    data: null
         ) - 1
+        tiles[index].value.content.dom.attr 'id', 'tael-node-'+index
+        index

     addLeafToContainer = (parent) ->
         # Create only one tile and add it as the child of the supplied parent.
         # As the container tile is the top-level tile, it has only a single
         # child tile.
-        parent.tile.value.child = do pushTile
+        parent.tile.value.child = pushTile parent

     addLeavesToLeaf = (parent) ->
         # Create two tiles; convert the 'leaf' tile into a 'branch' tile
@@ -35,9 +44,14 @@
         # 'branch' tile.
         parent.tile.value =
             type: 'branch'
+            content:
+                dom:
+                    parent.tile.value.content.dom
+                    .attr 'class', 'tael-node-branch'
+                    .html ''
             children:
-                left: do pushTile
-                right: do pushTile
+                left: pushTile parent
+                right: pushTile parent
             layout:
                 split: 'horizontal'
                 divider_location: 0.5
@@ -56,13 +70,7 @@
         when 'branch'
             error.throw "Branch tiles cannot spawn new children post-creation."

-newTile tiles, 0
+module.exports = ->
+    newTile tiles, 0
+    newTile tiles, 1

-module.exports = ->
-    ($ document).ready ->
-        ($ '.tael-container')
-        .append(
-            ($ '<div>')
-            .addClass 'tael-node-leaf'
-            .text tiles[1].value.content.data
-        )


Disable EventEmitter warning for number of listeners on a doc it's reasonable to have more than 10 users working on a doc

--- src/server/model.coffee
+++ src/server/model.coffee
@@ -222,6 +222,7 @@
         snapshotWriteLock: false
         dbMeta: dbMeta

+      doc.eventEmitter.setMaxListeners 0
       doc.opQueue = makeOpQueue docName, doc

       refreshReapingTimeout docName


multiple node selection now implemented

multiple node selection now implemented

--- app/scripts/alchemy/core/interactions.coffee
+++ app/scripts/alchemy/core/interactions.coffee
@@ -85,16 +85,23 @@

     nodeClick: (c) ->
         d3.event.stopPropagation()
-        alchemy.vis.selectAll('line')
-            .classed('selected', (d) -> return c.id is d.source.id or c.id is d.target.id)
-        alchemy.vis.selectAll('.node')
-            .classed('selected', (d) -> return c.id is d.id)
-            # also select 1st degree connections
-            .classed('selected', (d) ->
-                return d.id is c.id or alchemy.edges.some (e) ->
-                    return ((e.source.id is c.id and e.target.id is d.id) or
-                            (e.source.id is d.id and e.target.id is c.id)) and
-                            d3.select(".edge[source-target*='#{d.id}']").classed("active"))
+        # select the correct nodes
+        selected = alchemy.vis.select("#node-#{c.id}").classed('selected')
+        alchemy.vis.select("#node-#{c.id}").classed('selected', !selected)
+
+        # alchemy.vis.selectAll(".node").classed('selected', (d) ->
+        #     if d.id is c.id
+        #         return !selected
+        #     else
+        #         connections = alchemy.edges.some (e) ->
+        #             ((e.source.id is c.id and e.target.id is d.id) or
+        #             (e.source.id is d.id and e.target.id is c.id))
+        #             # and d3.select(".edge[source-target*='#{d.id}']").classed("active")
+        #         return connections
+        #     )
+
+        # selectedEdges = alchemy.vis.selectAll(".edge[source-target*='#{c.id}']")
+        # selectedEdges.classed("selected", !selected)

         if typeof alchemy.conf.nodeClick == 'function'
             alchemy.conf.nodeClick(c)


Trash icon for task list

Trash icon for task list

--- app/scripts/components/content/task.bar.cjsx
+++ app/scripts/components/content/task.bar.cjsx
@@ -12,11 +12,11 @@
         <button type="submit" className="btn btn-primary">Add</button>
       </form>

-
Cras justo odio

-
Dapibus ac facilisis in

-
Morbi leo risus

-
Porta ac consectetur ac

-
Vestibulum at eros

+
Cras justo odio

+
Dapibus ac facilisis in

+
Morbi leo risus

+
Porta ac consectetur ac

+
Vestibulum at eros


Always synch with others

Always synch with others

--- app/scripts/lib/script_modules/entity.coffee
+++ app/scripts/lib/script_modules/entity.coffee
@@ -327,7 +327,9 @@
   # Very useful for orchestrated attacks
   synchronizeOn: (name) ->
     (sequence) =>
-      @_verify(sequence)
+      # no sequence verification here, or else
+      # enemies on an alternate path but still alive
+      # will freeze others
       @synchronizer.synchronizeOn(name, this)

   setLocation: (location) ->


put host in error message

put host in error message

--- libraries/redis-wrapper/index.coffee
+++ libraries/redis-wrapper/index.coffee
@@ -45,7 +45,7 @@
 	_checkClient: (client, callback) ->
 		callback = _.once(callback)
 		timer = setTimeout () ->
-			error = new Error("redis client ping check timed out")
+			error = new Error("redis client ping check timed out #{client?.options?.host}")
 			console.error {
 				err: error,
 				key: client.options?.key # only present for cluster


Clean a little the code

Clean a little the code

--- admin/static/coffee/namespaces/shards.coffee
+++ admin/static/coffee/namespaces/shards.coffee
@@ -233,9 +233,8 @@
                 error_msg: @error_msg
             @error_msg = null
             @.$el.html(@template json)
-
-            shard_views = _.map(compute_renderable_shards_array(@namespace.get('id'), @shard_set), (shard) => new NamespaceView.ModifySingleShard @namespace, shard, @)
-            @.$('.shards tbody').append view.render().el for view in shard_views
+
+            @render_only_shards

             # Control the suggest button
             @.$('.btn-compute-shards-suggestion').button()


Wrapping pending changes service in a class

--- app/assets/javascripts/admin/index_utils/services/pending_changes.js.coffee
+++ app/assets/javascripts/admin/index_utils/services/pending_changes.js.coffee
@@ -1,29 +1,30 @@
 angular.module("admin.indexUtils").factory "pendingChanges", (dataSubmitter) ->
-  pendingChanges: {}
+  new class pendingChanges
+    pendingChanges: {}

-  add: (id, attr, change) ->
-    @pendingChanges["#{id}"] = {} unless @pendingChanges.hasOwnProperty("#{id}")
-    @pendingChanges["#{id}"]["#{attr}"] = change
+    add: (id, attr, change) =>
+      @pendingChanges["#{id}"] = {} unless @pendingChanges.hasOwnProperty("#{id}")
+      @pendingChanges["#{id}"]["#{attr}"] = change

-  removeAll: ->
-    @pendingChanges = {}
+    removeAll: =>
+      @pendingChanges = {}

-  remove: (id, attr) ->
-    if @pendingChanges.hasOwnProperty("#{id}")
-      delete @pendingChanges["#{id}"]["#{attr}"]
-      delete @pendingChanges["#{id}"] if @changeCount( @pendingChanges["#{id}"] ) < 1
+    remove: (id, attr) =>
+      if @pendingChanges.hasOwnProperty("#{id}")
+        delete @pendingChanges["#{id}"]["#{attr}"]
+        delete @pendingChanges["#{id}"] if @changeCount( @pendingChanges["#{id}"] ) < 1

-  submitAll: ->
-    all = []
-    for id, objectChanges of @pendingChanges
-      for attrName, change of objectChanges
-        all.push @submit(change)
-    all
+    submitAll: =>
+      all = []
+      for id, objectChanges of @pendingChanges
+        for attrName, change of objectChanges
+          all.push @submit(change)
+      all

-  submit: (change) ->
-    dataSubmitter(change).then (data) =>
-      @remove change.object.id, change.attr
-      change.scope.reset( data["#{change.attr}"] )
+    submit: (change) ->
+      dataSubmitter(change).then (data) =>
+        @remove change.object.id, change.attr
+        change.scope.reset( data["#{change.attr}"] )

-  changeCount: (objectChanges) ->
-    Object.keys(objectChanges).length
+    changeCount: (objectChanges) ->
+      Object.keys(objectChanges).length


Fix checkbox values on orders page

Fix checkbox values on orders page

--- app/assets/javascripts/admin/orders/controllers/orders_controller.js.coffee
+++ app/assets/javascripts/admin/orders/controllers/orders_controller.js.coffee
@@ -25,8 +25,8 @@
       'q[email_cont]': $scope['q']['email_cont'],
       'q[bill_address_firstname_start]': $scope['q']['bill_address_firstname_start'],
       'q[bill_address_lastname_start]': $scope['q']['bill_address_lastname_start'],
-      'q[completed_at_not_null]': $scope['q']['completed_at_not_null'],
-      'q[inventory_units_shipment_id_null]': $scope['q']['inventory_units_shipment_id_null'],
+      'q[completed_at_not_null]': $scope['q']['completed_at_not_null'] || null,
+      'q[inventory_units_shipment_id_null]': $scope['q']['inventory_units_shipment_id_null'] || null,
       'q[distributor_id_in]': $scope['q']['distributor_id_in'],
       'q[order_cycle_id_in]': $scope['q']['order_cycle_id_in'],
       'q[order_cycle_id_in]': $scope['q']['order_cycle_id_in'],


Revert removal of default argument in #fetchResults

--- app/assets/javascripts/admin/orders/controllers/orders_controller.js.coffee
+++ app/assets/javascripts/admin/orders/controllers/orders_controller.js.coffee
@@ -35,7 +35,7 @@
     $scope.setDefaults()
     $scope.fetchResults()

-  $scope.fetchResults = (page) ->
+  $scope.fetchResults = (page=1) ->
     startDateWithTime = $scope.appendStringIfNotEmpty($scope.q?.completed_at_gteq, ' 00:00:00')
     endDateWithTime = $scope.appendStringIfNotEmpty($scope.q?.completed_at_lteq, ' 23:59:59')


Ajustes na atualização do Qty Selector

Ajustes na atualização do Qty Selector

--- src/coffee/portal-qty-selector.coffee
+++ src/coffee/portal-qty-selector.coffee
@@ -16,12 +16,16 @@
 	init: =>
 		@render()
 		@bindEvents()
+		@element.trigger 'vtex.qty.changed', [@qty, @productId]

 	render: =>
 		$template = $($.qtySelector.template)
 		$template.find('.produtoQuantidade').attr('readonly', 'readonly') if @options.readonly
 		$template.find('.produtoQuantidade').val(@qty)
 		@element.html $template
+
+	update: =>
+		@element.find('.produtoQuantidade').val(@qty)

 	bindEvents: =>
 		$(window).on 'vtex.qty.changed', @qtyChanged
@@ -34,15 +38,17 @@
 	qtyChanged: (evt, qty, productId) =>
 		return unless @check(productId)
 		@qty = qty
-		@init()
+		@update()

 	decrementQty: =>
 		if @qty > 1
 			@element.trigger 'vtex.qty.changed', [@qty-1, @productId]
+		return false

 	incrementQty: =>
 		if @qty < @options.max
 			@element.trigger 'vtex.qty.changed', [@qty+1, @productId]
+		return false

 # PLUGIN ENTRY POINT


Retorna $el ao chamar skuSelector em um elemento.

--- src/coffee/portal-sku-selector.coffee
+++ src/coffee/portal-sku-selector.coffee
@@ -28,6 +28,8 @@
 		opts.skuVariationsPromise.fail (reason) -> opts.skuVariationsFailHandler($el, opts, reason)
 	else
 		console.error 'You must either provide a JSON or a Promise'
+
+	return $el

 $.fn.skuSelector.defaults =
 	skuVariationsPromise: undefined


Translated new sections and updated other translations

--- app/locale/es-419.coffee
+++ app/locale/es-419.coffee
@@ -787,7 +787,7 @@
     my_clans: "Mis Clanes"
     clan_name: "Nombre del clan"
     name: "Nombre"
-#    chieftain: "Chieftain"
+    chieftain: "Líder del Clan"
     type: "Tipo"
     edit_clan_name: "Editar el nombre del Clan"
     edit_clan_description: "Editar descripción del clan"
@@ -1188,27 +1188,27 @@
     poll: "Encuesta"
     user_polls_record: "Historia de Visitas de Encuestas"

-#  concepts:
-#    advanced_strings: "Advanced Strings"
-#    algorithms: "Algorithms"
-#    arguments: "Arguments"
-#    arithmetic: "Arithmetic"
-#    arrays: "Arrays"
-#    basic_syntax: "Basic Syntax"
-#    boolean_logic: "Boolean Logic"
-#    break_statements: "Break Statements"
-#    classes: "Classes"
-#    for_loops: "For Loops"
-#    functions: "Functions"
-#    if_statements: "If Statements"
-#    input_handling: "Input Handling"
-#    math_operations: "Math Operations"
-#    object_literals: "Object Literals"
-#    strings: "Strings"
-#    variables: "Variables"
-#    vectors: "Vectors"
-#    while_loops: "Loops"
-#    recursion: "Recursion"
+  concepts:
+    advanced_strings: "Cadenas - Avanzado"
+    algorithms: "Algoritmos"
+    arguments: "Argumentos"
+    arithmetic: "Aritmética"
+    arrays: "Arreglos"
+    basic_syntax: "Sintaxis Básica"
+    boolean_logic: "Lógica Booleana"
+    break_statements: "Sentencias Break"
+    classes: "Clases"
+    for_loops: "Bucle For"
+    functions: "Funciones"
+    if_statements: "Sentencias If"
+    input_handling: "Manejo de Entradas"
+    math_operations: "Operaciones Matemáticas"
+    object_literals: "Objetos Literales"
+    strings: "Cadenas"
+    variables: "Variables"
+    vectors: "Vectores"
+    while_loops: "Bucles"
+    recursion: "Recursividad"

   delta:
     added: "Agregado"


Add Poller class to poll rooms

Add Poller class to poll rooms

--- assets/javascripts/routes/index_route.js.coffee
+++ assets/javascripts/routes/index_route.js.coffee
@@ -1,24 +1,68 @@
 App.IndexRoute = App.AuthenticatedRoute.extend
   setupController: (controller, model)->
-    states   = []
+    stateData   = []
     stateIds = []
     activeState = null

-    model.forEach (item)->
+    # Loop thru all room states
+    #   find the first joined room to load
+    #   start pollers for all joined rooms
+    model.forEach (item)=>
       stateId = item.get("id")
-      if !activeState? && item.get("joined") == true
-        activeState = item
-      states.push(stateId)
-      states[stateId] = {}
+      stateIds.push(stateId)
+      console.log stateId
+
+      # This is to make sure the first active room is loaded
+      if item.get("joined") == true
+        activeState = item if !activeState?
+        poller = new App.Poller()
+        poller.store  = @store
+        poller.roomId = item.get("room").get("id")
+        poller.start(item.get("room").get("id"))
+        stateData[stateId] = {"poller": poller}
+      else
+        stateData[stateId] = {}

     for stateId in stateIds
-      console.log "k:", stateId, states[stateId]
+      console.log "stateId: #{stateId}"

     controller.set("stateIds", stateIds)
-    controller.set("states", states)
+    controller.set("stateData", stateData)
     controller.set("activeState", activeState)
     @_super(controller, model)

   model: ->
     @store.find("room_user_state")
+
+  deactivate: ->
+    states = controller.get("states")
+    console.log states
+
+
+App.Poller = Em.Object.extend
+  start: ()->
+    @started = true
+    @messages = []
+    @beforeMessageId = null
+    @timer = setInterval @onPoll.bind(@), 3000
+
+  stop: ->
+    clearInterval(@timer)
+    @started = false
+
+  onPoll: ->
+    console.log("polling now #{@roomId}")
+    if @afterMessageId
+      console.log "after #{@afterMessageId}"
+      url = "/api/messages/#{@roomId}?after=#{@afterMessageId}"
+    else
+      url = "/api/messages/#{@roomId}"
+
+    $.getJSON url, (response)=>
+      Em.$.each response.messages, (index, message)=>
+        if (! @store.recordIsLoaded(App.Message, message.id))
+          console.log message
+          @afterMessageId = message.id
+
+


Translated new words in settings.cson to zh-cn

--- def/zh-cn/settings.cson
+++ def/zh-cn/settings.cson
@@ -11,7 +11,7 @@
     }
     {
       _label: "System"
-      value: "System"
+      value: "系统"
     }
     {
       _label: "Keybindings"
@@ -38,7 +38,7 @@
   sectionHeadings: [
     {_label: "Core Settings", value: "核心设置"}
     {_label: "Editor Settings", value: "编辑器设置"}
-    {_label: "System Settings", value: "System Settings"}
+    {_label: "System Settings", value: "系统设置"}
     {_label: "Keybindings", value: "快捷键"}
     {_label: "Installed Packages", value: "已安装插件包"}
     {_label: "Choose a Theme", value: "选择主题"}
@@ -300,23 +300,23 @@
       }
       {
         _id: 'system.windows.file-handler'
-        title: "Register as file handler"
-        desc: "Show Atom in the \"Open with\" application list for easy association with file types."
+        title: "注册为打开文件时使用的应用程序"
+        desc: "在\"打开方式\"菜单中显示 Atom 以建立文件关联。"
       }
       {
         _id: 'system.windows.shell-menu-files'
-        title: "Show in file context menus"
-        desc: "Add \"Open with Atom\" to the File Explorer context menu for files."
+        title: "在文件的鼠标右键菜单中显示"
+        desc: "在文件资源管理器内，文件的鼠标右键菜单中添加 \"Open with Atom\"（用 Atom 打开）选项。"
       }
       {
         _id: 'system.windows.shell-menu-folders'
-        title: "Show in folder context menus"
-        desc: "Add \"Open with Atom\" to the File Explorer context menu for folders."
+        title: "在文件夹的鼠标右键菜单中显示"
+        desc: "在文件资源管理器内，文件夹的鼠标右键菜单中添加 \"Open with Atom\"（用 Atom 打开）选项。"
       }
     ]
   }
   system: {
-    notes: "These settings determine how Atom integrates with your operating system."
+    notes: "这些设置会决定 Atom 如何与你的操作系统整合。"
   }
   keymaps: {
     notes: {
@@ -333,7 +333,7 @@
     "table-header-selector": "选择器"
   }
   packages: {
-    searchBarText: 'Filter packages by name'
+    searchBarText: '按名称过滤插件包'
   }
   themes: {
     notes: {
@@ -346,7 +346,7 @@
     description1: "标签页、状态栏、树状图、下拉菜单的外观"
     title2: "语法主题"
     description2: "编辑器中文本的外观"
-    searchBarText: 'Filter themes by name'
+    searchBarText: '按名称过滤主题'
   }
   updates: {
     "check-updates": "检查更新"


code cleanup in KDTooltip

code cleanup in KDTooltip

--- client/Framework/components/tooltip/KDTooltip.coffee
+++ client/Framework/components/tooltip/KDTooltip.coffee
@@ -140,7 +140,7 @@
     # check the default values for overlapping boundaries, then
     # recalculate if there are overlaps

-    violations = boundaryViolations getCoordsFromPlacement(d, placement, direction),\
+    violations = getBoundaryViolations getCoordsFromPlacement(d, placement, direction),\
     d.container.width, d.container.height

     if Object.keys(violations).length > 0 # check for possible alternatives
@@ -160,7 +160,7 @@
       ]

       for variant in variants
-        if Object.keys(boundaryViolations(getCoordsFromPlacement(d,variant[0],variant[1]),\
+        if Object.keys(getBoundaryViolations(getCoordsFromPlacement(d,variant[0],variant[1]),\
         d.container.width, d.container.height)).length is 0
           [placement,direction] = variant
           break
@@ -262,7 +262,7 @@
     right   : "right"

   # will return an object with the amount of clipped pixels
-  boundaryViolations = (coordinates,width,height)=>
+  getBoundaryViolations = (coordinates,width,height)=>
     violations = {}
     if coordinates.left < 0
       violations.left   = -(coordinates.left)
@@ -282,12 +282,15 @@
     coordinates =
       top  : dimensions.selector.offset.top
       left : dimensions.selector.offset.left
+
     [staticAxis,dynamicAxis,staticC,dynamicC,exclusion] =
       if /o/.test placement then ['height','width','top','left','right']
       else ['width','height','left','top','bottom']
+
     coordinates[staticC]+= unless placement.length<5
         (dimensions.selector[staticAxis]+10)
       else -(dimensions.container[staticAxis]+10)
+
     unless direction is exclusion
       coordinates[dynamicC]+=getCoordsDiff(dimensions,dynamicAxis,direction is 'center')
     return coordinates


Account Settings: Credentials: Fix syntax

--- client/account/lib/accountcredentialeditmodal.coffee
+++ client/account/lib/accountcredentialeditmodal.coffee
@@ -33,7 +33,7 @@
         unless showError err
           @form.emit 'CredentialUpdated', credential

-    @addSubView @wrapper = new KDView cssClass 'stacks step-creds'
+    @addSubView @wrapper = new KDView cssClass : 'stacks step-creds'

     @wrapper.addSubView @form = ui.generateAddCredentialFormFor formOptions


MemberItemView: No need to wait groupControllers.ready.

--- client/admin/lib/views/members/memberitemview.coffee
+++ client/admin/lib/views/members/memberitemview.coffee
@@ -95,7 +95,6 @@

   handleRoleChange: (newRole) ->

-    { groupsController } = kd.singletons
     button   = @actionButtons[newRole]
     jAccount = @getData()

@@ -103,28 +102,27 @@

     @isInProgress = yes

-    groupsController.ready =>
-      group    = groupsController.getCurrentGroup()
-      newRoles = [ newRole ]
+    group    = kd.singletons.groupsController.getCurrentGroup()
+    newRoles = [ newRole ]

-      newRoles.push 'admin'  if newRole is 'owner'
+    newRoles.push 'admin'  if newRole is 'owner'

-      group.changeMemberRoles jAccount.getId(), newRoles, (err, response) =>
+    group.changeMemberRoles jAccount.getId(), newRoles, (err, response) =>
+      return @handleError button  if err
+
+      group.fetchUserRoles [ jAccount.getId() ], (err, roles) =>
+
         return @handleError button  if err

-        group.fetchUserRoles [ jAccount.getId() ], (err, roles) =>
+        data        = @getData()
+        data.roles  = (role.as for role in roles)
+        @memberRole = @getRole data.roles

-          return @handleError button  if err
+        @roleLabel.updatePartial "#{@memberRole.label} "
+        @settings.destroySubViews()
+        @createSettingsView()

-          data        = @getData()
-          data.roles  = (role.as for role in roles)
-          @memberRole = @getRole data.roles
-
-          @roleLabel.updatePartial "#{@memberRole.label} "
-          @settings.destroySubViews()
-          @createSettingsView()
-
-          @isInProgress = no
+        @isInProgress = no

   handleError: (button, message) ->
@@ -136,13 +134,11 @@

   kick: ->

-    { groupsController } = kd.singletons
-    groupsController.ready =>
-      groupsController.getCurrentGroup().kickMember @getData().getId(), (err) =>
+    kd.singletons.groupsController.getCurrentGroup().kickMember @getData().getId(), (err) =>

-        return @handleError @actionButtons.kick, 'Failed to kick user. Please try again.'  if err
+      return @handleError @actionButtons.kick, 'Failed to kick user. Please try again.'  if err

-        @destroy()
+      @destroy()

   toggleSettings: ->


client: clarify that function returns another function

--- client/app/lib/desktopnotificationscontroller.coffee
+++ client/app/lib/desktopnotificationscontroller.coffee
@@ -19,14 +19,15 @@
  * @param {Function} callback - to be called with err, and args object.
  * @return {Function}
 ###
-permitted = (callback) -> (args...) ->
-  browserNotifications
-    .requestPermissions()
-    .then (isPermitted) ->
-      return if isPermitted
-      then callback null, args...
-      else callback { message: 'Browser notifications are not permitted' }
-    .catch (err) -> callback err
+permitted = (callback) ->
+  return (args...) ->
+    browserNotifications
+      .requestPermissions()
+      .then (isPermitted) ->
+        return if isPermitted
+        then callback null, args...
+        else callback { message: 'Browser notifications are not permitted' }
+      .catch (err) -> callback err

 module.exports = class DesktopNotificationsController extends kd.Controller


Landing: fix team domain error

Landing: fix team domain error

--- client/landing/site.landing/coffee/core/utils.coffee
+++ client/landing/site.landing/coffee/core/utils.coffee
@@ -285,8 +285,13 @@

   verifySlug : (name, callbacks = {}) ->

-    unless 2 < name.length and /^[a-z0-9][a-z0-9-]+$/.test name
+    name = name.toLowerCase()
+
+    unless 2 < name.length
       return callbacks.error 'Domain name should be longer than 2 characters!'
+
+    unless /^[a-z0-9][a-z0-9-]+$/.test name
+      return callbacks.error 'Domain name is not valid, please try another one.'

     $.ajax
       url         : "/-/teams/verify-domain"


fix error with not existing templates array

--- client/views/common/addWeekModal/addWeekModal.coffee
+++ client/views/common/addWeekModal/addWeekModal.coffee
@@ -3,7 +3,8 @@
 	getProject: ->
 		project = Projects.findOne FlowRouter.getParam('projectId'), fields: tags: 1

-		for tag in project.tags when tag.templates.length > 0
+		if project && project.tags
+			for tag in project.tags when tag.templates && tag.templates.length > 0
 			return project

 		_id: project._id, noTemplate: true


Fixes tests, adds AppendEquals

Fixes tests, adds AppendEquals

--- javascripts/lib/math/expression_manipulation.coffee
+++ javascripts/lib/math/expression_manipulation.coffee
@@ -50,9 +50,6 @@
       initialize: (@opts)->
         @val = @opts.value

-      appendImplicitMultiplication: (expression)->
-        expression.append(comps.multiplication.build())
-
       doAppend: (expression)->
         last = expression.last()
         number_with_this_val = comps.number.build(value: @val)
@@ -61,7 +58,8 @@
           new_last = last.concatenate(@val)
           expression.replaceLast(new_last)
         else if (last && last instanceof comps.exponentiation) or (last && !last.isOperator())
-          @appendImplicitMultiplication(expression).append(number_with_this_val)
+          expression.append(comps.multiplication.build()).
+            append(number_with_this_val)
         else
           expression.append(number_with_this_val)

@@ -75,7 +73,7 @@
       initialize: (@opts={})->

       baseExpression: (base)->
-        comps.expression.build(expression: [base])
+        comps.expression.buildUnlessExpression base

       powerExpression: ()->
         power = if @opts.power
@@ -108,21 +106,16 @@
         else
           base = @baseExpression(expression.last())

-
         power = @powerExpression()
-
-

         expression.replaceLast(
           comps.exponentiation.build(base: base, power: power))

     class_mixer(ExponentiateLast)

-    class SquareLast
-
-
     class AppendMultiplication
       appendAction: (expression)->
+
         _OverrideIfOperatorOrAppend.build(expression).with comps.multiplication.build()

       invoke: (expression)->
@@ -131,6 +124,10 @@

     class_mixer(AppendMultiplication)

+    class AppendEquals
+      invoke: (expression)->
+        expression.append comps.equals.build()
+    class_mixer(AppendEquals)

     class AppendDivision
       invoke: (expression)->
@@ -225,12 +222,11 @@

     class CloseSubExpression
-      initialize: ->
-        @handled = false
       invoke: (expression)->
         logger.info("CloseSubExpression#invoke")
-        _FinalOpenSubExpressionApplication.build(expression).
+        ret = _FinalOpenSubExpressionApplication.build(expression).
           invoke((expression)-> expression.close())
+        ret
     class_mixer(CloseSubExpression)

@@ -271,7 +267,13 @@
       with: (operator)->
         last = @expression.last()
         if last && last.isOperator()
-          @expression.replaceLast(operator)
+          if last instanceof comps.exponentiation
+            if last.power().isBlank()
+              @expression.replaceLast(operator)
+            else
+              @expression.append(operator)
+          else
+            @expression.replaceLast(operator)
         else
           @expression.append(operator)
     class_mixer(_OverrideIfOperatorOrAppend)
@@ -294,6 +296,7 @@
       exponentiate_last: ExponentiateLast
       append_multiplication: AppendMultiplication
       append_addition: AppendAddition
+      append_equals: AppendEquals
       append_subtraction: AppendSubtraction
       negate_last: Negation
       open_sub_expression: OpenSubExpression


fix for showing of calculate questions

fix for showing of calculate questions

--- frontend/coffeescript/app/webform/views.coffee
+++ frontend/coffeescript/app/webform/views.coffee
@@ -51,7 +51,7 @@
               @currentLanguage = _.keys(document.flat_fields[0].label)[0]

             @repopulateForm()
-            @_display_form_buttons( 0, null )
+            @_display_form_buttons( 0, document.flat_fields[0] )
             @

         change_language: (language) ->
@@ -328,7 +328,7 @@

         _display_form_buttons: ( question_index, question ) ->

-            if question_index == @numberOfQuestions - 1 or ( question_index > 0 and (not question) )
+            if question_index == @numberOfQuestions - 1 or (not question)
                 $( '#prev_btn' ).show()
                 $( '#submit_btn' ).show()

@@ -350,6 +350,9 @@
                 $( '#submit_btn' ).hide()
                 # $("#xform_view").keydown (e) ->
                 #     $("#next_btn").click()  if e.keyCode is 13
+
+            if @currentQuestionIndex == 0
+              $( '#prev_btn' ).hide()

             if document.getElementById( 'detail_data_id' ) != null
                 $( '#submit_btn' ).show()
@@ -449,7 +452,7 @@
               return

             #Is this question relevant?  Or, is this question an equation?
-            if (current_question.bind and current_question.bind.calculate) or not
+            if (current_question.bind and current_question.bind.calculate != null) or not
               XFormConstraintChecker.isRelevant( current_question, @queryStringToJSON($( ".form" ).serialize()) )
                 # If its a calculation, calculate it!
                 $("#" + current_question.name).val _performCalcluate(current_question.bind.calculate)  if current_question.bind and current_question.bind.calculate


abort LP requests in-transit when stopping to silence network activity spinner

--- lib/assets/javascripts/firehose/long_poll.js.coffee
+++ lib/assets/javascripts/firehose/long_poll.js.coffee
@@ -37,7 +37,7 @@
     data.last_message_sequence = @_lastMessageSequence
     # TODO: Some of these options will be deprecated in jQuery 1.8
     #       See: http://api.jquery.com/jQuery.ajax/#jqXHR
-    $.ajax
+    @_lastRequest = $.ajax
       url:          @config.longPoll.url
       crossDomain:  true
       data:         data
@@ -48,6 +48,12 @@

   stop: =>
     @_stopRequestLoop = true
+    if @_lastRequest?
+      try @_lastRequest.abort() catch e
+      delete @_lastRequest
+    if @_lastPingRequest?
+      try @_lastPingRequest.abort() catch e
+      delete @_lastPingRequest

   _success: (data, status, jqXhr) =>
     if @_needToNotifyOfReconnect or not @_succeeded
@@ -67,7 +73,7 @@
     # Ping long poll server to verify internet connectivity
     # jQuery CORS doesn't support timeouts and there is no way to access xhr2 object
     # directly so we can't manually set a timeout.
-    $.ajax
+    @_lastPingRequest = $.ajax
       url:          @config.longPoll.url
       method:       'HEAD'
       crossDomain:  true


update to admin coffee

update to admin coffee

--- app/assets/javascripts/admin/admin.js.coffee
+++ app/assets/javascripts/admin/admin.js.coffee
@@ -49,5 +49,4 @@
     allowClear: true

   $.scrollUp()
-  return
-
+  return


Fix broken dont-preview checkbox

Fix broken dont-preview checkbox

--- app/assets/javascripts/app/imports.js.coffee
+++ app/assets/javascripts/app/imports.js.coffee
@@ -1,4 +1,4 @@
-$(document).on 'change', '#import_dont_preview', (e) ->
+$(document).on 'change', '#dont_preview', (e) ->
   $('#btn-execute').toggle(@checked)
   $('#btn-preview').toggle(!@checked)


fix #1452 typo: 'Matrial' in nicEditor

fix #1452 typo: 'Matrial' in nicEditor

--- app/assets/javascripts/card_markup.js.coffee
+++ app/assets/javascripts/card_markup.js.coffee
@@ -230,7 +230,7 @@
   window.nicAttachmentButton = nicMarkupButton.extend { type: "Attachment", clazz: "attachment" }
   nicOptions = {
     buttons : {
-      "material" : {name : "Matrial", type: "nicMaterialButton", tags : ["A"]},
+      "material" : {name : "Material", type: "nicMaterialButton", tags : ["A"]},
       "tool" : {name : "Tool", type: "nicToolButton", tags : ["A"]},
       "blueprint" : {name : "Blueprint", type: "nicBlueprintButton", tags : ["A"]},
       "attachment" : {name : "Attachment or Link", type: "nicAttachmentButton", tags : ["A"]},


add possibility to permit buttons; to filter buttons with conf.roles

--- modules/gui/lib/editor/Editor.coffee
+++ modules/gui/lib/editor/Editor.coffee
@@ -30,10 +30,14 @@
             super (err)=>
                 return done(err) if err

-                @_buildButtons @__buttons
-                @_wireMouse()
-
-                done()
+                try
+                    @_buildButtons @__buttons
+                    @_wireMouse()
+                    done()
+
+                catch err
+                    done(err)
+

         ##
@@ -65,16 +69,21 @@
                     view:
                         children: [ editor ]
                 , fn
-
-            for action, conf of buttons
-                do(action, conf)=>
+
+            @_process buttons,
+                done: (err)=>
+                    if err
+                        throw err
+
+                each: (action, conf, next)=>

                     if typeof conf is 'function'
                         conf =
                             handler: conf

-                    @_createButton action, conf, (err, button)=>
-
+                    @_createPermitedButton action, conf, (err, button)=>
+                        return next(err) if err || !button
+
                         if typeof conf is 'string'
                             conf =
                                 text: conf
@@ -103,12 +112,26 @@
                                     parent: @__root.parent()

                             return false
-
+
+                        next()
+
         ##
         ##
         ##
         _createButton: (action, conf, fn)->
             fn null, $('<button/>').addClass action
-
+

+        ##
+        ##
+        ##
+        _createPermitedButton: (action, conf, fn)->
+            if conf.roles
+                @identity.hasRole conf.roles, (err, hasRole)=>
+                    return fn(err) if err || !hasRole
+
+                    @_createButton action, conf, fn
+
+            else @_createButton action, conf, fn
+


remove window.current_user

remove window.current_user

--- app/assets/javascripts/components/login.cjsx
+++ app/assets/javascripts/components/login.cjsx
@@ -23,7 +23,7 @@
       if result?.data
         @setState
           user: result.data
-          loading: false, => window.current_user = @state.user
+          loading: false
       else
         @setState
           loading: false


Fix spelling for Operating costs in Merit Order chart. Fix #1301

--- app/assets/javascripts/d3/merit_order.coffee
+++ app/assets/javascripts/d3/merit_order.coffee
@@ -172,7 +172,7 @@
         .attr("data-tooltip-text", (d) ->
           html = "Installed capacity: #{Metric.autoscale_value(d.value_x(), 'MW', 2)}

-                  Operating costs: #{Metric.autoscale_value d.original_y_value(), 'euro', 2}
+                  Operating costs: #{Metric.autoscale_value d.original_y_value(), 'Eur/Mwh', 2}

                   Load factor: #{d.load_factor_value()}
                   "


Charts: added some comments

Charts: added some comments

--- app/assets/javascripts/d3/merit_order.coffee
+++ app/assets/javascripts/d3/merit_order.coffee
@@ -131,6 +131,8 @@
         left_margin: 15

     refresh: =>
+      # updated scales and axis
+      #
       max_height = @nodes.max_height()
       @y.domain([0, max_height])
       @inverted_y.domain([0, max_height])
@@ -140,7 +142,7 @@

       @min_node_height = 2

-      # let's calculate the x-offset
+      # let's calculate the x-offset of the blocks
       offset = 0
       sorted = @nodes.sortBy((d) -> d.value_y()).map (d) ->
         value = offset
@@ -173,12 +175,11 @@
           html
         )

-
+# Node collection added to simplify some calculations
+#
 class D3.merit_order.NodeList extends Backbone.Collection
   model: D3.merit_order.Node

-  max_height: =>
-    d3.max @models, (i) -> i.value_y()
+  max_height: => d3.max @models, (i) -> i.value_y()

-  total_width: =>
-    d3.sum @models, (n) -> n.value_x()
+  total_width: => d3.sum @models, (n) -> n.value_x()


fixed bug with random name

fixed bug with random name

--- app/assets/javascripts/settlements.js.coffee
+++ app/assets/javascripts/settlements.js.coffee
@@ -9,10 +9,11 @@
 Settlement = () ->
   @name = $('#settlement-name-field')
   @pop  = $('#settlement-population-field')
-  $('.randcheck.name').click =>
-    $.getJSON '/settlements/random-name', (name) =>
-      @name.val(name[0])
-      @pop.val(name[1])
+  $('.randcheck.name').click ->
+    $.get '/settlements/random-name',
+      {}
+      (data) -> $('#settlement-name-field').val(data)
+      'html'

   #make the selection box a pretty pretty princess
   $('select').selectbox()


Add leave method to the projects resource

--- app/coffee/modules/resources/projects.coffee
+++ app/coffee/modules/resources/projects.coffee
@@ -45,6 +45,9 @@
     service.stats = (projectId) ->
         return $repo.queryOneRaw("projects", "#{projectId}/stats")

+    service.leave = (projectId) ->
+        return $repo.queryOneRaw("projects", "#{projectId}/leave")
+
     service.memberStats = (projectId) ->
         return $repo.queryOneRaw("projects", "#{projectId}/member_stats")


add missing 'Backbone' require

add missing 'Backbone' require

fixes CNVS-4627

test plan:
- with compiled assets enabled, go to a page that uses editor toggle (like quiz
  edit)
- it should not throw an error about missing Backbone

Change-Id: I3207157396b74daa85cf1b1d55f91135976610b3
Reviewed-on: https://gerrit.instructure.com/18578
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
Reviewed-by: Stanley Stuart <2b43fb8b7a234825d50dd49ce7892d78a59da8f3@instructure.com>
QA-Review: Amber Taniuchi <ef4bfd1e6d278f6bc7d4a478f02be2cb9581a0a5@instructure.com>

--- app/coffeescripts/editor/EditorToggle.coffee
+++ app/coffeescripts/editor/EditorToggle.coffee
@@ -2,9 +2,10 @@
   'underscore'
   'i18n!editor'
   'jquery'
+  'Backbone'
   'compiled/fn/preventDefault'
   'tinymce.editor_box'
-], (_, I18n, $, preventDefault) ->
+], (_, I18n, $, Backbone, preventDefault) ->

   ##
   # Toggles an element between a rich text editor and itself
@@ -118,4 +119,4 @@

   _.extend(EditorToggle.prototype, Backbone.Events)

-  EditorToggle+  EditorToggle


tests

tests

--- apps/fair/components/browse/test/view.coffee
+++ apps/fair/components/browse/test/view.coffee
@@ -18,6 +18,7 @@
         for klass in ['BoothsView', 'FilterArtworksView']
           @[klass] = (opts) -> _.extend @, opts
           @[klass]::params = new Backbone.Model
+          @[klass]::counts = new Backbone.Model
           sinon.spy @, klass
           FairBrowseView.__set__ klass, @[klass]
         @fair = new Fair fabricate 'fair'
@@ -38,6 +39,15 @@
     it 'sets up filter artwork and filter booths views', ->
       @FilterArtworksView.calledWithNew.should.be.ok
       @BoothsView.calledWithNew.should.be.ok
+
+    it 'binds section changes to reset', ->
+      @view.boothsSection = sinon.stub()
+      @view.artworksSection = sinon.stub()
+      @view.initialize()
+      @view.boothParams.trigger 'change reset'
+      @view.boothsSection.callCount.should.equal 2
+      @view.artworkParams.trigger 'change reset'
+      @view.artworksSection.callCount.should.equal 2

   describe '#boothsSection', ->


focus new window, add Chrome.openURL

focus new window, add Chrome.openURL

--- HTML/osx.coffee
+++ HTML/osx.coffee
@@ -12,6 +12,7 @@
   createWindow: (path) ->
     c = OSX.AtomWindowController.alloc.initWithWindowNibName "AtomWindow"
     c.window
+    c.window.makeKeyAndOrderFront null

   # Set the active window's dirty status.
   setDirty: (bool) ->
@@ -43,6 +44,11 @@
     pb = OSX.NSPasteboard.generalPasteboard
     pb.declareTypes_owner [OSX.NSStringPboardType], null
     pb.setString_forType text, OSX.NSStringPboardType
+
+  openURL: (url) ->
+    c = OSX.AtomWindowController.alloc.initWithURL url
+    c.window
+    c.window.makeKeyAndOrderFront null

   # name - Command name, like "Find in file"
   # shortcut - String command name, e.g.


send audio attachment immediately for mobile device

--- www/js/controllers/msg.coffee
+++ www/js/controllers/msg.coffee
@@ -91,7 +91,7 @@
 								socket?.once 'error', reject
 					.then ->
 						# send attachment if it is audio file
-						if /^blob/.test(msg.file?.url) and /^audio/.test(msg.file?.type)
+						if /^blob/.test(msg.file?.url) and /^audio/.test(msg.file?.type) and env.isMobile()
 							return msg.$save().catch $log.error
 						collection.add msg
 						$scope.$apply 'collection.models'


Fix tests

Fix tests

--- spec/event-helpers.coffee
+++ spec/event-helpers.coffee
@@ -18,6 +18,13 @@
     setData: (key, value) -> @data[key] = "#{value}" # Drag events stringify data values
     getData: (key) -> @data[key]

+  Object.defineProperty(
+    dataTransfer,
+    'items',
+    get: ->
+      Object.keys(dataTransfer.data).map((key) -> {type: key})
+  )
+
   dragStartEvent = buildMouseEvent("dragstart", dragged)
   Object.defineProperty(dragStartEvent, 'dataTransfer', get: -> dataTransfer)


Recommend asking teacher for next course when all assigned courses are finished | clean code

--- app/views/courses/CoursesView.coffee
+++ app/views/courses/CoursesView.coffee
@@ -112,17 +112,12 @@
       @ownerNameMap[ownerID] = NameLoader.getName(ownerID) for ownerID in ownerIDs
       @render?()
     )
-    @allCompleted = true
-    for classroom, index in @classrooms.models
-      courseInstances = @courseInstances.where({classroomID: classroom.id})
-      for courseInstance in courseInstances
+
+    @allCompleted = not _.some @classrooms.models, classroom ->
+      _.some @courseInstances.where({classroomID: classroom.id}), courseInstance ->
         course = @store.state.courses.byId[courseInstance.get('courseID')]
         stats = classroom.statsForSessions(courseInstance.sessions, course._id)
-        unless stats.courseComplete
-          @allCompleted = false
-          break
-      unless @allCompleted
-        break
+        not stats.courseComplete

     _.forEach _.unique(_.pluck(@classrooms.models, 'id')), (classroomID) =>
       levels = new Levels()


Fix indentation

Fix indentation

--- app/models/Entity.coffee
+++ app/models/Entity.coffee
@@ -27,7 +27,7 @@
   update: =>
     # update method

-# viewDirection {0:up, 1:right, 2:down, 3: left}
+  # viewDirection {0:up, 1:right, 2:down, 3: left}
   moveUp: =>
     # get attributes
     position = @position


Add spec

Add spec

--- spec/snippets-spec.coffee
+++ spec/snippets-spec.coffee
@@ -20,7 +20,7 @@
       atom.packages.activatePackage('language-javascript')

     waitsForPromise ->
-      atom.packages.activatePackage("snippets")
+      atom.packages.activatePackage('snippets')

     runs ->
       editor = atom.workspace.getActiveTextEditor()
@@ -28,7 +28,7 @@

   afterEach ->
     waitsForPromise ->
-      Promise.resolve(atom.packages.deactivatePackage("snippets"))
+      atom.packages.deactivatePackage('snippets')

   describe "provideSnippets interface", ->
     snippetsInterface = null
@@ -38,11 +38,24 @@

     describe "bundledSnippetsLoaded", ->
       it "indicates the loaded state of the bundled snippets", ->
-        Snippets.loaded = false
         expect(snippetsInterface.bundledSnippetsLoaded()).toBe false
         Snippets.doneLoading()
         expect(snippetsInterface.bundledSnippetsLoaded()).toBe true

+      it "resets the loaded state after snippets is deactivated", ->
+        expect(snippetsInterface.bundledSnippetsLoaded()).toBe false
+        Snippets.doneLoading()
+        expect(snippetsInterface.bundledSnippetsLoaded()).toBe true
+
+        waitsForPromise -> atom.packages.deactivatePackage('snippets')
+        waitsForPromise -> atom.packages.activatePackage('snippets')
+
+        runs ->
+          expect(snippetsInterface.bundledSnippetsLoaded()).toBe false
+          Snippets.doneLoading()
+          expect(snippetsInterface.bundledSnippetsLoaded()).toBe true
+
+    describe "insertSnippet", ->
       it "can insert a snippet", ->
         editor.setSelectedBufferRange([[0, 4], [0, 13]])
         snippetsInterface.insertSnippet("hello ${1:world}", editor)


Op fix in the access routine

Op fix in the access routine

--- library/exposure/access.coffee
+++ library/exposure/access.coffee
@@ -82,7 +82,7 @@
             return callback format error if error
             p = get: -> return entity or undefined
             Object.defineProperty container, key, p
-            callback undefined; return this
+            assert container[key]?; callback()

     # Authenticate supplied entity as the authorized entity against
     # the session found in the specified session container. Session