Revert "switched to fluid table"

Revert "switched to fluid table"

This reverts commit 280e2bcc93d12bc03f743bd9bb286fa62e8c04b2.

Conflicts:

	app/assets/javascripts/trade/helpers/sandbox_shipments_table.js.coffee

--- app/assets/javascripts/trade/helpers/sandbox_shipments_table.js.coffee
+++ app/assets/javascripts/trade/helpers/sandbox_shipments_table.js.coffee
@@ -62,9 +62,7 @@
   rowHeight: 30
   contentBinding: 'annualReportUploadController.visibleShipments'
   selection: null
-  fluidTable: yes
   tableRowViewClass: "Trade.SandboxShipmentsTable.TableRow"
-
   columnNames: [
       'appendix', 'speciesName', 'termCode', 'quantity', 'unitCode',
       'tradingPartner', 'countryOfOrigin', 'importPermit', 'exportPermit',
@@ -74,43 +72,43 @@
   columns: Ember.computed ->
     columnProperties =
       appendix:
-        width: '4%'
+        width: 50
         header: 'Appdx'
       speciesName:
-        width: '15%'
+        width: 200
         header: 'Species Name'
       termCode:
-        width: '5%'
+        width: 50
         header: 'Term'
       quantity:
-        width: '6%'
+        width: 50
         header: 'Qty'
       unitCode:
-        width: '4%'
+        width: 50
         header: 'Unit'
       tradingPartner:
-        width: '5%'
+        width: 100
         header: 'Trading partner'
       countryOfOrigin:
-        width: '5%'
+        width: 100
         header: 'Ctry of Origin'
       importPermit:
-        width: '13%'
+        width: 140
         header: 'Import Permit'
       exportPermit:
-        width: '13%'
+        width: 140
         header: 'Export Permit'
       originPermit:
-        width: '13%'
+        width: 140
         header: 'Origin Permit'
       purposeCode:
-        width: '4%'
+        width: 50
         header: 'Purpose'
       sourceCode:
-        width: '4%'
+        width: 50
         header: 'Source'
       year:
-        width: '5%'
+        width: 50
         header: 'Year'

     columns = @get('columnNames').map (key, index) ->
@@ -121,8 +119,8 @@
         getCellContent: (row) -> row.get(key)
         setCellContent: (row, value) -> row.set(key, value)
     deleteColumn = Ember.Table.ColumnDefinition.create
-      columnWidth: '4%'
-      headerCellName: 'Del'
+      columnWidth: 50
+      headerCellName: 'Delete'
       tableCellViewClass: 'Trade.SandboxShipmentsTable.CheckboxTableCell'
       getCellContent: (row) -> row.get('_destroyed')
       setCellContent: (row, value) ->


Activity: make input widget's input view an option

--- client/Social/Activity/views/inputwidget.coffee
+++ client/Social/Activity/views/inputwidget.coffee
@@ -37,7 +37,9 @@
     options.destroyOnSubmit ?= no
     {defaultValue, placeholder} = options

-    @input = new ActivityInputView {defaultValue, placeholder}
+    inputViewClass = options.inputViewClass ? ActivityInputView
+
+    @input = new inputViewClass {defaultValue, placeholder}
     @input.on "Escape", @bound "reset"
     @input.on "Enter", @bound "submit"


activity: scroller default options changed

--- client/app/lib/components/scroller/index.coffee
+++ client/app/lib/components/scroller/index.coffee
@@ -11,7 +11,8 @@
     threshold             : 1
     onThresholdReached    : kd.noop
     onTopThresholdReached : kd.noop
-
+    minScrollbarLength    : 64
+    useSelectionScroll    : on

   renderTopWaypoint: ->


activity: use message delete animation for realtim messages

--- client/Social/Activity/views/messagepane.coffee
+++ client/Social/Activity/views/messagepane.coffee
@@ -242,13 +242,26 @@

   prependMessage: (message, index, callback = noop) ->
+
     KD.getMessageOwner message, (err, owner) =>
       return callback err  if err
       return callback() if KD.filterTrollActivity owner
       item = @listController.addItem message, index
       callback null, item

-  removeMessage: (message) -> @listController.removeItem null, message
+
+  removeMessage: (message) ->
+
+    listItems = @listController.getListItems()
+
+    [item] = listItems.filter (item) -> item.getData().getId() is message.getId()
+
+    if item?
+      item.once 'HideAnimationFinished', =>
+        @listController.removeItem item
+        @listController.showNoItemWidget() if @listController.getListItems().length is 0
+
+      item.hide()

   viewAppended: ->


Client: Activity: Added deletePrivateChannel action to delete channel from followedPrivateChannelIdsStore

--- client/activity/lib/flux/actions/channel.coffee
+++ client/activity/lib/flux/actions/channel.coffee
@@ -256,6 +256,27 @@
       return

     dispatch UNFOLLOW_CHANNEL_SUCCESS, { channelId }
+
+
+###*
+ * Action to delete private channel by given channelId
+ *
+ * @param {string} channelId
+###
+deletePrivateChannel = (channelId) ->
+
+  { SocialChannel } = remote.api
+  { DELETE_PRIVATE_CHANNEL_BEGIN
+    DELETE_PRIVATE_CHANNEL_SUCCESS
+    DELETE_PRIVATE_CHANNEL_FAIL } = actionTypes
+
+  SocialChannel.delete { channelId }
+    .then ->
+      dispatch DELETE_PRIVATE_CHANNEL_SUCCESS, { channelId }
+
+    .catch (err) =>
+      dispatch DELETE_PRIVATE_CHANNEL_FAIL , { channelId }
+      showErrorNotification err, userMessage: err.message

 addParticipants = (options = {}) ->
@@ -299,5 +320,6 @@
   loadPopularChannels
   loadChannelsByQuery
   setChannelParticipantsDropdownVisibility
+  deletePrivateChannel
 }


activity/flux: batch channel message load dispatchs

--- client/activity/lib/flux/actions/channel.coffee
+++ client/activity/lib/flux/actions/channel.coffee
@@ -62,10 +62,11 @@
       dispatch LOAD_CHANNEL_PARTICIPANTS_FAIL, { err, channelId }
       return

-    participants.forEach (participant) ->
-      { accountOldId } = participant
-      appActions.user.loadAccount accountOldId
-      dispatch LOAD_CHANNEL_PARTICIPANT_SUCCESS, { channelId, userId: accountOldId }
+    kd.singletons.reactor.batch ->
+      participants.forEach (participant) ->
+        { accountOldId } = participant
+        appActions.user.loadAccount accountOldId
+        dispatch LOAD_CHANNEL_PARTICIPANT_SUCCESS, { channelId, userId: accountOldId }

 ###*


payment: remove pointless code

payment: remove pointless code

Well, since Number#toFixed() returns a string, and String does not override #toLocaleString(), calling #toLocaleString() is completely pointless because it will just defer to #toString, and we're already dealing with a string

--- client/app/MainApp/payment/vmproductview.coffee
+++ client/app/MainApp/payment/vmproductview.coffee
@@ -3,7 +3,7 @@
     super options, data

   formatCurrency: (cents) ->
-    "#{ ( cents / 100 ).toFixed(2).toLocaleString currency: 'USD' }"
+    "#{ ( cents / 100 ).toFixed(2) }"

   pistachio: ->
     """


video/helper: move callback execution into same call

--- client/app/lib/videocollaboration/helper.coffee
+++ client/app/lib/videocollaboration/helper.coffee
@@ -115,10 +115,10 @@
   options.height = 265
   options.width  = 325

-  publisher = OT.initPublisher view.getElement(), options, (err) ->
-    return callback err  if err
+  publisher = OT.initPublisher view.getElement(), options, ->
     fixParticipantBackgroundImage publisher, whoami()
-    callback null, publisher
+
+  callback null, publisher

 ###*


Add production copy task

Add production copy task

--- grunt/copy.coffee
+++ grunt/copy.coffee
@@ -17,4 +17,11 @@
       expand: true,
       src: ['favicon.ico', '*.png'],
       dest: 'build/debug/'
-    }]+    }]
+  production:
+    files: [{
+      expand: true
+      cwd: 'build/debug/'
+      src: ['*/**']
+      dest: 'build/production/'
+    }]


Fixing bindings

Fixing bindings

--- admin/static/coffee/servers/index.coffee
+++ admin/static/coffee/servers/index.coffee
@@ -182,7 +182,7 @@
             @machine_list.on 'size_changed', @ml_size_changed
             @ml_size_changed()

-        ml_size_changed: ->
+        ml_size_changed: =>
             num_machines = @machine_list.element_views.length

             we_should_rerender = false
@@ -268,7 +268,7 @@

             @callbacks = []

-        ml_size_changed: ->
+        ml_size_changed: =>
             num_machines = @machine_list.element_views.length

             we_should_rerender = false


Remove dead code

Remove dead code

--- admin/static/coffee/servers/index.coffee
+++ admin/static/coffee/servers/index.coffee
@@ -14,10 +14,6 @@
             @fetch_servers()

             @loading = true # TODO Render that
-
-        render: =>
-            @$el.html @template({})
-            return @

         render: =>
             if @loading is true


Remove AccountsComponent from iot devices routes

--- packages/iot/devices/routes.coffee
+++ packages/iot/devices/routes.coffee
@@ -38,10 +38,3 @@
   action: (params, queryParams) ->
     BlazeLayout.render 'MainLayoutComponent',
       main: 'Device.NewThingComponent'
-
-# User must be logged in to visit account page
-loggedIn.route '/account',
-  name: 'Account'
-  action: (params, queryParams) ->
-    BlazeLayout.render 'MainLayoutComponent',
-      main: 'AccountComponent'


Update url on path change

Update url on path change

--- src/smc-webapp/project_store.coffee
+++ src/smc-webapp/project_store.coffee
@@ -473,6 +473,7 @@
             current_path           : path
             page_number            : 0
             most_recent_file_click : undefined
+        @set_url_to_path(path)
         if update_file_listing
             @set_directory_files(path)
             @set_all_files_unchecked()


Extract default options to a nice variable

--- core/app/backbone/factlink/tooltip_mixin.coffee
+++ core/app/backbone/factlink/tooltip_mixin.coffee
@@ -2,8 +2,13 @@

 Backbone.Factlink.TooltipMixin =

+  default_options:
+    side: 'left'
+    align: 'center'
+    margin: 0
+
   tooltipAdd: (selector, title, text, options) ->
-    options = _.extend {side: 'left', align: 'center', margin: 0}, options
+    options = _.extend @default_options, options

     @_tooltips ?= {}
     if @_tooltips[selector]?


[client][app][Dashboard] added redirection if user is not allowed to open group dashboard

--- client/landing/app/Applications/Dashboard.kdapplication/AppView.coffee
+++ client/landing/app/Applications/Dashboard.kdapplication/AppView.coffee
@@ -15,12 +15,13 @@

     @setListeners()
     @once 'viewAppended', =>
+      @header.hide()
+      @nav.hide()
       group = KD.getSingleton("groupsController").getCurrentGroup()
       group.canEditGroup (err, success)=>
         if err or not success
-          console.log err
-          @header.hide()
-          @nav.hide()
+          {entryPoint} = KD.config
+          KD.getSingleton('router').handleRoute "/Activity", { entryPoint }
         else
           @header.show()
           @nav.show()


remove _.

remove _.

--- vue/src/shared/services/record_loader.coffee
+++ vue/src/shared/services/record_loader.coffee
@@ -1,4 +1,5 @@
 import Records from '@/shared/services/records'
+import {merge, camelCase, defaults } from 'lodash'

 export default class RecordLoader
   constructor: (opts = {}) ->
@@ -7,7 +8,7 @@
     @loadingMore = false
     @loadingPrevious = false
     @collection   = opts.collection
-    @params       = _.merge({from: 0, per: 25, order: 'id'}, opts.params)
+    @params       = merge({from: 0, per: 25, order: 'id'}, opts.params)
     @path         = opts.path
     @numLoaded    = opts.numLoaded or 0
     @numRequested = opts.numRequested or @params['per']
@@ -20,9 +21,9 @@

   fetchRecords: (opts = {}) ->
     @loading = true
-    Records[_.camelCase(@collection)].fetch
+    Records[camelCase(@collection)].fetch
       path:   @path
-      params: _.defaults({}, opts, @params)
+      params: defaults({}, opts, @params)
     .then (data) =>
       records = data[@collection] || []
       @numLoaded += records.length


Removed prevent_scroll_propagation lib

Removed prevent_scroll_propagation lib

--- core/app/assets/javascripts/intermediate.coffee
+++ core/app/assets/javascripts/intermediate.coffee
@@ -1,5 +1,3 @@
-#= require jquery.prevent_scroll_propagation
-
 showFrame = document.getElementById("frame")
 xdm = window.easyXDM.noConflict("FACTLINK")
 window.remote = new xdm.Rpc {},


Use double HMAC instead of timing safe comparison


KiteSubscription: add method to remove plan

--- workers/social/lib/social/models/kite.coffee
+++ workers/social/lib/social/models/kite.coffee
@@ -88,3 +88,8 @@
         return  callback err if err
         @addPlan plan, callback

+  deletePlan: permit 'delete kite plan',
+    success: (client, planCode, callback)->
+      @fetchPlans (err, plan) ->
+        plan.remove
+


fixed profiling, not sure which change broke it...

--- console/runner.coffee
+++ console/runner.coffee
@@ -45,6 +45,8 @@
       process.stdin.pause()
       resume data

+  rs = new runtime.RuntimeState(stdout, read_stdin, exports.read_classfile)
+
   if argv.profile
     timings = {}
     call_counts = {}
@@ -69,7 +71,6 @@

   java_cmd_args = (argv.java?.toString().split /\s+/) or []

-  rs = new runtime.RuntimeState(stdout, read_stdin, exports.read_classfile)
   jvm.run_class rs, cname, java_cmd_args

   if argv.profile


Bugfix: lingering units.

Bugfix: lingering units.

--- src/map-view.coffee
+++ src/map-view.coffee
@@ -123,8 +123,7 @@
             new MapStateModel @opts

         drawUnits: (units, options) ->
-            if units.length == 0
-                @allMarkers.clearLayers()
+            @allMarkers.clearLayers()
             if units.filters?.bbox?
                 if @_skipBboxDrawing
                     return


server starts only if cache successfully rebuild

--- src/server/app.coffee
+++ src/server/app.coffee
@@ -23,9 +23,12 @@
 require('./controllers/index')(app)
 require('./controllers/auth')(app)
 require('./controllers/game')(app)
-require('./models/RebuildCache')(->)
+require('./models/RebuildCache')((err, result)->
+  if err
+    throw err

-app.listen(conf.port)
-console.log("Start listening #{conf.port}")
+  app.listen(conf.port)
+  console.log("Start listening #{conf.port}")
+)

 module.exports = app


indentatin indentation

indentatin
indentation

--- client/router.coffee
+++ client/router.coffee
@@ -88,8 +88,8 @@
   getParamsHash: (pattern, paramsArray) ->
     paramNames = _.map(pattern.match(extractParamNamesRe), (name) -> name.slice(1))
     _.inject(paramNames, (memo, name, i) ->
-     memo[name] = decodeURIComponent(paramsArray[i])
-     memo
+      memo[name] = decodeURIComponent(paramsArray[i])
+      memo
     , {})

   matchingRoute: (path) ->


PlayState#ended control now unneeded to test quitting.

Obviated by pressing Q.

--- src/state-play.coffee
+++ src/state-play.coffee
@@ -60,7 +60,6 @@
           @background.debugging = @platforms.debugging = @player.debugging = @debugging
           @debugDisplay.reset() unless @debugging
         @gui.add(@, 'detachedCamera').onFinishChange => @_toggleCameraAttachment()
-        @gui.add(@, 'ended')
         @gui.addOpenFolder('gravity').addRange @physics.arcade.gravity, 'y'

       # First:


Revise PlayState#_addMate and starting and ending point creation.

Use Sprite#anchor on mate, minor refactoring, no test animation.

Points need to only be created once.

--- src/state-play.coffee
+++ src/state-play.coffee
@@ -13,6 +13,8 @@
 ], (dat, Phaser, _, Background, defines, Helpers, Platforms, Player) ->

   'use strict'
+
+  MateLastFrame = 14

   class PlayState extends Phaser.State

@@ -88,13 +90,12 @@
       @background.layout()

     _addMate: ->
-      {x, y} = @_createEndingPoint()
-      x -= 20
-      y -= 46
+      {x, y} = @endingPoint
+      x += 20
+      y -= 10
       @mate = @add.sprite x, y, 'mate', 1
-      manager = @mate.animations
-      manager.add 'end', [1..14], 12
-      manager.play 'end' # @test
+      @mate.anchor = new Phaser.Point 0.5, 0.5
+      @mate.animations.add 'end', [1..MateLastFrame], 12

     _addMusic: ->
       @music = @add.audio 'bgm', 0, yes
@@ -108,15 +109,15 @@
         tileImageKey: 'balcony'
       , @game, @gui?.addOpenFolder 'platforms'

+      @endingPoint = @platforms.ledges[-1...][0].createMidpoint @platforms
+      @startingPoint = new Phaser.Point defines.playerW, @world.height - defines.playerH
+      # Use for debugging endpoints.
+      @startingPoint = @platforms.ledges[-2...-1][0].createMidpoint @platforms
+
     _addPlayer: ->
-      origin = @_createStartingPoint()
+      origin = @startingPoint
       @player = new Player { origin }, @game, @cursors, @gui?.addOpenFolder 'player'

-    _createEndingPoint: ->
-      _.last(@platforms.ledges).createMidpoint @platforms
-
-    _createStartingPoint: ->
-      new Phaser.Point defines.playerW, @world.height - defines.playerH

     _renderDebugDisplay: ->
       @resetDebugDisplayLayout()


Remove unnecessary func call parenthesis

--- test/api/test-departure.coffee
+++ test/api/test-departure.coffee
@@ -32,7 +32,7 @@
   else
     # return new instance so it can be mutated
     # and not effect instance returned by next call
-    moment(nowMoment)
+    moment nowMoment

 # build server
 server = helpers.buildServer '../../api/resources/departure',
@@ -165,7 +165,7 @@

   it "should return 200 with the expected AM departures, when it's morning", ->
     # now = 7AM Central
-    nowMoment = moment('2014-05-01T07:00-05:00')
+    nowMoment = moment '2014-05-01T07:00-05:00'

     # mock departures that return at 7AM
     departureData.fetchByRouteDirectionAndStop = (routeId, directionId, stopId) ->


process.exit doesnt wait for callbacks :(

--- bin/jen.coffee
+++ bin/jen.coffee
@@ -87,12 +87,10 @@
     cp.exec "rm -rf build", () ->
       fs.mkdir "build", 0777, () ->
         go()
-    process.exit(0)
   if DEPLOY
     Logger.info "  DEPLOYING (w/ #{Config.deploy})"
     cp.exec Config.deploy, ( err ) ->
       Logger.error "  WTF? #{err}" if err
       Logger.info "  DONE" unless err
-    process.exit(0)


Bind bootstrap alert to .alert class

Bind bootstrap alert to .alert class

--- app/assets/javascripts/buckets.js.coffee
+++ app/assets/javascripts/buckets.js.coffee
@@ -100,6 +100,7 @@
   makeBucketsSortable()
   makeIssueSyncable()
   makeIssuesArchivable()
+  $(".alert").alert()

 $(document).on "page:load", ->
   makeIssuesSortable()


Work around Ctrl key state issue, fixes #54

--- app/assets/javascripts/hotkeys.js.coffee
+++ app/assets/javascripts/hotkeys.js.coffee
@@ -1,4 +1,9 @@
 $ ->
+  key.filter = (event) ->
+    # https://github.com/grantovich/CSH-WebNews/issues/54
+    # https://github.com/madrobby/keymaster/issues/36
+    return !event.ctrlKey
+
   key.setScope('main')

   # Select the next/previous post


Partial fix #1714 taskboard order

Partial fix #1714 taskboard order

--- app/coffee/modules/taskboard/main.coffee
+++ app/coffee/modules/taskboard/main.coffee
@@ -145,7 +145,7 @@

     loadTasks: ->
         return @rs.tasks.list(@scope.projectId, @scope.sprintId).then (tasks) =>
-            @scope.tasks = tasks
+            @scope.tasks = _.sortBy(tasks, 'taskboard_order')
             @scope.usTasks = {}

             # Iterate over all userstories and
@@ -194,7 +194,7 @@

         task.user_story = usId
         task.status = statusId
-        task.order = order
+        task.taskboard_order = order

         promise = @repo.save(task)
         promise.then =>


fill taskboard scope user with the project users

--- app/coffee/modules/taskboard/main.coffee
+++ app/coffee/modules/taskboard/main.coffee
@@ -116,6 +116,8 @@

             @scope.$emit('project:loaded', project)

+            @.fillUsersAndRoles(project.users, project.roles)
+
             return project

     loadSprintStats: ->
@@ -185,7 +187,6 @@
             return data

         return promise.then(=> @.loadProject())
-                      .then(=> @.loadUsersAndRoles())
                       .then(=> @.loadTaskboard())

     refreshTasksOrder: (tasks) ->


fixing profile

fixing profile

--- app/controllers/ProfileController.coffee
+++ app/controllers/ProfileController.coffee
@@ -1,5 +1,5 @@
 Spine = require('spine')
-User  = require('models/User')
+User  = require('zooniverse/lib/models/user')

 class ProfileController extends Spine.Controller
   className: "ProfileController"
@@ -9,13 +9,13 @@

   active:->
     super
-    if User.count()==0
+    if not User.current?
       Spine.Route.navigate('/')
     else
       @render()

   render:=>
     @html require('views/profile/profile')
-      user : User.first()
+      user : User.current

 module.exports = ProfileController


Update sailthru.coffee

Update sailthru.coffee

--- src/sailthru.coffee
+++ src/sailthru.coffee
@@ -36,7 +36,7 @@
         parse_uri = url.parse uri
         options =
             host: parse_uri.hostname
-            port: parse_uri.port !== undefined ? parse_uri.port : (parse_uri.protocol === 'https:' ? 443 : 80)
+            port: if parse_uri.port isnt undefined then parse_uri.port else (if parse_uri.protocol is 'https:' then 443 else 80)
             path: parse_uri.pathname
             method: method
             query: data


Fixing require './foo' under --eval and REPL; issue 1035

--- coffee-script.coffee
+++ coffee-script.coffee
@@ -58,7 +58,7 @@
   while root.parent
     root = root.parent
   # Set the filename.
-  root.filename = fs.realpathSync options.fileName or '.'
+  root.filename = if options.fileName then fs.realpathSync(options.fileName) else '.'
   # Clear the module cache.
   root.moduleCache = {} if root.moduleCache
   # Compile.
@@ -70,7 +70,7 @@
 # Compile and evaluate a string of CoffeeScript (in a Node.js-like environment).
 # The CoffeeScript REPL uses this to run the input.
 exports.eval = (code, options) ->
-  __filename = options.fileName
+  __filename = module.filename = options.fileName
   __dirname  = path.dirname __filename
   eval compile code, options


CoffeeScript 1.5.0

CoffeeScript 1.5.0

--- coffee-script.coffee
+++ coffee-script.coffee
@@ -26,7 +26,7 @@
     require.extensions[ext] = loadFile

 # The current CoffeeScript version number.
-exports.VERSION = '1.5.0-pre'
+exports.VERSION = '1.5.0'

 # Expose helpers for testing.
 exports.helpers = require './helpers'


Add more event emitter wildcard tests

Add more event emitter wildcard tests

--- test/core/eventemitterwildcard.coffee
+++ test/core/eventemitterwildcard.coffee
@@ -7,6 +7,7 @@
 describe 'KDEventEmitterWildcard', ->
   beforeEach ->
     @instance = new KDEventEmitterWildcard
+    emitSpy = sinon.spy @instance.emit

   it 'exists', ->
     KDEventEmitterWildcard.should.exist
@@ -23,3 +24,17 @@
     it 'should set max listeners', ->
       @instance.setMaxListeners 20
       @instance._maxListeners.should.equal 20
+
+  describe 'off', ->
+    it 'should remove all listeners', ->
+      emitter = new KDEventEmitterWildcard
+      spy = sinon.spy emitter.removeAllListeners
+      emitter.off
+      spy.should.be.calledOnce
+
+    it 'should return itself', ->
+      @instance.off().should.deepEqual @instance
+
+  describe 'on', ->
+    it 'should return itself', ->
+      @instance.on('koding', (->)).should.be.an.instanceOf(KDEventEmitterWildcard)


Suppress ace scrolling warning

Suppress ace scrolling warning

--- src/assembler/main-assembler.coffee
+++ src/assembler/main-assembler.coffee
@@ -65,6 +65,8 @@
       enableBasicAutocompletion: true
       enableLiveAutocompletion: true

+    editor.$blockScrolling = Infinity
+
     editor.getSession().setMode 'ace/mode/chip8'
     editor.setTheme 'ace/theme/monokai'


Add modifiable pane body

Add modifiable pane body

--- views/components/ship/parts/index.cjsx
+++ views/components/ship/parts/index.cjsx
@@ -1,3 +1,3 @@
 module.exports =
-  Slotitems: require './slotitems.cjsx'
-  PaneBody: require './panebody.cjsx'
+  Slotitems: window.hack.ShipViewSlotitems || require './slotitems.cjsx'
+  PaneBody: window.hack.ShipViewPaneBody ||  require './panebody.cjsx'


corrected description

corrected description

--- src/spec/e2e.coffee
+++ src/spec/e2e.coffee
@@ -123,7 +123,7 @@
             postProcess: (result, params, resolve, reject) ->
                 reject 'operation not successful'

-        bdd.then 'postProcess should fail because it takes longer than 4 seconds', (done) ->
+        bdd.then 'postProcess should fail because it has been rejected', (done) ->
             httpGet '/async-timeout-hook-reject/hooks!', 4949
             .then (response) ->
                 response = JSON.parse response


unify variables

unify variables

--- src/steroids.coffee
+++ src/steroids.coffee
@@ -352,19 +352,17 @@

       when "serve"
-
         @port = if argv.port
           argv.port
         else
           4000

-        servePort = @port
-        url = "http://localhost:#{servePort}"
+        url = "http://localhost:#{@port}"

         WebServer = require "./steroids/servers/WebServer"

         server = @startServer
-          port: servePort
+          port: @port
           callback: ()=>
             webServer = new WebServer
               path: "/"


(fix) --watch enabled by default

(fix) --watch enabled by default

--- src/steroids.coffee
+++ src/steroids.coffee
@@ -298,7 +298,7 @@
         watchEnabled = !(argv.watch == false)
         livereloadEnabled = (argv.livereload == true)

-        unless livereloadEnabled
+        if argv.livereload == false
           watchEnabled = false

         showConnectScreen = !(argv['connect-screen'] == false)


update for Bot API 2.1

update for Bot API 2.1

--- src/telegram.coffee
+++ src/telegram.coffee
@@ -59,6 +59,11 @@
   editMessageText
   editMessageCaption
   editMessageReplyMarkup
+  getChat
+  leaveChat
+  getChatAdministrators
+  getChatMember
+  getChatMembersCount
 """

 createStub = (name) -> (options) ->


No proper unit tests now fails tests

No proper unit tests now fails tests

--- src/unit/cli.coffee
+++ src/unit/cli.coffee
@@ -5,10 +5,11 @@
 cp = require 'child_process'
 fs = require 'fs'
 unitsStack = require './stack'
+require './index'

 [_, _, path, optsArgv...] = process.argv

-realpath = fs.realpathSync ''
+realpath = fs.realpathSync './'
 opts =
 	require: []

@@ -55,11 +56,8 @@
 	else
 		globPath = path

-	glob globPath, (err, files) ->
-		if err
-			throw err
+	files = glob.sync globPath
+	for file in files
+		runTestFile file

-		for file in files
-			runTestFile file
-
-		return
+	return


Remove a totally wrong code

Remove a totally wrong code

--- packages/rocketchat-lib/server/functions/settings.coffee
+++ packages/rocketchat-lib/server/functions/settings.coffee
@@ -162,8 +162,6 @@

 	if not _id or not value?
 		return false
-
-	value._updatedAt = new Date

 	return RocketChat.models.Settings.updateValueById _id, value


fix message composing help

fix message composing help

Katex as well as multiple line help was not being shown
because of a typo and a missing function.

--- packages/rocketchat-ui-message/message/messageBox.coffee
+++ packages/rocketchat-ui-message/message/messageBox.coffee
@@ -12,10 +12,12 @@
 			return roomData.name
 	showMarkdown: ->
 		return RocketChat.Markdown
+	showHighlight: ->
+		return RocketChat.Highlight
 	showKatex: ->
 		return RocketChat.katex
 	showFormattingTips: ->
-		return RocketChat.settings.get('Message_ShowFormattingTips') and (RocketChat.Markdown or RocketChat.Highlight or RocketChat.Katex)
+		return RocketChat.settings.get('Message_ShowFormattingTips') and (RocketChat.Markdown or RocketChat.Highlight or RocketChat.katex)
 	canJoin: ->
 		return !! ChatRoom.findOne { _id: @_id, t: 'c' }
 	subscribed: ->


Fine tune the colours, and fix problem with empty strings.

--- app/static/script/directive/chart.coffee
+++ app/static/script/directive/chart.coffee
@@ -94,17 +94,21 @@
               growl.error "#{response.status} #{response.statusText}"

       fuzzyColor = (str) ->
-        sndx = soundexPhonetics(str)
+        if (words = str.split(' ')).length > 1
+          colors = R.map fuzzyColor, R.filter(R.I, words)
+          return R.reduce tinycolor.mix, R.head(colors), R.tail(colors)
+        sndx = soundexPhonetics(str or ' ')
         # hue = (sndx[0].charCodeAt() % 64)  * (360 / 64) # modulo is for unicode chars. Or 360?
-        hue = (((sndx[0].charCodeAt() - (65)) + 26) % 26) * (360 / 26)
-        sat = parseInt(sndx.slice(1, 3), 7) * (50 / 48) + 50    # 0...48 => 50...100
+        # hue = (((sndx[0].charCodeAt() - (65)) + 26) % 26) * (360 / 26)
+        hue = (((sndx[0].charCodeAt())) % 26) * (360 / 26)
+        sat = parseInt(sndx.slice(1, 3), 7) * (50 / 48) + 25    # 0...48 => 50...100
         lig = parseInt(sndx.slice(3, 4), 7) * (50 / 6) + 50   # 0..6 => 50...100 (minus word length)
         lig -= Math.min(50, str.length)
-        tinycolor({ h: hue, s: sat, l: lig }).toHexString()
+        tinycolor({ h: hue, s: sat, l: lig })

       scope.seriesStyle = (serie) ->
-        stroke: fuzzyColor(serie)
-        strokeDasharray: '2, 2' if serie.match /\(estimated\)/
+        stroke: fuzzyColor(serie).toHexString()
+        strokeDasharray: '6, 2' if serie.match /\(estimated\)/

       scope.$watchCollection 'chart.data', (val, old) ->
         # $log.info "Watching chart.data:", val


ie9 is slow

ie9 is slow

if delay is too low on undo-manager, ie will fail to coalesce because
it's too slow

--- test/unit/modules/undo-manager.coffee
+++ test/unit/modules/undo-manager.coffee
@@ -7,7 +7,7 @@
       </div>'
     @quill = new Quill(@container.firstChild, {
       modules: {
-        'undo-manager': { delay: 10 }
+        'undo-manager': { delay: 100 }
       }
     })
     @undoManager = @quill.getModule('undo-manager')


Add regression test for SrcArray.remove

Add regression test for SrcArray.remove

--- test/spec/test_reactive.coffee
+++ test/spec/test_reactive.coffee
@@ -598,3 +598,9 @@
     ys.push(6)
     xs.splice(0, 1, 0, 1)
     ys.replace([4,5,6,7])
+
+describe 'SrcArray', ->
+  it 'should not change anything if remove query not found', ->
+    xs = rx.array([0])
+    xs.remove(1)
+    expect(xs.raw()).toEqual([0])


Added validation to credit card model

Added validation to credit card model


refactoring the form view to be a proper container, removing the fieldset container

--- src/containers/fieldset_view.coffee
+++ src/containers/fieldset_view.coffee
@@ -1,47 +0,0 @@
-Luca.containers.FieldsetView = Luca.View.extend
-  componentType: 'fieldset_view'
-
-  tagName: 'fieldset'
-
-  className: 'luca-ui-fieldset'
-
-  labelAlign: 'top'
-
-  afterInitialize: ()->
-    return if @initialized is true
-
-    @components = _( @components ).map (component, index)=>
-      component.id = "#{ @cid }-#{ index }"
-      component.ctype ||= component.type + '_field'
-      Luca.util.LazyObject(component)
-
-    # HACK.  Why is double initialization happening in the first place?
-    @initialized = true
-
-  beforeRender: ()->
-    return if @beforeRenderCalled
-
-    _( @components ).each (component)=>
-      component.renderTo = component.container = @el
-      component.render()
-
-    # HACK.  Why is double initialization happening in the first place?
-    @beforeRenderCalled = true
-
-  afterRender: ()->
-    return if @afterRenderCalled
-
-    $(@el).addClass "label-align-#{ @labelAlign }"
-    $(@el).append("<legend>#{ @legend }</legend>") if @legend
-    $( @container ).append( $(@el) )
-
-    # HACK.  Why is double initialization happening in the first place?
-    @afterRenderCalled = true
-
-  initialize: (@options={})->
-    _.extend @, @options
-    Luca.View.prototype.initialize.apply @, arguments
-    @components ||= @fields
-
-  getFields: ()->
-    _( @components ).select (component)-> component.form_field is true


Fix path for fetching posts

Fix path for fetching posts

--- assets/javascripts/models/post.js.coffee
+++ assets/javascripts/models/post.js.coffee
@@ -48,15 +48,10 @@
       return Marbles.HTTP.TentClient.find entity: (params.entity || TentStatus.config.current_entity), (client) =>
         @fetch(params, _.extend(options, {client: client}))

-    path = if params.entity
-      "/posts/#{encodeURIComponent params.entity}/#{params.id}"
-    else
-      "/posts/#{params.id}"
-
     _params = _.clone(params)
     delete _params.id
     delete _params.entity
-    options.client.get path, _params, (res, xhr) =>
+    options.client.get "/posts/#{params.id}", _params, (res, xhr) =>
       if xhr.status != 200
         @trigger("fetch:failed", params, res, xhr)
         options.error?(res, xhr)


Prevent double rendering error

Prevent double rendering error

--- services/web/app/coffee/Features/Subscription/TeamInvitesController.coffee
+++ services/web/app/coffee/Features/Subscription/TeamInvitesController.coffee
@@ -43,7 +43,7 @@
 		TeamInvitesHandler.acceptInvite token, userId, (err, results) ->
 			return handleError(err, req, res, next) if err?

-		res.sendStatus 204
+			res.sendStatus 204

 	revokeInvite: (req, res) ->
 		email = req.params.email


Add tests to check that foreground commands are implemented

--- tests/unit_tests/commands_test.coffee
+++ tests/unit_tests/commands_test.coffee
@@ -3,6 +3,8 @@
 extend global, require "../../background_scripts/bg_utils.js"
 global.Settings = {postUpdateHooks: {}, get: (-> ""), set: ->}
 {Commands} = require "../../background_scripts/commands.js"
+global.KeyHandlerMode = {}
+{NormalModeCommands} = require "../../content_scripts/mode_normal.js"

 context "Key mappings",
   setup ->
@@ -114,6 +116,14 @@
     assert.equal "a", BgUtils.parseLines("  a  \n  b")[0]
     assert.equal "b", BgUtils.parseLines("  a  \n  b")[1]

-# TODO (smblott) More tests:
-# - Ensure each background command has an implmentation in BackgroundCommands
-# - Ensure each foreground command has an implmentation in vimium_frontent.coffee
+context "Commands implemented",
+  (for own command, options of Commands.availableCommands
+    do (command, options) ->
+      if options.background
+        should "#{command} (background command)", ->
+          # TODO: Import background_scripts/main.js and expose BackgroundCommands from there.
+          # assert.isTrue BackgroundCommands[command]
+      else
+        should "#{command} (foreground command)", ->
+          assert.isTrue NormalModeCommands[command]
+  )...


Edit management-problems.coffee (working)

--- picoCTF-web/web/coffee/management-problems.coffee
+++ picoCTF-web/web/coffee/management-problems.coffee
@@ -270,7 +270,7 @@

   onStateToggle: (e) ->
     e.preventDefault()
-    apiCall "POST", "/api/admin/problems/availability", {pid: @props.pid, state: !@props.disabled}
+    apiCall "PATCH", "http://localhost:5000/api/v1/problems/" + @props.pid, {disabled: !@props.disabled}
     .done @props.onProblemChange

   handleExpand: (e) ->
@@ -367,7 +367,7 @@

 ProblemDependencyView = React.createClass
   handleClick: (bundle) ->
-    apiCall "POST", "/api/admin/bundle/dependencies_active", {bid: bundle.bid, state: !bundle.dependencies_enabled}
+    apiCall "PATCH", "http://localhost:5000/api/v1/bundles/" + bundle.bid, {dependencies_enabled: !bundle.dependencies_enabled}
     .done @props.onProblemChange

   render: ->
@@ -402,10 +402,10 @@
     window.confirmDialog "Are you sure you want to #{change} these #{changeNumber} problems?", "Mass Problem State Change",
     "Yes", "No", (() ->
       calls = _.map @props.problems, (problem) ->
-        apiCall "POST", "/api/admin/problems/availability", {pid: problem.pid, state: !enabled}
+        apiCall "PATCH", "http://localhost:5000/api/v1/problems/" + problem.pid, {disabled: !enabled}
       ($.when.apply this, calls)
         .done (() ->
-          if _.all(_.map arguments, (call) -> (_.first call).status == 1)
+          if _.all(_.map arguments, (call) -> (_.first call).success == true)
             apiNotify {status: 1, message: "All problems have been successfully changed."}
           else
             apiNotify {status: 0, message: "There was an error changing some of the problems."}


Use tmpDir variable

Use tmpDir variable

--- build/Gruntfile.coffee
+++ build/Gruntfile.coffee
@@ -40,7 +40,7 @@
   appName = if process.platform is 'darwin' then 'Atom.app' else 'Atom'
   buildDir = grunt.option('build-dir') ? path.join(tmpDir, 'atom-build')
   installDir = grunt.option('install-dir')
-  atomShellDownloadDir = path.join(os.tmpdir(), 'atom-cached-atom-shells')
+  atomShellDownloadDir = path.join(tmpDir, 'atom-cached-atom-shells')
   symbolsDir = path.join(buildDir, 'Atom.breakpad.syms')
   shellAppDir = path.join(buildDir, appName)
   if process.platform is 'win32'


fixng repl prompt

fixng repl prompt

--- console/repl.coffee
+++ console/repl.coffee
@@ -7,9 +7,9 @@

 "use strict"

-repl_run = (rs, cname, args) ->
+repl_run = (rs, cname, args, done_cb) ->
   cname = cname[0...-6] if cname[-6..] is '.class'
-  jvm.run_class rs, cname, args
+  jvm.run_class rs, cname, args, done_cb

 read_stdin = (n_bytes, resume) ->
   process.stdin.resume()
@@ -34,9 +34,10 @@
   repl.on 'line', (line) ->
     toks = line.trim().split /\s+/
     if toks?[0]?.length > 0
-      repl_run rs, toks[0], toks[1..]
-      repl.output.write '\n'
-    repl.prompt()
+      repl_run rs, toks[0], toks[1..], ->
+        repl.prompt()
+    else
+      repl.prompt()

   # set the prompt, display it, and begin the loop
   repl.setPrompt 'doppio> '


consistency: order classes like in other case


[withoutHighLow] Old algorithm works better

--- routes/process.coffee
+++ routes/process.coffee
@@ -57,7 +57,7 @@
           assert.equal now.position, 0 # TODO assert
           now.return =
           (1 + last.return) *
-          (1 - now.trade * (now.openPrice / now.price - 1)) - 1
+          (1 - now.trade * (now.price / now.openPrice - 1)) - 1
           now.openPrice = 0
       else if @cutoff < i # Consider opening a position
         if last.max <= now.price
@@ -99,7 +99,7 @@
 predict = (data) ->
   a = _.groupBy data, (i) ->
     i[0][..9]
-  transaction = new Transaction 2, 15, 0.1, 0.01
+  transaction = new Transaction 4, 8, 0.1, 0.01
   ret = 0
   for i, j of a
     ret = transaction.processDay j, ret


allow migrate_update command

allow migrate_update command

--- salvus/compute.coffee
+++ salvus/compute.coffee
@@ -71,7 +71,7 @@
             start : 'starting'
             close : 'closing'
             save  : 'saving'
-        commands : ['start', 'close', 'save', 'copy_path', 'directory_listing', 'read_file', 'network', 'mintime', 'disk_quota', 'compute_quota', 'status']
+        commands : ['start', 'close', 'save', 'copy_path', 'directory_listing', 'read_file', 'network', 'mintime', 'disk_quota', 'compute_quota', 'status', 'migrate_live']

     running:
         desc     : 'The project is opened and ready to be used.'
@@ -79,7 +79,7 @@
         to       :
             stop : 'stopping'
             save : 'saving'
-        commands : ['stop', 'save', 'address', 'copy_path', 'directory_listing', 'read_file', 'network', 'mintime', 'disk_quota', 'compute_quota', 'status']
+        commands : ['stop', 'save', 'address', 'copy_path', 'directory_listing', 'read_file', 'network', 'mintime', 'disk_quota', 'compute_quota', 'status', 'migrate_live']

     saving:
         desc     : 'The project is being snapshoted and saved to cloud storage.'


numeric validator

numeric validator

--- src/validators.coffee
+++ src/validators.coffee
@@ -54,4 +54,34 @@
       true
     else
       @msg = "Value #{value} not for current format"
-      false+      false
+
+class NumericalityValidator extends Validator
+  validate: (value, attributes = {}) ->
+    if attributes['only_integers']
+      if /^\d+$/.test(value)
+        true
+      else
+        @msg = "Only allows integer numbers"
+        false
+
+    else
+      if /^\d+(?:\.\d{1,})?$/.test(value)
+        true
+      else
+        @msg = "Only allows numbers"
+        false
+
+
+
+
+
+
+
+
+
+
+
+
+
+


Options

Options

- Another fix for radio buttons: Store back value
  even with "prio_activate", if the store uses an Array.
  In that case we need to remove the value from the Array.

--- src/elements/Options/Options.coffee
+++ src/elements/Options/Options.coffee
@@ -342,8 +342,7 @@
 								@storeValue(_cb.getValue(), flags)
 						else
 							removeFromArray(_cb.getOptValue(), arr = @getValue().slice(0))
-							if not flags.prior_activate
-								@storeValue(arr, flags)
+							@storeValue(arr, flags)

 							if @_sortable
 								order_options_by_value_array()


Do not log HTTP response headers into debug log

--- src/webRequest.coffee
+++ src/webRequest.coffee
@@ -71,9 +71,6 @@
           @logger.error data
           return

-        @logger.debug 'Response headers:'
-        @logger.debug response.headers
-
         data = JSON.parse data

         @logger.debug 'HTTPS response body:'


Fix typo in Util.logError calls.

Fix typo in Util.logError calls.

--- src/webRequest.coffee
+++ src/webRequest.coffee
@@ -59,8 +59,8 @@

       response.on 'end', =>
         if response.statusCode >= 400
-          Util.logerror "Fleep API error : #{response.statusCode}"
-          Util.logerror data
+          Util.logError "Fleep API error : #{response.statusCode}"
+          Util.logError data

         Util.debug 'Response headers:'
         Util.debug response.headers
@@ -81,14 +81,14 @@
         callback? null, data, metaData

         response.on 'error', (err) ->
-          Util.logerror 'HTTPS response error:', err
+          Util.logError 'HTTPS response error:', err
           callback? err, null

     request.end body, 'binary'

     request.on 'error', (err) ->
-      Util.logerror 'HTTPS request error:', err
-      Util.logerror err.stack
+      Util.logError 'HTTPS request error:', err
+      Util.logError err.stack
       callback? err


Var rename FTW

Var rename FTW

--- app/assets/javascripts/roofpig/Side.js.coffee
+++ app/assets/javascripts/roofpig/Side.js.coffee
@@ -27,10 +27,10 @@
     square.computeBoundingSphere();
     new THREE.Mesh(square, new THREE.MeshBasicMaterial(color: color))

-  _offsets: (coverage, backside) ->
+  _offsets: (coverage, reversed) ->
     axis2 = v3(@normal.y, @normal.z, @normal.x)
     axis3 = v3(@normal.z, @normal.x, @normal.y)
-    flip = (@normal.y + @normal.z + @normal.x) * (if backside then -1.0 else 1.0)
+    flip = (@normal.y + @normal.z + @normal.x) * (if reversed then -1.0 else 1.0)

     dx = axis2.clone().add(axis3).multiplyScalar(coverage * flip)
     dy = axis2.clone().sub(axis3).multiplyScalar(coverage)


remove all assignment columns field from gradebook

This consolidates the "all assignment columns" field by managing
those column definitions through the gradebook grid.

refs CNVS-38346

test plan:
* ensure Jenkins passes

Change-Id: Ib34e6e37a02ade7f1682a2c1750b3e84015d807f
Reviewed-on: https://gerrit.instructure.com/121514
Tested-by: Jenkins
Reviewed-by: Gary Mei <4dff5ebcff9ed76793b70b9470ac64791a16ed11@instructure.com>
Reviewed-by: Derek Bender <d24d8a7ee520e34b1cec38800225d9f36b552df7@instructure.com>
Product-Review: Jeremy Neander <20252aad5efdc666f083d82ab63ed0f9f72a043e@instructure.com>
QA-Review: Jeremy Neander <20252aad5efdc666f083d82ab63ed0f9f72a043e@instructure.com>

--- app/coffeescripts/gradezilla/Gradebook.coffee
+++ app/coffeescripts/gradezilla/Gradebook.coffee
@@ -261,8 +261,6 @@
       @studentViewStudents = {}
       @courseContent.students = new StudentDatastore(@students, @studentViewStudents)

-      @allAssignmentColumns = []
-
       @gradebookGrid = {
         columns: {
           definitions: {}
@@ -773,14 +771,6 @@
       pattern = new RegExp(@userFilterTerm, 'i')
       _.any propertiesToMatch, (prop) ->
         student[prop]?.match pattern
-
-    filterAssignmentColumns: (columns) =>
-      columnsByAssignmentId = _.indexBy(columns, 'assignmentId')
-
-      assignments = _.pluck(columns, 'object')
-      filteredAssignments = @filterAssignments(assignments)
-
-      filteredAssignments.map (assignment) => columnsByAssignmentId[assignment.id]

     filterAssignments: (assignments) =>
       assignmentFilters = [
@@ -1455,9 +1445,10 @@
       @userFilter.el.setAttribute('aria-disabled', disabled)

     setVisibleGridColumns: ->
-      assignmentColumns = @filterAssignmentColumns(@allAssignmentColumns)
-
-      scrollableColumns = assignmentColumns
+      assignments = @filterAssignments(Object.values(@assignments))
+      scrollableColumns = assignments.map (assignment) =>
+        @gradebookGrid.columns.definitions[@getAssignmentColumnId(assignment.id)]
+
       unless @hideAggregateColumns()
         for assignmentGroupId of @assignmentGroups
           scrollableColumns.push(@gradebookGrid.columns.definitions[@getAssignmentGroupColumnId(assignmentGroupId)])
@@ -1615,7 +1606,6 @@

       for id, assignment of @assignments
         assignmentColumn = @buildAssignmentColumn(assignment)
-        @allAssignmentColumns.push(assignmentColumn)
         @gradebookGrid.columns.definitions[assignmentColumn.id] = assignmentColumn

       for id, assignmentGroup of @assignmentGroups
@@ -1639,7 +1629,7 @@
         syncColumnCellResize: true
         rowHeight: 35
         headerHeight: 38
-        numberOfColumnsToFreeze: @listVisibleCustomColumns().length + 1
+        numberOfColumnsToFreeze: @gradebookGrid.columns.frozen.length
       }, @options)

       @setVisibleGridColumns()


All macros that dont support matching should exit early to not cause looping


alan alan alan

alan alan alan

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

   robot.respond /(what should i do about)(.*)/i, (msg) ->
     msg.send "Escalate to Carson!"
+
+  robot.respond /(alan)/i, (msg) ->
+    msg.send "http://www.youtube.com/watch?v=aXqkNF8fgs8"


make onduty do autoplay and looping

make onduty do autoplay and looping

--- scripts/onduty.coffee
+++ scripts/onduty.coffee
@@ -1,6 +1,7 @@
-# Display a teemo video if anyone says on duty
+# Display a teemo video if anyone says on duty ... original link http://vimeo.com/34497207"
 #

 module.exports = (robot) ->
   robot.hear /on duty/i, (msg) ->
-    msg.send "http://vimeo.com/34497207"
+    msg.send "http://player.vimeo.com/video/34497207?autoplay=1&loop=1"
+


Testing indentation. Again.

Testing indentation. Again.

--- scripts/points.coffee
+++ scripts/points.coffee
@@ -91,4 +91,4 @@
             timerMethod = ->
                 for username in people
                     points[username] =+ 5
-                return
+                    return


hubot should reply

hubot should reply

--- scripts/sachin.coffee
+++ scripts/sachin.coffee
@@ -80,7 +80,7 @@
     msg.send "stretching so he can be more flexible with CBRIS code."

   robot.respond /learn punjabi with Sdog/i, (msg)->
-    msg.random punjabi
+    msg.send msg.random punjabi

   robot.respond /sachin super duper bomb( (\d+))?/i, (msg)->
     count = msg.match[2] || 5


update docs

update docs

--- code/li.coffee
+++ code/li.coffee
@@ -7,10 +7,10 @@

 command = {}

-# args[1] is the name of the command, e.g. start
+# args[1] is the name of the command, e.g. staut
 # args[2..] are the command args
 command.help =
-  help: "help                        Show this help"
+  help: "help\t\t\t\tShow this help"
   run: (args) ->
     cmdhelp = (command[cmd].help for cmd of command).join('\n    ')
     help =
@@ -23,7 +23,7 @@

 # args[2] is the name of the instance to start
 command.start =
-  help: "start <instance-name>       Start an instance"
+  help: "start <instance>\t\t\tStart an instance"
   run: (args) ->
     Linode.get args[2], (instance) ->
       instance.start ->
@@ -31,7 +31,7 @@

 # args[2] is the name of the instance to stop
 command.stop =
-  help: "stop <instance-name>        Stop an instance"
+  help: "stop <instance>\t\t\tStop an instance"
   run: (args) ->
     Linode.get args[2], (instance) ->
       instance.stop ->
@@ -39,7 +39,7 @@

 # args[2] is the name of the instance to restart
 command.restart =
-  help: "restart <instance-name>     Restart an instance"
+  help: "restart <instance>\t\t\tRestart an instance"
   run: restart = (args) ->
     Linode.get args[2], (instance) ->
       instance.restart ->
@@ -48,7 +48,7 @@
 # Takes a config name, and creates an instance based on that config.
 # args[2] is the name of the config
 command.create =
-  help: "create <config-name>        Create a instance by config"
+  help: "create <config>\t\t\tCreate and deploy an instance by config"
   run: (args) ->
     process.stdout.write('Creating...\n')
     Linode.create args[2], (dummy_, item) ->
@@ -62,7 +62,7 @@

 # args[2..] are the names of the instance to destroy
 command.destroy =
-  help: "destroy <instance-name> ... Destroy an instance (or instances)"
+  help: "destroy <instance> ...\t\tDestroy an instance (or instances)"
   run: (args) ->
     async.forEach args[2...], (item, callback) ->
       Linode.destroy item, ->
@@ -71,7 +71,7 @@

 # renames the instance args[2] to args[3]
 command.rename =
-  help: "rename <name> <new-name>    Rename an instance"
+  help: "rename <name> <new-name>\t\tRename an instance"
   run: (args) ->
     process.stdout.write "Renaming #{args[2]}...\n"
     Linode.rename args[2], args[3], (err) ->
@@ -84,7 +84,7 @@
 # args[2] is the name of the instance;
 # args[3] and onwards are the command to run.
 command.sh =
-  help: "sh <instance-name> <cmd>    Execute a command on instance"
+  help: "sh <instance> <cmd>\t\t\tExecute a command on instance"
   run: (args) ->
     Linode.get args[2], (instance) ->
       callback = (code) ->
@@ -96,7 +96,7 @@
 # ssh into an instance.
 # args[2] is the name of the instance.
 command.ssh =
-  help: "ssh [user@]<instance-name>  Log in to instance using ssh"
+  help: "ssh [user@]<instance>\t\tLog in to instance via ssh"
   run: (args) ->
     name = args[2]
     l = name.split '@'
@@ -114,7 +114,7 @@
 # args[3] is the config
 # args[4] is the optional hook name
 command.runhook =
-  help: "runhook <instance-name> <config> [hook]     Run deployment hook on instance"
+  help: "runhook <instance> <config> [hook]\tRun deployment hook(s)"
   run: (args) ->
     [instance_name, config, hook] = [ args[2], args[3], args[4] ]

@@ -126,7 +126,7 @@

 # Makes a comment on an instance
 command.comment =
-  help: "comment <name> [<comment>]  Make or display a comment on instance"
+  help: "comment <instance> [comment]\tMake or display a comment"
   run: (args) ->
     if args[3]?
       process.stdout.write "Making comment..."
@@ -144,7 +144,7 @@

 # Displays a list of instances
 command.list =
-  help: "list                        List instances"
+  help: "list\t\t\t\tList instances"
   run: (args) ->
     process.stdout.write "Listing instances...\n"
     Linode.list (err, list) ->
@@ -173,7 +173,7 @@
     '3' : 'Rebooting'            # Shutting Down

 command.jobs =
-  help: "jobs                        List all Linode jobs"
+  help: "jobs\t\t\t\tList all Linode jobs"
   run: (args) ->
     # Linode lists jobs per instance, so we have to iterate over
     # each of our instances.


Remove commented out code

Remove commented out code

--- src/middleware/authorisation.coffee
+++ src/middleware/authorisation.coffee
@@ -79,4 +79,3 @@
 # note: you cant spy on these method because of this :(
 if process.env.NODE_ENV == "test"
   exports.genAuthAudit = genAuthAudit
-  # exports.channelWithHighestPriority = channelWithHighestPriority


(Aside: For historical reasons we do make the filename extension optional, which is a deeply regrettable accident of history that I would not repeat, given the chance.)

--- command.coffee
+++ command.coffee
@@ -85,13 +85,8 @@
   fs.stat source, (err, stats) ->
     throw err if err and err.code isnt 'ENOENT'
     if err?.code is 'ENOENT'
-      if topLevel and source and path.extname(source) not in coffee_exts
-        source = sources[sources.indexOf(source)] = "#{source}.coffee"
-        return compilePath source, topLevel, base
-      if topLevel
-        console.error "File not found: #{source}"
-        process.exit 1
-      return
+      console.error "File not found: #{source}"
+      process.exit 1
     if stats.isDirectory() and path.dirname(source) isnt 'node_modules'
       watchDir source, base if opts.watch
       fs.readdir source, (err, files) ->


FIX: Fix template path of unit staff editor

--- src/app/units/unit-staff-editor/unit-staff-editor.coffee
+++ src/app/units/unit-staff-editor/unit-staff-editor.coffee
@@ -7,7 +7,7 @@
 .directive('unitStaffEditor', ->
   replace: true
   restrict: 'E'
-  templateUrl: 'unit/unit-staff-editor/unit-staff-editor.tpl.html'
+  templateUrl: 'units/unit-staff-editor/unit-staff-editor.tpl.html'
   controller: ($scope, $rootScope, Unit, UnitRole, alertService, groupService) ->
     temp = []
     users = []


strip unnecessary parts from new drag-handle component

--- app/assets/javascripts/components/mark/tools/text-row-tool/drag-handle.cjsx
+++ app/assets/javascripts/components/mark/tools/text-row-tool/drag-handle.cjsx
@@ -5,17 +5,10 @@
 FILL_COLOR = 'black'
 STROKE_WIDTH = 1.5

-CROSS_PATH = "
-  M #{-1 * RADIUS * 0.7 } 0
-  L #{RADIUS * 0.7 } 0
-  M 0 #{-1 * RADIUS * 0.7 }
-  L 0 #{RADIUS * 0.7 }
-"
-
 DESTROY_TRANSITION_DURATION = 0

 module.exports = React.createClass
-  displayName: 'DeleteButton'
+  displayName: 'DragHandle'

   getDefaultProps: ->
     x: 0
@@ -23,18 +16,11 @@
     rotate: 0

   render: ->
-    console.log 'POSITION: ', @props.position
     transform = "
       translate(#{@props.position.x}, #{@props.position.y})
       rotate(#{@props.rotate})
       scale(#{1 / @props.tool.props.xScale}, #{1 / @props.tool.props.yScale})
     "
-
     <g className="clickable drawing-tool-delete-button" transform={transform} stroke={STROKE_COLOR} strokeWidth={STROKE_WIDTH} onClick={@destroyTool}>
       <circle r={RADIUS} fill={FILL_COLOR} />
-      <path d={CROSS_PATH} transform="rotate(45)" />
     </g>
-
-  destroyTool: ->
-    @props.tool.setState destroying: true, =>
-      setTimeout @props.tool.props.onDestroy, DESTROY_TRANSITION_DURATION


omg, wtf SendMessage2: The Return of SendMessage

--- job-registry.cson
+++ job-registry.cson
@@ -184,6 +184,16 @@
     'send-message':
       task: 'meshblu-core-task-send-message'

+SendMessage2:
+  start: 'authenticate'
+  tasks:
+    'authenticate':
+      filter: 'Authenticate'
+      on:
+        204: 'send-message-2'
+    'send-message-2':
+      task: 'meshblu-core-task-send-message-2'
+
 SubscriptionList:
   start: 'authenticate'
   tasks:


create_app returns app object

create_app returns app object

--- zechat/static/test_account.coffee
+++ zechat/static/test_account.coffee
@@ -6,15 +6,15 @@
     $.post(zc.TESTING_URL_MAP.flush, -> done())

   it 'should prompt for email and password', (test_done) ->
-    $el = $('<div>')
+    app = zc.create_app(urls: zc.TESTING_URL_MAP, el: $('<div>')[0])

-    zc.waitfor(=> zc.some($el.find('form.login')))
+    zc.waitfor(=> zc.some(app.$el.find('form.login')))
     .done ($form) =>
       $form.find('[name=email]').val('foo@example.com')
       $form.find('[name=passphrase]').val('testing one two three')
       $form.submit()

-    zc.create_app(urls: zc.TESTING_URL_MAP, el: $el[0]).ready
+    app.ready
     .done (app) ->
       secret = app.request('identity').model.get('secret')
       expect(secret).toEqual('sk:WHls/a+QF+0YYLorUzLFRmE4l3bcndjJ2oStx6zeGp8=')


Correct a typo

Correct a typo

--- code/li.coffee
+++ code/li.coffee
@@ -18,7 +18,7 @@
     Usage: li <command> [OPTIONS]
       Commands:

-    """ + cmdhelp
+    """ + cmdhelp + '\n'
     process.stdout.write help

 # args[2] is the name of the instance to start


set invisible instead of remove to preserve space of voting arrows when voting is disabled

--- lms/static/coffee/src/discussion/content.coffee
+++ lms/static/coffee/src/discussion/content.coffee
@@ -387,4 +387,4 @@
     if not Discussion.getContentInfo id, 'can_openclose'
       $local(".discussion-openclose").remove()
     if not Discussion.getContentInfo id, 'can_vote'
-      $local(".discussion-vote").remove()
+      $local(".discussion-vote").css "visibility", "hidden"


fixes settings live reload

fixes settings live reload

--- lib/flatwhite.coffee
+++ lib/flatwhite.coffee
@@ -22,6 +22,7 @@
   enableTheme: (ebmedded) ->
     embedded_path = "#{__dirname}/../styles/settings.less"
     fs.writeFileSync embedded_path, "@import 'languages/embedded-#{@getNormalizedName(ebmedded)}';"
+    atom.packages.getLoadedPackage("#{@packageName}").reloadStylesheets()

   getNormalizedName: (name) ->
     "#{name}"


simple back to homepage navigation option primarily for museum of science

--- @client/proposal_navigation.coffee
+++ @client/proposal_navigation.coffee
@@ -364,6 +364,38 @@

+window.BackHomeNavigation = (args) ->
+  proposals = fetch('/proposals').proposals
+
+  heading_style = _.defaults {}, customization('list_label_style'),
+    fontSize: 36
+    fontWeight: 400
+    fontStyle: 'oblique'
+    textAlign: 'center'
+    marginBottom: 18
+
+
+  loc = fetch 'location'
+  hash = loc.url.split('/')[1].replace('-', '_')
+
+  DIV
+    style: {}
+
+    H2
+      style: heading_style
+
+
+      'Done? Go '
+
+      A
+        href: "/##{hash}"
+        style:
+          textDecoration: 'underline'
+          fontWeight: 700
+        "back to the homepage"
+      '.'
+
+


(fix) interpolation only works for double quotes. oops

--- lib/hmac-sha1.coffee
+++ lib/hmac-sha1.coffee
@@ -1,4 +1,5 @@
 crypto    = require('crypto')
+inspect = (require 'util').inspect

@@ -25,7 +26,7 @@
   return body if typeof body isnt 'object'
   for key, val of body
     continue if key is 'oauth_signature'
-    out.push '#{key}=#{special_encode(val)}'
+    out.push "#{key}=#{special_encode(val)}"

   special_encode out.sort().join('&')


Add block comment support for C/C++/ObjC and Java

--- lib/languages.coffee
+++ lib/languages.coffee
@@ -9,6 +9,7 @@
     nameMatchers:      ['.c', '.h']
     pygmentsLexer:     'c'
     highlightJS:       'cpp'
+    multiLineComment:  ['/*', '*', '*/']
     singleLineComment: ['//']
     ignorePrefix:      '}'
     foldPrefix:        '^'
@@ -25,6 +26,7 @@
   'C++':
     nameMatchers:      ['.cpp', '.hpp', '.c++', '.h++', '.cc', '.hh', '.cxx', '.hxx']
     pygmentsLexer:     'cpp'
+    multiLineComment:  ['/*', '*', '*/']
     singleLineComment: ['//']
     ignorePrefix:      '}'
     foldPrefix:        '^'
@@ -135,6 +137,7 @@
   Java:
     nameMatchers:      ['.java']
     pygmentsLexer:     'java'
+    multiLineComment:  ['/*', '*', '*/']
     singleLineComment: ['//']
     ignorePrefix:      '}'
     foldPrefix:        '^'
@@ -219,6 +222,7 @@
     nameMatchers:      ['.m', '.mm']
     pygmentsLexer:     'objc'
     highlightJS:       'objectivec'
+    multiLineComment:  ['/*', '*', '*/']
     singleLineComment: ['//']
     ignorePrefix:      '}'
     foldPrefix:        '^'


changed suffix of avg time graph to be s instead of %

--- client/components/gateblu-dashboard.cjsx
+++ client/components/gateblu-dashboard.cjsx
@@ -18,6 +18,7 @@
     failures: 0
     successPercentage: 0
     avgElapsedTime: 0
+    elapsedTimeChartData: {}
     gatebluAddDeviceSuccessOverTime:
       index: 'gateblu_device_add'
       datasets: []
@@ -70,7 +71,7 @@

       <OverTimeGauge
         title="Gateblu Add Device Average Time Over Time"
-        suffix="%"
+        suffix="s"
         elapsedTimeChartData={@state.elapsedTimeChartData} />
     </div>


add error handler to each stage of upload pipeline

--- services/track-changes/app/coffee/MongoAWS.coffee
+++ services/track-changes/app/coffee/MongoAWS.coffee
@@ -10,7 +10,12 @@

 	bulkLimit: 10

-	archiveDocHistory: (project_id, doc_id, callback = (error) ->) ->
+	archiveDocHistory: (project_id, doc_id, _callback = (error) ->) ->
+
+		callback = (args...) ->
+			_callback(args...)
+			_callback = () ->
+
 		query = {
 			doc_id: ObjectId(doc_id)
 			expiresAt: {$exists : false}
@@ -27,10 +32,14 @@
 		}

 		db.docHistory.find(query)
+			.on 'error', (err) ->
+				callback(err)
 			.pipe JSONStream.stringify()
-				.pipe upload
-				.on 'finish', () ->
-					return callback(null)
+			.pipe upload
+			.on 'error', (err) ->
+				callback(err)
+			.on 'finish', () ->
+				return callback(null)

 	unArchiveDocHistory: (project_id, doc_id, callback = (error) ->) ->


forest > Change some values.

forest > Change some values.

--- forest/src/app.coffee
+++ forest/src/app.coffee
@@ -106,12 +106,12 @@
         branchColor: greens[Math.floor(Math.random() * greens.length - 1)]
         branchLength: Math.random() * 40
         branchWidth: Math.random() * 20 + 5
-        divergence: Math.random() * 4
+        divergence: Math.random() * -0.5 * 2
         ease: eases[Math.floor(Math.random() * eases.length)]
         growSpeed: Math.random() * 1200 + 100
         stumpColor: browns[Math.floor(Math.random() * browns.length - 1)]
         stumpWidth: Math.random() * 20 + 5
-        totalLength: Math.random() * 8 + 2
+        totalLength: Math.random() * 8 + 5
         x: Math.random() * window.innerWidth
         y: Math.random() * window.innerHeight


Add a test for when the user can't be found.

--- test/unit/coffee/WebApiManager/WebApiManagerTests.coffee
+++ test/unit/coffee/WebApiManager/WebApiManagerTests.coffee
@@ -72,6 +72,16 @@
 					.calledWith(new Error("web returned failure status code: 500"))
 					.should.equal true

+		describe "when the user cannot be found", ->
+			beforeEach ->
+				@request.get = sinon.stub().callsArgWith(1, null, {statusCode: 404}, "nothing")
+				@WebApiManager.getUserInfo @user_id, @callback
+
+			it "should return a null value", ->
+				@callback
+					.calledWith(null, null)
+					.should.equal true
+

 	describe "getProjectDetails", ->
 		describe "successfully", ->


adding failing test case for #1182 execution order bug mentioned by @satyr

--- test/classes.coffee
+++ test/classes.coffee
@@ -461,3 +461,10 @@
     prop: nonce
     constructor: ->
   eq nonce, B::prop
+
+test "#1182: execution order needs to be considered as well", ->
+  counter = 0
+  makeFn = (n) -> eq n, ++counter; ->
+  class B extends (makeFn 1)
+    @B: makeFn 2
+    constructor: makeFn 3


Added test for opts.shouldPoll

Added test for opts.shouldPoll

--- test/queries.coffee
+++ test/queries.coffee
@@ -129,6 +129,22 @@
           setTimeout (-> done()), 20

     it 'works if you remove then re-add a document from a query' # Regression.
+
+    it 'does not poll if opts.shouldPoll returns false', (done) -> @create {x:5}, =>
+      opts.shouldPoll = (cName, docName, data, index, query) =>
+        assert.equal cName, @cName
+        assert.equal docName, @docName
+        assert.deepEqual query, {x:5}
+        no
+
+      @collection.queryPoll {'x':5}, opts, (err, emitter) =>
+        throw Error err if err
+
+        @db.query = -> throw Error 'query should not be called'
+        @db.queryDoc = -> throw Error 'queryDoc should not be called'
+
+        @collection.submit @docName, v:1, op:[{p:['x'], na:1}], (err, v) =>
+          done()

   describe 'queryFetch', ->
     it 'query fetch with no results works', (done) ->


Autofocused e-mail when forgot password form is rendered

--- client/accounts.coffee
+++ client/accounts.coffee
@@ -34,8 +34,11 @@

 # Autofocus username when login form is rendered
 Template._loginButtonsLoggedOutPasswordService.rendered = ->
-  console.log "Rendered fired"
   $('#login-username-or-email').focus()
+
+# Autofocus e-mail when forgot password form is rendered
+Template._forgotPasswordForm.rendered = ->
+  $('#forgot-password-email').focus()

 $(document).on 'keyup', (e) ->
   if e.keyCode is 27 # Escape key


Keyboard shortcut for Become Superuser (fix #529)

--- client/keyboard.coffee
+++ client/keyboard.coffee
@@ -2,3 +2,9 @@
   switch e.key
     when 'Escape', 'Esc'
       Modal.hide()  # leave modal if any currently shown
+    when 's', 'S'
+      return if e.target.tagName in ['INPUT', 'TEXTAREA']
+      return if e.ctrlKey or e.altKey or e.metaKey
+      e.preventDefault()
+      e.stopPropagation()
+      Session.set 'super', not Session.get 'super'  # toggle Become Superuser


make `/` start a find

make `/` start a find

--- keymaps/vim-mode.cson
+++ keymaps/vim-mode.cson
@@ -58,3 +58,4 @@
   'ctrl-r': 'core:redo'
   'ctrl-u': 'core:page-up'
   'ctrl-d': 'core:page-down'
+  '/': 'command-panel:find-in-file'


forgot the array check, oops

forgot the array check, oops

--- taglib/treelib.coffee
+++ taglib/treelib.coffee
@@ -35,7 +35,8 @@
         (c) -> a(c, s[c])

     scopy: (s) -> (ps) => # fat arrow
-        r = {}; v = @copy(s, r)
+        r = if @isArray(s) then [] else {}
+        v = @copy(s, r)
         v(c) for c in ps; r

     sclone: (s) ->


Added connect sync method

Added connect sync method

--- lib/cache_sync.coffee
+++ lib/cache_sync.coffee
@@ -53,8 +53,14 @@
   cursor: (query={}) -> return new CacheCursor(query, _.pick(@, ['model_type', 'wrapped_sync_fn']))

   destroy: (query, callback) ->
-    Cache.clear(@model_type.model_name) # TODO: optimize through selective cache clearing
-    @wrapped_sync_fn 'destroy', query, callback
+    @wrapped_sync_fn 'destroy', query, (err) =>
+      Cache.clear(@model_type.model_name) # TODO: optimize through selective cache clearing
+      callback(err)
+
+  connect: (url, callback) ->
+    @wrapped_sync_fn 'connect', url, (err) =>
+      Cache.clear(@model_type.model_name)
+      callback(err)

   cache: -> Cache


Byebye characters.coffee

Byebye characters.coffee

--- lib/characters.coffee
+++ lib/characters.coffee
@@ -1,170 +0,0 @@
-Y = require '../../yjs/lib/y.coffee'
-Y.List = require '../../y-list/lib/y-list.coffee'
-
-
-class Characters
-  constructor: (content, selections) ->
-    @_chars = []
-    if content?
-      @insert 0, content
-
-    if selections?
-      @selections = selections
-
-  _name: "Characters"
-
-  _setModel: (model) ->
-    delete @_chars
-    @_model = model
-
-  _getModel: (Y, Operation) ->
-    if not @_model?
-      console.log "Creating list"
-      model = new Y.List()
-      model._getModel Y, Operation
-      @insert 0, @_chars
-      @_setModel model
-    return @_model
-
-  # Function that creates a character object
-  # @param char [String] the character
-  # @param left [Array<Selection>] an array of selections starting at this char
-  # @param right [Array<Selection>] an array of selections ending at this char
-  # @return [Y.Object] a Y.Object instance
-  createChar: (char, left, right) ->
-    if not left?
-      left = []
-    if not right?
-      right = []
-    obj =
-      char: char
-      left: left
-      right: right
-    return (new Y.Object(obj))
-
-  # Insert content at position
-  #
-  # @param [Integer] position the position where to insert
-  # @param [String] content the content to insert
-  #
-  # @note if the _model hasn't been created, push it in  _chars
-  insert: (position, content) ->
-    console.log "insertion", position
-
-    pusher = (position, char) =>
-      if @_model?
-        @_model.insert position, char
-      else
-        @_chars.splice position, 0, char
-      return position + 1
-
-    if content != null
-      console.log "inserting \"#{content}\""
-      for char in content
-        console.log "here"
-        charObj = @createChar char
-        console.log "there"
-        pusher position, charObj
-        position++
-        console.dir charObj
-
-  # @override val (position)
-  #   get the content at position
-  #   @param position [Integer] the position where to get the value
-  #   @return [Object] character object at position
-  # @override val()
-  #   get all the content as an array
-  #   @return [Array<Object>] an array of all the characters
-  val: (position) ->
-    @_model.val position
-
-  # Get a reference to the position
-  # @param position [Integer] the position to reference
-  ref: (position) ->
-    @_model.ref position
-
-  # Remove content at position
-  # @param position [Integer] the first position where to delete the value
-  # @param length [Integer] the number of characters to remove, defaults to 1
-  # @return char [Object] the deleted character at position
-  delete: (position, length) ->
-    @_model.delete position, length
-
-  # Updates the character value at position with new character
-  # @param position [Integer] the position where to update the value
-  # @param newChar [String] the new character to put at position
-  # @return char [Object] the character at position
-  update: (position, newChar) ->
-    #TODO: update with an update function
-    old = @_model.delete position
-    updated = old.char = newChar
-    @_model.delete position
-    @_model.insert position, updated
-
-  # Bind a selection to a character
-  # @param position [Integer] the position of the character to bind
-  # @param selection [Selection] the selection to bind
-  # @param side [String] the side to bind, either "left" or "right"
-  # @return char [Object] the character at position
-  bindSelection: (position, selection, side) ->
-    if side == "left" or side == "right"
-      char = @val position
-      if not (selection in char.val(side))
-        char.val(side).push selection
-      return char
-
-  # Unbind a selection from a character
-  # @param position [Integer] the position of the character where to unbind
-  # @param selection [Selection] the selection to unbind
-  # @param side [String] the side to unbind, either "left" or "right"
-  # @return char [Y.Object] the character at position
-  unbindSelection: (position, selection, side) ->
-    if side == "left" or side == "right"
-      char = @val position
-      for sel, key in char.val(side)
-        if sel == selection
-          char.val(side).splice key, 1
-      return char
-
-  # Find the index of a character
-  # @param character [Y.Object] a character to find index
-  indexOf: (character) ->
-    for char, index in @_model.val()
-      if char == character
-        return index
-    return -1
-  # Apply a delta and return the new position
-  # @param delta [Object] a delta (see ot-types for more info)
-  # @param position [Integer] start position for the delta, default: 0
-  #
-  # @return [Integer] the position of the cursor after parsing the delta
-  delta: (delta, position = 0) ->
-    if delta?
-
-      arentNull = (el) -> el != null
-      if _.all delta.attributes, arentNull
-        operation = (@get "selections").select
-      else
-        operation = (@get "selections").unselect
-
-      if delta.insert?
-        @insert position, delta.insert
-        from = @val position
-        to = @val (position + delta.insert.length)
-        operation.call (@get "selections"), from, to, delta.attributes
-        return position + delta.insert.length
-
-      else if delta.delete?
-        @delete position, delta.delete
-        return position
-
-      else if delta.retain?
-        retain = parseInt delta.retain
-        from = @val position
-        to = @val (position + retain)
-
-        operation.call (@get "selections"), from, to, delta.attributes
-        return position + retain
-
-if module?
-  module.exports = Characters


Style cleanup :lipstick:

Style cleanup :lipstick:

--- lib/git-bridge.coffee
+++ lib/git-bridge.coffee
@@ -29,10 +29,10 @@
       throw "git status exit: #{code}" unless code is 0
       handler(conflicts)

-    GitBridge.process({
-      command: "git",
-      args: ["status", "--porcelain"],
-      options: { "cwd": baseDir },
+    @process({
+      command: 'git',
+      args: ['status', '--porcelain'],
+      options: { cwd: baseDir },
       stdout: stdoutHandler,
       stderr: stderrHandler,
       exit: exitHandler
@@ -62,7 +62,7 @@
     })

   @checkoutSide: (sideName, path, callback) ->
-    GitBridge.process({
+    @process({
       command: 'git',
       args: ['checkout', "--#{sideName}", path],
       options: { 'cwd': atom.project.path },
@@ -74,7 +74,7 @@
     })

   @add: (path, callback) ->
-    GitBridge.process({
+    @process({
       command: 'git',
       args: ['add', path],
       options: { 'cwd': atom.project.path },


Add text-size-adjust support

Add text-size-adjust support

--- data/prefixes.coffee
+++ data/prefixes.coffee
@@ -210,3 +210,8 @@
   prefix 'text-decoration-line',
          'text-decoration-color',
           browsers: browsers
+
+# Text Size Adjust
+feature require('caniuse-db/features-json/text-size-adjust'), (browsers) ->
+  prefix 'text-size-adjust',
+          browsers: browsers


fix: testcases for select language module

--- tests/integration/components/select-language-test.coffee
+++ tests/integration/components/select-language-test.coffee
@@ -13,4 +13,4 @@

   @render hbs """{{select-language}}"""

-  assert.equal @$().text().trim(), ' English Japanese'
+  assert.equal @$().text().trim().replace(/(\r\n|\n|\r|\t)/gm,""), 'English        日本語'


Fixing regex and scoping for deploy branch

--- scripts/deploy.coffee
+++ scripts/deploy.coffee
@@ -7,7 +7,7 @@
 whitelist = require '../support/whitelist'

 deploy = (msg, failMsg = 'Nope') ->
-  if whitelist.canDeploy(robot, msg.message.user)
+  if whitelist.canDeploy(msg.robot, msg.message.user)
     branch = msg.match[1] || 'master'
     msg.reply "Fetching and deploying #{branch}.  Wish me luck!"
     require('child_process').spawn('./bin/deploy.sh', [branch], detached: true).unref()
@@ -15,7 +15,7 @@
     msg.reply failMsg

 module.exports = (robot) ->
-  robot.respond /deploy ?(\w+)?/i, (msg) ->
+  robot.respond /deploy ?([a-zA-Z-_\&\^\!\#]+)?/i, (msg) ->
     deploy msg

   robot.respond /sudo deploy/i, (msg) ->


adding how is Kostas doing script

adding how is Kostas doing script

--- scripts/kostas.coffee
+++ scripts/kostas.coffee
@@ -34,4 +34,7 @@
     msg.send msg.random chorichoripics

   robot.respond /whose Kostas favorite actress/i, (msg)->
-    msg.send msg.random ranipics+    msg.send msg.random ranipics
+
+  robot.respond /how is Kostas doing in Mykonos/i, (msg)->
+    msg.send "https://github.com/sagasu/offshore-team/raw/master/img/KostasInMykonos.jpg"


clean up indent doc

clean up indent doc

--- lib/operators.coffee
+++ lib/operators.coffee
@@ -241,7 +241,7 @@

   # Public: Marks this as complete and saves the motion.
   #
-  # motion - The motion used to select what to delete.
+  # motion - The motion used to select what to indent.
   #
   # Returns nothing.
   compose: (motion) ->


Group early returns

Group early returns

--- lib/operators.coffee
+++ lib/operators.coffee
@@ -261,11 +261,12 @@
     editor = @editorView.editor
     pos = editor.getCursorBufferPosition()
     currentRowLength = editor.lineLengthForBufferRow(pos.row)
+
     # Do nothing on an empty line
     return unless currentRowLength > 0
-
     # Do nothing if asked to replace more characters than there are on a line
     return unless currentRowLength - pos.column >= count
+
     @undoTransaction =>
       start = editor.getCursorBufferPosition()
       _.times count, =>


global dismiss for modal

global dismiss for modal

--- _source/client/templates/components/modal.coffee
+++ _source/client/templates/components/modal.coffee
@@ -1,6 +1,10 @@
 class Apollos.modal extends Apollos.Component

   @register "Apollos.modal"
+
+  events: -> [
+    "click [data-dismiss]": @.destroy
+  ]

   destroy: ->
     self = @


Add shepherd-active class to body when a tour is active

--- coffee/shepherd.coffee
+++ coffee/shepherd.coffee
@@ -297,11 +297,15 @@
   done: ->
     Shepherd.activeTour = null

+    removeClass document.body, 'shepherd-active'
+
   show: (key=0) ->
     if @currentStep
       @currentStep.hide()

     Shepherd.activeTour = @
+
+    addClass document.body, 'shepherd-active'

     if typeof key is 'string'
       next = @getById key


updates to learning module

updates to learning module

--- scripts/learn.coffee
+++ scripts/learn.coffee
@@ -33,6 +33,14 @@
     'Does not compute.'
   ]

+  affirmativeResponses = [
+    'Ok.',
+    'Got it.',
+    'Alrighty.',
+    'Sweet.',
+    'Roger that.'
+  ]
+
   memory = {}

   loadMemory = () ->
@@ -46,7 +54,64 @@
   getAmbiguousUserText = (users) ->
     "Be more specific, I know #{users.length} people named like that: #{(user.name for user in users).join(", ")}"

-  robot.respond /(who|what) (is|are) @?(\w+)\??$/i, (msg) ->
+
+# --------------------- #
+# ====== SETTING ====== #
+# --------------------- #
+
+  robot.respond /@?(\w+) (is|are) (.*)/i, (msg) ->
+    loadMemory()
+    word = msg.match[1].trim()
+    key = inflection.singularize(word)
+    action = msg.match[2].trim()
+    term = msg.match[3].trim()
+
+    unless word in ['', 'who', 'what', 'where', 'when', 'why']
+      unless term.match(/^not\s+/i)
+        memory.words[key] ?= []
+        wordData = memory.words[key]
+        recall = (m for m in wordData when m.term is term)
+
+        if recall.length
+          msg.send msg.random(alreadyKnowResponses) + ' ' + recall[0].user + ' told me.'
+        else
+          wordData.push {
+            word: word,
+            action: action,
+            term: term,
+            user: msg.message.user.name.toLowerCase()
+          }
+          robot.brain.set('learned', memory)
+          msg.send (msg.random affirmativeResponses) + ' ' + [word, action, term].join(' ')
+
+  robot.respond /@?(\w+) (is|are) not (.*)/i, (msg) ->
+    loadMemory()
+    word = msg.match[1].trim()
+    key = inflection.singularize(word)
+    action = msg.match[2].trim()
+    term = msg.match[3].trim()
+
+    unless word in ['', 'who', 'what', 'where', 'when', 'why']
+      unless term.match(/^not\s+/i)
+        memory.words[key] ?= []
+        wordData = memory.words[key]
+        recall = (m for m in wordData when m.term is term)
+
+        if not recall.length
+          msg.send msg.random alreadyKnowResponses
+        else
+          memory.words[key] = (m for m in wordData when m.term is not term)
+          if not memory.words[key].length
+            delete memory.words[key]
+          robot.brain.set('learned', memory)
+          msg.send (msg.random affirmativeResponses) + ' ' + [word, action, 'not', term].join(' ')
+
+
+# --------------------- #
+# ====== GETTING ====== #
+# --------------------- #
+
+  robot.respond /(who|what) (is|are|do you think about) @?(\w+)\??$/i, (msg) ->
     loadMemory()
     action = msg.match[2].trim()
     word = msg.match[3].trim()
@@ -92,53 +157,6 @@
       else
         msg.send lookup.user

-  robot.respond /@?(\w+) (is|are) (.*)/i, (msg) ->
-    loadMemory()
-    word = msg.match[1].trim()
-    key = inflection.singularize(word)
-    action = msg.match[2].trim()
-    term = msg.match[3].trim()
-
-    unless word in ['', 'who', 'what', 'where', 'when', 'why']
-      unless term.match(/^not\s+/i)
-        memory.words[key] ?= []
-        wordData = memory.words[key]
-        recall = (m for m in wordData when m.term is term)
-
-        if recall.length
-          msg.send msg.random(alreadyKnowResponses) + ' ' + recall[0].user + ' told me.'
-        else
-          wordData.push {
-            word: word,
-            action: action,
-            term: term,
-            user: msg.message.user.name.toLowerCase()
-          }
-          robot.brain.set('learned', memory)
-          msg.send 'thanks for teaching me about ' + word
-
-  robot.respond /@?(\w+) (is|are) not (.*)/i, (msg) ->
-    loadMemory()
-    word = msg.match[1].trim()
-    key = inflection.singularize(word)
-    action = msg.match[2].trim()
-    term = msg.match[3].trim()
-
-    unless word in ['', 'who', 'what', 'where', 'when', 'why']
-      unless term.match(/^not\s+/i)
-        memory.words[key] ?= []
-        wordData = memory.words[key]
-        recall = (m for m in wordData when m.term is term)
-
-        if not recall.length
-          msg.send msg.random alreadyKnowResponses
-        else
-          memory.words[key] = (m for m in wordData when m.term is not term)
-          if not memory.words[key].length
-            delete memory.words[key]
-          robot.brain.set('learned', memory)
-          msg.send 'Ok, ' + [word, action, 'not', term].join(' ')
-
   robot.respond /what( words)? do you know\??/i, (msg) ->
     loadMemory()
     words = (memory.words[m][0].word for m in Object.keys(memory.words) when memory.words[m].length)


error -> errorMessage

error -> errorMessage

Closes #512

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -473,7 +473,7 @@
       errorMessage = errorLines.join('\n')

       # Windows 8 seems to return a 1 with no error output even on success
-      if process.platform is 'win32' and code is 1 and not error
+      if process.platform is 'win32' and code is 1 and not errorMessage
         failed = false

       handleError(errorMessage) if failed


Refactored steam.coffee (rewritten from scratch)

--- scripts/steam.coffee
+++ scripts/steam.coffee
@@ -1,10 +1,10 @@
 # Description:
-#   Steam Web API (Dota 2)
+#   Steam Web API w/ Dota 2
 #
 # Dependencies:
-#   "moment": "^2.6.0"
+#   "moment": "^2.8.1"
 #   "msgpack": "^0.2.3"
-#   "ref": "^0.2.1"
+#   "ref": "^0.3.2"
 #
 # Configuration:
 #   STEAM_API_KEY
@@ -20,15 +20,25 @@
 #
 # Notes:
 #   * Refactor persistence
-
-fs = require 'fs'
-moment = require 'moment'
-msgpack = require 'msgpack'
-require 'ref'
-
+#   * Community name? (Alias)
+
+fs = require "fs"
+moment = require "moment"
+msgpack = require "msgpack"
+require "ref"
+
+#
+# Config
+#
 STEAM_API_KEY = process.env.STEAM_API_KEY
-
-personaStates = [
+STEAM_API_URL = "https://api.steampowered.com"
+DOTA_HEROES_DATA_PATH = "data/heroes.bin"
+DOTA_MAX_RESULTS = 5
+
+#
+# Definitions
+#
+STEAM_USER_STATES = [
     "Offline"
     "Online"
     "Busy"
@@ -37,9 +47,7 @@
     "Looking to trade"
     "Looking to play"
 ]
-
-heroData = []
-lobbies =
+DOTA_LOBBIES =
     "-1": "Invalid"
     0: "Public matchmaking"
     1: "Practice"
@@ -49,7 +57,7 @@
     5: "Team match"
     6: "Solo queue"
     7: "Ranked match"
-towers = [
+DOTA_TOWERS = [
     "Ancient top"
     "Ancient bottom"
     "Bottom tier 3"
@@ -62,148 +70,179 @@
     "Top tier 2"
     "Top tier 1"
 ]
-
-module.exports = (robot) ->
-    if not STEAM_API_KEY?
-        return robot.logger.debug 'Missing STEAM_API_KEY in environment. Please set and try again.'
-
-    fs.readFile 'data/heroes.bin', (err, data) ->
-        return robot.logger.error if err
-        heroData = msgpack.unpack data
-
-    steamData = ->
-        robot.brain.data.steam or= {}
-
-    robot.respond /steam id( me)? (.*)/i, (msg) ->
-        getSteamID msg, msg.match[2], (id) ->
-            msg.match[2] += if msg.match[2].slice(-1) is "s" then "'" else "'s"
-            msg.reply "#{msg.match[2]} Steam ID is: #{id}"
-
-    robot.respond /steam status (.*)/i, (msg) ->
-        summary = (steamID) ->
-            steamRequest msg, "/ISteamUser/GetPlayerSummaries", steamids: steamID, (object) ->
-                player = object.response.players[0]
-                status = if player.communityvisibilitystate is 1 then 'Unavailable (Private)' else personaStates[player.personastate]
-                lastOnline = moment.unix(player.lastlogoff).fromNow()
-                msg.reply "#{msg.match[1]} belongs to #{player.personaname} who is currently #{status} and was last online #{lastOnline}."
-            , 2
-
-        if msg.match[1].match /\d{17}/
-            summary msg.match[1]
-        else
-            getSteamID msg, msg.match[1], (steamID) ->
-                summary steamID
-
-    robot.respond /dota history (.*)/i, (msg) ->
-        [command, value] = msg.match
-
-        params =
-            original: value
-            account_id: value
-            matches_requested: 5
-
-        history = (params, type = 'Steam ID') ->
-            steamRequest msg, "/IDOTA2Match_570/GetMatchHistory", params, (object) ->
-                if object.result.status is 15
-                    msg.reply "The #{type} you have entered (\"#{params.original}\") does not exist or it does not have match history enabled."
-                    return
-                else if object.result.num_results is 0
-                    msg.reply "No game matches were found for the #{type}: #{params.original}."
-                    return
-
-                communityID = getCommunityID params.account_id
-
-                for match in object.result.matches
-                    hero = "N/A"
-                    start = moment.unix(match.start_time).fromNow()
-
-                    for player in match.players
-                        if player.account_id is communityID
-                            hero = getHero(player.hero_id).LocalizedName
-                            break;
-
-                    msg.send "Match ID: #{match.match_id} | Lobby: #{lobbies[match.lobby_type]} | Hero: #{hero} | #{start}"
-
-        if value.match /\d{17}/
-            history params
-        else
-            getSteamID msg, value, (id) ->
-                params.account_id = id
-                history params, 'profile URL'
-
-    robot.respond /dota match (\d+)\s*(.*)?/i, (msg) ->
-        steamRequest msg, "/IDOTA2Match_570/GetMatchDetails", match_id: msg.match[1], (object) ->
-            match = object.result
-            start = moment.unix(match.start_time).fromNow()
-            duration = moment.duration(match.duration, 'seconds').minutes()
-            firstBlood = moment.duration(match.first_blood_time, 'seconds').humanize()
-            victor = if match.radiant_win then "Radiant" else "Dire"
-
-            radiantTowers = getTowers(match.tower_status_radiant).join(', ') or 'None'
-            direTowers = getTowers(match.tower_status_dire).join(', ') or 'None'
-
-            msg.send "Match ID #{match.match_id} is a #{lobbies[match.lobby_type].toLowerCase()} game that took place #{start}. The #{victor} won the game in #{duration} minutes. First blood was drawn #{firstBlood} into the game."
-            msg.send "Radiant towers remaining: #{radiantTowers} | Dire towers remaining: #{direTowers}"
-
-            playerInfo = (communityID) ->
-                for player in match.players
-                    if player.account_id is communityID
-                        faction = if player.player_slot > 4 then 'Dire' else 'Radiant'
-                        msg.reply "#{getHero(player.hero_id).LocalizedName} (Lvl #{player.level}), #{faction} | KDA: #{player.kills}/#{player.deaths}/#{player.assists} | LH: #{player.last_hits} | GPM: #{player.gold_per_min} | XPM: #{player.xp_per_min}"
-                        return
-                msg.reply "The Steam ID you have entered (\"#{msg.match[1]}\") was not found in Match ID #{match.match_id}."
-
-            if msg.match[2]?
-                if msg.match[2].match /\d{17}/
-                    playerInfo getCommunityID(msg.match[2])
-                else
-                    getSteamID msg, msg.match[2], (steamID) ->
-                        playerInfo getCommunityID(steamID)
-
-    getSteamID = (msg, customURL, handler) ->
-        for steamID, user of steamData()
-            return handler steamID if user.url is customURL
-
-        steamRequest msg, "/ISteamUser/ResolveVanityURL", vanityurl: customURL, (object) ->
-            if object.response.success is 42
-                msg.reply "The custom URL you have entered (\"#{customURL}\") does not exist."
-                return
-
-            steamData()[object.response.steamid] or= {}
-            steamData()[object.response.steamid].url = customURL
-
-            handler object.response.steamid
-
-    getCommunityID = (steamID) ->
-        if steamData()[steamID]?.cID?
-            return steamData()[steamID].cID
-
-        # 64 -> 32
-        buffer = new Buffer 8
-        buffer.writeUInt64LE steamID, 0
-        communityID = buffer.readUInt32LE 0
-
-        steamData()[steamID] or= {}
-        steamData()[steamID].cID = communityID
-
-        communityID
-
-getHero = (heroID) ->
-    return hero for hero in heroData.Heroes when hero.Id is heroID
-
-getTowers = (dec) ->
-    for status, tower in "00000000000#{(+dec).toString(2)}".slice(-11).split('')
-        if parseInt(status) then towers[tower] else continue
-
-steamRequest = (msg, endpoint, params = {}, handler, version = 1) ->
+DOTA_HEROES = {}
+
+#
+# Steam API
+#
+GetSteamResult = (msg, endpoint, params = {}, handler, version = 1) ->
     params.key = STEAM_API_KEY

-    msg.http("http://api.steampowered.com#{endpoint}/v#{version}/")
+    # Impl: Try-catch
+    msg.http("#{STEAM_API_URL}/#{endpoint}/v#{version}/")
         .query(params)
         .get() (err, res, body) ->
             if err or res.statusCode isnt 200
                 err = "Bad request (invalid Steam web API key)" if res.statusCode is 400
                 msg.reply "An error occurred while attempting to process your request."
                 return robot.logger.error err
-
-            handler JSON.parse(body)+            handler JSON.parse(body)
+
+#
+# User API
+#
+GetSteamID = (msg, customURL, callback) ->
+    GetSteamResult msg, "ISteamUser/ResolveVanityURL", vanityurl: customURL, (object) ->
+        if object.response.success is 42
+            msg.reply "The custom URL you have entered (\"#{customURL}\") does not exist."
+            return
+        callback object.response.steamid
+
+_GetCommunityID = (steamID) ->
+    # Impl: Try-catch
+    buffer = new Buffer 8
+    buffer.writeUInt64LE steamID, 0
+    communityID = buffer.readUInt32LE 0
+    communityID
+
+GetPlayerSummaries = (msg, genericID, callback) ->
+    summary = (steamID) ->
+        GetSteamResult msg, "ISteamUser/GetPlayerSummaries", steamids: steamID, (object) ->
+            callback object.response.players[0]
+        , 2
+
+    # Steam ID
+    if _IsSteamID genericID
+        summary genericID
+    # Custom URL -> Steam ID
+    else
+        GetSteamID msg, genericID, (steamID) ->
+            summary steamID
+
+_GetStatus = (status = 0, invisible = 0) ->
+    if invisible then "Unavailable (Private)" else STEAM_USER_STATES[status]
+
+_IsSteamID = (steamID) ->
+    steamID.match /\d{17}/
+
+#
+# Dota API
+#
+GetMatchDetails = (msg, matchID, callback) ->
+    GetSteamResult msg, "IDOTA2Match_570/GetMatchDetails", match_id: matchID, (object) ->
+        callback object.result
+
+GetMatchHistory = (msg, genericID, callback) ->
+    history = (steamID, type = "Steam ID") ->
+        params =
+            account_id: steamID
+            matches_requested: DOTA_MAX_RESULTS
+        GetSteamResult msg, "IDOTA2Match_570/GetMatchHistory", params, (object) ->
+            if object.result.status is 15
+                msg.reply "The user has disabled the \"Expose Public Match Data\" option."
+                return
+            else if object.result.num_results is 0
+                msg.reply "No game matches were found for the #{type}: #{genericID}."
+            callback steamID, object.result
+
+    # Steam ID
+    if _IsSteamID genericID
+        history genericID
+    # Custom URL -> Steam ID
+    else
+        GetSteamID msg, genericID, (steamID) ->
+            history steamID, "profile URL"
+
+_GetHero = (heroID) ->
+    return hero for hero in DOTA_HEROES.Heroes when hero.Id is heroID
+
+_GetTowers = (dec) ->
+    for status, tower in "00000000000#{(+dec).toString(2)}".slice(-11).split("")
+        if parseInt(status) then DOTA_TOWERS[tower] else continue
+
+_GetFaction = (position) ->
+    if position > 4 then "Dire" else "Radiant"
+
+_GetPlayer = (communityID, playersPool) ->
+    return player for player in playersPool when player.account_id is communityID
+    false
+
+#
+# Common
+#
+Init = (robot) ->
+    if not STEAM_API_KEY?
+        return robot.logger.debug "Missing STEAM_API_KEY in environment. Please set and try again."
+
+    # Impl: Try-catch
+    fs.readFile DOTA_HEROES_DATA_PATH, (err, data) ->
+        return robot.logger.error if err
+        DOTA_HEROES = msgpack.unpack data
+
+_PossessionModifier = (noun) ->
+    noun += if noun.slice(-1) is "s" then "'" else "'s"
+
+#
+# Hubot commands
+#
+module.exports = (robot) ->
+    Init robot
+
+    # GET 32-bit Steam ID
+    robot.respond /steam id( me)? (.+)/i, (msg) ->
+        customURL = msg.match[2]
+        GetSteamID msg, customURL, (steamID) ->
+            msg.reply "#{_PossessionModifier(customURL)} Steam ID is: #{steamID}"
+
+    # GET Steam profile status
+    robot.respond /steam status (.+)/i, (msg) ->
+        genericID = msg.match[1]
+        GetPlayerSummaries msg, genericID, (player) ->
+            status = _GetStatus player.personastate, player.communityvisibilitystate
+            lastOnline = moment.unix(player.lastlogoff).fromNow()
+            msg.reply "#{genericID} belongs to #{player.personaname} who is currently #{status} and was last online #{lastOnline}."
+
+    # GET Player Dota 2 match history (overview)
+    robot.respond /dota history (.+)/i, (msg) ->
+        genericID = msg.match[1]
+        GetMatchHistory msg, genericID, (steamID, history) ->
+            communityID = _GetCommunityID steamID
+
+            for match in history.matches
+                date = moment.unix(match.start_time).fromNow()
+                target = _GetPlayer communityID, match.players
+                hero = _GetHero(target.hero_id).LocalizedName
+                # W / L TBA
+                msg.send "Match ID: #{match.match_id} | Lobby: #{DOTA_LOBBIES[match.lobby_type]} | Hero: #{hero} | #{date}"
+
+    # GET Player Dota 2 match details
+    robot.respond /dota match (\d+)\s*(.+)?/i, (msg) ->
+        matchID = msg.match[1]
+        genericID = msg.match[2]
+        GetMatchDetails msg, matchID, (match) ->
+            date = moment.unix(match.start_time).fromNow()
+            duration = moment.duration(match.duration, "seconds").minutes()
+            firstBlood = moment.duration(match.first_blood_time, "seconds").humanize()
+            victor = if match.radiant_win then "Radiant" else "Dire"
+            radiantTowers = _GetTowers(match.tower_status_radiant).join(", ") or "None"
+            direTowers = _GetTowers(match.tower_status_dire).join(", ") or "None"
+
+            msg.send "Match ID #{match.match_id} is a #{DOTA_LOBBIES[match.lobby_type].toLowerCase()} game that took place #{date}. The #{victor} won the game in #{duration} minutes. First blood was drawn #{firstBlood} into the game."
+            msg.send "Radiant towers remaining: #{radiantTowers} | Dire towers remaining: #{direTowers}"
+
+            additionalInfo = (steamID, type = "Steam ID") ->
+                communityID = _GetCommunityID steamID
+                target = _GetPlayer communityID, match.players
+                unless target
+                    msg.reply "The #{type} you have entered (\"#{genericID}\") was not found in Match ID #{match.match_id}."
+                    return
+                faction = _GetFaction target.player_slot
+                msg.reply "#{faction} - #{_GetHero(target.hero_id).LocalizedName} (Lvl #{target.level}) | KDA: #{target.kills}/#{target.deaths}/#{target.assists} | LH: #{target.last_hits} | GPM: #{target.gold_per_min} | XPM: #{target.xp_per_min} | HD: #{target.hero_damage} | TD: #{target.tower_damage} | TGE: #{target.gold_per_min*duration}"
+                # Future: Team fight contribution? Overall contribution algo.
+
+            if genericID?
+                if _IsSteamID genericID
+                    additionalInfo steamID
+                else
+                    GetSteamID msg, genericID, (steamID) ->
+                        additionalInfo steamID, "profile URL"


Ignore preempt for mini editor, see #9

Ignore preempt for mini editor, see #9

--- lib/vim-state.coffee
+++ lib/vim-state.coffee
@@ -35,6 +35,8 @@
   # Returns nothing.
   registerInsertIntercept: ->
     @editor.preempt 'textInput', (e) =>
+      return if $(e.currentTarget).hasClass('mini')
+
       if @mode == 'insert'
         true
       else


Add util methods for creating a high res canvas element

--- public/js/utils.coffee
+++ public/js/utils.coffee
@@ -7,3 +7,24 @@
 			b: parseInt(result[3], 16)
 		else
 			null
+
+	# http://stackoverflow.com/questions/15661339/how-do-i-fix-blurry-text-in-my-html5-canvas
+	getPixelRatio: ->
+		ctx = document.createElement("canvas").getContext("2d")
+		dpr = window.devicePixelRatio || 1
+		bsr = ctx.webkitBackingStorePixelRatio ||
+			ctx.mozBackingStorePixelRatio ||
+			ctx.msBackingStorePixelRatio ||
+			ctx.oBackingStorePixelRatio ||
+			ctx.backingStorePixelRatio || 1
+		dpr / bsr
+
+	createHiDPICanvas: (w, h) ->
+		ratio = this.getPixelRatio()
+		can = document.createElement "canvas"
+		can.width = w * ratio
+		can.height = h * ratio
+		can.style.width = w + "px"
+		can.style.height = h + "px"
+		can.getContext("2d").setTransform ratio, 0, 0, ratio, 0, 0
+		can


change size

change size

--- graphics/001-terrain-gen/viewer.coffee
+++ graphics/001-terrain-gen/viewer.coffee
@@ -35,7 +35,7 @@
         context.clearRect(0, 0, 1024, 512)

         [num, len, heights] = data
-        width = 1024 / num
+        width = 512 / num
         for row in [0..num]
             for col in [0..num]
                 pos = row * num + row + col


Changed fullpie labels

Changed fullpie labels

--- widgets/fullpie/fullpie.coffee
+++ widgets/fullpie/fullpie.coffee
@@ -57,7 +57,7 @@
       .attr("text-anchor", "middle").text((d, i) -> data[i].label).attr('font-size', '24px')
       .append('svg:tspan')
       .attr('x', 0)
-      .attr('dy', 15)
+      .attr('dy', 25)
       .attr('font-size', '90%')
       .text((d,i) -> data[i].value + ' (' + Math.round(data[i].value/sum * 100) + '%)')