improved the hoff

improved the hoff

--- lib/util.coffee
+++ lib/util.coffee
@@ -15,18 +15,21 @@
         )
     )

-findTheHoff = (startPath, filter, callback) ->
-    files=fs.readdirSync(startPath)
-    for i in [0...files.length]
-        filename=path.join(startPath,files[i])
+findTheHoff = (startPath, filter) ->
+    files = fs.readdirSync(startPath).filter (x) -> x[0] != '.' and x != 'node_modules'
+    for file in files
+        filename = path.join(startPath, file)
         stat = fs.lstatSync(filename)
         if stat.isDirectory()
-            try findTheHoff(filename,filter, callback)
+            try
+                f = findTheHoff(filename, filter)
+                return f if f?
             catch e
-            finally continue
+                console.error 'E', e
         else if path.parse(filename).base == filter
-            console.log filename
-            return callback(filename)
+            return filename
+
+    return null

 spawnHoffServer = (command, args) ->
     resolved = false
@@ -44,8 +47,12 @@
                 hoffpath = path.parse(path.join hoffpath, '../').dir
             else if not fs.existsSync(path.join hoffpath, 'pghoffserver', 'pghoffserver.py')
                  atom.notifications.addWarning('Pg-Hoffserver not found, searching...')
-                 findTheHoff(os.homedir(), 'pghoffserver.py')
-                 atom.notifications.addInfo('found the hoff')
+                 hoffpath = findTheHoff(os.homedir(), 'pghoffserver.py')
+                 atom.config.set('pg-hoff.hoffServerPath', hoffpath)
+                 if hoffpath?
+                     atom.notifications.addInfo('Found the hoff!')
+                 else
+                     atom.notifications.addError('Could not find the hoff :(')

             s = spawn('python', [ (path.join hoffpath, 'pghoffserver', 'pghoffserver.py') ], { env: process.env, detached: true })


Prevent looping over childNodes when reaching a binder that blocks. [#256]

--- src/view.coffee
+++ src/view.coffee
@@ -39,7 +39,6 @@
   # binding declaration.
   build: =>
     @bindings = []
-    skipNodes = []
     bindingRegExp = @bindingRegExp()
     componentRegExp = @componentRegExp()

@@ -58,46 +57,49 @@
       @bindings.push new Rivets[binding] @, node, type, keypath, options

     parse = (node) =>
-      unless node in skipNodes
-        if node.nodeType is 3
-          parser = Rivets.TextTemplateParser
+      block = false

-          if delimiters = @templateDelimiters
-            if (tokens = parser.parse(node.data, delimiters)).length
-              unless tokens.length is 1 and tokens[0].type is parser.types.text
-                for token in tokens
-                  text = document.createTextNode token.value
-                  node.parentNode.insertBefore text, node
+      if node.nodeType is 3
+        parser = Rivets.TextTemplateParser

-                  if token.type is 1
-                    buildBinding 'TextBinding', text, null, token.value
-                node.parentNode.removeChild node
-        else if componentRegExp.test node.tagName
-          type = node.tagName.replace(componentRegExp, '').toLowerCase()
-          @bindings.push new Rivets.ComponentBinding @, node, type
+        if delimiters = @templateDelimiters
+          if (tokens = parser.parse(node.data, delimiters)).length
+            unless tokens.length is 1 and tokens[0].type is parser.types.text
+              for token in tokens
+                text = document.createTextNode token.value
+                node.parentNode.insertBefore text, node

-        else if node.attributes?
-          for attribute in node.attributes
-            if bindingRegExp.test attribute.name
-              type = attribute.name.replace bindingRegExp, ''
-              unless binder = @binders[type]
-                for identifier, value of @binders
-                  if identifier isnt '*' and identifier.indexOf('*') isnt -1
-                    regexp = new RegExp "^#{identifier.replace('*', '.+')}$"
-                    if regexp.test type
-                      binder = value
+                if token.type is 1
+                  buildBinding 'TextBinding', text, null, token.value
+              node.parentNode.removeChild node
+      else if componentRegExp.test node.tagName
+        type = node.tagName.replace(componentRegExp, '').toLowerCase()
+        @bindings.push new Rivets.ComponentBinding @, node, type

-              binder or= @binders['*']
+      else if node.attributes?
+        for attribute in node.attributes
+          if bindingRegExp.test attribute.name
+            type = attribute.name.replace bindingRegExp, ''

-              if binder.block
-                skipNodes.push n for n in node.childNodes
-                attributes = [attribute]
+            unless binder = @binders[type]
+              for identifier, value of @binders
+                if identifier isnt '*' and identifier.indexOf('*') isnt -1
+                  regexp = new RegExp "^#{identifier.replace('*', '.+')}$"
+                  if regexp.test type
+                    binder = value

-          for attribute in attributes or node.attributes
-            if bindingRegExp.test attribute.name
-              type = attribute.name.replace bindingRegExp, ''
-              buildBinding 'Binding', node, type, attribute.value
+            binder or= @binders['*']

+            if binder.block
+              block = true
+              attributes = [attribute]
+
+        for attribute in attributes or node.attributes
+          if bindingRegExp.test attribute.name
+            type = attribute.name.replace bindingRegExp, ''
+            buildBinding 'Binding', node, type, attribute.value
+
+      unless block
         parse childNode for childNode in (n for n in node.childNodes)

     parse el for el in @els


base implementation view to model binding

--- src/view.coffee
+++ src/view.coffee
@@ -13,6 +13,8 @@
 #   myTableView.render("
top
").prepend()
 #
 class Sirius.View
+
+  name: () -> 'View' # define name, because not work in IE: constructor.name

   constructor: (@element, clb = (txt) -> txt) ->
     @_result_fn = (args...) =>
@@ -72,31 +74,102 @@
   #
   # ### 4 Combination of 3 and 4
   # view1.bind(view2, from: ["id"], to: ["class"]
+  #
   # ### 5 View Model relation
+  # when it's model, then need inspect element, and extract children from current element
+  #
+  # simple example, bind one element for one attribute:
+  #   <input id="title" type='text' />
+  #
+  #   view = Sirius.View("#title")
+  #   model = new MyModel()//model with attrs: [title, id, description]
+  #   # bind
+  #   view.bind(model, {to: 'title'})
+  #   # more ...
+  #   view.bind(model, {to: 'other-attribute'}) #error, because attribute not found
+  #   # or possible
+  #   <input id="title" type='text' data-bind-to='title' />
+  #   view.bind(model) #to extracted automatically
+  #   #or
+  #   <input id="title" type='text' name='title' />
+  #   # to = name in attributes
+  #   #or possible bind attribute
+  #   data-from='class'
+  #   or
+  #   view.bind(model, {to: 'title', from: 'class'})
+  #
+  #
+  #  more complex example
+  #
+  #

+  #     <input type="text" data-bind-to='title' data-to='' />
+  #     <textarea data-bind='description' data-to='description'></textarea>
+  #

+  #
+  #   view.bind(model)
   #
   # ### 6 View to any function relation
   #
   #
   # # TODO Also need strategy for change: swap, append, prepend or custom
+  # # TODO default value, when text: undefined
   #
   # @param [Any] - klass, another view\model\function
   # @param [Object] - hash with setting: [to, from]
   bind: (klass, object_setting = {}) ->
     `var c = function(m){console.log(m);};`
+    adapter = Sirius.Application.adapter
     current = @element
     to   = object_setting['to'] || null
     from = object_setting['from'] || null
-    if klass && klass.constructor && klass.constructor.name
-      if klass.constructor.name == "View"
+
+    if klass
+      if klass.name && klass.name() == "View"
         # {text: null, attribute: null}
         clb = (result) ->
           txt = result['text']
           if txt && !result['attribute']
             klass.render(txt).swap(to)
           else
+            c "not implemented #1"
             c result
+        new Sirius.Observer(current, clb)
+      else # then it Sirius.Model
+        children = adapter.all("#{current} *")
+        if children.length == 0
+          # then it single element and we need extract data-bind-to, data-bind-from and name
+          data_bind_to   = adapter.get_attr(current, 'data-bind-to') || adapter.get_attr(current, 'name')
+          data_bind_from = adapter.get_attr(current, 'data-bind-from')

-        new Sirius.Observer(current, clb)
+          if to && data_bind_to
+            c "You define `to` attribute twice: #{to}, #{data_bind_to}"
+
+          if from && data_bind_from
+            c "You define `from` attribute twice: #{from}, #{data_bind_from}"
+
+          if !data_bind_to && !to
+            c "Error# need pass `to` attribute into `.bind` method or define `data-bind-to` or `name` into html element code"
+
+          to   = to   ? data_bind_to
+          from = from ? data_bind_from
+
+          # check if attribute present into model class
+          if klass.attributes.indexOf(to) == -1
+            c "Error attribute #{to} not exist in model class #{klass}"
+
+
+
+          clb = (result) ->
+            txt = result['text']
+            klass[to](txt)
+            c klass
+
+          new Sirius.Observer(current, clb)
+
+
+    else
+      if Sirius.Utils.is_function(klass)
+        1 #when it's only function

     @


Fix weird editor behavior by setting autoHeight to false (default value)

Fixes #47

--- lib/util.coffee
+++ lib/util.coffee
@@ -92,7 +92,7 @@
     @compileToFile(sourceEditor) if shouldWriteToFile

   buildCoffeeCompileEditor: (sourceEditor) ->
-    previewEditor = atom.workspace.buildTextEditor(autoHeight: true)
+    previewEditor = atom.workspace.buildTextEditor(autoHeight: false)

     shouldCompileToFile = sourceEditor? and fsUtil.isPathInSrc(sourceEditor.getPath()) and
       pluginManager.isEditorLanguageSupported(sourceEditor, true)


disabled enter in autocompletion

disabled enter in autocompletion

--- client/autocompletion.coffee
+++ client/autocompletion.coffee
@@ -17,6 +17,7 @@
       completeOnSingleClick: true
       completeSingle: false
       extraKeys:
+        Enter: ->
         Right: (cm, m) -> m.pick(); return
         Tab: (cm, m) -> m.moveFocus 1; return
         'Shift-Tab': (cm, m) -> m.moveFocus -1; return


Single quotes no long fuss for more than one char

--- src/whip.coffee
+++ src/whip.coffee
@@ -134,8 +134,7 @@
   else if input[0] is '"' and input[-1..] is '"'
     new Var 'literal', eval input
   else if input[0] is "'" and input[-1..] is "'"
-    s = eval input
-    new Var 'literal', s if s.length is 1
+    new Var 'literal', input[1..-2]
   else if input instanceof Object
     new Var 'dict', input
   else


Make PaneContainer::activePaneItem behavior distinct until changed

If we switch from one empty active pane to another empty active pane,
there's no reason to emit a value from the activePaneItem behavior.

--- src/pane-container.coffee
+++ src/pane-container.coffee
@@ -15,7 +15,9 @@
   previousRoot: null

   @behavior 'activePaneItem', ->
-    @$activePane.switch (activePane) -> activePane?.$activeItem
+    @$activePane
+      .switch((activePane) -> activePane?.$activeItem)
+      .distinctUntilChanged()

   constructor: (params) ->
     super


IDE: Put README.md file content.

IDE: Put README.md file content.

--- client/ide/namespaces.coffee
+++ client/ide/namespaces.coffee
@@ -1,4 +1,5 @@
 window.IDE = {}
+

 window.IDE.splashMarkups =
   getTerminal: (nickname) ->
@@ -6,3 +7,25 @@

   getFileTree: (nickname, machineLabel) ->
     return """

#{machineLabel}
<button type="button" class="kdbutton terminal" id="kd-276"></button>

/home/#{nickname}

Applications

Backup

Documents

Web

.bash_logout

.bashrc

.profile

"""
+
+
+window.IDE.contents =
+  workspace: """
+    # Welcome to your new Koding workspace
+
+    This workspace, which lives inside the 'Workspaces' folder of your
+    home directory, is the place where you can store all relevant and
+    related files to this project.
+
+    Workspaces help keep your projects organized. You can create any
+    number of sub-folders within this workspace in order to further
+    organize your work.
+
+    As you move back and forth between your workspaces, Koding will try
+    and remember everything about each workspace. This includes things
+    like IDE settings, files open, Terminals open, etc.
+
+    You can create as many new workspaces as you need on your VM.
+
+    Enjoy and Happy Koding!
+  """


Change the order of endgame score display for team colours

--- resources/assets/coffee/react/mp-history/game.coffee
+++ resources/assets/coffee/react/mp-history/game.coffee
@@ -54,7 +54,7 @@
       if showTeams && @props.event.game.end_time
         div {},
           div className: 'mp-history-game__team-scores',
-            ['blue', 'red'].map (m) =>
+            ['red', 'blue'].map (m) =>
               div className: "mp-history-game__team-score mp-history-game__team-score--#{m}", key: m,
                 span className: 'mp-history-game__team-score-text mp-history-game__team-score-text--name', osu.trans "multiplayer.match.teams.#{m}"
                 span className: 'mp-history-game__team-score-text mp-history-game__team-score-text--score', @props.teamScores[m].toLocaleString()


fix(generators/app/templatestests): fix unit test descriptions grammar

--- generators/demo/templates/test/unit/spec/models/example.spec.coffee
+++ generators/demo/templates/test/unit/spec/models/example.spec.coffee
@@ -31,7 +31,7 @@

         it( 'should be overridden', () ->

-            newString = 'This should be overriden now'
+            newString = 'This should have been overridden now'

             emptyModel.set( 'attributeOne', newString )

@@ -43,12 +43,12 @@
         return
     )

-    ##  Test model when xxx it data, check if defaults are overriden
+    ##  Test model when passing-in attributes on instantiation, check if defaults are being overridden.
     ##
     populatedModel =
         new ExampleModel(

-            attributeOne:    'This should be overriden now'
+            attributeOne:    'This should have been overridden now'
             attributeTwo:    false
         )

@@ -56,7 +56,7 @@

         it( 'Should have been populated with the attributes as they were passed-in on its constructor', () ->

-            expect( populatedModel.get( 'attributeOne' )).toEqual( 'This should be overriden now' )
+            expect( populatedModel.get( 'attributeOne' )).toEqual( 'This should have been overridden now' )
             expect( populatedModel.get( 'attributeTwo' )).toEqual( false )

             return


Make squad list a little more mobile friendly.

--- coffeescripts/backend.coffee
+++ coffeescripts/backend.coffee
@@ -117,9 +117,9 @@
     list: (builder, all=false) ->
         # TODO: Pagination
         if all
-            @squad_list_modal.find('.modal-header h3').text("Everyone's #{builder.faction} Squads")
+            @squad_list_modal.find('.modal-header .squad-list-header-placeholder').text("Everyone's #{builder.faction} Squads")
         else
-            @squad_list_modal.find('.modal-header h3').text("Your #{builder.faction} Squads")
+            @squad_list_modal.find('.modal-header .squad-list-header-placeholder').text("Your #{builder.faction} Squads")
         list_ul = $ @squad_list_modal.find('ul.squad-list')
         list_ul.text ''
         list_ul.hide()
@@ -328,7 +328,8 @@
         @squad_list_modal.append $.trim """

                 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
-

+

+

@@ -343,7 +344,7 @@
                     <button class="btn btn-inverse show-all-squads">All</button>
                     <button class="btn show-standard-squads">Standard</button>
                     <button class="btn show-epic-squads">Epic</button>
-                    <button class="btn show-team-epic-squads">Team Epic</button>
+                    <button class="btn show-team-epic-squads">Team Epic</button>

                 <button class="btn" data-dismiss="modal" aria-hidden="true">Close</button>


fixed spelling error in feedback messages

--- src/scripts/batman.coffee
+++ src/scripts/batman.coffee
@@ -32,7 +32,7 @@
   "Well I feel bad for Gotham city...",
   "Good because I can't take another Ben Affleck movie.",
   "So you want a cookie or something?",
-  "Don't expect me to get the bat mobile or something... Get it your damn self.",
+  "Don't expect me to get the Batmobile or something... Get it your damn self.",
   "Holy haberdashery, Batman!",
   "Holy priceless collection of Etruscan snoods!",
   "Holy heart failure, Batman!"


add unread cls on demand

add unread cls on demand

--- src/scripts/msglog.coffee
+++ src/scripts/msglog.coffee
@@ -79,7 +79,7 @@
     return @ if false is @trigger 'beforeappend', msgs, @
     fragment = document.createDocumentFragment()
     msgs.forEach (msg) => unless msg.rendered # is msg.ts for modifies
-      fragment.appendChild @_renderitem msg
+      fragment.appendChild @_renderitem msg, on
       msg.rendered = msg.ts
       return
     @el.appendChild fragment
@@ -93,13 +93,13 @@
     @scroll() # auto scroll
     @
   # end of append
-  _renderitem: (msg) ->
+  _renderitem: (msg, unread) ->
     # todo: renderer
     li = document.createElement 'li'
     li.className = 'log'
     @addcls li, msg.class or 'message' # default is message
     @addcls li, msg.type if msg.type
-    @addcls li, 'unread' unless msg.local
+    @addcls li, 'unread' if unread or not msg.local
     nick = if msg.user?.nick then @xss.str msg.user.nick else ''
     ts = new Date(msg.ts or new Date).getShortTimeString no
     li.innerHTML = "<div class=\"info\">


fixed some weird indentation issues

fixed some weird indentation issues

--- src/server/session.coffee
+++ src/server/session.coffee
@@ -86,8 +86,8 @@
         query.doc = lastReceivedDoc

       docState[query.doc] or= queue: syncQueue (query, callback) ->
-                                                   # When the session is closed, we'll nuke docState. When that happens, no more messages
-                                                   # should be handled.
+        # When the session is closed, we'll nuke docState. When that happens, no more messages
+        # should be handled.
         return callback() unless docState

         # Close messages are {open:false}
@@ -309,10 +309,10 @@

     # Authentication process has failed, send error and stop session
     failAuthentication = (error) ->
-      session.send {
-        auth: null,
+      session.send
+        auth: null
         error: error
-      }
+
       session.stop()

     # Wait for client to send an auth message, but don't wait forever
@@ -323,7 +323,7 @@
     # We don't process any messages from the agent until they've authorized. Instead,
     # they are stored in this buffer.
     buffer = []
-    session.on 'message', bufferMsg = (msg) ->
+    session.on 'message', bufferMsg = (msg) ->
       if typeof msg.auth != 'undefined'
         clearTimeout timeout
         data.authentication = msg.auth


Add "swap" command option for marks.

Add "swap" command option for marks.

In a browser, I find global marks considerably more useful than local
marks; for example, I use marks to quickly jump back to my email tab.

Unfortunately, inheriting vim's approach, global marks require capital
letters, so the `<Shift>` key.

This PR implements:

    map m Marks.activateCreateMode swap
    map ' Marks.activateGotoMode swap

which inverts the `isGlobalMark()` test for the `<Shift>` key.

Is the name `swap` the best one?  It could be `invert` instead, or
perhaps there's something better.

--- content_scripts/marks.coffee
+++ content_scripts/marks.coffee
@@ -2,6 +2,7 @@
 Marks =
   previousPositionRegisters: [ "`", "'" ]
   localRegisters: {}
+  currentRegistryEntry: null
   mode: null

   exit: (continuation = null) ->
@@ -26,10 +27,14 @@
   # If <Shift> is depressed, then it's a global mark, otherwise it's a local mark.  This is consistent
   # vim's [A-Z] for global marks and [a-z] for local marks.  However, it also admits other non-Latin
   # characters.  The exceptions are "`" and "'", which are always considered local marks.
+  # The "swap" command option inverts global and local marks.
   isGlobalMark: (event, keyChar) ->
-    event.shiftKey and keyChar not in @previousPositionRegisters
+    shiftKey = event.shiftKey
+    shiftKey = not shiftKey if @currentRegistryEntry.options.swap
+    shiftKey and keyChar not in @previousPositionRegisters

-  activateCreateMode: ->
+  activateCreateMode: (count, {registryEntry}) ->
+    @currentRegistryEntry = registryEntry
     @mode = new Mode
       name: "create-mark"
       indicator: "Create mark..."
@@ -52,7 +57,8 @@
             localStorage[@getLocationKey keyChar] = @getMarkString()
             @showMessage "Created local mark", keyChar

-  activateGotoMode: ->
+  activateGotoMode: (count, {registryEntry}) ->
+    @currentRegistryEntry = registryEntry
     @mode = new Mode
       name: "goto-mark"
       indicator: "Go to mark..."


Log args and their indexes, if they look like object ids

--- libraries/metrics/timeAsyncMethod.coffee
+++ libraries/metrics/timeAsyncMethod.coffee
@@ -21,5 +21,10 @@
 		realMethod.call this, firstArgs..., (callbackArgs...) ->
 			elapsedTime = timer.done()
 			if logger?
-				logger.log {key, elapsedTime}, "[Metrics] timed async method call"
+				loggableArgs = {}
+				try
+					for arg, idx in firstArgs
+						if arg.toString().match(/^[0-9a-f]{24}$/)
+							loggableArgs["#{idx}"] = arg
+				logger.log {key, args: loggableArgs, elapsedTime}, "[Metrics] timed async method call"
 			callback.apply this, callbackArgs


fixbug: 无法查看工单 in #64

fixbug: 无法查看工单 in #64

--- core/router/ticket.coffee
+++ core/router/ticket.coffee
@@ -4,7 +4,7 @@

 db = require '../db'
 config = require '../config'
-{requestAuthenticate, renderAccount} = require './middleware'
+{requestAuthenticate, renderAccount, getParam} = require './middleware'

 mAccount = require '../model/account'
 mTicket = require '../model/ticket'
@@ -29,7 +29,7 @@
   res.render 'ticket/create',
     ticketTypes: config.ticket.availableType

-exports.get '/view', requestAuthenticate, renderAccount, (req, res) ->
+exports.get '/view', requestAuthenticate, renderAccount, getParam, (req, res) ->
   mTicket.findId req.body.id, (ticket) ->
     unless ticket
       return res.send 404


J.fetching.isQsCovered

J.fetching.isQsCovered

--- fetching.coffee
+++ fetching.coffee
@@ -207,15 +207,15 @@

     _projectionToInclusionSet: (modelClass, projection) ->
         # See the projection semantics documentation at the top of this file.
-
-        # Returns {includedFieldOrReactiveName: true}
-
+        # Returns {includeSpec: true}
+
+        if projection is undefined then projection = {}
         inclusionSet = {}

         if projection._ is false
-            for fieldOrReactiveName, include of projection
-                continue if fieldOrReactiveName is '_'
-                if include then inclusionSet[fieldOrReactiveName] = true
+            for includeSpec, include of projection
+                continue if includeSpec is '_'
+                if include then inclusionSet[includeSpec] = true

         else
             for fieldName, fieldSpec of modelClass.fieldSpecs
@@ -226,12 +226,12 @@
                 if reactiveSpec.include ? false
                     inclusionSet[reactiveName] = true

-            for fieldOrReactiveName, include of projection
-                continue if fieldOrReactiveName is '_'
+            for includeSpec, include of projection
+                continue if includeSpec is '_'
                 if include
-                    inclusionSet[fieldOrReactiveName] = true
+                    inclusionSet[includeSpec] = true
                 else
-                    delete inclusionSet[fieldOrReactiveName]
+                    delete inclusionSet[includeSpec]

         inclusionSet

@@ -461,9 +461,30 @@
         # Returns whether the results from the querySpecs in superQsArr
         # are sufficient to provide accurate results to qs

-        return false if superQs.modelName isnt qs.modelName
-
-        false
+        qs = @makeCanonicalQs qs
+        superQs = @makeCanonicalQs superQs
+
+        return false if qs.modelName isnt superQs.modelName
+        modelClass = J.models[qs.modelName]
+
+        # Make sure the selector is covered
+        return false if not @isSelectorCovered qs, superQs
+
+        # And make sure the projection is covered
+        inclusionSet = @_projectionToInclusionSet modelClass, qs.fields
+        superInclusionSet = @_projectionToInclusionSet modelClass, superQs.fields
+        inclusionDiff = J.util.diffStrings _.keys(inclusionSet), _.keys(superInclusionSet)
+        for missingInclusionSpec in inclusionDiff.deleted
+            # Check for a situation like "w.x.y.z" being covered by "w.x"
+            ok = false
+            missingInclusionSpecParts = missingInclusionSpec.split('.')
+            for i in [0...missingInclusionSpecParts.length - 1]
+                if missingInclusionSpecParts[0..i].join('.') of superInclusionSet
+                    ok = true
+                    break
+            return false if not ok
+
+        true

     isSelectorCovered: (qs, superQs) ->


Avoid user serialization error

Avoid user serialization error

--- components/split_test/split_test.coffee
+++ components/split_test/split_test.coffee
@@ -1,7 +1,7 @@
 _ = require 'underscore'
 Backbone = require 'backbone'
 Cookies = require 'cookies-js'
-CurrentUser = require '../../models/current_user.coffee'
+sd = require('sharify').data
 { getProperty, setProperty, unsetProperty } = require '../../lib/analytics.coffee'

 module.exports = class SplitTest
@@ -29,7 +29,7 @@
     "is-splittest-#{@key}--#{@outcome()}"

   admin: ->
-    CurrentUser.orNull()?.isAdmin()
+    sd.CURRENT_USER?.type is 'Admin'

   toss: ->
     _.sample _.flatten _.map @outcomes, (probability, outcome) ->


Renamed more explicit to avoid overlaps

Renamed more explicit to avoid overlaps

--- main/static/src/script/site/user.coffee
+++ main/static/src/script/site/user.coffee
@@ -18,13 +18,13 @@

 user_select_row = ($element) ->
-  update_selections()
+  update_user_selections()
   ($ 'input[name=user_db]').each ->
     id = $element.val()
     ($ "##{id}").toggleClass 'warning', $element.is ':checked'

-update_selections = ->
+update_user_selections = ->
   selected = ($ 'input[name=user_db]:checked').length
   ($ '#user-actions').toggleClass 'hidden', selected == 0
   ($ '#user-merge').toggleClass 'hidden', selected < 2
@@ -61,7 +61,7 @@
           return
         ($ "##{result.join(', #')}").fadeOut ->
           ($ this).remove()
-          update_selections()
+          update_user_selections()
           show_notification success_message.replace('{users}', user_keys.length), 'success'


rfctr relative

rfctr relative

--- gulp-nav.coffee
+++ gulp-nav.coffee
@@ -22,13 +22,12 @@
 url = require 'url'
 through = require 'through2'

-relative = (start, finish) ->
-  start = start.split '/'
-  finish = finish.split '/'
-  finish.shift() while start.length and start.shift() is last = finish[0]
-  finish = last unless finish.length
-  ('..' for _ in start)                               # ascend out of remaining
-    .concat finish                                    # descend into remaining
+relative = (source, target) ->
+  source = source.split '/'
+  target = target.split '/'
+  target.shift() while source.length and source.shift() is last = target[0]
+  ('..' for _ in source)                              # ascend out of remaining
+    .concat if target.length then target else last    # descend into remaining
     .join '/'
     .replace /(^|\.\/)$/, '.'


Log output more judiciously in `gulp dist`

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -78,7 +78,7 @@
     process.chdir('../../')
     cb x...
   process.chdir('./dist/src/')
-  npm.load {only: 'prod'}, (err) ->
+  npm.load {production: yes, progress: false}, (err) ->
     return done(err) if err?
     npm.commands.install [], (err, data) ->
       return done(err) if err?
@@ -95,6 +95,7 @@
       version: packageInfo.devDependencies["electron-prebuilt"]
       platform: platform
       arch: arch
+      quiet: yes
     .pipe $.symdest "./dist/#{id}/Relief Valve v#{packageInfo.version}/"
   gulp.task "build:#{id}", ["clean:build", "dist:#{id}"], ->
     if platform is 'darwin'


remove console log

remove console log

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -5,8 +5,6 @@
 gulp = require('gulp')
 gutil = require 'gulp-util'
 args = require('yargs').argv
-
-console.log(args);

 options =
   minifiedBuild: !!args.minified


Ignore _test.js files for compilation.

Ignore _test.js files for compilation.


Use decorateMarker

Use decorateMarker

--- lib/react-bookmarks.coffee
+++ lib/react-bookmarks.coffee
@@ -28,7 +28,7 @@

   addDecorationsForBookmarks: =>
     for bookmark in @findBookmarkMarkers() when bookmark.isValid()
-      @editor.addDecorationForMarker(bookmark, {type: 'gutter', class: 'bookmarked'})
+      @editor.decorateMarker(bookmark, {type: 'gutter', class: 'bookmarked'})

     null

@@ -82,7 +82,7 @@
     bookmark = @displayBuffer().markBufferRange(range, @bookmarkMarkerAttributes(invalidate: 'surround'))
     @subscribe bookmark, 'changed', ({isValid}) ->
       bookmark.destroy() unless isValid
-    @editor.addDecorationForMarker(bookmark, {type: 'gutter', class: 'bookmarked'})
+    @editor.decorateMarker(bookmark, {type: 'gutter', class: 'bookmarked'})
     bookmark

   findBookmarkMarkers: (attributes={}) ->


Update base menus to match proposal

Update base menus to match proposal

--- menus/base.cson
+++ menus/base.cson
@@ -7,11 +7,10 @@
       { label: "Install update", command: 'application:install-update', visible: false }
       { type: 'separator' }
       { label: 'Preferences...', command: 'application:show-settings' }
+      { type: 'separator' }
       { label: 'Hide Atom', command: 'application:hide' }
       { label: 'Hide Others', command: 'application:hide-other-applications' }
       { label: 'Show All', command: 'application:unhide-all-applications' }
-      { type: 'separator' }
-      { label: 'Run Atom Specs', command: 'application:run-all-specs' }
       { type: 'separator' }
       { label: 'Quit', command: 'application:quit' }
     ]
@@ -21,10 +20,13 @@
     submenu: [
       { label: 'New Window', command: 'application:new-window' }
       { label: 'New File', command: 'application:new-file' }
+      { label: 'Open...', command: 'application:open' }
       { type: 'separator' }
-      { label: 'Open...', command: 'application:open' }
-      { label: 'Open In Dev Mode...', command: 'application:open-dev' }
+      { label: 'Save', command: 'core:save' }
+      { label: 'Save As...', command: 'core:save-as' }
+      { label: 'Save All', command: 'window:save-all' }
       { type: 'separator' }
+      { label: 'Close File', command: 'pane:close' }
       { label: 'Close Window', command: 'window:close' }
     ]
   }
@@ -39,6 +41,7 @@
       { label: 'Copy', command: 'core:copy' }
       { label: 'Paste', command: 'core:paste' }
       { label: 'Select All', command: 'core:select-all' }
+      { type: 'separator' }
     ]
   }

@@ -47,7 +50,15 @@
     submenu: [
       { label: 'Reload', command: 'window:reload' }
       { label: 'Toggle Full Screen', command: 'window:toggle-full-screen' }
-      { label: 'Toggle Developer Tools', command: 'window:toggle-dev-tools' }
+      {
+        label: 'Developer'
+        submenu: [
+          { label: 'Open In Dev Mode...', command: 'application:open-dev' }
+          { label: 'Run Atom Specs', command: 'application:run-all-specs' }
+          { label: 'Toggle Developer Tools', command: 'window:toggle-dev-tools' }
+        ]
+      }
+      { type: 'separator' }
     ]
   }


add notice on deleting myProject package that would cause a bower update to fail

--- src/steroids/Bower.coffee
+++ src/steroids/Bower.coffee
@@ -28,7 +28,9 @@
             promptConfigurationMigration (userAgreed) ->
               if userAgreed
                 console.log "Moving Bower configuration from #{configs.legacy.bower} to #{configs.bower}"
-                migrateLegacyConfiguration done
+                migrateLegacyConfiguration ->
+                  console.log "Migrated bower.json file, running bower update. If you encounter ENOTFOUND Package errors for myProject or similar, they were due to our configuration mistake. You can safely delete the folder from www/components and retry."
+                  done()
               else
                 declareConfigurationMissing()
           else


Only update atom-styles children on context attribute change if attached

--- src/styles-element.coffee
+++ src/styles-element.coffee
@@ -1,6 +1,8 @@
 {Emitter, CompositeDisposable} = require 'event-kit'

 class StylesElement extends HTMLElement
+  attached: false
+
   createdCallback: ->
     @emitter = new Emitter
     @styleElementClonesByOriginalElement = new WeakMap
@@ -19,10 +21,16 @@
     @emitter.on 'did-update-style-element', callback

   attachedCallback: ->
+    @attached = true
+
     @subscriptions = new CompositeDisposable
     @subscriptions.add atom.styles.observeStyleElements(@styleElementAdded.bind(this))
     @subscriptions.add atom.styles.onDidRemoveStyleElement(@styleElementRemoved.bind(this))
     @subscriptions.add atom.styles.onDidUpdateStyleElement(@styleElementUpdated.bind(this))
+
+  detachedCallback: ->
+    @attached = false
+    @subscriptions.dispose()

   styleElementAdded: (styleElement) ->
     return unless styleElement.context is @context
@@ -56,11 +64,9 @@
     @emitter.emit 'did-update-style-element', styleElementClone

   contextChanged: ->
-    @styleElementRemoved(child) for child in Array::slice.call(@children)
     @context = @getAttribute('context')
-    @styleElementAdded(styleElement) for styleElement in atom.styles.getStyleElements()
-
-  detachedCallback: ->
-    @subscriptions.dispose()
+    if @attached
+      @styleElementRemoved(child) for child in Array::slice.call(@children)
+      @styleElementAdded(styleElement) for styleElement in atom.styles.getStyleElements()

 module.exports = StylesElement = document.registerElement 'atom-styles', prototype: StylesElement.prototype


Check that port exists before accessing

Check that port exists before accessing

--- src/lib/Component.coffee
+++ src/lib/Component.coffee
@@ -449,6 +449,8 @@
     args = ['in'] unless args.length
     res = []
     for port in args
+      unless @ports[port]
+        throw new Error "Node #{@nodeInstance.nodeId} has no port '#{port}'"
       res.push @ports[port].listAttached()
     return res.pop() if args.length is 1
     res
@@ -469,10 +471,14 @@
       validate = -> true
     for port in args
       if Array.isArray port
+        unless @ports[port[0]]
+          throw new Error "Node #{@nodeInstance.nodeId} has no port '#{port[0]}'"
         unless @ports[port[0]].isAddressable()
           throw new Error "Non-addressable ports, access must be with string #{port[0]}"
         return false unless @ports[port[0]].has @scope, port[1], validate
         continue
+      unless @ports[port]
+        throw new Error "Node #{@nodeInstance.nodeId} has no port '#{port}'"
       if @ports[port].isAddressable()
         throw new Error "For addressable ports, access must be with array [#{port}, idx]"
       return false unless @ports[port].has @scope, validate


Fix indentation

Fix indentation

--- resources/assets/coffee/_classes/gallery.coffee
+++ resources/assets/coffee/_classes/gallery.coffee
@@ -25,11 +25,11 @@

   initiateOpen: (e) =>
-      $target = $(e.currentTarget)
-      return if $target.parents('a').length
+    $target = $(e.currentTarget)
+    return if $target.parents('a').length

-      e.preventDefault()
-      @open $target
+    e.preventDefault()
+    @open $target

   open: ($target) =>


FIX - oops, broke tests ALL 839 TESTS PASSING

--- source/Art/Engine/Core/Drawing/ElementDrawLib.coffee
+++ source/Art/Engine/Core/Drawing/ElementDrawLib.coffee
@@ -100,7 +100,7 @@
     o.to            = colors && if to?   then layoutToFrom to, drawArea else drawArea.bottomLeft
     o.radius        = radius

-    if colors.constructor == GradientFillStyle
+    if colors?.constructor == GradientFillStyle
       colors.to = o.to
       colors.from = o.from
     o


PIXI stopped events now are prevent defaulted

--- impl/pixi/level0/item.coffee
+++ impl/pixi/level0/item.coffee
@@ -82,14 +82,18 @@
 	window.addEventListener 'touchmove', checkInteractions

 	# wheel event
-	window.addEventListener cssUtils.wheelEvent.eventName, (e) ->
-		event = cssUtils.wheelEvent.getDelta e
-		stage = impl._pixiStage
-
-		for item in stage.interactionManager.interactiveItems
-			item.wheel? event
-
-		return
+	do ->
+		event = originalEvent: null
+		window.addEventListener cssUtils.wheelEvent.eventName, (e) ->
+			arg = cssUtils.wheelEvent.getDelta e
+			stage = impl._pixiStage
+
+			for item in stage.interactionManager.interactiveItems
+				event.originalEvent = e
+				if item.wheel?(event, arg) is signal.STOP_PROPAGATION
+					break
+
+			return

 	updateMask = (item) ->
 		data = item._impl
@@ -203,14 +207,12 @@
 	setItemX: (val) ->
 		val = round val
 		@_impl.x = val
-		# @_impl.elem.position.x = val
 		impl._dirty = true
 		return

 	setItemY: (val) ->
 		val = round val
 		@_impl.y = val
-		# @_impl.elem.position.y = round val
 		impl._dirty = true
 		return

@@ -257,17 +259,20 @@
 		uniqueName = ns + utils.capitalize(name)
 		implName = SIGNALS[uniqueName]

-		# TODO: support stop propagation on different items
 		if implName and uniqueName isnt 'pointerReleased'
 			elem.interactive = true
 			_super = elem[implName] or NOP
-			elem[implName] = (e) ->
-				if e instanceof PIXI.InteractionData
-					e = mouseEvent
-				if _super(e) isnt signal.STOP_PROPAGATION
-					if self[name](e) is signal.STOP_PROPAGATION
+			elem[implName] = (e, arg) ->
+				unless arg
+					arg = mouseEvent
+				if _super(e, arg) isnt signal.STOP_PROPAGATION
+					if self[name](arg) is signal.STOP_PROPAGATION
 						if uniqueName is 'pointerPressed'
 							mouseActiveItem = self
+						{originalEvent} = e
+						originalEvent.stopPropagation()
+						if originalEvent.cancelable and implName isnt 'touchend' and implName isnt 'touchstart'
+							originalEvent.preventDefault()
 						return signal.STOP_PROPAGATION
 				return


Reverted copy paste error

Reverted copy paste error

shame on me...

--- core/app/backbone/models/channel.coffee
+++ core/app/backbone/models/channel.coffee
@@ -50,8 +50,6 @@

     new OwnChannelCollection ret

-  urlRoot: "/facts/"
-
   url: ->
     if @collection
       Backbone.Model::url.apply this, arguments


fix comma typo

fix comma typo

--- index.coffee
+++ index.coffee
@@ -117,7 +117,7 @@
       args = ['-u', remoteReleases, '-r', outputDirectory.path()]

       if remoteToken?
-        args.push '-t' remoteToken
+        args.push '-t', remoteToken

       if useMono
         args.unshift(cmd)


better messgeing

better messgeing

--- index.coffee
+++ index.coffee
@@ -9,7 +9,7 @@
 	json:true

 dcb = (err)->
-	console.log(err)
+	console.log(err) if err

 cleanForTweet=(msg)->
 	if msg.length > 132
@@ -42,7 +42,7 @@
 		true
 	true
 start = ()->
-	console.log 'running it time ', ++i
+	console.log 'run number #', ++i
 	request params, (e,r,b)->
 		if b and b.alerts
 			b.alerts.forEach eachAlert


Attributes parsing improved

Attributes parsing improved

--- index.coffee
+++ index.coffee
@@ -169,6 +169,10 @@
 	for child in getByType(obj.body, OBJECT)
 		r += stringObject child
 		r += "if (#{child.id} instanceof Item){ #{child.id}.parent = #{obj.id}; }\n"
+
+	# initialize states
+	if getElemByName obj.body, ATTRIBUTE, 'states'
+		r += "#{obj.id}.states\n"

 	r

@@ -273,13 +277,14 @@
 	for elem in elems
 		code += stringObjectFull elem

+	idsKeys = Object.keys ids
+
 	code += "var mainItem;\n"
 	for elem in elems
 		if elem.type is OBJECT
 			code += "if (#{elem.id} instanceof Item) mainItem = #{elem.id};\n"

 	idsObject = '{'
-	idsKeys = Object.keys ids
 	for id in idsKeys
 		idsObject += "#{id}: #{id}, "
 	if idsKeys.length > 0


client.del 执行绑定 client

client.del 执行绑定 client

--- index.coffee
+++ index.coffee
@@ -29,7 +29,7 @@
 flush = (key, callback) ->
   client.keys(getKey(key), (error, list) ->
     return callback(error) if error
-    async.each(list, client.del, callback)
+    async.each(list, client.del.bind(client), callback)
   )

 del = (key, callback = console.error) ->


fix parsing symfile name with space

fix parsing symfile name with space

--- src/model/symfile.coffee
+++ src/model/symfile.coffee
@@ -107,9 +107,9 @@
         throw new Error 'Form must include a "symfile" field'

       contents = props.symfile
-      header = contents.split('\n')[0].split(/\s+/)
+      header = contents.split('\n')[0].match(/^(MODULE) ([^ ]+) ([^ ]+) ([0-9A-F]+) (.*)/)

-      [dec, os, arch, code, name] = header
+      [line, dec, os, arch, code, name] = header

       if dec != 'MODULE'
         msg = 'Could not parse header (expecting MODULE as first line)'


deal with path.exists -> fs.exists

deal with path.exists -> fs.exists

--- src/modules/build.coffee
+++ src/modules/build.coffee
@@ -9,6 +9,8 @@
 coffeekup = require 'coffeekup'
 stylus = require 'stylus'
 {cssmin} = require 'cssmin'
+exists = fs.exists or path.exists
+existsSync = fs.existsSync or path.existsSync
 #nib = require 'nib'

 HEADER = 'madtalk - yyfearth.com/myyapps.com'
@@ -36,13 +38,13 @@
     base = path.resolve.apply null, base
     while (r = _rel.shift())
       base = path.join base, r
-      fs.mkdirSync base unless path.existsSync base
+      fs.mkdirSync base unless existsSync base
   return
 # end of mkdir

 rmdir = (dir, callback) ->
   if callback? # async
-    path.exists dir, (exists) ->
+    exists dir, (exists) ->
       unless exists
         callback?()
         return
@@ -58,7 +60,7 @@
             return
           rmdir dir # fallback
   else # sync
-    unless path.existsSync dir
+    unless existsSync dir
       # console.log 'Directory <#{dir}> does not exist'
       return false


Add Factory#set

Add Factory#set

--- lib/support/factory.coffee
+++ lib/support/factory.coffee
@@ -8,6 +8,7 @@
 ## Types
 BuiltinType    = require '../type/builtin_type'
 SeqType        = require '../type/seq_type'
+SetType        = require '../type/set_type'
 SubType        = require '../type/sub_type'
 TupleType      = require '../type/tuple_type'
 UnionType      = require '../type/union_type'
@@ -155,11 +156,18 @@
     new UnionType(candidates, _name)

   #### Collections
+
   seq: (elmType, name) ->
     elmType = @type(elmType)
     name    = @name(name)

     new SeqType(elmType, name)
+
+  set: (elmType, name) ->
+    elmType = @type(elmType)
+    name    = @name(name)
+
+    new SetType(elmType, name)

  #### Tuples and relations


Fill out the rest of the Edit menu

Fill out the rest of the Edit menu

--- menus/base.cson
+++ menus/base.cson
@@ -45,17 +45,50 @@
       { label: 'Paste', command: 'core:paste' }
       { label: 'Select All', command: 'core:select-all' }
       { type: 'separator' }
+      { label: 'Toggle Comments', command: 'editor:toggle-line-comments' }
       {
         label: 'Lines',
         submenu: [
           { label: 'Indent', command: 'editor:indent-selected-rows' }
           { label: 'Outdent', command: 'editor:outdent-selected-rows' }
           { label: 'Auto Indent', command: 'editor:auto-indent' }
+          { type: 'separator' }
           { label: 'Move Line Up', command: 'editor:move-line-up' }
           { label: 'Move Line Down', command: 'editor:move-line-down' }
           { label: 'Duplicate Line', command: 'editor:duplicate-line' }
           { label: 'Delete Line', command: 'editor:delete-line' }
           { label: 'Join Lines', command: 'editor:join-line' }
+        ]
+      }
+      {
+        label: 'Text',
+        submenu: [
+          { label: 'Upper Case', command: 'editor:upper-case' }
+          { label: 'Lower Case', command: 'editor:lower-case' }
+          { type: 'separator' }
+          { label: 'Delete to End of Word', command: 'editor:delete-to-end-of-word' }
+          { label: 'Delete Line', command: 'editor:delete-line' }
+          { type: 'separator' }
+          { label: 'Transpose', command: 'editor:transpose' }
+        ]
+      }
+      {
+        label: 'Folding',
+        submenu: [
+          { label: 'Fold', command: 'editor:fold-current-row' }
+          { label: 'Unfold', command: 'editor:unfold-current-row' }
+          { label: 'Unfold All', command: 'editor:unfold-all' }
+          { type: 'separator' }
+          { label: 'Fold All', command: 'editor:fold-all' }
+          { label: 'Fold Level 1', command: 'editor:fold-at-indent-level-1' }
+          { label: 'Fold Level 2', command: 'editor:fold-at-indent-level-2' }
+          { label: 'Fold Level 3', command: 'editor:fold-at-indent-level-3' }
+          { label: 'Fold Level 4', command: 'editor:fold-at-indent-level-4' }
+          { label: 'Fold Level 5', command: 'editor:fold-at-indent-level-5' }
+          { label: 'Fold Level 6', command: 'editor:fold-at-indent-level-6' }
+          { label: 'Fold Level 7', command: 'editor:fold-at-indent-level-7' }
+          { label: 'Fold Level 8', command: 'editor:fold-at-indent-level-8' }
+          { label: 'Fold Level 9', command: 'editor:fold-at-indent-level-9' }
         ]
       }
     ]


Added Table to views/ui

Added Table to views/ui

--- src/views/ui/index.coffee
+++ src/views/ui/index.coffee
@@ -7,4 +7,5 @@
   NavGroup: require './nav_group'
   Tabs: require './tabs'
   Window: require './window'
-	TabbedBar: require './tabbed_bar'
+  TabbedBar: require './tabbed_bar'
+  Table: require './table'


Small correction in the API impl

Small correction in the API impl

--- library/gearing/api.coffee
+++ library/gearing/api.coffee
@@ -54,7 +54,7 @@
         methodNotAllowed = 405
         codes = http.STATUS_CODES
         message = codes[methodNotAllowed]
-        doesJson = response.accepts "json"
+        doesJson = response.accepts /json/
         response.writeHead(methodNotAllowed, message)
         descriptor = error: message, code: methodNotAllowed
         @emit("unsupported", request, response, next)
@@ -83,7 +83,7 @@
     # different interpretations other than the one in the HTTP spec.
     OPTIONS: (request, response) ->
         knowns = @constructor.SUPPORTED
-        doesJson = response.accepts "json"
+        doesJson = response.accepts /json/
         pathname = try url.parse(request.url).pathname
         checkIfSupported = (method) => @[method] isnt @unsupported
         supported = _.filter(knowns, checkIfSupported)


added clear method for cookies

added clear method for cookies

--- src/zombie/cookies.coffee
+++ src/zombie/cookies.coffee
@@ -91,6 +91,17 @@
         if in_path = in_domain[pathname]
           delete in_path[name.toLowerCase()]

+    #### cookies(host, path).clear()
+    #
+    # Clears all cookies.
+    #
+    this.clear = (options = {})->
+      if in_domain = cookies[hostname]
+        if in_path = in_domain[pathname]
+          for name, value of in_path
+            delete in_path[name]
+          # delete in_path[name.toLowerCase()]
+
     #### cookies(host, path).update(serialized)
     #
     # Update cookies from serialized form. This method works equally well for


Change url for ip route

Change url for ip route

--- src/scripts/httpd.coffee
+++ src/scripts/httpd.coffee
@@ -38,5 +38,5 @@
       child.stdin.end()

   robot.router.get "/hubot/ip", (req, res) ->
-    robot.http('http://checkip.dyndns.org').get() (err, r, body) ->
+    robot.http('http://ifconfig.me/ip').get() (err, r, body) ->
       res.end body


Steam now gives 2 messages to allow images to appear in some chats

--- src/scripts/steam.coffee
+++ src/scripts/steam.coffee
@@ -23,10 +23,12 @@
 HTMLParser = require "htmlparser"
 sanitize = require('validator').sanitize

+image = null

 module.exports = (robot) ->
   robot.respond /daily deal/i, (msg) ->
     getDeals msg, (responseText) ->
+      msg.send image
       msg.send responseText

@@ -45,7 +47,8 @@
   if dealObj?
     originalPrice = Select(handler.dom, '.dailydeal_content .discount_original_price')[0]
     finalPrice = Select(handler.dom, '.dailydeal_content .discount_final_price')[0]
-    deal = "From #{sanitize(originalPrice.children[0].data).entityDecode().trim()} to #{sanitize(finalPrice.children[0].data).entityDecode().trim()}  #{dealObj.children[0].attribs.src}  #{dealObj.attribs.href}"
+    image = "#{dealObj.children[0].attribs.src.replace /[?].*/ , ''}"
+    deal = "From #{sanitize(originalPrice.children[0].data).entityDecode().trim()} to #{sanitize(finalPrice.children[0].data).entityDecode().trim()} #{dealObj.attribs.href}"
   else
     msg.send "No daily deal found"


Making the server available in the callback of run

--- src/server/newbin.coffee
+++ src/server/newbin.coffee
@@ -7,6 +7,7 @@
 User    = require('./user')
 MongoInterface = require('./mongo_interface')
 {hashPassword} = require('../api/utils')
+{getConfigFromFile} = require('../shared/utils')

 class Runner

@@ -38,29 +39,13 @@
         'emailerConfig'
     ]

-    serverConfig     = {}
-    serverConfig.admins = []
-    serverConfigPath = null
-    projectRoot      = null
     Opts             = null
     dbName           = 'cloudbrowser'
+    projectRoot      = null
+    serverConfig     = {admins : []}
     mongoInterface   = null
-
-    # TODO : Refactor this function into some common file as it is used by
-    # application_manager too
-
-    # Parsing the json file into opts
-    getConfigFromFile = (path) ->
-        try
-            fileContent = Fs.readFileSync(path, {encoding:"utf8"})
-            content = JSON.parse(fileContent)
-        catch e
-            console.log "Parse error in file #{path}."
-            console.log "The file's content was:"
-            console.log fileContent
-            throw e
-
-        return content
+    adminCollection  = 'admin_interface.users'
+    serverConfigPath = null

     exclude = (key, value) ->
         if excludedProperties.indexOf(key) isnt -1 then return undefined
@@ -87,7 +72,6 @@
             for own k, v of config
                 if validConfigProperties.indexOf(k) isnt -1
                     serverConfig[k] = v
-
         # emailer_config.json
         if Fs.existsSync(emailerConfigPath)
             serverConfig.emailerConfig = getConfigFromFile(emailerConfigPath)
@@ -158,23 +142,21 @@
             if validConfigProperties.indexOf(k) isnt -1
                 serverConfig[k] = v

-    startServer = () ->
+    startServer = (callback) ->
         if serverConfig.deployment
-            console.log "Server started in deployment mode"
+            console.log("Server started in deployment mode")
+            # TODO : Implement deployment mode.
         else
             paths = []
-
             # List of all the unmatched positional args (the path names)
-            paths.push path for path in Opts._
-
-            server = new Server(serverConfig, paths, projectRoot, mongoInterface)
-
-        server.once 'ready', ->
-            console.log 'Server started in local mode'
+            paths.push(path) for path in Opts._
+            server = new Server(serverConfig, paths, projectRoot,
+                                mongoInterface)
+        server.once 'ready', () ->
+            callback(server)

     configureUser = (callback) ->
         user = null
-
         Async.waterfall [
             (next) ->
                 Read({prompt : "Email: "}, next)
@@ -186,7 +168,7 @@
                 else
                     user = new User(email)
                     # Find if the user already exists in the admin interface collection
-                    mongoInterface.findUser(user, 'admin_interface.users', next)
+                    mongoInterface.findUser(user, adminCollection, next)
             (userRec, next) ->
                 # Bypassing the waterfall
                 if userRec then callback(null, user)
@@ -197,58 +179,45 @@
                 # Insert into admin_interface collection
                 user.key  = result.key.toString('hex')
                 user.salt = result.salt.toString('hex')
-                mongoInterface.addUser user, 'admin_interface.users',
-                    (err, userRec) -> next(null, user)
+                mongoInterface.addUser(user, adminCollection, next)
+        ], (err, userRec) ->
+            return callback(err) if err
+            callback(null, user)
+
+    configureAllUsers = (callback) ->
+        Async.series [
+            (next) ->
+                return next(null) if serverConfig.admins.length
+                console.log("Please configure at least one admin")
+                configureUser (err, adminUser) ->
+                    return next(err) if err
+                    serverConfig.admins.push(adminUser.getEmail())
+                    writeConfigToFile()
+                    next(null)
+            , (next) ->
+                return next(null) if serverConfig.defaultUser
+                console.log("Please configure the default user")
+                configureUser (err, defaultUser) ->
+                    return next(err) if err
+                    serverConfig.defaultUser = defaultUser.getEmail()
+                    writeConfigToFile()
+                    next(null)
         ], callback

-    @run : () ->
+    @run : (callback) ->
         Async.series [
             (next) ->
                 mongoInterface = new MongoInterface(dbName, next)
-            , (next) ->
-                # Configuration
+            (next) ->
                 setProjectRoot()
-
                 setInitialConfig()
-
                 parseCmdLineOptions()
-
-                Async.series [
-                    (callback) ->
-                        if serverConfig.admins.length then callback(null)
-                        else
-                            console.log "Please configure at least one admin"
-                            Async.waterfall [
-                                (next) ->
-                                    configureUser(next)
-                                (adminUser, next) ->
-                                    serverConfig.admins.push(adminUser.getEmail())
-                                    writeConfigToFile()
-                                    next(null)
-                            ], callback
-                    , (callback) ->
-                        if serverConfig.defaultUser then callback(null)
-                        else
-                            console.log "Please configure the default user"
-                            Async.waterfall [
-                                (next) ->
-                                    configureUser(next)
-                                (defaultUser, next) ->
-                                    serverConfig.defaultUser = defaultUser.getEmail()
-                                    writeConfigToFile()
-                                    next(null)
-                            ], callback
-
-                ], (err, results) ->
-                    if err
-                        console.log(err)
-                        console.log "Could not start the server"
-                        process.exit(1)
-                    # Start the server only after the admin user and default user have
-                    # been configured
-                    startServer()
-                    next(null)
+                configureAllUsers(next)
         ], (err, results) ->
-            throw err if err
-
-Runner.run()
+            if err
+                console.log("Could not start the server #{err}")
+                process.exit(1)
+            else startServer(callback)
+
+Runner.run (server) ->
+    console.log('Server started in local mode')


Remove unused variable

Remove unused variable

--- resources/assets/coffee/react/beatmap-discussions/discussion.coffee
+++ resources/assets/coffee/react/beatmap-discussions/discussion.coffee
@@ -137,8 +137,6 @@

   doVote: (e) =>
-    downvoting = e.currentTarget.dataset.score == '-1'
-
     LoadingOverlay.show()

     @voteXhr?.abort()


Incorrect copy & paste of the check

Incorrect copy & paste of the check

--- resources/assets/coffee/react/beatmap-discussions/discussion.coffee
+++ resources/assets/coffee/react/beatmap-discussions/discussion.coffee
@@ -270,7 +270,7 @@

   isRead: (post) =>
-    @props.readPostIds.has(post.id) && !@isOwner(post) && !@props.preview
+    @props.readPostIds.has(post.id) || @isOwner(post) || !@props.preview

   isVisible: (object) =>


Position the settings dropdown to be next the minimap

It also properly handles minimap on left.

--- lib/minimap-open-settings-view.coffee
+++ lib/minimap-open-settings-view.coffee
@@ -13,13 +13,17 @@
       if @dropdown?
         @dropdown.destroy()
       else
-        offset = @offset()
+        minimap = @parent()
+        offset = minimap.offset()
         @dropdown = new MinimapPluginsDropdownView
-        @dropdown.css
-          top: offset.top
-          right: window.innerWidth - offset.left

-        @dropdown.attach()
+        css = top: offset.top
+        if atom.config.get('minimap.displayMinimapOnLeft')
+          css.left = offset.left + minimap.width()
+        else
+          css.right =  window.innerWidth - offset.left
+
+        @dropdown.css(css).attach()

         @dropdown.on 'minimap:settings-dropdown-destroyed', =>
           @dropdown.off()


add support for delete working across formatting/commas

--- app/assets/javascripts/angular/directives/smartNumber.js.coffee
+++ app/assets/javascripts/angular/directives/smartNumber.js.coffee
@@ -89,7 +89,10 @@

     findNewDeleteCursorPosition = (elem, initialPosition) ->
       if elem.val().length == 5 || elem.val().length == 9 # a comma has been removed
-        return initialPosition - 1
+        if commaAfterCursor(elem)
+          return initialPosition
+        else
+          return initialPosition - 1
       else # cursor is adding numbers at the end of the input field
         return initialPosition

@@ -103,9 +106,9 @@
       cursorPos = elem[0].selectionStart
       elem.val().charAt(elem[0].selectionStart - 1) == "," and event.which == 8

-    deleteAtComma = (elem, event) ->
+    commaAfterCursor = (elem) ->
       cursorPos = elem[0].selectionStart
-      elem.val().charAt(cursorPos) == "," and event.which == 46
+      elem.val().charAt(cursorPos) == ","

     moveCursorLeft = (elem) ->
       newCursorPos = elem[0].selectionStart - 1
@@ -134,7 +137,7 @@
     deletePressed = (elem, event) ->
       originalValue = elem.val() # the original value of the field before keypress
       position = elem[0].selectionStart # cursor position
-      if deleteAtComma(elem, event)
+      if commaAfterCursor(elem)
         newValue = [originalValue.slice(0, position), originalValue.slice(position + 2)].join('') # insert the new number into the field
       else
         newValue = [originalValue.slice(0, position), originalValue.slice(position + 1)].join('') # insert the new number into the field


add steps to survey template model

add steps to survey template model

--- app/assets/javascripts/hanuman/models/survey_template.js.coffee
+++ app/assets/javascripts/hanuman/models/survey_template.js.coffee
@@ -3,4 +3,5 @@
   status: DS.attr('string'),
   survey_type: DS.attr('string'),
   survey_questions: DS.hasMany('survey_question', {async: true})
+  steps: DS.attr('array')
 })


Minor angularjs change: make a copy of an array returned by an ajax call, before passing it to another function.

This ensures better data isolation.

--- app/assets/javascripts/ng-services/ng-FeedsFoldersSvc.js.coffee
+++ app/assets/javascripts/ng-services/ng-FeedsFoldersSvc.js.coffee
@@ -117,7 +117,7 @@
       delete $rootScope.loading_single_folder_feeds[folder.id]
       # Update unread counts with the received feeds. Set the unread counter for any feed in the folder but
       # not in the received JSON to zero.
-      update_folder_feeds folder, data
+      update_folder_feeds folder, data.slice()
     .error (data, status)->
       delete $rootScope.loading_single_folder_feeds[folder.id]
       if status==404


Change default contour color, fixes #1488

--- app/assets/javascripts/oxalis/geometries/contourgeometry.coffee
+++ app/assets/javascripts/oxalis/geometries/contourgeometry.coffee
@@ -7,7 +7,7 @@

 class ContourGeometry

-  COLOR_NORMAL : new THREE.Color(0x000000)
+  COLOR_NORMAL : new THREE.Color(0x0000ff)
   COLOR_DELETE : new THREE.Color(0xff0000)

   constructor : (@volumeTracing, @flycam) ->


Support for all known video containers with fallback to AVI

--- app/assets/javascripts/uploadcare/widget/tabs/camera-tab.coffee
+++ app/assets/javascripts/uploadcare/widget/tabs/camera-tab.coffee
@@ -166,9 +166,7 @@
       @__setState('recording')

       @__chunks = []
-      __recorderOptions = {
-        mimeType: 'video/webm'
-      }
+      __recorderOptions = {}

       if @settings.audioBitsPerSecond != null
         __recorderOptions.audioBitsPerSecond = @settings.audioBitsPerSecond
@@ -176,7 +174,10 @@
       if @settings.videoBitsPerSecond != null
         __recorderOptions.videoBitsPerSecond = @settings.videoBitsPerSecond

-      @__recorder = new @MediaRecorder(@__stream, __recorderOptions)
+      if Object.keys(__recorderOptions).length != 0
+        @__recorder = new @MediaRecorder(@__stream, __recorderOptions)
+      else
+        @__recorder = new @MediaRecorder(@__stream)

       @__recorder.start()
       @__recorder.ondataavailable = (e) =>
@@ -187,7 +188,8 @@

       @__recorder.onstop = =>
         blob = new Blob(@__chunks, {'type': @__recorder.mimeType})
-        blob.name = "record.webm"
+        ext = @__guessExtensionByMime(@__recorder.mimeType)
+        blob.name = "record.#{ext}"
         @dialogApi.addFiles('object', [[blob, {source: 'camera'}]])
         @dialogApi.switchTab('preview')
         @__chunks = []
@@ -198,3 +200,26 @@

       @__recorder.stop()
       @__chunks = []
+
+    __guessExtensionByMime: (mime) ->
+      known_containers = {
+        'mp4': 'mp4',
+        'ogg': 'ogg',
+        'webm': 'webm',
+        'quicktime': 'mov',
+        'x-matroska': 'mkv',
+      }
+      # e.g. "video/x-matroska;codecs=avc1,opus"
+      if mime
+        # e.g. ["video", "x-matroska;codecs=avc1,opus"]
+        mime = mime.split('/')
+        if mime[0] == 'video'
+          # e.g. "x-matroska;codecs=avc1,opus"
+          mime = mime.slice(1).join('/')
+          # e.g. "x-matroska"
+          container = mime.split(';')[0]
+          # e.g. "mkv"
+          if known_containers[container]
+            return known_containers[container]
+      # In all other cases just return the base extension for all times
+      return 'avi'


fixing wrong min resolution

fixing wrong min resolution

--- source/app/coffee/renderables/LoadingScreen.coffee
+++ source/app/coffee/renderables/LoadingScreen.coffee
@@ -144,8 +144,8 @@
         # min width: 800
         # min height: 600
         # if iOS
-        # console.log window.screen.availWidth, window.screen.availHeight
-        if window.screen.availWidth < 768 || window.screen.availHeight < 1024
+        # console.log 'resolution', window.screen.availWidth, window.screen.availHeight
+        if window.screen.availWidth < 800 || window.screen.availHeight < 600
             @soon = new Soon()
             @soon.x = AppData.WIDTH / 2
             @soon.y = AppData.HEIGHT / 2


Fix failing test after merging in updates to #56

--- karma/test-directives.coffee
+++ karma/test-directives.coffee
@@ -297,7 +297,10 @@
         type: 'pod_action_api_failure'
       }]

-      template = $compile('<cp-case-notifications></cp-case-notifications>')
+      template = $compile('
+        <cp-case-notifications notifications="notifications">
+        </cp-case-notifications>
+      ')
       el = template($rootScope)[0]
       $rootScope.$digest()


[FIX] split-right && split-down mixup

[FIX] split-right && split-down mixup

emacs defaults are:

- `ctrl-3` : `split-right`
- `ctrl-2` : `split-down`

source: http://www.gnu.org/software/emacs/manual/html_node/emacs/Split-Window.html

--- keymaps/transient-emacs.cson
+++ keymaps/transient-emacs.cson
@@ -14,8 +14,8 @@
   'ctrl-x ctrl-f':'application:open'
   'ctrl-x ctrl-c':'application:quit'

-  'ctrl-x 2':'pane:split-right'
-  'ctrl-x 3':'pane:split-down'
+  'ctrl-x 3':'pane:split-right'
+  'ctrl-x 2':'pane:split-down'
   'ctrl-x 0':'pane:close'
   'ctrl-x o':'window:focus-next-pane'


:bug: Replace : and $

:bug: Replace : and $

--- lib/autocomplete-hack.coffee
+++ lib/autocomplete-hack.coffee
@@ -7,13 +7,17 @@
     Hack = require('./hack')
     if typeof atom.packages.getLoadedPackage("autocomplete-plus") is 'undefined'
       return Hack.showError("autocomplete-plus Package not found, but is required for to provide autocomplete.")
-    Hack.init();
+    Hack.init()
     Provider =
       selector: '.source.cpp, .source.hack, .source.cpp'
       disableForSelector: '.comment'
-
-      getSuggestions: ({editor, bufferPosition, scopeDescriptor, prefix}) ->
+      getPrefix:(editor, bufferPosition)->
+        regex = /(?!(\:){2})(\:)*[\$\w0-9_-]+$/
+        line = editor.getTextInRange([[bufferPosition.row, 0], bufferPosition])
+        line.match(regex)?[0] or ''
+      getSuggestions: ({editor, bufferPosition, scopeDescriptor}) ->
         return [] unless Hack.config.status
+        prefix = Provider.getPrefix editor, bufferPosition
         Buffer = editor.getBuffer()
         Text = Buffer.getText()
         Index = Buffer.characterIndexForPosition(bufferPosition)
@@ -36,6 +40,7 @@
                 type: Type
                 text: Text
                 leftLabel: Label
+                replacementPrefix: prefix
                 score: prefix.length > 0 and Text.score(prefix)
               }
             )


Register additional file extensions as XML

Fixes https://github.com/wolfgangmm/atom-existdb/issues/20

--- lib/existdb-tree-view.coffee
+++ lib/existdb-tree-view.coffee
@@ -27,7 +27,8 @@

         initialize: (@state, @config, @main) ->
             mime.define({
-                "application/xquery": ["xq", "xql", "xquery", "xqm"]
+                "application/xquery": ["xq", "xql", "xquery", "xqm"],
+                "application/xml": ["odd", "xconf", "tei"]
             })

             atom.workspace.observeTextEditors((editor) =>


refactor(navbar): sugar

refactor(navbar): sugar

fixes #52

--- modules/scripts/directives/navbar.coffee
+++ modules/scripts/directives/navbar.coffee
@@ -15,14 +15,13 @@
   transclude: true
   scope:
     bpNavbarTitle: '@'
-  compile: (elem, attrs, transcludeFn) ->
-
-    getTitleFromState = (state) ->
+  controller: ($scope) ->
+    $scope.getTitleFromState = (state) ->
       state.data?.title or
       state.name.charAt(0).toUpperCase() + state.name.slice(1)

+  compile: (elem, attrs, transcludeFn) ->
     ios = if bpConfig.platform is 'android' then no else yes
-
     (scope, element, attrs) ->
       state = $state.current

@@ -30,9 +29,8 @@
         role: 'navigation'

       transcludeFn scope, (clone) ->
-
         unless attrs.bpNavbarTitle?
-          attrs.bpNavbarTitle = getTitleFromState state
+          attrs.bpNavbarTitle = scope.getTitleFromState state

         $title = $compile("
           <bp-navbar-title role='heading'>{{
@@ -43,7 +41,7 @@

         if state.data?.up? and not attrs.bpNavbarNoUp?
           upState = $state.get state.data.up
-          upTitle = getTitleFromState upState
+          upTitle = scope.getTitleFromState upState
           $up = $compile("
             <bp-action class='bp-action-up' bp-sref='#{upState.name}'>#{
               upTitle
@@ -61,17 +59,15 @@

             unless scope.navbarTitle then $timeout ->
               difference = $scndAction.outerWidth() - $frstAction.outerWidth()
-
               if difference isnt 0 and $frstAction.length
                 $spacer = angular.element("
                   <div style='
                     -webkit-box-flex:10;
                     max-width:#{Math.abs(difference)}px
                   '>")
-                if difference > 0
-                  $spacer.insertBefore $title
-                else if difference < 0
-                  $spacer.insertAfter $title
+                $spacer[if difference > 0
+                  'insertBefore'
+                else 'insertAfter'] $title
             , 0

           else
@@ -96,9 +92,7 @@

               element.append $up, $title, $frstAction, $scndAction
             else
-              element.append $up, $icon, $title, $frstAction, $scndAction
+              element.append $icon, $title, $frstAction, $scndAction

-        else
-          # TODO: implement Toolbar/Action overflow
-          console.warn 'Toolbar/Action overflow implementation missing'
-          console.log $actions
+        # else
+        # TODO: implement Toolbar/Action overflow


Rewrite notification message

Rewrite notification message

--- lib/notification-view.coffee
+++ lib/notification-view.coffee
@@ -15,7 +15,7 @@
       @span "' package"
   @content: ->
     @div tabindex: -1, class: 'notification overlay from-top native-key-bindings', =>
-      @h1 "Requires another package"
+      @h1 "Requires related package installation"
       @ul => @warn()
       @p "You can install and activate packages using the preference pane."


fix bugs

fix bugs

--- lib/pushservices/apns.coffee
+++ lib/pushservices/apns.coffee
@@ -33,7 +33,7 @@
             device.subscriberId = subscriber.id # used for error logging
             note.alert = payload.localizedTitle(info.lang)
             if subOptions?.ignore_message isnt true and message = payload.localizedMessage(info.lang)
-               note.alert = "[" + note.alert + "] " + alert
+               note.alert = "[" + note.alert + "] " + message
             note.badge = badge if not isNaN(badge = parseInt(payload.badge))
             note.sound = payload.sound
             if @payloadFilter?


Rename getElementRanges ~> getElementRangesFromNonNested + Comment

--- lib/text-manipulation.coffee
+++ lib/text-manipulation.coffee
@@ -68,7 +68,7 @@

         # @showIxRanges bufferText, nonNestedIxRanges

-        elementRanges = @getElementRanges bufferText, listStartIx, listEndIx, nonNestedIxRanges
+        elementRanges = @getElementRangesFromNonNested bufferText, listStartIx, listEndIx, nonNestedIxRanges

         # @showIxRanges bufferText, elementRanges

@@ -126,7 +126,10 @@
   addRange: (ranges, rangeStart, rangeEnd, isNested) ->
     ranges.push [rangeStart,rangeEnd] if not isNested && rangeStart != rangeEnd

-  getElementRanges: (bufferText, startIx, endIx, nonNestedRanges) ->
+  # Convert list of ranges that cover the entire list except its sublists, to ranges for its elements.
+  # The first separator encountered is expected to be the separator for the entire list.
+  getElementRangesFromNonNested: (bufferText, startIx, endIx, nonNestedRanges) ->
+    # By using nonNestedRanges, we can easily skip the sublists
     elementRanges = []
     elementStart = startIx
     separator = null


updated handlebars

updated handlebars

--- lib/web/views/ki-flow.coffee
+++ lib/web/views/ki-flow.coffee
@@ -81,7 +81,6 @@
       throw "Selector '#{selector}' returned #{nodeCount} elements. There were #{assertCount} asserts. #{numberOfCompared} elements were ok, but you need to #{verb} #{Math.abs(diff)} asserts!"
   null # cleaner javascript

-hb = Handlebars.create
 handlebarsCache = {}

 getCompiledTemplate = (templateId) ->
@@ -91,7 +90,7 @@
   template = $(templateId)
   if template.size() == 0
     throw "Could not locate template '#{templateId}'"
-  handlebarsCache[templateId] = hb.compile(template.html())
+  handlebarsCache[templateId] = Handlebars.compile(template.html())

 this.renderElements = (destId, templateId, data) ->
   dest = $(destId)


 Merge branch 'master.lightweightVersion.mixpanel' into 'master'

LightweightKoding: Added mixpanel to all pages.

--- server/lib/crawler/staticpages/analytics.coffee
+++ server/lib/crawler/staticpages/analytics.coffee
@@ -35,4 +35,9 @@
       f=d.getElementsByTagName("script")[0];s.src="//d37gvrvc0wt4s1.cloudfront.net/js/1/rollbar.min.js";s.async=!0;
       f.parentNode.insertBefore(s,f);};if(w.addEventListener){w.addEventListener("load",i,!1);}else{w.attachEvent("onload",i);}})(window,document);
     </script>
+
+    <!-- MIXPANEL -->
+    <script>
+      (function(e,b){if(!b.__SV){var a,f,i,g;window.mixpanel=b;a=e.createElement("script");a.type="text/javascript";a.async=!0;a.src=("https:"===e.location.protocol?"https:":"http:")+'//cdn.mxpnl.com/libs/mixpanel-2.2.min.js';f=e.getElementsByTagName("script")[0];f.parentNode.insertBefore(a,f);b._i=[];b.init=function(a,e,d){function f(b,h){var a=h.split(".");2==a.length&&(b=b[a[0]],h=a[1]);b[h]=function(){b.push([h].concat(Array.prototype.slice.call(arguments,0)))}}var c=b;"undefined"!==typeof d?c=b[d]=[]:d="mixpanel";c.people=c.people||[];c.toString=function(b){var a="mixpanel";"mixpanel"!==d&&(a+="."+d);b||(a+=" (stub)");return a};c.people.toString=function(){return c.toString(1)+".people (stub)"};i="disable track track_pageview track_links track_forms register register_once alias unregister identify name_tag set_config people.set people.set_once people.increment people.append people.track_charge people.clear_charges people.delete_user".split(" ");for(g=0;g<i.length;g++)f(c,i[g]);b._i.push([a,e,d])};b.__SV=1.2}})(document,window.mixpanel||[]);mixpanel.init("d35a8d0b14e284f32ab5380590c6848a");
+    </script>
 """


Fix taskTypes require

Fix taskTypes require

--- app/classifier/tasks/combo/index.cjsx
+++ app/classifier/tasks/combo/index.cjsx
@@ -20,7 +20,7 @@
         Object.assign task: childTaskKey, defaultAnnotation

     isAnnotationComplete: (taskDescription, annotation, workflow) ->
-      taskTypes = require '..'
+      taskTypes = require('..').default

       method = if taskDescription.loosen_requirements
         'some'


[Backport] Issue 4590: Untranslated strings

--- app/coffee/modules/admin/roles.coffee
+++ app/coffee/modules/admin/roles.coffee
@@ -392,7 +392,7 @@
                 { key: "view_tasks", name: "COMMON.PERMISIONS_CATEGORIES.TASKS.VIEW_TASKS"}
                 { key: "add_task", name: "COMMON.PERMISIONS_CATEGORIES.TASKS.ADD_TASKS"}
                 { key: "modify_task", name: "COMMON.PERMISIONS_CATEGORIES.TASKS.MODIFY_TASKS"}
-                { key: "comment_task", name: "COMMON.PERMISIONS_CATEGORIES.USER_STORIES.COMMENT_TASKS"}
+                { key: "comment_task", name: "COMMON.PERMISIONS_CATEGORIES.TASKS.COMMENT_TASKS"}
                 { key: "delete_task", name: "COMMON.PERMISIONS_CATEGORIES.TASKS.DELETE_TASKS"}
             ]
             categories.push({
@@ -404,7 +404,7 @@
                 { key: "view_issues", name: "COMMON.PERMISIONS_CATEGORIES.ISSUES.VIEW_ISSUES"}
                 { key: "add_issue", name: "COMMON.PERMISIONS_CATEGORIES.ISSUES.ADD_ISSUES"}
                 { key: "modify_issue", name: "COMMON.PERMISIONS_CATEGORIES.ISSUES.MODIFY_ISSUES"}
-                { key: "comment_issue", name: "COMMON.PERMISIONS_CATEGORIES.USER_STORIES.COMMENT_ISSUES"}
+                { key: "comment_issue", name: "COMMON.PERMISIONS_CATEGORIES.ISSUES.COMMENT_ISSUES"}
                 { key: "delete_issue", name: "COMMON.PERMISIONS_CATEGORIES.ISSUES.DELETE_ISSUES"}
             ]
             categories.push({


Code style fix

Code style fix

--- backoffice/javascripts/controllers/users_list_controller.coffee
+++ backoffice/javascripts/controllers/users_list_controller.coffee
@@ -2,7 +2,7 @@

 app = angular.module 'dashboard'

-INITIAL_PAGE = 1
+INITIAL_PAGE          = 1
 INITIAL_ROWS_PER_PAGE = 10

 app.controller 'UsersListController', ($scope, $q, $state, $modal, ngTableParams, API) ->
@@ -33,8 +33,8 @@
       remove(user._id)

   $scope.tableParams = new ngTableParams
-    page: INITIAL_PAGE
-    count: INITIAL_ROWS_PER_PAGE
+    page  : INITIAL_PAGE
+    count : INITIAL_ROWS_PER_PAGE
   ,
     # '$scope: $scope' line added to avoid
     # "TypeError: Cannot read property '$on' of null"


CONFIG: Add debug method to quickly inspect scope in debugger

--- src/app/app.coffee
+++ src/app/app.coffee
@@ -1,3 +1,4 @@
+# Define doubtfire
 angular.module("doubtfire", [
   # ng*
   "ngCookies"
@@ -120,4 +121,10 @@
   # $rootScope.$on "i18nReady", ->
   #   setPageTitle $state.current
   #   $rootScope.$on "$stateChangeSuccess", (evt, toState) -> setPageTitle toState
-)+)
+
+
+# Debug helper method
+scope = ($0) ->
+  throw new Error "Select a DOM element using 'Inspect Element' first, then call using scope($0)" unless $0?
+  angular.element($0).scope()


Set up .

Set up .

--- src/command.coffee
+++ src/command.coffee
@@ -265,13 +265,22 @@

 # Task - install packages
 task 'install', 'install packages', ->
+  logging.info 'Installing packages...'
+  pkgs = opts.arguments[2..]
+  if pkgs.length > 0
+    # install the packages
+    pkgmgr.install pkg for pkg in pkgs
+  else
+    # install all dependencies listed in component.json
+    config = require('component.json')
+    pkgmgr.install pkg for pkg, version of config.dependencies

 # Task - update packages
 task 'update', 'update packages', ->

 # Task - watch files and compile as needed
 task 'watch', 'watch files and compile as needed', ->
-  logging.info "Watching project..."
+  logging.info 'Watching project...'
   watch.setEnv (opts.env ? 'development'), opts
   fs.removeSync publicDir

@@ -288,7 +297,7 @@

 # Task - compile coffeescripts and copy assets into `public/` directory
 task 'build', 'compile coffeescripts and copy assets into public/ directory', ->
-  logging.info "Building project..."
+  logging.info 'Building project...'
   watch.setEnv (opts.env ? 'development'), opts
   fs.removeSync publicDir
   watch.compileDir clientDir
@@ -301,7 +310,7 @@

 # Task - minify and concatenate js/css files for production
 task 'minify', 'minify and concatenate js/css files for production', ->
-  logging.info "Preparing project files for production..."
+  logging.info 'Preparing project files for production...'
   watch.setEnv (opts.env ? 'production'), opts
   async.series [
     # Rebuild
@@ -337,7 +346,7 @@
 # Task - remove the `public/` directory
 task 'clean', 'remove the public/ directory', ->
   fs.removeSync publicDir
-  logging.warn "Removed the public/ directory."
+  logging.warn 'Removed the public/ directory.'

 # Task - run tests
 task 'test', 'run tests', ->


More cleanup...

More cleanup...

--- src/command.coffee
+++ src/command.coffee
@@ -30,7 +30,10 @@
           total: parseInt getbot.size, 10

       getbot.on 'data', (data, rate) ->
+<<<<<<< HEAD
         rate = "#{makeReadable rate}/s"
+=======
+>>>>>>> 8063a78fcb6d95529dc0da2ab9850f386d435e05
         bar.tick(data.length, {'rate': rate})
       return
     else


KD: removed deprecated method

KD: removed deprecated method

--- src/core/kd.coffee
+++ src/core/kd.coffee
@@ -79,8 +79,6 @@

   getAllKDInstances  :-> KD.instances

-  getKDViewInstanceFromDomElement:(el)-> @instances[el.getAttribute "data-id"]
-
   exportKDFramework:->
     (window[item] = KD.classes[item] for own item of KD.classes)
     KD.exportKDFramework = -> "Already exported."


don't error without a decking.json file if help command is being called

--- src/decking.coffee
+++ src/decking.coffee
@@ -14,10 +14,8 @@

 module.exports =
 class Decking
-  constructor: (options) ->
-    {@command, @args} = options
-
-    @config = @loadConfig "./decking.json"
+  constructor: ({@command, @args}) ->
+    @config = {}

   processConfig: (config) ->
     for name, details of config.containers
@@ -27,7 +25,7 @@

       details.name = name

-      details.dependencies = [] if not details.dependencies
+      details.dependencies ?= []
       details.aliases = []

       for dependency,i in details.dependencies
@@ -343,7 +341,7 @@
       log "Uploading tarball..."
       docker.buildImage tarball, options, (err, res) ->
         return done err if err
-
+
         res.pipe process.stdout

         res.on "end", ->
@@ -356,6 +354,8 @@
     fn = @commands[@command]

     throw new Error "Invalid argument" if typeof fn isnt "function"
+
+    @config = @loadConfig "./decking.json" if @command isnt "help"

     return fn.call this, (err) -> throw err if err


Remove ivars assigned in ctor

Remove ivars assigned in ctor

--- src/develop.coffee
+++ src/develop.coffee
@@ -13,9 +13,6 @@
 module.exports =
 class Develop extends Command
   @commandNames: ['dev', 'develop']
-
-  atomDirectory: null
-  atomDevPackagesDirectory: null

   constructor: ->
     @atomDirectory = config.getAtomDirectory()


Fix typo

Fix typo

--- src/grammar.coffee
+++ src/grammar.coffee
@@ -102,7 +102,7 @@
     o "Comment"
   ]

-  # A an indented block of expressions. Note that the [Rewriter](rewriter.html)
+  # An indented block of expressions. Note that the [Rewriter](rewriter.html)
   # will convert some postfix forms into blocks for us, by adjusting the
   # token stream.
   Block: [


.

Former-commit-id: f13405cd2eea568de04675b9b78a057e5d90693a [formerly f902ea0a8a25d8e52ed1961eadc84c8c3a22d462] [formerly eeb6567e61e6123a4c64f3af13ce9db42ed8b7b5 [formerly f442cfa6675fa1bfd4ed881f0a7c22d196a86093]] [formerly 330f4de84c4e7f6847ac15df575dd5ed14007353 [formerly e0393df933349eb52fcdfd39a4061a7a5b322b7f] [formerly 1107b444c09bc649043839361b320e31e0781a12 [formerly 1107b444c09bc649043839361b320e31e0781a12 [formerly dc5e934c1916e06d007de32f9ec0eb6257cec116]]]]
Former-commit-id: 23ba3ab8effbf7e87bda96857490dd3bb6640ac5 [formerly 70366e6531a431656415d3020c5d2b5e7a278d9b] [formerly f890a567a7ea1560212f0b3f01ea348d24d49779 [formerly fca801bdf175800e945d7f7f579b34820dc9f800]]
Former-commit-id: 93e06cb86f7a34f602e46630d06ab7d4441643ba [formerly 943a3ed26a7122429eb84cb7aa3a4e32755b25b9]
Former-commit-id: c167d0ae35765e92821d3fe34a27479d44dd9980
Former-commit-id: 390f7c00b7ca4e9b3f4df3c644ce3090a4a379fc

--- @client/third_party_auth.coffee
+++ @client/third_party_auth.coffee
@@ -17,7 +17,7 @@
     @popup = @openPopupWindow(url)

   pollLoginPopup : ->
-    if @popup? && @popup.current_user_hash?
+    if @popup? && @popup.document && window.document && window.document.domain == @popup.document.domain && @popup.current_user_hash?
       @callback @popup.current_user_hash
       @popup.close()
       @popup = null


Make it clearer that the error is in docs and can be ignored otherwise

--- js/outer/docs.coffee
+++ js/outer/docs.coffee
@@ -57,7 +57,7 @@
             @categories[slug] = @articles[slug]

       catch error
-        console.log "error generating #{slug}: #{error}"
+        console.log "error generating doc #{slug}: #{error} (it might not be a doc)"
         ## meaning: can't be rendered without more context. Should never be true of docs!

     # iterate through the articles, and update the hierarchy


stop using old popover binding

stop using old popover binding

--- js/inner.coffee
+++ js/inner.coffee
@@ -884,7 +884,6 @@

     $.getJSON url, (data) =>
       @repos((new Repo r for r in data))
-      $(".invite").popover()
       @loadingRepos(false)

 class Plan extends Obj


Make multi-line regex test more strict

Make multi-line regex test more strict

Regex patterns can only match across line boundaries if they contain a
carriage return, a newline, or a negated character class.

--- src/helpers.coffee
+++ src/helpers.coffee
@@ -2,7 +2,7 @@

 SpliceArrayChunkSize = 100000

-MULTI_LINE_REGEX_REGEX = /[\r\n\[]/
+MULTI_LINE_REGEX_REGEX = /\r|\n|^\[\^|[^\\]\[\^/

 module.exports =
   spliceArray: (originalArray, start, length, insertedArray=[]) ->


refactor

refactor

--- test/interpolation.coffee
+++ test/interpolation.coffee
@@ -76,23 +76,44 @@
 test 'string interpolating escaped braced variable', ->
 	assert.compileTo '''
 		figcaption {
-			content: "Figure {\\$chapter}-12";
-		}
-	''', '''
-		figcaption {
-			content: "Figure {\\$chapter}-12";
+			content: "Figure \\{\\$chapter}-12";
+		}
+	''', '''
+		figcaption {
+			content: "Figure \\{\\$chapter}-12";
 		}
 	'''

 test 'string interpolating braced identifier', ->
 	assert.compileTo '''
-		$chapter = 4;
 		figcaption {
 			content: "Figure {chapter}-12";
 		}
 	''', '''
 		figcaption {
-			content: "Figure {chapter}-12";
+			content: "Figure chapter-12";
+		}
+	'''
+
+test 'string interpolating addition', ->
+	assert.compileTo '''
+		figcaption {
+			content: "Figure { 5 + 1 }-12";
+		}
+	''', '''
+		figcaption {
+			content: "Figure 6-12";
+		}
+	'''
+
+test 'string interpolating string', ->
+	assert.compileTo '''
+		figcaption {
+			content: "Figure { "#6" }-12";
+		}
+	''', '''
+		figcaption {
+			content: "Figure #6-12";
 		}
 	'''

@@ -142,7 +163,7 @@
 		.icon-$name {
 			float: left;
 		}
-	''', {line: 6, column: 7}
+	''', { line: 6, column: 7 }

 test 'identifier interpolating multiple variables', ->
 	assert.compileTo '''
@@ -238,13 +259,13 @@
 	assert.failAt '''
 		$sel = '#';
 		$sel {}
-	''', {line: 2, column: 1}
+	''', { line: 2, column: 1 }

 test 'disallow selector interpolating top-level & selector', ->
 	assert.failAt '''
 		$sel = '&';
 		$sel {}
-	''', {line: 2, column: 1}
+	''', { line: 2, column: 1 }

 test 'complex selector interpolating selector', ->
 	assert.compileTo '''
@@ -278,7 +299,7 @@
 		body $sel {
 			width: auto;
 		}
-	''', {line: 2, column: 6}
+	''', { line: 2, column: 6 }

 test 'complex selector interpolating & selector nested in selector', ->
 	assert.compileTo '''
@@ -299,7 +320,7 @@
 	assert.failAt '''
 		$sel = 'div, p';
 		$sel {}
-	''', {line: 2, column: 1}
+	''', { line: 2, column: 1 }

 test 'selector interpolating identifier', ->
 	assert.compileTo '''
@@ -399,4 +420,4 @@
 		@media $qry {
 			body {}
 		}
-	''', {line: 2, column: 8}+	''', { line: 2, column: 8 }


[skip] Made BasicCollectionRenderer the default renderer for CollectionEditor (1m)

--- vendor/assets/javascripts/neat/collection_editor.coffee
+++ vendor/assets/javascripts/neat/collection_editor.coffee
@@ -19,9 +19,8 @@
   useKeyboardToChangeRows: true
   cancelOnEsc: true
   saveOnEnter: true
-  renderer: Neat.PaginatedCollectionRenderer
-  rendererOptions:
-    pageSize: 30
+  renderer: Neat.BasicCollectionRenderer
+  rendererOptions: {}

   initialize: ->
     @keyDownHandlers = {}


Add --quiet option to install command

Add --quiet option to install command

--- src/install.coffee
+++ src/install.coffee
@@ -37,7 +37,8 @@
     """
     options.alias('h', 'help').describe('help', 'Print this usage message')
     options.alias('d', 'dev').describe('dev', 'Install dev dependencies of atom packages being installed')
-    options.alias('s', 'silent').boolean('silent').describe('silent', 'Minimize output')
+    options.alias('s', 'silent').boolean('silent').describe('silent', 'Set the npm log level to silent')
+    options.alias('q', 'quiet').boolean('quiet').describe('quiet', 'Set the npm log level to warn')

   showHelp: (argv) -> @parseOptions(argv).showHelp()

@@ -63,6 +64,7 @@
     installArgs.push("--target=#{config.getNodeVersion()}")
     installArgs.push('--arch=ia32')
     installArgs.push('--silent') if options.argv.silent
+    installArgs.push('--quiet') if options.argv.quiet

     if vsArgs = @getVisualStudioFlags()
       installArgs.push(vsArgs)
@@ -120,6 +122,7 @@
     installArgs.push("--target=#{config.getNodeVersion()}")
     installArgs.push('--arch=ia32')
     installArgs.push('--silent') if options.argv.silent
+    installArgs.push('--quiet') if options.argv.quiet

     if vsArgs = @getVisualStudioFlags()
       installArgs.push(vsArgs)


fix for test broken by node upgrade

fix for test broken by node upgrade

--- test/songbird.spec.coffee
+++ test/songbird.spec.coffee
@@ -194,10 +194,10 @@

       it 'are supported', (done) ->
         f.promise().then (value) ->
-          expect(value).to.equal '[object global].f()'
-          done()
-
-      it 'are supposed using call() or apply()', (done) ->
+          expect(value).to.contain '.f()'
+          done()
+
+      it 'are supported using call() or apply()', (done) ->
         f.promise.call(10).then (value) ->
           expect(value).to.equal '10.f()'
           done()


Don't need ease.

Don't need ease.

--- js/flight.coffee
+++ js/flight.coffee
@@ -99,7 +99,6 @@
       window.setTimeout =>
         @currentPanel.style.webkitTransition = "#{@speed} ease"
         @currentPanel.style.webkitTransform = "translateX(-100%)"
-
       , 10

     # shortern the delay here to stop a gap appearing in android
@@ -110,7 +109,6 @@
     , 5

   finishTransition: =>
-    @currentPanel.style.webkitTransition = "0s ease"
     @currentPanel.style.left = "-100%"
     @currentPanel.style.display = "none"
     @removeClass(@currentPanel, 'visible')


ui: better focus handling

ui: better focus handling

--- ui/index.coffee
+++ ui/index.coffee
@@ -131,6 +131,11 @@
       idx = Math.max(idx - 1, 0)
       $($nodes.get(idx)).focus()

+    'focus:active .ItemView': (e) ->
+      $nodes = $('.ItemView', this.getDOMNode())
+      $nodes.not(e.target).attr('tabindex', -1)
+      $(e.target).attr('tabindex', 0)
+
   render: ->
     children = if this.props.model.items.length > 0
       this.props.model.items.map (item) => ItemView {item}
@@ -166,16 +171,20 @@
     if e.keyCode == 38 or e.keyCode == 75
       $node.trigger('focus:prev', $node)

+  onFocus: ->
+    $node = $ this.getDOMNode()
+    $node.trigger('focus:active', $node)
+
   render: ->
     item = this.props.item
     mainLink = if this.props.externalLink then item.uri else item.screenURL()
     cls = "ItemView #{this.props.className or ''}"
-    `

+    `

         {this.renderIcon()}
-        {item.title &&
{item.title}
}
+        {item.title &&
{item.title}
}
         {item.post && this.props.full &&
{item.post}
}
-        {item.uri && {item.uri && url.parse(item.uri).hostname}}
+        {item.uri && {item.uri && url.parse(item.uri).hostname}}
         <Timestamp class="created" relative value={item.created} />

by {username(item.creator)}

@@ -250,6 +259,11 @@
       idx = $nodes.index e.target
       idx = Math.max(idx - 1, 0)
       $($nodes.get(idx)).focus()
+
+    'focus:active .ItemView': (e) ->
+      $nodes = $('.ItemView', this.getDOMNode())
+      $nodes.not(e.target).attr('tabindex', -1)
+      $(e.target).attr('tabindex', 0)

   renderAddCommentButton: ->
     unless this.state?.commentEditorShown


Fix for unloaded Immersion section.

Fix for unloaded Immersion section.

--- packages/pixelartacademy-season1-episode0/chapter2/sections/immersion/immersion.coffee
+++ packages/pixelartacademy-season1-episode0/chapter2/sections/immersion/immersion.coffee
@@ -63,7 +63,7 @@

   initializeScript: ->
     @setThings
-      operator: @options.listener.avatars.operator
+      operator: @options.listener.avatars?.operator

     @setCallbacks
       ActivateHeadset: (complete) =>


debug

debug

--- JSKit/04_JSMenuView.coffee
+++ JSKit/04_JSMenuView.coffee
@@ -45,7 +45,7 @@
             select: (event, ui) =>
                 if (@_userInteractionEnabled == false)
                     return
-                JSLog(ui.item)
+                JSLog(ui)
                 item = ui.item.context.textContent
                 @selectMenuItem(item)
                 @closeMenu()


Make search bar work outside of landing. Fixes #219

--- client/header.coffee
+++ client/header.coffee
@@ -18,6 +18,7 @@
     naturalQueryChange $(template.findAll '.search-input').val()

   'change .search-input': (e, template) ->
+    Meteor.Router.to Meteor.Router.indexPath()
     Session.set 'searchActive', true
     Session.set 'searchFocused', true
     naturalQueryChange $(template.findAll '.search-input').val()
@@ -27,12 +28,14 @@

     # If user focused with tab or pressed some other non-content key we don't want to activate the search
     if val
+      Meteor.Router.to Meteor.Router.indexPath()
       Session.set 'searchActive', true
       Session.set 'searchFocused', true

     naturalQueryChange val

   'paste .search-input': (e, template) ->
+    Meteor.Router.to Meteor.Router.indexPath()
     Session.set 'searchActive', true
     Session.set 'searchFocused', true
     naturalQueryChange $(template.findAll '.search-input').val()


QUALITY: Set default task in task viewer to first in task list

--- src/app/units/states/tasks/viewer/viewer.coffee
+++ src/app/units/states/tasks/viewer/viewer.coffee
@@ -21,5 +21,5 @@

 .controller('TaskViewerStateCtrl', ($scope, Unit) ->
   $scope.unitTasks = $scope.unit.task_definitions
-  $scope.selectedTask = null
+  $scope.selectedTask = $scope.unitTasks[0]
 )


Capture errors.

Capture errors.

--- client/person.coffee
+++ client/person.coffee
@@ -80,20 +80,18 @@
         sha256 = hash.finalize()

         Meteor.call 'createPublication', file.name, sha256, (err, publicationId) ->
-          if err
-            throw err
-          else
-            console.log publicationId
-            meteorFile = new MeteorFile file
-            meteorFile.name = publicationId + '.pdf'
-            meteorFile.upload file, 'uploadPublication',
-              size: 128 * 1024
-            , (err) ->
-              if err
-                throw err
-              else
-                Meteor.call 'finishPublicationUpload', publicationId
-                console.log 'Upload successful'
+          throw err if err
+          console.log publicationId
+          meteorFile = new MeteorFile file
+          meteorFile.name = publicationId + '.pdf'
+          meteorFile.upload file, 'uploadPublication',
+            size: 128 * 1024
+          , (err) ->
+            throw err if err
+
+            Meteor.call 'finishPublicationUpload', publicationId, (err) ->
+              throw err if err
+              console.log 'Upload successful'

       reader.readAsArrayBuffer file


triggering 'action:start' and 'action:end' events in router

--- client/router.coffee
+++ client/router.coffee
@@ -16,7 +16,7 @@
   initialize: (options) ->
     @app = options.app
     @initRoutes()
-    @on 'action', @trackAction
+    @on 'action:start', @trackAction
     @app.on 'reload', @renderView

     @appView = new AppView({@app})
@@ -43,17 +43,21 @@

   getHandler: (pattern, route) ->
     (paramsArray...) =>
-      @trigger 'action', route
+      @trigger 'action:start', route
       if firstRender
         firstRender = false
         views = BaseView.attach(@app)
         @currentView = @getMainView(views)
+        @trigger 'action:end', route
       else
         params = @getParamsHash(pattern, paramsArray)
         handler = @getController(route.controller)[route.action]
         throw new Error("Missing action \"#{route.action}\" for controller \"#{route.controller}\"") unless handler
         handler = @authenticationFilter(handler, route)
-        handler.call(@, params, @render)
+        renderCallback = (args...) =>
+          @render(args...)
+          @trigger 'action:end', route
+        handler.call(@, params, renderCallback)

   # Hmm, there's probably a better way to do this.
   # By default, expect that there's only a single, main


thingy: Add support for after_construction functions in initializers.

--- client/thingy.coffee
+++ client/thingy.coffee
@@ -305,6 +305,8 @@
     if thingyverse._.per_thingy[ id ]?
       throw( "Heron.Thingy: Thingy #{id} already exists." )

+    after_construction_functions = []
+
     @_ =
       thingyverse: thingyverse
       typename:    typename
@@ -315,11 +317,16 @@
         if @_.removed
           throw( "Heron.Thingy: Trying to do something with removed thingy #{@id()}" )

+    @after_construction = ( f ) -> after_construction_functions.push( f )
     @_.delegate = @_.type_data.initializer.call( this, attrs, local_data )
+    delete @after_construction

     thingyverse._.per_thingy[ id ] =
       thingy:   this
       delegate: @_.delegate
+
+    for f in after_construction_functions
+      f.call( this )

   # ID.
   # @return [String] ID.
@@ -552,7 +559,12 @@
 #
 # Every initializer should be a function which takes two arguments -- `attrs`
 # and `local_data` -- and returns a delegate.  The `this` of the initializer
-# will be set to the thingy object.
+# will be set to the thingy object.  Initializers are run before the thingy
+# is fully created, so operations such as {#get} and {#set} will not work
+# (there is no delegate yet).  To aid with this, a special member
+# `@after_construction` is defined while the initializer is being called.
+# `@after_construction` takes a single parameter, a function, and will call
+# that function once the thingy is created.  It may be called multiple times.
 #
 # As convenience and for future compatibility, {Heron.Thingyverse} defines
 # {#batch}, {#begin}, and {#finish}, to provide batch semantics.  At present,
@@ -724,7 +736,7 @@
   #
   # All of the above occurs in a batch.
   #
-  # @param [String] typename   Name of a Thingy type as definede by {#define}.
+  # @param [String] typename   Name of a Thingy type as defined by {#define}.
   # @param [Object] attrs      Initial attributes.
   # @param [Object] local_data Local data.
   # @return [thingy] Newly created thingy.


Changed: Shorter pause for break-time (Atom plugin)

--- dotfiles/atom/config.cson
+++ dotfiles/atom/config.cson
@@ -161,6 +161,6 @@
   "atom-beautify":
     _analyticsUserId: "c8ae3b6d-a781-4233-9780-a96292c2afa8"
   "break-time":
-    microinterval: 25
+    microinterval: 30
     intervalcount: 1
-    break: 5
+    break: 1


Use safe colors

Use safe colors

--- lib/debug.coffee
+++ lib/debug.coffee
@@ -115,8 +115,9 @@

 # For the Browser Process
 class DebuggerBrowser extends DebuggerBase
+  colors = require 'colors/safe'
   _getLogFunc: (prefix) ->
-    console.log.bind console, "#{prefix} %s".cyan
+    console.log.bind console, colors.cyan("#{prefix} %s")

   init: ->
     return Debug.wrap('Already initialised') if @isInitialised()


add title attr to icon

add title attr to icon

--- example/examTabPane2.cjsx
+++ example/examTabPane2.cjsx
@@ -63,7 +63,7 @@
   render: ->
     icons = @iconNames.map (name, index) ->

-        <Button glyph={name} text={name} />
+        <Button glyph={name} title={name} text={name} />