Add support for custom image sizes

Add support for custom image sizes

The default image width of 1000px is sometimes not enough but was originally hardcoded. This PR adds the ability to specify the width/height of the image when calling the dashboard

--- src/grafana.coffee
+++ src/grafana.coffee
@@ -4,6 +4,8 @@
 #   Examples:
 #   - `hubot graf db graphite-carbon-metrics` - Get all panels in the dashboard
 #   - `hubot graf db graphite-carbon-metrics:3` - Get only the third panel, from left to right, of a particular dashboard
+#   - `hubot graf db graphite-carbon-metrics:3 width=1000` - Get only the third panel, from left to right, of a particular dashboard. Set the image width to 1000px
+#   - `hubot graf db graphite-carbon-metrics:3 height=2000` - Get only the third panel, from left to right, of a particular dashboard. Set the image height to 2000px
 #   - `hubot graf db graphite-carbon-metrics:panel-8` - Get only the panel of a particular dashboard with the ID of 8
 #   - `hubot graf db graphite-carbon-metrics:cpu` - Get only the panels containing "cpu" (case insensitive) in the title
 #   - `hubot graf db graphite-carbon-metrics now-12hr` - Get a dashboard with a window of 12 hours ago to now
@@ -67,6 +69,7 @@
     visualPanelId = false
     apiPanelId = false
     pname = false
+    imagesize = { "width": 1000, "height": 500 }

     # Parse out a specific panel
     if /\:/.test slug
@@ -89,6 +92,10 @@
       for part in remainder.trim().split ' '
         # Check if it's a variable or part of the timespan
         if part.indexOf('=') >= 0
+          #put imagesize stuff into its own dict
+          if part.split('=')[0] of imagesize
+            imagesize[part.split('=')[0]] = part.split('=')[1]
+
           variables = "#{variables}&var-#{part}"
           template_params.push { "name": part.split('=')[0], "value": part.split('=')[1] }

@@ -161,7 +168,7 @@

           # Build links for message sending
           title = formatTitleWithTemplate(panel.title, template_map)
-          imageUrl = "#{grafana_host}/render/#{apiEndpoint}/db/#{slug}/?panelId=#{panel.id}&width=1000&height=500&from=#{timespan.from}&to=#{timespan.to}#{variables}"
+          imageUrl = "#{grafana_host}/render/#{apiEndpoint}/db/#{slug}/?panelId=#{panel.id}&width=#{imagesize.width}&height=#{imagesize.height}&from=#{timespan.from}&to=#{timespan.to}#{variables}"
           link = "#{grafana_host}/dashboard/db/#{slug}/?panelId=#{panel.id}&fullscreen&from=#{timespan.from}&to=#{timespan.to}#{variables}"

           # Fork here for S3-based upload and non-S3


Don't copy node_modules/ html files.

Don't copy node_modules/ html files.

--- app/templates/_Gruntfile.coffee
+++ app/templates/_Gruntfile.coffee
@@ -55,7 +55,7 @@
           '<%%= project.app.styles %>/**/*'
           '<%%= project.app.scripts %>/**/*'
           '<%%= project.app.images %>/**/*'
-          '<%%= project.app.static %>**/*.html'
+          '<%%= project.app.static %>/**/*.html'
         ]

     # Runs a simple server for front-end testing and/or development
@@ -218,7 +218,7 @@
         files: [{
           expand  : true
           cwd     : '<%%= project.dev.static %>'
-          src     : ['**/*.html']
+          src     : ['**/*.html', '!node_modules/**/*']
           dest    : '<%%= project.app.static %>'
         }]


added --init flag to release

added --init flag to release

--- command-release.coffee
+++ command-release.coffee
@@ -19,6 +19,7 @@
   .option '-r, --repo <repo-name>', 'Project repo name'
   .option '-o, --owner <octoblu>', 'Project owner'
   .option '-t, --tag <tag>', 'Override project version'
+  .option '--init', 'Set version 1.0.0'
   .option '--major', 'Bump with semver major version'
   .option '--premajor', 'Bump with semver premajor version'
   .option '--minor', 'Bump with semver minor version'
@@ -55,10 +56,12 @@
     return semver.valid program.tag if semver.valid program.tag
     tag = @config.version
     release = @getRelease program
+    return '1.0.0' if release == 'init'
     preid = program.prerelease
     return semver.inc(tag, release, preid)

   getRelease: (program) =>
+    return 'init' if program['init']
     return 'major' if program['major']
     return 'premajor' if program['premajor']
     return 'minor' if program['minor']


SelectionView now supports setting an active option

--- app/assets/javascripts/admin/views/selection_view.coffee
+++ app/assets/javascripts/admin/views/selection_view.coffee
@@ -11,6 +11,9 @@

   childView : SelectionItemView

+  # keep track of the active option
+  active : null
+
   initialize : (options) ->

     @collection.fetch(
@@ -21,10 +24,16 @@
     )

     if options.name
-
       @$el.attr("name", options.name)

+    # set active option
+    if options.active
+      @active = options.active

+    # afterRender listener
+    @listenTo(@, "render", @afterRender)

+  afterRender : ->

-
+    if @active?
+      @$el.find("option[value=#{@active}]").attr("selected", "")


Maintenance: Limit layout_ref to developer_mode: true

--- app/assets/javascripts/app/controllers/layout_ref.coffee
+++ app/assets/javascripts/app/controllers/layout_ref.coffee
@@ -2441,4 +2441,4 @@
     )

 App.Config.set('layout_ref/kb_link_answer_to_answer', KnowledgeBaseLinkAnswerToAnswerRef, 'Routes')
-App.Config.set('LayoutRef', { prio: 1600, parent: '#current_user', name: 'Layout Reference', translate: true, target: '#layout_ref', permission: [ 'admin' ] }, 'NavBarRight')
+App.Config.set('LayoutRef', { prio: 1600, parent: '#current_user', name: 'Layout Reference', translate: true, target: '#layout_ref', permission: [ 'admin' ], setting: ['developer_mode'] }, 'NavBarRight')


Removed not needed log entry.

Removed not needed log entry.

--- app/assets/javascripts/app/controllers/navigation.coffee
+++ app/assets/javascripts/app/controllers/navigation.coffee
@@ -24,7 +24,6 @@

     # rebuild nav bar with given user data
     @bind 'auth', (user) =>
-      @log 'Navigation', 'debug', 'navbar rebuild', user
       @render()

     # fetch new recent viewed after collection change


Fix JS error on about page

Fix JS error on about page

--- app/assets/javascripts/neighborly/static/about.js.coffee
+++ app/assets/javascripts/neighborly/static/about.js.coffee
@@ -1,7 +1,7 @@
 Neighborly.Static ?= {}

 Neighborly.Static.About =
-  init:
+  init: ->
     $.getScript('https://maps.googleapis.com/maps/api/js?v=3.exp&sensor=false&callback=Neighborly.AboutPageMapInitialize')

 Neighborly.AboutPageMapInitialize = ->


apiのpath指定を修正

apiのpath指定を修正

--- app/assets/javascripts/utils/api/abilitysheet_api.coffee
+++ app/assets/javascripts/utils/api/abilitysheet_api.coffee
@@ -1,6 +1,8 @@
 class @AbilitysheetAPI
   @server = location.protocol + '//' + location.host
-  @apiPath = "#{Routes.root_path().split('/')[1]}/api/v1"
+  subDir = Routes.root_path().split('/')[1]
+  subDir = '/' + subDir if 0 < subDir.length
+  @apiPath = "#{subDir}/api/v1"

   @AjaxCall: (api, option = null) ->
     option = {}         unless option?


Refined drag-and-drop styles for better linux compatibility

--- app/coffee/modules/main/dashboard/views/macroList.coffee
+++ app/coffee/modules/main/dashboard/views/macroList.coffee
@@ -20,24 +20,16 @@

   onMouseOver: ->
     @$el.addClass('hovered')
-    # console.log 'onMouseOver'

   onMouseOut: ->
     @$el.removeClass('hovered')
-    # console.log 'onMouseOut'

   onDragStart: ->
     @$el.addClass('drag-start')

   onDrag: ->
     @$el.removeClass('drag-start hovered')
-    # @$el.removeClass('hovered')
-    # setTimeout( =>
-    #   @$el.removeClass('hovered')
-    #   @$el.siblings('.macro--child').removeClass('hovered')
-    # , 1000)
-
-    console.log 'onDragStart'
+    @$el.siblings('.macro--child').removeClass('drag-start hovered')

   removeMacro: ->
     @model.collection.remove(@model)


added gzip

added gzip

--- src/lib/bot.coffee
+++ src/lib/bot.coffee
@@ -87,6 +87,7 @@
         init: (callback) ->

             app.configure ->
+                app.use express.compress()
                 app.use express.urlencoded()
                 app.use express.json()


add help; single key for all operations

add help; single key for all operations

--- src/tf.coffee
+++ src/tf.coffee
@@ -21,13 +21,32 @@

 module.exports = (robot) ->

+  robot.respond /auth help$/, (msg) ->
+    cmds = []
+    arr = [
+      "tf create key - create rsa key, for git operations"
+      "tf destroy key - erase key"
+      "tf show key - display public key"
+      "tf clone <repourl> <projectname>"
+    ]
+
+    for str in arr
+      cmd = str.split " - "
+      cmds.push "`#{cmd[0]}` - #{cmd[1]}"
+
+    if replyInPrivate and msg.message?.user?.name?
+      msg.reply 'replied to you in private!'
+      robot.send {room: msg.message?.user?.name}, cmds.join "\n"
+    else
+      msg.reply cmds.join "\n"
+
   robot.respond /tf create key$/i, (msg) ->
     unless robot.auth.isAdmin(msg.envelope.user) or robot.auth.hasRole(msg.envelope.user,'tf')
       return msg.send {room: msg.message.user.name}, "Not authorized.  Missing 'tf' role."

-    fn = msg.message.user.name
-    fn.replace /\//, "_"
-    fp = basepath + "/" + fn
+    #fn = msg.message.user.name
+    #fn.replace /\//, "_"
+    fp = basepath + "/hubot-tf"

     if fs.existsSync("#{fp}.pub")
       return msg.send {room: msg.message.user.name}, "Key exists!  Destroy it, then try this again?"
@@ -41,9 +60,9 @@
     unless robot.auth.isAdmin(msg.envelope.user) or robot.auth.hasRole(msg.envelope.user,'tf')
       return msg.send {room: msg.message.user.name}, "Not authorized.  Missing 'tf' role."

-    fn = msg.message.user.name
-    fn.replace /\//, "_"
-    fp = basepath + "/" + fn
+    #fn = msg.message.user.name
+    #fn.replace /\//, "_"
+    fp = basepath + "/hubot-tf"

     if ! fs.existsSync("#{fp}.pub")
       return msg.send {room: msg.message.user.name}, "No key on file!  Create one first, eh?"
@@ -55,9 +74,9 @@
     unless robot.auth.isAdmin(msg.envelope.user) or robot.auth.hasRole(msg.envelope.user,'tf')
       return msg.send {room: msg.message.user.name}, "Not authorized.  Missing 'tf' role."

-    fn = msg.message.user.name
-    fn.replace /\//, "_"
-    fp = basepath + "/" + fn
+    #fn = msg.message.user.name
+    #fn.replace /\//, "_"
+    fp = basepath + "/hubot-tf"

     if ! fs.existsSync("#{fp}.pub")
       return msg.send {room: msg.message.user.name}, "No key on file!  Create one first, eh?"
@@ -74,9 +93,9 @@
     projname = msg.match[2].replace /\//, "_"
     projpath = basepath + "/" + projname

-    fn = msg.message.user.name
-    fn.replace /\//, "_"
-    fp = basepath + "/" + fn
+    #fn = msg.message.user.name
+    #fn.replace /\//, "_"
+    fp = basepath + "/hubot-tf"

     exec "GIT_SSH_COMMAND='ssh -i #{fp} -F /dev/null -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no' git clone #{url} #{projpath}", (error, stdout, stderr) ->
       if error


Cleanup

Cleanup

--- layout.coffee
+++ layout.coffee
@@ -195,7 +195,7 @@
 			() ->
 				val = scope[name]
 #				console.log 'event callback', name, val, scope
-				(new Function(['value'], js).bind(scope))(val)
+				(new Function(['value'], js)).bind(scope)(val)

 		bindConstraints: () ->
 			# register constraints last
@@ -257,7 +257,7 @@
 	#		console.log('setStyle', name, value)
 			@jqel.css(name, value)
 		setParent: (parent) ->
-			if (parent instanceof Sprite)
+			if parent instanceof Sprite
 				parent = parent.jqel

 			parent = $(parent) unless parent instanceof jQuery


Cleanup, share element attribute flattening

--- layout.coffee
+++ layout.coffee
@@ -200,12 +200,12 @@
     bindConstraints: () ->
       # register constraints last
       for name, value of @constraints
-       # console.log 'binding constraint', name, value, this
+        # console.log 'binding constraint', name, value, @
         @setAttribute(name, value())
         for bindexpression, binding of @constraints[name].bindings
           property = binding.property
           boundref = binding.compiled()
-          boundref = boundref.$view if boundref.$view
+          boundref ?= boundref.$view
           # console.log 'binding to', property, 'on', boundref
           boundref.bind(property, @constraintCallback(name, value))

@@ -313,6 +313,12 @@
       # console.log 'animate', arguments, @sprite.animate
       @sprite.animate.apply(this, arguments)

+  # flatten element.attributes to a hash
+  flattenattributes = (namednodemap)  ->
+    attributes = {}
+    for i in namednodemap
+      attributes[i.name] = i.value
+    attributes

   # init classes based on an existing element
   initFromElement = (el, parent) ->
@@ -321,10 +327,7 @@
       console.warn 'could not find class for tag', tagname, el
       return

-    attributes = {}
-    for i in el.attributes
-      # console.log 'attribute', i.name, i.value
-      attributes[i.name] = i.value
+    attributes = flattenattributes(el.attributes)

     # swallow builtin attributes
     for event in mouseEvents
@@ -365,10 +368,8 @@
       for elchild in el.childNodes
         if elchild.localName is 'attribute'
           # console.log 'attribute tag', elchild
-          attributes = {}
-            attributes[i.name] = i.value
-            # classattributes[i.name] = i.value
-
+          attributes = flattenattributes(elchild.attributes)
+          classattributes[attributes.name] = attributes.value
           classattributes.types[attributes.name] = attributes.type
           # console.log 'set class attribute', attributes, classattributes
       # serialize the tag's contents
@@ -380,7 +381,7 @@
       lz[name] = (instanceel, instanceattributes) ->
         attributes = {}
         for key, value of classattributes
-          attributes[key] = instanceattributes[key] ? value
+          attributes[key] = value
         for key, value of instanceattributes
           # console.log 'overriding class attribute', key, value
           attributes[key] = value


Clean up destroy code

Clean up destroy code

Avoid splicing an array that's being interated on

--- layout.coffee
+++ layout.coffee
@@ -335,12 +335,12 @@
       # console.log 'set_name', name, this
       @parent?[name] = @

-    _removeFromParent: (name, skipevents) ->
+    _removeFromParent: (name) ->
       return if not @parent
       index = @parent[name].indexOf(@)
       if (index != -1)
         @parent[name].splice(index, 1)
-        @parent.trigger(name) if (not skipevents) and @parent.events?[name]
+        @parent.sendEvent(name)
       return

     destroy: (skipevents) ->
@@ -351,13 +351,16 @@
         @stopListening()
       @unbind()

+      # remove name reference
       if (@parent?[@name] == @)
         delete @parent[@name]

-      for subnode, i in @subnodes
+      # console.log 'destroying', @subnodes, @
+      for subnode in @subnodes
+        # console.log 'destroying', subnode, @
         subnode.destroy(true)

-      @_removeFromParent('subnodes', skipevents)
+      @_removeFromParent('subnodes') unless skipevents

   class Sprite
@@ -486,10 +489,10 @@
     set_clip: (clip) ->
       @sprite.set_clip(clip)

-    destroy: ->
+    destroy: (skipevents) ->
       # console.log 'destroy view', @
-      super()
-      @_removeFromParent('subviews')
+      super(skipevents)
+      @_removeFromParent('subviews') unless skipevents

       @sprite.destroy()
       @sprite = null
@@ -696,11 +699,11 @@
     skip: ->
       true if @locked or (not @parent?.subviews) or (@parent.subviews.length == 0)

-    destroy: ->
+    destroy: (skipevents) ->
       @locked = true
       # console.log 'destroy layout', @
-      super()
-      @_removeFromParent('layouts')
+      super(skipevents)
+      @_removeFromParent('layouts') unless skipevents

   class SimpleLayout extends Layout


Fix #1 safely come back originalPane only when it's still alive

--- lib/ui.coffee
+++ lib/ui.coffee
@@ -35,7 +35,7 @@
     @editor.isModified = -> false

   destroy: ->
-    @originalPane.activate()
+    @originalPane.activate() if @originalPane.isAlive()
     @provider?.destroy?()
     @gutterMarker?.destroy()


cleanup further

cleanup further

--- lib/ui.coffee
+++ lib/ui.coffee
@@ -94,7 +94,11 @@
   registerCommands: ->
     atom.commands.add @editorElement,
       'core:confirm': => @confirm()
+      'core:move-up': (event) => @moveUpOrDown(event, 'previous')
+      'core:move-down': (event) => @moveUpOrDown(event, 'next')
+
       'narrow:close': (event) => @narrowClose(event)
+
       'narrow-ui:confirm-keep-open': => @confirm(keepOpen: true)
       'narrow-ui:protect': => @toggleProtected()
       'narrow-ui:preview-item': => @preview()
@@ -105,8 +109,6 @@
       'narrow-ui:move-to-prompt': => @moveToPrompt()
       'narrow-ui:start-insert': => @setReadOnly(false)
       'narrow-ui:stop-insert': => @setReadOnly(true)
-      'core:move-up': (event) => @moveUpOrDown(event, 'previous')
-      'core:move-down': (event) => @moveUpOrDown(event, 'next')
       'narrow-ui:update-real-file': => @updateRealFile()
       'narrow-ui:exclude-file': => @excludeFile()
       'narrow-ui:clear-excluded-files': => @clearExcludedFiles()
@@ -419,7 +421,7 @@

       oldSelectedItem = @items.getSelectedItem()
       wasAtSelectedItem = @isAtSelectedItem()
-      oldColumn = @editor.getCursorBufferPosition().column
+      oldColumn = @getCursorColumn()

       @items.setItems([@promptItem, items...])
       @renderItems(items)
@@ -429,8 +431,7 @@
           @items.selectItem(item)
           if wasAtSelectedItem
             @moveToSelectedItem(ignoreCursorMove: not @isActive())
-            row = @editor.getCursorBufferPosition().row
-            @editor.setCursorBufferPosition([row, oldColumn])
+            @editor.setCursorBufferPosition([@getCursorRow(), oldColumn])
         else
           @items.selectFirstNormalItem()
       else
@@ -530,8 +531,7 @@
   moveToSelectedItem: ({scrollToColumnZero, ignoreCursorMove}={}) ->
     return if (row = @items.getRowForSelectedItem()) is -1

-    {column} = @editor.getCursorBufferPosition()
-    point = scrollPoint = [row, column]
+    point = scrollPoint = [row, @getCursorColumn()]
     scrollPoint = [row, 0] if scrollToColumnZero

     moveAndScroll = =>
@@ -568,7 +568,7 @@
   # Cursor move and position status
   # ------------------------------
   isAtSelectedItem: ->
-    @editor.getCursorBufferPosition().row is @items.getRowForSelectedItem()
+    @getCursorRow() is @items.getRowForSelectedItem()

   moveToDifferentFileItem: (direction) ->
     unless @isAtSelectedItem()
@@ -601,13 +601,19 @@
       @editor.setCursorBufferPosition(@getPromptRange().end)
       @setReadOnly(false)
       @emitDidMoveToPrompt()
+
+  getCursorRow: ->
+    @editor.getCursorBufferPosition().row
+
+  getCursorColumn: ->
+    @editor.getCursorBufferPosition().column
   # -------------------------

   isPromptRow: (row) ->
     row is 0

   isAtPrompt: ->
-    @isPromptRow(@editor.getCursorBufferPosition().row)
+    @isPromptRow(@getCursorRow())

   getNormalItemsForEditor: (editor) ->
     if @provider.boundToSingleFile


Use a generator for coffeescript, too.

Use a generator for coffeescript, too.

This was a pain.  It took me a while to figure out that javascript
and coffeescript can't use yield directly from a regular function
passed to forEach.  The ruby way of having an explicit yielder wins.

--- sudoku.coffee
+++ sudoku.coffee
@@ -1,3 +1,4 @@
+# This needs to be run with coffee --nodejs --harmony.
 #!/usr/bin/env coffee

 fs = require("fs")
@@ -5,7 +6,10 @@
 main = ->
   text = fs.readFileSync(process.argv[2], {encoding: "utf8"})
   text = text.replace(/#.*/g, "").replace(/\s/g, "")
-  solutions = new Puzzle(text).solve()
+  # Well this sucks the big one.  Can't I use a comprehension to
+  # get stuff out of the generator?
+  collect = (gen) -> ref.value until (ref = gen.next()).done
+  solutions = collect new Puzzle(text).solve()
   console.log "#{solutions.length} solutions"

 class Puzzle
@@ -115,7 +119,7 @@
         position.place(position.possible[0])
         true

-  # Returns an Array of solved Puzzles.
+  # This is a generator . . .

   solve: ->
     # In order to come up with a sequence somewhat like a person
@@ -141,20 +145,26 @@
         # Solved.  Return this as a solution.
         console.log "Solved:"
         @print_puzzle()
-        [@]
+        yield @
       when next_position.possible.is_empty()
         # Failed.  No solution to return.
         console.log "Backing out."
-        []
       else
+        console.log "Have to guess"
         # Found an unplaced position with possibilities.  Guess each
         # possibility recursively, and return any solutions we find.
-        next_position.possible.flatMap (digit) =>
+        console.log "possibilities: #{next_position.possible}"
+        next_position.possible.forEach (digit) =>
+          console.log "possibility: #{digit}"
+        # Can't (easily) use forEach and have the generator work since
+        # forEach wants a regular function.  WTF javascript and coffeescript
+        for digit in next_position.possible
+          console.log "guessing #{digit}"
           console.log "trying #{digit} in position " +
                       "#{next_position.number} #{next_position.possible}"
           puzzle = new Puzzle(@to_string())
           puzzle.position(next_position.number).place(digit)
-          puzzle.solve()
+          yield from puzzle.solve()

   eliminate_with_tricky_sets: ->
     @tricky_sets.some ([subset, rest_of_set, elimination_set]) =>


Stand-alone functions to fetch/save parameters

--- lib/db.coffee
+++ lib/db.coffee
@@ -1,5 +1,5 @@
 ###
-# Copyright 2013, 2014  Zaid Abdulla
+# Copyright 2013-2016  Zaid Abdulla
 #
 # GenieACS is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Affero General Public License as
@@ -19,6 +19,7 @@
 mongodb = require 'mongodb'
 redis = require('redis')
 parameters = require './parameters'
+common = require './common'

 redisClient = null
 tasksCollection = null
@@ -223,8 +224,273 @@
   )

+# Optimize projection by removing overlaps
+# This modifies given object and returns it
+optimizeProjection = (obj) ->
+  keys = Object.keys(obj).sort()
+  return if keys.length <= 1
+  i = 1
+  while i < keys.length
+    a = keys[i-1]
+    b = keys[i]
+    if common.startsWith(b, a)
+      if b.charAt(a.length) == '.' or b.charAt(a.length - 1) == '.'
+        delete obj[b]
+        keys.splice(i--, 1)
+    ++ i
+  return obj
+
+
+fetchDevice = (id, timestamp, patterns, callback) ->
+  res = []
+
+  # Build projection
+  projection = {_id: 1}
+  for pattern in patterns
+    if pattern.length == 0
+      projection['_timestamp'] = 1
+    else if not pattern[0]?
+      projection[''] = 1
+    else if pattern[0] == 'Events'
+      if pattern.length == 1
+        res.push([['Events'], timestamp, 1, timestamp, 1, timestamp, 0])
+      else if pattern.length == 2
+        if not pattern[1]?
+          projection['_registered'] = 1
+          projection['_lastInform'] = 1
+          projection['_lastBootstrap'] = 1
+          projection['_lastBoot'] = 1
+          res.push([['Events', null], timestamp])
+        else if pattern[1] == 'Registered'
+          projection['_registered'] = 1
+        else if pattern[1] == 'Inform'
+          projection['_lastInform'] = 1
+        else if pattern[1] == '0_BOOTSTRAP'
+          projection['_lastBootstrap'] = 1
+        else if pattern[1] == '1_BOOT'
+          projection['_lastBoot'] = 1
+    else if pattern[0] == 'DeviceID'
+      if pattern.length == 1
+        res.push([['DeviceID'], timestamp, 1, timestamp, 1, timestamp, 0])
+      else if pattern.length == 2
+        if not pattern[1]?
+          projection['_id'] = 1
+          projection['_deviceId._Manufacturer'] = 1
+          projection['_deviceId._ProductClass'] = 1
+          projection['_deviceId._SerialNumber'] = 1
+          projection['_deviceId._OUI'] = 1
+          res.push([['DeviceID', null], timestamp])
+        else if pattern[1] == 'ID'
+          projection['_id'] = 1
+        else if pattern[1] == 'Manufacturer'
+          projection['_deviceId._Manufacturer'] = 1
+        else if pattern[1] == 'ProductClass'
+          projection['_deviceId._ProductClass'] = 1
+        else if pattern[1] == 'OUI'
+          projection['_deviceId._OUI'] = 1
+        else if pattern[1] == 'SerialNumber'
+          projection['_deviceId._SerialNumber'] = 1
+    else if pattern[0] == 'Tags'
+      if pattern.length == 1
+        res.push([['Tags'], timestamp, 1, timestamp, 1, timestamp, 0])
+      else if pattern.length == 2
+        res.push([['Tags', null], timestamp])
+        projection['_tags'] = 1
+    else
+      for i in [0...pattern.length] by 1
+        break if not pattern[i]
+
+      if i == pattern.length
+        s = pattern.join('.')
+        projection["#{s}._value"] = 1
+        projection["#{s}._timestamp"] = 1
+        projection["#{s}._type"] = 1
+        projection["#{s}._writable"] = 1
+        projection["#{s}._object"] = 1
+        projection["#{s}._orig"] = 1
+
+        # Timestamp from parent is needed for writable timestamp
+        if pattern.length <= 1
+          projection['_timestamp'] = 1
+        else
+          projection["#{pattern.slice(0, -1).join('.')}._timestamp"] = 1
+      else
+        s = pattern.slice(0, i).join('.')
+        projection[s] = 1
+
+  if projection['']
+    projection = {}
+
+  if projection?
+    optimizeProjection(projection)
+
+  devicesCollection.findOne({'_id' : id}, projection, (err, device) ->
+    return callback(err) if err or not device?
+
+    storeParams = (obj, path, timestamp, descendantsFetched) ->
+      if not descendantsFetched and not obj['_value']? and projection[path.join('.')]
+        descendantsFetched = true
+
+      if obj['_timestamp']?
+        obj['_timestamp'] = +obj['_timestamp']
+
+      v = [timestamp, 1]
+
+      if obj['_value']?
+        v[6] = obj['_timestamp'] ? timestamp
+        v[7] = [obj['_value'], obj['_type']]
+        obj['_object'] = false
+
+      if obj['_writable']?
+        v[4] = timestamp
+        v[5] = if obj['_writable'] then 1 else 0
+
+      if obj['_object']?
+        v[2] = timestamp
+        v[3] = if obj['_object'] then 1 else 0
+
+      if v.length
+        v[0] ?= timestamp ? 0
+        v[1] ?= 1
+        res.push([path].concat(v))
+
+      for k, v of obj
+        if k[0] != '_'
+          obj['_object'] = true
+          storeParams(v, path.concat(k), obj['_timestamp'] ? 0, descendantsFetched)
+
+      if obj['_object'] and descendantsFetched
+        res.push([path.concat(null), obj['_timestamp'] ? 0])
+
+    for k, v of device
+      if k == '_timestamp'
+        res.push([[null], v]) if not Object.keys(projection).length
+      else if k == '_lastInform'
+        res.push([['Events', 'Inform'], timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [v, 'xsd:dateTime']])
+      else if k == '_lastBoot'
+        res.push([['Events', '1_BOOT'], timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [v, 'xsd:dateTime']])
+      else if k == '_lastBootstrap'
+        res.push([['Events', '0_BOOTSTRAP'], timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [v, 'xsd:dateTime']])
+      else if k == '_registered'
+        res.push([['Events', 'Registered'], timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [v, 'xsd:dateTime']])
+      else if k == '_id'
+        res.push([['DeviceID', 'ID'], timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [v, 'xsd:string']])
+      else if k == '_tags'
+        for t in v
+          res.push([['Tags', t], timestamp, 1, timestamp, 0, timestamp, 1, timestamp, [true, 'xsd:boolean']])
+      else if k == '_deviceId'
+        for kk, vv of v
+          if kk == '_Manufacturer'
+            res.push([['DeviceID', 'Manufacturer'], timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [vv, 'xsd:string']])
+          else if kk == '_OUI'
+            res.push([['DeviceID', 'OUI'], timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [vv, 'xsd:string']])
+          if kk == '_ProductClass'
+            res.push([['DeviceID', 'ProductClass'], timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [vv, 'xsd:string']])
+          if kk == '_SerialNumber'
+            res.push([['DeviceID', 'SerialNumber'], timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [vv, 'xsd:string']])
+      else if common.typeOf(v) is common.OBJECT_TYPE
+        storeParams(v, [k], 0, projection == null)
+
+    return callback(null, res)
+  )
+
+
+saveDevice = (deviceId, diff, isNew, callback) ->
+  update = {'$set' : {}, '$unset' : {}, '$addToSet' : {}, '$pull' : {}}
+  for p in diff
+    base = p[2]
+    current = p[3]
+    if p[0][0] == 'Events'
+      if p[0].length == 2
+        if p[0][1] == 'Inform'
+          update['$set']['_lastInform'] = new Date(current[7][0])
+        else if p[0][1] == '0_BOOTSTRAP'
+          update['$set']['_lastBootstrap'] = new Date(current[7][0])
+        else if p[0][1] == '1_BOOT'
+          update['$set']['_lastBoot'] = new Date(current[7][0])
+        else if p[0][1] == 'Registered'
+          update['$set']['_registered'] = new Date(current[7][0])
+    else if p[0][0] == 'Tags'
+      if p[0].length == 2 and p[0][1]?
+        if current[7][0] and not base[7]?[0]
+          update['$addToSet']['_tags'] ?= {'$each' : []}
+          update['$addToSet']['_tags']['$each'].push(p[0][1])
+        else if not current[7]?[0] and base[7]?[0]
+          update['$pull']['_tags'] ?= {'$in' : []}
+          update['$pull']['_tags']['$in'].push(p[0][1])
+    else if p[0][0] == 'DeviceID'
+      if p[0].length == 2 and current[7]?
+        if p[0][1] == 'ID'
+          update['$set']['_id'] = current[7][0]
+        else if p[0][1] == 'Manufacturer'
+          update['$set']['_deviceId._Manufacturer'] = current[7][0]
+        else if p[0][1] == 'OUI'
+          update['$set']['_deviceId._OUI'] = current[7][0]
+        else if p[0][1] == 'ProductClass'
+          update['$set']['_deviceId._ProductClass'] = current[7][0]
+        else if p[0][1] == 'SerialNumber'
+          update['$set']['_deviceId._SerialNumber'] = current[7][0]
+    else if p[1] == p[0].length
+      param = p[0].join('.')
+      if current[2]?
+        if current[3] != base[3]
+          if current[3]
+            update['$set']["#{param}._object"] = true
+          else if base[3]?
+            update['$unset']["#{param}._object"] = 1
+      else if base[2]?
+        update['$unset']["#{param}._object"] = 1
+
+      if current[4]?
+        if current[5] != base[5]
+          update['$set']["#{param}._writable"] = !!current[5]
+      else if base[4]?
+        update['$unset']["#{param}._writable"] = 1
+
+      if current[6]? and current[7]?
+        if current[6] != base[6]
+          update['$set']["#{param}._timestamp"] = new Date(current[6])
+
+        if current[7][0] != base[7]?[0] or current[7][1] != base[7]?[1]
+          if current[7][1] == 'xsd:dateTime'
+            update['$set']["#{param}._value"] = new Date(current[7][0])
+          else
+            update['$set']["#{param}._value"] = current[7][0]
+          update['$set']["#{param}._type"] = current[7][1]
+      else if base[7]?
+        update['$unset']["#{param}._value"] = 1
+        update['$unset']["#{param}._type"] = 1
+    else if p[1] == p[0].length - 1 and not p[0][p[1]]?
+      param = p[0].slice(0, p[1]).concat('_timestamp').join('.')
+      if current?
+        update['$set'][param] = new Date(current)
+      else if base?
+        update['$unset'][param] = 1
+
+  # Remove empty keys
+  for k of update
+    if k == '$addToSet'
+      for kk of update[k]
+        delete update[k][kk] if update[k][kk]['$each'].length == 0
+    else if k == '$pull'
+      for kk of update[k]
+        delete update[k][kk] if update[k][kk]['$in'].length == 0
+    delete update[k] if Object.keys(update[k]).length == 0
+
+  return callback() if Object.keys(update).length == 0
+
+  devicesCollection.update({'_id' : deviceId}, update, {upsert: isNew}, (err, count) =>
+    if not err and count != 1
+      return callback(new Error("Device #{deviceId} not found in database"))
+
+    return callback(err)
+  )
+
+
 exports.getTask = getTask
 exports.getPresetsObjectsAliases = getPresetsObjectsAliases
 exports.getAliases = getAliases
 exports.connect = connect
 exports.disconnect = disconnect
+exports.fetchDevice = fetchDevice
+exports.saveDevice = saveDevice


unique title again after pending open support

introducing pending open reveal flaw of previous unique title logic
Now return unique title again

--- lib/ui.coffee
+++ lib/ui.coffee
@@ -36,12 +36,10 @@
     atom.views.getView(atom.workspace).classList.toggle('has-narrow', @uiByEditor.size)

   @getNextTitleNumber: ->
-    titleNumbers = [0]
-    console.log 'size', @uiByEditor.size
-
+    numbers = [0]
     @uiByEditor.forEach (ui) ->
-      titleNumbers.push(ui.titleNumber)
-    Math.max(titleNumbers...) + 1
+      numbers.push(ui.titleNumber)
+    Math.max(numbers...) + 1

   # UI.prototype
   # -------------------------
@@ -178,7 +176,6 @@

   constructor: (@provider, {@query, @activate, @pending}={}) ->
     @titleNumber = @constructor.getNextTitleNumber()
-    console.log @titleNumber
     @pending ?= false
     @activate ?= true
     @disposables = new CompositeDisposable
@@ -199,12 +196,7 @@

     providerDashName = @provider.getDashName()
     title = providerDashName + '-' + @titleNumber
-    # console.log title
     @editor.getTitle = -> title
-      # @title ?= providerDashName + '-' + @constructor.uiByEditor.size
-      # console.log @title
-      # @title
-      # # @title ?= providerDashName + '-' + @constructor.uiByEditor.size
     @editor.onDidDestroy(@destroy.bind(this))
     @editorElement = @editor.element
     @editorElement.classList.add('narrow', 'narrow-editor', providerDashName)


#58 manually destroy selection specially for find-and-replace:select-all

--- lib/ui.coffee
+++ lib/ui.coffee
@@ -67,6 +67,7 @@
     @registerCommands()
     @disposables.add(@observeInputChange())
     @observeCursorPositionChangeForNarrowEditor()
+    @observerNarrowEditorToProtectFromUnwantedMutation()

     @disposables.add atom.workspace.onDidStopChangingActivePaneItem (item) =>
       @rowMarker?.destroy() if item isnt @narrowEditor
@@ -328,3 +329,17 @@
     texts = items.map (item) => @provider.viewForItem(item)
     @appendText(texts.join("\n"))
     @selectItemForRow(@findValidItem(1, 'next'))
+
+  observerNarrowEditorToProtectFromUnwantedMutation: ->
+    # HACK:
+    # auto-DESTROY selection when `ctrl-cmd-g`(find-and-replace:select-all)
+    # which intersects first row(prompt-row).
+    @disposables.add atom.commands.onDidDispatch ({target, type}) =>
+      if type is 'find-and-replace:select-all' and
+          atom.workspace.isTextEditor(editor = target.getModel?()) and
+            editor is @narrowEditor
+
+        if editor.hasMultipleCursors()
+          promptRange = editor.bufferRangeForBufferRow(0)
+          for selection in editor.getSelections() when selection.getBufferRange().intersectsWith(promptRange)
+            selection.destroy()


Add `iron-media-query`

Add `iron-media-query`

--- snippets/iron-elements.cson
+++ snippets/iron-elements.cson
@@ -49,9 +49,16 @@
   "iron-localstorage":
     "prefix": "iron-localstorage"
     "body": """
-      <iron-localstorage
-        name="${0:my-app-storage}"
-        value="{{${1:cartoon}}}"></iron-localstorage>
+    <iron-localstorage
+      name="${0:my-app-storage}"
+      value="{{${1:cartoon}}}"></iron-localstorage>
+    """
+  "iron-media-query":
+    "prefix": "iron-media-query"
+    "body": """
+    <iron-media-query
+      query="${1:(min-width: 600px)}"
+      query-matches="{{${2:queryMatches}}}"></iron-media-query>
     """
   "iron-pages":
     "prefix": "iron-pages"


i can't here you akwan what did you say?

--- scripts/name-expander.coffee
+++ scripts/name-expander.coffee
@@ -97,8 +97,8 @@
   robot.hear /^mergers:(.+)/i, (msg) ->
     msg.send chef_repo_mergers.join(", ") + ": ^^"

-  robot.here /^@?(build)( team| dudes| guys)*:(.+)/i, (msg) ->
+  robot.hear /^@?(build)( team| dudes| guys)*:(.+)/i, (msg) ->
     msg.send build_members.join(", ") + ": ^^"

-  robot.here /^@?(di|devinf) ( team| dudes| guys)*:(.+)/i, (msg) ->
+  robot.hear /^@?(di|devinf) ( team| dudes| guys)*:(.+)/i, (msg) ->
     msg.send devinf_members.join(", ") + ": ^^"


main.dev: add new line otherwise it merges with next line

--- config/main.dev.coffee
+++ config/main.dev.coffee
@@ -248,6 +248,7 @@
       env = """
       export GOPATH=#{projectRoot}/go
       export GOBIN=#{projectRoot}/go/bin
+
       """
       env += "export #{key}='#{val}'\n" for key,val of KONFIG.ENV
       return env


Hide unresolved counter for deleted maps

--- resources/assets/coffee/react/beatmap-discussions/beatmap-list.coffee
+++ resources/assets/coffee/react/beatmap-discussions/beatmap-list.coffee
@@ -55,6 +55,8 @@
     menuItemClasses = "#{bn}__item"
     menuItemClasses += " #{bn}__item--current" if beatmap.id == @props.currentBeatmap.id

+    count = if beatmap.deleted_at? then null else @props.currentDiscussions.countsByBeatmap[beatmap.id]
+
     a
       href: BeatmapDiscussionHelper.hash beatmapId: beatmap.id
       className: menuItemClasses
@@ -64,7 +66,7 @@
       el BeatmapDiscussions.BeatmapListItem,
         beatmap: beatmap
         mode: 'version'
-        count: @props.currentDiscussions.countsByBeatmap[beatmap.id]
+        count: count

   hideSelector: (e) =>


Get the registration session from the scan.

--- app/javascripts/router.coffee
+++ app/javascripts/router.coffee
@@ -272,19 +272,25 @@
               registration.scan = scan
               # Set the key.
               registration.key = key
+              # The scan set.
+              scanSet = session.subject.scanSets[scan.scanType]
+              if not scanSet?
+                throw new ReferenceError "#{ subjectTitle(session) } does not have" +
+                                         " a #{ scan.scanType } scan set."
               # Set the registration configuration.
-              registration.configuration = session.subject.registration[key]
+              registration.configuration = scanSet.registration[key]
               if not registration.configuration?
                 throw new ReferenceError "#{ subjectTitle(session) } does not have" +
                                          " a #{ key } registration configuration."
+
               # Define the session property required by the image factory.
               # Image containers present a uniform interface, which includes
               # a session reference.
               Object.defineProperty registration, 'session',
                 get: ->
-                  scan.session
+                  registration.scan.session
               # Add the registration images.
-              addImageContainerContent(reg, key)
+              addImageContainerContent(registration, key)

             # Set the session reference.
             scan.session = session
@@ -301,7 +307,7 @@
                                      " Session #{ session.number }" +
                                      " does not reference a detail object"

-          Session.detail(id: session.detail).$promise.then (detail) =>
+          Session.detail(id: session.detail).$promise.then (detail) ->
             # Copy the fetched detail into the session.
             ObjectHelper.aliasPublicDataProperties(detail, session)
             # Add properties to the scans and their registration.


Don't allow zooming out as far, easy to get lost in level editor

--- app/lib/surface/Camera.coffee
+++ app/lib/surface/Camera.coffee
@@ -8,7 +8,7 @@
 d2r = (degrees) -> degrees / 180 * Math.PI

 MAX_ZOOM = 8
-MIN_ZOOM = 0.1
+MIN_ZOOM = 0.25
 DEFAULT_ZOOM = 2.0
 DEFAULT_TARGET = {x: 0, y: 0}
 DEFAULT_TIME = 1000


make keyDefinitions into a class

make keyDefinitions into a class

--- assets/js/keyDefinitions.coffee
+++ assets/js/keyDefinitions.coffee
@@ -4,246 +4,224 @@
   global.errors = require('./errors.coffee')
   global.Modes = require('./modes.coffee')

-###
-keyDefinitions defines the set of possible commands.
-Each command has a name, which the keyDefinitions dictionary maps to a definition,
-which describes what the command should do in various modes.
-
-Each definition has the following required fields:
-    description:
-        a string used for description in keybindings help screen
-The definition should have functions for each mode that it supports
-The functions will be passed contexts depending on each mode
-  TODO: document these
-  view:
-  keyStream:
-  repeat:
-It may also have, bindings:
-    another (recursive) set of key definitions, i.e. a dictionary from command names to definitions
-
-NOTE: there is a special command called 'MOTION', which is used in the bindings dictionaries
-    much like if the motion boolean is true, this command always takes an extra cursor argument.
-    TODO: this is a hack, and should be done more properly
-
-For more info/context, see keyBindings.coffee
-###
-
 ((exports) ->
-  MODES = Modes.modes
-
-  NORMAL_MODE_TYPE = Modes.NORMAL_MODE_TYPE
-  INSERT_MODE_TYPE = Modes.INSERT_MODE_TYPE
-
-  WITHIN_ROW_MOTIONS = {}
-  ALL_MOTIONS = {}
-
-  # set of possible commands for each mode
-  commands_by_mode = {}
-  for modename, mode of MODES
-    commands_by_mode[mode] = []
-
-  defaultHotkeys = {}
-
-  # key mappings for normal-like modes (normal, visual, visual-line)
-  defaultHotkeys[NORMAL_MODE_TYPE] = {}
-  # key mappings for insert-like modes (insert, mark, menu)
-  defaultHotkeys[INSERT_MODE_TYPE] = {}
-
-  COMMAND_SCHEMA = {
-    title: "Command metadata schema"
-    type: "object"
-    required: [ 'name' ]
-    properties: {
-      name: {
-        description: "Name of the command"
-        type: "string"
-        pattern: "^[A-Z_]{2,32}$"
-      }
-      description: {
-        description: "Description of the command"
-        type: "string"
-      }
-      default_hotkeys: {
-        description: "Default hotkeys for the command"
-        type: "object"
-        properties: {
-          all: {
-            description: "Default hotkeys for all modes"
-            type: "array"
-            default: []
-            items: { type: "string" }
-          }
-          normal_like: {
-            description: "Default hotkey for normal-like modes"
-            type: "array"
-            default: []
-            items: { type: "string" }
-          }
-          insert_like: {
-            description: "Default hotkey for insert-like modes"
-            type: "array"
-            default: []
-            items: { type: "string" }
-          }
-        }
-      }
-    }
-  }
-
   class Command
     constructor: (metadata) ->
       @metadata = metadata
       @name = metadata.name

-  commands = {}
-
-  registerCommand = (metadata) ->
-    utils.tv4_validate(metadata, COMMAND_SCHEMA, "command")
-    utils.fill_tv4_defaults(metadata, COMMAND_SCHEMA)
-    name = metadata.name
-    command = new Command metadata
-    commands[name] = command
-    defaultHotkeys[NORMAL_MODE_TYPE][name] =
-      (_.cloneDeep metadata.default_hotkeys.all).concat(
-       _.cloneDeep metadata.default_hotkeys.normal_like
-      )
-    defaultHotkeys[INSERT_MODE_TYPE][name] =
-      (_.cloneDeep metadata.default_hotkeys.all).concat(
-       _.cloneDeep metadata.default_hotkeys.insert_like
-      )
-    return command
-
-  # MOTIONS
-  # should have a fn, returns a motion fn (or null)
-  # the motion itself should take a cursor, and an options dictionary
-  # (it should presumably move the cursor, somehow)
-  # options include:
-  #     pastEnd: whether to allow going past the end of the line
-  #     pastEndWord: whether we consider the end of a word to be after the last letter
-
-  MOTION_SCHEMA = {
-    title: "Motion metadata schema"
-    type: "object"
-    required: [ 'description' ]
-    properties: {
-      description: {
-        description: "Description of the motion"
-        type: "string"
-      }
-      multirow: {
-        description: "Whether the motion is only for multi-row movements"
-        type: "boolean"
-        default: false
+  # NOTE: this is a special command, which causes definition functions
+  # to always takes an extra cursor argument.
+  # TODO: this is a hack, and should be done more properly
+  # For more info/context, see keyBindings.coffee and definitions of CHANGE/DELETE/YANK
+  motionCommandName = 'MOTION'
+
+  class KeyDefinitions
+    constructor: () ->
+      @WITHIN_ROW_MOTIONS = {}
+      @ALL_MOTIONS = {}
+
+      # list of possible commands for each mode
+      @commands_by_mode = {}
+      for modename, mode of Modes.modes
+        @commands_by_mode[mode] = []
+
+      @defaultHotkeys = {}
+      # key mappings for normal-like modes (normal, visual, visual-line)
+      @defaultHotkeys[Modes.NORMAL_MODE_TYPE] = {}
+      # key mappings for insert-like modes (insert, mark, menu)
+      @defaultHotkeys[Modes.INSERT_MODE_TYPE] = {}
+
+      @commands = {}
+
+      @motions = {}
+
+      @actions = {}
+      for modename, mode of Modes.modes
+        @actions[mode] = {}
+
+      # TODO: make sure that the default hotkeys accurately represents the set of possible commands under that mode_type
+      #       the following used to work, and should be replaced
+      # for mode_type, mode_type_obj of MODE_TYPES
+      #   errors.assert_arrays_equal(
+      #     _.keys(@defaultHotkeys[mode_type]),
+      #     _.union.apply(_, mode_type_obj.modes.map((mode) -> @commands_by_mode[mode]))
+      #   )
+
+    COMMAND_SCHEMA = {
+      title: "Command metadata schema"
+      type: "object"
+      required: [ 'name' ]
+      properties: {
+        name: {
+          description: "Name of the command"
+          type: "string"
+          pattern: "^[A-Z_]{2,32}$"
+        }
+        description: {
+          description: "Description of the command"
+          type: "string"
+        }
+        default_hotkeys: {
+          description: "Default hotkeys for the command"
+          type: "object"
+          properties: {
+            all: {
+              description: "Default hotkeys for all modes"
+              type: "array"
+              default: []
+              items: { type: "string" }
+            }
+            normal_like: {
+              description: "Default hotkey for normal-like modes"
+              type: "array"
+              default: []
+              items: { type: "string" }
+            }
+            insert_like: {
+              description: "Default hotkey for insert-like modes"
+              type: "array"
+              default: []
+              items: { type: "string" }
+            }
+          }
+        }
       }
     }
-  }
-
-  motionDefinitions = {}
-
-  registerMotion = (commands, motion, definition) ->
-    utils.tv4_validate(motion, MOTION_SCHEMA, "motion")
-    utils.fill_tv4_defaults(motion, MOTION_SCHEMA)
-    motion.definition = definition
-
-    if not commands.slice?
-      # commands isn't an array
-      commands = [commands]
-
-    obj = motionDefinitions
-    for i in [0...commands.length-1]
-      command = commands[i]
-
-      if not motion.multirow
-          WITHIN_ROW_MOTIONS[command.name] = true
-      ALL_MOTIONS[command.name] = true
-
-      if command.name not of obj
-        throw new errors.GenericError "Motion #{command.name} doesn't exist"
-      else if typeof obj[command.name] != 'object'
-        throw new errors.GenericError "Motion #{command.name} has already been defined"
-      obj = obj[command.name].definition
-
-    command = commands[commands.length-1]
-    if not motion.multirow
-        WITHIN_ROW_MOTIONS[command.name] = true
-    ALL_MOTIONS[command.name] = true
-
-    # motion.name = command.name
-    if command.name of obj
-      throw new errors.GenericError "Motion #{command.name} has already been defined"
-    obj[command.name] = motion
-
-  # TODO: make sure that the default hotkeys accurately represents the set of possible commands under that mode_type
-  #       the following used to work, and should be replaced
-  # for mode_type, mode_type_obj of MODE_TYPES
-  #   errors.assert_arrays_equal(
-  #     _.keys(defaultHotkeys[mode_type]),
-  #     _.union.apply(_, mode_type_obj.modes.map((mode) -> commands_by_mode[mode]))
-  #   )
-
-  ACTION_SCHEMA = {
-    title: "Action metadata schema"
-    type: "object"
-    required: [ 'description' ]
-    properties: {
-      description: {
-        description: "Description of the action"
-        type: "string"
+
+    registerCommand: (metadata) ->
+      utils.tv4_validate(metadata, COMMAND_SCHEMA, "command")
+      utils.fill_tv4_defaults(metadata, COMMAND_SCHEMA)
+      name = metadata.name
+      command = new Command metadata
+      @commands[name] = command
+      @defaultHotkeys[Modes.NORMAL_MODE_TYPE][name] =
+        (_.cloneDeep metadata.default_hotkeys.all).concat(
+         _.cloneDeep metadata.default_hotkeys.normal_like
+        )
+      @defaultHotkeys[Modes.INSERT_MODE_TYPE][name] =
+        (_.cloneDeep metadata.default_hotkeys.all).concat(
+         _.cloneDeep metadata.default_hotkeys.insert_like
+        )
+      return command
+
+    # MOTIONS
+    # should have a fn, returns a motion fn (or null)
+    # the motion itself should take a cursor, and an options dictionary
+    # (it should presumably move the cursor, somehow)
+    # options include:
+    #     pastEnd: whether to allow going past the end of the line
+    #     pastEndWord: whether we consider the end of a word to be after the last letter
+
+    MOTION_SCHEMA = {
+      title: "Motion metadata schema"
+      type: "object"
+      required: [ 'description' ]
+      properties: {
+        description: {
+          description: "Description of the motion, shows in HELP menu"
+          type: "string"
+        }
+        multirow: {
+          description: "Whether the motion is only for multi-row movements"
+          type: "boolean"
+          default: false
+        }
       }
     }
-  }
-
-  actionDefinitions = {}
-  for modename, mode of MODES
-    actionDefinitions[mode] = {}
-
-  registerAction = (modes, commands, action, definition) ->
-    utils.tv4_validate(action, ACTION_SCHEMA, "action")
-    action = _.cloneDeep action
-    action.definition = definition
-
-    if not commands.slice?
-      # commands isn't an array
-      commands = [commands]
-
-    for mode in modes
-      obj = actionDefinitions[mode]
-
+
+    registerMotion: (commands, motion, definition) ->
+      utils.tv4_validate(motion, MOTION_SCHEMA, "motion")
+      utils.fill_tv4_defaults(motion, MOTION_SCHEMA)
+      motion.definition = definition
+
+      if not commands.slice?
+        # commands isn't an array
+        commands = [commands]
+
+      obj = @motions
       for i in [0...commands.length-1]
         command = commands[i]
-        if command.name != 'MOTION'
-          commands_by_mode[mode].push command.name
+
+        if not motion.multirow
+            @WITHIN_ROW_MOTIONS[command.name] = true
+        @ALL_MOTIONS[command.name] = true

         if command.name not of obj
-          throw new errors.GenericError "Action #{command.name} doesn't exist"
+          throw new errors.GenericError "Motion #{command.name} doesn't exist"
         else if typeof obj[command.name] != 'object'
-          throw new errors.GenericError "Action #{command.name} has already been defined"
+          throw new errors.GenericError "Motion #{command.name} has already been defined"
         obj = obj[command.name].definition

       command = commands[commands.length-1]
-      if command.name != 'MOTION'
-        commands_by_mode[mode].push command.name
+      if not motion.multirow
+          @WITHIN_ROW_MOTIONS[command.name] = true
+      @ALL_MOTIONS[command.name] = true
+
       # motion.name = command.name
       if command.name of obj
-        throw new errors.GenericError "Action #{command.name} has already been defined"
-      obj[command.name] = action
+        throw new errors.GenericError "Motion #{command.name} has already been defined"
+      obj[command.name] = motion
+
+    ###
+    The definition should have functions for each mode that it supports
+    The functions will be passed contexts depending on each mode
+      TODO: document these
+      view:
+      keyStream:
+      repeat:
+    It may also have, bindings:
+        another (recursive) set of key definitions, i.e. a dictionary from command names to definitions
+    ###
+
+    ACTION_SCHEMA = {
+      title: "Action metadata schema"
+      type: "object"
+      required: [ 'description' ]
+      properties: {
+        description: {
+          description: "Description of the action, shows in HELP menu"
+          type: "string"
+        }
+      }
+    }
+
+    registerAction: (modes, commands, action, definition) ->
+      utils.tv4_validate(action, ACTION_SCHEMA, "action")
+      action = _.cloneDeep action
+      action.definition = definition
+
+      if not commands.slice?
+        # commands isn't an array
+        commands = [commands]
+
+      for mode in modes
+        obj = @actions[mode]
+
+        for i in [0...commands.length-1]
+          command = commands[i]
+          if command.name != motionCommandName
+            @commands_by_mode[mode].push command.name
+
+          if command.name not of obj
+            throw new errors.GenericError "Action #{command.name} doesn't exist"
+          else if typeof obj[command.name] != 'object'
+            throw new errors.GenericError "Action #{command.name} has already been defined"
+          obj = obj[command.name].definition
+
+        command = commands[commands.length-1]
+        if command.name != motionCommandName
+          @commands_by_mode[mode].push command.name
+        # motion.name = command.name
+        if command.name of obj
+          throw new errors.GenericError "Action #{command.name} has already been defined"
+        obj[command.name] = action

   ####################
   # COMMANDS
   ####################

-  me = {
-    commands: commands
-    commands_by_mode: commands_by_mode
-    actions: actionDefinitions
-    motions: motionDefinitions
-    defaultHotkeys: defaultHotkeys
-    registerCommand: registerCommand
-    registerMotion: registerMotion
-    registerAction: registerAction
-    ALL_MOTIONS: ALL_MOTIONS
-    WITHIN_ROW_MOTIONS: WITHIN_ROW_MOTIONS
-  }
+  me = new KeyDefinitions
   module?.exports = me
   window?.keyDefinitions = me
 )()


Fix preference option value

Fix preference option value

--- app/pages/settings/email.cjsx
+++ app/pages/settings/email.cjsx
@@ -30,7 +30,7 @@
     <td className="option">
       <input type="radio"
         name={preference.category}
-        value="{digest}"
+        value={digest}
         checked={preference.email_digest is digest}
         onChange={@handlePreferenceChange.bind this, preference}
       />


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -25,7 +25,7 @@
 # => Sets up a health meter that will be drawn during the enemy overlay event.
 # Since maxHealth wasn't provided, it is set to the value of I.health (500)

-someOtherObject
+someOtherObject.include

 # now player has all the methods provided by this module
 player.position()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -115,7 +115,7 @@
     position:
       x: 5
       y: 5
-    rad
+    radius:
     showName: true

   # =>


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -137,7 +137,7 @@
     radius - Border radius of the meter
     showName - Boolean to toggle whether or not to show the attribute associated with the meter
     text - A String to display over the meter. Overrides default name attribute
-    width -
+    width - How wid
   ###
   meter: (options={}) ->
     Object.reverseMerge options,


set v_end on pack creation

set v_end on pack creation

--- services/track-changes/app/coffee/PackManager.coffee
+++ services/track-changes/app/coffee/PackManager.coffee
@@ -300,6 +300,7 @@
 				top.pack = [ {v: d.v, meta: d.meta,  op: d.op, _id: d._id} ]
 				top.meta = { start_ts: d.meta.start_ts, end_ts: d.meta.end_ts }
 				top.sz = sz
+				top.v_end = d.v
 				delete top.op
 				delete top._id
 				packs.push top


Removed old calls to removed methods

Removed old calls to removed methods

--- app/assets/javascript/fancy_login/login_register_fancybox.coffee
+++ app/assets/javascript/fancy_login/login_register_fancybox.coffee
@@ -114,7 +114,6 @@

         if $(@element).hasClass('not-registered')
           $('#loginbox-header h5').text("Regístrate en #{$('#loginbox-header h5').data('appName')}")
-          self.getRegistrationsCount();
           $("#loginbox-sign-in").css("display","none")
           $("#loginbox-sign-up").css("display","block")
         else
@@ -129,7 +128,6 @@
       if target is "#signup"
         $("#loginbox-sign-in").fadeOut =>
           $('#loginbox-header h5').text("Regístrate en #{$('#loginbox-header h5').data('appName')}")
-          @getRegistrationsCount();
           $("#loginbox-sign-up").fadeIn()
       else if target is "#signin"
         $("#loginbox-sign-up").fadeOut ->


Don't clone ranges in PanToolView._update()

--- bokehjs/src/coffee/models/tools/gestures/pan_tool.coffee
+++ bokehjs/src/coffee/models/tools/gestures/pan_tool.coffee
@@ -39,11 +39,11 @@
     new_dx = dx - @last_dx
     new_dy = dy - @last_dy

-    hr = _.clone(frame.h_range)
+    hr = frame.h_range
     sx_low  = hr.start - new_dx
     sx_high = hr.end - new_dx

-    vr = _.clone(frame.v_range)
+    vr = frame.v_range
     sy_low  = vr.start - new_dy
     sy_high = vr.end - new_dy


add more information about methods.

add more information about methods.

--- Prism.sketchplugin/Contents/Sketch/src/FormatterBase.coffee
+++ Prism.sketchplugin/Contents/Sketch/src/FormatterBase.coffee
@@ -27,6 +27,7 @@
    format

    Uses default file name when its saved. Override this at Subclass.
+   This method will be called only `type` returns `EXPORT_TYPE_FILE`.
   ###
   format: ->

@@ -34,6 +35,8 @@
    type

    `EXPORT_TYPE_FILE` or `EXPORT_TYPE_FILES`. Override this at Subclass.
+   `EXPORT_TYPE_FILE` means 1 file will be exported. and Prism shows Save dialog.
+   `EXPORT_TYPE_FILES` means several files will be exported. Prism shows dialog to get export directory.
   ###
   type: ->
     @constructor.EXPORT_TYPE_FILE
@@ -41,7 +44,8 @@
   ###
    supportClipboard

-   If format supports clipboard then returns `true`.
+   If format supports clipboard then returns `true`. Text format should better support this basically.
+   If your format is binary then should return `false`.
   ###
   supportClipboard: ->
     true


:lipstick:

:lipstick:

--- src/packages/editor-stats/src/editor-stats-view.coffee
+++ src/packages/editor-stats/src/editor-stats-view.coffee
@@ -43,12 +43,12 @@
       .attr('class', 'x axis')
       .call(@xaxis)
     .selectAll('g')
-      .style('display', (d, i) ->
+      .classed('minor', (d, i) -> i % 5 == 0 && i % 15 != 0)
+      .style 'display', (d, i) ->
         if i % 15 == 0 || i % 5 == 0 || i == data.length - 1
           'block'
         else
           'none'
-      ).classed('minor', (d, i) -> i % 5 == 0 && i % 15 != 0)

     bars = vis.selectAll('rect.bar')
       .data(data)


Added regex quoting of query.

Added regex quoting of query.

--- app/assets/javascripts/app/lib/app_post/searchable_select.coffee
+++ app/assets/javascripts/app/lib/app_post/searchable_select.coffee
@@ -163,6 +163,7 @@
     @filterByQuery @query

   filterByQuery: (query) ->
+    query = escapeRegExp(query)
     regex = new RegExp(query.split(' ').join('.*'), 'i')

     @option_items


Extract showing search result

Extract showing search result

--- app/assets/javascripts/darkswarm/directives/map_search.js.coffee
+++ app/assets/javascripts/darkswarm/directives/map_search.js.coffee
@@ -10,7 +10,7 @@
       map = ctrl.getMap()

       searchBox = scope.createSearchBox map
-      scope.respondToSearch map, searchBox
+      scope.bindSearchResponse map, searchBox
       scope.biasResults map, searchBox

@@ -19,11 +19,16 @@
       map.controls[google.maps.ControlPosition.TOP_LEFT].push input
       return new google.maps.places.SearchBox(input)

-    scope.respondToSearch = (map, searchBox) ->
-      google.maps.event.addListener searchBox, "places_changed", ->
-        places = searchBox.getPlaces()
-        for place in places when place.geometry.viewport?
-          map.fitBounds place.geometry.viewport
+    scope.bindSearchResponse = (map, searchBox) ->
+      google.maps.event.addListener searchBox, "places_changed", =>
+        scope.showSearchResult map, searchBox
+
+    scope.showSearchResult = (map, searchBox) ->
+      places = searchBox.getPlaces()
+      for place in places when place.geometry.viewport?
+        map.fitBounds place.geometry.viewport
+        scope.$apply ->
+          model.$setViewValue elem.val()

     # Bias the SearchBox results towards places that are within the bounds of the
     # current map's viewport.


a little cleanup

a little cleanup

--- macros.coffee
+++ macros.coffee
@@ -1,9 +1,11 @@
 # All functions defined on "this" are available as atom commands.
 #
-# If the `toolbar` package is installed, toolbar icons are automatically generated.
+# If the `toolbar` package is installed, toolbar icons are automatically
+# generated.
 #
 # Set these properties of your function to configure the icons:
-# * icon - name of the icon (Or a method returning the icon name, possibly prepended with 'ion-' or 'fa-')
+# * icon - name of the icon (Or a method returning the icon name,
+#          possibly prepended with 'ion-' or 'fa-')
 # * title - The toolbar title (or a method returning the title)
 # * hideIcon - set to true to hide the icon from the toolbar
 #
@@ -12,18 +14,9 @@
   console.log 'Hello console'
   alert('Watch your console! (open with alt-cmd-i)')

-@helloConsole.icon = 'ion-clipboard' # icon from https://atom.io/packages/toolbar#supported-icon-sets
+@helloConsole.icon = 'ion-clipboard'
+# icon from https://atom.io/packages/toolbar#supported-icon-sets
 @helloConsole.title = 'Hello Console!'
-
-
-
-` // If you prefer Javascript, write it between backticks.
-this.helloFromJS = function() {
-  console.log('Hello from JS');
-  alert('Watch your console! (open with alt-cmd-i)');
-}
-this.helloFromJS.hideIcon = true; // don't show this on the toolbar
-`

 # Every property on "this" which is not a function triggers a separator
 @sp1 = "----------------"
@@ -45,7 +38,6 @@
       console.log "exec error: #{error}"

 @runShellCommand.icon = 'fa-pied-piper-alt'
-

 @openBrowser = ->
   open 'https://atom.io/packages/atom-macros'


fix typo

fix typo

--- services/web/app/coffee/Features/Analytics/AnalyticsController.coffee
+++ services/web/app/coffee/Features/Analytics/AnalyticsController.coffee
@@ -1,4 +1,4 @@
-AnalyticsManager = equire "./AnalyticsManager"
+AnalyticsManager = require "./AnalyticsManager"

 module.exports = AnalyticsController =
 	recordEvent: (req, res, next) ->


fix length menu

fix length menu

--- app/assets/javascripts/effective_datatables/initialize.js.coffee
+++ app/assets/javascripts/effective_datatables/initialize.js.coffee
@@ -51,7 +51,7 @@
       dom: "<'row'<'col-sm-4'l><'col-sm-8'B>><'row'<'col-sm-12'tr>><'row'<'col-sm-6'i><'col-sm-6'p>>"
       iDisplayLength: datatable.data('display-length')
       language: { 'lengthMenu': 'Show _MENU_ per page'}
-      lengthMenu: [[10, 25, 50, 100, 250, 1000, -1], ['10', '25', '50', '100', '250', '1000', 'All']]
+      lengthMenu: [[10, 25, 50, 100, 250, 1000, 9999999], ['10', '25', '50', '100', '250', '1000', 'All']]
       order: datatable.data('display-order')
       processing: true
       responsive: true


#516 fixed clicking on sorting icons

#516 fixed clicking on sorting icons

--- app/assets/javascripts/libs/behaviors/sort_table_behavior.coffee
+++ app/assets/javascripts/libs/behaviors/sort_table_behavior.coffee
@@ -60,7 +60,8 @@

   onClick: (evt) ->
-    $elem = $(evt.target)
+
+    $elem = if _.contains(evt.target.className, "sort-icon") then $(evt.target).closest("th") else $(evt.target)
     elemData = $elem.data()
     if "sort" not of elemData
       return


OX-706 Hide 3D cells in plane views

OX-706 Hide 3D cells in plane views

--- app/assets/javascripts/oxalis/controller/scene_controller.coffee
+++ app/assets/javascripts/oxalis/controller/scene_controller.coffee
@@ -86,6 +86,8 @@
     if id in constants.ALL_PLANES
       unless @showSkeleton
         @skeleton.setVisibility(false)
+      for mesh in @volumeMeshes
+        mesh.visible = false
       for i in constants.ALL_PLANES
         if i == id
           @planes[i].setOriginalCrosshairColor()
@@ -100,6 +102,8 @@
     else
       unless @showSkeleton
         @skeleton.setVisibility(true)
+      for mesh in @volumeMeshes
+        mesh.visible = true
       for i in constants.ALL_PLANES
         pos = @flycam.getPosition()
         @planes[i].setPosition(new THREE.Vector3(pos[0], pos[1], pos[2]))


simplified the app_router

simplified the app_router

--- app/routers/app_router.coffee
+++ app/routers/app_router.coffee
@@ -1,6 +1,5 @@
 BridgeModel  = require '../models/bridge'
 BridgeView  = require '../views/bridge'
-TestObjectFactory = require 'lib/test_object_factory'

 module.exports = class AppRouter extends Backbone.Router
     routes:
@@ -8,15 +7,18 @@
         'bridge/:id': 'bridge'

     bridge: (id) ->
-        Overpass?.Views?.AppView.$el.children('.layout-container').empty()
-        @factory = new TestObjectFactory
+        Overpass?.Views?.BridgeView?.remove()
+
+        @model = new BridgeModel
+            'id':id
+        @model.fetch()
+
         @bridge = new BridgeView
-            model: new BridgeModel
-                'id':id
-
-        @bridge.model.fetch()
+            model: @model

         Overpass?.Views?.BridgeView = @bridge

+        Overpass?.Views?.AppView.$el.children('.layout-container').empty()
+
         Overpass?.Views?.AppView.$el.children('.layout-container')
             .prepend @bridge.$el


Add no selection rule to spec selector

Add no selection rule to spec selector

--- spec/app/editor-spec.coffee
+++ spec/app/editor-spec.coffee
@@ -1766,7 +1766,7 @@
       it "doesn't highlight the background of the gutter", ->
         editor.getSelection().setBufferRange(new Range([0,0],[2,0]))
         expect(editor.getSelection().isSingleScreenLine()).toBe false
-        expect(editor.find('.line-number.cursor-line-no-selection').length).toBe 0
+        expect(editor.find('.line-number.cursor-line.cursor-line-no-selection').length).toBe 0

     it "when a newline is deleted with backspace, the line number of the new cursor position is highlighted", ->
       editor.setCursorScreenPosition([1,0])


added required format description

added required format description

--- app/assets/javascripts/admin/views/dataset/dataset_upload_view.coffee
+++ app/assets/javascripts/admin/views/dataset/dataset_upload_view.coffee
@@ -16,7 +16,7 @@

             Name

-            <input type="text" required name="name" value="" class="form-control" autofocus pattern="^[0-9a-zA-Z_\-]+$">
+            <input type="text" required name="name" value="" class="form-control" autofocus pattern="^[0-9a-zA-Z_\-]+$" title="Dataset names may only contain letters, numbers, _ and -">


Exclude form alerts specs

Exclude form alerts specs

--- spec/csv-editor-spec.coffee
+++ spec/csv-editor-spec.coffee
@@ -187,7 +187,7 @@
               expect(tableEditor.isModified()).toBeFalsy()
               expect(csvEditor.isModified()).toBeFalsy()

-    describe 'when the file cannot be parsed with the default', ->
+    xdescribe 'when the file cannot be parsed with the default', ->
       beforeEach ->
         openFixture('invalid.csv')


BREAKING CHANGE - TextElement now has the same default size as Element: ps: 1 (not cs: 1)

--- source/Art/Engine/Elements/Shapes/TextElement.coffee
+++ source/Art/Engine/Elements/Shapes/TextElement.coffee
@@ -16,7 +16,7 @@
 propSetterName = BaseObject._propSetterName
 module.exports = createWithPostCreate class TextElement extends ShadowableElement

-  defaultSize: cs:1
+  # defaultSize: cs:1

   constructor: ->
     super


Fix registration modal issue

Fix registration modal issue

--- desktop/components/credit_card/client/confirm_registration.coffee
+++ desktop/components/credit_card/client/confirm_registration.coffee
@@ -11,6 +11,9 @@

   template: template

+  events: -> _.extend super,
+    'click .bid-close-button': 'close'
+
   initialize: ({ @auction }) ->
     @user = CurrentUser.orNull()
     _.extend @templateData, paddleNumber: @user.get('paddle_number')
@@ -21,11 +24,19 @@
     @user.fetchBidderForAuction @auction,
       error: @isLoaded
       success: (bidder) =>
-        if bidder and not bidder.get 'qualified_for_bidding'
+        qualifiedForBidding = bidder && bidder.get 'qualified_for_bidding'
+
+        if !qualifiedForBidding
           @$('.credit-card-unqualified-msg').show()
           analyticsHooks.trigger 'creditcard:unqualified',
             auction_slug: @auction.get('id')
             bidder_id: bidder.get('id')
             user_id: @user.get('id')
+
         @updatePosition()
         @isLoaded()
+
+  close: (event) ->
+    replaceModalTriggerPath = location.pathname.replace('/confirm-registration', '')
+    history.replaceState({}, document.title, replaceModalTriggerPath)
+    super


Selection Count specs

Selection Count specs

--- spec/status-bar-spec.coffee
+++ spec/status-bar-spec.coffee
@@ -36,6 +36,7 @@
       expect(StatusBar.fileInfo.currentPath.text()).toBe 'sample.js'
       expect(StatusBar.fileInfo.bufferModified.text()).toBe ''
       expect(StatusBar.cursorPosition.text()).toBe '1,1'
+      expect(StatusBar.selectionCount).toBeHidden()

     describe "when associated with an unsaved buffer", ->
       it "displays 'untitled' instead of the buffer's path, but still displays the buffer position", ->
@@ -47,6 +48,7 @@
           statusBar = atom.workspaceView.find('.status-bar').view()
           expect(StatusBar.fileInfo.currentPath.text()).toBe 'untitled'
           expect(StatusBar.cursorPosition.text()).toBe '1,1'
+          expect(StatusBar.selectionCount).toBeHidden()

   describe ".deactivate()", ->
     it "removes the StatusBarView", ->
@@ -153,6 +155,15 @@
       editor.setCursorScreenPosition([1, 2])
       editorView.updateDisplay()
       expect(StatusBar.cursorPosition.text()).toBe '2,3'
+
+  describe "when the associated editor's selection changes", ->
+    it "updates the selection count in the status bar", ->
+      waitsForPromise ->
+        atom.workspace.open('sample.txt')
+
+      runs ->
+        editor.setSelectedBufferRange([0,0], [0,1])
+        expect(StatusBar.selectionCount.text()).toBe '(2)'

   describe "git branch label", ->
     beforeEach ->


Rename require from atom-api to atom

Rename require from atom-api to atom

--- spec/whitespace-spec.coffee
+++ spec/whitespace-spec.coffee
@@ -1,4 +1,4 @@
-{fs} = require 'atom-api'
+{fs} = require 'atom'

 describe "Whitespace", ->
   [editor, buffer, path] = []


autoexport

autoexport

--- content/db/main.coffee
+++ content/db/main.coffee
@@ -121,7 +121,7 @@
       for date in ['updated']
         if dest[date]
           dest[date] = Date.parse(dest[date])
-          dest[date] = null is isNaN(dest[date])
+          dest[date] = null if isNaN(dest[date])
         delete dest[date] unless dest[date]
       return
   }


Unncessary code removed

Unncessary code removed

--- coffee/codesearch.coffee
+++ coffee/codesearch.coffee
@@ -5,8 +5,6 @@

     $scope.results = resultsService
     $scope.modal = modalService
-    $scope.search =
-        term: ""
     return


properly close and remove modal

properly close and remove modal

--- MediaAdminBundle/Resources/public/coffee/MediaModalView.coffee
+++ MediaAdminBundle/Resources/public/coffee/MediaModalView.coffee
@@ -29,12 +29,14 @@
     else
       @options.domContainer.html @$el
       currentModal = @$el.detach().appendTo('body')
-      @$el.modal "show"
+      currentModal.modal "show"

   closeModal: ->
-    currentModal.modal "hide"
-    currentModal.remove()
-    $('body').removeClass('modal-open')
+    if currentModal
+      currentModal.modal "hide"
+      currentModal.on 'hidden.bs.modal', ->
+        this.remove()
+        return
     currentModal = null

   showFolder: (event) ->


fix(plugin-auth): POST returns 200, no response body

--- src/flux/nylas-api.coffee
+++ src/flux/nylas-api.coffee
@@ -440,8 +440,8 @@
         path: "/auth/plugin",
         body: {client_id: pluginId},
         json: true
-      }).then (result) =>
-        NylasEnv.config.set(cacheKey, Date.now()) if result.authed
+      }).then =>
+        NylasEnv.config.set(cacheKey, Date.now())
         return Promise.resolve()

   _requestPluginAuth: (pluginName, account) ->


now support configuring classes and id for code block

--- lib/md.coffee
+++ lib/md.coffee
@@ -538,6 +538,21 @@
     else
       $(preElement).replaceWith "<pre>please wait till preview finishes rendering graph </pre>"

+# eg '.class1 .class2 #id1'
+# return { classes: ['class1', 'class2'], id: 'id1' }
+formatClassesAndId = (opt)->
+  if classMatch = opt.match(/\.[^\s]+/g)
+    classes = classMatch.map (cl)-> cl.slice(1)
+  else
+    classes = []
+
+  if idMatch = opt.match(/\#[^\s]+/g)
+    id = idMatch[idMatch.length - 1].slice(1)
+  else
+    id = ''
+
+  return {classes, id}
+
 # resolve image path and pre code block...
 # check parseMD function, 'option' is the same as the option in paseMD.
 resolveImagePathAndCodeBlock = (html, graphData={}, codeChunksData={},  option={})->
@@ -574,9 +589,13 @@

   renderCodeBlock = (preElement, text, parameters, lineNo=null)->
     highlighter ?= new Highlights({registry: atom.grammars, scopePrefix: 'mpe-syntax--'})
-    match = parameters.match(/^\s*(\"[^\"]*\"|[^\s]*|[^}]*)(.*)$/)
-    lang = match[1]
-    parameters = match[2].trim()
+    if match = parameters.match(/\s*([^\s]+)\s+\{(.+?)\}/)
+      lang = match[1]
+      parameters = match[2]
+    else
+      lang = parameters
+      parameters = ''
+
     html = highlighter.highlightSync
             fileContents: text,
             scopeName: scopeForLanguageName(lang)
@@ -586,9 +605,10 @@

     $(preElement).replaceWith(highlightedBlock)

-    classMatch = parameters.match(/\s*class\s*:\s*\"([^\"]*)\"/) # check class
-    if classMatch and classMatch[1]
-      highlightedBlock.addClass(classMatch[1])
+    if parameters
+      {classes, id} = formatClassesAndId(parameters)
+      highlightedBlock.addClass(classes.join(' ')) if classes
+      highlightedBlock.attr('id', id) if id
       checkLineNumber(highlightedBlock)

   # parse eg:


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine_selector.coffee
+++ test/engine_selector.coffee
@@ -29,6 +29,7 @@

   obj2 = engine.add
     type: 'testObj'
-    x:
+    x: 9
+    y

 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine_selector.coffee
+++ test/engine_selector.coffee
@@ -9,7 +9,7 @@
     y: 5

   engine.add
-
+    class:

 test "#find", ->
   engine = Engine()


Fix indentation for chained calls

Fix indentation for chained calls

--- client/app/lib/providers/computecontroller.coffee
+++ client/app/lib/providers/computecontroller.coffee
@@ -617,16 +617,16 @@

     call = @getKloud().createSnapshot { machineId: machine._id, label }

-    .then (res) =>
-
-      kd.log "createSnapshot res:", res
-      @eventListener.addListener 'createSnapshot', machine._id
-
-    .timeout globals.COMPUTECONTROLLER_TIMEOUT
-
-    .catch (err) =>
-
-      (@errorHandler call, 'createSnapshot', machine) err
+      .then (res) =>
+
+        kd.log "createSnapshot res:", res
+        @eventListener.addListener 'createSnapshot', machine._id
+
+      .timeout globals.COMPUTECONTROLLER_TIMEOUT
+
+      .catch (err) =>
+
+        (@errorHandler call, 'createSnapshot', machine) err

   # Domain management


cc: unshare stack template, remove config info from stack and template

--- client/app/lib/providers/computecontroller.coffee
+++ client/app/lib/providers/computecontroller.coffee
@@ -1021,11 +1021,15 @@
         new kd.NotificationView { title : 'Stack Template is Unshared With Team' }

-  checkRevisonFromOriginalStackTemplate: (stackTemplateId, group) ->
+  removeRevisonFromUnSharedStackTemplate: (id) ->

     { reactor } = kd.singletons
-
-    stacks = @stacks.filter (stack) -> stack.config?.clonedFrom is stackTemplateId
+    stacks = @stacks.filter (stack) -> stack.config?.clonedFrom is id
+
+    stacks.forEach (stack) =>
+      config = stack.config ?= {}
+      config.needUpdate = no
+      @updateStackConfig stack, config

     return  unless stacks.length
     stacks.forEach (stack) ->


envdataprovider: add removeCollaborationMachine

--- client/app/lib/userenvironmentdataprovider.coffee
+++ client/app/lib/userenvironmentdataprovider.coffee
@@ -232,6 +232,11 @@
     sinkrow.dash queue, callback

+  removeCollaborationMachine: (machine) ->
+
+    @removeMachine 'collaboration', machine
+
+
   removeMachine: (type, machine) ->

     envData = globals.userEnvironmentData[type]


modified

modified

--- src/myretry.coffee
+++ src/myretry.coffee
@@ -1,5 +1,4 @@
 module.exports = (err, res)->
   retry = err? or 500 <= res?.statusCode < 600
-  if retry
-    console.log "retry data request..."
+  #if retry then console.log "retry data request..."
   return retry


activity: pass an instance of `CommentListViewController` as delegate to `NewCommentForm` instance

--- client/Social/Activity/views/comments/commentview.coffee
+++ client/Social/Activity/views/comments/commentview.coffee
@@ -22,18 +22,18 @@
     @setClass "fixed-height"
     @commentList.$().css {maxHeight}

-  createSubViews:(data)->
+  createSubViews: (data) ->

     @commentList = new KDListView
       type          : "comments"
       itemClass     : CommentListItemView
-      delegate      : @
+      delegate      : this
     , data

-    # @commentController        = new CommentListViewController view: @commentList
-    # @addSubView showMore      = new CommentViewHeader delegate: @commentList, data
-    # @addSubView @commentController.getView()
-    @addSubView @commentForm  = new NewCommentForm delegate : @commentList
+    @commentController        = new CommentListViewController view: @commentList
+    @addSubView showMore      = new CommentViewHeader delegate: @commentList, data
+    @addSubView @commentController.getView()
+    @addSubView @commentForm  = new NewCommentForm delegate: @commentController

     @commentList.on 'ReplyLinkClicked', (username) =>
       input = @commentForm.commentInput


Update Compile & Run Button on Ace

Update Compile & Run Button on Ace

--- client/app/Applications/Ace.kdapplication/AppView.coffee
+++ client/app/Applications/Ace.kdapplication/AppView.coffee
@@ -57,16 +57,17 @@

     @compileAndRunButton = new KDButtonView
       style     : "editor-button"
-      icon      : yes
-      iconOnly  : yes
-      iconClass : "compile"
-      tooltip   :
-        title   : "Compile & Run"
+      title     : "Compile & Run"
+      loader    :
+        color   : "#444444"
+        diameter: 12
       callback  : =>
         manifest = KodingAppsController.getManifestFromPath @getData().parentPath
+        @ace.notify "Compiling...", null, yes
         @getSingleton('kodingAppsController').compileApp manifest.name, =>
           @ace.notify "App compiled!", "success"
           @getSingleton('kodingAppsController').runApp manifest
+          @compileAndRunButton.hideLoader()

     @compileAndRunButton.hide() unless /\.kdapp\//.test @getData().path


Client: Activity: Added nickname tooltip to participant avatars

--- client/activity/lib/components/channelparticipantavatars/index.coffee
+++ client/activity/lib/components/channelparticipantavatars/index.coffee
@@ -14,6 +14,7 @@
 isUserGroupAdmin            = require 'app/util/isusergroupadmin'
 validator                   = require 'validator'
 showErrorNotification       = require 'app/util/showErrorNotification'
+Tooltip                     = require 'app/components/tooltip'

 module.exports = class ChannelParticipantAvatars extends React.Component
@@ -133,6 +134,13 @@

     nickname = participant.getIn ['profile', 'nickname']
     <span>{nickname}</span>
+
+
+  renderTooltip: (participant) ->
+
+    nickname = participant.getIn ['profile', 'nickname']
+
+    <Tooltip text={nickname} position='bottom' />

   renderAvatars: (participants, isNicknameVisible) ->
@@ -147,6 +155,7 @@
               account={participant.toJS()}
               height={30} />
             {@renderNickname participant, isNicknameVisible }
+            {@renderTooltip participant}
           </div>
         </ProfileLinkContainer>
       </div>


enabled back hiding landing page

enabled back hiding landing page

--- client/landing/app/MainApp/lazy/lazydomcontroller.coffee
+++ client/landing/app/MainApp/lazy/lazydomcontroller.coffee
@@ -95,17 +95,17 @@
   openPath:(path)->
     @getSingleton('router').handleRoute path

-    # {groupEntryPoint, profileEntryPoint} = KD.config
-    # entryPoint = groupEntryPoint ? profileEntryPoint
-    # staticPaths = [
-    #   '/Logout','/Login','/Register',
-    #   "/#{entryPoint}/Login",
-    #   "/#{entryPoint}/Logout",
-    #   "/#{entryPoint}/Register",
-    #   "/#{entryPoint}"
-    # ]
+    {groupEntryPoint, profileEntryPoint} = KD.config
+    entryPoint = groupEntryPoint ? profileEntryPoint
+    staticPaths = [
+      '/Logout','/Login','/Register',
+      "/#{entryPoint}/Login",
+      "/#{entryPoint}/Logout",
+      "/#{entryPoint}/Register",
+      "/#{entryPoint}"
+    ]

-    # @hideLandingPage() unless path in staticPaths
+    @hideLandingPage() unless path in staticPaths

   handleNavigationItemClick:(item, event)->


Re-open already visible conversation when drag finished

--- client/app/Applications/Demos.kdapplication/AppController.coffee
+++ client/app/Applications/Demos.kdapplication/AppController.coffee
@@ -108,12 +108,12 @@
   setDragHandlers:->

     @on 'DragStarted', (e, state)->
+      @conversationWasOpen = @conversation.isVisible()
       @_dragStarted = yes

     @on 'DragInAction', _.throttle (x, y)->
       if y isnt 0 and @_dragStarted
         distance = Math.round(y / 33)
-        @conversationWasOpen = @conversation.isVisible()
         @conversation.collapse()
         @setClass 'ondrag'
     , 300


ContentSearch: Refactor for listening click event for matched items in result view.

--- client/ide/views/contentsearch/idecontentsearchresultview.coffee
+++ client/ide/views/contentsearch/idecontentsearchresultview.coffee
@@ -33,16 +33,15 @@

           view.updatePartial "#{line.lineNumber}#{replaced}"

-  viewAppended: ->
-    super
+  click: (event) ->
+    {target} = event
+    return unless  target.classList.contains 'match'

-    $('.match').click ->
-      $el        = $ this
-      filePath   = $el.data 'file-path'
-      lineNumber = $el.data 'line-number'
+    filePath   = target.getAttribute 'data-file-path'
+    lineNumber = target.getAttribute 'data-line-number'
+    file       = FSHelper.createFileFromPath filePath

-      file = FSHelper.createFileFromPath filePath
-      file.fetchContents (err, contents) ->
-        KD.getSingleton('appManager').tell 'IDE', 'openFile', file, contents, (editorPane) ->
-          KD.utils.wait 500, -> # setting editor font size is kinda buggy, temp fix for it
-            editorPane.goToLine lineNumber
+    file.fetchContents (err, contents) ->
+      KD.getSingleton('appManager').tell 'IDE', 'openFile', file, contents, (editorPane) ->
+        KD.utils.wait 500, -> # setting editor font size is kinda buggy, temp fix for it
+          editorPane.goToLine lineNumber


Workspace: Fixed getProperty method. Removed unused AppStorage instance.

--- client/landing/app/CommonViews/workspace/panes/editorpane.coffee
+++ client/landing/app/CommonViews/workspace/panes/editorpane.coffee
@@ -6,9 +6,7 @@

     super options, data

-    @appStorage = new AppStorage "Ace", "1.0"
-
-    @files = @getProperty "files"
+    @files = @getOptions "files"

     if Array.isArray @files then @createEditorTabs() else @createSingleEditor()


Slight modification to the splash preloading-code, it now actually shows the splash for a few micro seconds.

--- community/server/src/main/coffeescript/neo4j/webadmin/modules/connectionmonitor/ConnectionMonitor.coffee
+++ community/server/src/main/coffeescript/neo4j/webadmin/modules/connectionmonitor/ConnectionMonitor.coffee
@@ -42,8 +42,8 @@
         # lost splash have been pre-loaded, so we
         # have them in case the server dies.
         splash = $(template())
-        splash.hide()
         $("body").append(splash)
+        setTimeout((-> splash.hide()),0)

       connectionLost : =>


Removed some logging.

Removed some logging.

--- app/views/params/range.coffee
+++ app/views/params/range.coffee
@@ -17,7 +17,6 @@
     @span = @$el.find('span')
     @value = @$el.find('.value')
     if @firstRender and typeof @model.get('value') isnt 'undefined'
-      console.log 'first render'
       @value.html @model.get('value').toFixed(2)
       position = (167 * (@model.get('value') - @min)) / (@max - @min)
       @span.css
@@ -28,7 +27,6 @@
     @

   onUserStartDrag: (e) =>
-    console.log 'starting drag'
     $('body').addClass 'unselectable'
     @dragging = true