Closes #3

Closes #3

--- components/Draw.coffee
+++ components/Draw.coffee
@@ -4,12 +4,13 @@
   description: 'Draws received drawing commands'
   icon: 'pencil-square'
   constructor: ->
+    @canvas = null
     @context = null
     @commands = []

     @inPorts =
       tick: new noflo.Port 'number'
-      context: new noflo.Port 'object'
+      canvas: new noflo.Port 'object'
       commands: new noflo.ArrayPort 'object'

     @inPorts.tick.on 'data', (tick) =>
@@ -20,8 +21,9 @@
         loglevel: 'error'
         message: 'Received commands but there\'s any 2d context attached.'

-    @inPorts.context.on 'data', (context) =>
-      @context = context
+    @inPorts.canvas.on 'data', (canvas) =>
+      @canvas = canvas
+      @context = canvas.getContext('2d')

     @inPorts.commands.on 'data', (commands, i) =>
       @commands[i] = commands
@@ -35,9 +37,7 @@
     if rectangle
       @context.clearRect.apply @context, rectangle
     else
-      w = @context.canvas.width
-      h = @context.canvas.height
-      @context.clearRect.apply @context, [0, 0, w, h]
+      @context.clearRect.apply @context, [0, 0, @canvas.width, @canvas.height]

   stroke: (strokableThings) =>
     for thing, arguments of strokableThings


fixed utils test

fixed utils test

--- dw.js/test/utils-test.coffee
+++ dw.js/test/utils-test.coffee
@@ -11,5 +11,8 @@
     .addBatch

         'check utils.purifyHtml':
-            'topic': dw.utils.purifyHtml('Evil<script>alert("foo")</script> data')
-            'removed script': (topic) -> assert.equal topic, 'Evil data'
+            'topic': dw.utils.purifyHtml 'Evil<script>alert("foo")</script> data'
+            'removed script': (topic) ->
+                assert.equal topic, 'Evilalert("foo") data'
+
+    .export module


:lipstick:

:lipstick:

--- src/package.coffee
+++ src/package.coffee
@@ -392,6 +392,7 @@
     incompatibleNativeModules

   # Public: Is this package compatible with this version of Atom?
+  #
   # Incompatible packages cannot be activated. This will include packages
   # installed to ~/.atom/packages that were built against node 0.11.10 but
   # now need to be upgrade to node 0.11.13.


Added: shouldComponentUpdate to MessageItem to prevent wasting time in render

--- client/app/components/message-list.coffee
+++ client/app/components/message-list.coffee
@@ -48,6 +48,7 @@
             @setState selected: selected
             if Object.keys(selected).length is 0
                 @setState allSelected: false, edited: false
+

     render: ->
         compact = @props.settings.get('listStyle') is 'compact'
@@ -472,6 +473,23 @@
     displayName: 'MessagesItem'

     mixins: [RouterMixin]
+
+    shouldComponentUpdate: (nextProps, nextState) ->
+        # we must do the comparison manually because the property "onSelect" is
+        # a function (therefore it should not be compared)
+        shouldUpdate =
+            not _.isEqual(nextState, @state) \
+            or not Immutable.is(nextProps.message, @props.message) \
+            or not Immutable.is(nextProps.settings, @props.settings) \
+            or nextProps.conversationLength isnt @props.conversationLength \
+            or nextProps.key isnt @props.key \
+            or nextProps.isActive isnt @props.isActive \
+            or nextProps.edited isnt @props.edited \
+            or nextProps.selected isnt @props.selected
+
+        return shouldUpdate
+
+

     render: ->
         message = @props.message


client: add channel endpoints under socialapicontroller

--- client/app/lib/socialapicontroller.coffee
+++ client/app/lib/socialapicontroller.coffee
@@ -898,6 +898,85 @@
         return callback null, mapChannels result

+    createChannelWithParticipants: (options, callback) ->
+      { id } = options
+      doXhrRequest
+        type     : 'POST'
+        endPoint : "/api/social/channel/#{id}/update"
+        data     : options
+      , callback
+
+
+    fetchActivities      : (options = {}, callback = kd.noop)->
+
+      # show exempt content if only requester is admin or exempt herself
+      showExempt = checkFlag?("super-admin") or whoami()?.isExempt
+
+      options.showExempt or= showExempt
+
+      err = {message: "An error occurred"}
+
+      endPoint = "/api/social/channel/#{options.id}/history?#{serialize(options)}"
+      doXhrRequest {type: 'GET', endPoint, async: yes}, (err, response) ->
+        return callback err  if err
+
+        return callback null, mapActivities response
+
+    fetchActivitiesWithComments : (options = {}, callback = kd.noop)->
+
+      # show exempt content if only requester is admin or exempt herself
+      showExempt = checkFlag?("super-admin") or whoami()?.isExempt
+
+      options.showExempt or= showExempt
+
+      err = {message: "An error occurred"}
+
+      endPoint = "/api/social/channel/#{options.id}/list?#{serialize(options)}"
+      doXhrRequest {type: 'GET', endPoint, async: yes}, (err, response) ->
+        return callback err  if err
+
+        return callback null, mapActivities response
+
+    fetchPopularPosts: (options, callback) ->
+      { channelName } = options
+      doXhrRequest
+        type     : 'GET'
+        endPoint : "/api/social/popular/posts/#{channelName}?limit=10"
+      , callback
+
+    fetchPopularTopics: (options, callback) ->
+      { type } = options
+      doXhrRequest
+        type     : 'GET'
+        endPoint : "/api/social/popular/topics/#{type}"
+      , callback
+
+    fetchPinnedMessages: (options, callback) ->
+
+      doXhrRequest
+        type     : 'GET'
+        endPoint : "/api/social/activity/pin/list"
+      , callback
+
+    # add if control if options have -> accountId & messageId & groupName
+    pinMessage: (options, callback) ->
+
+      doXhrRequest
+        type     : 'POST'
+        endPoint : "/api/social/activity/pin/add"
+        data     : options
+      , callback
+
+    # add if control if options have -> accountId & messageId & groupName
+    unpinMessage: (options, callback) ->
+
+      doXhrRequest
+        type     : 'POST'
+        endPoint : "/api/social/activity/pin/remove"
+        data     : options
+      , callback
+
+

   channel:
     byId                 : channelRequesterFn


Add clean task

Add clean task

--- Web/gruntfile.coffee
+++ Web/gruntfile.coffee
@@ -1,3 +1,4 @@
+fs = require 'fs'
 module.exports = (grunt) ->
 	grunt.initConfig
 		pkg: grunt.file.readJSON('package.json')
@@ -18,3 +19,12 @@
 	grunt.loadNpmTasks 'grunt-contrib-copy'
 	grunt.loadNpmTasks 'grunt-contrib-concat'
 	grunt.registerTask 'default', ['copy', 'sass']
+	grunt.registerTask 'clean', () ->
+		rmdir = (path) ->
+			return unless fs.existsSync path
+			fs.readdirSync( path ).forEach (file) ->
+				fs.unlinkSync( path + '/' + file )
+			fs.rmdirSync path
+
+		rmdir 'dist'
+		rmdir 'build'


Add a description for each package

Add a description for each package

--- package.cson
+++ package.cson
@@ -1,4 +1,5 @@
-'main': 'lib/markdown-preview'
+'main': './lib/markdown-preview'
+'description': 'Open a rendered version of the Markdown in the current editor with `ctrl-m`.'
 'activationEvents':
   'markdown-preview:show': '.editor'
 'deferredDeserializers': ['MarkdownPreviewView']


remove old unused headers

remove old unused headers

--- packages/rocketchat-cors/cors.coffee
+++ packages/rocketchat-cors/cors.coffee
@@ -29,8 +29,6 @@

 WebApp.rawConnectHandlers.use (req, res, next) ->
 	res.setHeader("Access-Control-Allow-Origin", "*")
-	res.setHeader("X-Rocket-Chat-Version", RocketChat.Info.version)
-	res.setHeader("Access-Control-Expose-Headers",  "X-Rocket-Chat-Version")

 	# Block next handlers to override CORS with value http://meteor.local
 	setHeader = res.setHeader
@@ -44,8 +42,6 @@
 _staticFilesMiddleware = WebAppInternals.staticFilesMiddleware
 WebAppInternals._staticFilesMiddleware = (staticFiles, req, res, next) ->
 	res.setHeader("Access-Control-Allow-Origin", "*")
-	res.setHeader("X-Rocket-Chat-Version", RocketChat.Info.version)
-	res.setHeader("Access-Control-Expose-Headers",  "X-Rocket-Chat-Version")
 	_staticFilesMiddleware(staticFiles, req, res, next)


Flag destroyed Model/Base items with _destroyed.

--- lib/ST/Model/Base.coffee
+++ lib/ST/Model/Base.coffee
@@ -227,6 +227,7 @@
         objectUuid: @_uuid
       }
       @forget()
+      @_destroyed = true

     @classMethod 'convertValueToType', (value, type) ->
       if value is null


[WIP] train bug fix

[WIP] train bug fix

--- src/plugins/faction/train.coffee
+++ src/plugins/faction/train.coffee
@@ -38,10 +38,10 @@
             try
                 reg = new RegExp(match[1])
             catch e
-                FactionUtil.send Bot.getTemplate('train.fail').fillPlayer(player).toString()
+                FactionUtil.send Bot.getTemplate('train.fail').fillPlayer(r.player).toString()
                 return callback()
             storage.rules[match[1]] = match[2]
-            FactionUtil.send Bot.getTemplate('train.ok').fillPlayer(player).toString()
+            FactionUtil.send Bot.getTemplate('train.ok').fillPlayer(r.player).toString()
             storage.save() if not argv.debug
             return callback()

@@ -49,7 +49,7 @@
         match = r.body.match /^train\s+(\S+)$/
         if match
             delete storage.rules[match[1]]
-            FactionUtil.send Bot.getTemplate('train.remove').fillPlayer(player).toString()
+            FactionUtil.send Bot.getTemplate('train.remove').fillPlayer(r.player).toString()
             storage.save() if not argv.debug
             return callback()

@@ -57,7 +57,7 @@
         for rule, response of storage.rules
             reg = new RegExp(rule, 'i')
             if reg.test r.body
-                FactionUtil.send Bot.generateTemplate('@{player} ' + response).fillPlayer(player).fillSmily().toString()
+                FactionUtil.send Bot.generateTemplate('@{player} ' + response).fillPlayer(r.player).fillSmily().toString()
                 return callback()

         callback()


page title

page title

--- js/app.coffee
+++ js/app.coffee
@@ -60,7 +60,7 @@

     @jobs = new CI.outer.Page("jobs", "Work at CircleCI", "View jobs")
     @enterprise = new CI.outer.Page("enterprise", "CircleCI for the enterprise")
-    @stories = new CI.outer.Page("stories", "CircleCI for the enterprise")
+    @stories = new CI.outer.Page("stories", "Shopify + CircleCI Success Story")
     @privacy = new CI.outer.Page("privacy", "Privacy", "View Privacy")
     @changelog = new CI.outer.Changelog("changelog", "ChangeLog", "View ChangeLog")
     # @contact = new CI.outer.Page("contact", "Contact us", "View Contact")


remove is-existing-user from app.coffee

remove is-existing-user from app.coffee

--- js/app.coffee
+++ js/app.coffee
@@ -99,8 +99,6 @@
         !@project().followed() &&
          @project().project_name() is @current_page().project_name

-
-    @is_existing_user = (new CI.ExistingUserHueristic).is_existing_user
     if window.renderContext.current_user
       CI.olark.disable()
       @current_user = ko.observable(new CI.inner.User window.renderContext.current_user)


KDFRAMEWORK: ahem. added defaultItem to KDListViewController

--- client/Framework/components/list/KDListViewController.coffee
+++ client/Framework/components/list/KDListViewController.coffee
@@ -2,19 +2,28 @@

   constructor:(options = {}, data)->

-    options.wrapper             ?= yes
-    options.scrollView          ?= yes
-    options.keyNav              ?= no
-    options.multipleSelection   ?= no
-    options.selection           ?= yes
-    options.startWithLazyLoader ?= no
-    options.itemChildClass     or= null
-    options.itemChildOptions   or= {}
-
-    @itemsOrdered                = [] unless @itemsOrdered
-    @itemsIndexed                = {}
-    @selectedItems               = []
-    @lazyLoader                  = null
+    options.wrapper               ?= yes
+    options.scrollView            ?= yes
+    options.keyNav                ?= no
+    options.multipleSelection     ?= no
+    options.selection             ?= yes
+    options.startWithLazyLoader   ?= no
+    options.itemChildClass        or= null
+    options.itemChildOptions      or= {}
+    options.showDefaultItem       or= no
+    options.defaultItem           or= {}
+    options.defaultItem.itemClass or= KDView
+    options.defaultItem.options   or= {}
+    options.defaultItem.data      or= {}
+
+    defaultOptions = options.defaultItem.options
+    defaultOptions.partial        or=  'This list is empty'
+
+    @itemsOrdered                 = [] unless @itemsOrdered
+    @itemsIndexed                 = {}
+    @selectedItems                = []
+    @lazyLoader                   = null
+

     if options.view
       @setListView listView = options.view
@@ -43,6 +52,18 @@
     listView.on 'ItemIsBeingDestroyed', (itemInfo)=> @unregisterItem itemInfo
     if options.keyNav
       listView.on 'KeyDownOnList', (event)=> @keyDownPerformed listView, event
+
+  addDefaultItem:->
+    {itemClass,options,data} = @getOptions().defaultItem
+    @getListView().addSubView @defaultItem new itemClass options, data
+
+  removeDefaultItem:->
+    @getListView().removeSubView @defaultItem if @defaultItem
+
+  putDefaultItem:(list=[])->
+    if @getOptions().showDefaultItem
+      if list.length is 0 then @addDefaultItem()
+      else @removeDefaultItem()

   loadView:(mainView)->

@@ -63,6 +84,8 @@
     newItems = for itemData in items
       @getListView().addItem itemData

+    @putDefaultItem newItems
+
     @emit "AllItemsAddedToList"

     # Implement no Item found widget support FIXME GG
@@ -112,10 +135,12 @@
   addItem:(itemData, index, animation)->

     @getListView().addItem itemData, index, animation
+    @putDefaultItem @getListView().items

   removeItem:(itemInstance, itemData, index)->

     @getListView().removeItem itemInstance, itemData, index
+    @putDefaultItem @getListView().items
     dataId = itemData.getId?()

   registerItem:(view, index)->


Update follow/unfollow to use new error function, no more require login

--- client/app/Applications/Members.kdapplication/AppView.coffee
+++ client/app/Applications/Members.kdapplication/AppView.coffee
@@ -188,23 +188,28 @@
       states          : [
         title         : "Follow"
         callback      : (callback)->
-          KD.requireMembership
-            callback  : =>
-              @getData().follow (err, response)=>
-                @hideLoader()
-                unless err
-                  @getData().followee = yes
-                  @setClass 'following-btn'
-                  KD.track "Members", "Follow", @getData().profile.nickname
-                  callback? null
-            tryAgain  : yes
-            onFailMsg : 'Login required to follow members'
-            onFail    : => @hideLoader()
+          @getData().follow (err, response)=>
+            @hideLoader()
+
+            KD.showError err,
+              AccessDenied : 'Permission denied to follow members'
+              KodingError  : 'Something went wrong while follow'
+
+            unless err
+              @getData().followee = yes
+              @setClass 'following-btn'
+              KD.track "Members", "Follow", @getData().profile.nickname
+              callback? null
       ,
         title         : "Unfollow"
         callback      : (callback)->
           @getData().unfollow (err, response)=>
             @hideLoader()
+
+            KD.showError err,
+              AccessDenied : 'Permission denied to unfollow members'
+              KodingError  : 'Something went wrong while unfollow'
+
             unless err
               @getData().followee = no
               @unsetClass 'following-btn'


Put node directory in PATH when spawning

This ensures the node used by the spawned process is the one bundled
with Atom.

This should prevent any "env: node: No such file or directory"
errors when spawning a node bin script such as nak.

Refs #514

--- src/stdlib/buffered-process.coffee
+++ src/stdlib/buffered-process.coffee
@@ -1,4 +1,6 @@
 ChildProcess = require 'child_process'
+path = require 'path'
+_ = require 'underscore'

 module.exports =
 class BufferedProcess
@@ -6,6 +8,8 @@
   killed: false

   constructor: ({command, args, options, stdout, stderr, exit}={}) ->
+    options ?= {}
+    @addNodeDirectoryToPath(options)
     @process = ChildProcess.spawn(command, args, options)

     stdoutClosed = true
@@ -36,6 +40,14 @@
         processExited = true
         triggerExitCallback()

+  addNodeDirectoryToPath: (options) ->
+    options.env ?= process.env
+    pathSegments = []
+    nodeDirectoryPath = path.resolve(process.execPath, '..', '..', '..', '..', 'Resources')
+    pathSegments.push(nodeDirectoryPath)
+    pathSegments.push(options.env.PATH) if options.env.PATH
+    options.env = _.extend({}, options.env, PATH: pathSegments.join(path.delimiter))
+
   bufferStream: (stream, onLines, onDone) ->
     stream.setEncoding('utf8')
     buffered = ''


in view animation speed

in view animation speed

--- src/assets/scripts/main.coffee
+++ src/assets/scripts/main.coffee
@@ -10,7 +10,7 @@
           speed: 1700
         inView:
           selector: '.animate-if-in-view'
-          speed:    700
+          speed:    830
           wait: 1200
           offsetTop: 100
           css:


:racehorse Use querySelectorAll to wire outlets

--- src/space-pen.coffee
+++ src/space-pen.coffee
@@ -81,8 +81,8 @@
     postProcessingSteps

   wireOutlets: (view) ->
-    @find('[outlet]').each ->
-      element = $(this)
+    for element in view[0].querySelectorAll('[outlet]')
+      element = $(element)
       outlet = element.attr('outlet')
       view[outlet] = element
       element.attr('outlet', null)


Fix link

Fix link

--- src/space-pen.coffee
+++ src/space-pen.coffee
@@ -440,7 +440,7 @@
     `command` is no longer available. Please subscribe to commands via
     `atom.commands.add`. See the docs at
     https://atom.io/docs/api/latest/CommandRegistry#instance-add
-    Collect the results in a CompositeDisposable https://atom.io/docs/api/v0.150.0/CompositeDisposable
+    Collect the results in a CompositeDisposable https://atom.io/docs/api/latest/CompositeDisposable
   """

 JQueryTrigger = $.fn.trigger


Test only web server directory

Test only web server directory

--- config/main.dev.coffee
+++ config/main.dev.coffee
@@ -1740,7 +1740,7 @@
         #{projectRoot}/scripts/node-testing/mocha-runner "#{projectRoot}/workers/social"

       elif [ "$1" == "nodeservertests" ]; then
-        #{projectRoot}/scripts/node-testing/mocha-runner "#{projectRoot}/servers"
+        #{projectRoot}/scripts/node-testing/mocha-runner "#{projectRoot}/servers/lib/server"

       # To run specific test directory or a single test file
       elif [ "$1" == "nodetestfiles" ]; then


Two more reactive publish functions for publications.

--- server/publication.coffee
+++ server/publication.coffee
@@ -365,19 +365,13 @@

   return unless id

-  # TODO: Make this reactive
-  person = Persons.findOne
-    _id: @personId
-  ,
-    library: 1
-
-  Publications.find
+  publishUsingMyLibrary @, (library) =>
     _id: id
     $or: [
       processed: true
     ,
       _id:
-        $in: _.pluck person?.library, '_id'
+        $in: library
     ]
   ,
     Publication.PUBLIC_FIELDS()
@@ -387,20 +381,14 @@

   return unless ids?.length

-  # TODO: Make this reactive
-  person = Persons.findOne
-    _id: @personId
-  ,
-    library: 1
-
-  Publications.find
+  publishUsingMyLibrary @, (library) =>
     _id:
       $in: ids
     $or: [
       processed: true
     ,
       _id:
-        $in: _.pluck person?.library, '_id'
+        $in: library
     ]
   ,
     Publication.PUBLIC_FIELDS()


Add id's to dropdown tooltips

Add id's to dropdown tooltips

--- browser/table_view.coffee
+++ browser/table_view.coffee
@@ -54,6 +54,7 @@
 		headers.append $("<th>").append dropdown
 		dropdown.multiselect
 			buttonText: do (dim) ->-> label dim
+			buttonTitle: do (dim) ->-> dim.id
 			onChange: do (dim) -> (el, selected) ->
 				field = el.val()
 				if selected
@@ -62,6 +63,7 @@
 					filters[dim.id][field] = false
 				refilter()
 			enableCaseInsensitiveFiltering: true
+		hack_multiselect_tooltips dropdown, dim

 	body = $('<tbody>').appendTo table
@@ -104,3 +106,11 @@

 	load_data 1
+
+hack_multiselect_tooltips = (el, dim) ->
+	container = el.parent()
+	dropdown = container.find(".multiselect-container")
+	dropdown.find('input[type="checkbox"]').each ->
+		val = $(@).val()
+		$(@).parent().parent().parent().prop "title", val
+	container.find("> .btn-group > .button").prop "title", dim.id


fix(service): fix possible internal npe on de-register (also as 913ac74)

Solves #57

--- src/service.coffee
+++ src/service.coffee
@@ -298,8 +298,7 @@
         if wrapped.handlers[address]
           index = wrapped.handlers[address].indexOf(callback)
           wrapped.handlers[address].splice(index, 1) if index > -1
-        if wrapped.handlers[address].length < 1
-          wrapped.handlers[address] = undefined
+          wrapped.handlers[address] = undefined if wrapped.handlers[address].length < 1
         # Remove from real instance
         if connectionState is vertxEventBus.EventBus.OPEN then util.unregisterHandler(address, callback)
       # Stub for util.send


Tidy up.

Tidy up.

--- js/flight.coffee
+++ js/flight.coffee
@@ -90,10 +90,8 @@
   #
   # Returns nothing.
   slide: (targetPage, currentPage) ->
-
     targetPage.style.display = "-webkit-box"
-
-    screenWidth = window.innerWidth +  'px';
+    screenWidth = window.innerWidth + 'px'

     if @back
       @translate(targetPage, "X", "-" + screenWidth, "0ms")


add debugging to streams

add debugging to streams

--- src/storage.coffee
+++ src/storage.coffee
@@ -258,7 +258,6 @@
                 console.error 'error from write stream:', errorRecv, 'uid=' + uid
                 if !callbackCalled
                     callbackCalled = true
-                    rstream.end()
                     console.error 'calling wstream error callback', 'uid=' + uid
                     cb error


Dragging existing images together groups them

--- src/trix/models/composition.coffee
+++ src/trix/models/composition.coffee
@@ -113,6 +113,8 @@
       @insertAttachment(attachment)

   insertFiles: (files) ->
+    position = @getPosition()
+
     headText = new Trix.Text
     tailText = new Trix.Text

@@ -125,12 +127,13 @@
         tailText = tailText.appendText(attachmentText)

     @insertText(headText.appendText(tailText))
-    @groupAdjacentAttachments()
+    @groupAdjacentAttachmentsAtPosition(position)

   insertAttachment: (attachment) ->
+    position = @getPosition()
     text = Trix.Text.textForAttachmentWithAttributes(attachment, @currentAttributes)
     @insertText(text)
-    @groupAdjacentAttachments()
+    @groupAdjacentAttachmentsAtPosition(position)

   deleteInDirection: (direction) ->
     locationRange = @getLocationRange()
@@ -171,6 +174,7 @@
     [position] = @getSelectedRange()
     @setDocument(@document.moveTextFromRangeToPosition(range, position))
     @setSelection(position)
+    @groupAdjacentAttachmentsAtPosition(position)

   removeAttachment: (attachment) ->
     if range = @document.getRangeOfAttachment(attachment)
@@ -444,8 +448,7 @@
       attachment.delegate = this
       @delegate?.compositionDidAddAttachment?(attachment)

-  groupAdjacentAttachments: ->
-    position = @getPosition()
+  groupAdjacentAttachmentsAtPosition: (position) ->
     range = @document.getRangeOfPreviewableAttachmentsAtPosition(position)
     length = range[1] - range[0]
     if length > 1


Add .tiff to image extensions

Add .tiff to image extensions

--- src/stdlib/fs.coffee
+++ src/stdlib/fs.coffee
@@ -161,6 +161,7 @@
       '.jpeg'
       '.jpg'
       '.png'
+      '.tiff'
     ], ext)

   isPdfExtension: (ext) ->


remove console.log

remove console.log

--- src/scripts/components/tile.cjsx
+++ src/scripts/components/tile.cjsx
@@ -3,7 +3,6 @@
 tile = React.createClass

   toggleColor: ->
-    console.log @props.row, @props.column
     @props.onUpdate @props.row, @props.column

   render: ->


Discussion course routes correction

Discussion course routes correction

--- common/static/coffee/src/discussion/discussion_router.coffee
+++ common/static/coffee/src/discussion/discussion_router.coffee
@@ -1,8 +1,11 @@
 if Backbone?
   class @DiscussionRouter extends Backbone.Router
     initialize: (options) ->
-        @route("#{DiscussionUtil.route_prefix}", "allThreads")
-        @route("#{DiscussionUtil.route_prefix}:forum_name/threads/:thread_id", "showThread")
+        @allThreadsRoute = DiscussionUtil.route_prefix
+        @singleThreadRoute = @getSingleThreadRoute(":forum_name", ":thread_id")
+
+        @route(@allThreadsRoute, "allThreads")
+        @route(@singleThreadRoute, "showThread")

         @discussion = options['discussion']
         @course_settings = options['course_settings']
@@ -29,6 +32,12 @@
         @listenTo( @newPostView, 'newPost:cancel', @hideNewPost )
         $('.new-post-btn').bind "click", @showNewPost
         $('.new-post-btn').bind "keydown", (event) => DiscussionUtil.activateOnSpace(event, @showNewPost)
+
+    getSingleThreadRoute: (commentable_id, thread_id) ->
+      base_route = "#{commentable_id}/threads/#{thread_id}"
+      if DiscussionUtil.route_prefix
+        base_route = "#{DiscussionUtil.route_prefix}/#{base_route}"
+      base_route

     allThreads: ->
       @nav.updateSidebar()
@@ -70,10 +79,10 @@

     navigateToThread: (thread_id) =>
       thread = @discussion.get(thread_id)
-      @navigate("#{DiscussionUtil.route_prefix}#{thread.get("commentable_id")}/threads/#{thread_id}", trigger: true)
+      @navigate(@getSingleThreadRoute(thread.get("commentable_id"), thread_id), trigger: true)

     navigateToAllThreads: =>
-      @navigate("#{DiscussionUtil.route_prefix}", trigger: true)
+      @navigate(@allThreadsRoute, trigger: true)

     showNewPost: (event) =>
       $('.forum-content').fadeOut(


Skeleton of "nicer" posts?

Skeleton of "nicer" posts?

--- mixirc/mixirc.coffee
+++ mixirc/mixirc.coffee
@@ -62,6 +62,33 @@
 				console.log "IRC => postComm: ", user, message, channelId,
 					info.mixlrUserLogin, info.mixlrAuthSession
 				postComm message, channelId, info, userAgent
+
+postMix = (user, url, data) ->
+	jar = request.jar()
+	c = request.cookie "mixlr_user_login=#{user.mixlrUserLogin}" +
+		"; mixlr_session=#{user.mixlrAuthSession}"
+	jar.setCookie c, url
+	opts =
+		url: url
+		jar: jar
+	req = request.post opts, (e, resp, body) ->
+		return console.log "postMix: POST failed" if e
+		if resp.statusCode == 200
+			info = JSON.parse body
+			console.log "postMix: #{k}: #{v}" for k, v of info
+		else
+			console.log "postMix: server returned #{resp.statusCode}"
+	if data
+		form = req.form()
+		form.append k, v for k, v of data
+
+xhrComm = (comment, channelId, user) ->
+	postMix user, 'http://mixlr.com/comments',
+		"comment[content]": comment
+		"comment[broadcaster_id]": channelId
+
+xhrCommAddHeart = (commentId, user) ->
+	postMix user, "http://mixlr.com/comments/#{commentId}/heart"

 sendHTTP = (httpHeader, data) ->
 	httpReq = http.request httpHeader, (res) ->


Allow size to be overridden

Allow size to be overridden

--- models/artist.coffee
+++ models/artist.coffee
@@ -48,7 +48,7 @@
   fetchRelatedArtists: (type, options = {}) ->
     @["related#{type}"].fetch _.extend
       remove: false
-      data: _.extend (options.data ? {}),
+      data: _.defaults (options.data ? {}),
         size: 5
         'artist[]': @get 'id'
         'exclude_artists_without_artworks': true


Fixes udefine.env.commonjs not being a boolean

--- src/udefine.coffee
+++ src/udefine.coffee
@@ -1,7 +1,7 @@
 'use strict'

 # Module switch
-hasModule = module? and module.exports
+hasModule = module? and module.exports?
 exportObject = {}

 # Root object hook


webapp/file tabs: this makes the tab icon for files consistent with the actual definition

--- src/smc-webapp/project_page.cjsx
+++ src/smc-webapp/project_page.cjsx
@@ -418,9 +418,11 @@
         return tabs

     file_tab: (path, index) ->
-        ext = misc.filename_extension(path).toLowerCase()
-        icon = file_associations[ext]?.icon ? 'code-o'
-        display_name = misc.trunc(misc.path_split(path).tail, 64)
+        filename         = misc.path_split(path).tail
+        # get the file_associations[ext] just like it is defined in the editor
+        {file_options}   = require('./editor')
+        icon             = file_options(filename)?.icon ? 'code-o'
+        display_name     = misc.trunc(filename, 64)
         <SortableFileTab
             index        = {index}
             key          = {path}
@@ -543,9 +545,11 @@
         @actions(project_id:@props.project_id).close_tab(path)

     file_menu_item: (path, index) ->
-        ext = misc.filename_extension(path).toLowerCase()
-        icon = file_associations[ext]?.icon ? 'code-o'
-        display_name = misc.trunc(misc.path_split(path).tail, 64)
+        filename        = misc.path_split(path).tail
+        # get the file_associations[ext] just like it is defined in the editor
+        {file_options}  = require('./editor')
+        icon            = file_options(filename)?.icon ? 'code-o'
+        display_name    = misc.trunc(filename, 64)

         label_styles =
             whiteSpace   : 'nowrap'


App: remove profile links for teams

App: remove profile links for teams

--- client/app/lib/routehandler.coffee
+++ client/app/lib/routehandler.coffee
@@ -7,6 +7,7 @@

 lazyrouter     = require './lazyrouter'
 registerRoutes = require './util/registerRoutes'
+isKoding       = require './util/isKoding'

 getAction = (formName) -> switch formName
@@ -41,6 +42,10 @@
   ({params:{name, slug}, query}, models, route)->

     {router} = kd.singletons
+
+    # don't render profile pages on team contexts.
+    return router.handleNotFound()  unless isKoding()
+
     route = name unless route

     if models?


* minor changes

* minor changes

--- src/type-info.coffee
+++ src/type-info.coffee
@@ -14,15 +14,26 @@
   initialize: (aOptions)->
     @encoding = aOptions.encoding if aOptions and aOptions.encoding
   path: ->
-    result = getProtoChain(@constructor)
+    result = getProtoChain(@Class)
     result.push Type.ROOT_NAME
     result.reverse()
-    #result.push(@name)
     result
-  encode: (aValue)->
-  decode: (aString)->
-  # create a TypeInfo class from the json string.
-  @createFromJson: (aString)->
-  toJson: ->
+  encode: (aValue, aCheckValidity, aOptions)->
+    throw new TypeError(aValue + ' is not a valid ' + @name) if aCheckValidity isnt false and not @validate(aValue)
+    aValue = @encoding.encode aValue if @encoding
+    aValue = @_encode aValue if @_encode
+    aValue
+  decode: (aString, aCheckValidity, aOptions)->
+    aString = @encoding.decode aString if @encoding
+    aString = @_decode aString, aCheckValidity if @_decode
+    aString
+  validate: (aValue)->true
+  # Get a Type class from the json string.
+  @fromJson: (aString)->
+    Type JSON.parse aString
+  toString: ->
     result = JSON.stringify @
+    result.name = @name
+    result.fullName = @path()
+    result


marker fix

marker fix

--- js/utils.coffee
+++ js/utils.coffee
@@ -11,7 +11,7 @@

 utils.poiStyleMap = new OpenLayers.StyleMap({
 							"default":new OpenLayers.Style(OpenLayers.Util.applyDefaults({
-              externalGraphic:"/img/taukkis-marker.png"
+              externalGraphic:"img/taukkis-marker.png"
               graphicOpacity:1
               pointRadius: 14
               }


Course is an argument, don't need to look it up

--- src/concept-coach/base.cjsx
+++ src/concept-coach/base.cjsx
@@ -150,8 +150,6 @@

   childComponent: (course) ->
     {view} = @state
-    course = User.getCourse(@props.collectionUUID)
-
     switch view
       when 'loading'
          Loading ...


Remove some testing tools

Remove some testing tools

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -492,6 +492,7 @@

         clear_query: =>
             #TODO remove when not testing
+            ###
             welcome = @r.table('Welcome-rdb')
             welcome.insert({
                 id: generate_id(25)
@@ -505,8 +506,9 @@
                     mobile: generate_number(10)+''+generate_number(10)+''+generate_number(10)+'-'+generate_number(10)+''+generate_number(10)+''+generate_number(10)+''+generate_number(10)+'-'+generate_number(10)+''+generate_number(10)+''+generate_number(10)+''+generate_number(10)
                 website: 'http://www.'+generate_string(12)+'.com'
                 }).run()
-
-            #@.$('.input_query').val ''
+            ###
+
+            @.$('.input_query').val ''
             @.$('.input_query').focus()

@@ -528,9 +530,6 @@
             window.conn = new rethinkdb.net.HttpConnection 'http://newton:35457/', ''
             window.r = rethinkdb.query

-
-            # TODO Remove when not testing
-            @id = 0

             #TODO Make this little thing prettier
             @unsafe_to_safe_regexstr = []
@@ -1083,8 +1082,8 @@

         #TODO Fix it for Firefox
         expand_raw_textarea: =>
-            setTimeout(@test, 0) #TODO remove this trick when we will remove bootstrap's tab
-        test: =>
+            setTimeout(@bootstrap_hack, 0) #TODO remove this trick when we will remove bootstrap's tab
+        bootstrap_hack: =>
             @expand_textarea 'raw_view_textarea'
             return @


Fix full view for dataexplorer

Fix full view for dataexplorer

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -767,24 +767,29 @@
         toggle_size: =>
             if @displaying_full_view
                 @display_normal()
+                $(window).unbind 'resize', @display_full
                 @displaying_full_view = false
             else
                 @display_full()
+                $(window).bind 'resize', @display_full
                 @displaying_full_view = true

         display_normal: =>
             $('.main-container').width '940'
             $('#cluster').width 700
-            $('.input_query').width 678
+            @codemirror.setSize 698, 100
+            #$('.input_query').width 678
             $('.dataexplorer_container').removeClass 'full_container'
             $('.dataexplorer_container').css 'margin', '0px'
             $('.change_size').val 'Full view'

         display_full: =>
             width = $(window).width() - 220 -40
+
             $('.main-container').width '100%'
             $('#cluster').width width
-            $('.input_query').width width-45
+            @codemirror.setSize width-22, 100
+            #$('.input_query').width width-45
             $('.dataexplorer_container').addClass 'full_container'
             $('.dataexplorer_container').css 'margin', '0px 0px 0px 20px'
             $('.change_size').val 'Smaller view'


One shot view::ready.

One shot view::ready.

--- src/view/view.coffee
+++ src/view/view.coffee
@@ -177,6 +177,8 @@
         @addToParentNode(@parentNode)

       return node
+
+  @::event('ready').oneShot = true

   initializeBindings: ->
     return if @isBound


Refactored ComponentLoader `load` method to allow extensibility in component instantiation.

--- src/lib/ComponentLoader.coffee
+++ src/lib/ComponentLoader.coffee
@@ -108,26 +108,46 @@
           @loadGraph name, component, callback, delayed, metadata
         , 0
       return
-    if typeof component is 'function'
-      implementation = component
-      if component.getComponent and typeof component.getComponent is 'function'
-        instance = component.getComponent metadata
-      else
-        instance = component metadata
-    # Direct component instance, return as is
-    else if typeof component is 'object' and typeof component.getComponent is 'function'
-      instance = component.getComponent metadata
-    else
-      implementation = require component
-      if implementation.getComponent and typeof implementation.getComponent is 'function'
-        instance = implementation.getComponent metadata
-      else
-        if typeof implementation isnt 'function'
-          throw new Error "Component #{name} is not loadable"
-        instance = implementation metadata
+
+    instance = @createComponent name, component, metadata
+
+    if not instance
+      throw new Error "Component #{name} could not be loaded."
+
     instance.baseDir = @baseDir if name is 'Graph'
     @setIcon name, instance
     callback instance
+
+  # Creates an instance of a component.
+  createComponent: (name, component, metadata) ->
+
+    implementation = component
+
+    # If a string was specified, attempt to `require` it.
+    if typeof implementation is 'string'
+      implementation = require implementation
+
+    # Attempt to create the component instance using the `getComponent` method.
+    instance = @createComponentWithGetComponent name, implementation, metadata
+
+    # A function factory might have been specified or returned from the
+    # require call.
+    if typeof instance isnt 'object'
+      implementation = instance or implementation
+      instance = @createComponentFromFactory name, implementation, metadata
+
+    return instance;
+
+  # Attempts to create a component instance using a `getComponent` function on the
+  # `implementation` object specified.
+  createComponentWithGetComponent: (name, implementation, metadata) ->
+    if implementation.getComponent and typeof implementation.getComponent is 'function'
+      return implementation.getComponent metadata
+
+  # Attempts to create a component using a factory function.
+  createComponentFromFactory: (name, fn, metadata) ->
+    if typeof fn is 'function'
+      return fn metadata

   isGraph: (cPath) ->
     return true if typeof cPath is 'object' and cPath instanceof nofloGraph.Graph


Turn into a function

Turn into a function

--- components/RuntimeToProject.coffee
+++ components/RuntimeToProject.coffee
@@ -1,5 +1,10 @@
 noflo = require 'noflo'
 uuid = require 'uuid'
+
+isComponentInProject = (namespace, component) ->
+  return true if componentName.indexOf('/') is -1
+  [library, component] = componentName.split '/'
+  return library is namespace

 fetchSources = (components, runtime, sources, callback) ->
   return callback null, sources unless components.length
@@ -24,9 +29,7 @@
   return callback null, [] unless runtime.definition?.namespace
   return callback null, [] unless runtime.definition.components
   components = Object.keys(runtime.definition.components).filter (componentName) ->
-    return true if componentName.indexOf('/') is -1
-    [library, component] = componentName.split '/'
-    return library is runtime.definition.namespace
+    isComponentInProject runtime.definition.namespace, componentName
   fetchSources components, runtime, [], callback

 exports.getComponent = ->


fix commitClassification(): do nothing if classification undefined

--- app/assets/javascripts/lib/workflow-methods-mixin.cjsx
+++ app/assets/javascripts/lib/workflow-methods-mixin.cjsx
@@ -40,6 +40,7 @@
           callback() if callback?

   commitClassification: (classification) ->
+    return unless classification?
     # Commit classification to backend
     classification.commit (classification) =>
       # Did this generate a child_subject? Update local copy:


fix #5 close #5

fix #5 close #5

--- app/assets/javascripts/qiniu_direct_uploader.js.coffee
+++ app/assets/javascripts/qiniu_direct_uploader.js.coffee
@@ -93,7 +93,7 @@
         $uploadForm.trigger("qiniu_upload_complete", [postData])

         currentFiles.splice($.inArray(data, currentFiles), 1) # remove that element from the array
-        $uploadForm.trigger("qiniu_upload_complete", [postData]) unless currentFiles.length
+        $uploadForm.trigger("qiniu_uploads_complete", [postData]) unless currentFiles.length

       fail: (e, data) ->
         content = buildCallbackData $uploadForm, data.files[0], data.result


Use arrays instead of while loops

Use arrays instead of while loops

--- src/text-editor-presenter.coffee
+++ src/text-editor-presenter.coffee
@@ -381,8 +381,8 @@
       gutterTile.display = "block"
       gutterTile.zIndex = zIndex

-      @updateLinesState(tile, tileStartRow, tileEndRow) if @shouldUpdateLinesState
-      @updateLineNumbersState(gutterTile, tileStartRow, tileEndRow) if @shouldUpdateLineNumbersState
+      @updateLinesState(tile, rowsWithinTile) if @shouldUpdateLinesState
+      @updateLineNumbersState(gutterTile, rowsWithinTile) if @shouldUpdateLineNumbersState

       visibleTiles[tileStartRow] = true
       zIndex++
@@ -401,23 +401,22 @@
       delete @state.content.tiles[id]
       delete @lineNumberGutter.tiles[id]

-  updateLinesState: (tileState, startRow, endRow) ->
+  updateLinesState: (tileState, screenRows) ->
     tileState.lines ?= {}
     visibleLineIds = {}
-    row = startRow
-    while row < endRow
-      line = @model.tokenizedLineForScreenRow(row)
+    for screenRow in screenRows
+      line = @model.tokenizedLineForScreenRow(screenRow)
       unless line?
         throw new Error("No line exists for row #{row}. Last screen row: #{@model.getLastScreenRow()}")

       visibleLineIds[line.id] = true
       if tileState.lines.hasOwnProperty(line.id)
         lineState = tileState.lines[line.id]
-        lineState.screenRow = row
-        lineState.decorationClasses = @lineDecorationClassesForRow(row)
+        lineState.screenRow = screenRow
+        lineState.decorationClasses = @lineDecorationClassesForRow(screenRow)
       else
         tileState.lines[line.id] =
-          screenRow: row
+          screenRow: screenRow
           text: line.text
           openScopes: line.openScopes
           tags: line.tags
@@ -430,8 +429,7 @@
           indentLevel: line.indentLevel
           tabLength: line.tabLength
           fold: line.fold
-          decorationClasses: @lineDecorationClassesForRow(row)
-      row++
+          decorationClasses: @lineDecorationClassesForRow(screenRow)

     for id, line of tileState.lines
       delete tileState.lines[id] unless visibleLineIds.hasOwnProperty(id)
@@ -603,32 +601,24 @@
       isVisible = isVisible and @showLineNumbers
     isVisible

-  updateLineNumbersState: (tileState, startRow, endRow) ->
+  isSoftWrappedRow: (bufferRow, screenRow) ->
+    return false if screenRow is 0
+
+    @model.bufferRowForScreenRow(screenRow - 1) is bufferRow
+
+  updateLineNumbersState: (tileState, screenRows) ->
     tileState.lineNumbers ?= {}
     visibleLineNumberIds = {}

-    if startRow > 0
-      rowBeforeStartRow = startRow - 1
-      lastBufferRow = @model.bufferRowForScreenRow(rowBeforeStartRow)
-    else
-      lastBufferRow = null
-
-    if endRow > startRow
-      bufferRows = @model.bufferRowsForScreenRows(startRow, endRow - 1)
-      for bufferRow, i in bufferRows
-        if bufferRow is lastBufferRow
-          softWrapped = true
-        else
-          lastBufferRow = bufferRow
-          softWrapped = false
-
-        screenRow = startRow + i
-        decorationClasses = @lineNumberDecorationClassesForRow(screenRow)
-        foldable = @model.isFoldableAtScreenRow(screenRow)
-        id = @model.tokenizedLineForScreenRow(screenRow).id
-
-        tileState.lineNumbers[id] = {screenRow, bufferRow, softWrapped, top, decorationClasses, foldable}
-        visibleLineNumberIds[id] = true
+    for screenRow in screenRows
+      bufferRow = @model.bufferRowForScreenRow(screenRow)
+      softWrapped = @isSoftWrappedRow(bufferRow, screenRow)
+      decorationClasses = @lineNumberDecorationClassesForRow(screenRow)
+      foldable = @model.isFoldableAtScreenRow(screenRow)
+      id = @model.tokenizedLineForScreenRow(screenRow).id
+
+      tileState.lineNumbers[id] = {screenRow, bufferRow, softWrapped, decorationClasses, foldable}
+      visibleLineNumberIds[id] = true

     for id of tileState.lineNumbers
       delete tileState.lineNumbers[id] unless visibleLineNumberIds[id]


Use associated partner

Use associated partner

--- apps/artwork/components/partner_locations/index.coffee
+++ apps/artwork/components/partner_locations/index.coffee
@@ -9,7 +9,7 @@
   constructor: (options = {}) ->
     { @$el, @artwork } = options
     return unless @artwork.has 'partner'
-    @partner = new Partner(@artwork.get 'partner')
+    @partner = @artwork.related().partner
     @locations = @partner.locations()
     @locations.fetchUntilEnd success: =>
       @renderLocations @locations


Make sections array a constructor property

--- src/trix/debug_controller.coffee
+++ src/trix/debug_controller.coffee
@@ -1,4 +1,6 @@
 class Trix.DebugController
+  @sections = "PositionOrRange Selections TextRuns".split(" ")
+
   constructor: (@element, @textController) ->

   render: ->
@@ -7,7 +9,7 @@

   renderDebugOutput: ->
     strings = []
-    for section in ["PositionOrRange", "Selections", "TextRuns"]
+    for section in @constructor.sections
       if result = @["render#{section}"].call(this)
         strings.push(result)
     strings.join("\n\n")


Move compact down

Move compact down

--- lib/hint.coffee
+++ lib/hint.coffee
@@ -43,8 +43,9 @@
     console.log "jshint didn't pass but returned no errors"
     []
   else
-    jshint.errors = _.compact(jshint.errors)   # last jshint.errors item could be null if it bailed because too many errors
     _.chain(jshint.errors)
+      # last jshint.errors item could be null if it bailed because too many errors
+      .compact(jshint.errors)
       # Convert errors to use coffee source locations instead of js locations
       .map((error) ->
         [line, col] = sourceMap.sourceLocation [error.line - 1, error.character - 1]


mobile drawer - ignore links - 2

mobile drawer - ignore links - 2

--- _assets/javascripts/shared/shared.coffee
+++ _assets/javascripts/shared/shared.coffee
@@ -64,8 +64,9 @@

   mobileDrawerCategoryWatch: ->
     $(".category.main h2").click (e) ->
-      e.preventDefault()
-
+      if $(window).width() < 720
+        e.preventDefault()
+
       $category = $(this).parents(".category")
       if $category.hasClass("expanded")
         $category.removeClass("expanded")


update test

update test

--- test/canary-spec.coffee
+++ test/canary-spec.coffee
@@ -94,98 +94,97 @@
       it 'should be in a passing state', ->
         expect(@sut.canary.getPassing().passing).to.equal true

-    describe 'when one of the flows hasn\'t been messaged in awhile', ->
-      before ->
-        @resetFlowTime 'flow-a', 0
+      describe 'when one of the flows hasn\'t been messaged in awhile', ->
+        before ->
+          @resetFlowTime 'flow-a', 0

-      it 'should be in a failing state', ->
-        expect(@sut.canary.getPassing().passing).to.equal false
+        it 'should be in a failing state', ->
+          expect(@sut.canary.getPassing().passing).to.equal false

-      describe 'and we message them a bunch', ->
-        before ->
-          messageCanary = =>
-            @DateMock.inc @CANARY_UPDATE_INTERVAL
-            @sut.postMessage {body:fromUuid:'flow-a'}, {end:=>}
-            @sut.postMessage {body:fromUuid:'flow-b'}, {end:=>}
-            @sut.postMessage {body:fromUuid:'flow-c'}, {end:=>}
-          _.times 20, messageCanary
+        describe 'and we message them a bunch', ->
+          before ->
+            messageCanary = =>
+              @DateMock.inc @CANARY_UPDATE_INTERVAL
+              @sut.postMessage {body:fromUuid:'flow-a'}, {end:=>}
+              @sut.postMessage {body:fromUuid:'flow-b'}, {end:=>}
+              @sut.postMessage {body:fromUuid:'flow-c'}, {end:=>}
+            _.times 20, messageCanary

-        it 'should be in a passing state', ->
-          # console.log JSON.stringify @sut.canary.getCurrentStats(), null, 2
-          expect(@sut.canary.getPassing().passing).to.equal true
+          it 'should be in a passing state', ->
+            # console.log JSON.stringify @sut.canary.getCurrentStats(), null, 2
+            expect(@sut.canary.getPassing().passing).to.equal true

-    describe 'when postTriggers is called', ->
-      before (done) ->
-        @triggerAPost = @triggerHost.post('/flows/flow-a/triggers/trigger-flow-a').reply(201)
-        @triggerBPost = @triggerHost.post('/flows/flow-b/triggers/trigger-flow-b').reply(201)
-        @sut.canary.postTriggers done
-
-      it 'should have posted to both triggers', ->
-        expect(@triggerAPost.isDone).to.be.true
-        expect(@triggerBPost.isDone).to.be.true
-
-    describe 'when one of the other flows hasn\'t been messaged in awhile', ->
-      before ->
-        @resetFlowTime 'flow-c', 0
-
-      it 'should be in a failing state', ->
-        expect(@sut.canary.getPassing().passing).to.equal false
-
-      describe 'when processUpdateInterval is called', ->
+      describe 'when postTriggers is called', ->
         before (done) ->
-          @getFlows = @apiHost.get('/api/flows').reply(200, @flows)
-          @startFlowC = @apiHost.post('/api/flows/flow-c/instance').reply(201)
           @triggerAPost = @triggerHost.post('/flows/flow-a/triggers/trigger-flow-a').reply(201)
           @triggerBPost = @triggerHost.post('/flows/flow-b/triggers/trigger-flow-b').reply(201)
+          @sut.canary.postTriggers done
+
+        it 'should have posted to both triggers', ->
+          expect(@triggerAPost.isDone).to.be.true
+          expect(@triggerBPost.isDone).to.be.true
+
+      describe 'when one of the other flows hasn\'t been messaged in awhile', ->
+        before ->
+          @resetFlowTime 'flow-c', 0
+
+        it 'should be in a failing state', ->
+          expect(@sut.canary.getPassing().passing).to.equal false
+
+        describe 'when processUpdateInterval is called', ->
+          before (done) ->
+            @getFlows = @apiHost.get('/api/flows').reply(200, @flows)
+            @startFlowC = @apiHost.post('/api/flows/flow-c/instance').reply(201)
+            @triggerAPost = @triggerHost.post('/flows/flow-a/triggers/trigger-flow-a').reply(201)
+            @triggerBPost = @triggerHost.post('/flows/flow-b/triggers/trigger-flow-b').reply(201)
+            @sut.canary.processUpdateInterval done
+
+          it 'should have fetched the flows, restarted the failed flow, and posted to triggers', ->
+            expect(@getFlows.isDone).to.be.true
+            expect(@startFlowC.isDone).to.be.true
+            expect(@triggerAPost.isDone).to.be.true
+            expect(@triggerBPost.isDone).to.be.true
+
+          it 'should have no errors in stats', ->
+            # console.log JSON.stringify @sut.canary.getCurrentStats(), null, 2
+            expect(@sut.canary.getCurrentStats().errors).to.not.exist
+
+      describe 'when processUpdateInterval and everything errors', ->
+        before (done) ->
+          @resetFlowTime 'flow-c', 0
+          @getFlows = @apiHost.get('/api/flows').reply(401, @flows)
+          @startFlowC = @apiHost.post('/api/flows/flow-c/instance').reply(401)
+          @triggerAPost = @triggerHost.post('/flows/flow-a/triggers/trigger-flow-a').reply(401)
+          @triggerBPost = @triggerHost.post('/flows/flow-b/triggers/trigger-flow-b').reply(401)
           @sut.canary.processUpdateInterval done

-        it 'should have fetched the flows, restarted the failed flow, and posted to triggers', ->
+        it 'should have tried to fetch the flows, restart the failed flow, and post to triggers', ->
           expect(@getFlows.isDone).to.be.true
           expect(@startFlowC.isDone).to.be.true
           expect(@triggerAPost.isDone).to.be.true
           expect(@triggerBPost.isDone).to.be.true

-        it 'should have no errors in stats', ->
+        it 'should have errors in stats', ->
           # console.log JSON.stringify @sut.canary.getCurrentStats(), null, 2
-          expect(@sut.canary.getCurrentStats().errors).to.not.exist
+          expect(@sut.canary.getCurrentStats().errors?.length).to.equal 4

-    describe 'when processUpdateInterval and everything errors', ->
-      before (done) ->
-        @resetFlowTime 'flow-c', 0
-        delete @sut.canary.stats.errors
-        @getFlows = @apiHost.get('/api/flows').reply(401, @flows)
-        @startFlowC = @apiHost.post('/api/flows/flow-c/instance').reply(401)
-        @triggerAPost = @triggerHost.post('/flows/flow-a/triggers/trigger-flow-a').reply(401)
-        @triggerBPost = @triggerHost.post('/flows/flow-b/triggers/trigger-flow-b').reply(401)
-        @sut.canary.processUpdateInterval done
+      describe 'when one of the flows is messaged too often', ->
+        before ->
+          @resetFlowTime 'flow-a', @DateMock.now()
+          @resetFlowTime 'flow-b', @DateMock.now()
+          @resetFlowTime 'flow-c', @DateMock.now()
+          delete @sut.canary.stats.errors

-      it 'should have tried to fetch the flows, restart the failed flow, and post to triggers', ->
-        expect(@getFlows.isDone).to.be.true
-        expect(@startFlowC.isDone).to.be.true
-        expect(@triggerAPost.isDone).to.be.true
-        expect(@triggerBPost.isDone).to.be.true
+        it 'should initialy be in a passing state', ->
+          expect(@sut.canary.getPassing().passing).to.equal true

-      it 'should have errors in stats', ->
-        # console.log JSON.stringify @sut.canary.getCurrentStats(), null, 2
-        expect(@sut.canary.getCurrentStats().errors?.length).to.equal 4
+        describe 'and we message a flow once', ->
+          before ->
+            messageCanary = =>
+              @DateMock.inc @CANARY_UPDATE_INTERVAL - (@CANARY_HEALTH_CHECK_MAX_DIFF*1.1)
+              @sut.postMessage {body:fromUuid:'flow-a'}, {end:=>}
+            _.times 1, messageCanary

-    describe 'when one of the flows is messaged too often', ->
-      before ->
-        @resetFlowTime 'flow-a', @DateMock.now()
-        @resetFlowTime 'flow-b', @DateMock.now()
-        @resetFlowTime 'flow-c', @DateMock.now()
-        delete @sut.canary.stats.errors
-
-      it 'should initialy be in a passing state', ->
-        expect(@sut.canary.getPassing().passing).to.equal true
-
-      describe 'and we message a flow once', ->
-        before ->
-          messageCanary = =>
-            @DateMock.inc @CANARY_UPDATE_INTERVAL - (@CANARY_HEALTH_CHECK_MAX_DIFF*1.1)
-            @sut.postMessage {body:fromUuid:'flow-a'}, {end:=>}
-          _.times 1, messageCanary
-
-        it 'should be in a failing state', ->
-          # console.log JSON.stringify @sut.canary.getCurrentStats(), null, 2
-          expect(@sut.canary.getPassing().passing).to.equal false
+          it 'should be in a failing state', ->
+            # console.log JSON.stringify @sut.canary.getCurrentStats(), null, 2
+            expect(@sut.canary.getPassing().passing).to.equal false


when user claims admin, send them to home page after, just like in the other cases

--- client/admin_login.coffee
+++ client/admin_login.coffee
@@ -32,8 +32,9 @@
     Meteor.logout()

   'click .become-houston-admin': (e) ->
+    e.preventDefault()
     Houston._call('make_admin', Meteor.userId())
-    e.preventDefault()
+    Houston._go 'home'
 )

 Template._houston_login.rendered = ->


全消しでハングするバグを修正

全消しでハングするバグを修正

--- client/coffee/game.coffee
+++ client/coffee/game.coffee
@@ -81,6 +81,8 @@

       for r, cs of _.groupBy(pieces, (p) -> p.col)
         {true:dels, false:lefs} = _.groupBy(cs, (p) -> p.del)
+        dels = dels ? []
+        lefs = lefs ? []

         for p, i in _.sortBy(lefs, (p) -> -p.row)
           p.row = (4 - i)


#338 add stopPropagation

#338 add stopPropagation

--- lib/main.coffee
+++ lib/main.coffee
@@ -171,11 +171,17 @@
         commands["set-input-char-#{char}"] = -> @setInputChar(char)

     getEditorState = @getEditorState.bind(this)
-    for name, fn of commands
-      do (fn) =>
-        commandName = "vim-mode-plus:#{name}"
-        @subscribe atom.commands.add 'atom-text-editor:not([mini])', commandName, ->
-          fn.call(getEditorState(@getModel()))
+
+    bindToVimState = (oldCommands) ->
+      newCommands = {}
+      for name, fn of oldCommands
+        do (fn) ->
+          newCommands["vim-mode-plus:#{name}"] = (event) ->
+            event.stopPropagation()
+            fn.call(getEditorState(@getModel()), event)
+      newCommands
+
+    @subscribe atom.commands.add('atom-text-editor:not([mini])', bindToVimState(commands))

   consumeStatusBar: (statusBar) ->
     @statusBarManager.initialize(statusBar)


Fixed the F401 error appearing at the end of the file instead of at the correct line.

Fixes #148.

--- lib/main.coffee
+++ lib/main.coffee
@@ -1,4 +1,5 @@
-tokenizedLineForRow = (textEditor, lineNumber) -> textEditor.displayBuffer.tokenizedBuffer.tokenizedLineForRow(lineNumber)
+tokenizedLineForRow = (textEditor, lineNumber) ->
+  textEditor.displayBuffer.tokenizedBuffer.tokenizedLineForRow(lineNumber)

 extractRange = ({code, message, lineNumber, colNumber, textEditor}) ->
   switch code
@@ -53,7 +54,7 @@
         for token in tokenizedLine.tokens
           if foundImport and token.value is symbol
             return [[lineNumber, offset], [lineNumber, offset + token.bufferDelta]]
-          if token.value is 'import' and 'keyword.control.flow.python' in token.scopes
+          if token.value is 'import' and 'keyword.control.import.python' in token.scopes
             foundImport = true
           offset += token.bufferDelta
         lineNumber += 1


Fix deprecated call

Fix deprecated call

--- lib/main.coffee
+++ lib/main.coffee
@@ -10,7 +10,7 @@

   activate: ->
     commandDisposable = atom.commands.add('atom-text-editor', 'grammar-selector:show', createGrammarListView)
-    atom.packages.onDidActivateAll(createGrammarStatusView)
+    atom.packages.onDidActivateInitialPackages(createGrammarStatusView)

   deactivate: ->
     commandDisposable?.dispose()


read package name from package.json

read package name from package.json

--- lib/main.coffee
+++ lib/main.coffee
@@ -1,6 +1,9 @@
+meta = require '../package.json'
+
 # Dependencies
 {spawn} = require 'child_process'
 os = require 'os'
+
 module.exports = BridlensisCore =
   config:
     pathToJar:
@@ -34,7 +37,7 @@
     editor = atom.workspace.getActiveTextEditor()

     unless editor?
-      atom.notifications.addWarning("**language-bridlensis**: No active editor", dismissable: false)
+      atom.notifications.addWarning("**#{meta.name}**: No active editor", dismissable: false)
       return

     script = editor.getPath()
@@ -47,7 +50,7 @@
         bridleJar  = atom.config.get('language-bridlensis.pathToJar')

         if not bridleJar
-          atom.notifications.addError("**language-bridlensis**: no valid `BridleNSIS.jar` specified in your config", dismissable: false)
+          atom.notifications.addError("**#{meta.name}**: no valid `BridleNSIS.jar` specified in your config", dismissable: false)
           return

         defaultArguments = ["-jar", bridleJar]
@@ -75,15 +78,15 @@

   getPath: (callback) ->
     if os.platform() is 'win32'
-      whichJava = spawn('where', ['java'])
+      whichCmd = spawn('where', ['java'])
     else
-      whichJava = spawn('which', ['java'])
+      whichCmd = spawn('which', ['java'])

     # Find Java
-    whichJava.stderr.on 'data', (data) ->
-      atom.notifications.addError("**language-bridlensis**: Java is not in your `PATH` [environmental variable](http://superuser.com/a/284351/195953)", dismissable: true)
+    whichCmd.stderr.on 'data', (data) ->
+      atom.notifications.addError("**#{meta.name}**: Java is not in your `PATH` [environmental variable](http://superuser.com/a/284351/195953)", dismissable: true)
       return

-    whichJava.stdout.on 'data', (data) ->
+    whichCmd.stdout.on 'data', (data) ->
       callback data
       return


opt-out command scopes of new post/draft

--- lib/main.coffee
+++ lib/main.coffee
@@ -18,7 +18,7 @@
   activate: (state) ->
     # general
     ["draft", "post"].forEach (file) =>
-      @registerCommand "new-#{file}", "./new-#{file}-view"
+      @registerCommand "new-#{file}", "./new-#{file}-view", optOutGrammars: true
     @registerCommand "publish-draft", "./publish-draft"

     # front-matter
@@ -36,16 +36,17 @@
     ["link"].forEach (media) =>
       @registerCommand "insert-#{media}", "./insert-#{media}-view"

-  registerCommand: (cmd, path, opts = {}) ->
+  registerCommand: (cmd, path, options = {}) ->
     atom.workspaceView.command "markdown-writer:#{cmd}", ->
-      editor = atom.workspace.getActiveEditor()
-      return unless editor?
+      unless options.optOutGrammars
+        editor = atom.workspace.getActiveEditor()
+        return unless editor?

-      grammars = atom.config.get('markdown-writer.grammars') ? []
-      return unless editor.getGrammar().scopeName in grammars
+        grammars = atom.config.get('markdown-writer.grammars') ? []
+        return unless editor.getGrammar().scopeName in grammars

       cmdModule = require(path)
-      cmdInstance = new cmdModule(opts.args)
+      cmdInstance = new cmdModule(options.args)
       cmdInstance.display()

   deactivate: ->


fix multiple load in tableView

fix multiple load in tableView

--- BackofficeBundle/Resources/public/coffee/table/TableviewCollectionView.coffee
+++ BackofficeBundle/Resources/public/coffee/table/TableviewCollectionView.coffee
@@ -14,6 +14,8 @@
     _.bindAll this, "render"
     @loadTemplates [
       'OpenOrchestraBackofficeBundle:BackOffice:Underscore/tableviewCollectionView'
+      'OpenOrchestraBackofficeBundle:BackOffice:Underscore/tableviewView',
+      'OpenOrchestraBackofficeBundle:BackOffice:Underscore/tableviewActions'
     ]
     return


added mockery full path

added mockery full path

--- spec/provider-spec.coffee
+++ spec/provider-spec.coffee
@@ -1,4 +1,4 @@
-mockery = require 'mockery'
+mockery = require '/usr/local/lib/node_modules/mockery'
 mockSpawn = require '/usr/local/lib/node_modules/mock-spawn'
 provider = {}
 spawn = {}


Get basic spec passing

Get basic spec passing

--- spec/snippets-spec.coffee
+++ spec/snippets-spec.coffee
@@ -114,7 +114,7 @@
             body: """
               line 1
               \tline 2$1
-
+              $2
             """

           "nested tab stops":
@@ -396,6 +396,19 @@
             expect(editor.lineTextForBufferRow(3)).toBe "      line 2"
             expect(editor.getCursorBufferPosition()).toEqual [3, 12]

+          it "places tab stops correctly", ->
+            expect(editor.getSoftTabs()).toBeTruthy()
+            editor.setCursorScreenPosition([2, Infinity])
+            editor.insertText ' t3'
+            atom.commands.dispatch editorElement, 'snippets:expand'
+
+            markers = editor.getMarkers()
+            expect(markers.length).toBe 2
+            expect(markers[0].getBufferRange().start).toEqual row: 3, column: 12
+            expect(markers[0].getBufferRange().end).toEqual markers[0].getBufferRange().start
+            expect(markers[1].getBufferRange().start).toEqual row: 4, column: 4
+            expect(markers[1].getBufferRange().end).toEqual markers[1].getBufferRange().start
+
       describe "when multiple snippets match the prefix", ->
         it "expands the snippet that is the longest match for the prefix", ->
           editor.insertText('t113')


Starting to frame up some basic tests using jasmine


highlight modules and imports

highlight modules and imports

--- grammars/language-idris.cson
+++ grammars/language-idris.cson
@@ -81,6 +81,36 @@
         0:
           name: 'punctuation.definition.entity.idris'
     }
+    {
+      begin: '\\b(module)\\b'
+      beginCaptures:
+        1:
+          name: 'keyword.control.idris'
+      end: '($|;|(?=--))'
+      name: 'meta.module.idris'
+      patterns:
+        [
+          {
+            match: "([a-zA-Z._']+)"
+            name: 'meta.declaration.module.idris'
+          }
+        ]
+    }
+    {
+      begin: '\\b(import)\\b'
+      beginCaptures:
+        1:
+          name: 'keyword.other.idris'
+      end: '($|;|(?=--))'
+      name: 'meta.import.idris'
+      patterns:
+        [
+          {
+            match: "([a-zA-Z._']+)"
+            name: 'support.other.module.idris'
+          }
+        ]
+    }
   ]
 repository:
   {


published_at = undefined

published_at = undefined

--- api/apps/articles/model/index.coffee
+++ api/apps/articles/model/index.coffee
@@ -119,7 +119,7 @@

 @present = (article) =>
   scheduled = if (date = article?.scheduled_publish_at) then moment(date).toISOString() else null
-  published = if (date = article?.published_at) then moment(date).toISOString() else moment().toISOString()
+  published = if (date = article?.published_at) then moment(date).toISOString() else undefined
   _.extend article,
     id: article?._id?.toString()
     _id: undefined


Updated to use API methods

Updated to use API methods

--- tests/api/test.manipulation.coffee
+++ tests/api/test.manipulation.coffee
@@ -17,15 +17,11 @@
   'empty' :
     topic : (html) ->
       return cheerio.load html
-
-    ###
-      TODO should be using children()
-    ###
+
     'single element' : ($) ->
       lorem = $('#lorem')
-
       lorem.empty()
-      lorem.get(0)['children'].should.have.length 0
+      lorem.children().should.have.length 0

   'html' :
     topic : (html) ->


sankey: fixed mouseover bug

sankey: fixed mouseover bug

clean-up

--- app/assets/javascripts/d3/sankey.coffee
+++ app/assets/javascripts/d3/sankey.coffee
@@ -199,9 +199,9 @@
     vertical_margin: 10

     initialize: =>
-      @view = D3.sankey.view
       @right_links = []
       @left_links = []
+      @view = @get 'view'

     # vertical position of the top left corner of the node. Adds some margin
     # between nodes
@@ -239,7 +239,7 @@

   Link: class extends Backbone.Model
     initialize: =>
-      @view = D3.sankey.view
+      @view = @get 'view'
       @series = @view.series
       @left  = @view.node_map[@get('left')]
       @right = @view.node_map[@get('right')]
@@ -313,8 +313,6 @@
   #
   View: class extends D3ChartView
     initialize: ->
-      namespace = D3.sankey
-      namespace.view = this
       @series = @model.series
       @key = @model.get('key')
       # ugly but simple and effective. If the chart key name ends with _2010
@@ -327,11 +325,12 @@
       else
         k = @key
       @node_map = {}
-      @node_list = _.map namespace.charts[k].data.nodes, (n) =>
-        node = new D3.sankey.Node(n)
+      @node_list = _.map D3.sankey.charts[k].data.nodes, (n) =>
+        node = new D3.sankey.Node(_.extend n, view: this)
         @node_map[n.id] = node
         node
-      @link_list = _.map namespace.charts[k].data.links, (l) -> new D3.sankey.Link(l)
+      @link_list = _.map D3.sankey.charts[k].data.links, (l) =>
+        new D3.sankey.Link(_.extend l, view: this)
       @initialize_defaults()

     # this method is called when we first render the chart. It is called if we
@@ -371,7 +370,6 @@
         .enter()
         .append("svg:g")
         .attr("class", (l) -> "link #{l.left.get('id')} #{l.right.get('id')}")
-        .attr("data-cid", (d) -> d.cid) # unique identifier
       # link path
       links.append("svg:path")
         .style("stroke-width", (link) -> link.value())
@@ -390,7 +388,6 @@
         .enter()
         .append("g")
         .attr("class", "node")
-        .attr("data-id", (d) -> d.get('id'))
         .on("mouseover", @node_mouseover)
         .on("mouseout", @node_mouseout)

@@ -428,10 +425,9 @@
     selectedLinkOpacity = 0.5
     hoverLinkOpacity = 0.9

-    node_mouseover: ->
-      klass = $(this).attr('data-id')
-      d3.selectAll(".link")
-        .filter((d) -> !d.connects(klass))
+    node_mouseover: (e) =>
+      @svg.selectAll(".link")
+        .filter((d) -> !d.connects(e.get 'id'))
         .transition()
         .duration(200)
         .style("opacity", unselectedLinkOpacity)


remove 'remove panel' link

remove 'remove panel' link

--- app/assets/javascripts/panels.js.coffee
+++ app/assets/javascripts/panels.js.coffee
@@ -52,6 +52,7 @@

   remove_fields = (event) ->
     $(event.target).prev('.nested_fields').hide()
+    $(event.target).hide()

   $(document).on 'click', '.add_fields', (event) ->


Reduces pusher verbosity

Reduces pusher verbosity

--- app/assets/javascripts/pusher.js.coffee
+++ app/assets/javascripts/pusher.js.coffee
@@ -4,7 +4,6 @@
   pusher_key = $('body').data('pusher-key')
   user_id    = $('body').data('user-id')
   $.getScript "https://d3dy5gmtp8yhk7.cloudfront.net/2.0/pusher.min.js", ->
-    console.log "pusher script loaded"
     pusher     = new Pusher(pusher_key)
     channel    = pusher.subscribe("user-#{user_id}")
     channel.bind 'echo', (data) ->
@@ -26,4 +25,3 @@
         n  = node.find('.notifications .figure')
         n.text(data.notifications)
         n.flash(start: 'af-flashable-burnin', end: 'af-flashable-burnout')
-    console.log "pusher bindings established"


break the regex up for readability

break the regex up for readability

--- app/assets/javascripts/task_list.coffee
+++ app/assets/javascripts/task_list.coffee
@@ -115,7 +115,11 @@
 # Useful when you need iterate over all items.
 itemPattern = ///
   ^
-  (?:\s*(?:>\s+)*[-+*]|(?:\d+\.))? # optional list prefix
+  (?:                     # optional list prefix, consisting of
+    \s*                   # optional leading whitespace
+    (?:>\s+)*             # zero or more blockquotes
+    [-+*]|(?:\d+\.)       # item indicato
+  )?
   \s*                     # optional whitespace prefix
   (                       # checkbox
     #{escapePattern(complete)}|


hound!

hound!

--- app/assets/javascripts/routes/manuscript_manager_template_new_route.js.coffee
+++ app/assets/javascripts/routes/manuscript_manager_template_new_route.js.coffee
@@ -7,17 +7,14 @@
       journal:   journal
       paperType: "Research"

-    newTemplate.get('phaseTemplates').pushObject(
+    newTemplate.get('phaseTemplates').pushObject
       @store.createRecord 'phaseTemplate', name: "Phase 1", position: 1
-    )

-    newTemplate.get('phaseTemplates').pushObject(
+    newTemplate.get('phaseTemplates').pushObject
       @store.createRecord 'phaseTemplate', name: "Phase 2", position: 2
-    )

-    newTemplate.get('phaseTemplates').pushObject(
+    newTemplate.get('phaseTemplates').pushObject
       @store.createRecord 'phaseTemplate', name: "Phase 3", position: 3
-    )

     @set('journal', journal)
     @set('newTemplate', newTemplate)


Return biggest break in summed stats

Return biggest break in summed stats

--- app/controllers/stats_controller.coffee
+++ app/controllers/stats_controller.coffee
@@ -2,7 +2,7 @@
 models  = require '../../models'
 _ = require 'underscore'

-frameStatKeys = [
+frameSumKeys = [
   'totalPoints',
   'potAttempts',
   'safetyAttempts',
@@ -50,9 +50,11 @@
     order: [['id', 'ASC']]

 getOverallStats = (player) ->
+  attributes = _.map(frameSumKeys, (key) ->
+    [models.sequelize.fn('SUM', models.sequelize.col(key)), key]
+  )
+  attributes.push [models.sequelize.fn('MAX', models.sequelize.col('biggestBreak')), 'biggestBreak']
   models.FrameStats.find(
     where: { PlayerId: player.id }
-    attributes: _.map(frameStatKeys, (key) ->
-      [models.sequelize.fn('SUM', models.sequelize.col(key)), key]
-    )
+    attributes: attributes
   ).then (result) -> _.mapObject(result.dataValues, parseInt)


Remove prototypejs Ajax monkeypatches

Remove prototypejs Ajax monkeypatches

The only remaining use of Ajax.Request are on `*_to_remote` helpers but those are all simple requests without any special handling. I think.

--- app/javascript/src/legacy/common.coffee
+++ app/javascript/src/legacy/common.coffee
@@ -151,65 +151,6 @@
     container = document.createElement('div')
     container.innerHTML = this
     container.removeChild container.firstChild
-
-### Prototype calls onSuccess instead of onFailure when the user cancelled the AJAX
-# request.  Fix that with a monkey patch, so we don't have to track changes inside
-# prototype.js.
-###
-
-Ajax.Request::successBase = Ajax.Request::success
-
-Ajax.Request::success = ->
-  try
-    responses = @transport.getAllResponseHeaders()
-    if !responses?
-      return false
-  catch e
-
-    ### FF throws an exception if we call getAllResponseHeaders on a cancelled request. ###
-
-    return false
-  @successBase()
-
-### Work around a Prototype bug; it discards exceptions instead of letting them fall back
-# to the browser where they'll be logged.
-###
-
-Ajax.Responders.register onException: (request, exception) ->
-
-  ### Report the error here; don't wait for onerror to get it, since the exception
-  # isn't passed to it so the stack trace is lost.
-  ###
-
-  data = ''
-  if request.url
-    data += 'AJAX URL: ' + request.url + '\n'
-  text = undefined
-  length = undefined
-  try
-    params = request.parameters
-    for key of params
-      text = params[key]
-      length = text.length
-      if text.length > 1024
-        text = text.slice(0, 1024) + '...'
-      data += 'Parameter (' + length + '): ' + key + '=' + text + '\n'
-  catch e
-    data += 'Couldn\'t get response parameters: ' + e + '\n'
-  try
-    text = request.transport.responseText
-    length = text.length
-    if text.length > 1024
-      text = text.slice(0, 1024) + '...'
-    data += 'Response (' + length + '): ->' + text + '<-\n'
-  catch e
-    data += 'Couldn\'t get response text: ' + e + '\n'
-  ReportError null, null, null, exception, data
-  (->
-    throw exception
-    return
-  ).defer()
-  return

 ###
 # In Firefox, exceptions thrown from event handlers tend to get lost.  Sometimes they


Utilise `eachSummaryFrame`

Utilise `eachSummaryFrame`

--- plugins/youtrack-issues/index.coffee
+++ plugins/youtrack-issues/index.coffee
@@ -65,8 +65,8 @@

     == Stacktrace ==

-    {html}
{{#each error.stacktrace}}{{#if inProject}}
-    {{file}}:{{lineNumber}} - {{method}}{{/if}}{{/each}}
{html}
+    {html}
{{#eachSummaryFrame error.stacktrace}}
+    {{file}}:{{lineNumber}} - {{method}}{{/eachSummaryFrame}}
{html}

     [View full stacktrace|{{error.url}}]
     """


fix for iphone 6.*

fix for iphone 6.*

--- src/create/throwable.coffee
+++ src/create/throwable.coffee
@@ -16,8 +16,7 @@
 # Constructor function for a new throwable
 throwableConstr = (@message) ->
   e = Error.call @, message # get stack with correct line number
-  if e.stack?
-    @stack = e.stack
+  @stack = e.stack
   return


fixed helpers

fixed helpers

--- src/crystal/generate.coffee
+++ src/crystal/generate.coffee
@@ -198,7 +198,7 @@
 					test = test.join('.')
 					loaded_modules[module_name][version_name].exports[export_name].filename.engine = loaded_modules[test][submodules[test]].exports[test2].engine

-				if exported.helper
+				if typeof(exported.helper) == 'object'
 					helpers = []

 					for helper_name of exported.helper


compile single entries after locals are set

--- lib/index.coffee
+++ lib/index.coffee
@@ -30,11 +30,13 @@
     constructor: (@roots) ->
       @util = new RootsUtil(@roots)
       @roots.config.locals ?= {}
+      @roots.config.locals.contentful ?= {}

     setup: ->
-      configure_content(opts.content_types)
-        .then(get_all_content.bind(@))
-        .then (res) => @roots.config.locals.contentful = res
+      configure_content(opts.content_types).with(@)
+        .then(get_all_content)
+        .tap(set_locals)
+        .tap(compile_entries)

     ###*
      * Configures content types set in app.coffee. Sets default values if
@@ -63,13 +65,11 @@
     ###

     get_all_content = (types) ->
-      W.reduce types, (m, t) =>
+      W.map types, (t) =>
         fetch_content(t)
           .then(format_content)
-          .tap((c) => compile_entries.bind(@)(t, c))
-          .then((c) -> m[t.name] = c)
-          .yield(m)
-      , {}
+          .then((c) -> t.content = c)
+          .yield(t)

     ###*
      * Fetch entries for a single content type object
@@ -99,15 +99,25 @@
       _.assign(_.omit(e, 'fields'), e.fields)

     ###*
-     * Compiles single entry views if a template is given for the content type
-     * @param {Object} type - Type configuration object
-     * @param {Array} content - Formatted entries for the type from Contentful
+     * Builds locals object from types objects with content
+     * @param {Array} types - populated content type objects
+     * @return {Promise} - promise for when complete
+    ###
+
+    set_locals = (types) ->
+      W.map types, (t) => @roots.config.locals.contentful[t.name] = t.content
+
+    ###*
+     * Compiles single entry views for content types
+     * @param {Array} types - Populated content type objects
      * @return {Promise} - promise for when compilation is finished
     ###

-    compile_entries = (type, content) ->
-      if not type.template then return W.resolve()
-      W.map content, (entry) =>
-        locals = _.merge(@roots.config.locals, entry: entry)
-        jade.renderFile(path.join(@roots.root, type.template), locals)
-          .then((res) => @util.write("#{type.path(entry)}.html", res))
+    compile_entries = (types) ->
+      W.map types, (t) =>
+        if not t.template then return W.resolve()
+        W.map t.content, (entry) =>
+          locals = _.merge(@roots.config.locals, entry: entry)
+          jade.renderFile(path.join(@roots.root, t.template), locals)
+            .then((res) => @util.write("#{t.path(entry)}.html", res))
+


fix else if

fix else if

--- lib/index.coffee
+++ lib/index.coffee
@@ -38,7 +38,7 @@

       if method is 'scope'
         app.loadRoutes route, path
-      if method is 'mount'
+      else if method is 'mount'
         app.use path, route
       else if _.isFunction route
         loadRoute path, route, method


Add max_line_length fixtures to fixtures test helper

--- test/support/fixtures.coffee
+++ test/support/fixtures.coffee
@@ -23,6 +23,9 @@
 	'trim_trailing_whitespace/none.txt': readFixture 'trim_trailing_whitespace/none.txt'
 	'insert_final_newline/none.txt': readFixture 'insert_final_newline/none.txt'
 	'insert_final_newline/newline.txt': readFixture 'insert_final_newline/newline.txt'
+	'max_line_length/under-80.txt': readFixture 'max_line_length/under-80.txt'
+	'max_line_length/80.txt': readFixture 'max_line_length/80.txt'
+	'max_line_length/over-80.txt': readFixture 'max_line_length/over-80.txt'

 global.fixtures.cli =
 	'validate-valid-file': resolveFixture 'cli/validate-valid-file'


Correction d'un bug empêchant le tir

Correction d'un bug empêchant le tir

--- js/game/entities/Bullet.coffee
+++ js/game/entities/Bullet.coffee
@@ -10,7 +10,7 @@
             super @scene

             @x = @plane.x + @plane.width + 3
-            @y = @plane.y + @plane.height / 2 + (@plane.vSpeedPercentage + 0.1) *   16
+            @y = @plane.y + @plane.height / 2 + (@plane.vSpeedPercentage() + 0.1) *   16

             # Vitesse (en pixels/s)
             @speed = 800


Update primary_uuid/ack_expectations/replica_affinities if a datacenter is deleted

--- admin/static/coffee/servers/index.coffee
+++ admin/static/coffee/servers/index.coffee
@@ -450,7 +450,7 @@
                     @machines_to_delete[machine.get('id')] =
                         datacenter_uuid: universe_datacenter.get('id')

-            # We first unassign machines
+            # We first unassign machines, then change acks/replication/primary_uuid for namespaces and then delete the datacenter
             @unassign_machines_in_datacenter()

         unassign_machines_in_datacenter: =>
@@ -459,8 +459,46 @@
                 type: 'POST'
                 data: JSON.stringify(@machines_to_delete)
                 contentType: 'application/json'
+                success: @remove_responsabilities
+                error: @on_error
+
+        remove_responsabilities: =>
+            namespaces_to_update = {}
+            for namespace in namespaces.models
+
+                # Create the new replica affinities (done by setting the value for @datacenter to 0)
+                new_replica_affinities = namespace.get('replica_affinities')
+                new_replica_affinities[@datacenter.get('id')] = 0
+
+                # The same with acks now
+                new_ack_expectations = namespace.get('ack_expectations')
+                new_ack_expectations[@datacenter.get('id')] = 0
+
+                # Set Universe as new primary if @datacenter was the primary of a table
+                if namespace.get('primary_uuid') is @datacenter.get('id')
+                    namespaces_to_update[namespace.get('id')] =
+                        primary_uuid: universe_datacenter.get('id')
+                else
+                    namespaces_to_update[namespace.get('id')] = {}
+
+                # If the deleted datacenter is primary for a table, since we set Universe as a primary, we need at least one ack and one replica for Universe
+                if namespace.get('primary_uuid') is @datacenter.get('id')
+                    if not new_replica_affinities[universe_datacenter.get('id')]?
+                        new_replica_affinities[universe_datacenter.get('id')] = 0
+                     if not new_ack_expectations[universe_datacenter.get('id')]? or new_ack_expectations[universe_datacenter.get('id')] is 0
+                        new_ack_expectations[universe_datacenter.get('id')] = 1
+
+                namespaces_to_update[namespace.get('id')]['replica_affinities'] = new_replica_affinities
+                namespaces_to_update[namespace.get('id')]['ack_expectations'] = new_ack_expectations
+
+            $.ajax
+                url: "/ajax/semilattice/rdb_namespaces"
+                type: 'POST'
+                data: JSON.stringify(namespaces_to_update)
+                contentType: 'application/json'
                 success: @delete_datacenter
                 error: @on_error
+

         delete_datacenter: =>
             # The first call to unassign the machines was a success, so we can change their datacenter to universe


Wrap widget element in jQuery

Wrap widget element in jQuery

But probably needs a good API instead of exposing classes from weird
namespaces.

--- app/assets/javascripts/uploadcare/widget/widget.js.coffee
+++ app/assets/javascripts/uploadcare/widget/widget.js.coffee
@@ -15,7 +15,8 @@

   namespace 'uploadcare.widget', (ns) ->
     class ns.Widget
-      constructor: (@element) ->
+      constructor: (element) ->
+        @element = $(element)
         @settings = utils.buildSettings @element.data()
         @uploader = new uploads.Uploader(@settings)


[Backport] Fix issue tg-4648: Epics display show only first 30 related user stories

--- app/modules/resources/userstories-resource.service.coffee
+++ app/modules/resources/userstories-resource.service.coffee
@@ -45,6 +45,7 @@
         params = {
             project: projectId
         }
+
         return http.get(url, params, httpOptions)
             .then (result) ->
                 return Immutable.fromJS(result.data)
@@ -52,13 +53,19 @@
     service.listInEpic = (epicIid) ->
         url = urlsService.resolve("userstories")

-        params = {
-            'epic': epicIid,
-            'order_by': 'epic_order',
-            'include_tasks': true
+        httpOptions = {
+            headers: {
+                "x-disable-pagination": "1"
+            }
         }

-        return http.get(url, params)
+        params = {
+            epic: epicIid,
+            order_by: 'epic_order',
+            include_tasks: true
+        }
+
+        return http.get(url, params, httpOptions)
             .then (result) ->
                 return Immutable.fromJS(result.data)


fix syntax problem checking for project name

--- cli/src/commands/add_project.coffee
+++ cli/src/commands/add_project.coffee
@@ -6,7 +6,7 @@
 	exec = (require "child_process").exec

 	constructor:( @the, @options )->
-		if not options.length[1]?
+		if not options[1]?

 			console.log "ERROR".bold.red + " You must specify a name for your project"


default title for kdtooltips

default title for kdtooltips

--- client/Framework/core/KDView.coffee
+++ client/Framework/core/KDView.coffee
@@ -694,7 +694,7 @@
       left       : "e"
       right      : "w"

-    o.title     or= ""
+    o.title     or= "Default title..."
     o.placement or= "top"
     o.direction or= "center"
     o.offset    or=


Fix for terminal height after VM is changed

--- client/Terminal/src/Terminal.coffee
+++ client/Terminal/src/Terminal.coffee
@@ -208,10 +208,11 @@
   updateAppSize: ->

     { appView } = @getOptions()
+    {width: charWidth, height: charHeight} = @getCharSizes()

     height = appView.parent.getHeight() - 24 # padding

-    newHeight = Math.floor(height / @_mbHeight) * @_mbHeight
+    newHeight = Math.floor(height / charHeight) * charHeight

     appView.setHeight newHeight


Login: appending a style tag on interpretion for a random bg image

--- client/landing/Login/AppView.coffee
+++ client/landing/Login/AppView.coffee
@@ -78,6 +78,11 @@
     image.src  = bgImageUrl

     image.classList.add 'off-screen-login-image'
+
+    document.head.appendChild (new KDCustomHTMLView {
+      tagName    : 'style'
+      partial    : ".kdview.login-screen:after { background-image : url('#{bgImageUrl}')}"
+    }).getElement()

   constructor:(options = {}, data)->


remove useless debug

remove useless debug

--- src/api/host.coffee
+++ src/api/host.coffee
@@ -292,7 +292,6 @@
     return
   )

-  $debug('After parsing JSON host')
   memoryFree = []
   memoryUsed = []
   memory = []


atom-snippets: handlebars / htmlbars

atom-snippets: handlebars / htmlbars

--- init/snippets.cson
+++ init/snippets.cson
@@ -19,6 +19,108 @@
 # If you are unfamiliar with CSON, you can read more about it in the
 # Atom Flight Manual:
 # https://atom.io/docs/latest/using-atom-basic-customization#cson
+
+'.text.html.mustache':
+  'Log Helper':
+    'prefix': 'log'
+    'body': """
+      {{log ${1:value}}}
+    """
+
+  'Debug Helper':
+    'prefix': 'debugger'
+    'body': """
+      {{debugger}}
+    """
+
+  'Escape':
+    'prefix': 'escape'
+    'body': """
+      {{{${1:value}}}}
+    """
+
+  'Each':
+    'prefix': 'each'
+    'body': """
+      {{#each ${1:${2:item} ${3:in ${4:value}}}}}
+        ${5}
+      {{/each}}
+    """
+
+  'Each As':
+    'prefix': 'eachas'
+    'body': """
+      {{#each ${2:item} as |${4:value}|}}
+        ${5}
+
+      {{else}}
+        ${6}
+      {{/each}}
+    """
+
+  'Each In':
+    'prefix': 'eachin'
+    'body': """
+      {{#each-in ${2:item} as |${4:key} ${5:value}|}}
+        ${6}
+      {{/each-in}}
+    """
+
+  'Get':
+    'prefix': 'get'
+    'body': """
+      {{get ${1:${2:item} ${3:value}}}}
+    """
+
+  'If':
+    'prefix': 'if'
+    'body': """
+      {{#if ${1:bool}}}
+        $2
+      {{/if}}
+    """
+
+  'Else':
+    'prefix': 'else'
+    'body': '{{else}}${1}'
+
+  'If/Else Block':
+    'prefix': 'ifelse'
+    'body': """
+      {{#if ${1:bool}}}
+        ${2}
+
+      {{else}}
+        ${3}
+      {{/if}}
+    """
+
+  'Unless':
+    'prefix': 'unless'
+    'body': """
+      {{#unless ${1: item}}}
+        ${2}
+      {{/unless}}
+    """
+
+  'Unless/Else':
+    'prefix': 'unelse'
+    'body': """
+      {{#unless ${1:bool}}}
+        ${2}
+
+      {{else}}
+        ${3}
+      {{/unless}}
+    """
+
+  'With Block':
+    'prefix': 'with'
+    'body': """
+      {{#with ${1:item} ${2:as ${3:|${4:value}|}}}}
+        ${5}
+      {{/with}}
+    """

 '.source.js':
   'Ember\'s setupController off route':


namespaced unbind

namespaced unbind

--- src/bindable.coffee
+++ src/bindable.coffee
@@ -44,6 +44,7 @@
   is triggered.
   ###
   bind: (event, callback) ->
+    e
     eventCallbacks[event] = eventCallbacks[event] || []

     eventCallbacks[event].push(callback)


Fixes partials expression.

Fixes partials expression.

--- src/bindings.coffee
+++ src/bindings.coffee
@@ -593,7 +593,7 @@
 # </div>
 # ```
 chip.binding 'partial', 50, (element, expr, controller) ->
-	parts = expr.split /\s+as\s+\s+with\s+/
+	parts = expr.split /\s+as\s+|\s+with\s+/
 	nameExpr = parts.pop()
 	[ itemExpr, itemName ] = parts
 	childController = null


Add commercialConditionData to expected orderForm sections

--- src/checkout.coffee
+++ src/checkout.coffee
@@ -69,6 +69,7 @@
         'giftRegistryData'
         'ratesAndBenefitsData'
         'openTextField'
+        'commercialConditionData'
       ]

   ###


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/cooldown.coffee
+++ src/cooldown.coffee
@@ -29,7 +29,7 @@

     player.I.shootTimer # => 9

-    # Turbo
+    # Turbo Cooldown
     player = GameObject()

     # by default the cooldown


finally a passing build

finally a passing build

--- apps/user_profile/test/client.coffee
+++ apps/user_profile/test/client.coffee
@@ -76,7 +76,7 @@
         sd: {}
       }, =>
         { CollectionView } = mod = benv.require resolve __dirname, '../client/collection'
-        stubChildClasses mod, @, ['ArtworkColumnsView', 'ShareModal', 'EditWorkModal', 'FavoritesEmptyStateView'], ['appendArtworks']
+        stubChildClasses mod, @, ['ArtworkColumnsView', 'ShareModal', 'FavoritesEmptyStateView'], ['appendArtworks']
         @view = new CollectionView
           el: $('body')
           artworkCollection: new ArtworkCollection id: 'saved-artwork', user_id: 'craig'


fix bug with increasing limit when there's no more items

--- lib/pages.coffee
+++ lib/pages.coffee
@@ -819,8 +819,13 @@
           return

         if (@scrollBox.scrollTop() + @scrollBox[0].offsetHeight >= l)
-          @sess("limit", @sess("limit") + @infiniteStep)
-
+          n = @Collection.find({},
+            fields: @fields
+            sort: @sort
+          ).count()
+          if (@sess("limit") < n)
+            @sess("limit", @sess("limit") + @infiniteStep)
+
           ###
           if @lastPage < @sess "totalPages"
             console.log "i want page #{@lastPage + 1}"


Adds another natural test

Adds another natural test

--- api/apps/channels/test/model.test.coffee
+++ api/apps/channels/test/model.test.coffee
@@ -36,6 +36,22 @@
           results[0].name.should.equal 'Channel'
           done()

+    it 'naturally sorts channels by last updated', (done) ->
+      Channel.save {
+        name: 'Apple'
+      }, (err, channel) ->
+        Channel.save {
+          name: 'Banana'
+        }, (err, channel) ->
+          Channel.where
+            offset: 10
+          , (err, res) ->
+            { total, count, results } = res
+            results[0].name.should.equal 'Apple'
+            results[1].name.should.equal 'Banana'
+            results.length.should.equal 2
+            done()
+
     it 'can sort channels', (done) ->
       Channel.save {
         name: 'Apple'
@@ -44,12 +60,13 @@
           name: 'Banana'
         }, (err, channel) ->
           Channel.where
-            sort: 'name'
+            sort: '-name'
+            offset: 9
           , (err, res) ->
             { total, count, results } = res
-            results[0].name.should.equal 'Apple'
+            results[0].name.should.equal 'Editorial'
             results[1].name.should.equal 'Banana'
-            results[2].name.should.equal 'Editorial'
+            results[2].name.should.equal 'Apple'
             done()

   describe '#find', ->


sideview: emit an event after data is rendered

--- client/Social/Activity/sidebar/activitysideview.coffee
+++ client/Social/Activity/sidebar/activitysideview.coffee
@@ -82,6 +82,7 @@

     {dataPath} = @getOptions()
     items = KD.singletons.socialapi.getPrefetchedData dataPath
+
     if items?.length
     then @renderItems null, items
     else @reload()
@@ -97,6 +98,7 @@

   renderItems: (err, items = []) ->
+
     {limit} = @getOptions()

     @listController.hideLazyLoader()
@@ -105,6 +107,9 @@

     @listController.addItem itemData for itemData, i in items when i < limit

+    KD.utils.defer => @emit 'DataReady', items
+
+

   pistachio: ->
     """


code cleanup

code cleanup

--- client/app/CommonViews/opinions/opinionformview.coffee
+++ client/app/CommonViews/opinions/opinionformview.coffee
@@ -50,7 +50,6 @@
       cssClass        : "fullscreen-button"
       title           : "Fullscreen Edit"
       callback: =>
-
         @textContainer = new KDView
           cssClass:"modal-fullscreen-text"

@@ -60,7 +59,6 @@
           defaultValue : @opinionBody.getValue()

         @textContainer.addSubView @text
-

         modal = new KDModalView
           title       : "What do you want to discuss?"
@@ -86,8 +84,6 @@
                 @opinionBody.generatePreview()
                 modal.destroy()

-
-
         modal.$(".kdmodal-content").height modal.$(".kdmodal-inner").height()-modal.$(".kdmodal-buttons").height()-modal.$(".kdmodal-title").height()-12 # minus the margin, border pixels too..
         modal.$(".fullscreen-data").height modal.$(".kdmodal-content").height()-30-23
         modal.$(".input_preview").height   modal.$(".kdmodal-content").height()-0-21
@@ -106,8 +102,6 @@

         modal.$(".fullscreen-data").width contentWidth-halfWidth-5
         modal.$(".input_preview").width halfWidth-5
-
-

     @markdownLink = new KDCustomHTMLView
       tagName     : 'a'


Sidebar: Select workspace when ready

Sidebar: Select workspace when ready

--- client/app/lib/activity/sidebar/activitysidebar.coffee
+++ client/app/lib/activity/sidebar/activitysidebar.coffee
@@ -498,8 +498,10 @@

     { machine, workspace } = data

-    for machineList in @machineLists
-      machineList.selectMachineAndWorkspace machine.uid, workspace.slug
+    kd.getSingleton('mainController').ready =>
+
+      for machineList in @machineLists
+        machineList.selectMachineAndWorkspace machine.uid, workspace.slug

   fetchEnvironmentData: (callback) ->


Disable conversations for guests, show it by default when loggedIn

--- client/landing/app/Applications/Chat.kdapplication/Views/mainchatpanel.coffee
+++ client/landing/app/Applications/Chat.kdapplication/Views/mainchatpanel.coffee
@@ -17,6 +17,10 @@
         @getSingleton('windowController').addLayer @
         @once 'ReceivedClickElsewhere', @bound 'hidePanel'

+    # FIXME Later ~ GG
+    {mainController} = KD.singletons
+    mainController.on "accountChanged.to.loggedIn", @bound 'showPanel'
+
   createConversation:(data)->
     # Data includes chatChannel and the conversation
     @conversationListController.addItem data
@@ -25,9 +29,11 @@
     @addSubView @header
     @addSubView @conversationList
     @conversationListController.loadItems()
-    # @showPanel()
+    @showPanel()  if KD.isLoggedIn()

   showPanel:->
+    return  unless KD.isLoggedIn()
+
     @show()
     @utils.defer =>
       @setClass 'visible'


plumbing to make appearance of plot selection overlay configurable

--- bokehjs/src/coffee/renderer/overlay/poly_selection.coffee
+++ bokehjs/src/coffee/renderer/overlay/poly_selection.coffee
@@ -4,9 +4,17 @@
   "common/has_parent"
   "common/collection"
   "common/plot_widget"
-], (_, HasParent, Collection, PlotWidget) ->
+  "renderer/properties"
+], (_, HasParent, Collection, PlotWidget, properties) ->

   class PolySelectionView extends PlotWidget
+
+    initialize: (options) ->
+      super(options)
+      @props = {
+        line: new properties.Line(@)
+        fill: new properties.Fill(@)
+      }

     bind_bokeh_events: () ->
       @listenTo(@model, 'change:data', @plot_view.request_render)
@@ -29,11 +37,29 @@
           ctx.lineTo(sx, sy)
       if @mget('auto_close')
         ctx.closePath()
-      ctx.stroke()
+      if @props.line.do_stroke
+        @props.line.set(ctx)
+        ctx.stroke()
+      if @props.fill.do_fill and @mget('auto_close')
+        @props.fill.set(ctx)
+        ctx.fill()

   class PolySelection extends HasParent
     default_view: PolySelectionView
     type: "PolySelection"
+
+    display_defaults: () ->
+      return _.extend({}, super(), {
+        fill_color: null
+        fill_alpha: 0.2
+        line_color: 'grey'
+        line_width: 2
+        line_alpha: 0.8
+        line_join: 'miter'
+        line_cap: 'butt'
+        line_dash: [4]
+        line_dash_offset: 0
+      })

     defaults: () ->
       return _.extend({}, super(), {


Add fatal-error notification to emulator

--- emulation/atom-emulator.coffee
+++ emulation/atom-emulator.coffee
@@ -309,6 +309,8 @@
       @addNotification msg, '#e8d0a1', 3000
     addError: (msg) ->
       @addNotification msg, '#ecb1b1', 4000
+    addFatalError: (msg) ->
+      @addNotification msg, '#ecb1b1', 4000

   clipboard:
     clipboard:


fixed typo for method call in place class

--- lib/place.coffee
+++ lib/place.coffee
@@ -52,7 +52,7 @@
   else if __.isObject(egal)
     #log.debug "find parameter is an object"
     if egal.city and egal.country
-      @findByKeyPattern "#{egal.country}:*:#{egal.city}", callback
+      @findByKeyPattern "#{egal.country()}:*:#{egal.city()}", callback

     else if egal.address_components
       @fromGoogleGeocoder egal, callback


dotchart: simplify use of d3-force

dotchart: simplify use of d3-force

--- src/dotchart.coffee
+++ src/dotchart.coffee
@@ -180,18 +180,23 @@

         else if jitter == "beeswarm"
             if horizontal
-                x_strength = 8
-                y_strength = 1
+                d3.range(scaledPoints.length).map( (i) ->
+                    scaledPoints[i].fx = scaledPoints[i].x )
+
+                force = d3.forceSimulation(scaledPoints)
+                      .force("y", d3.forceY((d) -> d.y))
+                      .force("collide", d3.forceCollide(pointsize*1.1))
+                      .stop()
             else
-                y_strength = 8
-                x_strength = 1
-
-            force = d3.forceSimulation(scaledPoints)
-                      .force("x", d3.forceX((d) -> d.x).strength(x_strength))
-                      .force("y", d3.forceY((d) -> d.y).strength(y_strength))
-                      .force("collide", d3.forceCollide(pointsize*1.5))
+                d3.range(scaledPoints.length).map( (i) ->
+                    scaledPoints[i].fy = scaledPoints[i].y)
+
+                force = d3.forceSimulation(scaledPoints)
+                      .force("x", d3.forceX((d) -> d.x))
+                      .force("collide", d3.forceCollide(pointsize*1.1))
                       .stop()
-            d3.range(128).map((i) -> force.tick())
+
+            d3.range(100).map((i) -> force.tick())

             points.attr("cx", (d) -> d.x)
                   .attr("cy", (d) -> d.y)