various typo and grammar fix

various typo and grammar fix

--- client/app/locales/fr.coffee
+++ client/app/locales/fr.coffee
@@ -5,7 +5,7 @@
     "Download" : "Télécharger"
     "Edit" : "Modifier"
     "Stop editing" : "Arrêter modifier"
-    "It will appears on your homepage." : "It apparaitra votre page d'accueil"
+    "It will appears on your homepage." : "Il apparaîtra sur votre page d'accueil"
     "Make it Hidden" : "masqué"
     "Make it Private" : "privé"
     "Make it Public" : "public"
@@ -15,16 +15,16 @@
     "hidden" : "masqué"
     "There is no photos in this album" : "Pas de photos dans cet album"
     "There is no public albums." : "Il n'y a aucun album"
-    "This album is private" : "Cet album est Privé"
-    "This album is hidden" : "Cet album est Masqué"
-    "This album is public" : "Cet album est Public"
+    "This album is private" : "Cet album est privé"
+    "This album is hidden" : "Cet album est masqué"
+    "This album is public" : "Cet album est public"
     "Title ..." : "Titre ..."
     "Write some more ..." : "Description ..."
     "View" : "Voir"
     "is too big (max 10Mo)":"est trop grosse (max 10Mo)"
     "is not an image": "n'est pas une image"
     "Share album by mail": "Partagez par mail"
-    "Upload your contacts ...": "Uploader vos contacts ..."
+    "Upload your contacts ...": "Uploadez vos contacts…"
     "Share album": "Partagez l'album"
     "Add contact": "Ajouter contact"
     "Send mail": "Envoyez mail"
@@ -38,33 +38,33 @@
         l'album.'''

     "pick from computer":"""
-        Click Here or drag your photos below to upload
+        Cliquez ici ou glissez-déposez vos photos pour uploader
     """
     "pick from files":"""
-        Click Here to pick from cozy files
+        Cliquez ici pour choisir dans vos fichiers de cozy
     """

     "hidden-description": """
-      Il n'apparaitra pas sur votre page d'accueil,
+      Il n'apparaîtra pas sur votre page d'accueil,
       Mais vous pouvez partager cet url :
     """

     "It cannot be accessed from the public side" : """
-      Il ne peut pas être vu depuis le coté public
+      Il ne peut pas être vu depuis le côté public
     """

     "rebuild thumbnails": "Regénérer les vignettes"

      # clearance modal
-    "also have access" : "Ces personnes ont égalment accès, car ils ont accès à un dossier parent"
+    "also have access" : "Ces personnes ont égalment accès, car elles ont accès à un dossier parent"
     "cancel" : "Annuler"
-    "copy paste link" : "Pour donner accès à votre contact envoyez lui ce lien : "
-    "details" : "Details"
+    "copy paste link" : "Pour donner accès à votre contact envoyez-lui ce lien : "
+    "details" : "Détails"
     "inherited from" : "hérité de"
     "modal question album shareable" : "Choisissez le mode de partage pour cet album"
-    "modal shared album custom msg" : "Entrez un email et appuyez sur entrez"
-    "modal shared album link msg" : "Envoyez ce lien pour qu'ils puissent accéder à cet album"
-    "only you can see" : "Seul vous et les personnes ci-dessous peuvent accéder à cette ressource."
+    "modal shared album custom msg" : "Entrez un email et appuyez sur Entrée"
+    "modal shared album link msg" : "Envoyez ce lien pour donner accès à cet album"
+    "only you can see" : "Seuls vous et les personnes ci-dessous peuvent accéder à cette ressource."
     "public" : "Public"
     "private" : "Privé"
     "shared" : "Partagé"
@@ -77,17 +77,17 @@
     "forced public" : "Ce dossier est public car un parent est public : "
     "confirm" : "Confirmer"
     "share forgot add" : """
-Il semble que vous ayez oublié d'appuyer sur le boutton Add
+Il semble que vous ayez oublié d'appuyer sur le boutton Ajouter
 """
     "share confirm save" : """
-Les changements effectués sur les permissions ne seront pas sauvegardés. Etes vous sur ?
+Les changements effectués sur les permissions ne seront pas sauvegardées. Etes vous sûr ?
 """
     "yes forgot" : "Retour"
     "no forgot" : "Ok"
     "perm" : "peut "
     "r" : "lire cet album"
     "perm r album" : "parcourir cet album"
-    "perm rw album" : "parcourir cet ablum et ajouter des photos"
+    "perm rw album" : "parcourir cet album et ajouter des photos"
     "change notif" : """
 Cocher cette case pour recevoir une notification cozy quand un contact
 ajoute une photo à cet album."""


updated isActiveSort syntax

updated isActiveSort syntax

--- src/components/performance.cjsx
+++ src/components/performance.cjsx
@@ -20,9 +20,7 @@
     isNameSort: true

   sortClick: (event) ->
-    isActiveSort =
-      _.contains(event.target.classList, 'is-ascending') or
-      _.contains(event.target.classList, 'is-descending')
+    isActiveSort = event.target.classList.contains('is-ascending', 'is-descending')
     # this is a special case for the name header data which is one level above nested data
     if not _.contains(event.target.classList, 'student-name')
       @setState({isNameSort: false})


This is going to get annoying

This is going to get annoying

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -1,7 +1,7 @@
 'editor':
-  'fontSize': 15
   'showIndentGuide': true
   'fontFamily': 'Menlo'
+  'fontSize': 15
 'core':
   'excludeVcsIgnoredPaths': true
   'ignoredNames': [
@@ -31,5 +31,5 @@
 'welcome':
   'showOnStartup': false
 'release-notes':
-  'viewedVersion': '0.85.0'
+  'viewedVersion': '0.89.0'
 'tree-view': {}


Add atom-material-ui to config.cson [auto generated]

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -1,5 +1,7 @@
 "*":
   Zen: {}
+  "atom-material-ui":
+    fonts: {}
   "autocomplete-plus": {}
   core:
     disabledPackages: [


Update Task.tag() so you don't have to specify the #

--- app/models/task.coffee
+++ app/models/task.coffee
@@ -45,6 +45,6 @@
     return [] unless tag
     tag = tag.toLowerCase()
     @select (item) ->
-      item.name?.toLowerCase().indexOf(tag) > -1
+      item.name?.toLowerCase().indexOf('#'+tag) > -1

 module.exports = Task


(fix) Fix saving of corrections.

(fix) Fix saving of corrections.

--- app/scripts/api.coffee
+++ app/scripts/api.coffee
@@ -33,7 +33,7 @@
   put:
     exercise: (id, content) -> put "/exercises/#{id}", content
     correction: (id, results) -> put "/correction/store",
-        id: id
+        solution: id
         results: results
   post:
     login: (username, password) -> post "/login",


Remove some unnecessary console logging

Remove some unnecessary console logging

--- lib/autocomplete-fortran.coffee
+++ lib/autocomplete-fortran.coffee
@@ -43,7 +43,7 @@
     @provider

   rebuild: ()->
-    console.log "Rebuild triggered"
+    #console.log "Rebuild triggered"
     if @provider?
       @provider.rebuildIndex()

@@ -52,7 +52,7 @@
     varWord = editor.getSelectedText()
     bufferRange = editor.getSelectedBufferRange()
     defPos = @provider.goToDef(varWord, editor, bufferRange.end)
-    console.log defPos
+    #console.log defPos
     if defPos?
       splitInfo = defPos.split(":")
       fileName = splitInfo[0]


Issue #445: disable drag-and-drop for textarea

--- src/drag_and_drop_handler.coffee
+++ src/drag_and_drop_handler.coffee
@@ -89,7 +89,7 @@
         return true

     mustCaptureElement: ($element) ->
-        return not $element.is('input,select')
+        return not $element.is('input,select,textarea')

     canMoveToArea: (area) ->
         if not area


:bug: Fix markers not displayed after restart

--- lib/color-buffer-element.coffee
+++ lib/color-buffer-element.coffee
@@ -19,6 +19,7 @@
         @classList.remove('overlay')

   attachedCallback: ->
+    @updateMarkers()

   detachedCallback: ->

@@ -33,8 +34,6 @@
     @colorBuffer.initialize().then => @updateMarkers()
     @subscriptions.add @colorBuffer.onDidUpdateColorMarkers => @updateMarkers()
     @subscriptions.add @editor.onDidChangeScrollTop => @updateMarkers()
-
-    @updateMarkers()

     @attach()


sprinkle keywords everywhere to disambiguate

--- grammar.coffee
+++ grammar.coffee
@@ -88,10 +88,10 @@
       repeat(seq(@import_decl, terminator())),
       repeat(seq(@top_level_decl, terminator())))

-    package_clause: -> seq("package", @package_name)
+    package_clause: -> seq(keyword("package"), @package_name)
     package_name: -> @_identifier

-    import_decl: -> seq("import", choice(@import_spec, seq("(", repeat(seq(@import_spec, terminator())), ")")))
+    import_decl: -> seq(keyword("import"), choice(@import_spec, seq("(", repeat(seq(@import_spec, terminator())), ")")))
     import_spec: -> seq(optional(choice(".", @package_name)), @import_path)
     import_path: -> @_string_lit

@@ -146,7 +146,7 @@
     element_index: -> @expression
     value: -> choice(@expression, @literal_value)

-    function_lit: -> seq("func", @function)
+    function_lit: -> seq(keyword("func"), @function)
     function: -> seq(@signature, @function_body)
     function_body: -> @block

@@ -184,7 +184,7 @@
     array_type: -> seq("[", @array_length, "]", @element_type)
     array_length: -> @expression

-    struct_type: -> seq("struct", "{", repeat(seq(@field_decl, terminator())), "}")
+    struct_type: -> seq(keyword("struct"), "{", repeat(seq(@field_decl, terminator())), "}")
     field_decl: -> seq(
       choice(seq(@identifier_list, @type), @anonymous_field),
       optional(@tag))
@@ -194,24 +194,24 @@
     pointer_type: -> seq("*", @base_type)
     base_type: -> @type

-    function_type: -> seq("func", @signature)
+    function_type: -> seq(keyword("func"), @signature)
     signature: -> seq(@parameters, optional(@result))
     result: -> choice(@parameters, @type)
     parameters: -> seq("(", optional(seq(@parameter_list, optional(","))), ")")
     parameter_list: -> seq(@parameter_decl, repeat(seq(",", @parameter_decl)))
     parameter_decl: -> seq(optional(@identifier_list), optional("..."), @type)

-    interface_type: -> seq("interface", "{", repeat(seq(@method_spec, terminator())), "}")
+    interface_type: -> seq(keyword("interface"), "{", repeat(seq(@method_spec, terminator())), "}")
     method_spec: -> choice(seq(@method_name, @signature), @interface_type_name)
     method_name: -> @_identifier
     interface_type_name: -> @type_name

     slice_type: -> seq("[", "]", @element_type)

-    map_type: -> seq("map", "[", @key_type, "]", @element_type)
+    map_type: -> seq(keyword("map"), "[", @key_type, "]", @element_type)
     key_type: -> @type

-    channel_type: -> seq(choice("chan", seq("chan", "<-"), seq("chan", "->")), @element_type)
+    channel_type: -> seq(choice(keyword("chan"), seq(keyword("chan"), "<-"), seq(keyword("chan"), "->")), @element_type)

     #########################################################################
     # Statements
@@ -248,19 +248,19 @@
     top_level_decl: -> choice(@declaration, @function_decl, @method_decl)

     const_decl: -> seq(
-      "const",
+      keyword("const"),
       choice(@const_spec, seq("(", repeat(@const_spec, terminator()), ")")))
     const_spec: -> seq(
       @identifier_list,
       optional(seq(optional(@type), "=", @expression_list)))

     type_decl: -> seq(
-      "type",
+      keyword("type"),
       choice(@type_spec, seq("(", repeat(@type_spec, terminator()), ")")))
     type_spec: -> seq(@_identifier, @type)

     var_decl: -> seq(
-      "var",
+      keyword("var"),
       choice(@var_spec, seq("(", repeat(@var_spec, terminator()), ")")))
     var_spec: -> seq(
       @identifier_list,
@@ -269,12 +269,12 @@
         seq("=", @expression_list)))

     function_decl: -> seq(
-      "func",
+      keyword("func"),
       @function_name, choice(@function, @signature))
     function_name: -> @_identifier

     method_decl: -> seq(
-      "func",
+      keyword("func"),
       @receiver, @method_name, choice(@function, @signature))
     receiver: -> seq("(", optional(@_identifier), optional("*", @base_type_name), ")")
     base_type_name: -> @_identifier
@@ -295,36 +295,36 @@

     short_var_decl: -> seq(@identifier_list, ":=", @expression_list)

-    go_stmt: -> seq("go", @expression)
-
-    return_stmt: -> seq("return", optional(@expression_list))
-
-    break_stmt: -> seq("break", optional(@label))
-    continue_stmt: -> seq("continue", optional(@label))
-    goto_stmt: -> seq("goto", optional(@label))
-    fallthrough_stmt: -> "fallthrough"
+    go_stmt: -> seq(keyword("go"), @expression)
+
+    return_stmt: -> seq(keyword("return"), optional(@expression_list))
+
+    break_stmt: -> seq(keyword("break"), optional(@label))
+    continue_stmt: -> seq(keyword("continue"), optional(@label))
+    goto_stmt: -> seq(keyword("goto"), optional(@label))
+    fallthrough_stmt: -> keyword("fallthrough")

     if_stmt: -> seq(
-      "if",
+      keyword("if"),
       optional(seq(@simple_stmt, ";")),
       @expression,
       @block,
       optional(seq(
-        "else",
+        keyword("else"),
         choice(@if_stmt, @block))))

     switch_stmt: -> choice(@expr_switch_stmt, @type_switch_stmt)
     expr_switch_stmt: -> seq(
-      "switch",
+      keyword("switch"),
       optional(seq(@simple_stmt, ";")),
       optional(@expression),
       "{",
       repeat(@expr_case_clause),
       "}")
     expr_case_clause: -> seq(@expr_switch_case, ":", @statement_list)
-    expr_switch_case: -> choice(seq("case", @expression_list), "default")
+    expr_switch_case: -> choice(seq(keyword("case"), @expression_list), keyword("default"))
     type_switch_stmt: -> seq(
-      "switch",
+      keyword("switch"),
       optional(seq(@simple_stmt, ";")),
       @type_switch_guard,
       "{",
@@ -335,18 +335,18 @@
       @primary_expr,
       ".",
       "(",
-      "type",
+      keyword("type"),
       ")")
     type_case_clause: -> seq(@type_switch_case, ":", @statement_list)
-    type_switch_case: -> choice(seq("case", @type_list), "default")
+    type_switch_case: -> choice(seq(keyword("case"), @type_list), keyword("default"))

     select_stmt: -> seq(
-      "select",
+      keyword("select"),
       "{",
       @comm_clause,
       "}")
     comm_clause: -> seq(@comm_case, ":", @statement_list)
-    comm_case: -> choice(seq("case", choice(@send_stmt, @recv_stmt)), "default")
+    comm_case: -> choice(seq(keyword("case"), choice(@send_stmt, @recv_stmt)), keyword("default"))
     recv_stmt: -> seq(
       optional(choice(
         seq(@expression_list, "="),
@@ -355,7 +355,7 @@
     recv_expr: -> @expression

     for_stmt: -> seq(
-      "for",
+      keyword("for"),
       optional(choice(@condition, @for_clause, @range_clause)),
       @block)
     condition: -> @expression
@@ -366,7 +366,7 @@
       choice(
         seq(@expression_list, "="),
         seq(@identifier_list, ":=")),
-      "range",
+      keyword("range"),
       @expression)

-    defer_stmt: -> seq("defer", @expression)
+    defer_stmt: -> seq(keyword("defer"), @expression)


hub -- put in a delay before sending a reconnect message

--- salvus/hub.coffee
+++ salvus/hub.coffee
@@ -854,7 +854,7 @@

         if err?
             if mesg.id?
-                @error_to_client(id:mesg.id, error:err)
+                @error_to_client(id:mesg.id, error:err)
             cb(err)
             return

@@ -1871,7 +1871,6 @@
         # generate new edits, and send those out so that the client
         # can complete the sync cycle.

-
         if @_upstream_sync_lock? and @_upstream_sync_lock
             client.push_to_client(message.codemirror_diffsync_retry_later(id:mesg.id))
             return
@@ -1879,7 +1878,14 @@
         winston.debug("client_diffsync; the clients are #{misc.keys(@diffsync_clients)}")
         ds_client = @diffsync_clients[client.id]
         if not ds_client?
-            client.push_to_client(message.reconnect(id:mesg.id, reason:"Client with id #{client.id} is not registered with this hub."))
+            f = () ->
+                r = message.reconnect(id:mesg.id, reason:"Client with id #{client.id} is not registered with this hub for editing #{@path} in some project.")
+                client.push_to_client(r)
+            # We wait a bit before sending the reconnect message, since this is often the
+            # result of resetting the local_hub connection (which takes 10-15 seconds), and
+            # the client will instantly try to reconnect again, which will fail and lead to
+            # this again, which ends up slowing everything down.
+            setTimeout(f, 1000)
             return

         before = @diffsync_server.live


fix console.tick()

fix console.tick()

--- code/serv.coffee
+++ code/serv.coffee
@@ -145,7 +145,7 @@

 console.tick = (stuff...) ->
   # Prints out the time as well as stuff.
-  console.log.apply [(new Date()).toISOString()].concat(stuff)
+  console.log.apply console, [(new Date()).toISOString()].concat(stuff)

 # Exec endpoint - see wiki for note about security
 app.post "/:profile/:project/exec/?", check_api_key


PaneMode: snippet elements are now shown on snippet load, resolves #120

--- wzk/ui/grid/PaneMode.coffee
+++ wzk/ui/grid/PaneMode.coffee
@@ -20,6 +20,10 @@
   @DATA:
     MODE: 'mode'
     SNIPPET: 'snippet'
+    SNIPPET_SCOPE: 'snippetScope'
+
+  @CSS:
+    HIDDEN: 'hidden'

   ###*
     @param {Element} table
@@ -78,6 +82,24 @@
         @processSnippet name, url, el

   ###*
+    Scope parent, is parent with data attribute DATA.SNIPPET_SCOPE
+    Scope parent is found in processSnippet method.
+
+    If scope parent is found, it's set to be shown.
+
+    @protected
+    @param {Element} el
+    @param {string} snippetName
+  ###
+  findScopeParent: (el, snippetName) ->
+    el = @dom.getParentElement el
+    while el?
+      if goog.dom.dataset.get(el, wzk.ui.grid.PaneMode.DATA.SNIPPET_SCOPE) is snippetName
+        return el
+      el = @dom.getParentElement el
+    return undefined
+
+  ###*
     @protected
     @param {string} attr
     @return {Object}
@@ -97,4 +119,11 @@
     @client.request url + '?snippet=' + name, 'GET', {}, (json) =>
       if json['snippets']?
         el.innerHTML = json['snippets'][name]
+
+        # Find scope parent nad then show it
+        scopeParent = @findScopeParent(el, name)
+        if scopeParent?
+          goog.dom.classes.remove scopeParent, wzk.ui.grid.PaneMode.CSS.HIDDEN
+          goog.style.setElementShown scopeParent, true
+
       @reg.process el


Minor pull request feedback

Minor pull request feedback

--- client/dialogs.coffee
+++ client/dialogs.coffee
@@ -5,8 +5,7 @@
 $(document).on 'click focus keypress', (e) ->
   # originalEvent is defined only for native events, but we are triggering
   # click manually as well, so originalEvent is not always defined
-  Session.set 'newsletterActive', false unless e.originalEvent?.newsletterDialogBoxEvent
-  Session.set 'inviteDialogActive', false unless e.originalEvent?.inviteDialogBoxEvent
+  (Session.set variable, false for variable in ['newsletterActive', 'inviteDialogActive']) unless e.originalEvent?.dialogBoxEvent

   return # Make sure CoffeeScript does not return anything

@@ -22,7 +21,7 @@
     e.preventDefault()
     Session.set 'newsletterActive', true
     Session.set 'newsletterError', null
-    $(template.findAll '#newsletter-dialog-email').val(Meteor.user()?.emails?[0]?.address or '')
+    $(template.findAll '#newsletter-dialog-email').val(Meteor.person()?.email() or '')

     Meteor.setTimeout =>
       $(template.findAll '#newsletter-dialog-email').focus()
@@ -31,7 +30,7 @@
     return # Make sure CoffeeScript does not return anything

   'click .newsletter, focus .newsletter, keypress .newsletter': (e, template) ->
-    e.newsletterDialogBoxEvent = true
+    e.dialogBoxEvent = true
     return # Make sure CoffeeScript does not return anything

 Template.newsletter.displayed = ->
@@ -53,7 +52,7 @@
 # element of all and not directly on child elements. For example, when input is
 # disabled, its click handler is not called, but newsletter-dialog handler is.
   'click .newsletter-dialog, focus .newsletter-dialog, keypress .newsletter-dialog': (e, template) ->
-    e.newsletterDialogBoxEvent = true
+    e.dialogBoxEvent = true
     return # Make sure CoffeeScript does not return anything

   'submit .newsletter-subscribe': (e, template) ->
@@ -105,7 +104,7 @@
     return # Make sure CoffeeScript does not return anything

   'click .invite-button, focus .invite-button, keypress .invite-button': (e, template) ->
-    e.inviteDialogBoxEvent = true
+    e.dialogBoxEvent = true
     return # Make sure CoffeeScript does not return anything

 Template.inviteDialog.displayed = ->
@@ -127,7 +126,7 @@
 # element of all and not directly on child elements. For example, when input is
 # disabled, its click handler is not called, but invite-dialog handler is.
   'click .invite-dialog, focus .invite-dialog, keypress .invite-dialog': (e, template) ->
-    e.inviteDialogBoxEvent = true
+    e.dialogBoxEvent = true
     return # Make sure CoffeeScript does not return anything

   'submit .invite-send': (e, template) ->


coffeescript: grabbing keys from hash; sorting

--- coffeescript/features.coffee
+++ coffeescript/features.coffee
@@ -177,3 +177,16 @@
 {x, y} = rect
 console.log "x = #{x}"
 console.log "y = #{y}"
+
+####################
+### hashes, sorting
+####################
+console.log '\n## hashes ##'
+counts =
+  1: 2, 2: 6, 3: 8, 4: 2, 8: 1, 9: 6, 10: 4, 11: 6, 12: 4
+  5: 4, 6: 12, 7: 2, 13: 3, 14: 2, 15: 9, 20: 1, 21: 1, 22: 4
+  16: 2, 17: 1
+keys = (key for key of counts) # grab keys
+console.log(keys.join("|"))
+console.log(keys.sort().join("|"))             # sort lexicographically
+console.log(keys.sort((a,b) -> a-b).join("|")) # sort numerically


Fixed KeyID DNS (still need to test it again though)

--- src/lib/blockchains/keyid.coffee
+++ src/lib/blockchains/keyid.coffee
@@ -74,4 +74,4 @@
                     result.value = {}
                 cb null, result

-        dnsHandler: require('./namecoin').prototype.dnsHandler
+        dnsHandler: require('./namecoin')(dnschain).prototype.dnsHandler


Consistent use of <i> vs. <span> for Font Awesome

Consistent use of  vs.  for Font Awesome

"We like the  tag for brevity and for the fact that most folks are using  for emphasized/italicized semantic text these days. If that’s not your cup of tea, using a  is more semantically correct." -- https://fontawesome.com/how-to-use/on-the-web/referencing-icons/basic-use

--- client/message.coffee
+++ client/message.coffee
@@ -158,9 +158,9 @@
       title = "User '#{user.username}': " # like linkToAuthor
       if user.username of subscribed
         title += 'Subscribed to email notifications'
-        icon = ' '
+        icon = ' '
       else
-        icon = ' '
+        icon = ' '
         unless user.emails?[0]?
           title += "No email address"
         else if not user.emails[0].verified


Remove log.

Remove log.

--- component.coffee
+++ component.coffee
@@ -12,8 +12,6 @@
         container = $("##{@spec.containerId}")
         container.append("<div id='#{blockId}' class='block'/>")
         @block = container.children("##{blockId}")
-
-        console.log "block>", @block

     update: ->


Fixed issue #352 from LosFuzzys/meta

Fixed issue #352 from LosFuzzys/meta

--- main.coffee
+++ main.coffee
@@ -207,9 +207,9 @@
   acces_token = escape acces_token
   unless resource[0] == '/'
     resource = "/#{resource}"
-  url = "https://api.github.com#{resource}?access_token=#{acces_token}"
+  url = "https://api.github.com#{resource}"
   #console.log "performing github api request to #{url}"
-  headers = {'User-Agent': 'request', 'Accept': 'application/json'}
+  headers = {'User-Agent': 'request', 'Accept': 'application/json', 'Authorization': "token #{acces_token}"}
   request.get url, headers: headers, (err, eresp, body) ->
     #console.log "result: #{eresp.statusCode} - #{body}"
     if not err and eresp.statusCode == 200


update patterns and scopes

update patterns and scopes

--- grammars/nlf.cson
+++ grammars/nlf.cson
@@ -1,36 +1,36 @@
 fileTypes: [
   "nlf"
 ]
-name: "NSIS Language File"
+name: "nlf Language File"
 comment: "https://github.com/atom/atom-language-nlf"
 patterns: [
   {
     match: "^\\s*#.*$\\n?"
-    name: "comment.line.number-sign.nlf"
+    scope: "comment.line.number-sign.nlf"
   }
   {
     match: "\\b((0(x|X)[0-9a-fA-F]+)|([0-9]+(\\.[0-9]+)?))\\b"
-    name: "constant.numeric.nlf"
+    name: constant.numeric.nlf
   }
   {
-    match: "\\$_[\\w]+"
-    name: "entity.name.function.nlf"
+    match: "\\$_CLICK"
+    name: "variable.language.nlf"
   }
   {
-    match: "\\$[\\w]+"
-    name: "storage.type.function.nlf"
+    match: "\\$\\w+"
+    name: "variable.other.nlf"
   }
   {
-    match: "\\$\\(\\^[\\w]+\\)"
-    name: "string.value.nlf"
+    match: "\\%\\w+"
+    name: "constant.language.nlf"
   }
   {
-    match: "\\%[\\w]+"
-    name: "string.replacement.nlf"
+    match: "(\\w*?&\\w+)"
+    name: "constant.character.nlf"
   }
   {
-    match: "([\\w]?&[\\w]+)"
-    name: "constant.shortcut.nlf"
+    match: "\\$\\([\\w\\.:\\^-]+\\)"
+    name: "constant.other.nlf"
   }
   {
     match: "\\\\."


atom: update plugins

atom: update plugins

--- atom/.atom/config.cson
+++ atom/.atom/config.cson
@@ -1,5 +1,25 @@
 "*":
+  "atom-beautify":
+    general:
+      _analyticsUserId: "7c261ce3-6cd2-4c8d-8448-f6c5da663968"
+    js:
+      beautify_on_save: true
+      brace_style: "collapse-preserve-inline"
+      default_beautifier: "Prettier"
+      end_with_newline: true
+      object_curly_spacing: true
+      preserve_newlines: false
+      space_before_conditional: false
+      wrap_line_length: 80
+    json:
+      beautify_on_save: true
+      brace_style: "collapse-preserve-inline"
+      end_with_newline: true
+      object_curly_spacing: true
+      preserve_newlines: false
+      space_before_conditional: false
   "atom-ide-ui":
+    "atom-ide-code-format": {}
     "atom-ide-diagnostics-ui": {}
     use: {}
   "atom-material-ui":
@@ -27,6 +47,7 @@
       "linter-flow"
       "linter"
       "js-hyperclick"
+      "atom-beautify"
     ]
     packagesWithKeymapsDisabled: []
     telemetryConsent: "limited"
@@ -36,7 +57,7 @@
     ]
   editor:
     fontFamily: "Iosevka Light"
-    fontSize: 13
+    fontSize: 12
     showIndentGuide: true
     showInvisibles: true
   "exception-reporting":
@@ -48,6 +69,8 @@
   "hit-stats": {}
   hyperclick:
     linuxTriggerKeys: "shiftKey,ctrlKey"
+  "ide-flowtype":
+    tryFlowBin: true
   "language-babel":
     autoIndentJSX: true
   linter: {}


atom: julia-client config.

atom: julia-client config.

--- atom/.atom/config.cson
+++ atom/.atom/config.cson
@@ -14,7 +14,7 @@
     userId: "b108caee-a0a1-4a8c-9bb0-94ac632859e5"
   "julia-client":
     firstBoot: false
-    juliaPath: "$HOME/Downloads/julia/bin/julia"
+    juliaPath: "$HOME/applications/julia-0.6.4/bin/julia"
     uiOptions:
       enableMenu: true
       enableToolBar: true


remove minute from subscription option

remove minute from subscription option

Former-commit-id: 238af46bf39357a227c38dfbc3ce5aadb2068058 [formerly 3a41cb0a3e5a4b453279f2c9655d5cbf2bbc0ef0] [formerly 22513f5a677c23c2a7529486d144fb7348b810c0 [formerly dd19601013d4feb8bebeb64d26501138c009c09d]] [formerly c60cf5e1add1a66fb03649a7d7d60ae58c82164a [formerly f1ba215e7739696dc6a31cf199c5201e5dafaa7c] [formerly 3cfe89ddc99b62edbc4742d9ec7fee08949854b6 [formerly 3cfe89ddc99b62edbc4742d9ec7fee08949854b6 [formerly ea247aed7a76c1af0115df2eefaba5863a1566e9]]]]
Former-commit-id: 88b7f41d6c263f07e7d688439d3196506b4d2008 [formerly 4a6387629b12a72524c2ba50983e66b03926c974] [formerly 23fc4ffdcaeebb4e69dc4e8e138a1c68f07c92a3 [formerly f6b58721c08ab997f435672012be554251343ce1]]
Former-commit-id: 105da32119adeede20b590a85279f8113fd6d406 [formerly 7c4d2a631d144f118628968026d0ac58ca483d3a]
Former-commit-id: f9af45b623d91ce891e0b4e75d1caf02fda9a8b7
Former-commit-id: 7edf5f2d0e1cd074ed047557be858fe3e852fd6d

--- @client/notifications.coffee
+++ @client/notifications.coffee
@@ -260,7 +260,7 @@
           relation_config.subscription = "#{num}_#{e.target.value}"
           save current_user

-        for u in ['minute', 'hour', 'day', 'month']
+        for u in ['hour', 'day', 'month']
           OPTION
             value: u
             u


Leave runtime:packetsent as invalid

Leave runtime:packetsent as invalid

That seems to get fbp-spec to continue..

--- lib/commandstream.coffee
+++ lib/commandstream.coffee
@@ -500,11 +500,16 @@
     command: 'setiovaluecompleted'
     payload: null
   return m
-responses.SendPacketDone = () ->
+responses.SendPacketDone = (componentLib, graph) ->
   m =
     protocol: 'runtime'
     command: 'packetsent'
-    payload: null
+    payload:
+      #port: 'sssss' # FIXME
+      event: 'data'
+      type: 'any'
+      graph: graph.name
+      payload: {} # FIXME
   return m

 buildResponseMapping = () ->


Copy entire spec folder to application dir

--- tasks/build-task.coffee
+++ tasks/build-task.coffee
@@ -22,7 +22,6 @@
     nonPackageDirectories = [
       'benchmark'
       'dot-atom'
-      'spec'
       'vendor'
     ]

@@ -52,6 +51,7 @@
     for directory in packageDirectories
       cp directory, path.join(appDir, directory), filter: /.+\.(cson|coffee|less)$/

+    cp 'spec', path.join(appDir, 'spec')
     cp 'src', path.join(appDir, 'src'), filter: /.+\.(cson|coffee|less)$/
     cp 'static', path.join(appDir, 'static'), filter: /.+\.less$/
     cp 'themes', path.join(appDir, 'themes'), filter: /.+\.(cson|less)$/


Add [reset/append/prepend]Models Collection methods

--- src/marbles/collection.js.coffee
+++ src/marbles/collection.js.coffee
@@ -109,6 +109,12 @@
     @trigger('reset', models) unless options.silent
     models

+  resetModels: (models, options = {}) =>
+    @model_ids = []
+    models = @appendModels(models, silent: true)
+    @trigger('reset', models) unless options.silent
+    models
+
   appendJSON: (json, options = {}) =>
     return [] unless json?.length

@@ -116,6 +122,16 @@
       model = @buildModel(attrs)
       @model_ids.push(model.cid)
       model
+
+    @trigger('append', models) unless options.silent
+
+    models
+
+  appendModels: (models, options = {}) =>
+    return [] unless models?.length
+
+    for model in models
+      @model_ids.push(model.cid)

     @trigger('append', models) unless options.silent

@@ -129,6 +145,17 @@
       model = @buildModel(attrs)
       @model_ids.unshift(model.cid)
       model
+
+    @trigger('prepend', models) unless options.silent
+
+    models
+
+  prependModels: (models, options = {}) =>
+    return [] unless models?.length
+
+    for i in [models.length-1..0]
+      model = models[i]
+      @model_ids.unshift(model.cid)

     @trigger('prepend', models) unless options.silent


include an assertion for the start event in recording test

--- test/integration.coffee
+++ test/integration.coffee
@@ -19,18 +19,21 @@
     ok response.access_token
     start()

-asyncTest "Audio Recording and Uploading", 1, ->
+asyncTest "Audio Recording and Uploading", 2, ->
   trackTitle = "JS SDK Test Recording"
-  SC.record progress: ->
-    SC.recordStop()
-    SC.recordUpload
-      track:
-        title: trackTitle
-        sharing: "private"
-    , (track) ->
-      fixtureTrackId = track.id
-      equal track.title, trackTitle
-      start()
+  SC.record
+    start: ->
+      ok("start event fired")
+    progress: ->
+      SC.recordStop()
+      SC.recordUpload
+        track:
+          title: trackTitle
+          sharing: "private"
+      , (track) ->
+        fixtureTrackId = track.id
+        equal track.title, trackTitle
+        start()

 asyncTest "Receive latest tracks", 1, ->
   SC.get "/tracks",


fix for missing processes

fix for missing processes

--- server/job.coffee
+++ server/job.coffee
@@ -184,7 +184,7 @@
   job_job.each 'NOT count:*', {size:50}, (j) ->
     counter += 1
     console.log counter
-    job_job.update j._id, {count: j.processes.length}
+    job_job.update j._id, {count: if j.processes then j.processes.length else 0}
 else
   console.log  'All jobs have count'


Support unauthorized sign in

Support unauthorized sign in

--- app/scripts/account.coffee
+++ app/scripts/account.coffee
@@ -1,6 +1,11 @@
 'use strict'

 class Account
+
+  PARAMS:
+    AccountId: '890423234739'
+    RoleArn: 'arn:aws:iam::890423234739:role/Cognito_noteUnauth_DefaultRole'
+    IdentityPoolId: 'us-east-1:735704f2-6054-4700-a851-0bb1c573ff3e'

   constructor: ->
     document.querySelector('#account')?.addEventListener 'click', (event) =>
@@ -9,7 +14,22 @@
         @signin id[1]

   signin: (site) ->
-    console.log site
+    AWS.config.region = 'us-east-1'
+    AWS.config.credentials = new AWS.CognitoIdentityCredentials @PARAMS
+
+    AWS.config.credentials.get (err) ->
+	    if !err
+        console.log "Cognito Identity Id: " + AWS.config.credentials.identityId
+      else
+        console.log "Error: " + err
+
+    cognitoSyncClient = new AWS.CognitoSync()
+    cognitoSyncClient.listDatasets {
+      IdentityId: AWS.config.credentials.identityId
+      IdentityPoolId: "YOUR_COGNITO_IDENTITY_POOL_ID"
+    }, (err, data) ->
+      if !err
+        console.log JSON.stringify data

 window.Account = Account


Gulp build: fix scope

Gulp build: fix scope

--- gulp/build.coffee
+++ gulp/build.coffee
@@ -27,19 +27,19 @@
     )

 gulp.task 'build-stylus', ->
-    gulp.src ['src/stylus/*.styl']
+    gulp.src ['src/stylus/**/*.styl']
         .pipe stylus()
         .pipe gulp.dest('dist/css')
         .pipe browserSync.stream()

 gulp.task 'build-css', ->
-    gulp.src ['src/css/*.css']
+    gulp.src ['src/css/**/*.css']
         .pipe gulp.dest('dist/css')
         .pipe browserSync.stream()

 gulp.task 'build-assets', (cb) ->
     gulp
-        .src ['src/assets/*']
+        .src ['src/assets/**/*']
         .pipe gulp.dest('dist/assets')
         .pipe browserSync.stream()


Use DisplayMarkerLayer for Results

Use DisplayMarkerLayer for Results

--- lib/editor/result.coffee
+++ lib/editor/result.coffee
@@ -32,7 +32,8 @@

 metrics = throttle metrics, 60*60*1000

-resultEditorRegistry = {}
+resultEditorRegistry = new Set
+layers = {}

 module.exports =
 class Result
@@ -134,7 +135,7 @@
         resultEditorRegistry[ed.id] = true
         # create new editor specific result animation method
         listener = debounce((->
-          res = ed.findMarkers().filter((m) -> m.result?).map((m) -> m.result)
+          res = layers[ed.id].getMarkers().filter((m) -> m.result?).map((m) -> m.result)
           # reads
           rect = null
           fastdom.measure ->
@@ -148,7 +149,9 @@
         window.requestAnimationFrame listener

   initMarker: ->
-    @marker = @editor.markBufferRange @lineRange(@start, @end)
+    if not layers.hasOwnProperty @editor.id
+      layers[@editor.id] = @editor.addMarkerLayer()
+    @marker = layers[@editor.id].markBufferRange @lineRange(@start, @end)
     @marker.result = @
     @decorateMarker()
     @disposables.add @marker.onDidChange (e) => @checkMarker e
@@ -218,7 +221,7 @@
   @all: -> # TODO: scope selector
     results = []
     for item in atom.workspace.getPaneItems() when atom.workspace.isTextEditor item
-      item.findMarkers().filter((m) -> m.result?).forEach (m) ->
+      layers[item.id]?.getMarkers().forEach (m) ->
         results.push m.result
     results

@@ -228,18 +231,18 @@
       result.invalidate()

   @forLines: (ed, start, end, type = 'any') ->
-    ed.findMarkers().filter((m) -> m.result? &&
-                                   m.getBufferRange().intersectsRowRange(start, end) &&
-                                  (m.result.type == type || type == 'any'))
-                    .map((m) -> m.result)
+    layers[ed.id]?.findMarkers(intersectsBufferRowRange: [start, end])
+                  .filter((m) -> (m.result.type == type || type == 'any'))
+                  .map((m) -> m.result)

   @removeLines: (ed, start, end, type = 'any') ->
     rs = @forLines(ed, start, end, type)
+    return unless rs?
     rs.map (r) -> r.remove()
     rs.length > 0

   @removeAll: (ed = atom.workspace.getActiveTextEditor()) ->
-    ed?.findMarkers().filter((m) -> m.result?).map((m) -> m.result.remove())
+    layers[ed.id]?.getMarkers().map((m) -> m.result.remove())

   @removeCurrent: (e) ->
     if (ed = atom.workspace.getActiveTextEditor())
@@ -252,6 +255,7 @@
     ed = atom.workspace.getActiveTextEditor()
     for sel in ed.getSelections()
       rs = @forLines ed, sel.getHeadBufferPosition().row, sel.getTailBufferPosition().row
+      continue unless rs?
       rs.map (r) -> r.toggleView()

   # Commands


Refactor if/else statements

Refactor if/else statements

--- lib/rubocop-auto-correct.coffee
+++ lib/rubocop-auto-correct.coffee
@@ -30,8 +30,7 @@
     buffer = editor.getBuffer()
     bufferSavedSubscription = buffer.onDidSave =>
       buffer.transact =>
-        if atom.config.get('rubocop-auto-correct.autoRun')
-          @run(editor)
+        @run(editor) if atom.config.get('rubocop-auto-correct.autoRun')

     editorDestroyedSubscription = editor.onDidDestroy ->
       bufferSavedSubscription.dispose()
@@ -72,27 +71,23 @@
     atom.notifications.addSuccess(@toggleMessage("Debug Mode", !setting))

   run: (editor) ->
-    if editor
-      unless editor.getGrammar().scopeName.match("ruby")
-        return atom.notifications.addError("Only use source.ruby")
-      if atom.config.get('rubocop-auto-correct.correctFile')
-        if editor.isModified()
-          editor.save()
-        @autoCorrectFile(editor)
-      else
-        @autoCorrectBuffer(editor)
+    return if !editor
+    unless editor.getGrammar().scopeName.match("ruby")
+      return atom.notifications.addError("Only use source.ruby")
+    if atom.config.get('rubocop-auto-correct.correctFile')
+      editor.save() if editor.isModified()
+      @autoCorrectFile(editor)
+    else
+      @autoCorrectBuffer(editor)

   rubocopConfigPath: (filePath) ->
     configFile = '/.rubocop.yml'
     [projectPath, relativePath] = atom.project.relativizePath(filePath)
     projectConfigPath = projectPath + configFile
     homeConfigPath = fs.getHomeDirectory() + configFile
-    if (fs.existsSync(projectConfigPath))
-      ['--config', projectConfigPath]
-    else if (fs.existsSync(homeConfigPath))
-      ['--config', homeConfigPath]
-    else
-      []
+    return ['--config', projectConfigPath] if (fs.existsSync(projectConfigPath))
+    retrun ['--config', homeConfigPath] if (fs.existsSync(homeConfigPath))
+    []

   rubocopCommand: ->
     commandWithArgs = atom.config.get('rubocop-auto-correct.rubocopCommandPath')
@@ -114,15 +109,11 @@
       .concat(@rubocopConfigPath(buffer.getPath()))

     which command, (err) =>
-      if (err)
-        @rubocopNotFoundError()
-      else
-        rubocop = spawnSync(command, args, { encoding: 'utf-8', timeout: 5000 })
-        if (rubocop.stderr)
-          @rubocopOutput({"stderr": "#{rubocop.stderr}"})
-        else
-          buffer.setTextViaDiff(fs.readFileSync(tempFilePath, 'utf-8'))
-          @rubocopOutput(JSON.parse(rubocop.stdout))
+      return @rubocopNotFoundError() if (err)
+      rubocop = spawnSync(command, args, { encoding: 'utf-8', timeout: 5000 })
+      return @rubocopOutput({"stderr": "#{rubocop.stderr}"}) if (rubocop.stderr)
+      buffer.setTextViaDiff(fs.readFileSync(tempFilePath, 'utf-8'))
+      @rubocopOutput(JSON.parse(rubocop.stdout))

   autoCorrectFile: (editor)  ->
     filePath = editor.getPath()
@@ -141,10 +132,8 @@
       @rubocopOutput({"stderr": "#{output}"})

     which command, (err) =>
-      if (err)
-        @rubocopNotFoundError()
-      else
-        new BufferedProcess({command, args, stdout, stderr})
+      return @rubocopNotFoundError() if (err)
+      new BufferedProcess({command, args, stdout, stderr})

   rubocopNotFoundError: ->
     atom.notifications.addError(
@@ -166,29 +155,33 @@

     if (data.stderr)
       atom.notifications.addError(data.stderr) if notification
-    else if (data.summary.offense_count == 0)
+      return
+
+    if (data.summary.offense_count == 0)
       if !onlyFixesNotification
         atom.notifications.addSuccess("No offenses found") if notification
-    else
-      if !onlyFixesNotification
-        atom.notifications.addWarning(
-          "#{data.summary.offense_count} offenses found!"
-        ) if notification
-      for file in data.files
-        for offense in file.offenses
-          if offense.corrected
-            atom.notifications.addSuccess(
+      return
+
+    if !onlyFixesNotification
+      atom.notifications.addWarning(
+        "#{data.summary.offense_count} offenses found!"
+      ) if notification
+
+    for file in data.files
+      for offense in file.offenses
+        if offense.corrected
+          atom.notifications.addSuccess(
+            "Line: #{offense.location.line},
+            Col:#{offense.location.column} (FIXED)",
+            { detail: "#{offense.message}" }
+          ) if notification
+        else
+          if !onlyFixesNotification
+            atom.notifications.addWarning(
               "Line: #{offense.location.line},
-              Col:#{offense.location.column} (FIXED)",
+              Col:#{offense.location.column}",
               { detail: "#{offense.message}" }
             ) if notification
-          else
-            if !onlyFixesNotification
-              atom.notifications.addWarning(
-                "Line: #{offense.location.line},
-                Col:#{offense.location.column}",
-                { detail: "#{offense.message}" }
-              ) if notification

   makeTempFile: (filename) ->
     directory = temp.mkdirSync()


Change paramater name

Change paramater name

--- lib/flex-tool-bar.coffee
+++ lib/flex-tool-bar.coffee
@@ -97,13 +97,13 @@
     @toolBar = toolBar 'flex-toolBar'
     @reloadToolbar true

-  reloadToolbar: (init=false) ->
+  reloadToolbar: (withNotification=false) ->
     return unless @toolBar?
     try
       toolBarButtons = @loadConfig()
       @removeButtons()
       @addButtons toolBarButtons
-      atom.notifications.addSuccess 'The tool-bar was successfully updated.' unless init
+      atom.notifications.addSuccess 'The tool-bar was successfully updated.' if withNotification
     catch error
       atom.notifications.addError 'Your `toolbar.json` is **not valid JSON**!'
       console.error error


Fixed callback target

Fixed callback target

--- lib/tool-bar-button-view.coffee
+++ lib/tool-bar-button-view.coffee
@@ -22,7 +22,7 @@
     @on 'click', =>
       if not @hasClass 'disabled'
         if typeof options.callback is 'string'
-          atom.commands.dispatch document.activeElement, options.callback
+          atom.commands.dispatch atom.views.getView(atom.workspace.getActiveTextEditor()), options.callback
         else
           options.callback(options.data)


set in default log level

set in default log level

--- library/nucleus/cakefile.coffee
+++ library/nucleus/cakefile.coffee
@@ -136,6 +136,7 @@
         logging = options.logging or DEFAULT_LOGGING
         process.env["NODE_ENV"] = scoping.toString()
         process.env["log:level"] = logging.toString()
+        logger.level = logging.toString() # default
         granite = require "#{__dirname}/../../index"
         assert resolved = paths.resolve library or null
         missingLibrary = "missing library: #{resolved}"
@@ -157,6 +158,7 @@
         logging = options.logging or DEFAULT_LOGGING
         process.env["NODE_ENV"] = scoping.toString()
         process.env["log:level"] = logging.toString()
+        logger.level = logging.toString() # default
         granite = require "#{__dirname}/../../index"
         assert resolved = paths.resolve library or null
         missingLibrary = "missing library: #{resolved}"
@@ -178,6 +180,7 @@
         logging = options.logging or DEFAULT_LOGGING
         process.env["NODE_ENV"] = scoping.toString()
         process.env["log:level"] = logging.toString()
+        logger.level = logging.toString() # default
         granite = require "#{__dirname}/../../index"
         assert resolved = paths.resolve library or null
         missingLibrary = "missing library: #{resolved}"


Fixed default options for pie charts (fixes #153).

--- src/scripts/widgets/chart.coffee
+++ src/scripts/widgets/chart.coffee
@@ -15,13 +15,14 @@
       getErrorMessageHtml: () -> return 'Unable to fetch data.'
       chartOptions: {
         fullWidth: true,
-        chartPadding: {
-          right: 20
-        },
         lineSmooth: false
       }
       responsiveOptions: {}
     }, options
+
+    if options.type == 'Line' or options.type == 'Bar'
+      unless options.chartOptions.chartPadding
+        options.chartOptions.chartPadding = { right: 20 }

     if(options.type == 'Donut')
       options.type = 'Pie'


Use the computed style to find the height

--- src/text-editor-component.coffee
+++ src/text-editor-component.coffee
@@ -731,8 +731,7 @@
   measureDimensions: ->
     return unless @mounted

-    {position} = getComputedStyle(@hostElement)
-    {height} = @hostElement.style
+    {position, height} = getComputedStyle(@hostElement)

     if position is 'absolute' or height
       @presenter.setAutoHeight(false)


:fire: Unused workspace parameter in TextEditorComponent

--- src/text-editor-component.coffee
+++ src/text-editor-component.coffee
@@ -42,7 +42,7 @@
       @assert domNode?, "TextEditorComponent::domNode was set to null."
       @domNodeValue = domNode

-  constructor: ({@editor, @hostElement, @rootElement, @stylesElement, tileSize, @views, @themes, @workspace, @assert, scrollPastEnd}) ->
+  constructor: ({@editor, @hostElement, @rootElement, @stylesElement, tileSize, @views, @themes, @assert, scrollPastEnd}) ->
     @tileSize = tileSize if tileSize?
     @disposables = new CompositeDisposable


:green_heart:

--- src/text-editor-presenter.coffee
+++ src/text-editor-presenter.coffee
@@ -364,6 +364,9 @@
   getEndTileRow: ->
     @constrainRow(@tileForRow(@endRow))

+  isValidScreenRow: (screenRow) ->
+    screenRow >= 0 and screenRow < @model.getScreenLineCount()
+
   getScreenRows: ->
     startRow = @getStartTileRow()
     endRow = @constrainRow(@getEndTileRow() + @tileSize)
@@ -371,10 +374,10 @@
     screenRows = [startRow...endRow]
     if longestScreenRow = @model.getLongestScreenRow()
       screenRows.push(longestScreenRow)
-    if @screenRows?
-      screenRows.push(@screenRows...)
-
-    screenRows = screenRows.filter (row) => @constrainRow(row) is row
+    if @screenRowsToMeasure?
+      screenRows.push(@screenRowsToMeasure...)
+
+    screenRows = screenRows.filter @isValidScreenRow.bind(this)
     screenRows.sort (a, b) -> a - b
     _.uniq(screenRows, true)

@@ -397,7 +400,6 @@
     zIndex = 0

     for tileStartRow in [@tileForRow(endRow)..@tileForRow(startRow)] by -@tileSize
-      tileEndRow = @constrainRow(tileStartRow + @tileSize)
       rowsWithinTile = []

       while screenRowIndex >= 0


Corrected saleItems attribute name

Corrected saleItems attribute name

--- scripts/bungie.coffee
+++ scripts/bungie.coffee
@@ -65,7 +65,7 @@
           for category in saleItemCategories
             if category.categoryIndex in Constants.BOUNTIES_CATEGORY_INDICES
               console.log category
-              Array::push.apply bountyItems, category.salesItems
+              Array::push.apply bountyItems, category.saleItems
               --remainingIteration

               if remainingIteration is 0


removed my idea :(

removed my idea :(

--- scripts/fierce.coffee
+++ scripts/fierce.coffee
@@ -92,6 +92,3 @@

   robot.hear /(pause|paws)/i, (msg) ->
     msg.send "http://www.wppl.org/kidsteens/paws.jpg"
-
-  robot.hear /(security|secure)/i, (msg) ->
-    msg.send "<!--- TODO: add security --->"


Update google.coffee

Update google.coffee

--- scripts/google.coffee
+++ scripts/google.coffee
@@ -1,20 +1,29 @@
+Code  Issues 35  Pull requests 14  Pulse
+src/scripts/google.coffee
+
 # Description:
-#   A way to interact with the Google Web API.
+#   Returns the URL of the first google hit for a query
+#
+# Dependencies:
+#   None
+#
+# Configuration:
+#   None
 #
 # Commands:
-#   hubot google|g|구글 <query> - Queries Google Web for <query> and returns the first result.
+#   hubot google me <query> - Googles <query> & returns 1st result's URL
+#
+# Author:
+#   searls

 module.exports = (robot) ->
-  robot.respond /(g|google|구글) (.*)/i, (msg) ->
-    googleMe msg, msg.match[2], (url) ->
+  robot.respond /(google)( me)? (.*)/i, (msg) ->
+    googleMe msg, msg.match[3], (url) ->
       msg.send url

 googleMe = (msg, query, cb) ->
-  msg.http('http://ajax.googleapis.com/ajax/services/search/web')
-    .query(v: '1.0', q: query)
+  msg.http('http://www.google.com/search')
+    .query(q: query)
     .get() (err, res, body) ->
-      pages = JSON.parse(body)
-      pages = pages.responseData?.results
-      if pages?.length > 0
-        page = pages[0]
-        cb "*#{page.titleNoFormatting}*\n_ #{page.unescapedUrl} _"
+      cb body.match(/class="r"><a href="\/url\?q=([^"]*)(&sa.*)">/)?[1] || "Sorry, Google had zero results for '#{query}'"
+ Desktop version


Update lmgtfy.coffee

Update lmgtfy.coffee

--- scripts/lmgtfy.coffee
+++ scripts/lmgtfy.coffee
@@ -12,8 +12,8 @@
 #

 module.exports = (robot) ->
-   robot.respond /lmgtfy?\s?(?:@(\w*))? (.*)/i, (msg) ->
+   robot.respond /lmgtfy( me)? \s?(?:@(\w*))? (.*)/i, (msg) ->
      link = ""
      link += "#{msg.match[1]}: " if msg.match[1]
      link += "http://lmgtfy.com/?q=#{escape(msg.match[2])}"
-     msg.send link
+        msg.send msg.message.user.name + ":Take this:" link


Be more specific with brain.

Be more specific with brain.

--- scripts/points.coffee
+++ scripts/points.coffee
@@ -111,10 +111,10 @@
             pointcount = msg.match[1]

             # Currently only seems to get results from in-chat users at time of command
-            score = ["#{robot.brain.username} has #{robot.brain.points[username]}"]
+            score = ["#{robot.brain.people.username} has #{robot.brain.people.points[username]}"]

             topscore = score.sort((a, b) ->
-                b.robot.brain.points[username] - a.robot.brain.points[username]
+                b.robot.brain.people.points[username] - a.robot.brain.people.points[username]
             ).slice(0, pointcount)

             msg.send "The top #{pointcount} users with the most points are: #{topscore}"


fix test related typo

fix test related typo

--- test/target-test.coffee
+++ test/target-test.coffee
@@ -181,10 +181,11 @@
 			describe 'with 1 js file and no dependencies', ->
 				it 'should write 1 file to disk', (done) ->
 					@tgt.input = path.resolve('src/basic.coffee')
-					@tgt.options.concat = true
+					@tgt.options.concat = false
 					@tgt.isDir = false
 					file =
 						moduleID: 'basic'
+						qualifiedName: 'basic'
 						getContent: -> fs.readFileSync(path.resolve('src/basic.coffee'), 'utf8')
 						filepath: path.resolve('src/basic.coffee')
 						dependencies: []
@@ -192,7 +193,6 @@
 							compile: true
 							compiler: processors.js.compilers.coffee
 							module: processors.js.module
-						qualifiedName: 'basic'
 					@tgt._outputFile file, (err) =>
 						fs.existsSync(path.resolve('temp/basic.js')).should.be.true
 						done()
@@ -203,24 +203,24 @@
 					@tgt.isDir = false
 					file =
 						moduleID: 'package/classcamelcase'
+						qualifiedName: 'package/ClassCamelCase'
 						filepath: path.resolve('src/package/ClassCamelCase.coffee')
 						getContent: -> fs.readFileSync(path.resolve('src/package/ClassCamelCase.coffee'), 'utf8')
 						dependencies: [
 							{
 								moduleID: 'package/class'
+								qualifiedName: 'package/Class'
 								filepath: path.resolve('src/package/Class.coffee')
 								getContent: -> fs.readFileSync(path.resolve('src/package/Class.coffee'), 'utf8')
 								dependencies: []
 								options:
 									compiler: processors.js.compilers.coffee
 									module: processors.js.module
-								qualifiedName: 'package/Class'
 							}
 						]
 						options:
 							compiler: processors.js.compilers.coffee
 							module: processors.js.module
-						qualifiedName: 'package/ClassCamelCase'
 					@tgt._outputFile file, (err) =>
-						fs.existsSync(path.resolve('temp/package/classcamelcase.js')).should.be.true
-						done()
+						fs.existsSync(path.resolve('temp/package/ClassCamelCase.js')).should.be.true
+						done()


fixed scroll sync for presentation for pandoc parser


look at email before using

look at email before using

--- server/methods.coffee
+++ server/methods.coffee
@@ -21,7 +21,8 @@
     Stripe = StripeAPI(Billing.settings.secretKey)
     create = Async.wrap Stripe.customers, 'create'
     try
-      customer = create email: user.emails[0].address, card: card.id
+      email = if user.emails then user.emails[0].address else ''
+      customer = create email: email, card: card.id
       Meteor.users.update _id: user._id,
         $set: 'billing.customerId': customer.id, 'billing.cardId': customer.default_card
     catch e


Add command to trigger a table cut

Add command to trigger a table cut

--- lib/table-element.coffee
+++ lib/table-element.coffee
@@ -1159,6 +1159,7 @@
     e.preventDefault()
   'core:confirm': -> @startCellEdit()
   'core:copy': -> @tableEditor.copySelectedCells()
+  'core:cut': -> @tableEditor.cutSelectedCells()
   'core:paste': -> @tableEditor.pasteClipboard()
   'core:undo': -> @tableEditor.undo()
   'core:redo': -> @tableEditor.redo()


do initial sync on startup

do initial sync on startup

--- server/startup.coffee
+++ server/startup.coffee
@@ -51,5 +51,7 @@
       syncCollection(Incidents, process.env.ONE_WAY_SYNC_URL + "/api/incidents")
       syncCollection(Articles, process.env.ONE_WAY_SYNC_URL + "/api/articles")

+    # Do initial sync on startup
+    Meteor.setTimeout(pullRemoteInstanceData, 1000)
     # Pull data every 6 hours
     Meteor.setInterval(pullRemoteInstanceData, 6 * 60 * 60 * 1000)


hubot: allow one space between `engage` and `!`


Marking is now by dragging only. No clicking points individually.

--- scripts/controllers/CreaturePicker.coffee
+++ scripts/controllers/CreaturePicker.coffee
@@ -10,236 +10,236 @@
 style = require 'style'

 class CreaturePicker extends Spine.Controller
-	classification: null
-
-	className: 'creature-picker'
-	template: TEMPLATE
-
-	paper: null
-	indicator: null
-
-	strayCircles: null
-	strayAxes: null
-
-	markers: null
-
-	selectedSpecies: ''
-	selectedMarkerType: ''
-
-	disabled: false
-
-	elements:
-		'.map img': 'map'
-		'.selection-area': 'selectionArea'
-		'.selection-area img': 'image'
-
-	events:
-		'mousedown': 'onMouseDown'
-
-	constructor: ->
-		super
-
-		@html @template
-		@refreshElements()
-
-		@paper = Raphael @selectionArea[0], '100%', '100%'
-		@image.insertBefore @paper.canvas
-
-	ESC = 27
-	delegateEvents: =>
-		super
-
-		# $(window).on 'resize', @resize
-
-		$(document).on 'mousemove', @onMouseMove
-		$(document).on 'mouseup', @onMouseUp
-
-		$(document).on 'keydown', (e) =>
-			@resetStrays() if e.keyCode is ESC
-
-	getSize: =>
-		width: @image.width(), height: @image.height()
-
-	resize: =>
-		imageProportion = @image[0].naturalWidth / @image[0].naturalHeight
-		elProportion = @el.width() / @el.height()
-
-		if imageProportion < elProportion
-			@selectionArea.css width: '', height: '100%'
-			@image.css width: '', height: '100%'
-			@selectionArea.css width: @image.width()
-			@selectionArea.css left: (@el.width() - @selectionArea.width()) / 2, top: ''
-		else
-			@selectionArea.css width: '100%', height: ''
-			@image.css width: '100%', height: ''
-			@selectionArea.css height: @image.height()
-			@selectionArea.css left: '', top: (@el.height() - @selectionArea.height()) / 2
-
-		@paper.setSize @selectionArea.width(), @selectionArea.height()
-
-		marker.render() for marker in @markers or []
-
-	mouseIsDown: false
-	onMouseDown: (e) =>
-		return if @disabled
-		return unless @image.add(@paper.canvas).is e.target
-
-		@mouseIsDown = true
-
-		m.deselect() for m in @markers when m.selected
-
-		{left, top} = @selectionArea.offset()
-		@createStrayCircle e.pageX - left, e.pageY - top
-
-		@checkStrays()
-
-		e.preventDefault() # Disable text selection
-
-	createStrayCircle: (cx, cy) =>
-		circle = @paper.circle cx, cy
-		circle.attr style.circle
-		@strayCircles.push circle
-
-		@el.trigger 'create-stray-circle'
-
-		circle
-
-	createStrayAxis: =>
-		strayCircle1 = @strayCircles[@strayCircles.length - 2]
-		strayCircle2 = @strayCircles[@strayCircles.length - 1]
-
-		line = @paper.path Marker::lineBetween strayCircle1, strayCircle2
-		line.toBack()
-		line.attr style.boundingBox
-		@strayAxes.push line
-
-		@el.trigger 'create-stray-axis'
-
-		line
-
-	createStrayBoundingCircle: (cx, cy) =>
-		circle = @paper.circle cx, cy
-		circle.attr style.line
-
-		circle
-
-	dragThreshold: 10
-	mouseMoves: 0
-	movementCircle: null
-	movementAxis: null
-	movementBoundingCircle: null
-	onMouseMove: (e) =>
-		return unless @mouseIsDown and not @disabled
-		return if @strayCircles.length % 2 is 0 and not @movementCircle
-
-		@mouseMoves += 1
-		return if @mouseMoves < @dragThreshold
-
-		@movementCircle ||= @createStrayCircle()
-		@movementAxis ||= @createStrayAxis()
-
-		if @selectedMarkerType is 'circle'
-			@movementBoundingCircle ||= @createStrayBoundingCircle @strayCircles[0].attr('cx'), @strayCircles[0].attr('cy')
-
-		{left, top} = @selectionArea.offset()
-
-		@movementCircle.attr
-			cx: e.pageX - left
-			cy: e.pageY - top
-
-		secondLastCircle = @strayCircles[@strayCircles.length - 2]
-		@movementAxis.attr
-			path: Marker::lineBetween secondLastCircle, @movementCircle
-
-		@movementBoundingCircle?.attr
-			r: @movementAxis.getTotalLength()
-
-	onMouseUp: (e) =>
-		return unless @mouseIsDown and not @disabled
-		@mouseIsDown = false
-		@mouseMoves = 0
-
-		@checkStrays()
-
-		@movementCircle = null
-		@movementAxis = null
-
-		@movementBoundingCircle?.remove()
-		@movementBoundingCircle = null
-
-	createCircleMarker: (x, y) =>
-		marking = @createMarking()
-		marker = new CircleMarker
-			marking: marking
-			paper: @paper
-
-	createAxesMarker: =>
-		marking = @createMarking()
-		marker = new AxesMarker
-			marking: marking
-			paper: @paper
-
-	createMarking: =>
-		marking = @classification.markings().create
-			species: @selectedSpecies
-
-		marking.bind 'destroy', => @classification.trigger 'change'
-
-		{width: w, height: h} = @getSize()
-
-		for circle in @strayCircles
-			point = marking.points().create {}
-			point.updateAttributes
-				x: circle.attr('cx') / w
-				y: circle.attr('cy') / h
-
-		@classification.trigger 'change'
-
-		@el.trigger 'create-marking'
-
-		marking
-
-	checkStrays: =>
-		@indicator.setStep @strayCircles.length
-
-		if @strayCircles.length is 2
-			if @selectedMarkerType is 'circle'
-				marker = @createCircleMarker()
-			else if @strayAxes.length is 0
-				@createStrayAxis()
-		else if @strayCircles.length is 4
-			marker = @createAxesMarker()
-
-		if marker?
-			@markers.push marker
-			marker.deselect()
-
-			marker.bind 'select', (marker) =>
-				m.deselect() for m in @markers when m isnt marker
-				@trigger 'change-selection'
-
-			marker.bind 'deselect', =>
-				@trigger 'change-selection'
-
-			marker.bind 'release', =>
-				@markers.splice(i, 1) for m, i in @markers when m is marker
-
-			@resetStrays()
-
-	setDisabled: (@disabled) =>
-		if @disabled then marker.deselect() for marker in @markers or [] when marker.selected
-		if @disabled then @selectionArea.addClass 'disabled' else @selectionArea.removeClass 'disabled'
-
-	changeClassification: (@classification) =>
-		if @markers then @markers[0].release() until @markers.length is 0
-		@markers = []
-		@resetStrays()
-
-	resetStrays: =>
-		@strayCircles?.remove()
-		@strayCircles = @paper.set()
-
-		@strayAxes?.remove()
-		@strayAxes = @paper.set()
+  classification: null
+
+  className: 'creature-picker'
+  template: TEMPLATE
+
+  paper: null
+  indicator: null
+
+  strayCircles: null
+  strayAxes: null
+
+  markers: null
+
+  selectedSpecies: ''
+  selectedMarkerType: ''
+
+  disabled: false
+
+  elements:
+    '.map img': 'map'
+    '.selection-area': 'selectionArea'
+    '.selection-area img': 'image'
+
+  events:
+    'mousedown': 'onMouseDown'
+
+  constructor: ->
+    super
+
+    @html @template
+
+    @paper = Raphael @selectionArea[0], '100%', '100%'
+    @image.insertBefore @paper.canvas
+
+  ESC = 27
+  delegateEvents: =>
+    super
+
+    $(document).on 'mousemove', @onMouseMove
+    $(document).on 'mouseup', @onMouseUp
+
+    $(document).on 'keydown', (e) =>
+      @resetStrays() if e.keyCode is ESC
+
+  getSize: =>
+    width: @image.width(), height: @image.height()
+
+  resize: =>
+    imageProportion = @image[0].naturalWidth / @image[0].naturalHeight
+    elProportion = @el.width() / @el.height()
+
+    if imageProportion < elProportion
+      @selectionArea.css width: '', height: '100%'
+      @image.css width: '', height: '100%'
+      @selectionArea.css width: @image.width()
+      @selectionArea.css left: (@el.width() - @selectionArea.width()) / 2, top: ''
+    else
+      @selectionArea.css width: '100%', height: ''
+      @image.css width: '100%', height: ''
+      @selectionArea.css height: @image.height()
+      @selectionArea.css left: '', top: (@el.height() - @selectionArea.height()) / 2
+
+    @paper.setSize @selectionArea.width(), @selectionArea.height()
+
+    marker.render() for marker in @markers or []
+
+  createStrayCircle: (cx, cy) =>
+    circle = @paper.circle cx, cy
+    circle.attr style.circle
+    @strayCircles.push circle
+
+    @el.trigger 'create-stray-circle'
+
+    circle
+
+  createStrayAxis: =>
+    # It'll always be between the last two stray circles.
+    strayCircle1 = @strayCircles[@strayCircles.length - 2]
+    strayCircle2 = @strayCircles[@strayCircles.length - 1]
+
+    line = @paper.path Marker::lineBetween strayCircle1, strayCircle2
+    line.toBack()
+    line.attr style.boundingBox
+    @strayAxes.push line
+
+    @el.trigger 'create-stray-axis'
+
+    line
+
+  createStrayBoundingCircle: =>
+    # It'll always be centered on the first stray circle.
+    cx = @strayCircles[0].attr 'cx'
+    cy = @strayCircles[0].attr 'cy'
+
+    circle = @paper.circle cx, cy
+    circle.attr style.line
+    @strayAxes.push circle # Not an axis, but same idea.
+
+    circle
+
+  mouseIsDown: false
+  onMouseDown: (e) =>
+    return if @disabled
+    return unless @image.add(@paper.canvas).is e.target
+
+    m.deselect() for m in @markers when m.selected
+
+    @mouseIsDown = true
+
+    {left, top} = @selectionArea.offset()
+
+    @createStrayCircle e.pageX - left, e.pageY - top
+
+    e.preventDefault() # Disable text selection.
+
+  dragThreshold: 10
+  mouseMoves: 0
+  movementCircle: null
+  movementAxis: null
+  movementBoundingCircle: null
+  onMouseMove: (e) =>
+    return unless @mouseIsDown and not @disabled
+
+    @mouseMoves += 1
+    return if @mouseMoves < @dragThreshold
+
+    {left, top} = @selectionArea.offset()
+
+    @movementCircle ||= @createStrayCircle()
+    @movementCircle.attr
+      cx: e.pageX - left
+      cy: e.pageY - top
+
+    @movementAxis ||= @createStrayAxis()
+    secondLastCircle = @strayCircles[@strayCircles.length - 2]
+    @movementAxis.attr
+      path: Marker::lineBetween secondLastCircle, @movementCircle
+
+    if @selectedMarkerType is 'circle'
+      @movementBoundingCircle ||= @createStrayBoundingCircle()
+      @movementBoundingCircle.attr
+        r: @movementAxis.getTotalLength()
+
+  onMouseUp: (e) =>
+    return unless @mouseIsDown and not @disabled
+    @mouseIsDown = false
+    @mouseMoves = 0
+
+    @checkStrays()
+
+    @movementCircle = null
+    @movementAxis = null
+    @movementBoundingCircle = null
+
+  checkStrays: =>
+    if @strayCircles.length is 1
+      @resetStrays()
+    if @strayCircles.length is 2
+      if @selectedMarkerType is 'circle'
+        marker = @createCircleMarker()
+    else if @strayCircles.length is 3
+      @strayCircles.pop().remove()
+    else if @strayCircles.length is 4
+      marker = @createAxesMarker()
+
+    @indicator.setStep @strayCircles.length - 1
+
+    if marker?
+      @markers.push marker
+
+      setTimeout marker.deselect, 250
+
+      marker.bind 'select', (marker) =>
+        m.deselect() for m in @markers when m isnt marker
+        @trigger 'change-selection'
+
+      marker.bind 'deselect', =>
+        @trigger 'change-selection'
+
+      marker.bind 'release', =>
+        @markers.splice(i, 1) for m, i in @markers when m is marker
+
+      @resetStrays()
+
+  createCircleMarker: (x, y) =>
+    marking = @createMarking()
+    marker = new CircleMarker
+      marking: marking
+      paper: @paper
+
+  createAxesMarker: =>
+    marking = @createMarking()
+    marker = new AxesMarker
+      marking: marking
+      paper: @paper
+
+  createMarking: =>
+    marking = @classification.markings().create
+      species: @selectedSpecies
+
+    marking.bind 'destroy', => @classification.trigger 'change'
+
+    {width: w, height: h} = @getSize()
+
+    for circle in @strayCircles
+      point = marking.points().create {}
+      point.updateAttributes
+        x: circle.attr('cx') / w
+        y: circle.attr('cy') / h
+
+    @classification.trigger 'change'
+
+    @el.trigger 'create-marking'
+
+    marking
+
+  setDisabled: (@disabled) =>
+    if @disabled then marker.deselect() for marker in @markers or [] when marker.selected
+    if @disabled then @selectionArea.addClass 'disabled' else @selectionArea.removeClass 'disabled'
+
+  changeClassification: (@classification) =>
+    if @markers then @markers[0].release() until @markers.length is 0
+    @markers = []
+    @resetStrays()
+
+  resetStrays: =>
+    @strayCircles?.remove()
+    @strayCircles = @paper.set()
+
+    @strayAxes?.remove()
+    @strayAxes = @paper.set()

 exports = CreaturePicker


* add test for replacement as function

* add test for replacement as function

--- test/test_packer.coffee
+++ test/test_packer.coffee
@@ -300,5 +300,36 @@

       p_obj.buildPackage package_config, res_fn

-    # TODO need test with normal and function in replacement at onr time!
-
+    it 'should build pack with replacements as functon and as file (at one time)', (done) ->
+
+      package_config =
+        package_name : 'my_package'
+        bundle :
+          substractor : fixturesSingle
+          summator : fixturesNpm
+        environment :
+          printer : fixturesTwoChild
+        replacement :
+          './power' : fixturesReplacer
+          lodash    : -> @_
+
+      lodash_runtime_file = "#{__dirname}/../node_modules/lodash/lodash.js"
+      lodash_runtime = fs.readFileSync lodash_runtime_file, 'utf8'
+      vm.runInNewContext lodash_runtime, sandbox = {}
+
+      res_fn = (err, code) ->
+        expect(err).to.be.null
+        # oh, its better than eval :)
+        vm.runInNewContext code, sandbox
+
+        # now we are got this back
+        # yes, I know, it just stupid naming
+        {substractor} = sandbox.my_package.substractor
+        {summator,magic_summator} = sandbox.my_package.summator
+
+        (substractor 10, 2).should.to.be.equal 8
+        (summator 10, 5).should.to.be.equal 15
+        (magic_summator 10, 5).should.to.be.equal 25
+        done()
+
+      p_obj.buildPackage package_config, res_fn


Lower case for

Lower case for

--- lib/updates-panel.coffee
+++ lib/updates-panel.coffee
@@ -13,7 +13,7 @@
         @div class: 'section-container updates-container', =>
           @h1 class: 'section-heading icon icon-cloud-download', 'Available Updates', =>
             @button outlet: 'updateAllButton', class: 'pull-right update-all-button btn btn-primary', 'Update All'
-            @button outlet: 'checkButton', class: 'pull-right update-all-button btn btn', 'Check For Updates'
+            @button outlet: 'checkButton', class: 'pull-right update-all-button btn btn', 'Check for Updates'

           @div class: 'text native-key-bindings', tabindex: -1, =>
             @span class: 'icon icon-question'


mejoras graphql

mejoras graphql

--- snippets/graphql.cson
+++ snippets/graphql.cson
@@ -25,7 +25,7 @@
     'body': """
       const $1 = async () => {
         try {
-          await this.props.$2({$3})
+          await mutate({})
           showMessage('Success [cambiar este texto]')
         } catch (error) {
           showMessage(error)


fixed code style on servers.lib.server.github_callback.coffee

--- servers/lib/server/github_callback.coffee
+++ servers/lib/server/github_callback.coffee
@@ -1,66 +1,66 @@
 {
   redirectOauth
   saveOauthToSession
-}        = require './helpers'
+}          = require './helpers'

-{github} = KONFIG
-http     = require "https"
-provider = "github"
+{ github } = KONFIG
+http       = require 'https'
+provider   = 'github'

-saveOauthAndRedirect = (resp, res, clientId, req)->
-  {returnUrl} = resp
-  saveOauthToSession resp, clientId, provider, (err)->
-    options = {provider, returnUrl}
+saveOauthAndRedirect = (resp, res, clientId, req) ->
+  { returnUrl } = resp
+  saveOauthToSession resp, clientId, provider, (err) ->
+    options = { provider, returnUrl }
     redirectOauth err, req, res, options

 module.exports = (req, res) ->
-  {code, returnUrl} = req.query
-  {clientId}        = req.cookies
-  access_token      = null
-  scope             = null
+  { code, returnUrl } = req.query
+  { clientId }        = req.cookies
+  access_token        = null
+  scope               = null

   unless code
-    redirectOauth "No code", req, res, {provider}
+    redirectOauth 'No code', req, res, { provider }
     return

   headers =
-    "Accept"     : "application/json"
-    "User-Agent" : "Koding"
+    'Accept'     : 'application/json'
+    'User-Agent' : 'Koding'

   # Get access token with code
-  authorizeUser = (authUserResp)->
-    rawResp = ""
-    authUserResp.on "data", (chunk) -> rawResp += chunk
-    authUserResp.on "end", ->
+  authorizeUser = (authUserResp) ->
+    rawResp = ''
+    authUserResp.on 'data', (chunk) -> rawResp += chunk
+    authUserResp.on 'end', ->

       try
         authResponse = JSON.parse rawResp
       catch e
-        return redirectOauth "could not parse github response", req, res, {provider}
+        return redirectOauth 'could not parse github response', req, res, { provider }

-      {access_token, scope} = authResponse
+      { access_token, scope } = authResponse

       if access_token
         options =
-          host    : "api.github.com"
+          host    : 'api.github.com'
           path    : "/user?access_token=#{access_token}"
-          method  : "GET"
+          method  : 'GET'
           headers : headers
         r = http.request options, fetchUserInfo
         r.end()

   # Get user info with access token
   fetchUserInfo = (userInfoResp) ->
-    rawResp = ""
-    userInfoResp.on "data", (chunk) -> rawResp += chunk
-    userInfoResp.on "end", ->
+    rawResp = ''
+    userInfoResp.on 'data', (chunk) -> rawResp += chunk
+    userInfoResp.on 'end', ->

       try
         userInfo = JSON.parse rawResp
       catch e
-        return redirectOauth "could not parse github response", req, res, {provider}
+        return redirectOauth 'could not parse github response', req, res, { provider }

-      {login, id, email, name} = userInfo
+      { login, id, email, name } = userInfo

       if name
         [firstName, restOfNames...] = name.split ' '
@@ -78,44 +78,44 @@
         returnUrl : returnUrl
       }

-      headers["Accept"] = "application/vnd.github.v3.full+json"
+      headers['Accept'] = 'application/vnd.github.v3.full+json'

       # Some users don't have email in public profile, so we make 2nd call
       # to get them.
-      if not email? or email is ""
+      if not email? or email is ''
         options =
-          host    : "api.github.com"
+          host    : 'api.github.com'
           path    : "/user/emails?access_token=#{access_token}"
-          method  : "GET"
+          method  : 'GET'
           headers : headers
-        r = http.request options, (newResp)-> fetchUserEmail newResp, resp
+        r = http.request options, (newResp) -> fetchUserEmail newResp, resp
         r.end()
       else
         saveOauthAndRedirect resp, res, clientId, req

-  fetchUserEmail = (userEmailResp, originalResp)->
-    rawResp = ""
-    userEmailResp.on "data", (chunk) -> rawResp += chunk
-    userEmailResp.on "end", ->
+  fetchUserEmail = (userEmailResp, originalResp) ->
+    rawResp = ''
+    userEmailResp.on 'data', (chunk) -> rawResp += chunk
+    userEmailResp.on 'end', ->
       try
         emails = JSON.parse(rawResp)
       catch e
-        return redirectOauth "could not parse github response", req, res, {provider}
+        return redirectOauth 'could not parse github response', req, res, { provider }

       for email in emails when email.verified and email.primary
         originalResp.email = email.email

       saveOauthAndRedirect originalResp, res, clientId, req

-  path = "/login/oauth/access_token?"
+  path = '/login/oauth/access_token?'
   path += "client_id=#{github.clientId}&"
   path += "client_secret=#{github.clientSecret}&"
   path += "code=#{code}"

   options =
-    host   : "github.com"
+    host   : 'github.com'
     path   : path
-    method : "POST"
+    method : 'POST'
     headers : headers
   r = http.request options, authorizeUser
   r.end()


Added step to validate that the index file is defined in files

--- api/plunks.coffee
+++ api/plunks.coffee
@@ -28,7 +28,7 @@
     {valid, errors} = schema.validate(json, require("../lib/schema/create"))

     # Trigger an appropriate error if validation fails
-    unless valid then return next({number: 422, message: "Validation failed", errors: errors })
+    return next({number: 422, message: "Validation failed", errors: errors }) unless valid

     # Files can be provided as a hash of filename => contents or filename => file descriptor
     # This code normalizes them to the latter format
@@ -40,6 +40,8 @@

       json.files[filename] = _.clone(file)

+    return next({number: 422, message: "Validation failed", errors: [{field: "index", message: "No file defined for index"}]}) unless json.files[json.index]
+
     store.create json, (err, plunk) ->
       if err then next(err)
       else res.json(plunk)


Each tile becomes a small rotation to look more natural

--- app/views/tile_view.coffee
+++ app/views/tile_view.coffee
@@ -40,8 +40,13 @@

     left = (tileData.position % game.board.SIZE) * @WIDTH
     top = (Math.ceil((tileData.position + 1) / game.board.SIZE) - 1) * @HEIGHT
+    angle = 2 * Math.random() - 1

-    $(@el).css({ 'z-index': tileData.position }).anim({ translate3d: "#{left}px, #{top}px, 0"}, 0.125, "ease-out")
+    console.log angle
+
+    $(@el)
+      .css({ 'z-index': tileData.position })
+      .anim({ translate3d: "#{left}px, #{top}px, 0", rotate: "#{angle}deg"}, 0.125, "ease-out")

     @


Remove magic numbers

Remove magic numbers

--- app/lib/3w.coffee
+++ app/lib/3w.coffee
@@ -26,6 +26,8 @@
     @top = options.top or 10
     @height = options.height or 350
     @colors = colorbrewer[colorScheme][numColors]
+    @whenStepSize = 14
+    @whenTimeout = 1000

   calcProjection: (projection, width) =>
     # http://stackoverflow.com/a/14691788/408556
@@ -208,7 +210,7 @@
     if (value <= @max) and not @paused
       @$element.val(value).change()
       @updateCharts value
-      setTimeout (=> @play(value + 14)), 1000
+      setTimeout (=> @play(value + @whenStepSize)), @whenTimeout
     else if @paused
       @paused = false
     else if value > @max


datetime to the filename

datetime to the filename


Workspace: Changed Join Session input to KDHitEnterInputView and added callback for enter key.

--- client/landing/app/CommonViews/workspace/collaborativeworkspace.coffee
+++ client/landing/app/CommonViews/workspace/collaborativeworkspace.coffee
@@ -100,6 +100,14 @@
     options = @getOptions()
     delete options.sessionKey
     @addSubView new CollaborativeWorkspace options
+
+  joinSession: (sessionKey) ->
+    {parent}           = @
+    options            = @getOptions()
+    options.sessionKey = sessionKey
+    @destroy()
+    parent.addSubView new CollaborativeWorkspace options
+    log "user joined a new session:", sessionKey

   showDisconnectedModal: ->
     @disconnectedModal = new KDModalView
@@ -162,21 +170,20 @@
                 tagName         : "p"
                 partial         : "Paste the session ID that you want to join and start collaborating."
               SessionInput      :
-                itemClass       : KDInputView
+                itemClass       : KDHitEnterInputView
                 type            : "text"
+                callback        : =>
+                  sessionKey = modal.modalTabs.forms["Join A Session"].inputs.SessionInput.getValue()
+                  @joinSession sessionKey
+                  modal.destroy()
             buttons             :
               Join              :
                 title           : "Join Session"
                 cssClass        : "modal-clean-green"
                 callback        : =>
-                  sessionKey         = modal.modalTabs.forms["Join A Session"].inputs.SessionInput.getValue()
-                  {parent}           = @
-                  options            = @getOptions()
-                  options.sessionKey = sessionKey
-                  @destroy()
-                  parent.addSubView new CollaborativeWorkspace options
+                  sessionKey = modal.modalTabs.forms["Join A Session"].inputs.SessionInput.getValue()
+                  @joinSession sessionKey
                   modal.destroy()
-                  log "user joined a new session:", sessionKey
               Close             :
                 title           : "Close"
                 cssClass        : "modal-cancel"


Add conditional so this works with stable

--- spec/keybinding-resolver-view-spec.coffee
+++ spec/keybinding-resolver-view-spec.coffee
@@ -51,28 +51,30 @@

       atom.commands.dispatch workspaceElement, 'key-binding-resolver:toggle'

-      # Not partial because it dispatches the command for `x` immediately due to only having keyup events in remainder of partial match
-      document.dispatchEvent atom.keymaps.constructor.buildKeydownEvent('x', target: workspaceElement)
-      expect(workspaceElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe 'x'
-      expect(workspaceElement.querySelectorAll('.key-binding-resolver .used')).toHaveLength 1
-      expect(workspaceElement.querySelectorAll('.key-binding-resolver .unused')).toHaveLength 0
-      expect(workspaceElement.querySelectorAll('.key-binding-resolver .unmatched')).toHaveLength 1
+      # TODO: remove this conditional when keyup change to keymap is in beta
+      if atom.keymaps.constructor.buildKeyupEvent?
+        # Not partial because it dispatches the command for `x` immediately due to only having keyup events in remainder of partial match
+        document.dispatchEvent atom.keymaps.constructor.buildKeydownEvent('x', target: workspaceElement)
+        expect(workspaceElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe 'x'
+        expect(workspaceElement.querySelectorAll('.key-binding-resolver .used')).toHaveLength 1
+        expect(workspaceElement.querySelectorAll('.key-binding-resolver .unused')).toHaveLength 0
+        expect(workspaceElement.querySelectorAll('.key-binding-resolver .unmatched')).toHaveLength 1

-      # It should not render the keyup event data because there is no match
-      document.dispatchEvent atom.keymaps.constructor.buildKeyupEvent('x', target: workspaceElement)
-      expect(workspaceElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe 'x ^x'
-      expect(workspaceElement.querySelectorAll('.key-binding-resolver .used')).toHaveLength 1
-      expect(workspaceElement.querySelectorAll('.key-binding-resolver .unused')).toHaveLength 0
-      expect(workspaceElement.querySelectorAll('.key-binding-resolver .unmatched')).toHaveLength 0
+        # It should not render the keyup event data because there is no match
+        document.dispatchEvent atom.keymaps.constructor.buildKeyupEvent('x', target: workspaceElement)
+        expect(workspaceElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe 'x ^x'
+        expect(workspaceElement.querySelectorAll('.key-binding-resolver .used')).toHaveLength 1
+        expect(workspaceElement.querySelectorAll('.key-binding-resolver .unused')).toHaveLength 0
+        expect(workspaceElement.querySelectorAll('.key-binding-resolver .unmatched')).toHaveLength 0

-      document.dispatchEvent atom.keymaps.constructor.buildKeydownEvent('a', target: workspaceElement)
-      expect(workspaceElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe 'a (partial)'
-      expect(workspaceElement.querySelectorAll('.key-binding-resolver .used')).toHaveLength 0
-      expect(workspaceElement.querySelectorAll('.key-binding-resolver .unused')).toHaveLength 1
-      expect(workspaceElement.querySelectorAll('.key-binding-resolver .unmatched')).toHaveLength 0
+        document.dispatchEvent atom.keymaps.constructor.buildKeydownEvent('a', target: workspaceElement)
+        expect(workspaceElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe 'a (partial)'
+        expect(workspaceElement.querySelectorAll('.key-binding-resolver .used')).toHaveLength 0
+        expect(workspaceElement.querySelectorAll('.key-binding-resolver .unused')).toHaveLength 1
+        expect(workspaceElement.querySelectorAll('.key-binding-resolver .unmatched')).toHaveLength 0

-      document.dispatchEvent atom.keymaps.constructor.buildKeyupEvent('a', target: workspaceElement)
-      expect(workspaceElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe 'a ^a'
-      expect(workspaceElement.querySelectorAll('.key-binding-resolver .used')).toHaveLength 1
-      expect(workspaceElement.querySelectorAll('.key-binding-resolver .unused')).toHaveLength 0
-      expect(workspaceElement.querySelectorAll('.key-binding-resolver .unmatched')).toHaveLength 0
+        document.dispatchEvent atom.keymaps.constructor.buildKeyupEvent('a', target: workspaceElement)
+        expect(workspaceElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe 'a ^a'
+        expect(workspaceElement.querySelectorAll('.key-binding-resolver .used')).toHaveLength 1
+        expect(workspaceElement.querySelectorAll('.key-binding-resolver .unused')).toHaveLength 0
+        expect(workspaceElement.querySelectorAll('.key-binding-resolver .unmatched')).toHaveLength 0


Run mkdeb task right before publishing

Run mkdeb task right before publishing

--- build/Gruntfile.coffee
+++ build/Gruntfile.coffee
@@ -229,8 +229,8 @@

   ciTasks = ['output-disk-space', 'download-atom-shell', 'build']
   ciTasks.push('dump-symbols') if process.platform isnt 'win32'
+  ciTasks.push('set-version', 'check-licenses', 'lint')
   ciTasks.push('mkdeb') if process.platform is 'linux'
-  ciTasks.push('set-version', 'check-licenses', 'lint')
   ciTasks.push('test') if process.platform isnt 'linux'
   ciTasks.push('codesign', 'publish-build')
   grunt.registerTask('ci', ciTasks)


feature(cli): allow force initialize on directory not empty

--- lib/cli.coffee
+++ lib/cli.coffee
@@ -111,18 +111,20 @@
     console.log "edit article in #{settings.source}/#{category}/#{title}/index.md".prompt
     callback and callback()

-cmd_init = ->
+cmd_init = (to='.', options) ->
   init = ->
     global_settings = require '../assets/settings'
-    root = path.resolve __dirname, '..'
-    src = global_settings.source
-    dest = global_settings.destination
+    assets = path.resolve __dirname, '../assets'
+    to = path.resolve to
+    src = path.join to, global_settings.source
+    dest = path.join to, global_settings.destination

     console.log 'creates site skeleton structure'.info
+
     if not fs.existsSync(src)
       fs.mkdirSync(src)
       console.log 'copying default blog content'.info
-      fs.copy "#{root}/assets/examples", "#{src}/examples"
+      fs.copy "#{assets}/assets/examples", "#{src}/examples"
     else
       console.log "#{src} exist, leave without touch".warn

@@ -132,9 +134,9 @@
       console.log "#{dest} exist, leave without touch".warn

     assets = [
-      ["#{root}/assets/plugins", 'plugins']
-      ["#{root}/assets/themes", 'themes']
-      ["#{root}/assets/settings.json", 'settings.json']
+      ["#{assets}/plugins", "#{to}/plugins"]
+      ["#{assets}/themes", "#{to}/themes"]
+      ["#{assets}/settings.json", "#{to}/settings.json"]
     ]
     for asset in assets
       if not fs.existsSync asset[1]
@@ -143,7 +145,7 @@
       else
         console.log "#{asset[1]} exist, leave without touch".warn

-  if not fs.readdirSync('.').length
+  if not fs.readdirSync(to).length or options.force
     init()
   else
     # directory not empty
@@ -156,7 +158,7 @@
       if answers.ifProcess
         init()

-cmd_publish = ->
+cmd_publish = (args) ->
   create_post '', ->
     process.stdin.destroy()

@@ -217,31 +219,32 @@
   .version(require('../package.json').version)

 program
-  .command('init')
+  .command('init [to]')
   .description('initialize project, create new directory before initialize')
+  .option('-f --force', 'force initialize on directory not empty')
   .action(cmd_init)

 program
-  .command('publish')
+  .command('publish [to]')
   .description('publish new article')
   .action(cmd_publish)

 program
-  .command('preview')
+  .command('preview [to]')
   .description('preview generated html files')
   .option('-s --server [port]', 'start local server')
   .option('-a --auto', 'watch for file change and auto update')
   .action(cmd_preview)

 program
-  .command('build')
+  .command('build [to]')
   .description('build html files')
   .option('-s --server [port]', 'start local server')
   .option('-a --auto', 'watch for file change and auto update')
   .action(cmd_build)

 program
-  .command('migrate <path>')
+  .command('migrate <from> [to]')
   .description('migrate exist markdown file into project')
   .action(cmd_migrate)


Need to remove the event afterwards, and register_callback does it for us!

--- lib/esl.coffee
+++ lib/esl.coffee
@@ -361,7 +361,8 @@
   # The callback is only called when the command has completed.
   command_uuid: (uuid,app_name,app_arg,cb) ->
     if cb?
-      @socket.on "CHANNEL_EXECUTE_COMPLETE #{app_name} #{app_arg}", cb
+      event = "CHANNEL_EXECUTE_COMPLETE #{app_name} #{app_arg}"
+      @register_callback event, cb
     @execute_uuid uuid,app_name,app_arg

   # Hangup a call


Fixes #677

Fixes #677

--- lib/git.coffee
+++ lib/git.coffee
@@ -29,7 +29,7 @@
 getRepoForCurrentFile = ->
   new Promise (resolve, reject) ->
     project = atom.project
-    path = atom.workspace.getActiveTextEditor()?.getPath()
+    path = atom.workspace.getCenter().getActiveTextEditor()?.getPath()
     directory = project.getDirectories().filter((d) -> d.contains(path))[0]
     if directory?
       project.repositoryForDirectory(directory).then (repo) ->


Determine current slideshow number for speakerdeck

--- coffee/app.coffee
+++ coffee/app.coffee
@@ -234,7 +234,11 @@
       console? && console.log @container

     getCurrentSlideNumber: () ->
-      $('.goToSlideLabel > input', @container).val()
+      current_url = $('#player-content-wrapper > #slide_image', @container).prop('src')
+      r = /\/slide_([0-9]+)\./gi
+      if m = r.exec(current_url)
+        return m[1]
+      return false

     getCurrentSlideScreenshot: () ->
       $('#player-content-wrapper > #slide_image', @container).prop('src')


Fix for permission dialogs not showing in the Study Guide.

--- packages/pixelartacademy-studyguide/pages/home/menu/items/items.coffee
+++ packages/pixelartacademy-studyguide/pages/home/menu/items/items.coffee
@@ -162,7 +162,7 @@
       else
         # Settings have changed. Ask to save and return to menu when answered.
         LOI.settings.persistSettings.showDialog
-          dialogProvider: @menu.layout
+          dialogProvider: @menu.home.layout
           callback: =>
             @currentScreen @constructor.Screens.MainMenu

@@ -185,7 +185,7 @@

     else
       LOI.settings.persistLogin.showDialog
-        dialogProvider: @menu.layout
+        dialogProvider: @menu.home.layout
         callback: (value) =>
           Accounts._autoLoginEnabled = value
           Accounts._enableAutoLogin() if value
@@ -196,7 +196,7 @@

     else
       consentField.showDialog
-        dialogProvider: @menu.layout
+        dialogProvider: @menu.home.layout

   onClickBackToSettings: (event) ->
     @currentScreen @constructor.Screens.Settings


misc debugging on mouse rollovers after ajax submit

--- public/dreamcatcher/controllers/common/context_panel_controller.coffee
+++ public/dreamcatcher/controllers/common/context_panel_controller.coffee
@@ -26,15 +26,10 @@
   updateFollowRelationship: (el,json) ->
     prevClass = $("#relationship").attr('className')
     newClass = json.new_relationship
-    newText = json.new_relationship
-    newText = 'follow' if newText is 'none'
-    newText = 'following you' if newText is 'followed_by'
-    newText = @polarFollowText[@followButtonState['currentText']] if @loadingRelationship

     $("#relationship").removeClass(prevClass).addClass(newClass)
-    el.children(':first').text newText
     $("#relationship").data('status', json.new_relationship)
-    log "updated relationship to: #{json.new_relationship} newText: #{newText}"
+    log "updated relationship to: #{json.new_relationship}"

   # DOM Listeners

@@ -99,7 +94,7 @@
     log("currentRelationship: #{currentRelationship} currentText: #{currentText} hoverText #{hoverText} ")

   '#relationship mouseout': (el) ->
-    el.children(':first').text @followButtonState['currentText'] unless @loadingRelationship
+    el.children(':first').text @followButtonState['currentText']

   '#relationship click': (el) ->
     @loadingRelationship = true


remove the accidental double-wrap from Pygments from the first code block in the document.

--- docco.coffee
+++ docco.coffee
@@ -36,9 +36,10 @@
   pygments.addListener 'output', (result) ->
     output += result if result
   pygments.addListener 'exit', ->
+    output: output.replace(highlight_start, '').replace(highlight_end, '')
     fragments: output.split language.divider_html
     for section, i in sections
-      section.code_html: '

' + fragments[i] + '

'
+      section.code_html: highlight_start + fragments[i] + highlight_end
       section.docs_html: showdown.makeHtml section.docs_text
       callback()
   pygments.write((section.code_text for section in sections).join(language.divider_text))
@@ -130,6 +131,12 @@
 destination: (filepath) ->
   path.basename(filepath, path.extname(filepath)) + '.html'

+# The start of each Pygments highlight block.
+highlight_start: '

'
+
+# The end of each Pygments highlight block.
+highlight_end: '

'
+
 # Run the script.
 # For each source file passed in as an argument, generate the documentation.
 generate_documentation source for source in process.ARGV


Bug fix

Bug fix

--- test/generators/flat/find.coffee
+++ test/generators/flat/find.coffee
@@ -278,7 +278,7 @@

               for model in models
                 assert.ok(model.get('name') is NAME, 'matching name attribute')
-                done()
+              done()

     it 'Handles $lt and $lte with find not equal', (done) ->
@@ -345,7 +345,7 @@

               for model in models
                 assert.ok(model.get('name') is NAME, 'matching name attribute')
-                done()
+              done()

     it 'Handles $gt and $gte with find not equal', (done) ->
       NAME = 'Bob'


add 3rd option

add 3rd option

--- test/multiple-choice.spec.coffee
+++ test/multiple-choice.spec.coffee
@@ -15,42 +15,46 @@
       answers: [
         { id: 'option1', value: '[OPTION_1]' }
         { id: 'option2', value: '[OPTION_2]' }
+        { id: 'option3', value: ['option1', 'option2'] }
       ]

     @type = Components.getQuestionType('multiple-choice')

-    # Helper
-    @querySelector = (selector) ->
+    # Helpers
+    @querySelector = (selector) =>
       @component.getDOMNode().querySelector(selector)
+    @render = =>
+      node = @type {config: @question}
+      @component = React.renderComponent(node, @document.body)
+

   describe 'simple', ->
     beforeEach ->
-      node = @type {config: @question}
-      @component = React.renderComponent(node, @document.body)
+      @render()

-    it 'renders with 2 options', ->
-      html = @component.getDOMNode().innerHTML
-      expect(html).to.contain('[QUESTION_STEM]')
-      expect(html).to.contain('[OPTION_1]')
-      expect(html).to.contain('[OPTION_2]')
+    it 'renders with 3 options', ->
+      text = @component.getDOMNode().textContent
+      expect(text).to.contain('[QUESTION_STEM]')
+      expect(text).to.contain('[OPTION_1]')
+      expect(text).to.contain('[OPTION_2]')
+      expect(text).to.contain('(a)(b)')

     it 'has radio options', ->
       inputs = TestUtils.scryRenderedDOMComponentsWithTag(@component, 'input')
-      expect(inputs).to.have.length(2)
+      expect(inputs).to.have.length(3)

     it 'sets the answer when a radio is clicked', ->
       expect(AnswerStore.getAnswer(@question)).to.not.exist
       inputs = TestUtils.scryRenderedDOMComponentsWithTag(@component, 'input')
       TestUtils.Simulate.change(inputs[0])
       expect(AnswerStore.getAnswer(@question)).to.equal('option1')
-      TestUtils.Simulate.change(inputs[1])
-      expect(AnswerStore.getAnswer(@question)).to.equal('option2')
+      TestUtils.Simulate.change(inputs[2])
+      expect(AnswerStore.getAnswer(@question)).to.equal('option3')

   describe 'with an answer', ->
     beforeEach ->
       @question.answer = 'option1'
-      node = @type {config: @question}
-      @component = React.renderComponent(node, @document.body)
+      @render()

     it 'no longer has radio options', ->
       inputs = TestUtils.scryRenderedDOMComponentsWithTag(@component, 'input')
@@ -60,8 +64,7 @@
     beforeEach ->
       @question.answer = 'option1'
       @question.correct = 'option1'
-      node = @type {config: @question}
-      @component = React.renderComponent(node, @document.body)
+      @render()

     it 'has a "correct" class on the option', ->
       expect(@querySelector('.question.answered .option.correct')).to.exist
@@ -71,21 +74,18 @@
     beforeEach ->
       @question.answer = 'option1'
       @question.correct = 'option2'
-      node = @type {config: @question}
-      @component = React.renderComponent(node, @document.body)
+      @render()

-    it 'does NOT have a "correct" or "missed" class on the correct option', ->
-      expect(@querySelector('.question.answered .option.incorrect')).to.exist
-      expect(@querySelector('.question.answered .option.correct')).to.not.exist
-      expect(@querySelector('.question.answered .option.missed')).to.not.exist
+    it 'does NOT have a "correct" class on the correct option', ->
+      expect(@querySelector('.question.answered .option.incorrect'), 'incorrect').to.exist
+      expect(@querySelector('.question.answered .option.correct'), 'correct').to.not.exist

   describe '3 options with an incorrect answer', ->
     beforeEach ->
       @question.answers.push {id: 'option3', value: '[OPTION_3]'}
       @question.answer = 'option1'
       @question.correct = 'option2'
-      node = @type {config: @question}
-      @component = React.renderComponent(node, @document.body)
+      @render()

     it 'DOES have a "missed" class on the correct option', ->
       expect(@querySelector('.question.answered .option.incorrect')).to.exist


[MPS/LT] [106225716] Added a specific test for without-feature

--- test/spec/css_tagger_spec.coffee
+++ test/spec/css_tagger_spec.coffee
@@ -34,6 +34,19 @@

       tagger ele, pinballQueue, ''
       expect(ele.className).toEqual ' use-feature-a use-feature-c without-feature-b'
+
+    it 'should add without-feature classes for inactive and disabled pinball features', ->
+      ele = document.createElement 'div'
+      pinballQueue = [
+        [
+          'add'
+          feature_a: 'inactive'
+          feature_b: 'disabled'
+        ]
+      ]
+
+      tagger ele, pinballQueue, ''
+      expect(ele.className).toEqual ' without-feature-a without-feature-b'

     it 'should add classes from queue and query params', ->
       ele = document.createElement 'div'


Adding test to ensure controller is disposed when redirecting from action

--- test/spec/dispatcher_spec.coffee
+++ test/spec/dispatcher_spec.coffee
@@ -397,11 +397,20 @@

         done()

-    it 'should dispose when redirecting to a URL', (done) ->
-      dispose = sinon.spy Test1Controller.prototype, 'dispose'
-      publishMatch route1, params, options
-      publishMatch redirectToURLRoute, params, options
-      loadTest1Controller ->
+    it 'should dispose when redirecting to a URL from controller action', (done) ->
+      class RedirectingController extends Controller
+        show: ->
+          dispatcher.controllerLoaded route1, null, {changeUrl: true}, Test1Controller
+
+      dispose = sinon.spy RedirectingController.prototype, 'dispose'
+
+      controllerName = 'redirecting_controller'
+      loadRedirectingController = makeLoadController controllerName,
+        RedirectingController
+
+      route = {controller: controllerName, action: 'show', path}
+      publishMatch route, params, options
+      loadRedirectingController ->
         expect(dispose).was.calledOnce()
         dispose.restore()
         done()


Right acts as move if autocomplete is not active.

--- coffee/main.coffee
+++ coffee/main.coffee
@@ -30,12 +30,13 @@
             C.focusPrompt e

         isOverride = (e) ->
-            override   = ([9, 38, 40].indexOf(e.keyCode) isnt -1)
+            override   = ([9, 38, 39, 40].indexOf(e.keyCode) isnt -1)
             override or= ([13].indexOf(e.keyCode) isnt -1 and not e.shiftKey)
             override or= ([74, 75, 76, 82, 85].indexOf(e.keyCode) isnt -1 and e.ctrlKey)

         C.$prompt.addEventListener 'keydown', (e) ->
             return unless isOverride e
+            return if e.keyCode is 39 and not C.autoComplete.ing
             e.preventDefault()

             # enter
@@ -44,8 +45,8 @@
             # tab
             else if e.keyCode is 9 and e.shiftKey
                 C.autoComplete.nav -1
-            # tab
-            else if e.keyCode is 9 and not e.shiftKey
+            # shift+tab or right
+            else if (e.keyCode is 9 and not e.shiftKey) or (e.keyCode is 39)
                 C.autoComplete.nav +1
             # up
             else if e.keyCode is 38