Cleanup, encapsulation

Cleanup, encapsulation

--- layout.coffee
+++ layout.coffee
@@ -184,52 +184,58 @@
     # console.log 'onunload', localStorage[cacheKey]
   )

-  compileCache.bindings ?= {}
-  bindingCache = compileCache.bindings
-  scopes = null
-  propertyBindings =
-    find: (expression) ->
+  findPropertyBindings = do ->
+    compileCache.bindings ?= {}
+    bindingCache = compileCache.bindings
+    scopes = null
+    propertyBindings =
+      MemberExpression: (n) ->
+        # grab the property name
+        name = n.property.name
+
+        # remove the property so we can compute the rest of the expression
+        n = n.object
+
+        # push the scope onto the list
+        scopes.push({binding: acorn.stringify(n), property: name})
+        # console.log 'MemberExpression', name, acorn.stringify n
+        return true
+
+    (expression) ->
       return bindingCache[expression] if expression of bindingCache
       ast = acorn.parse(expression)
       scopes = []
-      acorn.walkDown(ast, @)
+      acorn.walkDown(ast, propertyBindings)
       bindingCache[expression] = scopes
       # console.log compileCache.bindings
       # return scopes
-    MemberExpression: (n) ->
-      # grab the property name
-      name = n.property.name
-
-      # remove the property so we can compute the rest of the expression
-      n = n.object
-
-      scopes.push({binding: acorn.stringify(n), property: name})
-      # console.log 'MemberExpression', name, acorn.stringify n
-      return true
-  # propertyBindings.find = _.memoize(propertyBindings.find)
-
-  compileCache.script ?= {'coffee': {}}
-  coffeeCache = compileCache.script.coffee
-  compilermappings =
-    coffee: (script) ->
-      if script of coffeeCache
-        # console.log 'cache hit', script
-        return coffeeCache[script]
-      if not window.CoffeeScript
-        console.warn 'missing coffee-script.js include'
-        return
-      # console.log 'compiling coffee-script', script
-      coffeeCache[script] = CoffeeScript.compile(script, bare: true) if script
-      # console.log 'compiled coffee-script', script
-      # console.log coffeeCache
-      # return coffeeCache[script]
-
-  transformScript = (script='', compiler) ->
-    return script unless compiler of compilermappings
-    script = compilermappings[compiler](script)
-
-  # cache scripts to speed up instantiation
+    # propertyBindings.find = _.memoize(propertyBindings.find)
+
+  # transforms a script to javascript using a runtime compiler
+  transformScript = do ->
+    compileCache.script ?= {'coffee': {}}
+    coffeeCache = compileCache.script.coffee
+    compilers =
+      coffee: (script) ->
+        if script of coffeeCache
+          # console.log 'cache hit', script
+          return coffeeCache[script]
+        if not window.CoffeeScript
+          console.warn 'missing coffee-script.js include'
+          return
+        # console.log 'compiling coffee-script', script
+        coffeeCache[script] = CoffeeScript.compile(script, bare: true) if script
+        # console.log 'compiled coffee-script', script
+        # console.log coffeeCache
+        # return coffeeCache[script]
+
+    (script='', name) ->
+      return script unless name of compilers
+      compilers[name](script)
+
+  # cache compiled scripts to speed up instantiation
   scriptCache = {}
+  # compile a string into a function
   compileScript = (script='', args=[]) ->
     key = script + args.join()
     return scriptCache[key] if key of scriptCache
@@ -240,28 +246,21 @@
     catch e
       console.error('failed to compile', args, script, e)

-  # generate a callback for an event expression in a way that preserves scope, e.g. on_x="console.log(value, this, ...)"
-  eventCallback = (name, script, scope, fnargs=['value']) ->
-    # console.log 'binding to event expression', name, script, scope, fnargs
-    js = compileScript(script, fnargs)
-    ->
-      if name of scope
-        args = [scope[name]]
-      else
-        args = arguments
-      # console.log 'event callback', name, args, scope, js
-      js.apply(scope, args)
-
-
-  # this _must_ be called after initFromElement() to allow late binding constraints
-  constraintScopes = []
-  initConstraints = ->
-    for constraint in constraintScopes
-      constraint._bindConstraints()
-    constraintScopes = []

   class Node extends Eventable
     matchConstraint = /\${(.+)}/
+
+    # generate a callback for an event expression in a way that preserves scope, e.g. on_x="console.log(value, this, ...)"
+    eventCallback = (name, script, scope, fnargs=['value']) ->
+      # console.log 'binding to event expression', name, script, scope, fnargs
+      js = compileScript(script, fnargs)
+      ->
+        if name of scope
+          args = [scope[name]]
+        else
+          args = arguments
+        # console.log 'event callback', name, args, scope, js
+        js.apply(scope, args)

     constructor: (el, attributes = {}) ->
       @subnodes = []
@@ -317,7 +316,7 @@
         bindings: {}

       bindings = @constraints[property].bindings
-      scopes = propertyBindings.find(expression)
+      scopes = findPropertyBindings(expression)
       # console.log 'found scopes', scopes
       for scope in scopes
         bindexpression = scope.binding
@@ -570,10 +569,15 @@
       attributes[i.name] = i.value
     attributes

+  # a list of constraint scopes gathered at init time
+  constraintScopes = []
   # initialize an element
   initFromElement = (el) ->
     initElement(el)
-    initConstraints()
+    # init constraints
+    for constraint in constraintScopes
+      constraint._bindConstraints()
+    constraintScopes = []

   specialtags = ['handler', 'method', 'attribute', 'setter']
   # recursively init classes based on an existing element
@@ -599,11 +603,8 @@
     children = (child for child in el.childNodes when child.nodeType == 1)

     if tagname is 'class'
-      # defer creation of children
+      # defer creation of children if selected by init()
       for child in children
-        # console.log 'creating child', tagname, child.localName, child, parent
-        # console.dir(child)
-        # console.log 'defer', child
         child.$defer = true

     processSpecialTags(el, attributes, attributes.type) unless tagname is 'class'


remove a service i dont use

remove a service i dont use

--- src/js/controllers/maps.coffee
+++ src/js/controllers/maps.coffee
@@ -1,5 +1,5 @@
 angular.module('app.controllers')
-  .controller 'MapsIndexController', ($scope, $routeParams, $location, godoClient, $modal) ->
+  .controller 'MapsIndexController', ($scope, $routeParams, $location, $modal) ->
     directionsService = directionsDisplay = autocomplete = map = marker = null

     $scope.tripStart = moment(new Date(2014, 12-1, 19))


descending and building tree structure

descending and building tree structure

--- lib/class/nest.coffee
+++ lib/class/nest.coffee
@@ -1,19 +1,36 @@
 _ = require("lodash")
 promise = require('../promise')
 test = require('../helpers').test
+
+indent = (amount, text) ->
+  space = _([0..amount]).map((i) -> "  ").join('')
+  console.log(space + text)

 module.exports = (rules, strm) ->
   do (rules, strm) ->
     p = promise(['atom', 'error', 'end'])
     strm.onError((error) -> p.error(error))
     strm.onEnd(() -> p.end())
-    state = _([])
+    stack = []

     strm.onAtom((atom) ->
       if test(rules.starters, atom)
-        state.push(_(atom).assign(child:[]))
+        atom = _.assign({}, atom, des:[])
+        if stack.length > 0
+          asc = _.last(stack)
+          asc.des.push(atom)
+        stack.push(atom)
+
       else if test(rules.enders, atom)
-        p.atom(state.pop())
-      else if state.length > 0
-        state.last().child.push(atom))
+        asc = stack.pop()
+        if not asc? or not rules.pairs[asc.ch] == atom.ch
+          throw "Unbalanced pairs at #{asc.r}:#{asc.c} and #{atom.r}:#{atom.c}" if asc?
+          throw "Unbalanced pairs at #{atom.r}:#{atom.c}" if not asc?
+        asc.des.push(atom)
+        if stack.length == 0
+          p.atom(asc)
+      else
+        if stack.length > 0
+          asc = _.last(stack)
+          asc.des.push(atom))
     p


Reset

Reset

--- mobile/app/mopidy/mopidy.coffee
+++ mobile/app/mopidy/mopidy.coffee
@@ -180,12 +180,10 @@
       buttons = [
         text: 'Queue'
         action: -> mopidy.queueTrack track
+      ,
+        text: 'Play all from here'
+        action: -> mopidy.setTracklist $scope.tracks, track
       ]
-
-      if $scope.isSet
-        buttons.push
-          text: 'Play all from here'
-          action: -> mopidy.setTracklist $scope.tracks, track

       $ionicActionSheet.show
         buttons: buttons
@@ -345,7 +343,6 @@
   replace: true
   scope:
     tracks: '='
-    isSet: '@'
   templateUrl: 'mopidy/templates/tracklist.html'
   controller: 'TracklistCtrl'


Disable continue button when saving

Disable continue button when saving

A task step should not be continued while it's state is saving or
loading. The earlier code was checking the isSaving state, but wasn't
passing it onto the AsyncButton.

--- src/components/task-step/step-mixin.cjsx
+++ src/components/task-step/step-mixin.cjsx
@@ -13,20 +13,24 @@

   renderContinueButton: ->
     return null if @hideContinueButton?()
-    isWaiting = TaskStepStore.isLoading(@props.id)
-    isSaving = TaskStepStore.isSaving(@props.id)
-    isFailed = TaskStepStore.isFailed(@props.id)
+
     # if this is the last step completed and the view is read-only,
     # then you cannot continue, and this will override @isContinueEnabled
-    cannotContinue = not StepPanel.canContinue(@props.id)
+    waitingText = switch
+      when TaskStepStore.isLoading(@props.id) then "Loading…"
+      when TaskStepStore.isSaving(@props.id)  then "Saving…"
+      else null
+
+    cannotContinue = not StepPanel.canContinue(@props.id) or not @isContinueEnabled?()

     <AsyncButton
       bsStyle='primary'
       className='continue'
       onClick={@onContinue}
-      disabled={not @isContinueEnabled?() or cannotContinue}
-      isWaiting={isWaiting}
-      isFailed={isFailed}
+      disabled={cannotContinue}
+      isWaiting={!!waitingText}
+      waitingText={waitingText}
+      isFailed={TaskStepStore.isFailed(@props.id)}
       >
       {@continueButtonText?() or 'Continue'}
     </AsyncButton>


better flow tooltip text

better flow tooltip text

--- frontend/bubbles/bubbles-aiddata.coffee
+++ frontend/bubbles/bubbles-aiddata.coffee
@@ -38,16 +38,19 @@

 bubbleTooltip = (d) ->
   "#{d.name}" +
-  " in #{state.selMagnAttr()}" +
+  #" in #{state.selMagnAttr()}" +
   (if d.outbound > 0 then "
donated #{magnitudeFormat(d.outbound)}" else "") +
   (if d.inbound > 0 then "
received #{magnitudeFormat(d.inbound)}" else "")

 flowTooltip = (d) ->
+  recipient = d.target.data[conf.nodeLabelAttr]
+  donor = d.source.data[conf.nodeLabelAttr]
+
   "#{magnitudeFormat(d.data[state.selMagnAttr()])}
"+
-  "were donated to #{d.target.data[conf.nodeLabelAttr]}
"+
-  "by #{d.source.data[conf.nodeLabelAttr]}
"+
-  "in #{state.selMagnAttr()}
"
+  "were donated to #{recipient}
"+
+  (if donor.length > 20 then "by #{donor}" else "by #{donor}")
+  #"
 in #{state.selMagnAttr()}
"

 mapProj = winkelTripel()


Prefer relative URLs to absolute ones

Prefer relative URLs to absolute ones

Absolute URLs are bound to the base URL '/', which compromises deployment in
subdirectories (among other things).

--- lib/views/index.coffee
+++ lib/views/index.coffee
@@ -5,10 +5,10 @@
         title "#{if @title then @title+' - ' else ''}Concrete"
         meta(name: 'description', content: @desc) if @desc?
         link rel: 'stylesheet', href: 'stylesheets/app.css'
-        script src: '/js/jquery-1.6.2.min.js'
-        script src: '/js/coffeekup.js'
-        script src: '/concrete.js'
-
+        script src: 'js/jquery-1.6.2.min.js'
+        script src: 'js/coffeekup.js'
+        script src: 'concrete.js'
+
     body ->
         header ->
             hgroup ->


incremental changes to `clear cart` functionality.

--- assets/coffeescript/pupil_store.coffee
+++ assets/coffeescript/pupil_store.coffee
@@ -108,7 +108,7 @@
       if LocalStorage.length() > 0
         # remove empty cart text
         $("#Cart-empty").hide()
-        $("#Cart-table").show()
+        $(".Cart-container").show()
         for k,v of LocalStorage.dict()
           # product, id, specs, price, quantity
           newRow = "<div class='Cart-rowContainer'>
@@ -147,7 +147,7 @@
         # hide cart table
         # display message with link back to the store
         # $("#Cart-empty").show()
-        $("#Cart-table").hide()
+        $(".Cart-container").hide()

   eventRemoveCartItem: ->
     if $(@cartPage).length > 0


Only destroy attentionSpan when it exists

--- js-library/app/js/jail_iframe/classes/icon_buttons.coffee
+++ js-library/app/js/jail_iframe/classes/icon_buttons.coffee
@@ -151,7 +151,7 @@

   destroy: ->
     @_iconButton.destroy()
-    @_attentionSpan.destroy()
+    @_attentionSpan?.destroy()
     @_robustParagraphHover?.destroy()
     FactlinkJailRoot.off 'factlink.factsLoaded factlinkAdded', @_destroyUnlessValid
     FactlinkJailRoot.off 'hideAllParagraphButtons', @_onHideAllParagraphButtons


fix links

fix links

--- assets/js/view.coffee
+++ assets/js/view.coffee
@@ -113,7 +113,7 @@
   if options.charclick
     for x in line
       x.renderOptions.text = x.char
-      if not x.renderOptions.onclick
+      if not x.renderOptions.href
         x.renderOptions.onclick = options.charclick.bind @, x.column
       renderSpec.push x.renderOptions
       if x.break


Atom updates

Atom updates

--- atom/atom/config.cson
+++ atom/atom/config.cson
@@ -65,14 +65,15 @@
     projectHome: "/Users/Felix/Development"
     telemetryConsent: "limited"
     themes: [
-      "one-dark-ui"
-      "ariake-dark-syntax"
-    ]
-    useCustomTitleBar: true
+      "dracula-ui"
+      "dracula-theme"
+    ]
+    titleBar: "custom-inset"
   docblockr: {}
   editor:
     autoIndentOnPaste: false
     fontFamily: "Menlo"
+    fontSize: 12
     invisibles:
       space: "•"
       tab: "›"
@@ -104,10 +105,6 @@
     defaultPreset: "airbnb"
   linter:
     clearOnChange: true
-    lintOnFly: false
-    showErrorPanel: false
-    showErrorTabFile: false
-    showErrorTabProject: false
     showInfoMessages: true
     subtleLinterErrors: [
       "jshint"


dev (grunt commit r815)

dev (grunt commit r815)

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -66,7 +66,7 @@
 				options:
 					banner: '<%= meta.camouflage %>' + '\n\n'
 				src: [
-					DEST
+					CLIENT
 				]
 				dest: CLIENT
 		concat:


Grunt changes for "ide" folder

Grunt changes for "ide" folder

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -106,6 +106,9 @@
       app:
         options: compress: true, import: [ '__init']
         files: '<%=meta.bower%>/<%=pkg.name%>.app.css': '<%=source.stylus.app%>'
+      ide:
+        options: compress: true, import: [ '__init']
+        files: '<%=meta.bower%>/<%=pkg.name%>.ide.css': '<%=source.stylus.ide%>'
       theme:
         options: compress: false, import: [ '__init']
         files: '<%=meta.bower%>/<%=pkg.name%>.app.theme.css': '<%=source.stylus.theme%>'
@@ -131,6 +134,9 @@
       app:
         files: ['<%= source.app %>']
         tasks: ['concat:app', 'coffee:app', 'uglify:app', 'notify:app']
+      ide:
+        files: ['<%= source.ide %>']
+        tasks: ['concat:ide', 'coffee:ide', 'uglify:ide', 'notify:ide']
       stylus_app:
         files: ['<%= source.stylus.app %>']
         tasks: ['stylus:app']


Add shuati to pattern

Add shuati to pattern

--- scripts/example.coffee
+++ scripts/example.coffee
@@ -104,6 +104,6 @@
   # robot.respond /sleep it off/i, (res) ->
   #   robot.brain.set 'totalSodas', 0
   #   res.reply 'zzzzz'
-  robot.hear /(question|题目)/i, (msg) ->
+  robot.hear /(question|题目|刷题)/i, (msg) ->
     number = Math.floor(Math.random() * 600) + 1
     msg.send "#{number}"


no message

no message

--- scripts/example.coffee
+++ scripts/example.coffee
@@ -15,8 +15,3 @@
     idname = robot.adapter.client.rtm.dataStore.getUserByName('raoka0000')
     msg.send "テスト中だよ #{idname}"

-  robot.adapter.client?.rtm?.on? 'raw_message', (msg) ->
-    message = JSON.parse msg
-    return unless message.type is 'message'
-    msg.send "成功"
-


fixed copy/paste error

fixed copy/paste error

--- lib/ObjectTemplate.coffee
+++ lib/ObjectTemplate.coffee
@@ -83,7 +83,7 @@

     # if context is an array, just add the value
     if sysmo.isArray(context)
-      context.push(formatted.value)
+      context.push(value)
       return context

     existing = context[key]


destructuring assignment

destructuring assignment

--- scripts/tflobby.coffee
+++ scripts/tflobby.coffee
@@ -25,27 +25,27 @@
 # Author:
 #   skibz

-commands = require('../src/commands')
+{ lifecycle, public, rcon, admin } = require('../src/commands')

 module.exports = (robot) ->

-  robot.enter (msg) -> commands.lifecycle.onEnter.call(robot, msg)
-  robot.leave (msg) -> commands.lifecycle.onLeave.call(robot, msg)
+  robot.enter (msg) -> lifecycle.onEnter.call(robot, msg)
+  robot.leave (msg) -> lifecycle.onLeave.call(robot, msg)

-  robot.respond /(add|add (me|.*))/i, (msg) -> commands.lifecycle.add.call(robot, msg)
-  robot.respond /(rem|rem (me|.*))/i, (msg) -> commands.lifecycle.rem.call(robot, msg)
+  robot.respond /(add|add (me|.*))/i, (msg) -> lifecycle.add.call(robot, msg)
+  robot.respond /(rem|rem (me|.*))/i, (msg) -> lifecycle.rem.call(robot, msg)

-  robot.respond /(status|games)/i, (msg) -> commands.public.status.call(robot, msg)
-  robot.respond /(previous|lastgame)/i, (msg) -> commands.public.previous.call(robot, msg)
-  robot.respond /(top|today) (maps|players)/i, (msg) -> commands.public.top.call(robot, msg)
+  robot.respond /(status|games)/i, (msg) -> public.status.call(robot, msg)
+  robot.respond /(previous|lastgame)/i, (msg) -> public.previous.call(robot, msg)
+  robot.respond /(top|today) (maps|players)/i, (msg) -> public.top.call(robot, msg)

-  robot.respond /rcon (say|message|msg) (.*) on (.*)/i, (msg) -> commands.rcon.rconSay.call(robot, msg)
-  robot.respond /rcon send (list|the list|roster|players) on (.*)/i, (msg) -> commands.rcon.rconRoster.call(robot, msg)
-  robot.respond /rcon (change map|changelevel|map) on (.*) to (.*)/i, (msg) -> commands.rcon.rconMap.call(robot, msg)
+  robot.respond /rcon (say|message|msg) (.*) on (.*)/i, (msg) -> rcon.rconSay.call(robot, msg)
+  robot.respond /rcon send (list|the list|roster|players) on (.*)/i, (msg) -> rcon.rconRoster.call(robot, msg)
+  robot.respond /rcon (change map|changelevel|map) on (.*) to (.*)/i, (msg) -> rcon.rconMap.call(robot, msg)

-  robot.respond /((sg|new)|(sg|new) (.*)|(sg|new) random (.*) map)/i, (msg) -> commands.admin.sg.call(robot, msg)
-  robot.respond /(cg|kill)/i, (msg) -> commands.admin.cg.call(robot, msg)
-  robot.respond /format (.*)/i, (msg) -> commands.admin.format.call(robot, msg)
-  robot.respond /(random (.*) map|map (.*))/i, (msg) -> commands.admin.map.call(robot, msg)
-  robot.respond /server (.*)/i, (msg) -> commands.admin.server.call(robot, msg)
-  robot.respond /change (.*) to (.*)/i, (msg) -> commands.admin.change.call(robot, msg)
+  robot.respond /((sg|new)|(sg|new) (.*)|(sg|new) random (.*) map)/i, (msg) -> admin.sg.call(robot, msg)
+  robot.respond /(cg|kill)/i, (msg) -> admin.cg.call(robot, msg)
+  robot.respond /format (.*)/i, (msg) -> admin.format.call(robot, msg)
+  robot.respond /(random (.*) map|map (.*))/i, (msg) -> admin.map.call(robot, msg)
+  robot.respond /server (.*)/i, (msg) -> admin.server.call(robot, msg)
+  robot.respond /change (.*) to (.*)/i, (msg) -> admin.change.call(robot, msg)


Anims: Better hurt animation

Anims: Better hurt animation

--- client/app/js/views/battle_view.coffee
+++ client/app/js/views/battle_view.coffee
@@ -303,7 +303,8 @@
     $attacker = @$sprite(player, slot)
     $defender = @$sprite(targetPlayer, targetSlot)
     [a, d] = [$attacker.position(), $defender.position()]
-    scale = (if a.top < d.top then 1.3 else 1/1.3)
+    front = (a.top < d.top)
+    scale = (if front then 1.3 else 1/1.3)
     if 'contact' in moveData.flags
       # Simple attack animation
       # Tackling the opponent
@@ -311,9 +312,8 @@
         .transition(x: d.left - a.left, y: d.top - a.top, scale: scale, 250, 'in')
         .transition(x: 0, y: 0, scale: 1, 250, 'out')
       $defender.delay(400)
-        .transition(x: -4, 0, 'linear').delay(50)
-        .transition(x: 4, 0, 'linear').delay(50)
-        .transition(x: 0, 0, 'linear', done)
+        .transition(x: (if front then -16 else 8), 50, 'easeOutCubic')
+        .transition(x: 0, 50, 'easeInOutCubic', done)
     else if moveData['power'] > 0
       # Non-contact attacking move
       # Projectile


display network activity overlay while loading

--- client/lanes/components/grid/Grid.cjsx
+++ client/lanes/components/grid/Grid.cjsx
@@ -57,8 +57,8 @@

     render: ->
         cellStyles = new Lanes.Components.Grid.CellStyles(@query.fields)
-
         <div className='grid-component'>
+            <LC.NetworkActivityOverlay model={@query} />
             <Lanes.Components.Grid.Toolbar {...@props} startEdit={@startEdit} />
             <Lanes.Components.Grid.Header  {...@props} cellStyles={cellStyles} />
             <Lanes.Components.Grid.Body


Fix disconnect sockets count.

Fix disconnect sockets count.

--- lib/chat.coffee
+++ lib/chat.coffee
@@ -882,7 +882,7 @@
         endDisconnect args...
       @userState.socketRemove id, withEH unlock, =>
         @userState.socketsGetAll withEH unlock, (sockets) =>
-          nsockets = sockets.lenght
+          nsockets = sockets.length
           if nsockets > 0 then unlock()
           else @sendAllRoomsLeave unlock


roomTypes NEEDS to be declared before startup

--- client/startup/defaultRoomTypes.coffee
+++ client/startup/defaultRoomTypes.coffee
@@ -1,43 +1,42 @@
-Meteor.startup ->
-	RocketChat.roomTypes.add 'starred',
-		template: 'starredRooms'
-		icon: 'icon-star'
+RocketChat.roomTypes.add 'starred',
+	template: 'starredRooms'
+	icon: 'icon-star'

-	RocketChat.roomTypes.add 'c',
-		template: 'channels'
-		icon: 'icon-hash'
-		route:
-			name: 'channel'
-			path: '/channel/:name'
-			action: (params, queryParams) ->
-				Session.set 'showUserInfo'
-				openRoom 'c', params.name
-			link: (sub) ->
-				return { name: sub.name }
-		permissions: [ 'view-c-room' ]
+RocketChat.roomTypes.add 'c',
+	template: 'channels'
+	icon: 'icon-hash'
+	route:
+		name: 'channel'
+		path: '/channel/:name'
+		action: (params, queryParams) ->
+			Session.set 'showUserInfo'
+			openRoom 'c', params.name
+		link: (sub) ->
+			return { name: sub.name }
+	permissions: [ 'view-c-room' ]

-	RocketChat.roomTypes.add 'd',
-		template: 'directMessages'
-		icon: 'icon-at'
-		route:
-			name: 'direct'
-			path: '/direct/:username'
-			action: (params, queryParams) ->
-				Session.set 'showUserInfo'
-				openRoom 'd', params.name
-			link: (sub) ->
-				return { username: sub.name }
-		permissions: [ 'view-d-room' ]
+RocketChat.roomTypes.add 'd',
+	template: 'directMessages'
+	icon: 'icon-at'
+	route:
+		name: 'direct'
+		path: '/direct/:username'
+		action: (params, queryParams) ->
+			Session.set 'showUserInfo'
+			openRoom 'd', params.username
+		link: (sub) ->
+			return { username: sub.name }
+	permissions: [ 'view-d-room' ]

-	RocketChat.roomTypes.add 'p',
-		template: 'privateGroups'
-		icon: 'icon-lock'
-		route:
-			name: 'group'
-			path: '/group/:name'
-			action: (params, queryParams) ->
-				Session.set 'showUserInfo'
-				openRoom 'p', params.name
-			link: (sub) ->
-				return { name: sub.name }
-		permissions: [ 'view-p-room' ]
+RocketChat.roomTypes.add 'p',
+	template: 'privateGroups'
+	icon: 'icon-lock'
+	route:
+		name: 'group'
+		path: '/group/:name'
+		action: (params, queryParams) ->
+			Session.set 'showUserInfo'
+			openRoom 'p', params.name
+		link: (sub) ->
+			return { name: sub.name }
+	permissions: [ 'view-p-room' ]


Determine when to scroll content back to top dynamically

--- src/scripts/modules/media/nav/nav.coffee
+++ src/scripts/modules/media/nav/nav.coffee
@@ -1,4 +1,5 @@
 define (require) ->
+  $ = require('jquery')
   router = require('cs!router')
   analytics = require('cs!helpers/handlers/analytics')
   BaseView = require('cs!helpers/backbone/views/base')
@@ -26,10 +27,11 @@
       @navigate(@model.previousPage())

     navigate: (page) ->
+      maxY = $('.media-header').offset().top
       y = window.pageYOffset || document.documentElement.scrollTop

-      if y > 700
-        window.scrollTo(0, 300)
+      if y > maxY
+        window.scrollTo(0, maxY)

       route = "/content/#{router.current().params[0]}:#{page}" # Deterimine the new route
       router.navigate(route) # Update browser URL to reflect the new route


Make search results views model

Make search results views model

--- src/scripts/modules/search/search.coffee
+++ src/scripts/modules/search/search.coffee
@@ -13,13 +13,13 @@
       super()

       if location.search and location.search isnt '?q='
-        @results = new SearchResults({query: location.search})
+        @model = new SearchResults({query: location.search})

     regions:
       search: '.search'

     onRender: () ->
-      if @results
-        @regions.search.show(new SearchResultsView({results: @results}))
+      if @model
+        @regions.search.show(new SearchResultsView({model: @model}))
       else
         @regions.search.show(new AdvancedSearchView())


Sim: lockdown and mod are now admin commands.

--- server/commands.coffee
+++ server/commands.coffee
@@ -139,7 +139,7 @@
         return next()

 desc "Prevents new battles from starting. Usage: /lockdown [on|off]"
-makeOwnerCommand "lockdown", (user, room, next, option = "on") ->
+makeAdminCommand "lockdown", (user, room, next, option = "on") ->
   if option not in [ "on", "off" ]
     user.error(errors.COMMAND_ERROR, "Usage: /lockdown [on|off]")
     return next()
@@ -147,7 +147,7 @@
   next()

 desc "Mods a username permanently. Usage: /mod username"
-makeOwnerCommand "mod", (user, room, next, username) ->
+makeAdminCommand "mod", (user, room, next, username) ->
   if !username
     user.error(errors.COMMAND_ERROR, "Usage: /mod username")
     return next()


Add default keymap for incremental search input element for arrow and tab key..

--- keymaps/vim-mode-plus.cson
+++ keymaps/vim-mode-plus.cson
@@ -319,9 +319,16 @@
 # -------------------------
 'atom-text-editor.vim-mode-plus-search':
   'ctrl-r ctrl-w': 'vim-mode-plus:search-set-cursor-word'
-  'ctrl-v': 'vim-mode-plus:search-set-literal-char'
-  'ctrl-c': 'vim-mode-plus:search-cancel'
-  'ctrl-[': 'vim-mode-plus:search-cancel'
+
+  'ctrl-v':    'vim-mode-plus:search-set-literal-char'
+  'ctrl-c':    'vim-mode-plus:search-cancel'
+  'ctrl-[':    'vim-mode-plus:search-cancel'
+  'shift-tab': 'vim-mode-plus:search-visit-prev'
+  'tab':       'vim-mode-plus:search-visit-next'
+  'left':      'vim-mode-plus:search-visit-prev'
+  'right':     'vim-mode-plus:search-visit-next'
+  'down':      'vim-mode-plus:search-scroll-next'
+  'up':        'vim-mode-plus:search-scroll-prev'

   # Don't afraid to set following agressive keymap
   # If you want input literlly, `ctrl-v` then input char you want to insert


read file as binary so images work

read file as binary so images work

--- src/smc-hub/kucalc/compute-client.coffee
+++ src/smc-hub/kucalc/compute-client.coffee
@@ -43,7 +43,7 @@
     return

 exports.get_file = get_file = (url, cb) ->
-    request.get url, (err, response, body) ->
+    request.get url, {encoding: null}, (err, response, body) ->
         if err
             cb(err)
         else if response.statusCode != 200


feat: modify primary key condition

feat: modify primary key condition

--- lib/ModelLink.coffee
+++ lib/ModelLink.coffee
@@ -43,7 +43,7 @@
               Type: field.type.toLowerCase(),
               Null: if field.allowNull then 'YES' else 'NO',
               Default: defaultValue,
-              Key: field.primayKey
+              Key: if field.primaryKey == true then 'PRI' else null
             }
             columnInfoList.push(columnInfo)


debug

debug

--- lib/atom-butterfly.coffee
+++ lib/atom-butterfly.coffee
@@ -31,10 +31,10 @@
       Q.Promise (resolve, reject, notify) ->
         # 生成默认的公、密钥到userhome/.ssh
         if atom.config.get('atom-butterfly.gitCloneEnvironmentPath')
-          if process.platform is 'win32'
-            command = "set path=%path%#{path.delimiter}#{atom.config.get('atom-butterfly.gitCloneEnvironmentPath')}"
-          else
-            command = "export PATH=$PATH#{path.delimiter}#{atom.config.get('atom-butterfly.gitCloneEnvironmentPath')}"
+          if process.platform is 'win32' and process.env.Path.indexOf "#{atom.config.get('atom-butterfly.gitCloneEnvironmentPath')}" == -1
+            command = "setx PATH \"%PATH%#{path.delimiter}#{atom.config.get('atom-butterfly.gitCloneEnvironmentPath')}\""
+          else if process.platform != 'win32' and process.env.PATH.indexOf "#{atom.config.get('atom-butterfly.gitCloneEnvironmentPath')}" == -1
+            command = "cat /etc/profile && echo \\nexport PATH=$PATH#{path.delimiter}#{atom.config.get('atom-butterfly.gitCloneEnvironmentPath')} >> /etc/profile && source /etc/profile"
           cp = exec command, (error, stdout, stderr) ->
               if error
                 reject(error)


menu settings add a warn

menu settings add a warn

--- lib/atom-chameleon.coffee
+++ lib/atom-chameleon.coffee
@@ -82,6 +82,8 @@
     #New
     atom.workspaceView.command "atom-chameleon:create-project", => @cmdCreateProject()
     atom.workspaceView.command "atom-chameleon:create-file", => @cmdCreateFile()
+
+    atom.workspaceView.command "atom-chameleon:settings", => @cmdCreateFile()

   deactivate: ->
     @projectManager.deactivate?()


test(InventorySync): add more test for restockable

--- src/spec/sync/inventory-sync.spec.coffee
+++ src/spec/sync/inventory-sync.spec.coffee
@@ -109,6 +109,52 @@
       expect(update).toBeDefined()
       expect(update.actions[0].action).toBe 'setExpectedDelivery'
       expect(update.actions[0].expectedDelivery).toBeUndefined()
+
+    iit 'should add restockableInDays', ->
+      ieNew =
+        sku: 'ijk'
+        quantityOnStock: 5
+        restockableInDays: 7
+        version: 1
+      ieOld =
+        sku: 'ijk'
+        quantityOnStock: 5
+        version: 1
+      update = @sync.buildActions(ieNew, ieOld).getUpdatePayload()
+      expect(update).toBeDefined()
+      expect(update.actions[0].action).toBe 'setRestockableInDays'
+      expect(update.actions[0].restockableInDays).toBe 7
+
+    iit 'should update restockableInDays', ->
+      ieNew =
+        sku: 'oik'
+        quantityOnStock: 0
+        restockableInDays: 3
+        version: 1
+      ieOld =
+        sku: 'oik'
+        quantityOnStock: 0
+        restockableInDays: 7
+        version: 1
+      update = @sync.buildActions(ieNew, ieOld).getUpdatePayload()
+      expect(update).toBeDefined()
+      expect(update.actions[0].action).toBe 'setRestockableInDays'
+      expect(update.actions[0].restockableInDays).toBe 3
+
+    iit 'should remove restockableInDays', ->
+      ieNew =
+        sku: 'oik'
+        quantityOnStock: 0
+        version: 1
+      ieOld =
+        sku: 'oik'
+        quantityOnStock: 0
+        version: 1
+        restockableInDays: 10
+      update = @sync.buildActions(ieNew, ieOld).getUpdatePayload()
+      expect(update).toBeDefined()
+      expect(update.actions[0].action).toBe 'setRestockableInDays'
+      expect(update.actions[0].restockableInDays).toBeUndefined()

     it 'no differences in restockableInDays', ->
       ie =


Fix getting word under cursor for various Ruby scopes

In Ruby blocks within ERB, go-to-declaration wouldn't work when cursor
was positioned on identifiers such as `foo?` or `foo!` because the
`?!` characters wouldn't be considered part of the current "word". This
is due to the fact that the previous special case for `source.ruby`
scope didn't apply for ERB where the scope is `source.ruby.embedded.erb`.

--- lib/tag-reader.coffee
+++ lib/tag-reader.coffee
@@ -7,12 +7,12 @@

 module.exports =
   find: (editor, callback) ->
-    if editor.getLastCursor().getScopeDescriptor().getScopesArray().indexOf('source.ruby') isnt -1
-      # Include ! and ? in word regular expression for ruby files
-      range = editor.getLastCursor().getCurrentWordBufferRange(wordRegex: /[\w!?]*/g)
-    else
-      range = editor.getLastCursor().getCurrentWordBufferRange()
+    cursor = editor.getLastCursor()
+    scopes = cursor.getScopeDescriptor().getScopesArray()
+    rubyScopes = scopes.filter (scope) -> /^source\.ruby($|\.)/.test(scope)
+    wordRegex = /[a-zA-Z_][\w!?]*/g if rubyScopes.length

+    range = cursor.getCurrentWordBufferRange({wordRegex})
     symbol = editor.getTextInRange(range)

     unless symbol?.length > 0


handle suscription save errors

handle suscription save errors

--- coffee/subscriptions.controller.coffee
+++ coffee/subscriptions.controller.coffee
@@ -238,11 +238,21 @@
                     google_conversion_currency: currency.toUpperCase()
                 })

-        @subscriptionsService.selectMyPlan(plan).then(@._onSuccessSelectPlan.bind(this))
+        promise = @subscriptionsService.selectMyPlan(plan)
+        promise.then () =>
+            @._onSuccessSelectPlan()
+        promise.catch (e) =>
+            @._onFailedSelectPlan(e.data.detail)

     _onSuccessSelectPlan: () ->
         message = @translate.instant("SUBSCRIPTIONS.SELECT_PLAN.SUCCESS")
         @confirm.notify('success', message, '', 5000)
+        @authService.refresh()
+
+        @._loadPlans()
+
+    _onFailedSelectPlan: (msg) ->
+        @confirm.notify("error", msg)
         @authService.refresh()

         @._loadPlans()


an apology for certain methods

an apology for certain methods

--- coffeescript/service/dispatcher.coffee
+++ coffeescript/service/dispatcher.coffee
@@ -95,6 +95,9 @@
       match[key] = value
     match

+  # this code was stolen and adapted from Djinn, which
+  # is why it looks so hideous.  Not that Djinn is hideous,
+  # just that you're seeing it out of context.
   try_sequence: (sequence) ->
     stage = @matchers
     current = [new MatchTracker(null, stage)]


Fixed typo in help text

Fixed typo in help text

--- src/scripts/diagnostics.coffee
+++ src/scripts/diagnostics.coffee
@@ -180,9 +180,9 @@
                   \n
                   If you make any manual changes to the spreadsheet, you should
                   run `/sync`. Running this resyncs Ibizan with the spreadsheet.
-                  Do to some existing limitations in Google Sheets, changes
+                  Due to some existing limitations in Google Sheets, changes
                   made directly to the spreadsheet are not immediately
-                  reflected by Ibizan and must be followed up with a resync.\n
+                  reflected by Ibizan and must be followed up with a `/sync`.\n
                   \n
                   For more documentation, please check out
                   https://github.com/ibizan/ibizan.github.io/wiki"


Revert app shortcuts feature

Revert app shortcuts feature

--- components/buttons/KDButtonMenu.coffee
+++ components/buttons/KDButtonMenu.coffee
@@ -27,7 +27,6 @@
     menuHeight    = @getHeight()
     menuWidth     = @getWidth()

-
     ghostCss = if top + menuHeight > mainHeight
       top = button.getY() - menuHeight
       @setClass "top-menu"


add method to get hash of live version of syncstring

--- src/smc-util/syncstring.coffee
+++ src/smc-util/syncstring.coffee
@@ -1512,11 +1512,14 @@
     # has_uncommitted_changes below for determining whether there are changes
     # that haven't been commited to the database yet.
     has_unsaved_changes: () =>
-        return misc.hash_string(@get()) != @hash_of_saved_version()
+        return @hash_of_live_version() != @hash_of_saved_version()

     # Returns hash of last version saved to disk (as far as we know).
     hash_of_saved_version: =>
         return @_syncstring_table?.get_one()?.getIn(['save', 'hash'])
+
+    hash_of_live_version: =>
+        return misc.hash_string(@get())

     # Initiates a save of file to disk, then if cb is set, waits for the state to
     # change to done before calling cb.


fix race condition in autosave that might cause project to blank file the first time it is opened

--- src/smc-util/syncstring.coffee
+++ src/smc-util/syncstring.coffee
@@ -488,6 +488,10 @@
             if err
                 console.warn("error creating SyncDoc: '#{err}'")
                 @emit('error', err)
+            else
+                if @_client.is_project()
+                    # CRITICAL: do not start autosaving this until syncstring is initialized!
+                    @init_project_autosave()

         if opts.file_use_interval and @_client.is_user()
             is_chat = misc.filename_extension(@_path) == 'sage-chat'
@@ -510,9 +514,6 @@
                 @_cursors?.set(x,'none')
             @_throttled_set_cursor_locs = underscore.throttle(set_cursor_locs, 2000)

-        if @_client.is_project()
-            @init_project_autosave()
-
     # Used for internal debug logging
     dbg: (f) ->
         return @_client.dbg("SyncString.#{f}:")
@@ -527,7 +528,7 @@
         if not LOCAL_HUB_AUTOSAVE_S or not @_client.is_project() or @_project_autosave?
             return
         f = () =>
-            if @has_unsaved_changes()
+            if @hash_of_saved_version()? and @has_unsaved_changes()
                 @_save_to_disk()
         @_project_autosave = setInterval(f, LOCAL_HUB_AUTOSAVE_S*1000)


make an optimization to opening files -- determine read-only status at some time as initialization

--- src/smc-util/syncstring.coffee
+++ src/smc-util/syncstring.coffee
@@ -816,7 +816,7 @@
     # The project calls this once it has checked for the file on disk; this
     # way the frontend knows that the syncstring has been initialized in
     # the database, and also if there was an error doing the check.
-    _set_initialized: (error, cb) =>
+    _set_initialized: (error, is_read_only, cb) =>
         init = {time: misc.server_time()}
         if error
             init.error = "error - #{JSON.stringify(error)}"  # must be a string!
@@ -829,6 +829,7 @@
                     project_id : @_project_id
                     path       : @_path
                     init       : init
+                    read_only  : is_read_only
             cb : cb

     # List of timestamps of the versions of this string in the sync
@@ -976,33 +977,36 @@
                     cb : cb
         ], (err)=>cb?(err))

-    _update_if_file_is_read_only: (cb) =>
+    _file_is_read_only: (cb) =>
         @_client.path_access
             path : @_path
             mode : 'w'
             cb   : (err) =>
-                @_set_read_only(!!err)
+                cb(undefined, !!err)
+
+    _update_if_file_is_read_only: (cb) =>
+        @_file_is_read_only (err, is_read_only) =>
+                @_set_read_only(is_read_only)
                 cb?()

     _load_from_disk_if_newer: (cb) =>
         tm     = @last_changed()
         dbg    = @_client.dbg("syncstring._load_from_disk_if_newer('#{@_path}')")
-        exists = undefined
-        async.series([
+        locals = {exists: false, is_read_only: false}
+        async.series([
             (cb) =>
                 dbg("check if path exists")
                 @_client.path_exists
                     path : @_path
-                    cb   : (err, _exists) =>
+                    cb   : (err, exists) =>
                         if err
                             cb(err)
                         else
-                            exists = _exists
+                            locals.exists = exists
                             cb()
             (cb) =>
-                if not exists
+                if not locals.exists
                     dbg("file does NOT exist")
-                    @_set_read_only(false)
                     cb()
                     return
                 if tm?
@@ -1020,18 +1024,20 @@
                                 cb()
                 else
                     dbg("never edited before")
-                    if exists
+                    if locals.exists
                         dbg("path exists, so load from disk")
                         @_load_from_disk(cb)
                     else
                         cb()
             (cb) =>
-                if exists
-                    @_update_if_file_is_read_only(cb)
+                if locals.exists
+                    @_file_is_read_only (err, is_read_only) ->
+                        locals.is_read_only = is_read_only
+                        cb(err)
                 else
                     cb()
         ], (err) =>
-            @_set_initialized(err, cb)
+            @_set_initialized(err, locals.is_read_only, cb)
         )

     _patch_table_query: (cutoff) =>


write history to file

write history to file

--- lib/ui/console.coffee
+++ lib/ui/console.coffee
@@ -23,6 +23,7 @@

   deactivate: ->
     @cmd.dispose()
+    history.write @c.history

   create: ->
     @c = new @ink.Console


Fix backspacing

Fix backspacing

--- src/trix/models/selection_manager.coffee
+++ src/trix/models/selection_manager.coffee
@@ -41,7 +41,7 @@
       else
         textRange.moveStart(granularity, -1)
       textRange.select()
-    @adjustSelectionInDirection(direction)
+    @selectionDidChange(null, direction)

   lock: ->
     if @lockCount++ is 0


delegate browser open to browsersync

delegate browser open to browsersync

--- lib/cli/watch.coffee
+++ lib/cli/watch.coffee
@@ -42,13 +42,6 @@
       res.watcher = w
       res.server = app
       node.call(app.start.bind(app), port)
-    .tap ->
-      # TODO: remove this, delgate to browsersync
-      if project.config.open_browser and not args.no_open
-        if project.config.open_browser == true
-          open("http://localhost:#{port}/")
-        else
-          open(project.config.open_browser)
     .yield(res)

 ###*


removed reference to 'box'

removed reference to 'box'

--- lib/collector.coffee
+++ lib/collector.coffee
@@ -1,5 +1,5 @@
 {EventEmitter} = require 'events'
-{objInclude, find, box} = require './util'
+{objInclude, find} = require './util'
 normalizePayload = require './normalizePayload'
 applyOp = require './applyOp'
 _ = require 'lodash'


undo sort setting default value

undo sort setting default value

--- lib/atom-ternjs.coffee
+++ lib/atom-ternjs.coffee
@@ -38,7 +38,7 @@
       title: 'Sort'
       description: 'Determines whether the result set will be sorted.'
       type: 'boolean'
-      default: false
+      default: true
     caseInsensitive:
       title: 'Case-insensitive'
       description: 'Whether to use a case-insensitive compare between the current word and potential completions.'


Added traverse and formatting variables

Added traverse and formatting variables

--- snippets/template.cson
+++ snippets/template.cson
@@ -117,6 +117,40 @@
   'Level':
     'prefix': 'level'
     'body': 'Level(${1:1})${0}'
+  # Scope traverse
+  'Traverse Up Scope':
+    'prefix': 'up'
+    'body': 'Up'
+  'Current Object Scope':
+    'prefix': 'me'
+    'body': 'Me'
+  'Top Level Scope':
+    'prefix': 'top'
+    'body': 'Top'
+  # Breadcrumbs
+  'Breadcrumbs':
+    'prefix': 'breadcrumbs'
+    'body': 'Breadcrumbs'
+  # Form
+  'Form':
+    'prefix': 'form'
+    'body': 'Form'
+  # Formating
+  'LimitWordCount':
+    'prefix': 'limitwordcount'
+    'body': 'LimitWordCount(${1:26},${1:\'...\'})'
+  'LimitCharacters':
+    'prefix': 'limitcharacters'
+    'body': 'LimitCharacters(${1:20},${1:\'...\'})'
+  'EscapeXML'
+    'prefix': 'escapexml'
+    'body': 'EscapeXML'
+  'FirstParagraph':
+    'prefix': 'firstparagraph'
+    'body': 'FirstParagraph'
+  'NoHTML':
+    'prefix': 'nohtml'
+    'body': 'NoHTML'

 '.text.html.ss .variable':
   # Image manipulation 3.2


Add an author object

Add an author object

--- lib/y-richtext.coffee
+++ lib/y-richtext.coffee
@@ -94,6 +94,7 @@
         selections: new Y.Selections()
         characters: new Y.List()
         cursors: new Y.Object()
+        authors: new Y.Object()
       @_model = new Operation.MapManager(@, null, {}, content_operations )
         .execute()

@@ -126,8 +127,11 @@
   # @param position [Integer] the position where to insert it
   setCursor : (position) ->
     @selfCursor = @_model.getContent("characters").ref(position)
+
     @_model.getContent("cursors").val(@_model.HB.getUserId(), @selfCursor)

+  setAuthor : (@author) ->
+    @_model.getContent('authors').val(@_model.HB.getUserId(), @author)

   # pass deltas to the character instance
   # @param deltas [Array<Object>] an array of deltas
@@ -149,7 +153,9 @@
       @selfCursor = @_model.getContent("characters").ref(obj)
     else
       @selfCursor = obj
+
     @_model.getContent("cursors").val(@_model.HB.getUserId(), @selfCursor)
+

   # describe how to propagate yjs events to the editor
   # TODO: should be private!
@@ -231,6 +237,13 @@
       if event.action is "userLeft"
         @_model.getContent("cursors").delete(event.user)

+    @_model.getContent('authors').observe (events) => @locker.try ()=>
+      for event in events
+        @editor.removeCursor event.changedBy
+
+
+
+
   # Apply a delta and return the new position
   # @param delta [Object] a *single* delta (see ot-types for more info)
   # @param position [Integer] start position for the delta, default: 0


cleanup

cleanup

--- lib/developer.coffee
+++ lib/developer.coffee
@@ -10,20 +10,6 @@

 packageScope = 'vim-mode-plus'
 getEditorState = null
-
-# Borrowed from underscore-plus
-modifierKeyMap =
-  cmd: '\u2318'
-  ctrl: '\u2303'
-  alt: '\u2325'
-  option: '\u2325'
-  shift: '\u21e7'
-  enter: '\u23ce'
-  left: '\u2190'
-  right: '\u2192'
-  up: '\u2191'
-  down: '\u2193'
-

 getParent = (obj) ->
   obj.__super__?.constructor
@@ -126,30 +112,47 @@
     settings.set('debug', not settings.get('debug'))
     console.log "#{settings.scope} debug:", settings.get('debug')

+  # Borrowed from underscore-plus
+  modifierKeyMap =
+    cmd: '\u2318'
+    "ctrl-": '\u2303'
+    alt: '\u2325'
+    option: '\u2325'
+    enter: '\u23ce'
+    left: '\u2190'
+    right: '\u2192'
+    up: '\u2191'
+    down: '\u2193'
+    backspace: 'BS'
+    space: 'SPC'
+
+  # [NOTE] Order is important. I depend on chrome's "keep object order".
+  selectorMap =
+    "atom-text-editor.vim-mode-plus": ''
+    ".operator-pending-mode": 'o'
+    ".visual-mode.characterwise": 'vC'
+    ".visual-mode.blockwise": 'vB'
+    ".visual-mode.linewise": 'vL'
+    ".visual-mode": 'v'
+    ".insert-mode.replace": 'iR'
+    ".insert-mode": 'i'
+    ".normal-mode": 'n'
+    ".with-count": '#'
+
   getCommandSpecs: ->
     compactSelector = (selector) ->
+      pattern = ///(#{_.keys(selectorMap).map(_.escapeRegExp).join('|')})///g
       selector.split(/,\s*/g).map (scope) ->
         scope
-          .replace(/atom-text-editor\.vim-mode-plus/, '')
           .replace(/:not\((.*)\)/, '!$1')
-          .replace(/\.normal-mode/, 'n')
-          .replace(/\.visual-mode\.blockwise/, 'vB')
-          .replace(/\.visual-mode\.linewise/, 'vL')
-          .replace(/\.visual-mode\.characterwise/, 'vC')
-          .replace(/\.visual-mode/, 'v')
-          .replace(/\.insert-mode\.replace/, 'iR')
-          .replace(/\.insert-mode/, 'i')
-          .replace(/\.operator-pending-mode/, 'o')
+          .replace(pattern, (s) -> selectorMap[s])
       .join(",")

-
     compactKeystrokes = (keystrokes) ->
+      pattern = ///(#{_.keys(modifierKeyMap).map(_.escapeRegExp).join('|')})///
       keystrokes
         .replace(/(`|_)/g, '\\$1')
-        .replace(/ctrl-/, modifierKeyMap["ctrl"])
-        .replace(/down|up|left|right|enter|cmd|option/, (s) -> modifierKeyMap[s])
-        .replace('backspace', 'BS')
-        .replace('space', 'SPC')
+        .replace(pattern, (s) -> modifierKeyMap[s])
         .replace(/\s+/, '')

     commands = (
@@ -164,8 +167,6 @@
             "`#{compactSelector(selector)}` #{compactKeystrokes(keystrokes)}"
           .join("
")

-        # keystrokes#{keystrokes}"
-
         {name, commandName, kind, description, keymap}
     )
     commands
@@ -174,8 +175,7 @@
   generateSummaryTableForCommandSpecs: (specs, {header}={}) ->
     grouped = _.groupBy(specs, 'kind')
     str = ""
-    for kind in kinds
-      specs = grouped[kind]
+    for kind in kinds when specs = grouped[kind]

       report = [
         "## #{kind}"
@@ -196,17 +196,22 @@

   generateCommandSummaryTable: ->
     header = """
-    # Description
-
-    - `!i`: :not(.insert-mode)
-    - `i`: insert-mode
-    - `o`: operator-pending-mode
-    - `n`: normal-mode
-    - `v`: visual-mode
-    - `vB`: visual-mode.blockwise
-    - `vL`: visual-mode.linewise
-    - `vC`: visual-mode.characterwise
-    - `iR`: insert-mode.replace
+    # Keymap selector abbreviations
+
+    In this document, following abbreviations are used for shortness.
+
+    | Abbrev | Selector                     | Description             |
+    |:-------|:-----------------------------|:------------------------|
+    | `!i`   | `:not(.insert-mode)`         | except insert-mode      |
+    | `i`    | `.insert-mode`               |                         |
+    | `o`    | `.operator-pending-mode`     |                         |
+    | `n`    | `.normal-mode`               |                         |
+    | `v`    | `.visual-mode`               |                         |
+    | `vB`   | `.visual-mode.blockwise`     |                         |
+    | `vL`   | `.visual-mode.linewise`      |                         |
+    | `vC`   | `.visual-mode.characterwise` |                         |
+    | `iR`   | `.insert-mode.replace`       |                         |
+    | `#`    | `.with-count`                | when count is specified |

     """
     @generateSummaryTableForCommandSpecs(@getCommandSpecs(), {header})


fixed email sender tests looking for wrong settings

--- services/web/app/coffee/Features/Email/EmailSender.coffee
+++ services/web/app/coffee/Features/Email/EmailSender.coffee
@@ -52,7 +52,7 @@
 			text: options.text
 			replyTo: options.replyTo || Settings.email.replyToAddress
 			socketTimeout: 30 * 1000
-		if settings.email.textEncoding?
+		if Settings.email.textEncoding?
 			opts.textEncoding = textEncoding
 		client.sendMail options, (err, res)->
 			if err?


remove useless process import

remove useless process import

--- lib/ewg/stage.coffee
+++ lib/ewg/stage.coffee
@@ -1,5 +1,3 @@
-process = require 'process'
-
 stage = {
   current: ->
     return 'production' if stage.isProduction()


dont crunch volume if its present

dont crunch volume if its present

--- js/music_cruncher.coffee
+++ js/music_cruncher.coffee
@@ -58,9 +58,10 @@
     # Add normalized volumes to rhythmic events based on the segment volume within them.
     for eventType in ['sections', 'bars', 'beats']
       for event in @data[eventType]
-        event.volume = 0
-        for segment in @eventsInRange 'segments', event
-          event.volume = segment.volume if segment.volume > event.volume
+        unless event.volume?
+          event.volume = 0
+          for segment in @eventsInRange 'segments', event
+            event.volume = segment.volume if segment.volume > event.volume

     return


Resolve merge conflicts in changelog

Resolve merge conflicts in changelog

--- js/outer/versions.coffee
+++ js/outer/versions.coffee
@@ -219,7 +219,7 @@
   riak: "1.4.8-1"
   cassandra: "2.0.6"
   redis: "2.8.12"
-  memcache: "1.4.13"
+  memcached: "1.4.13"
   sphinx: "2.0.4-release"
   elasticsearch: "0.90.2"
   beanstalkd: "1.4.6"


Enable linting files without having _build directory

The following commit allows the linter to work even if the file/project does not have `_build` directory

--- lib/init.coffee
+++ lib/init.coffee
@@ -99,8 +99,11 @@
     getDepsPa = (textEditor) ->
       env = if isTestFile(textEditor) then "test" else "dev"
       buildDir = path.join("_build", env, "lib")
-      fs.readdirSync(path.join(projectPath(textEditor), buildDir)).map (item) ->
-        path.join(projectPath(textEditor), buildDir, item, "ebin")
+      try
+        fs.readdirSync(path.join(projectPath(textEditor), buildDir)).map (item) ->
+          path.join(projectPath(textEditor), buildDir, item, "ebin")
+      catch e
+        []
     lintElixirc = (textEditor) =>
       elixircArgs = [
         "--ignore-module-conflict", "--app", "mix", "--app", "ex_unit", "-o", os.tmpDir(),


Fix: Destructable automatic unbinding - don't try to unbind destroyed objects.

--- lib/ST/Destructable.coffee
+++ lib/ST/Destructable.coffee
@@ -34,7 +34,8 @@
     # Unbind any loose bindings
     if @_boundTo
       for binding in @_boundTo
-        binding.source.unbindOne binding.trigger, this
+        unless binding.source._destroyed
+          binding.source.unbindOne binding.trigger, this

     # Release any retained properties
     c = @_class


whoops

whoops

--- src/ui/models/viewstate.coffee
+++ src/ui/models/viewstate.coffee
@@ -43,6 +43,7 @@
     selectNextConv: (offset = 1) ->
         conv = require './conv'
         id = @selectedConv
+        c = conv[id]
         list = (i for i in conv.list() when not conv.isPureHangout(c))
         for c, index in list
             if id == c.conversation_id.id


Moved functions.

Moved functions.

--- lib/list.coffee
+++ lib/list.coffee
@@ -1,30 +1,8 @@
-require 'coffee-script'
-
-# ---
-
 parse= require './parse.coffee'
 command = require './command.coffee'

 # ---

-###
-	* @param {function(?err, result)} callback
-###
-exports.vms = (callback) ->
-	command.exec 'list', 'vms', (err, code, output) ->
-		return callback err if err
-		return callback new Error "cannot list vms" if code > 0
-		return callback null, parse.namepair_list(output) if callback
-
-###
-	* @param {function(?err, result)} callback
-###
-exports.running_vms = (callback) ->
-	command.exec 'list', 'runningvms', (err, code, output) ->
-		return callback err if err
-		return callback new Error "cannot list running vms" if code > 0
-		return callback null, parse.namepair_list(output) if callback
-
 ###
 	* @param {function(?err, result)} callback
 ###
@@ -68,15 +46,6 @@
 	command.exec 'list', 'hostonlyifs', (err, code, output) ->
 		return callback err if err
 		return callback new Error "cannot list hostonly ifs" if code > 0
-		return callback null, parse.linebreak_list(output) if callback
-
-###
-	* @param {function(?err, result)} callback
-###
-exports.dhcp_servers = (callback) ->
-	command.exec 'list', 'dhcpservers', (err, code, output) ->
-		return callback err if err
-		return callback new Error "cannot list dhcp servers" if code > 0
 		return callback null, parse.linebreak_list(output) if callback

 ###


add providers to mapping instances instead of prototype

--- source/injector.coffee
+++ source/injector.coffee
@@ -72,7 +72,7 @@
 class Mapping

   constructor: (@_id, providers) ->
-    @constructor::[key] = @_setup(provider) for key, provider of providers
+    @[key] = @_setup(provider) for key, provider of providers

   provide: -> @_provider.provide()


fix: Atom grammar reload error (#164)

fix: Atom grammar reload error (#164)

--- lib/main.coffee
+++ lib/main.coffee
@@ -94,7 +94,7 @@
     # Reload grammars for each editor
     atom.workspace.getTextEditors().forEach (editor) ->
       if editor.getGrammar().packageName is 'language-asciidoc'
-        editor.reloadGrammar()
+        atom.textEditors.maintainGrammar(editor)

     console.log 'AsciiDoc grammars reloaded'


fix: when was not flashed on jump across buffer. Remaining flash invalidate immediately

--- lib/main.coffee
+++ lib/main.coffee
@@ -170,39 +170,45 @@
       @saveHistory(location, setIndexToHead: false, subject: "Save head position")
       needToLog = false

-    land = (editor) =>
-      @land(editor, point)
-      if settings.get('debug') and needToLog
-        @logHistory(direction)
-
     activePane = atom.workspace.getActivePane()
     if editor.getURI() is URI
-      land(editor)
+      @land(editor, point, direction, log: needToLog)
     else if item = findEditorForPaneByURI(activePane, URI)
       activePane.activateItem(item)
-      land(item)
+      @land(item, point, direction, forceFlash: true, log: needToLog)
     else
-      atom.workspace.open(URI, searchAllPanes: settings.get('searchAllPanes')).then(land)
-
-  land: (editor, point) ->
+      atom.workspace.open(URI, searchAllPanes: settings.get('searchAllPanes')).then (editor) =>
+        @land(editor, point, direction, forceFlash: true, log: needToLog)
+
+  land: (editor, point, direction, options={}) ->
     originalRow = editor.getCursorBufferPosition().row
-
     editor.setCursorBufferPosition(point, autoscroll: false)
     editor.scrollToCursorPosition(center: true)

-    if settings.get('flashOnLand') and (originalRow isnt point.row)
-      @flash(editor)
+    if settings.get('flashOnLand')
+      if options.forceFlash or (originalRow isnt point.row)
+        @flash(editor)
+
+    if settings.get('debug') and options.log
+      @logHistory(direction)
+

   flashMarker: null
   flash: (editor) ->
     @flashMarker?.destroy()
-    @flashMarker = editor.markBufferPosition(editor.getCursorBufferPosition())
+    cursorPosition = editor.getCursorBufferPosition()
+    @flashMarker = editor.markBufferPosition(cursorPosition)
     decorationOptions = {type: 'line', class: 'cursor-history-flash-line'}
     editor.decorateMarker(@flashMarker, decorationOptions)

-    setTimeout =>
+    destroyMarker = =>
+      disposable?.destroy()
+      disposable = null
       @flashMarker?.destroy()
-    , 1000 # [NOTE] animation-duration has to be shorter than this value(1sec)
+
+    disposable = editor.onDidChangeCursorPosition(destroyMarker)
+    # [NOTE] animation-duration has to be shorter than this value(1sec)
+    setTimeout(destroyMarker, 1000)

   logHistory: (msg) ->
     s = """


Properly serialize panes

Properly serialize panes

HT to @maxbrunsfeld

--- lib/main.coffee
+++ lib/main.coffee
@@ -5,10 +5,12 @@
 HISTORY_URI = 'atom://git/view-history'
 CHANGES_URI = 'atom://git/view-changes'

+changesView = null
+historyView = null
+serializedState = {}
+
 module.exports = GitExperiment =
   subscriptions: null
-  historyView: null
-  changesView: null
   state: null

   activate: (@state) ->
@@ -18,29 +20,31 @@
     process.nextTick =>
       @subscriptions.add atom.workspace.onWillDestroyPaneItem (pane) =>
         switch pane.item
-          when @historyView
-            @state.history = @historyView.serialize()
-          when @changesView
-            @state.changes = @changesView.serialize()
+          when historyView
+            serializedState.history = historyView.serialize()
+          when changesView
+            serializedState.changes = changesView.serialize()

       @subscriptions.add atom.workspace.registerOpener (filePath) =>
         switch filePath
           when HISTORY_URI
-            @historyView or= if @state.history?
+            historyView or= if @state.history?
+              console.log 'deserialized'
               atom.deserializers.deserialize(@state.history)
             else
+              console.log 'created'
               createHistoryView(uri: HISTORY_URI)
           when CHANGES_URI
-            @changesView or= if @state.changes?
+            changesView or= if @state.changes?
               atom.deserializers.deserialize(@state.changes)
             else
               createChangesView(uri: CHANGES_URI)

+  serialize: ->
+    serializedState
+
   deactivate: ->
     @subscriptions?.dispose()
-
-  serialize: ->
-    @state

   openHistoryView: ->
     atom.workspace.open(HISTORY_URI)
@@ -56,15 +60,15 @@

 createHistoryView = (state) ->
  HistoryView ?= require './history/history-view'
- view = new HistoryView
- view.initialize(state)
- view
+ historyView = new HistoryView
+ historyView.initialize(state)
+ historyView

 createChangesView = (state) ->
   ChangesView ?= require './changes/changes-view'
-  view = new ChangesView
-  view.initialize(state)
-  view
+  changesView = new ChangesView
+  changesView.initialize(state)
+  changesView

 atom.deserializers.add
   name: 'GitHistoryView'


Formatting

Formatting

--- lib/main.coffee
+++ lib/main.coffee
@@ -6,16 +6,19 @@
 module.exports =

   config:
+
     addListItems:
       title: 'Add new list-items'
       description: 'Automatically add a new list-item after the current (non-empty) one when pressing `ENTER`'
       type: 'boolean'
       default: true
+
     disableLanguageGfm:
       title: 'Disable language-gfm'
       description: 'Disable the default `language-gfm` package as this package is intended as its replacement'
       type: 'boolean'
       default: true
+
     indentListItems:
       title: 'Indent list-items'
       description: 'Automatically in- and outdent list-items by pressing `TAB` and `SHIFT+TAB`'


Rename "Lint On Fly" in settings to "Lint As You Type"

--- lib/main.coffee
+++ lib/main.coffee
@@ -3,12 +3,14 @@
   instance: null
   config:
     lintOnFly:
-      description: 'Lint files while typing, without the need to save them'
+      title: 'Lint As You Type'
+      description: 'Lint files while typing, without the need to save'
       type: 'boolean'
       default: true
       order: 1
     lintOnFlyInterval:
-      description: 'Interval at which providers are triggered on the fly'
+      title: 'Lint As You Type Interval'
+      description: 'Interval at which providers are triggered as you type (in ms)'
       type: 'integer'
       default: 300
       order: 1


update description

update description

--- lib/main.coffee
+++ lib/main.coffee
@@ -38,7 +38,7 @@
       order: 4
     manageDependencies:
       title: "Manage Dependencies"
-      description: "When enabled, this will automatically install third-party dependencies"
+      description: "When enabled, third-party dependencies will be installed automatically"
       type: "boolean"
       default: true
       order: 5


capitalize VCS

capitalize VCS

--- lib/main.coffee
+++ lib/main.coffee
@@ -6,6 +6,7 @@
     hideVcsIgnoredFiles:
       type: 'boolean'
       default: false
+      title: 'Hide VCS Ignored Files'
     hideIgnoredNames:
       type: 'boolean'
       default: false


WIP #3 ProjectView is also used in fuzzy-finder

--- lib/main.coffee
+++ lib/main.coffee
@@ -13,6 +13,7 @@
   editorSubscriptions: null
   lastEditor: null
   locked: false
+  _locker: null

   activate: ->
     History = require './history'
@@ -55,10 +56,12 @@
         # So we can't use TexitEditor::getCursorBufferPosition(), fotunately,
         # symbols-view serialize buffer state initaially, we use this.
         onShow: =>
+          console.log "Shown FileView"
           editor = @getEditor()
           point  = panel.getItem().initialState?.bufferRanges[0].start
           {editor, point}
         onHide: =>
+          console.log "Hidden FileView"
           editor = @getEditor()
           point  = editor.getCursorBufferPosition()
           {editor, point}
@@ -66,34 +69,35 @@
     ProjectView: (panel) ->
       @withPanel panel,
         onShow: =>
+          console.log "Shown ProjectView"
           editor = @getEditor()
           point  = editor.getCursorBufferPosition()
           {editor, point}
         onHide: =>
+          console.log "Hidden ProjectView"
           editor = @getEditor()
           point  = editor.getCursorBufferPosition()
           {editor, point}
-
-    GoBackView: (panel) ->
-      panel.onDidChangeVisible (visible) =>
-        if visible
-          console.log "GoBackView shown"
-        else
-          console.log "GoBackView hidden"
-
-    GoToView: (panel) ->
-      panel.onDidChangeVisible (visible) =>
-        if visible
-          console.log "GoToView shown"
-        else
-          console.log "GoToView hidden"

   observeModalPanel: ->
     atom.workspace.panelContainers['modal'].onDidAddPanel ({panel, index}) =>
-      itemKind = panel.getItem().constructor.name
-      # return unless itemKind in ['FileView', 'ProjectView']
-      return unless itemKind in ['GoToView', 'GoBackView', 'FileView', 'ProjectView']
-      @symbolsViewHandlers[itemKind]?.bind(this)(panel)
+      # [CAUTION] Simply checking constructor name is not enough
+      # e.g. ProjectView is also used in `fuzzy-finder`.
+      item = panel.getItem()
+      name = item.constructor.name
+      return unless name in ['FileView', 'ProjectView']
+
+      if name is 'FileView'
+        @symbolsViewHandlers[name]?.bind(this)(panel)
+      else
+        # [BUG]
+        # Since symbols-view class is not exist just after panel added.
+        # We need delay checking classList.
+        setTimeout =>
+          return unless item.element.classList.contains('symbols-view')
+          # return unless name in ['GoToView', 'GoBackView', 'FileView', 'ProjectView']
+          @symbolsViewHandlers[name]?.bind(this)(panel)
+        , 300

   observeTextEditors: ->
     handleChangePath = (editor) =>
@@ -139,6 +143,7 @@
       {editor, point, URI: lastURI} = @lastEditor.getInfo()
       if not @isLocked() and (lastURI isnt item.getURI())
         @history.add editor, point, lastURI, dumpMessage: "[Pane item changed] save history"
+      console.log "lock?: #{@isLocked()}: #{@_locker}"

       @lastEditor.set item
       @debug "set LastEditor #{path.basename(item.getURI())}"
@@ -163,7 +168,7 @@
     [oldEditor, oldPoint, newEditor, newPoint] = []
     panelSubscription = panel.onDidChangeVisible (visible) =>
       if visible
-        @lock()
+        @lock panel.getItem().constructor.name
         {editor: oldEditor, point: oldPoint} = onShow()
       else
         # ProjectView delayed changing cursor position after panel hidden(),
@@ -179,7 +184,10 @@
     @subscriptions.add panel.onDidDestroy ->
       panelSubscription.dispose()

-  lock:     -> @locked = true
+  lock: (locker) ->
+    @_locker = locker
+    @locked = true
+
   unLock:   -> @locked = false
   isLocked: -> @locked

@@ -193,7 +201,7 @@
       URI   = activeEditor.getURI()
       @history.pushToHead activeEditor, point, URI

-    @lock()
+    @lock "Jump#{direction}"

     {URI, point} = entry

@@ -224,6 +232,7 @@
     atom.workspace.getActiveTextEditor()

   dump: ->
+    console.log @isLocked()
     @history.dump '', true

   debug: (msg) ->


removed unnecessary code

removed unnecessary code

--- lib/main.coffee
+++ lib/main.coffee
@@ -29,7 +29,6 @@

     rels = @_cache[key]?[value] or {}
     if _.isArray names
-      names = box names
       return _.pick rels, names...
     else if names?
       return rels[names]


changed the map tiles

changed the map tiles

--- lib/main.coffee
+++ lib/main.coffee
@@ -13,9 +13,10 @@
   init: ->
     @_super()
     map = new L.Map @get('divId')
-    cloudmadeUrl = "http://{s}.tile.cloudmade.com/#{SM.CLOUDMADE_API_KEY}/997/256/{z}/{x}/{y}.png"
-    cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18})
-    map.addLayer cloudmade
+    cloudmadeTiles = new L.TileLayer "http://{s}.tile.cloudmade.com/#{SM.CLOUDMADE_API_KEY}/998/256/{z}/{x}/{y}.png",
+      maxZoom: 18
+    map.addLayer cloudmadeTiles
+    map.attributionControl.setPrefix('')
     @set('mapObject', map)
     @set('objects',[])
   addObject: (object) ->


improve deactivate()

improve deactivate()

--- lib/main.coffee
+++ lib/main.coffee
@@ -16,7 +16,8 @@
     @subscriptions.add atom.commands.add 'atom-workspace', 'BridleNSIS:save-&-compile': => @buildScript()

   deactivate: ->
-    @subscriptions.dispose()
+    @subscriptions?.dispose()
+    @subscriptions = null

   buildScript: ->
     editor = atom.workspace.getActiveTextEditor()


atom-config: font size bump, tree-view adjustments

--- init/config.cson
+++ init/config.cson
@@ -22,6 +22,7 @@
       "vim-mode"
     ]
     openEmptyEditorOnStart: false
+    telemetryConsent: "no"
     themes: [
       "dracula-ui"
       "dracula-theme"
@@ -29,6 +30,7 @@
   docblockr: {}
   editor:
     fontFamily: "SourceCodePro-Light"
+    fontSize: 15
     invisibles: {}
     preferredLineLength: 1000
     showIndentGuide: true
@@ -67,6 +69,7 @@
   "travis-ci-status": {}
   "tree-view":
     hideIgnoredNames: true
+    hideVcsIgnoredFiles: true
     showOnRightSide: true
     squashDirectoryNames: true
   "vim-mode":


add tests for evaluating event values late, to find laziness-related bugs

--- spec/SpecHelper.coffee
+++ spec/SpecHelper.coffee
@@ -43,11 +43,12 @@
     relativeTime = () ->
       Math.floor(now() - t0)
     withRelativeTime = (x) -> [relativeTime(), x]
-    src().map(withRelativeTime)
+    src().flatMap(withRelativeTime)
   @expectStreamEvents(srcWithRelativeTime, expectedEventsAndTimings, options)

 @expectStreamEvents = (src, expectedEvents, {unstable} = {}) ->
   verifySingleSubscriber src, expectedEvents
+  verifyLateEval src, expectedEvents
   if not unstable
     verifySwitching src, expectedEvents unless browser
     verifySwitchingWithUnsub src, expectedEvents unless browser
@@ -82,6 +83,16 @@
   it "has correct final state", ->
     verifyFinalState(property, lastNonError(expectedEvents))
   it "cleans up observers", verifyCleanup
+
+verifyLateEval = (srcF, expectedEvents) ->
+  verifyStreamWith "(late eval)", srcF, expectedEvents, (src, events, done) ->
+    src.subscribe (event) ->
+      if event.isEnd()
+        done()
+      else
+        expect(event instanceof Bacon.Initial).to.deep.equal(false)
+        events.push(event)
+

 verifySingleSubscriber = (srcF, expectedEvents) ->
   verifyStreamWith "(single subscriber)", srcF, expectedEvents, (src, events, done) ->
@@ -152,7 +163,7 @@
     before (done) ->
       collectF(src, events, done)
     it "outputs expected value in order", ->
-      expect(events).to.deep.equal(toValues(expectedEvents))
+      expect(toValues(events)).to.deep.equal(toValues(expectedEvents))
     it "the stream is exhausted", ->
        verifyExhausted src
     it "cleans up observers", verifyCleanup


Fix spec descriptions

Fix spec descriptions

--- spec/attributes.coffee
+++ spec/attributes.coffee
@@ -56,14 +56,14 @@
         beforeEach ->
           @resource.__links = {}

-        it 'returns the attribute', ->
+        it 'does not return the attribute', ->
           expect(@resource.attributes()['__links']).toBeUndefined()

       describe 'if attribute is a function', ->
         beforeEach ->
           @resource.myMethod = ->

-        it 'returns the attribute', ->
+        it 'does not return the attribute', ->
           expect(@resource.attributes()['myMethod']).toBeUndefined()

     describe '#reload()', ->


new rels

new rels

--- node/fabricate2.coffee
+++ node/fabricate2.coffee
@@ -35,7 +35,7 @@
       _links:
         curies: [
           name: "image"
-          href: "http://stagic3.artsy.net/profile_icons/504e2c4fbb39040002000483/{?rel}"
+          href: "http://stagic3.artsy.net/profile_icons/504e2c4fbb39040002000483/{rel}"
           templated: true
         ]
         thumbnail:


Add an afterTest func and Fix a test for markers

--- spec/jumpy-spec.coffee
+++ spec/jumpy-spec.coffee
@@ -70,7 +70,9 @@
         waitsForPromise ->
             statusBarPromise

-    describe 'activate', ->
+    afterEach ->
+        atom.commands.dispatch textEditorElement, 'jumpy:clear'
+
         it 'creates the commands', ->
             expect(hasCommand(workspaceElement, 'jumpy:toggle')).toBeTruthy()
             expect(hasCommand(workspaceElement, 'jumpy:reset')).toBeTruthy()
@@ -87,14 +89,13 @@

     describe "when the jumpy:toggle event is triggered", ->
         it "draws correct labels", ->
-            labels = textEditorElement.shadowRoot
-                .querySelectorAll '.jumpy.label'
-            expect(labels.length)
+            decorations = textEditor.getOverlayDecorations()
+            expect(decorations.length)
                 .toBe NUM_TOTAL_WORDS + NUM_CAMEL_SPECIFIC_MATCHES
-            expect(labels[0].innerHTML).toBe 'aa'
-            expect(labels[1].innerHTML).toBe 'ab'
-            expect(labels[82].innerHTML).toBe 'de'
-            expect(labels[83].innerHTML).toBe 'df'
+            expect(decorations[0].getProperties().item.textContent).toBe 'aa'
+            expect(decorations[1].getProperties().item.textContent).toBe 'ab'
+            expect(decorations[82].getProperties().item.textContent).toBe 'de'
+            expect(decorations[83].getProperties().item.textContent).toBe 'df'
         it "clears beacon effect", ->
             expect(textEditorElement.
                 querySelectorAll('cursors .cursor.beacon').length).toBe 0


more functional approach to rendering

more functional approach to rendering

--- intervals.coffee
+++ intervals.coffee
@@ -18,8 +18,8 @@
 string_spacing = 20
 fret_width = 45
 fret_overhang = .3 * fret_width
-fretboard_width_with_gutter = 2 * v_gutter + fret_width * fret_count + fret_overhang
-fretboard_height_with_gutter = 2 * h_gutter + (string_count - 1) * string_spacing
+padded_fretboard_width = 2 * v_gutter + fret_width * fret_count + fret_overhang
+padded_fretboard_height = 2 * h_gutter + (string_count - 1) * string_spacing

 draw_diagrams = true
 if draw_diagrams
@@ -29,8 +29,8 @@
   h_gutter = 5
   v_gutter = 5
   note_radius = 1
-  fretboard_width_with_gutter = 2 * h_gutter + (string_count - 1) * string_spacing
-  fretboard_height_with_gutter = 2 * v_gutter + fret_height * fret_count + fret_overhang
+  padded_fretboard_width = 2 * h_gutter + (string_count - 1) * string_spacing
+  padded_fretboard_height = 2 * v_gutter + fret_height * fret_count + fret_overhang

 build_directory = __dirname + '/build/'
 canvas = null
@@ -96,8 +96,8 @@
     for fret_number in frets
       y = fret_number * fret_height + v_gutter
       ctx.beginPath()
-      ctx.moveTo v_gutter, y
-      ctx.lineTo v_gutter + (string_count - 1) * string_spacing, y
+      ctx.moveTo v_gutter-.5, y
+      ctx.lineTo v_gutter+.5 + (string_count - 1) * string_spacing, y
       ctx.lineWidth = 3 if fret_number == 0
       ctx.stroke()
       ctx.lineWidth = 1
@@ -112,9 +112,6 @@
     ctx.arc v_gutter + (6 - string_number) * string_spacing, y, note_radius, 0, 2 * Math.PI, false
     ctx.fill() if fret_number
     ctx.stroke()
-    # ctx.fill() unless color and color.match(/rgba/)
-    # ctx.strokeStyle = color if color and color.match(/rgba/)
-    # ctx.stroke() if color and color.match(/rgba/)
     ctx.strokeStyle = 'black'

@@ -125,7 +122,7 @@
 note_number_at = (string, fret) ->
   string_note_numbers[string - 1] + fret

-positions_each = (fn) ->
+fingerings_each = (fn) ->
   for string in strings
     for fret in frets
       fn string, fret
@@ -146,8 +143,8 @@
       draw_note string, fret, false, color

 interval_cards = ->
-  positions_each (string, fret) ->
-    canvas = new Canvas(fretboard_width_with_gutter, fretboard_height_with_gutter)
+  fingerings_each (string, fret) ->
+    canvas = new Canvas(padded_fretboard_width, padded_fretboard_height)
     ctx = canvas.getContext('2d')
     erase_background()
     draw_fingerboard()
@@ -156,7 +153,7 @@
     save_canvas_to_png canvas, filename

     for interval_name, semitones in intervals
-      canvas = new Canvas(fretboard_width_with_gutter, fretboard_height_with_gutter)
+      canvas = new Canvas(padded_fretboard_width, padded_fretboard_height)
       ctx = canvas.getContext('2d')
       erase_background()
       draw_fingerboard()
@@ -168,8 +165,8 @@
 intervals_from_note_sheet = (string, fret, pdf) ->
   canvas_gutter = 20
   header_height = 40
-  canvas_width = fretboard_width_with_gutter * 3 + canvas_gutter * 3
-  canvas_height = (fretboard_height_with_gutter + header_height) * 4 + canvas_gutter * 3
+  canvas_width = padded_fretboard_width * 3 + canvas_gutter * 3
+  canvas_height = (padded_fretboard_height + header_height) * 4 + canvas_gutter * 3

   canvas = new Canvas(canvas_width, canvas_height, pdf and 'pdf') unless pdf and canvas
   ctx = canvas.getContext('2d')
@@ -177,8 +174,8 @@

   for interval_name, semitones in intervals
     ix = (semitones + 11) % 12
-    dx = (ix % 3) * (fretboard_width_with_gutter + canvas_gutter) + canvas_gutter / 2
-    dy = Math.floor(ix / 3) * (header_height + fretboard_height_with_gutter + canvas_gutter)
+    dx = (ix % 3) * (padded_fretboard_width + canvas_gutter) + canvas_gutter / 2
+    dy = Math.floor(ix / 3) * (header_height + padded_fretboard_height + canvas_gutter)
     ctx.save()
     ctx.translate dx, dy
     ctx.translate 0, header_height
@@ -194,11 +191,11 @@
   save_canvas_to_png canvas, filename unless pdf

 intervals_from_note_sheets = ->
-  positions_each (string, fret) ->
+  fingerings_each (string, fret) ->
     intervals_from_note_sheet string, fret

 intervals_from_root_book = ->
-  positions_each (string, fret) ->
+  fingerings_each (string, fret) ->
     intervals_from_note_sheet string, fret, true
     ctx.addPage()
   filename = "Fretboard Intervals by Root.pdf"
@@ -209,8 +206,8 @@
   header_height = 40
   cols = fret_count + 1
   rows = (string_count - hidden_strings.length)
-  canvas_width = fretboard_width_with_gutter * cols + canvas_gutter * cols
-  canvas_height = header_height + fretboard_height_with_gutter * rows + canvas_gutter * rows
+  canvas_width = padded_fretboard_width * cols + canvas_gutter * cols
+  canvas_height = header_height + padded_fretboard_height * rows + canvas_gutter * rows

   canvas = new Canvas(canvas_width, canvas_height, pdf and 'pdf') unless pdf and canvas
   ctx = canvas.getContext('2d')
@@ -222,21 +219,21 @@
   ctx.fillStyle = 'rgb(128, 128, 128)'
   ctx.fillText canvas_label, canvas_gutter / 2, header_height

-  positions_each (string, fret) ->
+  fingerings_each (string, fret) ->
     return if hidden_strings.indexOf(string) >= 0
     row = string - 1
     row -= hidden_strings.length if show_hidden_strings and string > hidden_strings[0]
-    dx = fret * (fretboard_width_with_gutter + canvas_gutter) + canvas_gutter / 2
-    dy = header_height + row * (fretboard_height_with_gutter + canvas_gutter) + canvas_gutter / 2
+    dx = fret * (padded_fretboard_width + canvas_gutter) + canvas_gutter / 2
+    dy = header_height + row * (padded_fretboard_height + canvas_gutter) + canvas_gutter / 2
     ctx.save()
     ctx.translate dx, dy
     draw_fingerboard()
     draw_intervals_from semitones, string, fret
     if show_hidden_strings
       ctx.fillStyle = 'rgba(255,255,255,0.75)'
-      ctx.fillRect 0, v_gutter + 1.5 * string_spacing, fretboard_width_with_gutter, hidden_strings.length * string_spacing
+      ctx.fillRect 0, v_gutter + 1.5 * string_spacing, padded_fretboard_width, hidden_strings.length * string_spacing
       ctx.fillStyle = 'rgba(0,0,0,0.0125)'
-      ctx.fillRect 0, v_gutter + 1.5 * string_spacing, fretboard_width_with_gutter, hidden_strings.length * string_spacing
+      ctx.fillRect 0, v_gutter + 1.5 * string_spacing, padded_fretboard_width, hidden_strings.length * string_spacing
     ctx.restore()

   unless pdf
@@ -260,66 +257,81 @@

 # intervals_from_root_book()

+mode = null
+pdf = false
+page = (width, height, draw_page) ->
+  return [width, height] if mode == 'measure'
+  canvas ||= new Canvas(width, height, pdf and 'pdf')
+  ctx = canvas.getContext('2d')
+  ctx.textDrawingMode = 'glyph' if pdf
+  erase_background()
+  draw_page ctx
+  unless pdf
+    filename = "test.pdf"
+    fs.writeFile build_directory + filename, canvas.toBuffer()
+
+grid = (cols, rows, cell_width, cell_height, header_height, draw_page) ->
+  page cols * cell_width, header_height + rows * cell_height, (ctx) ->
+    i = 0
+    draw_page ctx, (draw_cell) ->
+      ctx.save()
+      ctx.translate (i % cols) * cell_width, header_height + Math.floor(i / cols) * cell_height
+      draw_cell()
+      ctx.restore()
+      i += 1
+
 chord_page = (title, intervals, outervals, pdf) ->
   outervals ||= []

-  canvas_gutter = 20
+  diagram_gutter = 20
   header_height = 40
   diagram_title_height = 30
   cols = 3
   rows = 4
   if draw_diagrams
-    diagram_title_height = 15
-    canvas_gutter = 2
-  canvas_width = fretboard_width_with_gutter * cols + canvas_gutter * cols
-  canvas_height = header_height + (fretboard_height_with_gutter + diagram_title_height) * rows + canvas_gutter * rows
-
-  roots = []
-  positions_each (string, fret) ->
-    number = note_number_at(string, fret) % 12
-    roots[number] = [string, fret]
-
-  canvas = new Canvas(canvas_width, canvas_height, pdf and 'pdf') unless pdf and canvas
-  ctx = canvas.getContext('2d')
-  ctx.textDrawingMode = 'glyph' if pdf
-  erase_background()
-
-  ctx.font = '20px Impact'
-  ctx.fillStyle = 'rgb(128, 128, 128)'
-  ctx.fillText "#{title} Chords", canvas_gutter / 2, header_height
+    diagram_title_height = 35
+    diagram_gutter = 10
+
+  pitch_fingers = []
+  fingerings_each (string, fret) ->
+    pitch_number = note_number_at(string, fret) % 12
+    pitch_fingers[pitch_number] = [string, fret]

   note_names = "E F F# G G# A A# B C C# D D#".split(/\s/)
   colors = ['red', 'blue', 'green', 'orange']
   other_colors = ['rgba(255,0,0 ,.1)', 'rgba(0,0,255, 0.1)', 'rgba(0,255,0, 0.1)', 'rgba(255,0,255, 0.1)']

-  roots.map (note, ix) ->
-    note_number = (ix * 5 + 7) % 12
-    note = roots[note_number]
-    string = note[0]
-    fret = note[1]
-    col = ix % cols
-    row = Math.floor(ix / cols)
-    dx = col * (fretboard_width_with_gutter + canvas_gutter) + canvas_gutter / 2
-    dy = header_height + row * (fretboard_height_with_gutter + canvas_gutter + diagram_title_height) + canvas_gutter / 2
-    ctx.save()
-    ctx.translate dx, dy + diagram_title_height
+  grid cols, rows, padded_fretboard_width + diagram_gutter, padded_fretboard_height + diagram_gutter, diagram_title_height, (ctx, draw_cell) ->
     ctx.font = '20px Impact'
-    ctx.font = '5pt Times' if draw_diagrams
-    ctx.fillStyle = 'rgb(10,20,30)'
-    ctx.fillText note_names[note_number] + ' ' + title, h_gutter, -3
-    draw_fingerboard()
-    for semitones, si in outervals
-      continue if semitones in intervals
-      draw_intervals_from semitones, string, fret, other_colors[si]
-    for semitones, si in intervals
-      draw_intervals_from semitones, string, fret, colors[si]
-    ctx.restore()
-
-  unless pdf
-    filename = "test.pdf"
-    fs.writeFile build_directory + filename, canvas.toBuffer()
+    ctx.fillStyle = 'rgb(128, 128, 128)'
+    ctx.fillText "#{title} Chords", diagram_gutter / 2, header_height / 2
+
+    for ix in [0...12]
+      pitch_number = (ix * 5 + 7) % 12
+      fingering = pitch_fingers[pitch_number]
+      string = fingering[0]
+      fret = fingering[1]
+      draw_cell ->
+        ctx.font = '20px Impact'
+        ctx.font = '5pt Times' if draw_diagrams
+        ctx.fillStyle = 'rgb(10,20,30)'
+        ctx.fillText note_names[pitch_number] + ' ' + title, h_gutter, -3
+        draw_fingerboard()
+        for semitones, si in outervals
+          continue if semitones in intervals
+          draw_intervals_from semitones, string, fret, other_colors[si]
+        for semitones, si in intervals
+          draw_intervals_from semitones, string, fret, colors[si]

 # chord_page 'Major', [0, 4, 7]
+
+book = (filename, draw_book) ->
+  pdf = true
+  mode = 'draw'
+  draw_book (draw_page) ->
+    draw_page()
+    ctx.addPage()
+  fs.writeFile build_directory + filename, canvas.toBuffer()

 chord_book = ->
   chords = [
@@ -335,10 +347,8 @@
     ['Dim7', [0, 3, 6, 9]],
     ['6th', [0, 4, 7, 9]],
   ]
-  for chord in chords
-    chord_page chord[0], chord[1], chord[2], true
-    ctx.addPage()
-  filename = "Combined Fretboard Chords.pdf"
-  fs.writeFile build_directory + filename, canvas.toBuffer()
+  book "Combined Fretboard Chords.pdf", (pager) ->
+    for chord in chords
+      pager -> chord_page chord[0], chord[1], chord[2], true

 chord_book()


updating reading plan spec

updating reading plan spec

--- test/components/reading-plan.spec.coffee
+++ test/components/reading-plan.spec.coffee
@@ -8,12 +8,17 @@

 TEST_TOC = [
   {
-    id: '2'
-    title: 'Chapter title'
-    type: 'part'
-    children: [
-      {id: '1', type: 'page', title: 'Introduction'}
-    ]
+    "id":"1",
+    "title":"Updated Tutor HS Physics Content - legacy",
+    "type":"part",
+    children: [{
+      id: '2'
+      title: 'Chapter title'
+      type: 'part'
+      children: [
+        {id: '1', type: 'page', title: 'Introduction'}
+      ]
+    }]
   }
 ]


add nodoc references

add nodoc references

--- src/3d/Engine3D.coffee
+++ src/3d/Engine3D.coffee
@@ -38,10 +38,12 @@
     @statsManager = StatsManager.get()
     @statsManager.toggle() if @config.showStatsOnLoad

+  # @nodoc
   onDocumentMouseEvent: (event) =>
       raycaster = @_parseMouseEvent(event)
       @sceneManager.currentScene().doMouseEvent(event, raycaster) if raycaster?

+  # @nodoc
   onDocumentKeyboardEvent: (event) =>
       @sceneManager.currentScene().doKeyboardEvent(event)

@@ -79,6 +81,7 @@
     if @config.anaglyph
       @anaglyphEffect.render @sceneManager.currentScene().scene, @camera

+  # @nodoc
   _parseMouseEvent: (event) ->
     event.preventDefault() if @config.preventDefaultMouseEvents
     if event.target is @renderer.domElement


site: Rename var in newGreyhound

site: Rename var in newGreyhound

--- lib/site.coffee
+++ lib/site.coffee
@@ -36,12 +36,12 @@

   newGreyhound: (greyhound) ->
     num = 0
-    file = greyhound
-    while fs.existsSync "#{self.sitePath}/_greyhounds/#{file}.md"
+    fileName = greyhound
+    while fs.existsSync "#{self.sitePath}/_greyhounds/#{fileName}.md"
       num += 1
-      file = "#{greyhound}#{num}"
-    console.log "New greyhound #{file}"
-    return file
+      fileName = "#{greyhound}#{num}"
+    console.log "New greyhound #{fileName}"
+    return fileName

   loadGreyhound: (greyhound, callback) ->
     file = "#{self.sitePath}/_greyhounds/#{greyhound}.md"


Extract onMouseDown handler

Extract onMouseDown handler

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -35,28 +35,7 @@
     @on 'dblclick', '.tree-view-resize-handle', => @resizeToFitContent()
     @on 'click', '.entry', (e) =>
       @entryClicked(e) unless e.shiftKey or e.metaKey or e.ctrlKey
-    @on 'mousedown', '.entry', (e) =>
-      e.stopPropagation()
-      currentTarget = $(e.currentTarget)
-      # return early if we're opening a contextual menu (right click) during multi-select mode
-      return if @multiSelectEnabled() && currentTarget.hasClass('selected') &&
-                # mouse right click or ctrl click as right click on darwin platforms
-                (e.button is 2 || e.ctrlKey && process.platform is 'darwin')
-
-      entryToSelect = e.currentTarget
-
-      if e.shiftKey
-        @selectContinuousEntries(entryToSelect)
-        @showMultiSelectMenu()
-      # only allow ctrl click for multi selection on non darwin systems
-      else if e.metaKey || (e.ctrlKey && process.platform isnt 'darwin')
-        @selectMultipleEntries(entryToSelect)
-
-        # only show the multi select menu if more then one file/directory is selected
-        @showMultiSelectMenu() if @selectedPaths().length > 1
-      else
-        @selectEntry(entryToSelect)
-        @showFullMenu()
+    @on 'mousedown', '.entry', (e) => @onMouseDown(e)

     # turn off default scrolling behavior from ScrollView
     @off 'core:move-up'
@@ -264,7 +243,7 @@
     for pathComponent in activePathComponents
       currentPath += path.sep + pathComponent
       entry = @entryForPath(currentPath)
-      if entry.hasClass('directory')
+      if entry instanceof DirectoryView
         entry.expand()
       else
         centeringOffset = (@scrollBottom() - @scrollTop()) / 2
@@ -593,6 +572,31 @@
     @[0].offsetWidth
     @[0].style.display = 'block'

+  onMouseDown: (event) ->
+    event.stopPropagation()
+
+    # return early if we're opening a contextual menu (right click) during multi-select mode
+    if @multiSelectEnabled() and
+       event.currentTarget.classList.contains('selected') and
+       # mouse right click or ctrl click as right click on darwin platforms
+       (e.button is 2 or e.ctrlKey && process.platform is 'darwin')
+      return
+
+    entryToSelect = e.currentTarget
+
+    if e.shiftKey
+      @selectContinuousEntries(entryToSelect)
+      @showMultiSelectMenu()
+    # only allow ctrl click for multi selection on non darwin systems
+    else if e.metaKey or (e.ctrlKey && process.platform isnt 'darwin')
+      @selectMultipleEntries(entryToSelect)
+
+      # only show the multi select menu if more then one file/directory is selected
+      @showMultiSelectMenu() if @selectedPaths().length > 1
+    else
+      @selectEntry(entryToSelect)
+      @showFullMenu()
+
   onSideToggled: (newValue) ->
     @detach()
     @attach()


Don't automatically focus when attach is called

Only focus when toggling the tree view or revealing
an active file.

This prevents the tree view from stealing focus when it
is attached during deserialization.

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -72,15 +72,12 @@
     if @hasFocus()
       @detach()
     else
-      if @hasParent()
-        @focus()
-      else
-        @attach()
+      @attach() unless @hasParent()
+      @focus()

   attach: ->
     return unless project.getPath()
     rootView.horizontal.prepend(this)
-    @focus()

   detach: ->
     @scrollTopAfterAttach = @scrollTop()
@@ -134,6 +131,7 @@

   revealActiveFile: ->
     @attach()
+    @focus()

     return unless activeFilePath = rootView.getActiveEditor()?.getPath()


will be nil before client is installed

will be nil before client is installed

--- lib/uber-juno.coffee
+++ lib/uber-juno.coffee
@@ -26,7 +26,8 @@
     @setupPath()

   setupPath: ->
-    return if atom.config.get('julia-client.juliaPath') isnt 'julia'
+    current = atom.config.get 'julia-client.juliaPath'
+    return if current? and current isnt 'julia'
     pathfinder.juliaShell().then (valid) ->
       if not valid
         pathfinder.getpath().then (p) ->


search history

search history

--- lib/vim-state.coffee
+++ lib/vim-state.coffee
@@ -17,6 +17,7 @@
   constructor: (@editor) ->
     @opStack = []
     @history = []
+    @searchHistory = []
     @registers = {}
     @mode = 'command'

@@ -228,7 +229,7 @@
   #
   # Returns nothing
   pushSearchHistory: (search) ->
-    @history.push search
+    @searchHistory.push search

   ##############################################################################
   # Commands


Payment: fixed indentation

Payment: fixed indentation

--- client/Pricing/views/productform.coffee
+++ client/Pricing/views/productform.coffee
@@ -24,8 +24,8 @@
       multiple     : no
       callback     : =>
         KD.singleton("router").handleRoute switch @toggle.getValue()
-            when "DEVELOPER" then "/Pricing/Developer"
-            when "TEAM" then "/Pricing/Team"
+          when "DEVELOPER" then "/Pricing/Developer"
+          when "TEAM" then "/Pricing/Team"

   showDeveloperPlan: ->
     @toggle.setValue "DEVELOPER"


Klient: Indent promise chain

Klient: Indent promise chain

--- client/app/lib/kite/kites/klient.coffee
+++ client/app/lib/kite/kites/klient.coffee
@@ -174,15 +174,15 @@

         @tell 'storage.set', {key, value}

-        .then (res) ->
-          locked = no
-          consume()
-          resolve res
-
-        .catch (err) ->
-          locked = no
-          consume()
-          reject err
+          .then (res) ->
+            locked = no
+            consume()
+            resolve res
+
+          .catch (err) ->
+            locked = no
+            consume()
+            reject err

       new Promise (resolve, reject) ->
         queue.push {key, value, resolve, reject}


dataset recovery is no longer possible: open an intercom message instead

--- client/code/view/dataset/deleted.coffee
+++ client/code/view/dataset/deleted.coffee
@@ -8,14 +8,10 @@
     console.log @model
     @el.innerHTML = """

That dataset has been deleted.

-                    Click here to recover the dataset
+                    Contact us for recovery
                     """
     @

   recover: =>
-    @$el.find('#recover').addClass('loading').html('Recoving dataset…')
-    @model.recover()
-    setTimeout ->
-      window.location.reload()
-    , 1500
+    window.Intercom('show')


test/dashboard: convert myteamhelpers to teamHelpers

--- client/test/lib/dashboard/myteam.coffee
+++ client/test/lib/dashboard/myteam.coffee
@@ -2,7 +2,6 @@
 helpers = require '../helpers/helpers.js'
 teamsHelpers = require '../helpers/teamshelpers.js'
 utils = require '../utils/utils.js'
-myteamhelpers = require '../helpers/myteamhelpers.js'
 myTeamLink = "#{helpers.getUrl(yes)}/Home/my-team"

@@ -38,13 +37,13 @@
       .setValue teamNameSelector, 'random'
       .click saveChangesButton

-    myteamhelpers.assertConfirmation browser, successMessage
+    teamsHelpers.assertConfirmation browser, successMessage
     browser
       .clearValue teamNameSelector
       .setValue teamNameSelector, user.teamSlug
       .click saveChangesButton

-    myteamhelpers.assertConfirmation browser, successMessage
+    teamsHelpers.assertConfirmation browser, successMessage

     browser.end()

@@ -66,10 +65,10 @@
     browser
       .url myTeamLink, (result) ->
         if result.status is 0
-          myteamhelpers.inviteUsers browser, invitations, (res) ->
-            myteamhelpers.acceptAndJoinInvitation host, browser, invitations[indexOfTargetUser1], (res) ->
-              myteamhelpers.acceptAndJoinInvitation host, browser, invitations[indexOfTargetUser2], (res) ->
-                myteamhelpers.acceptAndJoinInvitation host, browser, invitations[indexOfTargetUser3], (res) ->
+          teamsHelpers.inviteUsers browser, invitations, (res) ->
+            teamsHelpers.acceptAndJoinInvitation host, browser, invitations[indexOfTargetUser1], (res) ->
+              teamsHelpers.acceptAndJoinInvitation host, browser, invitations[indexOfTargetUser2], (res) ->
+                teamsHelpers.acceptAndJoinInvitation host, browser, invitations[indexOfTargetUser3], (res) ->
                   browser
                     .url myTeamLink
                     .waitForElementVisible section, 20000
@@ -125,13 +124,13 @@
       .scrollToElement scrollElement
       .waitForElementVisible selector('role', 1), 20000
       .click selector('role', 1), ->
-        myteamhelpers.checkTeammates browser, invitations[0], nthItem(1), nthItem(2), selector('role', 1), no, ->
+        teamsHelpers.checkTeammates browser, invitations[0], nthItem(1), nthItem(2), selector('role', 1), no, ->
           browser.waitForElementVisible selector('role', 1), 20000
           browser.click selector('role', 2), ->
-            myteamhelpers.checkTeammates browser, invitations[1], nthItem(1), nthItem(2), selector('role', 2), no, ->
+            teamsHelpers.checkTeammates browser, invitations[1], nthItem(1), nthItem(2), selector('role', 2), no, ->
               browser.expect.element(selector('role', index + 1)).text.to.contain 'Owner'
               browser.click selector('role', lastPendingInvitationIndex + 1), ->
-                myteamhelpers.checkTeammates browser, invitations[lastPendingInvitationIndex], nthItem(1), nthItem(2), selector('role', lastPendingInvitationIndex + 1), yes, ->
+                teamsHelpers.checkTeammates browser, invitations[lastPendingInvitationIndex], nthItem(1), nthItem(2), selector('role', lastPendingInvitationIndex + 1), yes, ->
                   browser
                     .waitForElementNotPresent selector('fullname', lastPendingInvitationIndex + 1), 20000
                     .end()
@@ -141,9 +140,9 @@

     user = teamsHelpers.loginTeam browser
     browser.url myTeamLink
-    myteamhelpers.inviteUser browser, 'member'
+    teamsHelpers.inviteUser browser, 'member'
     browser.pause 5000
-    myteamhelpers.inviteUser browser, 'admin'
+    teamsHelpers.inviteUser browser, 'admin'
     browser.end()

@@ -151,7 +150,7 @@

     user = teamsHelpers.loginTeam browser
     browser.url myTeamLink
-    myteamhelpers.inviteAll browser
+    teamsHelpers.inviteAll browser
     browser.end()

@@ -159,7 +158,7 @@

     user = teamsHelpers.loginTeam browser
     browser.url myTeamLink
-    myteamhelpers.uploadCSV browser
+    teamsHelpers.uploadCSV browser
     browser.end()

@@ -167,9 +166,9 @@

     user = teamsHelpers.loginTeam browser
     browser.url myTeamLink
-    myteamhelpers.resendInvitation browser, 'member'
+    teamsHelpers.resendInvitation browser, 'member'
     browser.pause 5000
-    myteamhelpers.resendInvitation browser, 'admin'
+    teamsHelpers.resendInvitation browser, 'admin'
     browser.end()

@@ -177,9 +176,9 @@

     user = teamsHelpers.loginTeam browser
     browser.url myTeamLink
-    myteamhelpers.newInviteFromResendModal browser, 'member'
+    teamsHelpers.newInviteFromResendModal browser, 'member'
     browser.pause 5000
-    myteamhelpers.newInviteFromResendModal browser, 'admin'
+    teamsHelpers.newInviteFromResendModal browser, 'admin'
     browser.end()


rotation for asteroids for #74

rotation for asteroids for #74

--- ng-modules/travelScreen/ng-randy/Event.coffee
+++ ng-modules/travelScreen/ng-randy/Event.coffee
@@ -84,6 +84,7 @@
                     fname = 'assets/sprites/asteroids/' + n + '.png'
                 dimensions.r = "random"
                 dimensions.scale = "random"
+                dimensions.spin = "random"
                 return new Sprite(fname, dimensions, -1000, 'random')

             else
@@ -92,4 +93,4 @@
     trigger: ()->
         @count += 1
         @scope.$broadcast("encounter", @)
-        @sprite = @getSprite(@spriteKey)  # re-get sprite to re-randomize+        @sprite = @getSprite(@spriteKey)  # re-get sprite to re-randomize


fix test

fix test

--- components/split_test/split_test.coffee
+++ components/split_test/split_test.coffee
@@ -57,4 +57,4 @@
     if outcome?
       @set outcome
     else
-      @set toss()
+      @set @toss()


Changed again, no "TBD" 😶

Changed again, no "TBD" 😶

--- js/script.coffee
+++ js/script.coffee
@@ -57,7 +57,7 @@
 cb_spacex = (r) ->
 	table = document.createElement 'table'
 	for p in r
-		if p.launch_date_unix != 'TBD'
+		if p.launch_date_unix
 			core_reused = if p.reused then '*' else ''
 			cap_reused = if p.reuse.capsule then '*' else ''
 			t_minus = p.launch_date_unix - (new_date / 1000)