New Redis operations initial.

New Redis operations initial.

--- lib/RedisState.coffee
+++ lib/RedisState.coffee
@@ -5,7 +5,11 @@
 _ = require 'lodash'
 async = require 'async'

-{ bindTE, withEH, asyncLimit } = require './utils.coffee'
+{ asyncLimit
+  bindTE
+  extend
+  withEH
+} = require './utils.coffee'

 # @private
@@ -14,11 +18,30 @@

 # @private
 # @nodoc
-initState = (redis, state, values, cb) ->
-  unless values
-    return process.nextTick -> cb()
-  redis.del state, withEH cb, ->
-    redis.sadd state, values, cb
+initSet = (redis, set, values, cb) ->
+  redis.del set, withEH cb, ->
+    unless values
+      return process.nextTick -> cb()
+    redis.sadd set, values, cb
+
+
+# State init/remove operations.
+# @mixin
+# @private
+# @nodoc
+stateOperations =
+
+  # @private
+  initState : (state, cb) ->
+    @redis.setnx @makeKeyName('exists'), 1, @withTE cb, (exists) =>
+      if exists then return cb @errorBuilder.makeError @exitsErrorName, name
+      @stateReset state, @withTE cb, =>
+        @redis.set @makeKeyName('exists'), 1, @withTE cb
+
+  # @private
+  removeState : (cb) ->
+    @initState null, @withTE cb, =>
+      @redis.set @makeKeyName('exists'), 0, @withTE cb

 # Implements state API lists management.
@@ -27,14 +50,8 @@
 class ListsStateRedis

   # @private
-  makeDBListName : (listName) ->
-    #TODO
-    "#{namespace}:#{@prefix}:#{listName}:#{@name}"
-
-  # @private
-  makeDBHashName : (hashName) ->
-    #TODO
-    "#{namespace}:#{@prefix}:#{hashName}"
+  makeKeyName : (keyName) ->
+    "#{namespace}:#{@prefix}:{#{@name}}:#{keyName}"

   # @private
   checkList : (listName, cb) ->
@@ -45,43 +62,46 @@
   # @private
   addToList : (listName, elems, cb) ->
     @checkList listName, withEH cb, =>
-      @redis.sadd @makeDBListName(listName), elems, @withTE cb
+      @redis.sadd @makeKeyName(listName), elems, @withTE cb

   # @private
   removeFromList : (listName, elems, cb) ->
     @checkList listName, withEH cb, =>
-      @redis.srem @makeDBListName(listName), elems, @withTE cb
+      @redis.srem @makeKeyName(listName), elems, @withTE cb

   # @private
   getList : (listName, cb) ->
     @checkList listName, withEH cb, =>
-      @redis.smembers @makeDBListName(listName), @withTE cb, (data) ->
+      @redis.smembers @makeKeyName(listName), @withTE cb, (data) ->
         cb null, data

   # @private
   hasInList : (listName, elem, cb) ->
     @checkList listName, withEH cb, =>
-      @redis.sismember @makeDBListName(listName), elem, @withTE cb, (data) ->
+      @redis.sismember @makeKeyName(listName), elem, @withTE cb, (data) ->
         data = if data then true else false
         cb null, data

   # @private
   whitelistOnlySet : (mode, cb) ->
-    whitelistOnly = if mode then true else false
-    @redis.hset @makeDBHashName('whitelistmodes'), @name, whitelistOnly
+    whitelistOnly = if mode then 1 else 0
+    @redis.set @makeKeyName('whitelistMode'), whitelistOnly
     , @withTE cb

   # @private
   whitelistOnlyGet : (cb) ->
-    @redis.hget @makeDBHashName('whitelistmodes'), @name, @withTE cb
+    @redis.get @makeKeyName('whitelistMode'), @name, @withTE cb
     , (data) ->
-      cb null, JSON.parse data
+      result = if data then true else false
+      cb null, result

 # Implements room state API.
 # @private
 # @nodoc
 class RoomStateRedis extends ListsStateRedis
+
+  extend @, stateOperations

   # @private
   constructor : (@server, @name) ->
@@ -89,78 +109,69 @@
     bindTE @
     @historyMaxGetMessages = @server.historyMaxGetMessages
     @historyMaxMessages = @server.historyMaxMessages
-    @redis = @server.state.redis
-    @prefix = 'room'
+    @redis = @server.redis
+    @exitsErrorName = 'roomExists'
+    @prefix = 'rooms'

   # @private
   hasList : (listName) ->
     return listName in [ 'adminlist', 'whitelist', 'blacklist', 'userlist' ]

   # @private
-  initState : (state = {}, cb) ->
-    #TODO
+  stateReset : (state = {}, cb) ->
     { whitelist, blacklist, adminlist
     , lastMessages, whitelistOnly, owner } = state
     async.parallel [
       (fn) =>
-        initState @redis, @makeDBListName('whitelist'), whitelist, fn
-      (fn) =>
-        initState @redis, @makeDBListName('blacklist'), blacklist, fn
-      (fn) =>
-        initState @redis, @makeDBListName('adminlist'), adminlist, fn
-      (fn) =>
-        unless whitelistOnly then return fn()
-        @redis.hset @makeDBHashName('whitelistmodes'), @name, whitelistOnly, fn
-      (fn) =>
-        unless owner then return fn()
-        @redis.hset @makeDBHashName('owners'), @name, owner, fn
-    ] , @withTE cb
-
-  # @private
-  removeState : (cb) ->
-    #TODO
-    async.parallel [
-      (fn) =>
-        @redis.del @makeDBListName('whitelist'), @makeDBListName('blacklist')
-        , @makeDBListName('adminlist'), @makeDBListName('history')
-        , fn
-      (fn) =>
-        @redis.hdel @makeDBHashName('whitelistmodes'), @name, fn
-      (fn) =>
-        @redis.hdel @makeDBHashName('owners'), @name, fn
-    ] , @withTE cb
+        initSet @redis, @makeKeyName('whitelist'), whitelist, fn
+      (fn) =>
+        initSet @redis, @makeKeyName('blacklist'), blacklist, fn
+      (fn) =>
+        initSet @redis, @makeKeyName('adminlist'), adminlist, fn
+      (fn) =>
+        initSet @redis, @makeKeyName('userlist'), null, fn
+      (fn) =>
+        redis.del @makeKeyName('messagesHistory'), fn
+      (fn) =>
+        redis.del @makeKeyName('messagesTimestamps'), fn
+      (fn) =>
+        redis.del @makeKeyName('messagesIDs'), fn
+      (fn) =>
+        redis.set @makeKeyName('lastMessageID'), 0, fn
+      (fn) =>
+        whitelistOnly = if whitelistOnly then 1 else 0
+        @redis.set @makeKeyName('whitelistMode'), whitelistOnly, fn
+      (fn) =>
+        owner = '' unless owner
+        @redis.set @makeKeyName('owner'), owner, fn
+    ] , cb

   # @private
   ownerGet : (cb) ->
-    #TODO
-    @redis.hget @makeDBHashName('owners'), @name, @withTE cb
+    @redis.get @makeKeyName('owner'), @withTE cb

   # @private
   ownerSet : (owner, cb) ->
-    #TODO
-    @redis.hset @makeDBHashName('owners'), @name, owner, @withTE cb
+    @redis.set @makeKeyName('owner'), owner, @withTE cb

   # @private
   messageAdd : (msg, cb) ->
     #TODO
-    if @historyMaxMessages <= 0 then return process.nextTick -> cb()
-    val = JSON.stringify msg
-    @redis.lpush @makeDBListName('history'), val, @withTE cb, =>
-      @redis.ltrim @makeDBListName('history'), 0, @historyMaxMessages - 1
-      , @withTE cb

   # @private
   messagesGetRecent : (cb) ->
     #TODO
-    @redis.lrange @makeDBListName('history'), 0, @historyMaxGetMessages - 1
-    , @withTE cb, (data) ->
-      messages = _.map data, JSON.parse
-      cb null, messages
+
+  messagesGetLastId : (cb) ->
+    @redis.get @makeKeyName('lastMessageID'), @withTE cb
+
+  messagesGetAfterId : (id, cb) ->
+    #TODO

   # @private
   getCommonUsers : (cb) ->
-    @redis.sdiff @makeDBListName('userlist'), @makeDBListName('whitelist')
-    , @makeDBListName('adminlist'), @withTE cb
+    @redis.sdiff @makeKeyName('userlist'), @makeKeyName('whitelist')
+    , @makeKeyName('adminlist'), @withTE cb

 # Implements direct messaging state API.
@@ -168,42 +179,33 @@
 # @nodoc
 class DirectMessagingStateRedis extends ListsStateRedis

+  extend @, stateOperations
+
   # @private
   constructor : (@server, @userName) ->
-    @name = @userName
-    @prefix = 'direct'
-    @redis = @server.state.redis
     @errorBuilder = @server.errorBuilder
     bindTE @
+    @name = @userName
+    @prefix = 'user'
+    @exitsErrorName = 'userExists'
+    @redis = @server.redis

   # @private
   hasList : (listName) ->
     return listName in [ 'whitelist', 'blacklist' ]

   # @private
-  initState : (state = {}, cb) ->
-    #TODO
+  resetState : (state = {}, cb) ->
     { whitelist, blacklist, whitelistOnly } = state
     async.parallel [
       (fn) =>
-        initState @redis, @makeDBListName('whitelist'), whitelist, fn
-      (fn) =>
-        initState @redis, @makeDBListName('blacklist'), blacklist, fn
-      (fn) =>
-        unless whitelistOnly then return fn()
-        @redis.hset @makeDBHashName('whitelistmodes'), @name, whitelistOnly, fn
-    ] , @withTE cb
-
-  # @private
-  removeState : (cb) ->
-    #TODO
-    async.parallel [
-      (fn) =>
-        @redis.del @makeDBListName('whitelist'), @makeDBListName('blacklist')
-        , fn
-      (fn) =>
-        @redis.hdel @makeDBHashName('whitelistmodes'), @name, fn
-    ] , @withTE cb
+        initSet @redis, @makeKeyName('whitelist'), whitelist, fn
+      (fn) =>
+        initSet @redis, @makeKeyName('blacklist'), blacklist, fn
+      (fn) =>
+        whitelistOnly = if whitelistOnly then 1 else 0
+        @redis.set @makeKeyName('whitelistMode'), whitelistOnly, fn
+    ] , cb

 # Implements user state API.
@@ -215,22 +217,22 @@
   constructor : (@server, @userName) ->
     @name = @userName
     @prefix = 'user'
-    @redis = @server.state.redis
+    @redis = @server.redis
     @errorBuilder = @server.errorBuilder
     @echoChannel = @makeEchoChannelName @userName
     bindTE @

   # @private
-  makeDBListName : (listName) ->
-    "#{namespace}:#{@prefix}:#{listName}:#{@name}"
+  makeKeyName : (keyName) ->
+    "#{namespace}:#{@prefix}:{#{@name}}:#{keyName}"

   # @private
   makeSocketToRoomsName : (id) ->
-    "#{namespace}:#{@prefix}:socketrooms:#{id}"
+    "#{namespace}:#{@prefix}:{#{@name}}:socketsToRooms:#{id}"

   # @private
   makeRoomToSocketsName : (id) ->
-    "#{namespace}:#{@prefix}:roomsockets:#{id}"
+    "#{namespace}:#{@prefix}:{#{@name}}:roomsToSockets:#{id}"

   # @private
   makeEchoChannelName : (userName) ->
@@ -302,14 +304,16 @@
     @server.redis = @redis

   # @private
-  makeDBListName : (hashName) ->
-    "#{namespace}:#{hashName}"
-
-  makeLockName : (name) ->
-    "#{namespace}:locks:#{name}"
-
-  makeDBSocketsName : (inst) ->
-    "#{namespace}:instancesockets:#{inst}"
+  makeKeyName : (prefix, name, keyName) ->
+    "#{namespace}:#{prefix}:{#{name}}:#{keyName}"
+
+  # @private
+  hasRoom : (name, cb) ->
+    @redis.get @makeKeyName('rooms', name, 'exists'), @withTE cb
+
+  # @private
+  hasUser : (name, cb) ->
+    @redis.get @makeKeyName('user', name, 'exists'), @withTE cb

   # @private
   close : (cb) ->
@@ -318,64 +322,49 @@

   # @private
   getRoom : (name, cb) ->
-    @redis.sismember @makeDBListName('rooms'), name, @withTE cb, (data) =>
-      unless data
-        error = @errorBuilder.makeError 'noRoom', name
-        return cb error
+    @hasRoom name, withEH cb, (exists) =>
+      unless exists then return cb @errorBuilder.makeError 'noRoom', name
       room = new Room @server, name
       cb null, room

   # @private
   addRoom : (name, state, cb) ->
     room = new Room @server, name
-    @redis.sadd @makeDBListName('rooms'), name, @withTE cb, (nadded) =>
-      if nadded != 1
-        return cb @errorBuilder.makeError 'roomExists', name
-      if state
-        room.initState state, cb
-      else
-        cb()
+    room.initState state, cb

   # @private
   removeRoom : (name, cb) ->
-    @redis.sismember @makeDBListName('rooms'), name, @withTE cb, (data) =>
-      unless data
-        return cb @errorBuilder.makeError 'noRoom', name
-      @redis.srem @makeDBListName('rooms'), name, @withTE cb
+    @hasRoom name, withEH cb, (exists) =>
+      unless exists then return cb @errorBuilder.makeError 'noRoom', name
+      room = new Room @server, name
+      room.removeState cb

   # @private
   listRooms : (cb) ->
-    @redis.smembers @makeDBListName('rooms'), @withTE cb
+    #TODO

   # @private
   removeSocket : (uid, id, cb) ->
     #TODO
-    @redis.srem @makeDBSocketsName(uid), id, @withTE cb

   # @private
   loginUserSocket : (uid, name, id, cb) ->
     user = new User @server, name
-    @redis.sadd @makeDBListName('users'), name, @withTE cb, ->
+    user.initState null, ->
       user.registerSocket id, cb

   # @private
   getUser : (name, cb) ->
-    #TODO sockets
     user = new User @server, name
-    @redis.sismember @makeDBListName('users'), name, @withTE cb, (data) =>
-      if data then return cb null, user
-      else return cb @errorBuilder.makeError 'noUser', name
+    @hasUser name, withEH cb, (exists) =>
+      user.getAllSockets name, withEH cb, (sockets) =>
+        unless exists then return cb @errorBuilder.makeError 'noUser', name
+        cb null, user, sockets

   # @private
   addUser : (name, state, cb) ->
-    @redis.sadd @makeDBListName('users'), name, @withTE cb, (nadded) ->
-      if nadded == 0
-        return cb @errorBuilder.makeError 'userExists', name
-      if state
-        user = new User @server, name
-        user.initState state, cb
-      else
-        cb()
+    user = new User @server, name
+    user.initState state, cb

 module.exports = RedisState


Make sure the container is visible also

Make sure the container is visible also

--- app/js/jail_iframe/classes/paragraph_buttons.coffee
+++ app/js/jail_iframe/classes/paragraph_buttons.coffee
@@ -34,8 +34,11 @@
       'div#page', 'div.page', 'div#site', 'div.site'
     ]

-    for s in selectors when $(s).length == 1 # Only match if selector is unique
-      return "#{s} p, #{s} h2, #{s} h3, #{s} h4, #{s} h5, #{s} h6, #{s} li, #{s} dd, #{s} dt"
+    for s in selectors
+      $element = $(s)
+      # Only match if selector is unique
+      if $element.length == 1 && $element.is(':visible')
+        return "#{s} p, #{s} h2, #{s} h3, #{s} h4, #{s} h5, #{s} h6, #{s} li, #{s} dd, #{s} dt"

     null


Client: Activity: Added onDropdownItemConfirmed method. When dropdown item selected it invokes channel addparticipant action

--- client/activity/lib/components/channelparticipantavatars/index.coffee
+++ client/activity/lib/components/channelparticipantavatars/index.coffee
@@ -242,6 +242,20 @@
     </div>

+  onDropdownItemConfirmed: (item) ->
+
+    channelId   = @props.channelThread.get 'channelId'
+    participant = @state.selectedItem
+
+    options =
+      userId            : participant.get '_id'
+      channelId         : @props.channelThread.get 'channelId'
+      accountIds        : [ whoami().socialApiId, participant.get 'socialApiId' ]
+
+    { channel } = ActivityFlux.actions
+    channel.addParticipants options
+
+
   renderAddNewChannelParticipantsDropdown: ->

     <ChannelParticipantsDropdown


app thumbs corrected on starttab

app thumbs corrected on starttab

--- client/landing/app/Applications/StartTab.kdapplication/AppView.coffee
+++ client/landing/app/Applications/StartTab.kdapplication/AppView.coffee
@@ -10,14 +10,14 @@
     @listenWindowResize()

     mainView = @getSingleton('mainView')
-
+
     # mainView.sidebar.finderResizeHandle.on "DragInAction", =>
     #   log "DragInAction", mainView.contentPanel.getWidth()
-
+
     # mainView.sidebar.finderResizeHandle.on "DragFinished", =>
     #   @utils.wait 301, =>
     #     log "DragFinished", mainView.contentPanel.getWidth()
-
+
     @loader = new KDLoaderView
       size          :
         width       : 128
@@ -29,10 +29,11 @@
         color       : "#ff9200"

     @button = new KDButtonView
-      cssClass : "editor-button"
-      title    : "refresh apps"
-      loader   : yes
-      callback : =>
+      cssClass    : "editor-button"
+      title       : "refresh apps"
+      loader      :
+        diameter  : 16
+      callback    : =>
         @removeAppIcons()
         @loader.show()
         @getSingleton("kodingAppsController").refreshApps (err, apps)=>
@@ -50,7 +51,7 @@
           storage.update $set : { "bucket.apps" : {} }, =>
             @loader.hide()
             log arguments, "kodingAppsController storage cleared"
-
+
     @appItemContainer = new AppItemContainer
       cssClass : 'app-item-container'
       delegate : @
@@ -61,7 +62,7 @@

     @recentFilesWrapper = new KDView
       cssClass : 'file-container'
-
+
   viewAppended:->

     super
@@ -72,15 +73,15 @@

   _windowDidResize:->

-
+

   addApps:->
-
+
     for app in apps
-      @appItemContainer.addSubView new StartTabOldAppView
+      @appItemContainer.addSubView new StartTabOldAppView
         tab : @
       , app
-
+
   pistachio:->
     """

To start from a new file, select an editor or open an existing file from your file tree

@@ -99,9 +100,9 @@
       {{> @recentFilesWrapper}}

     """
-
+
   addRealApps:->
-
+
     @removeAppIcons()
     @loader.show()

@@ -118,7 +119,7 @@
       @noAppsWarning.show()

   removeAppIcons:->
-
+
     @appItemContainer.destroySubViews()
     @appIcons = {}

@@ -172,7 +173,7 @@
   addRecentFiles:->

     @recentFileViews = {}
-
+
     appManager.fetchStorage 'Finder', '1.0', (err, storage)=>

       storage.on "update", => @updateRecentFileViews()
@@ -185,22 +186,22 @@
         @getSingleton('mainController').on "NoSuchFile", (file)=>
           recentFilePaths.splice recentFilePaths.indexOf(file.path), 1
           # log "updating storage", recentFilePaths.length
-          storage.update {
+          storage.update {
             $set: 'bucket.recentFiles': recentFilePaths
           }, => log "storage updated"
-
+

   updateRecentFileViews:()->
-
+
     appManager.fetchStorage 'Finder', '1.0', (err, storage)=>

       recentFilePaths = storage.getAt('bucket.recentFiles')
       # log "updating views", recentFilePaths.length
-
+
       for path, view of @recentFileViews
         @recentFileViews[path].destroy()
         delete @recentFileViews[path]
-
+
       if recentFilePaths?.length
         recentFilePaths.forEach (filePath)=>
           @recentFileViews[filePath] = new StartTabRecentFileView {}, filePath
@@ -265,25 +266,30 @@

     options.tagName    = 'figure'
     options.attributes = href : '#'
-
-    if data.disabled?
+
+    if data.disabled?
       options.cssClass += ' disabled'
-    else if data.catalog?
+    else if data.catalog?
       options.cssClass += ' appcatalog'

     super options, data
-
-    {icns} = @getData()
+
+    {icns, name, version} = @getData()
+    if icns and (icns['256'] or icns['512'] or icns['128'] or icns['160'] or icns['64'])
+      thumb = "#{KD.appsUri}/sinan/#{name}/#{version}/#{if icns then icns['256'] or icns['512'] or icns['128'] or icns['160'] or icns['64']}"
+    else
+      thumb = "#{KD.apiUri + '/images/default.app.listthumb.png'}"
+
     @imgHolder = new KDView
       tagName : "p"
-      partial : ""
+      partial : ""

     @loader = new KDLoaderView
       size          :
         width       : 40

   showLoader:->
-
+
     @loader.show()
     @imgHolder.$().css "opacity", "0.5"

@@ -301,7 +307,7 @@
     """

 class StartTabOldAppView extends KDView
-
+
   constructor:(options, data)->
     newClass = if data.disabled? then 'start-tab-item disabled' else if data.catalog? then 'start-tab-item appcatalog' else 'start-tab-item'
     options = $.extend
@@ -309,11 +315,11 @@
       cssClass    : newClass
     , options
     super options, data
-
+
   viewAppended:->
     @setTemplate @pistachio()
     @template.update()
-
+
   pistachio:->
     {image} = @getData()
     """
@@ -321,13 +327,13 @@
       {{ #(name)}}
       {{ #(type)}}
     """
-
+
   click:(event)->
     {appToOpen, disabled} = @getData()
     {tab}                 = @getOptions()
     if appToOpen isnt "Apps"
       appManager.replaceStartTabWithApplication appToOpen, tab unless disabled
-    else
+    else
       appManager.openApplication appToOpen

@@ -341,20 +347,20 @@
         template  : '

'
     , options
     super options, data
-
+
   pistachio:->
     path = @getData()
     name = (path.split '/')[(path.split '/').length - 1]
     extension = __utils.getFileExtension name
     fileType  = __utils.getFileType extension
-
+
     """

         #{name}

     """
-
+
   click:(event)->
     # appManager.notify()
     file = FSHelper.createFileFromPath @getData()
@@ -370,5 +376,4 @@
       else
         file.contents = contents
         appManager.openFile file
-
-      +


If anon user, display login or enrollment chooser

--- src/course/registration.cjsx
+++ src/course/registration.cjsx
@@ -2,6 +2,7 @@

 NewCourseRegistration = require './new-registration'
 ModifyCourseRegistration = require './modify-registration'
+EnrollOrLogin = require './enroll-or-login'

 UserStatus = require '../user/status-mixin'
 Course = require './model'
@@ -14,11 +15,14 @@
   mixins: [UserStatus]

   render: ->
-    course = @getUser().getCourse(@props.collectionUUID)
+    user = @getUser()
+    course = user.getCourse(@props.collectionUUID)
     body = if course and course.isRegistered()
       <ModifyCourseRegistration {...@props} course={course} />
+    else if user.isLoggedIn()
+      <NewCourseRegistration {...@props} />
     else
-      <NewCourseRegistration {...@props} />
+      <EnrollOrLogin {...@props} />

     <div className="row">
       {body}


slideshowShell: play subshells when they load

--- coffee/src/shells/slideshow.shell.coffee
+++ coffee/src/shells/slideshow.shell.coffee
@@ -27,6 +27,10 @@

   className: @classNameExtend 'slideshow-shell'
+
+
+  defaults: => _.extend super,
+    playOnReady: true

   _initializeControlsView: =>


VideoLinkShell: pass self when fwding events.

--- coffee/src/shells/videolink.shell.coffee
+++ coffee/src/shells/videolink.shell.coffee
@@ -115,7 +115,12 @@
       eventhub: @eventhub
       noControls: true

-    @listenTo @playerView, 'all', _.bind(@trigger, @)
+    @listenTo @playerView, 'all', =>
+      # replace @playerView with @
+      args = _.map arguments, (arg) =>
+        if arg is @playerView then @ else arg
+
+      @trigger.apply @, args

     @on 'Media:StateChange', @togglePlayPauseControls


modified status-check@

modified status-check@

--- scripts/status-check.coffee
+++ scripts/status-check.coffee
@@ -15,6 +15,7 @@
     for u, s in urls #u: url, s:status
       robot.logger.info u, s #@@
       robot.emit 'healthcheck:url', {url: u, status: s}
+
 #    robot.emit 'healthcheck:url', {url: 'https://www.google.com', status: 200}
 #    robot.emit 'healthcheck:url', {url: 'http://takamachi.com/hogehoge', status: 200}
 #    robot.emit 'healthcheck:url', {url: 'http://sasukene.info', status: 500}


Fix order of `site` doc in feeds view

Fix order of `site` doc in feeds view

--- site/server/views.coffee
+++ site/server/views.coffee
@@ -80,7 +80,7 @@
     types = ['essay','scene','video','profile']
     if doc.type is 'site'
       # Add the site doc
-      emit [doc._id, null], null
+      emit [doc._id, {}], null
     else if doc.site and doc.type and types.indexOf(doc.type) >= 0 and doc.published_at and doc.published
       timestamp = new Date(doc.published_at).getTime()
       emit [doc.site, timestamp, doc._id, {}], null


Minor fix, check if jsType exists

Minor fix, check if jsType exists

--- src/access/table-gateway.coffee
+++ src/access/table-gateway.coffee
@@ -60,7 +60,7 @@
         for key, value of values
             property = @schema.columnsByProperty[key]

-            converter = property?.jsType.convert
+            converter = property?.jsType?.convert
             continue if (!_.isFunction(converter))

             values[key] = converter(value)


Remove unnecesary code

Remove unnecesary code

--- source/quo.gestures.swipe.coffee
+++ source/quo.gestures.swipe.coffee
@@ -31,7 +31,6 @@

     move = (target, data) ->
       if data.length is 1
-        console.log target
         delta = x: (data[0].x - _start.x), y: (data[0].y - _start.y)
         is_first = _last is null
         _last = x: data[0].x, y: data[0].y, delta: delta


[fix] nodeWrapper.toString() was returning undefined for nodes without locationData

--- src/NodeWrapper.coffee
+++ src/NodeWrapper.coffee
@@ -161,6 +161,7 @@
         if @childName then answer += "#{@childName}[#{@childIndex}]:"
         answer += @type
         if @node.locationData? then answer += " (#{@node.locationData?.first_line + 1}:#{@node.locationData.first_column + 1})"
+        answer

 forNodeAndChildren = (node, fn) ->
     fn node


Fix action_header_style definition to work with observables

--- js/inner.coffee
+++ js/inner.coffee
@@ -161,11 +161,8 @@
       success: @komp => @status() == "success"
       failed: @komp => @status() == "failed"

-    @action_header_button_style = @komp =>
-      if @has_content()
-        @action_header_style()
-      else
-        {}
+    @action_header_button_style =
+      @action_header_style

     @action_log_style =
       minimize: @minimize


Switch driver.button to use driver.content

--- source/webdriverjs-helper.coffee
+++ source/webdriverjs-helper.coffee
@@ -170,7 +170,7 @@
     @findElement webdriver.By.partialLinkText partialText

   button: (label) ->
-    @findElement webdriver.By.xpath('//button[text()="' + label + '"]')
+    @content label, 'button'

   dropdownlist: (selector) -> @element selector


Correct references to renamed CSS classes.

--- spec/conflict-marker-spec.coffee
+++ spec/conflict-marker-spec.coffee
@@ -33,25 +33,17 @@
     it 'adds the conflicted class', ->
       expect(editorView.hasClass 'conflicted').toBe(true)

-    it 'removes any old classes', ->
-      line = editorView.find('.line').eq(2)
-      line.addClass "ours"
-      m.remark()
-      expect(line.hasClass 'ours').toBe(false)
-
     it 'locates the correct lines', ->
       lines = m.linesForMarker m.conflicts[1].ours.marker
       expect(lines.text()).toBe("My middle changes")

     it 'applies the "ours" class to our sides of conflicts', ->
       lines = m.linesForMarker m.conflicts[0].ours.marker
-      expect(lines.hasClass 'conflict-line').toBe(true)
-      expect(lines.hasClass 'ours').toBe(true)
+      expect(lines.hasClass 'conflict-ours').toBe(true)

     it 'applies the "theirs" class to their sides of conflicts', ->
       lines = m.linesForMarker m.conflicts[0].theirs.marker
-      expect(lines.hasClass 'conflict-line').toBe(true)
-      expect(lines.hasClass 'theirs').toBe(true)
+      expect(lines.hasClass 'conflict-theirs').toBe(true)

     it 'applies the "dirty" class to modified sides', ->
       editor = editorView.getEditor()
@@ -61,15 +53,14 @@

       m.remark()
       lines = m.linesForMarker m.conflicts[1].ours.marker
-      expect(lines.hasClass 'dirty').toBe(true)
-      expect(lines.hasClass 'ours').toBe(false)
+      expect(lines.hasClass 'conflict-dirty').toBe(true)
+      expect(lines.hasClass 'conflict-ours').toBe(false)

     it 'applies the "resolved" class to resolved conflicts', ->
       m.conflicts[1].ours.resolve()
       m.remark()
       lines = m.linesForMarker m.conflicts[1].ours.marker
-      expect(lines.hasClass 'conflict-line').toBe(true)
-      expect(lines.hasClass 'resolved').toBe(true)
+      expect(lines.hasClass 'conflict-resolved').toBe(true)

     it 'broadcasts the "merge-conflicts:resolved" event', ->
       event = null
@@ -169,10 +160,6 @@
         expect(editorView.find('.overlayer .side').length).toBe(0)
         expect(editorView.find('.overlayer .navigation').length).toBe(0)

-      it 'removes all line classes', ->
-        for klass in ['ours', 'theirs', 'parent', 'dirty']
-          expect(editorView.find(".lines .#{klass}").length).toBe(0)
-
       it 'removes the .conflicted class', ->
         expect(editorView.hasClass 'conflicted').toBe(false)


invert keymap and separate out definitions

--- assets/js/keyBindings.coffee
+++ assets/js/keyBindings.coffee
@@ -6,7 +6,355 @@
   Cursor = require('./cursor.coffee')
   Menu = require('./menu.coffee')
   actions = require('./actions.coffee')
-
+  _ = require('underscore')
+
+# display:
+#   is displayed in keybindings help screen
+#
+# each should have 1 of the following four
+# fn:
+#   takes a view and mutates it
+#   if this is a motion, takes an extra cursor argument first
+# continue:
+#   a function which takes next key
+# bindings:
+#   a dictionary from keyDefinitions to functions
+#   if the key is 'MOTION', the function takes a cursor # TODO: make that less janky
+# motion:
+#   if the binding is a motion
+keyDefinitions =
+  HELP:
+    display: 'Show/hide key bindings'
+    drop: true
+    fn: () ->
+      @keybindingsDiv.toggleClass 'active'
+      if localStorage?
+        localStorage['showKeyBindings'] = @keybindingsDiv.hasClass 'active'
+
+  ZOOM_IN:
+    display: 'Zoom in by one level'
+    fn: () ->
+      do @view.rootDown
+  ZOOM_OUT:
+    display: 'Zoom out by one level'
+    fn: () ->
+      do @view.rootUp
+  ZOOM_IN_ALL:
+    display: 'Zoom in onto cursor'
+    fn: () ->
+      do @view.rootInto
+  ZOOM_OUT_ALL:
+    display: 'Zoom out to home'
+    fn: () ->
+      do @view.reroot
+
+  INDENT_RIGHT:
+    display: 'Indent row right'
+    fn: () ->
+      do @view.indent
+  INDENT_LEFT:
+    display: 'Indent row left'
+    fn: () ->
+      do @view.unindent
+  MOVE_BLOCK_RIGHT:
+    display: 'Move block right'
+    fn: () ->
+      do @view.indentBlock
+  MOVE_BLOCK_LEFT:
+    display: 'Move block left'
+    fn: () ->
+      do @view.unindentBlock
+  MOVE_BLOCK_DOWN:
+    display: 'Move block down'
+    fn: () ->
+      do @view.swapDown
+  MOVE_BLOCK_UP:
+    display: 'Move block up'
+    fn: () ->
+      do @view.swapUp
+
+  TOGGLE_FOLD:
+    display: 'Toggle whether a block is folded'
+    fn: () ->
+      do @view.toggleCurBlock
+
+  # content-based navigation
+
+  SEARCH:
+    display: 'Search'
+    drop: true
+    menu: (view, text) ->
+      # a list of {contents, highlights, fn}
+      # SEE: menu.coffee
+      results = []
+
+      selectRow = (row) ->
+        view.rootInto row
+
+      for found in view.find text
+        row = found.row
+        index = found.index
+
+        highlights = {}
+        for i in [index ... index + text.length]
+          highlights[i] = true
+
+        results.push {
+          contents: view.data.getLine row
+          highlights: highlights
+          fn: selectRow.bind(@, row)
+        }
+      return results
+
+  # traditional vim stuff
+  INSERT:
+    display: 'Insert at character'
+    insert: true
+    fn: () -> return
+  INSERT_AFTER:
+    display: 'Insert after character'
+    insert: true
+    fn: () ->
+      @view.cursor.right {cursor: 'pastEnd'}
+  INSERT_HOME:
+    display: 'Insert at beginning of line'
+    insert: true
+    fn: () ->
+      do @view.cursor.home
+  INSERT_END:
+    display: 'Insert after end of line'
+    insert: true
+    fn: () ->
+      @view.cursor.end {cursor: 'pastEnd'}
+  INSERT_LINE_BELOW:
+    display: 'Insert on new line after current line'
+    insert: true
+    fn: () ->
+      do @view.newLineBelow
+  INSERT_LINE_ABOVE:
+    display: 'Insert on new line before current line'
+    insert: true
+    fn: () ->
+      do @view.newLineAbove
+  REPLACE:
+    display: 'Replace character'
+    continue: (char) ->
+      num = Math.min(@repeat, do @view.curLineLength - @view.cursor.col)
+      newChars = (char for i in [1..num])
+      @view.spliceCharsAfterCursor num, newChars, {cursor: 'beforeEnd'}
+
+  # EX:
+  #   display: 'Enter EX mode'
+
+  UNDO:
+    display: 'Undo'
+    drop: true
+    fn: () ->
+      for i in [1..@repeat]
+        do @view.undo
+  REDO:
+    display: 'Redo'
+    drop: true
+    fn: () ->
+      for i in [1..@repeat]
+        do @view.redo
+  REPLAY:
+    display: 'Replay last command'
+
+  LEFT:
+    display: 'Move cursor left'
+    motion: true
+    fn: (cursor, option) ->
+      cursor.left {cursor: option}
+  RIGHT:
+    display: 'Move cursor right'
+    motion: true
+    fn: (cursor, option) ->
+      cursor.right {cursor: option}
+  UP:
+    display: 'Move cursor up'
+    motion: true
+    fn: (cursor, option) ->
+      cursor.up {cursor: option}
+  DOWN:
+    display: 'Move cursor down'
+    motion: true
+    fn: (cursor, option) ->
+      cursor.down {cursor: option}
+  HOME:
+    display: 'Move cursor to beginning of line'
+    motion: true
+    fn: (cursor, option) ->
+      cursor.home {cursor: option}
+  END:
+    display: 'Move cursor to end of line'
+    motion: true
+    fn: (cursor, option) ->
+      cursor.end {cursor: option}
+  BEGINNING_WORD:
+    display: 'Move cursor to the first word-beginning before it'
+    motion: true
+    fn: (cursor, option) ->
+      cursor.beginningWord {cursor: option}
+  END_WORD:
+    display: 'Move cursor to the first word-ending after it'
+    motion: true
+    fn: (cursor, option) ->
+      cursor.endWord {cursor: option}
+  NEXT_WORD:
+    display: 'Move cursor to the beginning of the next word'
+    motion: true
+    fn: (cursor, option) ->
+      cursor.nextWord {cursor: option}
+  BEGINNING_BLOCK:
+    display: 'Move cursor to the first block-beginning before it'
+    motion: true
+    fn: (cursor, option) ->
+      cursor.beginningWord {cursor: option, block: true}
+  END_BLOCK:
+    display: 'Move cursor to the first block-ending after it'
+    motion: true
+    fn: (cursor, option) ->
+      cursor.endWord {cursor: option, block: true}
+  NEXT_BLOCK:
+    display: 'Move cursor to the beginning of the next block'
+    motion: true
+    fn: (cursor, option) ->
+      cursor.nextWord {cursor: option, block: true}
+  FIND_NEXT_CHAR:
+    display: 'Move cursor to next occurrence of character in line'
+    motion: true
+    continue: (char, cursor, option) ->
+      cursor.findNextChar char, {cursor: option}
+  FIND_PREV_CHAR:
+    display: 'Move cursor to previous occurrence of character in line'
+    motion: true
+    continue: (char, cursor, option) ->
+      cursor.findPrevChar char, {cursor: option}
+  TO_NEXT_CHAR:
+    display: 'Move cursor to just before next occurrence of character in line'
+    motion: true
+    continue: (char, cursor, option) ->
+      cursor.findNextChar char, {cursor: option, beforeFound: true}
+  TO_PREV_CHAR:
+    display: 'Move cursor to just after previous occurrence of character in line'
+    motion: true
+    continue: (char, cursor, option) ->
+      cursor.findPrevChar char, {cursor: option, beforeFound: true}
+
+  NEXT_SIBLING:
+    display: 'Move cursor to the next sibling of the current line'
+    motion: true
+    fn: (cursor, option) ->
+      cursor.nextSibling {cursor: option}
+
+  PREV_SIBLING:
+    display: 'Move cursor to the previous sibling of the current line'
+    motion: true
+    fn: (cursor, option) ->
+      cursor.prevSibling {cursor: option}
+
+  GO:
+    display: 'Various commands for navigation (operator)'
+    motion: true
+    bindings:
+      GO:
+        display: 'Go to the beginning of visible document'
+        motion: true
+        fn: (cursor, option) ->
+          do cursor.visibleHome
+  GO_END:
+    display: 'Go to end of visible document'
+    motion: true
+    fn: (cursor, option) ->
+      do cursor.visibleEnd
+  DELETE_CHAR:
+    display: 'Delete character'
+    fn: () ->
+      @view.delCharsAfterCursor @repeat, {yank: true}
+  DELETE_LAST_CHAR:
+    display: 'Delete last character'
+    fn: () ->
+      num = Math.min @view.cursor.col, @repeat
+      if num > 0
+        @view.delCharsBeforeCursor num, {yank: true}
+  CHANGE_CHAR:
+    display: 'Change character'
+    insert: true
+    fn: () ->
+      @view.delCharsAfterCursor 1, {cursor: 'pastEnd'}, {yank: true}
+  DELETE:
+    display: 'Delete (operator)'
+    bindings:
+      DELETE:
+        display: 'Delete blocks'
+        fn: () ->
+          @view.delBlocks @repeat, {addNew: false}
+      MOTION:
+        display: 'Delete from cursor with motion'
+        fn: (cursor) ->
+          @view.deleteBetween @view.cursor, cursor, {yank: true}
+  CHANGE:
+    display: 'Change (operator)'
+    bindings:
+      CHANGE:
+        display: 'Delete blocks, and enter insert mode'
+        insert: true
+        fn: () ->
+          @view.delBlocks @repeat, {addNew: true}
+      MOTION:
+        display: 'Delete from cursor with motion, and enter insert mode'
+        insert: true
+        fn: (cursor) ->
+          @view.deleteBetween @view.cursor, cursor, {cursor: 'pastEnd', yank: true}
+
+  YANK:
+    display: 'Yank (operator)'
+    bindings:
+      YANK:
+        display: 'Yank blocks'
+        drop: true
+        fn: () ->
+          @view.yankBlocks @repeat
+      MOTION:
+        display: 'Yank from cursor with motion'
+        drop: true
+        fn: (cursor) ->
+          @view.yankBetween @view.cursor, cursor
+  PASTE_AFTER:
+    display: 'Paste after cursor'
+    fn: () ->
+      do @view.pasteAfter
+  PASTE_BEFORE:
+    display: 'Paste before cursor'
+    fn: () ->
+      do @view.pasteBefore
+
+  JOIN_LINE:
+    display: 'Join current line with line below'
+    fn: () ->
+      do @view.joinAtCursor
+
+  SCROLL_DOWN:
+    display: 'Scroll half window down'
+    drop: true
+    fn: () ->
+      @view.scrollPages 0.5
+  SCROLL_UP:
+    display: 'Scroll half window up'
+    drop: true
+    fn: () ->
+      @view.scrollPages -0.5
+  EXPORT:
+    display: 'Save a file'
+    fn: () ->
+      do @view.data.export
+  RECORD_MACRO:
+    display: 'Begin/stop recording a macro'
+  PLAY_MACRO:
+    display: 'Play a macro'
+
+# end of keyDefinitions

 # manages a stream of keys, with the ability to
 # - queue keys
@@ -59,352 +407,6 @@

 class KeyBindings

-  # display:
-  #   is displayed in keybindings help screen
-  #
-  # each should have 1 of the following four
-  # fn:
-  #   takes a view and mutates it
-  # continue:
-  #   a function which takes next key
-  # bindings:
-  #   a dictionary from keyDefinitions to functions
-  #   if the key is 'MOTION', the function takes a cursor # TODO: make that less janky
-  # motion:
-  #   if the key can be used as a motion, then this is a function
-  #   taking a cursor and mutating it
-  keyDefinitions =
-    HELP:
-      display: 'Show/hide key bindings'
-      drop: true
-      fn: () ->
-        @keybindingsDiv.toggleClass 'active'
-        if localStorage?
-          localStorage['showKeyBindings'] = @keybindingsDiv.hasClass 'active'
-
-    ZOOM_IN:
-      display: 'Zoom in by one level'
-      fn: () ->
-        do @view.rootDown
-    ZOOM_OUT:
-      display: 'Zoom out by one level'
-      fn: () ->
-        do @view.rootUp
-    ZOOM_IN_ALL:
-      display: 'Zoom in onto cursor'
-      fn: () ->
-        do @view.rootInto
-    ZOOM_OUT_ALL:
-      display: 'Zoom out to home'
-      fn: () ->
-        do @view.reroot
-
-    INDENT_RIGHT:
-      display: 'Indent row right'
-      fn: () ->
-        do @view.indent
-    INDENT_LEFT:
-      display: 'Indent row left'
-      fn: () ->
-        do @view.unindent
-    MOVE_BLOCK_RIGHT:
-      display: 'Move block right'
-      fn: () ->
-        do @view.indentBlock
-    MOVE_BLOCK_LEFT:
-      display: 'Move block left'
-      fn: () ->
-        do @view.unindentBlock
-    MOVE_BLOCK_DOWN:
-      display: 'Move block down'
-      fn: () ->
-        do @view.swapDown
-    MOVE_BLOCK_UP:
-      display: 'Move block up'
-      fn: () ->
-        do @view.swapUp
-
-    TOGGLE_FOLD:
-      display: 'Toggle whether a block is folded'
-      fn: () ->
-        do @view.toggleCurBlock
-
-    # content-based navigation
-
-    SEARCH:
-      display: 'Search'
-      drop: true
-      menu: (view, text) ->
-        # a list of {contents, highlights, fn}
-        # SEE: menu.coffee
-        results = []
-
-        selectRow = (row) ->
-          view.rootInto row
-
-        for found in view.find text
-          row = found.row
-          index = found.index
-
-          highlights = {}
-          for i in [index ... index + text.length]
-            highlights[i] = true
-
-          results.push {
-            contents: view.data.getLine row
-            highlights: highlights
-            fn: selectRow.bind(@, row)
-          }
-        return results
-
-    # traditional vim stuff
-    INSERT:
-      display: 'Insert at character'
-      insert: true
-      fn: () -> return
-    INSERT_AFTER:
-      display: 'Insert after character'
-      insert: true
-      fn: () ->
-        @view.cursor.right {cursor: 'pastEnd'}
-    INSERT_HOME:
-      display: 'Insert at beginning of line'
-      insert: true
-      fn: () ->
-        do @view.cursor.home
-    INSERT_END:
-      display: 'Insert after end of line'
-      insert: true
-      fn: () ->
-        @view.cursor.end {cursor: 'pastEnd'}
-    INSERT_LINE_BELOW:
-      display: 'Insert on new line after current line'
-      insert: true
-      fn: () ->
-        do @view.newLineBelow
-    INSERT_LINE_ABOVE:
-      display: 'Insert on new line before current line'
-      insert: true
-      fn: () ->
-        do @view.newLineAbove
-    REPLACE:
-      display: 'Replace character'
-      continue: (char) ->
-        num = Math.min(@repeat, do @view.curLineLength - @view.cursor.col)
-        newChars = (char for i in [1..num])
-        @view.spliceCharsAfterCursor num, newChars, {cursor: 'beforeEnd'}
-
-    # EX:
-    #   display: 'Enter EX mode'
-
-    UNDO:
-      display: 'Undo'
-      drop: true
-      fn: () ->
-        for i in [1..@repeat]
-          do @view.undo
-    REDO:
-      display: 'Redo'
-      drop: true
-      fn: () ->
-        for i in [1..@repeat]
-          do @view.redo
-    REPLAY:
-      display: 'Replay last command'
-
-    LEFT:
-      display: 'Move cursor left'
-      motion: true
-      fn: (cursor, option) ->
-        cursor.left {cursor: option}
-    RIGHT:
-      display: 'Move cursor right'
-      motion: true
-      fn: (cursor, option) ->
-        cursor.right {cursor: option}
-    UP:
-      display: 'Move cursor up'
-      motion: true
-      fn: (cursor, option) ->
-        cursor.up {cursor: option}
-    DOWN:
-      display: 'Move cursor down'
-      motion: true
-      fn: (cursor, option) ->
-        cursor.down {cursor: option}
-    HOME:
-      display: 'Move cursor to beginning of line'
-      motion: true
-      fn: (cursor, option) ->
-        cursor.home {cursor: option}
-    END:
-      display: 'Move cursor to end of line'
-      motion: true
-      fn: (cursor, option) ->
-        cursor.end {cursor: option}
-    BEGINNING_WORD:
-      display: 'Move cursor to the first word-beginning before it'
-      motion: true
-      fn: (cursor, option) ->
-        cursor.beginningWord {cursor: option}
-    END_WORD:
-      display: 'Move cursor to the first word-ending after it'
-      motion: true
-      fn: (cursor, option) ->
-        cursor.endWord {cursor: option}
-    NEXT_WORD:
-      display: 'Move cursor to the beginning of the next word'
-      motion: true
-      fn: (cursor, option) ->
-        cursor.nextWord {cursor: option}
-    BEGINNING_BLOCK:
-      display: 'Move cursor to the first block-beginning before it'
-      motion: true
-      fn: (cursor, option) ->
-        cursor.beginningWord {cursor: option, block: true}
-    END_BLOCK:
-      display: 'Move cursor to the first block-ending after it'
-      motion: true
-      fn: (cursor, option) ->
-        cursor.endWord {cursor: option, block: true}
-    NEXT_BLOCK:
-      display: 'Move cursor to the beginning of the next block'
-      motion: true
-      fn: (cursor, option) ->
-        cursor.nextWord {cursor: option, block: true}
-    FIND_NEXT_CHAR:
-      display: 'Move cursor to next occurrence of character in line'
-      motion: true
-      continue: (char, cursor, option) ->
-        cursor.findNextChar char, {cursor: option}
-    FIND_PREV_CHAR:
-      display: 'Move cursor to previous occurrence of character in line'
-      motion: true
-      continue: (char, cursor, option) ->
-        cursor.findPrevChar char, {cursor: option}
-    TO_NEXT_CHAR:
-      display: 'Move cursor to just before next occurrence of character in line'
-      motion: true
-      continue: (char, cursor, option) ->
-        cursor.findNextChar char, {cursor: option, beforeFound: true}
-    TO_PREV_CHAR:
-      display: 'Move cursor to just after previous occurrence of character in line'
-      motion: true
-      continue: (char, cursor, option) ->
-        cursor.findPrevChar char, {cursor: option, beforeFound: true}
-
-    NEXT_SIBLING:
-      display: 'Move cursor to the next sibling of the current line'
-      motion: true
-      fn: (cursor, option) ->
-        cursor.nextSibling {cursor: option}
-
-    PREV_SIBLING:
-      display: 'Move cursor to the previous sibling of the current line'
-      motion: true
-      fn: (cursor, option) ->
-        cursor.prevSibling {cursor: option}
-
-    GO:
-      display: 'Various commands for navigation (operator)'
-      motion: true
-      bindings:
-        GO:
-          display: 'Go to the beginning of visible document'
-          motion: true
-          fn: (cursor, option) ->
-            do cursor.visibleHome
-    GO_END:
-      display: 'Go to end of visible document'
-      motion: true
-      fn: (cursor, option) ->
-        do cursor.visibleEnd
-    DELETE_CHAR:
-      display: 'Delete character'
-      fn: () ->
-        @view.delCharsAfterCursor @repeat, {yank: true}
-    DELETE_LAST_CHAR:
-      display: 'Delete last character'
-      fn: () ->
-        num = Math.min @view.cursor.col, @repeat
-        if num > 0
-          @view.delCharsBeforeCursor num, {yank: true}
-    CHANGE_CHAR:
-      display: 'Change character'
-      insert: true
-      fn: () ->
-        @view.delCharsAfterCursor 1, {cursor: 'pastEnd'}, {yank: true}
-    DELETE:
-      display: 'Delete (operator)'
-      bindings:
-        DELETE:
-          display: 'Delete blocks'
-          fn: () ->
-            @view.delBlocks @repeat, {addNew: false}
-        MOTION:
-          display: 'Delete from cursor with motion'
-          fn: (cursor) ->
-            @view.deleteBetween @view.cursor, cursor, {yank: true}
-    CHANGE:
-      display: 'Change (operator)'
-      bindings:
-        CHANGE:
-          display: 'Delete blocks, and enter insert mode'
-          insert: true
-          fn: () ->
-            @view.delBlocks @repeat, {addNew: true}
-        MOTION:
-          display: 'Delete from cursor with motion, and enter insert mode'
-          insert: true
-          fn: (cursor) ->
-            @view.deleteBetween @view.cursor, cursor, {cursor: 'pastEnd', yank: true}
-
-    YANK:
-      display: 'Yank (operator)'
-      bindings:
-        YANK:
-          display: 'Yank blocks'
-          drop: true
-          fn: () ->
-            @view.yankBlocks @repeat
-        MOTION:
-          display: 'Yank from cursor with motion'
-          drop: true
-          fn: (cursor) ->
-            @view.yankBetween @view.cursor, cursor
-    PASTE_AFTER:
-      display: 'Paste after cursor'
-      fn: () ->
-        do @view.pasteAfter
-    PASTE_BEFORE:
-      display: 'Paste before cursor'
-      fn: () ->
-        do @view.pasteBefore
-
-    JOIN_LINE:
-      display: 'Join current line with line below'
-      fn: () ->
-        do @view.joinAtCursor
-
-    SCROLL_DOWN:
-      display: 'Scroll half window down'
-      drop: true
-      fn: () ->
-        @view.scrollPages 0.5
-    SCROLL_UP:
-      display: 'Scroll half window up'
-      drop: true
-      fn: () ->
-        @view.scrollPages -0.5
-    EXPORT:
-      display: 'Save a file'
-      fn: () ->
-        do @view.data.export
-    RECORD_MACRO:
-      display: 'Begin/stop recording a macro'
-    PLAY_MACRO:
-      display: 'Play a macro'
-
   MODES =
     NORMAL: 0
     INSERT: 1
@@ -412,89 +414,101 @@
     MENU: 3

   defaultVimKeyBindings =
-    '?': 'HELP'
-    'i': 'INSERT'
-    'a': 'INSERT_AFTER'
-    'I': 'INSERT_HOME'
-    'A': 'INSERT_END'
-    'o': 'INSERT_LINE_BELOW'
-    'O': 'INSERT_LINE_ABOVE'
-    'r': 'REPLACE'
-    # ':': 'EX'
-    'u': 'UNDO'
-    'ctrl+r': 'REDO'
-    '.': 'REPLAY'
-    'h': 'LEFT'
-    'left': 'LEFT'
-    'l': 'RIGHT'
-    'right': 'RIGHT'
-    'k': 'UP'
-    'up': 'UP'
-    'j': 'DOWN'
-    'down': 'DOWN'
-    '0': 'HOME'
-    '^': 'HOME'
-    '$': 'END'
-    'b': 'BEGINNING_WORD'
-    'e': 'END_WORD'
-    'w': 'NEXT_WORD'
-    'B': 'BEGINNING_BLOCK'
-    'E': 'END_BLOCK'
-    'W': 'NEXT_BLOCK'
-    'f': 'FIND_NEXT_CHAR'
-    'F': 'FIND_PREV_CHAR'
-    't': 'TO_NEXT_CHAR'
-    'T': 'TO_PREV_CHAR'
-    'g': 'GO'
-    'G': 'GO_END'
-    'd': 'DELETE'
-    'c': 'CHANGE'
-    'x': 'DELETE_CHAR'
-    'X': 'DELETE_LAST_CHAR'
-    's': 'CHANGE_CHAR'
-    'y': 'YANK'
-    'p': 'PASTE_AFTER'
-    'P': 'PASTE_BEFORE'
-    'J': 'JOIN_LINE'
-
-    'tab': 'INDENT_RIGHT'
-    'shift+tab': 'INDENT_LEFT'
-    '>': 'MOVE_BLOCK_RIGHT'
-    '<': 'MOVE_BLOCK_LEFT'
-    'ctrl+l': 'MOVE_BLOCK_RIGHT'
-    'ctrl+h': 'MOVE_BLOCK_LEFT'
-    'ctrl+j': 'MOVE_BLOCK_DOWN'
-    'ctrl+k': 'MOVE_BLOCK_UP'
-
-    'alt+h': 'ZOOM_OUT'
-    'alt+l': 'ZOOM_IN'
-    'alt+j': 'NEXT_SIBLING'
-    'alt+k': 'PREV_SIBLING'
-
-    'ctrl+s': 'EXPORT'
-
-    'z': 'TOGGLE_FOLD'
-    '[': 'ZOOM_OUT'
-    ']': 'ZOOM_IN'
-    '{': 'ZOOM_OUT_ALL'
-    '}': 'ZOOM_IN_ALL'
-    'ctrl+left': 'ZOOM_OUT'
-    'ctrl+right': 'ZOOM_IN'
-    'ctrl+d': 'SCROLL_DOWN'
-    'ctrl+u': 'SCROLL_UP'
-
-    '/': 'SEARCH'
-    'ctrl+f': 'SEARCH'
-    'q': 'RECORD_MACRO'
-    '@': 'PLAY_MACRO'
+    'HELP'              : ['?']
+    'INSERT'            : ['i']
+    'INSERT_HOME'       : ['I']
+    'INSERT_AFTER'      : ['a']
+    'INSERT_END'        : ['A']
+    'INSERT_LINE_BELOW' : ['o']
+    'INSERT_LINE_ABOVE' : ['O']
+
+    'REPLACE'           : ['r']
+    'UNDO'              : ['u']
+    'REDO'              : ['ctrl+r']
+    'REPLAY'            : ['.']
+
+    'LEFT'              : ['h', 'left']
+    'RIGHT'             : ['l', 'right']
+    'UP'                : ['k', 'up']
+    'DOWN'              : ['j', 'down']
+    'HOME'              : ['0', '^']
+    'END'               : ['$']
+    'BEGINNING_WORD'    : ['b']
+    'END_WORD'          : ['e']
+    'NEXT_WORD'         : ['w']
+    'BEGINNING_BLOCK'   : ['B']
+    'END_BLOCK'         : ['E']
+    'NEXT_BLOCK'        : ['W']
+    'FIND_NEXT_CHAR'    : ['f']
+    'FIND_PREV_CHAR'    : ['F']
+    'TO_NEXT_CHAR'      : ['t']
+    'TO_PREV_CHAR'      : ['T']
+
+    'GO'                : ['g']
+    'GO_END'            : ['G']
+    'DELETE'            : ['d']
+    'CHANGE'            : ['c']
+    'DELETE_CHAR'       : ['x']
+    'DELETE_LAST_CHAR'  : ['X']
+    'CHANGE_CHAR'       : ['s']
+    'YANK'              : ['y']
+    'PASTE_AFTER'       : ['p']
+    'PASTE_BEFORE'      : ['P']
+    'JOIN_LINE'         : ['J']
+
+    'INDENT_RIGHT'      : ['tab']
+    'INDENT_LEFT'       : ['shift+tab']
+    'MOVE_BLOCK_RIGHT'  : ['>', 'ctrl+l']
+    'MOVE_BLOCK_LEFT'   : ['<', 'ctrl+h']
+    'MOVE_BLOCK_DOWN'   : ['ctrl+j']
+    'MOVE_BLOCK_UP'     : ['ctrl+k']
+
+    'NEXT_SIBLING'      : ['alt+j']
+    'PREV_SIBLING'      : ['alt+k']
+
+    'TOGGLE_FOLD'       : ['z']
+    'ZOOM_OUT'          : ['[', 'alt+h', 'ctrl+left']
+    'ZOOM_IN'           : [']', 'alt+l', 'ctrl+right']
+    'ZOOM_OUT_ALL'      : ['{']
+    'ZOOM_IN_ALL'       : ['}']
+    'SCROLL_DOWN'       : ['ctrl+d']
+    'SCROLL_UP'         : ['ctrl+u']
+
+    'SEARCH'            : ['/', 'ctrl+f']
+    'RECORD_MACRO'      : ['q']
+    'PLAY_MACRO'        : ['@']
+
+    'EXPORT'            : ['ctrl+s']
+
+
+  # takes keyDefinitions and keyMaps, and combines them
+  getBindings = (definitions, keyMap) ->
+    bindings = {}
+    for name, v of definitions
+      if name == 'MOTION'
+        keys = ['MOTION']
+      else if (name of keyMap)
+        keys = keyMap[name]
+      else
+        throw "Error:  keyMap missing key for #{name}"
+
+      v = _.clone v
+      v.name = name
+      if v.bindings
+        v.bindings = getBindings v.bindings, keyMap
+
+      for key in keys
+        if key of bindings
+          throw "Error:  Duplicate binding on key #{key}"
+        bindings[key] = v
+    return bindings

   constructor: (view, divs = {}) ->
     @view = view

-    @bindings = keyDefinitions
-    @keyMap = {}
-    for k, v of defaultVimKeyBindings
-      @keyMap[k] = v
+    @keyMap = _.clone defaultVimKeyBindings
+
+    @bindings = getBindings keyDefinitions, @keyMap

     if divs.keyBindingsDiv
       @keybindingsDiv = divs.keyBindingsDiv
@@ -518,33 +532,29 @@
       do @view.save

   buildBindingsDiv: () ->
-    typeToKeys = {}
-    for k,v of @keyMap
-      if not typeToKeys[v]
-        typeToKeys[v] = []
-      typeToKeys[v].push k
-
     table = $('<table>')

-    buildTableContents = (bindings, onto) ->
-      for k,v of bindings
+    buildTableContents = (definitions, onto) =>
+      for k,v of definitions
         if k == 'MOTION'
           keys = ['<MOTION>']
         else
-          keys = typeToKeys[k]
+          keys = @keyMap[k]
           if not keys
             continue
         row = $('<tr>')
-        row.append $('<td>').text keys[0]
+
+        # row.append $('<td>').text keys[0]
+        row.append $('<td>').text keys.join(' OR ')
+
         display_cell = $('<td>').css('width', '100%').text v.display
-
-        row.append display_cell
         if v.bindings
           buildTableContents v.bindings, display_cell
-        # row.append $('<td>').text typeToKeys[k].join(' OR ')
+        row.append display_cell
+
         onto.append row

-    buildTableContents @bindings, table
+    buildTableContents keyDefinitions, table
     @keybindingsDiv.empty().append(table)

   setMode: (mode) ->
@@ -560,9 +570,6 @@
       @keybindingsDiv.toggleClass 'hidden', (mode == MODES.MENU)
     if @view.mainDiv
       @view.mainDiv.toggleClass 'hidden', (mode == MODES.MENU)
-
-  getKey: (name) ->
-    return @bindings[name].key

   handleKey: (key) ->
     console.log('handling', key)
@@ -637,8 +644,7 @@
         do keyStream.wait
         return null

-      binding = @keyMap[motionKey]
-      info = bindings[binding] || {}
+      info = bindings[motionKey] || {}
       if not info.motion
         do keyStream.forget
         return null
@@ -685,11 +691,10 @@
       # TODO: something better for passing repeat through?
       repeat = repeat * newrepeat

-      binding = @keyMap[key]
       fn = null
       args = []

-      if not (binding of bindings)
+      if not (key of bindings)
         if 'MOTION' of bindings
           info = bindings['MOTION']

@@ -705,7 +710,7 @@
         else
           return do keyStream.forget
       else
-        info = bindings[binding] || {}
+        info = bindings[key] || {}

       if info.motion
         motion = getMotion key
@@ -748,7 +753,7 @@
         else
           return do keyStream.save

-      if binding == 'RECORD_MACRO'
+      if info.name == 'RECORD_MACRO'
         if @recording == null
           nkey = do keyStream.dequeue
           if nkey == null then return do keyStream.wait
@@ -761,7 +766,7 @@
           @recording = null
           @recording_key = null
         return do keyStream.forget
-      if binding == 'PLAY_MACRO'
+      if info.name == 'PLAY_MACRO'
           nkey = do keyStream.dequeue
           if nkey == null then return do keyStream.wait
           recording = @macros[nkey]
@@ -774,7 +779,7 @@
           # but we should save the macro-playing sequence itself
           return do keyStream.save

-      if binding == 'REPLAY'
+      if info.name == 'REPLAY'
         for i in [1..repeat]
           newStream = new KeyStream @keyStream.lastSequence
           newStream.on 'save', () =>


[grunt] fix not renamed url to href

[grunt] fix not renamed url to href

--- tasks/grunt-nunjucks-2-html.coffee
+++ tasks/grunt-nunjucks-2-html.coffee
@@ -224,7 +224,7 @@
            * @return {string} Logs to Grunt console
           ###
           env.addGlobal 'warn', (input...) ->
-            grunt.log.error(input..., '[' + this.ctx.page.url + ']')
+            grunt.log.error(input..., '[' + this.ctx.page.href + ']')

           ###*
            * Get list of files or directories inside specified directory


Remove catch

Remove catch

--- spec/package-manager-spec.coffee
+++ spec/package-manager-spec.coffee
@@ -839,8 +839,6 @@
         fs.mkdirSync(autocompleteCSSPath)
         fs.writeFileSync(path.join(autocompleteCSSPath, 'package.json'), '{}')
         fs.symlinkSync(path.join(fixturePath, 'package-with-main'), autocompletePlusPath, 'dir')
-      catch e
-        undefined

       expect(fs.isDirectorySync(autocompleteCSSPath)).toBe true
       expect(fs.isSymbolicLinkSync(autocompletePlusPath)).toBe true


Added tests for the 'animateFullImage' helper.

--- spec/rambling.slider.spec.coffee
+++ spec/rambling.slider.spec.coffee
@@ -217,11 +217,14 @@
     beforeEach ->
       options =
         effect: 'newTransition'
+        afterChange: ->
         imageTransitions:
           newTransition: ->
             helper = @
             called = true
       spyOn(options.imageTransitions, 'newTransition').andCallThrough()
+      spyOn($.fn, 'trigger').andCallThrough()
+      spyOn(options, 'afterChange')
       create_slider options
       interval_callback()

@@ -329,6 +332,54 @@
         it 'should set the expected slice image', ->
           expect(result.find('img').attr 'src').toEqual slide_element.attr('src')

+    describe 'and calling the animate full image helper function', ->
+      beforeEach ->
+        spyOn($.fn, 'css').andCallThrough()
+        spyOn($.fn, 'animate').andCallFake (options, speed, easing, callback) -> callback() if callback
+
+      describe 'as is', ->
+        beforeEach ->
+          result = helper.animateFullImage ->
+
+        it 'should call the after change callback', ->
+          expect(options.afterChange).toHaveBeenCalled()
+
+        it 'should raise the rambling:finished event', ->
+          expect($.fn.trigger).toHaveBeenCalledWith 'rambling:finished'
+
+      describe 'and it is aligned to the top', ->
+        beforeEach ->
+          rambling_slider.ramblingSlider 'option', 'alignBottom', false
+          result = helper.animateFullImage ->
+
+        it 'should set the top to 0 and the bottom to auto', ->
+          expect($.fn.css).toHaveBeenCalledWith top: '0', bottom: 'auto'
+
+      describe 'and it is aligned to the bottom', ->
+        beforeEach ->
+          rambling_slider.ramblingSlider 'option', 'alignBottom', true
+          result = helper.animateFullImage ->
+
+        it 'should set the top to auto and the bottom to 0', ->
+          expect($.fn.css).toHaveBeenCalledWith top: 'auto', bottom: '0'
+
+      describe 'and nothing is returned by the animation set up callback', ->
+        beforeEach ->
+          result = helper.animateFullImage ->
+
+        it 'should animate the slice width to the width of the slider', ->
+          expect($.fn.animate).toHaveBeenCalledWith {width: "#{rambling_slider.width()}px"}, $.fn.ramblingSlider.defaults.speed * 2, '', jasmine.any(Function)
+
+      describe 'and something is returned by the animation set up callback', ->
+        animate = null
+
+        beforeEach ->
+          animate = height: '500px'
+          result = helper.animateFullImage -> animate
+
+        it 'should animate the slice width to the width of the slider', ->
+          expect($.fn.animate).toHaveBeenCalledWith animate, $.fn.ramblingSlider.defaults.speed * 2, '', jasmine.any(Function)
+
   # Methods
   describe 'when getting the effect', ->
     it 'should return the default one', ->


Admin: Stacks: Emit onboarding completed event when all pages are shown.

--- client/admin/lib/views/stacks/onboarding/onboardingview.coffee
+++ client/admin/lib/views/stacks/onboarding/onboardingview.coffee
@@ -41,12 +41,14 @@
       nextIndex  = if direction is 'next' then ++pageIndex else --pageIndex
       targetPage = @pages[nextIndex]

-      @currentPage.hide()
-      targetPage.show()
-
-      @setClass 'get-started'  if targetPage is @getStartedView
-
-      @currentPage = targetPage
+      if targetPage
+        @currentPage.hide()
+        targetPage.show()
+        @setClass 'get-started'  if targetPage is @getStartedView
+        @currentPage = targetPage
+      else
+        @hide()
+        @emit 'StackOnboardingCompleted'

     @getStartedView.on 'NextPageRequested', =>


move templates down to views for no reason

--- source/app/app.js.coffee
+++ source/app/app.js.coffee
@@ -3,8 +3,8 @@
 #= require_tree ./models
 #= require_tree ./controllers
 #= require_tree ./helpers
+#= require_tree ./views
 #= require_tree ./templates
-#= require_tree ./views
 #= require      ./router

 window.App = Ember.Application.create()


auto pin mode & logging

auto pin mode & logging

--- grovepi.coffee
+++ grovepi.coffee
@@ -38,7 +38,7 @@
 	mode: (port, mode, callback) ->
 		unless modes[port]? is mode
 			modes[port] = mode
-			@send CMD.mode, port, mode
+			@send CMD.mode, port, [mode, 0]

 	send: ( cmd, port, args... ) ->
 		unless cmd is CMD.mode
@@ -56,10 +56,11 @@

 		callback ?= ->

+		debug.log 'Send', data, 'to port', port
+
 		writeArgs.push (error) ->
 			callback()
-			debug.log "Send", data, 'to port', port
-			debug.log "ERROR:", error if error
+			debug.log 'ERROR:', error if error

 		wire[writeCmd] writeArgs...

@@ -77,10 +78,11 @@
 			callback = args[0]
 			readCmd = 'readByte'

+		debug.log 'Receive from port', port
 		readArgs.push (error, data) ->
 			callback data
-			debug.log "Receive", (Array.prototype.slice.call data, 0), 'from port', port
-			debug.log "ERROR:", error if error
+			debug.log 'Received', (Array.prototype.slice.call data, 0)
+			debug.log 'ERROR:', error if error

 		wire[readCmd] readArgs...


turn on ENV.RAISE_ON_DEPRECATION

turn on ENV.RAISE_ON_DEPRECATION

--- assets/scripts/travis.coffee
+++ assets/scripts/travis.coffee
@@ -1,5 +1,8 @@
 require 'ext/jquery'
 require 'ext/ember/namespace'
+
+window.ENV ||= {}
+window.ENV.RAISE_ON_DEPRECATION = true

 # TODO: how can I put it in Travis namespace and use immediately?
 Storage = Em.Object.extend


Make routing work for docs articles

Make routing work for docs articles

--- js/outer.coffee
+++ js/outer.coffee
@@ -1,90 +1,103 @@
-circle = $.sammy("body", ->
+circle = $.sammy "body", ->

   # Page
   class Page
     constructor: (@name, @title) ->

-    init: ->
+    init: (cx) =>
       document.title = "Circle - " + @title

       # Render content
-      @render()
+      @render(cx)

       # Land at the right anchor on the page
-      @scroll(window.location.hash)
+      @scroll window.location.hash

       # Fetch page-specific libraries
       @lib() if @lib?

-    render: ->
+    render: (cx) =>
       $("body").attr("id","#{@name}-page").html HAML['header'](renderContext)
       $("body").append HAML[@name](renderContext)
       $("body").append HAML['footer'](renderContext)

-    load: ->
-      $.getScript("/assets/views/outer/#{@name}/#{@name}.hamlc", => @init())
+    load: (cx) =>
+      $.getScript "/assets/views/outer/#{@name}/#{@name}.hamlc", =>
+         @init(cx)

-    scroll: (hash) ->
+    scroll: (hash) =>
       if hash == '' or hash == '#' then hash = "body"
       $('html, body').animate({scrollTop: $(hash).offset().top}, 0)

-    display: ->
+    display: (cx) =>
       if HAML? and HAML[@name]?
-        @init()
+        @init(cx)
       else
-        @load()
+        @load(cx)

   # Doc
-  class Doc extends Page
-    render: ->
-      if $('.categories').length > 0
-        $('.doc-title').remove();
-        $('.categories').removeClass('categories').addClass('article');
-        $(".article").html(HAML['categories']).append(HAML[@name](renderContext));
+  class Docs extends Page
+    constructor: (args...) ->
+      super(args...)
+      @lib = highlight
+
+    filename: (cx) =>
+      if cx.params.splat[0]
+        cx.params.splat[0].replace('/', '').replace('#', '').replace('-', '_')
+      else
+        "docs"
+
+    render: (cx) =>
+      name = @filename cx
+      if name == "docs"
+        super cx
       else
         $("body").attr("id","#{@name}-page").html(HAML['header'](renderContext))
         $("body").append(HAML['title'](renderContext))
         $("#title h1").text("Documentation")
         $("body").append("

")
-        $(".article").append(HAML['categories']).append(HAML[@name](renderContext))
+        $(".article").append(HAML['categories']).append(HAML[name](renderContext))
         $("body").append(HAML['footer'](renderContext))

-    load: ->
+    load: (cx) =>
       if !HAML['categories']
-        $.getScript("/assets/views/outer/docs/categories.hamlc", => @article())
+        $.getScript "/assets/views/outer/docs/categories.hamlc", =>
+          @article(cx)
       else
-        @article()
+        @article(cx)

-    article: ->
-      $.getScript("/assets/views/outer/docs/topics/#{@name}.hamlc", => @init())
+    article: (cx) =>
+      name = @filename cx
+      if name == "docs"
+        $.getScript "/assets/views/outer/docs/docs.hamlc", =>
+          @init(cx)
+      else
+        $.getScript "/assets/views/outer/docs/categories/#{name}.hamlc", =>
+          @init(cx)

   # Pages
   home = new Page("home", "Continuous Integration made easy")
   about = new Page("about", "About Us")
   privacy = new Page("privacy", "Privacy and Security")
-  docs = new Page("docs", "Documentation")
-
-  # Docs
-  article = new Doc("article", "Sample template for an Article")
+  docs = new Docs("docs", "Documentation")

   # Per-Page Libs
-  highlight = ->
+  highlight = =>
     if !hljs?
-      $.getScript("/assets/js/vendor/highlight.pack.js", ->
-        $("pre code").each (i, e) -> hljs.highlightBlock e
-      )
+      $.getScript "/assets/js/vendor/highlight.pack.js", =>
+        $("pre code").each (i, e) => hljs.highlightBlock e
+
     else
-      $("pre code").each (i, e) -> hljs.highlightBlock e
+      $("pre code").each (i, e) => hljs.highlightBlock e

-  placeholder = ->
+  placeholder = =>
     if !Modernizr.input.placeholder
-      $.getScript("/assets/js/vendor/jquery.placeholder.js", ->
+      $.getScript "/assets/js/vendor/jquery.placeholder.js", =>
         $("input, textarea").placeholder()
-      )
+

   home.lib = placeholder
   about.lib = placeholder
-  article.lib = highlight

   # Google analytics
   @bind 'event-context-after', ->
@@ -97,14 +110,11 @@
       window.Hoptoad.notify data.error

   # Navigation
-  @get "/", (context) -> home.display()
-  @get "/about.*", (context) -> about.display()
-  @get "/privacy.*", (context) -> privacy.display()
-  @get "/docs", (context) -> docs.display()
+  @get "/", (cx) => home.display(cx)
+  @get "/about.*", (cx) => about.display(cx)
+  @get "/privacy.*", (cx) => privacy.display(cx)
+  @get "/docs(.*)", (cx) => docs.display(cx)

-  # Documentation
-  @get "/docs/article.*", -> article.display()
-)

 # Global polyfills
 if $.browser.msie and $.browser.version > 6 and $.browser.version < 9


comments

comments

--- src/binomial_coefficient.coffee
+++ src/binomial_coefficient.coffee
@@ -1,7 +1,8 @@
 factorial = require './factorial'

 ###
-Calculate binomial coefficients.  Uses Math.floor to offset javascript math errors.
+Calculate binomial coefficients.  Also known as "n choose k".
+Uses Math.floor to offset javascript math errors.

 @param n [Integer] The corresponding "row" in pascal's triangle
 @param k [Integer] The corresponding "column" in pascal's triangle (dependent on the row number, since it's a triangle!)


Implemented different CSS for different opinion type

Conflicts:
	app/backbone/views/evidence/top_fact_evidence_view.coffee

--- core/app/backbone/views/evidence/top_fact_evidence_view.coffee
+++ core/app/backbone/views/evidence/top_fact_evidence_view.coffee
@@ -1,12 +1,30 @@
 class TopFactEvidenceLayoutView extends Backbone.Marionette.Layout
   template:
     text: """
-        Blah
+        {{#is_unsure}}
+

+

+        {{/is_unsure}}
+

+

+

+

+

+          Blah.
+

       """

+  templateHelpers:
+    opinion_type_css: ->
+      switch @opinion_type
+        when 'believe' then 'supporting'
+        when 'disbelieve' then 'weakening'
+        when 'doubt' then 'unsure'
+
+    is_unsure: -> @opinion_type == 'doubt'
+
 class window.TopFactEvidenceView extends Backbone.Marionette.CollectionView
-  className: -> 'top-fact-arguments'
-
+  className: 'top-fact-evidence'
   itemView: TopFactEvidenceLayoutView

   constructor: ->


Wrap inputs in p tag to add some spacing

--- app/assets/javascripts/comfy/admin/cms/resources.js.coffee
+++ app/assets/javascripts/comfy/admin/cms/resources.js.coffee
@@ -4,8 +4,8 @@
       ev.preventDefault()

       $addResourcesEl.find(".js-target-resources").append("""
-        <input type="text" name="resources[][label]" placeholder="Label" class="form-control">
-        <input type="file" name="resources[][file]" placeholder="File" class="form-control">
+
<input type="text" name="resources[][label]" placeholder="Label" class="form-control">

+
<input type="file" name="resources[][file]" placeholder="File" class="form-control">

       """)

       $addResourcesEl.find(".js-target-resources").addClass('py-2')
@@ -38,8 +38,8 @@
       ev.preventDefault()

       $addrelatedLinksEl.find(".js-target-related-links").append("""
-          <input type="text" name="related_links[][label]" placeholder="Label" class="form-control">
-          <input type="url" name="related_links[][url]" placeholder="URL" class="form-control">
+
<input type="text" name="related_links[][label]" placeholder="Label" class="form-control">

+
<input type="url" name="related_links[][url]" placeholder="URL" class="form-control">

       """)

       $addrelatedLinksEl.find(".js-target-related-links").addClass('py-2')


Draw all merit hourly load series

Draw all merit hourly load series

... even those with zero load. Previously, if a series was not rendered
when the chart is first created, it would not be rendered later even if
ETEngine returned value; I believe due to the fact that the series has
no `path.area` element.

Ref #2127

--- app/assets/javascripts/d3/merit_order_hourly_supply.coffee
+++ app/assets/javascripts/d3/merit_order_hourly_supply.coffee
@@ -43,11 +43,8 @@
       Math.abs((d3.max(values) - d3.min(values)) / d3.sum(values))

     getSeries: ->
-      _.filter(@model.non_target_series(), (serie) ->
-        d3.max(serie.future_value()) > 0
-      ).sort((a,b) =>
+      @model.non_target_series().sort (a, b) =>
         @getSpikiness(a) > @getSpikiness(b)
-      )

     drawData: (xScale, yScale, area, line) ->
       @svg.selectAll('path.serie')


Update User

Update User

--- src/add/perform.coffee
+++ src/add/perform.coffee
@@ -28,8 +28,40 @@

 interior.innerHTML = t users

+tbody = $ 'tbody', interior
+.pop()
+
 perform = ->
-  popup "Hi!"
+  for u, i in users
+    row = tbody.rows[i]
+    for step, n in steps
+      cell = row.cells[2+n]
+      try
+        step u
+        cell.innerHTML = '+'
+      catch error
+        cell.innerHTML = '#'
+        cell.title = error.message

 require './loop'
 .push perform
+
+steps = []
+
+# ПОЛ
+steps.push (u)->
+  cmd = mssql.command """
+    Update MBUser
+    Set
+      UserName=?,
+      Domain = ?
+    Where UserLogin=?
+    """
+  assign cmd, 0, u.AD.cn
+  assign cmd, 1, ad.dc
+  assign cmd, 2, u.UserLogin
+  cmd.Execute()
+
+  POL = directum.app.ReferencesFactory.ПОЛ.GetObjectById u.Analit
+  POL.Дополнение3 = u.AD.cn
+  POL.Save()


Code readability

Code readability

--- src/foundation-player.coffee
+++ src/foundation-player.coffee
@@ -93,7 +93,7 @@
       return

     # XXX WaveSurfer setup
-    setUpWaveSurfer: () ->
+    setUpWaveSurfer: ->
       @wavesurfer.init
         # Customizable stuff
         # Opiniated defaults for WaveSurfer
@@ -116,7 +116,7 @@
       return

     # Setup default class
-    setUpClassAndStyle: () ->
+    setUpClassAndStyle: ->
       @$el.addClass(@options.size)

       # Calculate player width
@@ -128,60 +128,60 @@
       @$player.width Math.floor(5 + playerWidth/actualWidth*100) + '%'

     # Set up Play/Pause
-    setUpButtonPlayPause: () ->
+    setUpButtonPlayPause: ->
       @$play.bind 'click', @, (e) ->
         e.data.wavesurfer.playPause() # Play or pause
         e.data.updateButtonPlay()
     # Update Play/Pause
-    updateButtonPlay: () ->
+    updateButtonPlay: ->
       if @wavesurfer.isPlaying() # Update button class
         swithClass @$play, 'fi-play', 'fi-pause'
       else
         swithClass @$play, 'fi-pause', 'fi-play'

     # Set up volume button
-    setUpButtonVolume: () ->
+    setUpButtonVolume: ->
       @$volume.bind 'click', @, (e) ->
         e.data.wavesurfer.toggleMute() # Play or pause
         e.data.updateButtonVolume()
     # Update volume button
-    updateButtonVolume: () ->
+    updateButtonVolume: ->
       if @wavesurfer.isMuted
         swithClass @$volume, 'fi-volume-strike', 'fi-volume'
       else
         swithClass @$volume, 'fi-volume', 'fi-volume-strike'

     # Set up rewind button
-    setUpButtonRewind: () ->
+    setUpButtonRewind: ->
       @$rewind.on 'click', @, (e) ->
         e.data.wavesurfer.skipBackward()
         e.data.updateSlider()

     # Update all statuses
-    updateStatus: () ->
+    updateStatus: ->
       @updateStatusElapsed()
       @updateStatusRemains()
     # Update $elapsed time status
-    updateStatusElapsed: () ->
+    updateStatusElapsed: ->
       @$elapsed.text prettyTime @wavesurfer.getCurrentTime()
     # Update $remains time status
-    updateStatusRemains: () ->
+    updateStatusRemains: ->
       w = @wavesurfer
       @$remains.text '-' + prettyTime w.getDuration()-w.getCurrentTime()
-    updateSlider: () ->
+    updateSlider: ->
       @updatePercentage()
       @updateSliderPosition()
-    updateSliderPosition: () ->
+    updateSliderPosition: ->
       # XXX Shit!
       # @$slider.foundation('slider', 'set_value', @playedPercentage);

     # Update @playedPercentage according played state
-    updatePercentage: () ->
+    updatePercentage: ->
       w = @wavesurfer
       @playedPercentage = Math.floor (w.getCurrentTime()/w.getDuration())*100
     # Update @sliderPosition according slider position
     # This updates continiusly durig slider drag
-    updateSliderPercentage: () ->
+    updateSliderPercentage: ->
       @sliderPosition = @$slider.attr('data-slider')
       # @playedPercentage = Math.floor (w.getCurrentTime()/w.getDuration())*100
     # Setup range slider


reorder methods into request, response and idle event handling sections

--- lib/dispatcher.coffee
+++ lib/dispatcher.coffee
@@ -49,9 +49,9 @@
   # Set the idle handler
   on_idle: (@_idle_handler) -> this

-  # Print a debugging statement
-  _debug: (args...) ->
-    #console.log 'dispatcher:', args...
+  # Clear the timeout for idling
+  _clear_timeout: ->
+    clearTimeout @_timeout

   # Called when a worker requests keys. The keys requested are
   # recorded as dependencies, and any new key requests are
@@ -86,94 +86,10 @@
     @_dependency_count[source] = 0
     @_handle_request source, keys

-  # Called when a key is completed. Any jobs depending on this
-  # key are updated, and if they have no more dependencies, are
-  # signalled to run again.
-  _responded: (key) ->
-    @_audit_log.response key
-    @_state[key] = 'done'
-    targets = @deps[key] ? []
-    delete @deps[key]
-    @_progress targets
-
-  # The seed request was completed. In test mode, quit the workers.
-  _unseed: ->
-    @_clear_timeout()
-    @quit() if @_test_mode
-
-  # Make progress on each of the given keys by decrementing
-  # their count of remaining dependencies. When any reaches
-  # zero, it is rescheduled.
-  _progress: (keys) ->
-    for key in keys
-      unless --@_dependency_count[key]
-        @_reschedule key
-
-  # Clear the timeout for idling
-  _clear_timeout: ->
-    clearTimeout @_timeout
-
   # Reset the timer that checks if the process is broken
   _reset_timeout: ->
     @_clear_timeout()
     @_timeout = setTimeout (=> @_idle()), @_idle_timeout
-
-  # Activate a handler for idle timeouts. By default, this means
-  # calling the doctor.
-  _idle: ->
-    if @_idle_handler
-      @_idle_handler()
-    else
-      @_call_doctor()
-
-  # Let the doctor figure out what's wrong here
-  _call_doctor: ->
-    console.log "Oops... calling the doctor!" if @_verbose
-    @doc ?= new Doctor @deps, @_state, @_seed_key
-    @doc.diagnose()
-    if @doc.is_stuck()
-      @doc.report() if @_verbose
-      @_recover()
-    else
-      console.log "Hmm, the doctor couldn't find anything amiss..." if @_verbose
-
-  # Recover from a stuck process.
-  _recover: ->
-    if @doc.recoverable()
-      for cycle in @doc.cycles
-        return @_fail_recovery() if @_seen_cycle cycle
-      for key, deps of @doc.cycle_dependencies()
-        @_signal_worker_of_cycles key, deps
-    else
-      @_fail_recovery()
-
-  # Determine if we've seen this cycle before
-  _seen_cycle: (cycle) ->
-    key = cycle.sort().join()
-    return true if @_cycles[key]
-    @_cycles[key] = true
-    false
-
-  # Tell the given worker that they have cycle dependencies.
-  _signal_worker_of_cycles: (key, deps) ->
-    @_remove_dependencies key, deps
-    @_control_channel.cycle key, deps
-
-  # Remove given dependencies from the key
-  _remove_dependencies: (key, deps) ->
-    @_dependency_count[key] -= deps.length
-    @deps[dep] = _.without @deps[dep], key for dep in deps
-
-  # Recovery failed, let the callback know about it.
-  _fail_recovery: ->
-    @_stuck_callback?(@doc, @_control_channel.db())
-
-  # Signal a job to run again by sending a resume message
-  _reschedule: (key) ->
-    delete @_dependency_count[key]
-    return @_unseed() if key == '!seed'
-    return if @_state[key] == 'done'
-    @_control_channel.resume key

   # Handle the requested keys by marking them as dependencies
   # and turning any unsatisfied ones into new jobs.
@@ -195,6 +111,90 @@
     @_state[req] = 'wait'
     @_control_channel.push_job req

+  # Signal a job to run again by sending a resume message
+  _reschedule: (key) ->
+    delete @_dependency_count[key]
+    return @_unseed() if key == '!seed'
+    return if @_state[key] == 'done'
+    @_control_channel.resume key
+
+  # The seed request was completed. In test mode, quit the workers.
+  _unseed: ->
+    @_clear_timeout()
+    @quit() if @_test_mode
+
+  # Called when a key is completed. Any jobs depending on this
+  # key are updated, and if they have no more dependencies, are
+  # signalled to run again.
+  _responded: (key) ->
+    @_audit_log.response key
+    @_state[key] = 'done'
+    targets = @deps[key] ? []
+    delete @deps[key]
+    @_progress targets
+
+  # Make progress on each of the given keys by decrementing
+  # their count of remaining dependencies. When any reaches
+  # zero, it is rescheduled.
+  _progress: (keys) ->
+    for key in keys
+      unless --@_dependency_count[key]
+        @_reschedule key
+
+  # Activate a handler for idle timeouts. By default, this means
+  # calling the doctor.
+  _idle: ->
+    if @_idle_handler
+      @_idle_handler()
+    else
+      @_call_doctor()
+
+  # Let the doctor figure out what's wrong here
+  _call_doctor: ->
+    console.log "Oops... calling the doctor!" if @_verbose
+    @doc ?= new Doctor @deps, @_state, @_seed_key
+    @doc.diagnose()
+    if @doc.is_stuck()
+      @doc.report() if @_verbose
+      @_recover()
+    else
+      console.log "Hmm, the doctor couldn't find anything amiss..." if @_verbose
+
+  # Recover from a stuck process.
+  _recover: ->
+    if @doc.recoverable()
+      for cycle in @doc.cycles
+        return @_fail_recovery() if @_seen_cycle cycle
+      for key, deps of @doc.cycle_dependencies()
+        @_signal_worker_of_cycles key, deps
+    else
+      @_fail_recovery()
+
+  # Determine if we've seen this cycle before
+  _seen_cycle: (cycle) ->
+    key = cycle.sort().join()
+    return true if @_cycles[key]
+    @_cycles[key] = true
+    false
+
+  # Recovery failed, let the callback know about it.
+  _fail_recovery: ->
+    @_stuck_callback?(@doc, @_control_channel.db())
+
+  # Tell the given worker that they have cycle dependencies.
+  _signal_worker_of_cycles: (key, deps) ->
+    @_remove_dependencies key, deps
+    @_control_channel.cycle key, deps
+
+  # Remove given dependencies from the key
+  _remove_dependencies: (key, deps) ->
+    @_dependency_count[key] -= deps.length
+    @deps[dep] = _.without @deps[dep], key for dep in deps
+
+  # Print a debugging statement
+  _debug: (args...) ->
+    #console.log 'dispatcher:', args...
+
 module.exports =

   # Create a new dispatcher instance and start it listening for


Add quit handler and single_use option to dispatcher.

--- lib/dispatcher.coffee
+++ lib/dispatcher.coffee
@@ -17,6 +17,7 @@
     @deps = {}
     @link = {}
     @_test_mode = options.test_mode
+    @_single_use = options.single_use ? @_test_mode
     @_verbose = options.verbose
     @_idle_timeout = options.idle_timeout ? (if @_test_mode then 500 else 10000)
     @_audit_log = new AuditLog stream: options.audit
@@ -44,6 +45,7 @@
       @_requests_channel.end()
       @_responses_channel.end()
       @_control_channel.end()
+      @_quit_handler?()
     setTimeout finish, 500

   # Provide a callback to be called when the dispatcher detects the process is stuck
@@ -51,6 +53,9 @@

   # Set the idle handler
   on_idle: (@_idle_handler) -> this
+
+  # Set the quit handler
+  on_quit: (@_quit_handler) -> this

   # Clear the timeout for idling
   _clear_timeout: ->
@@ -167,7 +172,7 @@
     @_dump_link()
     unless --@_seed_count
       @_clear_timeout()
-      @quit() if @_test_mode
+      @quit() if @_single_use

   # Called when a key is completed. Any jobs depending on this
   # key are updated, and if they have no more dependencies, are


Make DragMove work while animating

Make DragMove work while animating

--- framer/LayerDraggable.coffee
+++ framer/LayerDraggable.coffee
@@ -381,7 +381,10 @@
 		else
 			updatePoint[axis] = state.x

+		@emit(Events.DragWillMove, {animation:true})
 		@layer[axis] = @updatePosition(updatePoint)[axis]
+		@emit(Events.DragMove, {animation:true})
+		@emit(Events.DragDidMove, {animation:true})

 	_onSimulationStop: (axis, state) =>


Close all connections when finished

Close all connections when finished

--- lib/clone.iced
+++ lib/clone.iced
@@ -45,6 +45,7 @@

   unless sourceDB? and targetDB?
     console.log "Source and target databases are required!"
+    console.log HELPTEXT
     return null

   if "#{sourceHost}:#{sourcePort}" is "#{targetHost}:#{targetPort}" and sourceDB is targetDB
@@ -247,7 +248,11 @@
     insert_queue.drain = ->
       sourceTableList = _.uniq(sourceTableList)
       if completed_tables.length >= sourceTableList.length
-        console.log _.keys(tableConns)
+        #close all cursor connections
+        for key in _.keys(tableConns)
+          await tableConns[key].close(defer(err, result))
+        console.log "\n"
+        console.log "DONE!"
         done()

     insert_queue.suturate = ->


fix error if artist has no current show or auction

--- apps/artist_2/components/current_show_auction/index.coffee
+++ apps/artist_2/components/current_show_auction/index.coffee
@@ -12,4 +12,4 @@
  module.exports = (artist) ->
    type = _.find ['auction', 'show'], (type) ->
      artist[type]?.length > 0
-   current(type, artist)+   current(type, artist) if type


Client: Activity: Added new actionTypes for scroll position

--- client/activity/lib/flux/actions/actiontypes.coffee
+++ client/activity/lib/flux/actions/actiontypes.coffee
@@ -5,6 +5,9 @@

   'SET_ALL_MESSAGES_LOADED'
   'UNSET_ALL_MESSAGES_LOADED'
+
+  'SET_LOADED_WITH_SCROLL'
+  'UNSET_LOADED_WITH_SCROLL'

   'LOAD_MESSAGE_BEGIN'
   'LOAD_MESSAGE_FAIL'


activity/flux: add missing generic LOAD_MESSAGE action types

--- client/activity/lib/flux/actions/actiontypes.coffee
+++ client/activity/lib/flux/actions/actiontypes.coffee
@@ -57,6 +57,9 @@
   'LOAD_CHANNEL_BY_NAME_FAIL'
   'LOAD_CHANNEL_BY_ID_BEGIN'
   'LOAD_CHANNEL_BY_ID_FAIL'
+
+  'LOAD_CHANNEL_BEGIN'
+  'LOAD_CHANNEL_FAIL'
   'LOAD_CHANNEL_SUCCESS'

   'LOAD_FOLLOWED_PRIVATE_CHANNELS_BEGIN'


Notifications menu button fix

Notifications menu button fix

--- client/app/lib/avatararea/avatarareaiconlink.coffee
+++ client/app/lib/avatararea/avatarareaiconlink.coffee
@@ -30,11 +30,11 @@
     then @$('.count').addClass "hidden"
     else @$('.count').removeClass "hidden"

-  click: (event) ->
+  click: do (skipNextClick = no) -> (event) ->

     kd.utils.stopDOMEvent event

-    return clickedInside = no  if clickedInside
+    return skipNextClick = no  if skipNextClick

     popup = @getDelegate()

@@ -44,7 +44,7 @@
       @setClass 'active'
       popup.show()
       popup.once "ReceivedClickElsewhere", (event) =>
-        clickedInside = @isInside event.target
+        skipNextClick = @isInside event.target
         @unsetClass 'active'
         popup.hide()


Remove doubleTap event (inexistent) & re-indent

--- source/quo.events.coffee
+++ source/quo.events.coffee
@@ -21,7 +21,6 @@
     touchmove         : "mousemove"
     touchend          : "mouseup"
     touch             : "click"
-    doubletap         : "dblclick"
     orientationchange : "resize"

   READY_EXPRESSION = /complete|loaded|interactive/
@@ -149,56 +148,52 @@
     event

   _subscribe = (element, event, callback, selector, delegate_callback) ->
-      event = _environmentEvent(event)
-      element_id = _getElementId(element)
-      element_handlers = HANDLERS[element_id] or (HANDLERS[element_id] = [])
-      delegate = delegate_callback and delegate_callback(callback, event)
-      handler =
-          event: event
-          callback: callback
-          selector: selector
-          proxy: _createProxyCallback(delegate, callback, element)
-          delegate: delegate
-          index: element_handlers.length
+    event = _environmentEvent(event)
+    element_id = _getElementId(element)
+    element_handlers = HANDLERS[element_id] or (HANDLERS[element_id] = [])
+    delegate = delegate_callback and delegate_callback(callback, event)
+    handler =
+      event   : event
+      callback: callback
+      selector: selector
+      proxy   : _createProxyCallback(delegate, callback, element)
+      delegate: delegate
+      index   : element_handlers.length

-      element_handlers.push handler
-      $$.fn.addEvent element, handler.event, handler.proxy
+    element_handlers.push handler
+    $$.fn.addEvent element, handler.event, handler.proxy

   _unsubscribe = (element, event, callback, selector) ->
-      event = _environmentEvent(event)
-      element_id = _getElementId(element)
-      _findHandlers(element_id, event, callback, selector).forEach (handler) ->
-          delete HANDLERS[element_id][handler.index]
+    event = _environmentEvent(event)
+    element_id = _getElementId(element)
+    _findHandlers(element_id, event, callback, selector).forEach (handler) ->
+      delete HANDLERS[element_id][handler.index]
+      $$.fn.removeEvent element, handler.event, handler.proxy

-          $$.fn.removeEvent element, handler.event, handler.proxy
-
-  _getElementId = (element) -> element._id or (element._id = ELEMENT_ID++)
+  _getElementId = (element) ->
+    element._id or (element._id = ELEMENT_ID++)

   _environmentEvent = (event) ->
-      environment_event = (if ($$.isMobile()) then event else EVENTS_DESKTOP[event])
-      (environment_event) or event
+    environment_event = if $$.isMobile() then event else EVENTS_DESKTOP[event]
+    (environment_event) or event

   _createProxyCallback = (delegate, callback, element) ->
-      callback = delegate or callback
-      proxy = (event) ->
-          result = callback.apply(element, [ event ].concat(event.data))
-          event.preventDefault() if result is false
-          result
-      proxy
+    callback = delegate or callback
+    proxy = (event) ->
+      result = callback.apply(element, [ event ].concat(event.data))
+      event.preventDefault() if result is false
+      result
+    proxy

   _findHandlers = (element_id, event, fn, selector) ->
-      (HANDLERS[element_id] or []).filter (handler) ->
-          handler and (not event or handler.event is event) and (not fn or handler.callback is fn) and (not selector or handler.selector is selector)
+    (HANDLERS[element_id] or []).filter (handler) ->
+      handler and (not event or handler.event is event) and (not fn or handler.callback is fn) and (not selector or handler.selector is selector)

   _createProxy = (event) ->
-      proxy = $$.extend( originalEvent: event, event)
-      $$.each EVENT_METHODS, (name, method) ->
-          proxy[name] = ->
-              @[method] = ->
-                  true
-
-              event[name].apply event, arguments
-
-          proxy[method] = ->
-              false
-      proxy
+    proxy = $$.extend( originalEvent: event, event)
+    $$.each EVENT_METHODS, (name, method) ->
+      proxy[name] = ->
+        @[method] = -> true
+        event[name].apply event, arguments
+      proxy[method] = -> false
+    proxy


Resolve issue with incorrect url for remote CSS when using addCssUrl, and bundle option is false.  This results from the file.url property being a boolean value instead of a string, and the result is the link tag is output as <link href='true' /> rather than with the correct url.  Looks like this was already fixed for JS files, but not CSS.

--- lib/css.coffee
+++ lib/css.coffee
@@ -29,7 +29,8 @@
     style = ''
     for file in @files
       if file.namespace == namespace
-        style += "<link href='#{file.url}' rel='stylesheet' type='text/css'/>"
+        url = if typeof file.url == 'boolean' then file.file else file.url
+        style += "<link href='#{url}' rel='stylesheet' type='text/css'/>"
     return style

   _convertFilename: (filename) ->


Shortcut out of isHidden when we can.

Shortcut out of isHidden when we can.

Checking computed style is an order of magnitude more expensive than
checking the style property.

--- src/jquery-extensions.coffee
+++ src/jquery-extensions.coffee
@@ -40,7 +40,14 @@
 $.fn.isHidden = ->
   # We used to check @is(':hidden'). But this is much faster than the
   # offsetWidth/offsetHeight check + all the pseudo selector mess in jquery.
-  @css('display') == 'none'
+  style = this[0].style
+
+  if style.display == 'none' or not @isOnDom()
+    true
+  else if style.display
+    false
+  else
+    getComputedStyle(this[0]).display == 'none'

 $.fn.isDisabled = ->
   !!@attr('disabled')


Added buttons to hide and delete projects matching a search

--- src/smc-webapp/projects.cjsx
+++ src/smc-webapp/projects.cjsx
@@ -958,7 +958,7 @@
         search            : ''

     getInitialState: ->
-        show_remove_from_all : false
+        show_alert: 'none'

     render_header: ->
         if @props.nb_projects > 0 and (@props.hidden or @props.deleted)
@@ -972,7 +972,7 @@
     render_span: (query) ->
         whose title, description or users contain {query}
         <Space/><Space/>
-        <Button onClick={=>@setState(show_remove_from_all:false); @props.on_cancel()}>
+        <Button onClick={=>@setState(show_alert: 'none'); @props.on_cancel()}>
             Cancel
         </Button>

@@ -989,17 +989,66 @@
                 projects<Space/>
                 {if query isnt '' then @render_span(query)}
                 {@render_remove_from_all_button() if @props.visible_projects.length > 0}
-                {@render_remove_from_all() if @state.show_remove_from_all}
+                {@render_delete_all_button() if @props.visible_projects.length > 0 and not @props.deleted}
+                {@render_hide_all_button() if @props.visible_projects.length > 0 and not @props.hidden}
+                {switch @state.show_alert
+                    when 'hide'
+                        @render_hide_all()
+                    when 'remove'
+                        @render_remove_from_all()
+                    when 'delete'
+                        @render_delete_all()
+                }
             </Alert>
+
+    render_hide_all_button: ->
+        <Button
+            className = 'pull-right'
+            disabled  = {@state.show_alert == 'hide'}
+            onClick   = {=>@setState(show_alert: 'hide')}
+            >
+            <Icon name='eye-slash'/>  Hide
+        </Button>
+
+    render_delete_all_button: ->
+        <Button
+            className = 'pull-right'
+            disabled  = {@state.show_alert == 'delete'}
+            onClick   = {=>@setState(show_alert: 'delete')}
+            >
+            <Icon name='trash'/>  Delete
+        </Button>

     render_remove_from_all_button: ->
         <Button
             className = 'pull-right'
-            disabled  = {@state.show_remove_from_all}
-            onClick   = {=>@setState(show_remove_from_all:true)}
+            disabled  = {@state.show_alert == 'remove'}
+            onClick   = {=>@setState(show_alert: 'remove')}
             >
-            <Icon name='user-times'/>  Remove Myself...
+            <Icon name='user-times'/>  Remove Myself
         </Button>
+
+    render_hide_all: ->
+        if @props.visible_projects.length == 0
+            return
+        <Alert key='hide-all' style={marginTop:"15px"}>
+
<Icon name="eye-slash"/>  Hide Projects

+            Are you sure you want to hide the {@props.visible_projects.length} {misc.plural(@props.visible_projects.length, 'project')} listed below? This can be undone in the {if @props.visible_projects.length > 1 then "projects'" else "project's"} settings.
+
+            <ButtonToolbar style={marginTop:'15px'}>
+                <Button bsStyle='warning' onClick={@do_hide_all}  >
+                    <Icon name='eye-slash'/> Hide {@props.visible_projects.length} {misc.plural(@props.visible_projects.length, 'project')}
+                </Button>
+                <Button onClick={=>@setState(show_alert:'none')} >
+                    Cancel
+                </Button>
+            </ButtonToolbar>
+        </Alert>
+
+    do_hide_all: ->
+        for project in @props.visible_projects
+            @actions('projects').toggle_hide_project(project.project_id)
+        @setState(show_alert: 'none')

     collab_projects: ->
         # Determine visible projects this user does NOT own.
@@ -1015,7 +1064,7 @@
                 {head}
                 You are the owner of every displayed project.  You can only remove yourself from projects that you do not own.

-                <Button onClick={=>@setState(show_remove_from_all:false)} >
+                <Button onClick={=>@setState(show_alert:'none')} >
                     Cancel
                 </Button>
             </Alert>
@@ -1028,7 +1077,7 @@
                     desc = "You are a collaborator on the one project listed below."
                 else
                     desc = "You are a collaborator on ALL of the #{v.length} #{misc.plural(v.length, 'project')} listed below."
-            <Alert  style={marginTop:'15px'}>
+            <Alert style={marginTop:'15px'}>
                 {head} {desc}

@@ -1038,7 +1087,7 @@
                     <Button bsStyle='danger' onClick={@do_remove_from_all}  >
                         <Icon name='user-times'/> Remove myself from {v.length} {misc.plural(v.length, 'project')}
                     </Button>
-                    <Button onClick={=>@setState(show_remove_from_all:false)} >
+                    <Button onClick={=>@setState(show_alert:'none')} >
                         Cancel
                     </Button>
                 </ButtonToolbar>
@@ -1047,7 +1096,38 @@
     do_remove_from_all: ->
         for project in @collab_projects()
             @actions('projects').remove_collaborator(project.project_id, webapp_client.account_id)
-        @setState(show_remove_from_all:false)
+        @setState(show_alert: 'none')
+
+    render_delete_all: ->
+        if @props.visible_projects.length == 0
+            return
+        own = @props.visible_projects.length - @collab_projects().length
+        if own == 0
+            desc = 'You do not own any of the projects listed below.'
+        else if own < @props.visible_projects.length
+            desc = "You are the owner on #{own} of the #{@props.visible_projects.length} of projects listed below."
+        else
+            desc = "You are the owner of every displayed project."
+        <Alert key='delete_all' style={marginTop:'15px'}>
+
<Icon name='trash'/>  Delete Projects

+            {desc}
+
+

+            Are you sure you want to delete the {@props.visible_projects.length} {misc.plural(@props.visible_projects.length, 'project')} listed below? This will delete the {misc.plural(@props.visible_projects.length, 'project')} for all participants.
+            <ButtonToolbar style={marginTop:'15px'}>
+                <Button bsStyle='danger' onClick={@do_delete_all}  >
+                    <Icon name='trash'/> Delete {@props.visible_projects.length} {misc.plural(@props.visible_projects.length, 'project')}
+                </Button>
+                <Button onClick={=>@setState(show_alert:'none')} >
+                    Cancel
+                </Button>
+            </ButtonToolbar>
+        </Alert>
+
+    do_delete_all: ->
+        for project in @props.visible_projects
+            @actions('projects').toggle_delete_project(project.project_id)
+        @setState(show_alert: 'none')

     render: ->


make fixes

make fixes

--- src/smc-webapp/projects.cjsx
+++ src/smc-webapp/projects.cjsx
@@ -702,7 +702,7 @@
             title_text        : ''
             description_text  : ''
             error             : ''
-            create_button_hit : ''
+            create_button_hit : '' # Options are 'with_members_and_internet' and 'with_custom_upgrades'

     toggle_editing: ->
         if @state.state == 'view'
@@ -766,8 +766,8 @@
             <Alert bsStyle='info'>Working hard to build your project... <Icon name='circle-o-notch' spin /></Alert>

     create_project_with_members_and_internet: ->
-        total_upgrades = redux.getStore('account').get_total_upgrades() ? {}
-        if total_upgrades.member_host > 0 and total_upgrades.network > 0
+        remaining_upgrades = misc.map_diff(@props.upgrades_you_can_use, @props.upgrades_you_applied_to_all_projects)
+        if remaining_upgrades.member_host > 0 and remaining_upgrades.network > 0
             @create_project({member_host: 1, network: 1})
         else
             @setState(create_button_hit: 'with_members_and_internet')


fix https://github.com/sagemathinc/smc/issues/966  -- "toggling sagews cells in %md collapses too much"

--- src/smc-webapp/sagews.coffee
+++ src/smc-webapp/sagews.coffee
@@ -52,6 +52,27 @@
         # these two lines are assumed, at least by the history browser
         @codemirror  = @editor.codemirror
         @codemirror1 = @editor.codemirror1
+
+        # We set a custom rangeFinder that is output cell marker aware.
+        # See https://github.com/sagemathinc/smc/issues/966
+        foldOptions =
+            rangeFinder : (cm, start) ->
+                helpers = cm.getHelpers(start, "fold")
+                for h in helpers
+                    cur = h(cm, start)
+                    if cur
+                        i = start.line
+                        while i < cur.to.line and cm.getLine(i+1)?[0] != MARKERS.output
+                            i += 1
+                        if cm.getLine(i+1)?[0] == MARKERS.output
+                            cur.to.line = i
+                            cur.to.ch = cm.getLine(i).length
+                        return cur
+
+        for cm in @codemirrors()
+            cm.setOption('foldOptions', foldOptions)
+
+

         if @opts.static_viewer
             @readonly   = true


get rid of padding problem for chat

get rid of padding problem for chat

--- src/smc-webapp/smc_chat.cjsx
+++ src/smc-webapp/smc_chat.cjsx
@@ -636,7 +636,6 @@

         chat_input_style =
             margin       : "0"
-            padding      : "0"
             height       : '90px'
             fontSize     : @props.font_size