Allow JSON API interface methods to be inherited

--- src/interfaces/json_api.coffee
+++ src/interfaces/json_api.coffee
@@ -53,15 +53,15 @@
   #
   # @param [Object] the object to convert the attributes of to underscore format
   # @return [Object] the object with attributes in underscore format
-  toUnderscored = (object) ->
+  toUnderscored: (object) ->
     underscored = {}
     for k, v of object
       underscored[s.underscored(k)] =
         if _.isArray(v)
           _.map v, (i) ->
-            toUnderscored(i)
+            this.toUnderscored(i)
         else if _.isObject(v) && !v.isA?(ActiveResource::Base) && !v.isA?(ActiveResource::Collection) && !_.isDate(v)
-          toUnderscored(v)
+          this.toUnderscored(v)
         else
           v

@@ -74,15 +74,15 @@
   #
   # @param [Object] the object to convert the attributes of to camelCase format
   # @return [Object] the object with attributes in camelCase format
-  toCamelCase = (object) ->
+  toCamelCase: (object) ->
     camelized = {}
     for k, v of object
       camelized[s.camelize(k)] =
         if _.isArray(v)
           _.map v, (i) ->
-            toCamelCase(i)
+            this.toCamelCase(i)
         else if _.isObject(v) && !v.isA?(ActiveResource::Base) && !v.isA?(ActiveResource::Collection)
-          toCamelCase(v)
+          this.toCamelCase(v)
         else
           v

@@ -107,7 +107,7 @@
   #
   # 1. Go through each key of the object, map its array of fields to underscored fields
   # 2. Take the mapped array of fields and join them, replacing the value of the key with the joined string
-  buildSparseFieldset = (fields) ->
+  buildSparseFieldset: (fields) ->
     _.mapObject fields, (fieldArray) ->
       _.map(fieldArray, (f) -> s.underscored(f)).join()

@@ -139,7 +139,7 @@
   #      eventually returning an array of strings ['transactions.deeperInclude.X','transactions.deeperInclude.Y']
   # 3. If include is string, it is formatted
   # 4. Return the includeStrArray as a ',' joined string
-  buildIncludeTree = (includes) ->
+  buildIncludeTree: (includes) ->
     buildNestedIncludes = (object) ->
       modelName = s.underscored(_.keys(object)[0])
       value = _.values(object)[0]
@@ -174,7 +174,7 @@
   #
   # @param [Object] sortObject the object defining sorting columns
   # @return [String] a JSON API formatted string that defines sorting
-  buildSortList = (sortObject) ->
+  buildSortList: (sortObject) ->
     output = []
     for column, dir of sortObject
       if dir == 'asc'
@@ -196,7 +196,7 @@
   #
   # @param [ActiveResource::Base] the resource to convert to a resource identifier
   # @return [Object] the resource identifier for the object
-  buildResourceIdentifier = (resource) ->
+  buildResourceIdentifier: (resource) ->
     identifier = { type: resource.klass().queryName }
     if (primaryKeyValue = resource[resource.klass().primaryKey])
       identifier[resource.klass().primaryKey] = primaryKeyValue.toString()
@@ -206,10 +206,10 @@
   #
   # @param [ActiveResource::Base] resource the resource to get relationship data from
   # @return [Object] the built relationship object for the resource
-  buildResourceRelationships = (resource, relationships, onlyChanged = false) ->
+  buildResourceRelationships: (resource, relationships, onlyChanged = false) ->
     output = {}

-    _.each relationships, (relationship) ->
+    _.each relationships, (relationship) =>
       reflection = resource.klass().reflectOnAssociation(relationship)

       target = resource.association(reflection.name).target
@@ -217,11 +217,11 @@
       return if (reflection.collection() && target.empty()) || target == null

       output[s.underscored(reflection.name)] = {
-        data: buildResourceDocument({
+        data: this.buildResourceDocument({
           resourceData: target,
           onlyResourceIdentifiers: !reflection.autosave(),
           onlyChanged: onlyChanged,
-          parentReflection: reflection.inverseOf() || {name: reflection.options['as']}
+          parentReflection: reflection.inverseOf() || { name: reflection.options['as'] }
         })
       }

@@ -233,13 +233,13 @@
   # @param [Boolean] onlyResourceIdentifiers if true, only renders the primary key/type (a resource identifier)
   #   if false, also renders attributes and relationships
   # @return [Array] an array of resource identifiers, possibly with attributes/relationships
-  buildResourceDocument = ({ resourceData, onlyResourceIdentifiers, onlyChanged, parentReflection }) ->
+  buildResourceDocument: ({ resourceData, onlyResourceIdentifiers, onlyChanged, parentReflection }) ->
     onlyResourceIdentifiers = onlyResourceIdentifiers || false
     onlyChanged = onlyChanged || false

     data =
-      ActiveResource::Collection.build(resourceData).compact().map (resource) ->
-        documentResource = buildResourceIdentifier(resource)
+      ActiveResource::Collection.build(resourceData).compact().map (resource) =>
+        documentResource = this.buildResourceIdentifier(resource)

         unless onlyResourceIdentifiers
           attributes = _.omit(resource.attributes(), resource.klass().primaryKey)
@@ -253,8 +253,8 @@
             attributes = _.pick(attributes, changedFields...)
             relationships = _.intersection(relationships, changedFields)

-          documentResource['attributes'] = toUnderscored(attributes)
-          documentResource['relationships'] = buildResourceRelationships(resource, relationships, onlyChanged)
+          documentResource['attributes'] = this.toUnderscored(attributes)
+          documentResource['relationships'] = this.buildResourceRelationships(resource, relationships, onlyChanged)

         documentResource

@@ -297,7 +297,7 @@
     attributes[resource.klass().primaryKey] = data[resource.klass().primaryKey].toString()
     attributes = @addRelationshipsToAttributes(attributes, data['relationships'], includes, resource)

-    resource.__assignFields(toCamelCase(attributes))
+    resource.__assignFields(this.toCamelCase(attributes))

     resource.__links = _.pick(data['links'], 'self')
     resource.klass().reflectOnAllAssociations().each (reflection) ->
@@ -493,11 +493,11 @@
   # @param [Object] queryParams query params to send to the server
   get: (url, queryParams = {}) ->
     data = {}
-    data['filter']  = toUnderscored(queryParams['filter'])       if queryParams['filter']?
-    data['fields']  = buildSparseFieldset(queryParams['fields']) if queryParams['fields']?
-    data['include'] = buildIncludeTree(queryParams['include'])   if queryParams['include']?
+    data['filter']  = this.toUnderscored(queryParams['filter'])       if queryParams['filter']?
+    data['fields']  = this.buildSparseFieldset(queryParams['fields']) if queryParams['fields']?
+    data['include'] = this.buildIncludeTree(queryParams['include'])   if queryParams['include']?
     if queryParams['sort']?
-      data['sort']    = buildSortList(queryParams['sort'])
+      data['sort']  = this.buildSortList(queryParams['sort'])
     data['page']    = queryParams['page']                        if queryParams['page']?
     data['limit']   = queryParams['limit']                       if queryParams['limit']?
     data['offset']  = queryParams['offset']                      if queryParams['offset']?
@@ -526,13 +526,13 @@
   # @option [Boolean] onlyResourceIdentifiers if false, render the attributes and relationships
   #   of each resource into the resource document
   post: (url, resourceData, options = {}) ->
-    data = { data: buildResourceDocument(resourceData: resourceData, onlyResourceIdentifiers: options['onlyResourceIdentifiers']) }
+    data = { data: this.buildResourceDocument(resourceData: resourceData, onlyResourceIdentifiers: options['onlyResourceIdentifiers']) }

     unless options['onlyResourceIdentifiers']
       queryParams = resourceData.queryParams()

-      data['fields']  = buildSparseFieldset(queryParams['fields']) if queryParams['fields']?
-      data['include'] = buildIncludeTree(queryParams['include'])   if queryParams['include']?
+      data['fields']  = this.buildSparseFieldset(queryParams['fields']) if queryParams['fields']?
+      data['include'] = this.buildIncludeTree(queryParams['include'])   if queryParams['include']?

     _this = this
     @request(url, 'POST', data)
@@ -555,7 +555,7 @@
   #   @see #post
   patch: (url, resourceData, options = {}) ->
     data = {
-      data: buildResourceDocument({
+      data: this.buildResourceDocument({
         resourceData: resourceData,
         onlyResourceIdentifiers: options['onlyResourceIdentifiers'],
         onlyChanged: true
@@ -565,8 +565,8 @@
     unless options['onlyResourceIdentifiers']
       queryParams = resourceData.queryParams()

-      data['fields']  = buildSparseFieldset(queryParams['fields']) if queryParams['fields']?
-      data['include'] = buildIncludeTree(queryParams['include'])   if queryParams['include']?
+      data['fields']  = this.buildSparseFieldset(queryParams['fields']) if queryParams['fields']?
+      data['include'] = this.buildIncludeTree(queryParams['include'])   if queryParams['include']?

     _this = this
     @request(url, 'PATCH', data)
@@ -588,13 +588,13 @@
   # @param [Object] options options that may modify the data sent to the server
   #   @see #post
   put: (url, resourceData, options = {}) ->
-    data = { data: buildResourceDocument(resourceData: resourceData, onlyResourceIdentifiers: options['onlyResourceIdentifiers']) }
+    data = { data: this.buildResourceDocument(resourceData: resourceData, onlyResourceIdentifiers: options['onlyResourceIdentifiers']) }

     unless options['onlyResourceIdentifiers']
       queryParams = resourceData.queryParams()

-      data['fields']  = buildSparseFieldset(queryParams['fields']) if queryParams['fields']?
-      data['include'] = buildIncludeTree(queryParams['include'])   if queryParams['include']?
+      data['fields']  = this.buildSparseFieldset(queryParams['fields']) if queryParams['fields']?
+      data['include'] = this.buildIncludeTree(queryParams['include'])   if queryParams['include']?

     _this = this
     @request(url, 'PUT', data)
@@ -624,7 +624,7 @@
   delete: (url, resourceData, options = {}) ->
     data =
       if resourceData?
-        { data: buildResourceDocument(resourceData: resourceData, onlyResourceIdentifiers: true) }
+        { data: this.buildResourceDocument(resourceData: resourceData, onlyResourceIdentifiers: true) }
       else
         {}


Fix url race condition on logout.

Fix url race condition on logout.

--- app/stores/user-store.coffee
+++ app/stores/user-store.coffee
@@ -14,14 +14,12 @@
 parseJson = (response) ->
   response.json()

-assignmentPath = ''
-
 extractToken = (hash) ->
   if hash.indexOf('assignment') > -1
     match = hash.match(/classify\/assignment-(\d+)\/access_token=([\w\-\.]+)/)
-    console.log 'match: ', match
     if !!match && match[2]
-      assignmentPath = 'classify/assignment-' + match[1]
+      assignmentPath = '/classify/assignment-' + match[1]
+      window.location.hash = assignmentPath
       match[2]
     else
       null
@@ -88,7 +86,6 @@

   signInUrl: (location = null) ->
     location ?= window.location
-    location.hash = assignmentPath
     client.host + '/oauth/authorize' +
       "?response_type=token" +
       "&client_id=#{ client.appID }" +


fix scroll when in find-replace or in Vim mode; or we can find a better solution

--- lib/minimap-view.coffee
+++ lib/minimap-view.coffee
@@ -124,12 +124,17 @@

     setImmediate => @updateScroll()

-  updateScroll: =>
+  updateScroll: (top) =>
     minimapHeight = @miniScrollView.outerHeight()
     scrollViewHeight = @scrollView.outerHeight()
-    scrollViewOffset = @scrollView.offset().top
-    overlayerOffset = @scrollView.find('.overlayer').offset().top
-    overlayY = -overlayerOffset + scrollViewOffset
+    # Need scroll-top value when in find-replace or in Vim mode(`gg`, `shift+g`).
+    # Or we can find a better solution.
+    if top isnt undefined
+      overlayY = top
+    else
+      scrollViewOffset = @scrollView.offset().top
+      overlayerOffset = @scrollView.find('.overlayer').offset().top
+      overlayY = -overlayerOffset + scrollViewOffset
     scrollRatio = overlayY / (minimapHeight - scrollViewHeight)
     minimapMaxScroll = ((minimapHeight * @scaleY) - scrollViewHeight) / @scaleY
     minimapCanScroll = (minimapHeight * @scaleY) > scrollViewHeight
@@ -155,7 +160,6 @@
       @activatePaneViewMinimap() unless @minimapIsAttached()
       @storeActiveEditor()
       @setMinimapEditorView()
-      @updateMinimapView()
     else
       # Ignore any tab that is not an editor
       @deactivatePaneViewMinimap()


fix overlayer goto top issue when resizing

--- lib/minimap-view.coffee
+++ lib/minimap-view.coffee
@@ -123,8 +123,6 @@
     @miniOverlayer.css
       width: @scrollViewRect.width
       height: @editorViewRect.height
-      '-webkit-transform': @translateY()
-      transform: @translateY()
     @miniOverlayer.removeClass 'hide'

     @transform @miniWrapper[0], @minimapScale


fix overlayer go to top when clicking

fix overlayer go to top when clicking

--- lib/minimap-view.coffee
+++ lib/minimap-view.coffee
@@ -184,7 +184,7 @@
     else
       @transform @miniWrapper[0], @minimapScale

-    @miniScrollView.data('top', minimapScroll)
+    @data('top', minimapScroll)
     @transform @miniOverlayer[0], @translateY(overlayY)

   scale: (x=1,y=1) -> "scale(#{x}, #{y}) "


Remove _sMasked

Remove _sMasked

--- lib/mixins/color.coffee
+++ lib/mixins/color.coffee
@@ -36,19 +36,7 @@
   _setColor: (color, stroke) ->
     color = @_normalizeColor color
     return no unless color
-
-    # clear sMask
-    if @_sMasked
-      gstate = @ref
-        Type: 'ExtGState'
-        SMask: 'None'
-
-      gstate.end()
-      name = "Gs#{++@_opacityCount}"
-      @page.ext_gstates[name] = gstate
-      @addContent "/#{name} gs"
-      @_sMasked = false
-
+
     op = if stroke then 'SCN' else 'scn'

     if color instanceof PDFGradient
@@ -271,4 +259,4 @@
   white: [255, 255, 255]
   whitesmoke: [245, 245, 245]
   yellow: [255, 255, 0]
-  yellowgreen: [154, 205, 50]+  yellowgreen: [154, 205, 50]


:fire: cleanup

:fire: cleanup

--- lib/mode-manager.coffee
+++ lib/mode-manager.coffee
@@ -133,7 +133,7 @@
   activateVisualMode: (submode) ->
     @normalizeSelections() if @submode?

-    # We only select-forwad only when
+    # We only select-forward only when
     #  -  submode shift(@submode? is true)
     #  -  initial activation(@submode? is false) and selection was empty.
     for selection in @editor.getSelections() when @submode? or selection.isEmpty()
@@ -158,11 +158,8 @@
         null
       when 'linewise'
         for selection in @editor.getSelections() when not selection.isEmpty()
-          # console.log 'before restore column', selection.getBufferRange().toString()
           swrap(selection).restoreColumnFromProperties()
-          # console.log 'restored column', selection.getBufferRange().toString()
           swrap(selection).translateSelectionEndAndClip('forward')
-          # console.log 'clip forwarded', selection.getBufferRange().toString()
       when 'blockwise'
         for bs in @vimState.getBlockwiseSelections()
           bs.restoreCharacterwise()
@@ -172,7 +169,6 @@
     for selection in @editor.getSelections() when not selection.isEmpty()

       swrap(selection).translateSelectionEndAndClip('backward')
-      # console.log 'clip backwarded', selection.getBufferRange().toString()

   # Narrow to selection
   # -------------------------


Resurrection: group switcher popup logout added

--- client/app/MainApp/avatararea/avatarareagroupswitcherpopup.coffee
+++ client/app/MainApp/avatararea/avatarareagroupswitcherpopup.coffee
@@ -53,6 +53,15 @@
       group =  groupsController.getCurrentGroup()
       if group?.slug isnt "koding"
         backToKodingView.updatePartial "Back to Koding"
+
+    @avatarPopupContent.addSubView new KDCustomHTMLView
+      tagName    : 'a'
+      attributes : href : '#'
+      cssClass   : 'logout'
+      partial    : 'Logout'
+      click      : (event)->
+        KD.utils.stopDOMEvent event
+        KD.getSingleton('router').handleRoute '/Logout'

   populatePendingGroups:->


home/send invites: dont accept multiple files

--- client/home/lib/myteam/components/hometeamsendinvites/view.coffee
+++ client/home/lib/myteam/components/hometeamsendinvites/view.coffee
@@ -67,7 +67,7 @@
           title='UPLOAD CSV'
           className={'custom-link-view HomeAppView--button ft'}
           callback={@props.onUploadCsv} />
-        <input ref={(input) => @input = input} multiple accept='.csv' className='kdinput file uploadcsv' type='file' />
+        <input ref={(input) => @input = input} accept='.csv' className='kdinput file uploadcsv' type='file' />
       </fieldset>
     </div>


add maxScaleSize for coloc

add maxScaleSize for coloc

--- app/vis/ColocationVis.coffee
+++ app/vis/ColocationVis.coffee
@@ -5,7 +5,7 @@
     console.info "New ColocationVis."

   rotation: 0
-  maxAmount: 0
+  maxSizeScale: 2

   start: (network) =>
     radius = 600
@@ -16,7 +16,7 @@

     @amountScale = d3.scale.linear()
       .domain([1, maxAmount])
-      .range([1, 4])
+      .range([1, @maxSizeScale])

     root = makeTree network
     root = @setNetworkPositions root, radius
@@ -35,7 +35,7 @@
     @fadeToArray(1, 1000) root._text_object.children

     traverse = (node, index, array) =>
-      delay = 2000
+      delay = 1000

       next_child = node.children.filter((_) -> _.children?)[0]

@@ -56,6 +56,9 @@
       node._text_object.parent.add child._text_object
       scale = @amountScale child.amt || 1
       child._text_object.scale.multiplyScalar scale
+      child._final_position = child._text_object.position.clone()
+      child._start_position = node._text_object.position.clone()
+      # child._text_object.position.copy child._start_position
     @adjustCameraToFit node._text_object.parent, 1.7
     promises = node.children.map (child) =>
       # Stagger fade-in of children


Check file's existence after the command exited.

--- spec/package-generator-spec.coffee
+++ spec/package-generator-spec.coffee
@@ -49,8 +49,12 @@
       packageGeneratorView.miniEditor.setText(packagePath)
       packageGeneratorView.trigger "core:confirm"

-      expect(packagePath).not.toExistOnDisk()
-      expect(path.join(path.dirname(packagePath), "camel-case-is-for-the-birds")).toExistOnDisk()
+      originalDetach = packageGeneratorView.detach
+      packageGeneratorView.detach = =>
+        packageGeneratorView.detach = originalDetach
+        packageGeneratorView.detach()
+        expect(packagePath).not.toExistOnDisk()
+        expect(path.join(path.dirname(packagePath), "camel-case-is-for-the-birds")).toExistOnDisk()

     describe 'which is regular package theme', ->
       beforeEach -> rootView.trigger("package-generator:generate-package")


improve click-to-scroll

improve click-to-scroll

--- lib/minimap-view.coffee
+++ lib/minimap-view.coffee
@@ -122,7 +122,8 @@
     @transform(width, [scaleX, scaleY], [x, y])
     #if @scrollViewRect.height < @editorViewRect.height #else

-    @scrollTop(@editorView.scrollTop())
+    setImmediate =>
+      @scrollTop(@editorView.scrollTop())

   reset: ->
@@ -148,27 +149,34 @@

   scrollTop: (top) ->
     h = @miniEditorView.height() * scaleY
+    miniOverLayerHeight = @miniOverlayer.height()
+    n = top / (@scrollViewLines.outerHeight() - miniOverLayerHeight)
     if h > @scrollView.height()
-      miniOverLayerHeight = @miniOverlayer.height()
-      n = top / (@scrollViewLines.outerHeight() - @editorView.height())
       #@miniScrollView.css({ top: -(@miniScrollView.outerHeight() - miniOverLayerHeight / scaleY) * n })
+      #@miniOverlayer.css({ top: n * (miniOverLayerHeight / scaleY - miniOverLayerHeight) })
+      top = -(@miniScrollView.outerHeight() - miniOverLayerHeight / scaleY) * n
+      @miniScrollView.data('top', top)
       @miniScrollView[0].style.webkitTransform =
-        @miniScrollView[0].style.transform = 'translate3d(0, ' + (-(@miniScrollView.outerHeight() - miniOverLayerHeight / scaleY) * n) + 'px, 0)'
-      #@miniOverlayer.css({ top: n * (miniOverLayerHeight / scaleY - miniOverLayerHeight) })
+        @miniScrollView[0].style.transform = 'translate3d(0, ' + top + 'px, 0)'
       @miniOverlayer[0].style.webkitTransform =
         @miniOverlayer[0].style.transform = 'translate3d(0, ' + (n * (miniOverLayerHeight / scaleY - miniOverLayerHeight)) + 'px, 0)'
     else
       @miniOverlayer[0].style.webkitTransform =
-        @miniOverlayer[0].style.transform = 'translate3d(0, ' + top + 'px, 0)'
+        @miniOverlayer[0].style.transform = 'translate3d(0, ' + (n * (@miniEditorView.height() - miniOverLayerHeight)) + 'px, 0)'

   isClicked: false
   mouseDown: (e) ->
     @isClicked = true
     e.preventDefault()
     e.stopPropagation
+    miniOverLayerHeight = @miniOverlayer.height()
+    h = @miniEditorView.height()
     y = e.pageY - @offset().top
-    top = Math.max(y / scaleY - @miniOverlayer.height() / 2, 0)
-    top = Math.min(top, @miniEditorView.height() - @miniOverlayer.height() / 2)
+    y -= @miniScrollView.data('top') * scaleY || 0
+    n = y / (miniOverLayerHeight * scaleY)
+    top = n * miniOverLayerHeight - miniOverLayerHeight / 2
+    top = Math.max(top, 0)
+    top = Math.min(top, @miniScrollView.outerHeight() - miniOverLayerHeight)
     # @note: currently, no animation.
     @editorView.scrollTop(top)
     # Fix trigger `mousewheel` event.


:bug: Fix error raised when clicking on minimap

Fixes #198

--- lib/minimap-view.coffee
+++ lib/minimap-view.coffee
@@ -494,13 +494,15 @@
     e.preventDefault()
     e.stopPropagation()

-    y = e.pageY - @offsetTop
-    top = (y + @renderView.scrollTop()) / @scaleY
-
-    position = @editor.displayBuffer.screenPositionForPixelPosition({top, left: 0})
-    @editor.scrollToScreenPosition(position, center: true)
-
-    # Fix trigger `mousewheel` event.
+    {pageY, target} = e
+
+    y = pageY - target.getBoundingClientRect().top
+    row = Math.floor(y / @getLineHeight()) + @getFirstVisibleScreenRow()
+
+    scrollTop = row * @getTextEditor().getLineHeightInPixels() - @getTextEditor().getHeight() / 2
+
+    @getTextEditor().setScrollTop(scrollTop)
+
     setTimeout =>
       @isClicked = false
     , 377


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/active_bounds.coffee
+++ test/active_bounds.coffee
@@ -14,6 +14,7 @@
   obj.bind 'destroy', ->
     destroySpy = true

-  obj.I.x =
+  obj.I.x = 10000
+

 module()


atom.syntax -> atom.grammars

atom.syntax -> atom.grammars

--- lib/preview-view.coffee
+++ lib/preview-view.coffee
@@ -254,7 +254,7 @@
         # Check if result is a string and therefore source code
         if typeof result is "string"
           outLang = renderer.lang()
-          grammar = atom.syntax.selectGrammar("source.#{outLang}", result)
+          grammar = atom.grammars.selectGrammar("source.#{outLang}", result)
           editor.setGrammar grammar
           editor.setText result
           # Restore Preview's Scroll Positon


On ping, call pong w/ data and dontFailWhenClosed option

--- src/client.coffee
+++ src/client.coffee
@@ -130,7 +130,7 @@
         @socketUrl = null

       @ws.on 'ping', (data, flags) =>
-        @ws.pong
+        @ws.pong data, null, true

       return true


remove dj working, fixes to the rpc calling function related to annoyances with how the Array object works in nodejs.

--- src/client.coffee
+++ src/client.coffee
@@ -102,7 +102,8 @@
 	sendRPC: (name, args, callback)->
 		if (args == undefined)
 			args = []
-		if (typeof args != 'object' && args not instanceof 'Array')
+		if (Object.prototype.toString.apply(args) != "[object Array]")
+			console.log(args, 'is not an array so were wrapping it', args not instanceof Array, args instanceof Array)
 			args = [args]
 		rpcId = ++apiId

@@ -183,15 +184,15 @@
 	leaveBooth: (callback)->
 		@sendRPC "booth.leave", [], callback

-	removeDj: ()->
-		console.log('Not implemented')
+	removeDj: (userid, callback)->
+		@sendRPC "moderate.remove_dj", userid, callback

 	addDj: (callback)->
 		@joinBooth(callback)

 	remDj: (userid, callback)->
-		if (userid && userid != @userid)
-			@joinBooth(callback)
+		if (userid && userid == @userid)
+			@leaveBooth(callback)
 		else
 			@removeDj(userid, callback)


Remove unneeded ?

Remove unneeded ?

--- src/atom.coffee
+++ src/atom.coffee
@@ -584,7 +584,7 @@
   storeWindowBackground: ->
     return if @inSpecMode()

-    workspaceElement = @views?.getView(@workspace)
+    workspaceElement = @views.getView(@workspace)
     backgroundColor = window.getComputedStyle(workspaceElement)['background-color']
     window.localStorage.setItem('atom:window-background-color', backgroundColor)


remove logs

remove logs

--- jsapp/xlform/src/view.rowSelector.coffee
+++ jsapp/xlform/src/view.rowSelector.coffee
@@ -154,7 +154,6 @@
       return

     hide: ->
-      console.log('rowSelector hide')
       @button.removeClass('btn--hidden')
       @line.empty().removeClass("expanded").css "height": 0
       @line.parents(".survey-editor__null-top-row")
@@ -163,7 +162,6 @@
       return

     onSelectNewQuestionType: (evt)->
-      console.log('onSelectNewQuestionType')
       @question_name = @line.find('input').val()
       $rowSelect = $('select.skiplogic__rowselect')
       if $rowSelect.data('select2')


Add comment to keep preview tab for pane

--- lib/tab-bar-view.coffee
+++ lib/tab-bar-view.coffee
@@ -13,6 +13,9 @@

   initialize: (@pane) ->
     @subscriptions = new CompositeDisposable
+
+    @subscriptions.add atom.commands.add atom.views.getView(@pane),
+      'tabs:keep-preview-tab': => tab.clearPreview() for tab in @getTabs()

     @subscriptions.add atom.commands.add @element,
       'tabs:close-tab': => @closeTab()


revert attempt to fix https://github.com/sagemathinc/smc/issues/156 -- latex and tikz issues

--- src/smc-webapp/editor.coffee
+++ src/smc-webapp/editor.coffee
@@ -1978,7 +1978,11 @@

     default_tex_command: () =>
         # errorstopmode recommended by http://tex.stackexchange.com/questions/114805/pdflatex-nonstopmode-with-tikz-stops-compiling
-        return "pdflatex -synctex=1 -interact=errorstopmode '#{@filename_tex}'"
+        # since in some cases things will hang (using )
+        #return "pdflatex -synctex=1 -interact=errorstopmode '#{@filename_tex}'"
+        # However, users hate nostopmode, so we use nonstopmode, which can hang in rare cases with tikz.
+        # See https://github.com/sagemathinc/smc/issues/156
+        return "pdflatex -synctex=1 -interact=nonstopmode '#{@filename_tex}'"

     # runs pdflatex; updates number of pages, latex log, parsed error log
     update_pdf: (opts={}) =>


Add ctrl + enter for projects

Add ctrl + enter for projects

--- src/smc-webapp/projects.cjsx
+++ src/smc-webapp/projects.cjsx
@@ -877,7 +877,7 @@
             value        = {@props.search}
             placeholder  = 'Search for projects...'
             on_change    = {(value)=>@actions('projects').setState(search: value)}
-            on_submit    = {@props.open_first_project}
+            on_submit    = {(_, opts)=>@props.open_first_project(not opts.ctrl_down)}
             button_after = {@delete_search_button()}
         />

@@ -1337,11 +1337,11 @@
             marginBottom : '1ex'
         <div style={projects_title_styles}><Icon name='thumb-tack' /> Projects </div>

-    open_first_project: ->
+    open_first_project: (switch_to=true) ->
         project = @visible_projects()[0]
         if project?
             @actions('projects').setState(search : '')
-            @actions('projects').open_project(project_id: project.project_id, switch_to: true)
+            @actions('projects').open_project(project_id: project.project_id, switch_to: switch_to)
     ###
     # Consolidate the next two functions.
     ###


Fix for using .tab-bar rather than .tabs

--- lib/tab-bar-view.coffee
+++ lib/tab-bar-view.coffee
@@ -131,7 +131,7 @@
     fromPane      = @paneContainer.paneAtIndex(fromPaneIndex)
     toIndex       = @getDropTargetIndex(event)
     toPane        = $(event.target).closest('.pane').view()
-    draggedTab    = fromPane.find(".tabs .sortable:eq(#{fromIndex})").view()
+    draggedTab    = fromPane.find(".tab-bar .sortable:eq(#{fromIndex})").view()
     item          = draggedTab.item

     if toPane is fromPane
@@ -143,8 +143,8 @@
     toPane.focus()

   removeDropTargetClasses: ->
-    rootView.find('.tabs .is-drop-target').removeClass 'is-drop-target'
-    rootView.find('.tabs .drop-target-is-after').removeClass 'drop-target-is-after'
+    rootView.find('.tab-bar .is-drop-target').removeClass 'is-drop-target'
+    rootView.find('.tab-bar .drop-target-is-after').removeClass 'drop-target-is-after'

   getDropTargetIndex: (event) ->
     el = $(event.target).closest('.sortable')


'neft create' now install local dependencies for the project

--- lib/tasks/create.coffee
+++ lib/tasks/create.coffee
@@ -2,6 +2,7 @@

 fs = require 'fs-extra'
 pathUtils = require 'path'
+cp = require 'child_process'

 {log} = Neft

@@ -12,7 +13,12 @@
 		log.error "Destination '#{dest}' already exists"
 		return

+	log "Copy sample project into '#{dest}'"
 	src = pathUtils.resolve(__dirname, '../../node_modules/sample-project')
-	log "Copy '#{src}' into '#{dest}'"
 	fs.copySync src, dest
-	log.ok "Project created"
+	fs.removeSync "#{dest}/.git"
+
+	log "Install modules (may take a while)"
+	cp.execSync "cd #{dest}; npm install"
+
+	log.ok "Project created in '#{dest}'"


return if error when getting versions

return if error when getting versions

--- lib/view_manager.coffee
+++ lib/view_manager.coffee
@@ -131,10 +131,13 @@

   # Load versions of LoL and sources
   championify.version (err, version) ->
+    return if err
     $('#lol_version').text(hlp.spliceVersion(version))
   championgg.version (err, version) ->
+    return if err
     $('#championgg_version').text(version)
   lolflavor.version (err, version) ->
+    return if err
     $('#lolflavor_version').text(version)

   # Load preferences


Make the supervisor container remove itself rather than just exiting when launching a new supervisor.

--- src/supervisor-update.coffee
+++ src/supervisor-update.coffee
@@ -20,9 +20,8 @@
 	.catch (err) ->
 		return process.env.HOSTNAME

-getCurrentContainer = ->
-	getContainerId().then (containerId) ->
-		docker.getContainer(containerId).inspectAsync()
+currentContainer = getContainerId().then (containerId) ->
+		docker.getContainer(containerId)

 startNewSupervisor = (currentSupervisor, waitForSuccess = true) ->
 	console.log('Creating supervisor container:', localImage)
@@ -64,10 +63,18 @@
 			throw e
 	.then ->
 		# We've started the new container, so we're done here! #pray
-		console.log('Exiting to let the new supervisor take over')
-		process.exit()
+		console.log('Removing our container and letting the new supervisor take over')
+		currentContainer.then (container) ->
+			container.removeAsync(force: true)
+		.finally ->
+			# This should never really be reached as docker should already have terminated us,
+			# but in case it hasn't, then we'll just commit harakiri
+			console.log('And process.exiting..')
+			process.exit()

-currentSupervisor = getCurrentContainer().tap (currentSupervisor) ->
+currentSupervisorConfig = currentContainer.then (container) ->
+	container.inspectAsync()
+.tap (currentSupervisor) ->
 	# The volume keys are the important bit.
 	expectedVolumes = _.sortBy(_.keys(config.supervisorContainer.Volumes))
 	actualVolumes = _.sortBy(_.keys(currentSupervisor.Volumes))
@@ -88,7 +95,7 @@
 		restart()

 # This is a promise that resolves when we have fully initialised.
-exports.initialised = currentSupervisor.then (currentSupervisor) ->
+exports.initialised = currentSupervisorConfig.then (currentSupervisor) ->
 	utils = require './utils'
 	JSONStream = require 'JSONStream'


Whoops, actually calc. sum of weights of equipment

--- src/calc.coffee
+++ src/calc.coffee
@@ -50,7 +50,9 @@
 			.map (item) -> weightOf item
 			.reduce ((p, c) -> p+c), 0

-		eqpWeight = (weightOf item for slot,item of subject.equipment)
+		eqpWeight =
+			(weightOf item for slot,item of subject.equipment)
+			.reduce ((p, c) -> p+c), 0

 		(weightOf subject) + invWeight + eqpWeight


Add new unit tests for Redis cache lookup

--- test/document-spec.coffee
+++ test/document-spec.coffee
@@ -41,9 +41,19 @@
         assert.deepEqual res.body, src
         done()

+  it 'should set cache header when using the cache', (done) ->
+    url = '/turer/' + data[51]._id + '?api_key=dnt'
+    req.get(url)
+      .expect(200)
+      .end (err, res) ->
+        throw err if err
+        req.get(url)
+          .expect(200)
+          .expect('X-Cache-Hit', 'true', done)
+
   it 'should set last modified header correctly', (done) ->
-    time = new Date(data[50].endret).getTime().toString()
-    req.get('/turer/' + data[50]._id + '?api_key=dnt')
+    time = new Date(data[51].endret).toUTCString()
+    req.get('/turer/' + data[51]._id + '?api_key=dnt')
       .expect(200)
       .expect('Last-Modified', time, done)

@@ -68,7 +78,7 @@
     doc = _id: new ObjectID().toString(), name: 'kristian'
     req.post('/turer?api_key=dnt').send(doc).expect(201).end (err, res) ->
       throw err if err
-      assert.equal res.body.documents[0], doc._id
+      assert.equal res.body.document._id, doc._id
       req.get('/turer/' + doc._id + '?api_key=dnt')
         .expect(200)
         .end (err, res) ->
@@ -77,10 +87,11 @@
           done()

   it 'should handle rapid fire', (done) ->
+    this.timeout(5000)
+
     count = 0
     for d in data
       request(app).get('/turer/' + d._id + '?api_key=dnt')
-        .expect(200)
         .end (err, res) ->
           throw err if err
           done() if ++count is data.length


Fix incorrect test in Document spec

Fix incorrect test in Document spec

--- test/document_test.coffee
+++ test/document_test.coffee
@@ -1,6 +1,8 @@
 treeSitter = require "tree-sitter"
 assert = require "assert"
 compiler = require ".."
+
+require("segfault-handler").registerHandler()

 describe "Document", ->
   document = null
@@ -17,7 +19,7 @@
   beforeEach ->
     document = new treeSitter.Document()

-  describe "setLanguage", ->
+  describe "::setLanguage(language)", ->
     describe "when the supplied object is not a tree-sitter language", ->
       it "throws an exception", ->
         assert.throws((->
@@ -33,7 +35,25 @@
         document.setLanguage(language)
         assert.equal(null, document.rootNode())

-  describe "setInput", ->
+  describe "::setInput(input)", ->
+    describe "when the language has been set", ->
+      beforeEach ->
+        document.setLanguage(language)
+
+      it "parses the input", ->
+        document.setInput({
+          read: ->
+            @_readIndex++
+            ["st", "u", "ff", "", ""][@_readIndex - 1]
+
+          seek: (n) ->
+            0
+
+          _readIndex: 0,
+        })
+
+        assert.equal("(the_rule)", document.rootNode())
+
     describe "when the supplied object does not implement #seek(n)", ->
       it "throws an exception", ->
         assert.throws((->
@@ -58,18 +78,3 @@
         })
         assert.equal(null, document.rootNode())

-    describe "when the language has been set", ->
-      it "parses the input", ->
-        it "succeeds otherwise", ->
-          document.setInput({
-            read: ->
-              @_readIndex++
-              ["st", "u", "ff"][@_readIndex]
-
-            seek: (n) ->
-              0
-
-            _readIndex: 0,
-          })
-
-          assert.equal("(the_rule)", document.rootNode())


shrink the world

shrink the world

--- src/config.coffee
+++ src/config.coffee
@@ -1,6 +1,6 @@
 exports.config = {
-  size: 640
-  split: 8
+  size: 480
+  split: 6
   grid_path: {
     isa: "closure"
     args: ["size", "split"]


add button deferred support

add button deferred support

--- core/view.coffee
+++ core/view.coffee
@@ -143,10 +143,17 @@
                     i = el.attr 'id'
                     args.unshift i.substring id.length

+                if el.data('after-click') is 'defer'
+                    deferred = me.createDeferred()
+                    el.addClass 'disabled'
+                    deferred.always ->
+                        el.removeClass 'disabled'
+
                 me.loadDeferred.done ->
                     method = me.eventHandlers[value]
                     return me.logger.error "No handler defined with name: #{value}" unless method
-                    method.apply me, args
+                    arr = if deferred then [deferred].concat args else args
+                    method.apply me, arr

         getEl: ->
             if @region then @region.getEl @ else null


Typo fix

Typo fix

--- src/lib/ComponentLoader.coffee
+++ src/lib/ComponentLoader.coffee
@@ -94,7 +94,7 @@
       implementation = component
       instance = new component
     # Direct component instance, return as is
-    if typeof component is 'object' and typeof component.getComponent is 'function'
+    else if typeof component is 'object' and typeof component.getComponent is 'function'
       instance = component.getComponent()
     else
       implementation = require component


fix shift+enter

fix shift+enter

--- editor.coffee
+++ editor.coffee
@@ -76,7 +76,8 @@
         to: CodeMirror.Pos cur.line, cur.ch

 CodeMirror.commands.run = (cm) ->
-  setTimeout(cm.lead_context.run, 1)
+  cm.lead_context.run()
+  add_context()

 CodeMirror.commands.contextHelp = (cm) ->
   cur = cm.getCursor()


Use event.currentTarget instead of event.target (google those terms for explanation)

--- js/inner.coffee
+++ js/inner.coffee
@@ -9,7 +9,7 @@

 finishAjax = (event, attrName, buttonName) ->
   if event
-    t = $(event.target)
+    t = $(event.currentTarget)
     done = t.attr(attrName) or buttonName
     textVal t, done

@@ -18,41 +18,38 @@
       t.removeClass "disabled"
     setTimeout(func, 1500)

-$(document).ajaxSuccess((ev, xhr, options) -> finishAjax(xhr.event, "data-success-text", "Saved"))
-
-$(document).ajaxError((ev, xhr, status, errorThrown) ->
+$(document).ajaxSuccess (ev, xhr, options) ->
+  finishAjax(xhr.event, "data-success-text", "Saved")
+
+$(document).ajaxError (ev, xhr, status, errorThrown) ->
   finishAjax(xhr.event, "data-failed-text", "Failed")
-
   if xhr.responseText.indexOf("<!DOCTYPE") is 0
     notifyError "An unknown error occurred: (#{xhr.status} - #{xhr.statusText})."
   else
     notifyError (xhr.responseText or xhr.statusText)
-)
+
+$(document).ajaxSend (ev, xhr, options) ->
+  xhr.event = options.event
+  if xhr.event
+    t = $(xhr.event.currentTarget)
+    t.addClass "disabled"
+    # change to loading text
+    loading = t.attr("data-loading-text") or "..."
+    xhr.event.savedText = textVal t
+    textVal t, loading
+
+
+# Make the buttons disabled when clicked
+$.ajaxSetup
+  contentType: "application/json"
+  accepts: {json: "application/json"}
+  dataType: "json"

 ko.observableArray["fn"].setIndex = (index, newItem) ->
   @valueWillMutate()
   result = @()[index] = newItem
   @valueHasMutated()
   result
-
-$(document).ajaxSend((ev, xhr, options) ->
-
-  xhr.event = options.event
-  if xhr.event
-    t = $(xhr.event.target)
-    t.addClass "disabled"
-    # change to loading text
-    loading = t.attr("data-loading-text") or "..."
-    xhr.event.savedText = textVal t
-    textVal t, loading
-)
-
-# Make the buttons disabled when clicked
-$.ajaxSetup
-  contentType: "application/json"
-  accepts: {json: "application/json"}
-  dataType: "json"
-

 class Obj
   constructor: (json={}, defaults={}) ->


Sort plans by price

Sort plans by price

--- js/inner.coffee
+++ js/inner.coffee
@@ -403,7 +403,10 @@

     @availablePlans = @komp =>
-      (v for k,v of @plans())
+      ap = (v for k,v of @plans())
+      ap.sort (a, b) ->
+        b.price - a.price # most expensive first
+      ap

     # team billing
     @collaborators = ko.observable(null)


remove console.log statement

remove console.log statement

--- test/running-tests.coffee
+++ test/running-tests.coffee
@@ -1,7 +1,6 @@
 ran = {}
 stop = {}
 fn1 = (_, context) ->
-  console.log 'fn1 context: ', context
   if context?.running is true
     ran.fn1 = true
     stop.fn1 = false


Show backtraces for Up.js errors

Show backtraces for Up.js errors

--- lib/assets/javascripts/up/util.js.coffee
+++ lib/assets/javascripts/up/util.js.coffee
@@ -133,8 +133,8 @@
     $error.addClass('up-error')
     $error.text(asString)
     # alert "#{asString}\n\nOpen the developer console for details."
-    throw asString
-
+    throw new Error(asString)
+
   CONSOLE_PLACEHOLDERS = /\%[odisf]/g

   stringifyConsoleArgs = (args) ->


conv last seen: skip useless avatars

conv last seen: skip useless avatars

--- src/ui/views/messages.coffee
+++ src/ui/views/messages.coffee
@@ -137,12 +137,13 @@
                             div class: 'seen-list', () ->
                                 for e in events
                                     for chat_id in last_seen_chat_ids_with_event(last_seen, e)
+                                        skip = entity.isSelf(chat_id) or (chat_id == u.cid)
                                         drawSeenAvatar(
                                             entity[chat_id],
                                             e.event_id,
                                             viewstate,
                                             entity
-                                        ) if not entity.isSelf(chat_id)
+                                        ) if not skip

     # Go through all the participants and only show his last seen status
     if c?.current_participant?


33% translation completed

33% translation completed

--- def/zh-cn/settings.cson
+++ def/zh-cn/settings.cson
@@ -59,64 +59,58 @@
     notes: [
       {
         _id: 'core-settings-note'
-        html: '这些是 Atom 的核心设置，与文本编辑无关。不同的插件包可能有它们自己的附
-              加设置，你可以在插件包列表中找到这
-              些设置。'
+        html: '这些是 Atom 的核心设置，与文本编辑无关。不同插件包可能有附加设置，你可以在插件包列表中找到这些设置。'
       }
       {
         _id: 'editor-settings-note'
-        html: '这些是有关文本编辑的设置。其中一些设置可以被每种语言的设置覆盖，你可以在
-               插件包列表中点击这些插件包，
-               查看这些语法的设置。'
+        html: '这些是有关文本编辑的设置。其中一些设置可以被编程语言的设置覆盖，你可以在插件包列表中点击这些插件包，查看这些语法的设置。'
       }
     ]
     controls: [
       {
         _id: 'core.allowPendingPaneItems'
-        title: "Allow Pending PaneItems"
-        desc: "Allow items to be previewed without adding them to a pane
-               permanently, such as when single clicking files in the tree view."
+        title: "启用预览标签页"
+        desc: "允许使用预览标签页查看文件，不需要将其加入窗格，例如在树状图中单击文件时打开预览标签页。"
       }
       {
         _id: 'core.audioBeep'
-        title: "Audio Beep"
-        desc: "Trigger the system's beep sound when certain actions cannot be
-               executed or there are no results."
+        title: "提示音"
+        desc: "当某些操作不能执行，或是没有执行结果时，触发系统提示音。"
       }
       {
         _id: 'core.automaticallyUpdate'
-        title: "Automatically Update"
-        desc: "Automatically update Atom when a new release is available."
+        title: "自动更新"
+        desc: "当有可用的更新时，自动更新 Atom 。"
       }
       {
         _id: 'core.autoHideMenuBar'
         title: "自动隐藏菜单栏"
-        desc: "按 Alt 以切换菜单栏。目前仅支持Windows和Linux."
+        desc: "按 Alt 以切换菜单栏。仅支持 Windows 和 Linux 操作系统。"
       }
       {
         _id: 'core.closeEmptyWindows'
-        title: "Close Empty Windows"
-        desc: "When a window with no open tabs or panes is given the 'Close Tab' command, close that window."
+        title: "关闭空窗口"
+        desc: "当窗口内没有打开的标签页或窗格时，“关闭标签页”选项会关闭该窗口。"
       }
       {
         _id: 'core.destroyEmptyPanes'
-        title: "Destroy Empty Panes"
-        desc: "When the last tab of a pane is closed, remove that pane as well."
+        title: "自动移除空窗格"
+        desc: "当窗格内的最后一个标签页被关闭时，移除该窗格。"
       }
       {
         _id: 'core.excludeVcsIgnoredPaths'
-        title: "Exclude VCS Ignored Paths"
-        desc: "Files and directories ignored by the current project's VCS system will be ignored by some packages, such as the fuzzy finder and find and replace. For example, projects using Git have these paths defined in the .gitignore file. Individual packages might have additional config settings for ignoring VCS ignored files and folders."
+        title: "排除版本控制系统（VCS）忽略的路径"
+        desc: "让某些插件包例如：模糊查找（fuzzy finder）、查找和替换（find and replace）,忽略当前项目中版本控制系统所忽略的文件和路径。例如，使用 Git 的项目会在 .gitignore 文件里定义所忽略的路径。一些插件包可能后提供附加设置来选择是否忽略这些路径。"
       }
       {
         _id: 'core.fileEncoding'
         title: "文件编码"
-        desc: "Default character set encoding to use when reading and writing files."
+        desc: "读写文件时默认使用的字符编码。"
       }
       {
         _id: 'core.followSymlinks'
-        title: "Follow Symlinks"
-        desc: "Follow symbolic links when searching files and when opening files with the fuzzy finder."
+        title: "使用软链接（Symbolic link）"
+        desc: "当查找或者在模糊查找中打开文件时遵循软链接（symbolic links）导向。"
       }
       {
         _id: 'core.ignoredNames'
@@ -125,8 +119,8 @@
       }
       {
         _id: 'core.openEmptyEditorOnStart'
-        title: "Open Empty Editor On Start"
-        desc: "Automatically open an empty editor on startup."
+        title: "启动时打开空编辑器"
+        desc: "启动时自动打开一个空白的文本编辑器。"
       }
       {
         _id: 'core.packagesWithKeymapsDisabled'
@@ -134,8 +128,8 @@
       }
       {
         _id: 'core.projectHome'
-        title: "Project Home"
-        desc: "The directory where projects are assumed to be located. Packages created using the Package Generator will be stored here by default."
+        title: "项目根目录"
+        desc: "此文件夹是项目的默认位置。使用 Package Generator 创建的插件包默认放在此文件夹内。"
       }
       {
         _id: 'core.reopenProjectMenuCount'
@@ -333,54 +327,54 @@
   }
   keymaps: {
     notes: {
-      text1: "You can override these keybindings by copying "
-      text2: "and pasting them into "
-      text3: "your keymap file"
-      text4: ""   # optional
+      text1: "你可以复制这些快捷键设置，"
+      text2: "然后粘贴至"
+      text3: "你的快捷键配置文件"
+      text4: "，来覆盖这些快捷键设置"   # optional
       _template: "${text1} <icon /> ${text2} ${text3} ${text4}"
     }
-    "search-bar-placeholder": "Search keybindings"
-    "table-header-keystroke": "Keystroke"
-    "table-header-command": "Command"
-    "table-header-source": "Source"
-    "table-header-selector": "Selector"
+    "search-bar-placeholder": "搜索快捷键设置"
+    "table-header-keystroke": "键盘输入"
+    "table-header-command": "命令"
+    "table-header-source": "来源"
+    "table-header-selector": "选择器"
   }
   packages: {
   }
   themes: {
     notes: {
-      text1: "You can also style Atom by editing "
-      text2: "your stylesheet"
-      text3: ""   # optional
+      text1: "你也可以通过编辑"
+      text2: "你的样式表"
+      text3: "来调整 Atom 的外观"   # optional
       _template: "${text1} ${text2} ${text3}"
     }
-    title1: "UI Theme"
-    description1: "This styles the tabs, status bar, tree view, and dropdowns"
-    title2: "Syntax Theme"
-    description2: "This styles the text inside the editor"
+    title1: "界面主题"
+    description1: "标签页、状态栏、树状图、下拉菜单的外观"
+    title2: "语法主题"
+    description2: "编辑器中文本的外观"
   }
   updates: {
-    "check-updates": "Check for Updates"
-    "update-all": "Update All"
-    "checking-updates": "Checking for updates..."
-    "all-updated-message": "All of your installed packages are up to date!"
+    "check-updates": "检查更新"
+    "update-all": "安装所有更新"
+    "checking-updates": "正在检查更新..."
+    "all-updated-message": "所有已安装的插件包都是最新版本了！"
   }
   install: {
     notes: {
-      text1: "Packages are published to "
-      text2: " and are installed to "
+      text1: "插件包会被发布到 "
+      text2: " 且安装在文件夹 "
       text3: ""   # optional
       _template: "${text1}atom.io${text2} /path/to/pkg ${text3}"
     }
-    "searchbar-placeholder": "Search packages"
-    "btn-packages": "Packages"
-    "btn-themes": "Themes"
+    "searchbar-placeholder": "搜索插件包或主题"
+    "btn-packages": "插件包"
+    "btn-themes": "主题"
   }
   "control-btns":{
-    install: "Install"
-    update: "Update to"
-    setting: "Settings"
-    uninstall: "Uninstall"
-    disable: "Disable"
-    enable: "Enable"
-  }
+    install: "安装"
+    update: "更新至"
+    setting: "设置"
+    uninstall: "卸载"
+    disable: "禁用"
+    enable: "启用"
+  }


fix minor bug

fix minor bug

--- lib/md.coffee
+++ lib/md.coffee
@@ -1009,6 +1009,7 @@

     if tocBracketEnabled # [TOC]
       tocObject = toc(tocConfigs.headings, {ordered: false, depthFrom: 1, depthTo: 6, tab: markdownPreview?.editor?.getTabText() or '\t'})
+      DISABLE_SYNC_LINE = true # otherwise tocHtml will break scroll sync.
       tocHtml = md.render(tocObject.content)
       html = html.replace /^\s*\[MPETOC\]\s*/gm, tocHtml


Remove redundancy expect

Remove redundancy expect

--- spec/rubocop-auto-correct-spec.coffee
+++ spec/rubocop-auto-correct-spec.coffee
@@ -46,7 +46,6 @@
       buffer.setText("{ :atom => 'A hackable text editor for the 21st Century' }\n")

     it "manually run", ->
-      expect(buffer.getText()).toBe "{ :atom => 'A hackable text editor for the 21st Century' }\n"
       atom.commands.dispatch workspaceElement, 'rubocop-auto-correct:current-file'

       bufferChangedSpy = jasmine.createSpy()
@@ -57,7 +56,6 @@
         expect(buffer.getText()).toBe "{ atom: 'A hackable text editor for the 21st Century' }\n"

     it "auto run", ->
-      expect(buffer.getText()).toBe "{ :atom => 'A hackable text editor for the 21st Century' }\n"
       atom.config.set('rubocop-auto-correct.autoRun', true)
       editor.save()


remove global.IN_TESTING_ENVIRONMENT definition in test-setup.coffee

--- test-setup.coffee
+++ test-setup.coffee
@@ -24,7 +24,6 @@
 global.eq      = (a, b, msg) -> ok egal(a, b), msg ? "#{inspect a} === #{inspect b}"
 global.arrayEq = (a, b, msg) -> ok arrayEgal(a,b), msg ? "#{inspect a} === #{inspect b}"

-global.IN_TESTING_ENVIRONMENT = yes
 global.FIXTURES_DIR = path.join __dirname, 'fixtures'

 sfs = fs.scoped FIXTURES_DIR


enlarge "bucegi" area

enlarge "bucegi" area


Remove verbose flag.

Remove verbose flag.

--- src/deploy.coffee
+++ src/deploy.coffee
@@ -9,11 +9,10 @@
 pulsarJobConfirmList = require('./pulsar-job-confirm-list')

 module.exports = (robot) ->
-  robot.respond /deploy (-v|)\s?([^\s]+) ([^\s]+)$/i, (chat) ->
+  robot.respond /deploy ([^\s]+) ([^\s]+)$/i, (chat) ->
     return unless robot.isAuthorized(chat)
-    app = chat.match[2]
-    env = chat.match[3]
-    isVerbose = chat.match[1] == '-v'
+    app = chat.match[1]
+    env = chat.match[2]

     pending = pulsarApi.createJob(app, env, 'deploy:pending')
     pending.on('close', ()->
@@ -27,10 +26,6 @@
       ).on('error', () ->
         chat.send "#{@} failed due to #{JSON.stringify(error)}"
       )
-      if isVerbose
-        deploy.on('change', (output)->
-          chat.send output
-        )
       pulsarJobConfirmList.add(chat, deploy)
     ).on('error', (error)->
       chat.send "#{@} failed due to #{JSON.stringify(error)}"


[TERMINAL] Added help by default

[TERMINAL] Added help by default

--- src/coffee/terminal.coffee
+++ src/coffee/terminal.coffee
@@ -27,6 +27,8 @@

 class @Terminal extends App

+  openedOnce: false
+
   # Construct terminal from div container
   #
   # $screen [jQuery] jQuery element for the terminal-screen div
@@ -73,6 +75,10 @@

   # Focus on terminal prompt
   onOpen: =>
+    if not @openedOnce
+      @openedOnce = true
+      @print "Pour voir les commandes de bases, entrez help"
+
     # set initial focus and prevent losing focus by brute-force
     @$promptInput.focus()
     @$promptInput.on "blur.autofocus", =>


Remove pseudo message

Remove pseudo message

--- _includes/coffeescripts/contact.coffee
+++ _includes/coffeescripts/contact.coffee
@@ -26,7 +26,9 @@
         data: $form.serialize()
         success: (data) ->
           # Replace the form with the repsonce html
-          # This is either the success msg or the form w/ validation msgs
+          # This is either
+          #   * the success message
+          #   * the form w/ validation messages
           $form.replaceWith(data)
         error: (x, y, z) ->
           $form.after('
Ett fel inträffade, vänligen försök senare.
')


tweaks

tweaks

--- test/functional/druidFunctional.mocha.coffee
+++ test/functional/druidFunctional.mocha.coffee
@@ -603,11 +603,11 @@

     it.skip "works with absolute value split", (testComplete) ->
       ex = ply()
-      .apply('AbsSplit',
-        $('wiki').split($('commentLength').absolute(), 'AbsCommentLength')
-        .sort('$AbsCommentLength', 'descending')
-        .limit(3)
-      )
+        .apply('AbsSplit',
+          $('wiki').split($('commentLength').absolute(), 'AbsCommentLength')
+          .sort('$AbsCommentLength', 'descending')
+          .limit(4)
+        )

       basicExecutor(ex).then((result) ->
         expect(result.toJS()).to.deep.equal([
@@ -622,13 +622,16 @@
               {
                 "AbsCommentLength": 100
               }
-            ]
-          }
-        ])
-        testComplete()
-      ).done()
-
-    it "can timebucket a time column that is the timeAttribute one", (testComplete) ->
+              {
+                "AbsCommentLength": 101
+              }
+            ]
+          }
+        ])
+        testComplete()
+      ).done()
+
+    it "can timeBucket a time column that is the timeAttribute one", (testComplete) ->
       ex = ply()
         .apply('Time',
           $("wiki").split($("time").timeBucket('PT1H', 'Etc/UTC'), 'TimeCol')
@@ -660,7 +663,7 @@
         testComplete()
       ).done()

-    it "can timebucket a time column thats not the timeAttribute one", (testComplete) ->
+    it "can timeBucket a time column that's not the timeAttribute one", (testComplete) ->
       ex = ply()
       .apply('TimeLater',
         $("wiki").split($("sometimeLater").timeBucket('PT1H', 'Etc/UTC'), 'SometimeLater')


Make Editor::onDidChangeGrammar emit from Editor

Seems like the initial implementation was to handle the event, unfold
all then, re-emit the grammar-changed event.

--- src/editor.coffee
+++ src/editor.coffee
@@ -305,7 +305,7 @@
     @displayBuffer.onDidChangeSoftWrapped(callback)

   onDidChangeGrammar: (callback) ->
-    @displayBuffer.onDidChangeGrammar(callback)
+    @emitter.on 'did-change-grammar', callback

   onDidChangeCharacterWidths: (callback) ->
     @displayBuffer.onDidChangeCharacterWidths(callback)
@@ -2534,6 +2534,7 @@
   handleGrammarChange: ->
     @unfoldAll()
     @emit 'grammar-changed'
+    @emitter.emit 'did-change-grammar'

   handleMarkerCreated: (marker) =>
     if marker.matchesAttributes(@getSelectionMarkerAttributes())


Now making statusCode available as code on PubSubFailureResponse

--- src/errors.coffee
+++ src/errors.coffee
@@ -44,8 +44,8 @@
     super message, cause, statusCode, details

 class PubSubFailureResponse extends PubSubError
-  constructor: (message, cause, statusCode, details, @record) ->
-    super message, cause, statusCode, details
+  constructor: (message, cause, @code, details, @record) ->
+    super message, cause, code, details

 module.exports =
   ApiError: ApiError


ie makes their hex values lowercase

ie makes their hex values lowercase

--- src/format.coffee
+++ src/format.coffee
@@ -75,7 +75,7 @@
   constructor: (@root, @keyName, @cssName, @styles, @matchFn) ->
     @matchFn or= (cssValue) =>
       for key,value of @styles
-        return key if value == cssValue
+        return key if value.toUpperCase() == cssValue.toUpperCase()
       return false
     super

@@ -175,7 +175,7 @@
   @matchColor: (cssValue) ->
     color = Scribe.Format.Color.normalizeColor(cssValue)
     for key,value of @styles
-      return key if value == color
+      return key if value.toUpperCase() == color.toUpperCase()
     return false

   constructor: (@root) ->


calculate and draw links

calculate and draw links

--- layout.coffee
+++ layout.coffee
@@ -8,6 +8,7 @@
     topNodes = []
     nodes = []
     nodeMap = {}
+    links = []

     for topState in tree
         walk topState, (state, parent) ->
@@ -26,7 +27,13 @@
             else
                 topNodes.push(node)

-    r = 40
+    for topState in tree
+        walk topState, (state) ->
+            for tr in state.transitions or []
+                links.push(
+                    source: nodeMap[state.name]
+                    target: nodeMap[tr.target]
+                )

     cell = svg.selectAll('.cell')
         .data(nodes)
@@ -44,6 +51,11 @@

     cell.append('text')
         .text((node) -> node.name)
+
+    link = svg.selectAll('.link')
+        .data(links)
+      .enter().append('line')
+        .attr('class', 'link')

     force = d3.layout.force()
         .charge(0)
@@ -83,6 +95,11 @@
             .attr('width', (node) -> node.width)
             .attr('height', (node) -> node.height)

+        link.attr('x1', (d) -> d.source.x)
+            .attr('y1', (d) -> d.source.y)
+            .attr('x2', (d) -> d.target.x)
+            .attr('y2', (d) -> d.target.y)
+

 doCollisions = (children) ->
     if children.length


Specs for the unsupported state

Specs for the unsupported state

--- spec/about-spec.coffee
+++ spec/about-spec.coffee
@@ -43,7 +43,7 @@
         expect(atom.clipboard.read()).toBe atom.getVersion()

   describe "updates", ->
-    [aboutElement] = []
+    [aboutElement, updateModel] = []

     beforeEach ->
       jasmine.attachToDOM(workspaceElement)
@@ -52,33 +52,49 @@
         atom.workspace.getActivePaneItem()
       runs ->
         aboutElement = workspaceElement.querySelector('.about')
+        updateModel = $(aboutElement).view().update

-    it "shows the correct panels when the app checks for updates and there is no update available", ->
-      expect(aboutElement.querySelector('.app-up-to-date')).toBeVisible()
+    describe "when the updates are not supported by the platform", ->
+      it "hides the auto update UI", ->
+        spyOn(atom.autoUpdater, 'platformSupportsUpdates').andReturn(false)
+        updateModel.resetState()
+        expect(aboutElement.querySelector('.about-updates')).not.toBeVisible()

-      MockUpdater.checkForUpdate()
-      expect(aboutElement.querySelector('.app-up-to-date')).not.toBeVisible()
-      expect(aboutElement.querySelector('.app-checking-for-updates')).toBeVisible()
+    describe "when updates are supported by the platform", ->
+      beforeEach ->
+        spyOn(atom.autoUpdater, 'platformSupportsUpdates').andReturn(true)
+        updateModel.resetState()

-      MockUpdater.updateNotAvailable()
-      expect(aboutElement.querySelector('.app-up-to-date')).toBeVisible()
-      expect(aboutElement.querySelector('.app-checking-for-updates')).not.toBeVisible()
+      it "shows the auto update UI", ->
+        expect(aboutElement.querySelector('.about-updates')).toBeVisible()

-    it "shows the correct panels when the app checks for updates and an update is downloaded", ->
-      expect(aboutElement.querySelector('.app-up-to-date')).toBeVisible()
+      it "shows the correct panels when the app checks for updates and there is no update available", ->
+        expect(aboutElement.querySelector('.about-default-update-message')).toBeVisible()

-      MockUpdater.checkForUpdate()
-      expect(aboutElement.querySelector('.app-up-to-date')).not.toBeVisible()
-      expect(aboutElement.querySelector('.app-checking-for-updates')).toBeVisible()
+        MockUpdater.checkForUpdate()
+        expect(aboutElement.querySelector('.app-up-to-date')).not.toBeVisible()
+        expect(aboutElement.querySelector('.app-checking-for-updates')).toBeVisible()

-      MockUpdater.downloadUpdate()
-      expect(aboutElement.querySelector('.app-checking-for-updates')).not.toBeVisible()
-      expect(aboutElement.querySelector('.app-downloading-update')).toBeVisible()
+        MockUpdater.updateNotAvailable()
+        expect(aboutElement.querySelector('.app-up-to-date')).toBeVisible()
+        expect(aboutElement.querySelector('.app-checking-for-updates')).not.toBeVisible()

-      MockUpdater.finishDownloadingUpdate(42)
-      expect(aboutElement.querySelector('.app-downloading-update')).not.toBeVisible()
-      expect(aboutElement.querySelector('.app-update-available-to-install')).toBeVisible()
-      expect(aboutElement.querySelector('.app-update-available-to-install .about-updates-version').textContent).toBe('42')
+      it "shows the correct panels when the app checks for updates and an update is downloaded", ->
+        expect(aboutElement.querySelector('.about-default-update-message')).toBeVisible()
+
+        MockUpdater.checkForUpdate()
+        expect(aboutElement.querySelector('.app-up-to-date')).not.toBeVisible()
+        expect(aboutElement.querySelector('.app-checking-for-updates')).toBeVisible()
+
+        MockUpdater.downloadUpdate()
+        expect(aboutElement.querySelector('.app-checking-for-updates')).not.toBeVisible()
+        expect(aboutElement.querySelector('.app-downloading-update')).toBeVisible()
+
+        MockUpdater.finishDownloadingUpdate(42)
+        expect(aboutElement.querySelector('.app-downloading-update')).not.toBeVisible()
+        expect(aboutElement.querySelector('.app-update-available-to-install')).toBeVisible()
+        expect(aboutElement.querySelector('.app-update-available-to-install .about-updates-version').textContent).toBe('42')
+

 describe "the status bar", ->
   workspaceElement = null


:lipstick: Cleanup

:lipstick: Cleanup

--- spec/jumpy-spec.coffee
+++ spec/jumpy-spec.coffee
@@ -210,9 +210,8 @@
         it "the beacon animation class is removed", ->
             atom.commands.dispatch workspaceElement, 'jumpy:a'
             waitsFor ->
-                setTimeout ->
+                ->
                     atom.commands.dispatch workspaceElement, 'jumpy:c'
-                ,100 + 10 # max default I'd probably use + a buffer
             runs ->
                 expect(textEditorElement.shadowRoot
                     .querySelectorAll('.beacon').length)


Fix issue with challenge failure on HTTP


test for manifest with bundle. checked bundle hash

--- tests/test_builder.coffee
+++ tests/test_builder.coffee
@@ -1192,6 +1192,39 @@
         builder = spa.Builder.from_config("/testimonial/spa.yaml")

         builder.build()
+
+        expect(fs.existsSync("/testimonial/manifest.json")).to.be.true
+        manifest = JSON.parse(fs.readFileSync("/testimonial/manifest.json", encoding: "utf8"))
+
+        expect(manifest).to.have.properties
+            modules: -> @that.to.be.an("Array").with.length(4).and.properties
+                0: -> @that.to.have.properties
+                    id: -> @that.equals("d")
+                    deps: -> @that.deep.equals({})
+                    type: -> @that.equals("cjs")
+                    url: -> @that.equals("http://127.0.0.1:8010/d.js")
+                1: -> @that.to.have.properties
+                    id: -> @that.equals("c")
+                    deps: -> @that.deep.equals
+                        "./d.js": "d"
+                    type: -> @that.equals("cjs")
+                    url: -> @that.equals("http://127.0.0.1:8010/c.js")
+                2: -> @that.to.have.properties
+                    id: -> @that.equals("b")
+                    deps: -> @that.deep.equals
+                        "./c.js": "c"
+                    type: -> @that.equals("cjs")
+                    url: -> @that.equals("http://127.0.0.1:8010/b.js")
+                3: -> @that.to.have.properties
+                    id: -> @that.equals("a")
+                    deps: -> @that.deep.equals
+                        "./b.js": "b"
+                    type: -> @that.equals("cjs")
+                    url: -> @that.equals("http://127.0.0.1:8010/a.js")
+            bundle: -> @that.to.have.properties
+                hash: -> @that.equals("e260fa41f30a6999b14fcd135cb93b0d")
+                url: -> @that.equals("http://127.0.0.1:8010/bundle.js")
+
         expect(fs.existsSync("/testimonial/hosting.json")).to.be.true
         hosting = JSON.parse(fs.readFileSync("/testimonial/hosting.json", encoding: "utf8"))


DC updates

DC updates

--- webapp/components/about-me/about-me.coffee
+++ webapp/components/about-me/about-me.coffee
@@ -279,14 +279,17 @@
 		compile: (element, attributes) ->
 			skills = Skills.getSkills()
 			yearExtent = d3.extent(skills, (skill) -> skill.year)
-			yearExtent[0] = Math.min(2000, yearExtent[0])
+			yearExtent[0] = Math.max(2000, yearExtent[0])
+			latestYear = yearExtent[1]
 			skillsCrossFilter = crossfilter(skills)
 			typeDimension = skillsCrossFilter.dimension((skill)->skill.type)
 			typeDimension.filter("Language")
 			skillsNameDimension = skillsCrossFilter.dimension( (skill) ->
 				skill.skillName
 			)
-			skillsNameGroup = skillsNameDimension.group().reduceSum( (skill) -> skill.skillLevel)
+			skillsNameGroup = skillsNameDimension.group().reduceSum( (skill) ->
+				return Math.max(0.5, (skill.year - (latestYear - 6))) * skill.skillLevel
+			)
 			skillsYearDimension = skillsCrossFilter.dimension((skill)->
 				[skill.skillName, skill.year]
 			)
@@ -303,7 +306,10 @@
 				.group(skillsNameGroup)
 				.transitionDuration(500)
 				.colors( (skill) ->
-						colorScale(skill)
+					colorScale(skill)
+				)
+				.title( (skill) ->
+					skill.key
 				)
 			skillsSeriesChart = dc.seriesChart('#skills-series-chart')
 				.width(700)


DRY date calculation in BOM js

DRY date calculation in BOM js

This also minimises the risk of values changing in the meantime.

--- app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee
+++ app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee
@@ -26,18 +26,21 @@
       $scope.startDate = moment(OrderCycles.byID[$scope.orderCycleFilter].orders_open_at).format('YYYY-MM-DD')
       $scope.endDate = moment(OrderCycles.byID[$scope.orderCycleFilter].orders_close_at).startOf('day').format('YYYY-MM-DD')

+    formatted_start_date = moment($scope.startDate).format()
+    formatted_end_date = moment($scope.endDate).add(1,'day').format()
+
     RequestMonitor.load $scope.orders = Orders.index(
       "q[state_not_eq]": "canceled",
       "q[completed_at_not_null]": "true",
-      "q[completed_at_gteq]": "#{moment($scope.startDate).format()}",
-      "q[completed_at_lt]": "#{moment($scope.endDate).add(1,'day').format()}"
+      "q[completed_at_gteq]": formatted_start_date,
+      "q[completed_at_lt]": formatted_end_date
     )

     RequestMonitor.load $scope.lineItems = LineItems.index(
       "q[order][state_not_eq]": "canceled",
       "q[order][completed_at_not_null]": "true",
-      "q[order][completed_at_gteq]": "#{moment($scope.startDate).format()}",
-      "q[order][completed_at_lt]": "#{moment($scope.endDate).add(1,'day').format()}"
+      "q[order][completed_at_gteq]": formatted_start_date,
+      "q[order][completed_at_lt]": formatted_end_date
     )

     unless $scope.initialized


Make controls cloneable

Make controls cloneable

--- src/config/controls.coffee
+++ src/config/controls.coffee
@@ -13,7 +13,6 @@
     half = scale / 2
     "m#{-half},#{-half}  h#{scale} v#{scale} h-#{scale } v#{-scale}"
   BUTTON: {
-    _CSS: "button"
     i: (world) -> world.get('_INDEX') / 2.0
     j: 0
   }
@@ -39,6 +38,7 @@
     {
       _LABEL: "turtle_controls"
       _AUTHORITY: (world) -> world.get('BUTTON')
+      _CSS: "cloneable"
       _CHILDREN: [
         {
           _LABEL: "current"


better debug events

better debug events

--- src/hinoki.coffee
+++ src/hinoki.coffee
@@ -94,7 +94,7 @@
           event: 'valueIsAlreadyAwaitingResolution'
           path: path
           resolution: resolution
-          value: promiseAwaitingResolution
+          promise: promiseAwaitingResolution
         }
         return promiseAwaitingResolution


Agent pie: edit pointer transition

Agent pie: edit pointer transition

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -302,32 +302,24 @@
             ).style("opacity", 1)

         pointers = pointerGroup.selectAll('path.pointer').data(piedata)
+        pointers.transition().duration(500).style('opacity',0)
         pointers.enter()
             .append('path')
             .attr('class', 'pointer')
             .style('fill', 'none')
             .style('stroke', 'black')
-            #.attr('stroke-width', 1)
+            .attr('stroke-width', 1)
             .attr('marker-end', 'url(#circ)')
             #.style('opacity', 0)
         pointers.exit().remove()

-        #pointers.transition().duration(2000).attr('d', (d) ->
-        #        console.log('label transition d: ',d)
-        #        if d.cx > d.l
-        #            return 'M' + (d.l + 2) + ',' + d.b + 'L' + (d.r - 2) + ',' + d.b + ' ' + d.cx + ',' + d.cy
-        #        else
-        #            return 'M' + (d.r - 2) + ',' + d.b + 'L' + (d.l + 2) + ',' + d.b + ' ' + d.cx + ',' + d.cy
-        #    )
-        #    #.style('opacity', 1)
-        pointers.transition().duration(2000).attr("d", (d) ->
+        pointers.transition().delay(1000).duration(500).attr('d', (d) ->
                 if d.cx > d.l
-                    console.log('if: ','M' + (d.l + 2) + ',' + d.b + 'L' + (d.r - 2) + ',' + d.b + ' ' + d.cx + ',' + d.cy)
                     'M' + (d.l + 2) + ',' + d.b + 'L' + (d.r - 2) + ',' + d.b + ' ' + d.cx + ',' + d.cy
                 else
-                    console.log('else: ','M' + (d.r - 2) + ',' + d.b + 'L' + (d.l + 2) + ',' + d.b + ' ' + d.cx + ',' + d.cy)
                     'M' + (d.r - 2) + ',' + d.b + 'L' + (d.l + 2) + ',' + d.b + ' ' + d.cx + ',' + d.cy
             )
+            .style('opacity',1)

         # Display total count
         totalTickets = 0


Agent pie: start/end angle pie transition work

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -6,7 +6,7 @@
         #

     onData: (data) ->
-        oldPieData = piedata
+        @oldPieData = ''

         #$(@node).fadeOut().fadeIn()
         @container = $(@node).parent()
@@ -152,8 +152,8 @@

         path.transition().duration(750).attrTween('d', (d,i) ->
             `var i`
-            theOldDataInPie = oldPieData ? piedata
-            console.log('oldPieData',oldPieData)
+            theOldDataInPie = @oldPieData ? piedata
+            console.log('@oldPieData',@oldPieData)
             # Interpolate the arcs in data space
             s0 = undefined
             e0 = undefined
@@ -363,5 +363,5 @@
             .attr('font-size',(if typeof totalTickets isnt 'string' then radiusi + 'px' else '2em'))
             .style('opacity', 1)

-        oldPieData = piedata
+        @oldPieData = piedata
         return


Agent pie: test globalizing variables

Agent pie: test globalizing variables

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -19,10 +19,10 @@
         @height = (Dashing.widget_base_dimensions[1] * @container.data('sizey')) - 120

         # Calculated min dimension of the SVG area
-        @radius = Math.min(@width, height) / 2
+        @radius = Math.min(@width, @height) / 2

         # Outer radius of the pie
-        @radiuso = Math.min(@width, height) / 3
+        @radiuso = Math.min(@width, @height) / 3

         # Inner radius of the pie (zero = pie, non-zero = donut)
         @radiusi = @radiuso * 2 / 3


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -242,16 +242,20 @@
             .attrTween("d", (d,i) ->
                 i = d3.interpolate({startAngle: 0,endAngle: 0}, {startAngle: d.startAngle,endAngle: d.endAngle})
                 return (t) ->
-                    b = i(t);
+                    b = i(t)
                     return arc(b)
             )

-        #this.path.exit()
-        #    .transition()
-        #    .duration(300)
-        #    .attrTween("d", that.removePieTween)
-        #    .remove();
         path.exit()
+            .transition()
+            .duration(300)
+            .attrTween("d", (d,i) ->
+                i = d3.interpolate({startAngle: d.startAngle,endAngle: d.endAngle},{startAngle: 2 * Math.PI,endAngle: 2 * Math.PI})
+                return (t) ->
+                    b = i(t)
+                    return arc(b)
+            )
+            .remove()

         labels = labelGroup.selectAll("text")
             .data(data.sort((p1,p2) -> return p1.startAngle - p2.startAngle))


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -88,7 +88,7 @@
         .attr("filter","url(#dropshadow)")

       arcs.append("svg:text").attr("transform", (d, i) ->
-        d.innerRadius = radiuso - 50 #i #(radiuso * (100-procent_val)/100) - radiuso/2 #45  #45=max text size/2
+        d.innerRadius = 0 #(radiuso * (100-procent_val)/100) - radiuso/2 #45  #45=max text size/2
         d.outerRadius = radiuso
         "translate(" + arc.centroid(d) + ")")
         .attr('fill', "#fff")


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -74,6 +74,7 @@
         labelGroup = svg.select('g.labels')
         if labelGroup.empty()
             labelGroup = svg.append('g').attr('class', 'labels')
+
         totalLabel = svg.select('g.totalLabel')
         if totalLabel.empty()
             totalLabel = svg.append('g').attr('class', 'totalLabel')
@@ -255,12 +256,10 @@
         for pd in dataSet
             totaltickets += pd.value
         #console.log totaltickets
-        totalLabel#.enter()
-            .append('text')
+        totalLabel.append('text')
             .text(totalTickets)
             .attr('text-anchor', 'middle')
             .attr("filter","url(#dropshadow)")
-        #totalLabel.exit().remove()

         oldPieData = piedata
         return


Split fetchDomains function to be able to use it internally too.

--- workers/social/lib/social/models/vm.coffee
+++ workers/social/lib/social/models/vm.coffee
@@ -264,7 +264,19 @@
     #     return callback err  if err
     #     callback null, [vm]

-  @fetchDomains = permit 'list all vms',
+  # Private static method to fetch domains
+  @fetchDomains = (selector, callback)->
+    JDomain = require './domain'
+    JDomain.someData selector, {domain:1}, \
+    (err, cursor)->
+      return callback err, []  if err
+      cursor.toArray (err, arr)->
+        return callback err, []  if err
+        callback null, arr.map (vm)-> vm.domain
+
+  # Public(shared) static method to fetch domains
+  # which points to given hostnameAlias
+  @fetchDomains$ = permit 'list all vms',
     success:(client, hostnameAlias, callback)->
       {delegate} = client.connection

@@ -277,13 +289,8 @@

         JVM.one selector, {hostnameAlias:1}, (err, vm)->
           return callback err, []  if err or not vm
-          JDomain = require './domain'
-          JDomain.someData {hostnameAlias: vm.hostnameAlias}, {domain:1}, \
-          (err, cursor)->
-            return callback err, []  if err
-            cursor.toArray (err, arr)->
-              return callback err, []  if err
-              callback null, arr.map (vm)-> vm.domain
+          JVM.fetchDomains {hostnameAlias: vm.hostnameAlias}, callback
+
   @removeRelatedDomains = (vm)->
     vmInfo = @parseAlias vm.hostnameAlias
     return  unless vmInfo


Reorder projects-listing

Reorder projects-listing

--- projects.cson
+++ projects.cson
@@ -14,6 +14,15 @@
 		icon: "terminal-icon"
 	}
 	{
+		title: "Utils"
+		paths: [
+			"~/Labs/Utils"
+			"~/Labs/JG"
+			"~/Labs/EAL"
+		]
+		icon: "icon-tools"
+	}
+	{
 		title: "language-roff"
 		paths: [
 			"~/Labs/language-roff"
@@ -25,19 +34,12 @@
 		title: "file-icons"
 		paths: [
 			"~/Labs/file-icons/atom"
+			"~/Labs/file-icons/compiler"
 			"~/Labs/file-icons/icons"
 			"~/Labs/file-icons/vscode"
+			"~/Labs/file-icons/atom-fs"
 		]
 		icon: "atom-icon"
-	}
-	{
-		title: "Utils"
-		paths: [
-			"~/Labs/Utils"
-			"~/Labs/JG"
-			"~/Labs/EAL"
-		]
-		icon: "icon-tools"
 	}
 	{
 		title: "Mono"


Fix favicon specs for async behaviour

Fix favicon specs for async behaviour

--- tests/unit/utils/favicon-manager-test.coffee
+++ tests/unit/utils/favicon-manager-test.coffee
@@ -25,9 +25,13 @@
   link = fakeHead.find('link')[0]

   ok link, 'link tag should be added by favicon manager'
-  equal link.getAttribute('href'), 'foobar', 'href attribute for the link should be properly set'
-  equal link.getAttribute('rel'), 'icon', 'rel attribute for the link should be properly set'
-  equal link.getAttribute('type'), 'image/png', 'type attribute for the link should be properly set'
+  stop()
+  setTimeout ->
+    start()
+    equal link.getAttribute('href'), 'foobar', 'href attribute for the link should be properly set'
+    equal link.getAttribute('rel'), 'icon', 'rel attribute for the link should be properly set'
+    equal link.getAttribute('type'), 'image/png', 'type attribute for the link should be properly set'
+  , 20

 test 'replace exisiting link tag', ->
   fakeHead.append($('<link id="foo" rel="icon"></link>'))
@@ -42,9 +46,13 @@
   link = links[0]

   ok !link.getAttribute('id'), 'existing link should be replaced with a new one'
-  equal link.getAttribute('href'), 'foobar', 'href attribute for the link should be properly set'
-  equal link.getAttribute('rel'), 'icon', 'rel attribute for the link should be properly set'
-  equal link.getAttribute('type'), 'image/png', 'type attribute for the link should be properly set'
+  stop()
+  setTimeout ->
+    start()
+    equal link.getAttribute('href'), 'foobar', 'href attribute for the link should be properly set'
+    equal link.getAttribute('rel'), 'icon', 'rel attribute for the link should be properly set'
+    equal link.getAttribute('type'), 'image/png', 'type attribute for the link should be properly set'
+  , 20

 test 'find link with rel=icon only', ->
   fakeHead.append($('<link id="foo" rel="foo"></link>'))


Fix for multiple scroll events of dashboard infinite scrolling

--- app/assets/javascripts/dashboard.js.coffee
+++ app/assets/javascripts/dashboard.js.coffee
@@ -95,5 +95,9 @@
   console.log "dashboard loading"
   load_activities()
   @last_top = 0
+  @timeout_id = null
   $(document).scroll (event) ->
-    scroll_handler(event)
+    if @timeout_id
+      clearTimeout(@timeout_id)
+    @timeout_id = setTimeout( scroll_handler, 100)
+


handle empty responses as empty json object and don't throw error, #900

--- app/assets/javascripts/libs/request.coffee
+++ app/assets/javascripts/libs/request.coffee
@@ -18,8 +18,7 @@
         body : if typeof(data) == "string" then data else JSON.stringify(data)
         headers :
           "Content-Type" : "application/json"
-      (response) ->
-        response.json()
+      @handleEmptyJsonResponse
     )

@@ -40,8 +39,7 @@

         method : "POST"
         body : formData
-      (response) ->
-        response.json()
+      @handleEmptyJsonResponse
     )

@@ -57,8 +55,7 @@
         body : if typeof(data) == "string" then data else data.serialize()
         headers :
           "Content-Type" : "application/x-www-form-urlencoded"
-      (response) ->
-        response.json()
+      @handleEmptyJsonResponse
     )

@@ -139,7 +136,15 @@
       )
     else
       Toast.error(error)
-      Promise.reject(error)
+
+
+  handleEmptyJsonResponse : (response) ->
+
+    contentLength = parseInt(response.headers.get("Content-Length"))
+    if contentLength == 0
+      Promise.resolve({})
+    else
+      response.json()

   # Extends the native Promise API with `always` functionality similar to jQuery.


OX-120 debug

OX-120 debug

--- app/assets/javascripts/model/binary.coffee
+++ app/assets/javascripts/model/binary.coffee
@@ -159,7 +159,7 @@
       unless lastPosition
         lastPosition = [0, 0, 0]

-      unless lastDirection
+      unless @lastDirection
         @lastDirection = [1, 0, 0]

       @lastPosition = position


Different colors for walls and floor

Different colors for walls and floor

--- src/io/web.coffee
+++ src/io/web.coffee
@@ -111,8 +111,11 @@

 		for cx in [0...map.w]
 			for cy in [0...map.h]
-				viewport.fillStyle = '#ff0000'
+				viewport.fillStyle =
+					if map.data[cy][cx] is '#' then '#00ff00'
+					else '#333'
 				viewport.fillRect x + cx * 8, y + cy * 8, 8, 8
+
 		log 'Done rendering!!'

 module.exports =


Add sections to file for managing paths and traversing

--- src/file.coffee
+++ src/file.coffee
@@ -123,6 +123,10 @@
   setDigest: (contents) ->
     @digest = crypto.createHash('sha1').update(contents ? '').digest('hex')

+  ###
+  Section: Managing Paths
+  ###
+
   # Public: Returns the {String} path for the file.
   getPath: -> @path

@@ -142,6 +146,10 @@
   # Public: Return the {String} filename without any directory information.
   getBaseName: ->
     path.basename(@path)
+
+  ###
+  Section: Traversing
+  ###

   # Public: Return the {Directory} that contains this file.
   getParent: ->


Update app externs.

Update app externs.

--- app/client/js/externs.coffee
+++ app/client/js/externs.coffee
@@ -7,9 +7,12 @@
   Quoting properties is verbose and tricky.
   Externs ftw.
 ###
-class appSongsStore
-  appSongsStore::songs
-  appSongsStore::newSong
+class appUserStore
+  appUserStore::songs
+  appUserStore::newSong
+  # User.
+  appUserStore::user
+  appUserStore::displayName

 # PATTERN(steida): For app.songs.Song create appSongsSong.
 class appSongsSong


Update atom config

Update atom config

--- config.cson
+++ config.cson
@@ -2,43 +2,19 @@
   autosave:
     enabled: true
   core:
-    audioBeep: false
-    closeDeletedFileTabs: true
-    excludeVcsIgnoredPaths: false
-    packagesWithKeymapsDisabled: []
     telemetryConsent: "limited"
     themes: [
       "one-light-ui"
       "one-light-syntax"
     ]
-  "custom-title":
-    template: "<%= fileName %><% if (projectPath) { %> - <%= projectPath %><% if (gitHead) { %> [<%= gitHead %>]<% } %><% } %>"
   editor:
-    backUpBeforeSaving: true
-    fontSize: 19
-    invisibles: {}
-    preferredLineLength: 120
+    fontSize: 22
     scrollPastEnd: true
-    showIndentGuide: true
     softWrap: true
-    softWrapAtPreferredLineLength: true
-    zoomFontWhenCtrlScrolling: true
   "exception-reporting":
-    userId: "23726a94-4d58-6922-8de3-2676f1bd97ca"
-  pigments:
-    extendAutocompleteToColorValue: true
-    extendAutocompleteToVariables: true
-  "release-notes":
-    viewedVersion: "0.94.0"
-  "tree-view":
-    hideVcsIgnoredFiles: true
-    squashDirectoryNames: true
-  "tree-view-open-files": {}
+    userId: "c0f59876-8ce3-42c6-824c-22e0f8fe3238"
   welcome:
     showOnStartup: false
-  wordcount:
-    alwaysOn: true
-    noextension: true
 ".css.source.stylus":
   editor:
     preferredLineLength: 120


lead form trigger event after submit

lead form trigger event after submit

--- app/cells/folio/lead_form/lead_form.coffee
+++ app/cells/folio/lead_form/lead_form.coffee
@@ -6,4 +6,7 @@
     $wrap.addClass('folio-lead-form-submitting')

     $.post($form.attr('action'), $form.serialize())
-      .always((response) -> $wrap.replaceWith(response))
+      .always (response) ->
+        $response = $(response)
+        $wrap.replaceWith($response)
+        $response.trigger('submitted.folio')


calendar2: use locale-aware weekday in start_date computation

test plan:
 - set your user locale to UK English (or another locale that
   starts the week on Monday instead of Sunday)
 - go to the calendar week view
 - ensure when switching weeks, the view_start in the URL bar
   matches the Monday at the beginning of the week
 - ensure when reloading the calendar page, the displayed
   date range doesn't change
 - regression test with a Sunday-first locale (e.g. US English)
   and ensure view_start and reload work as expected

fixes CNVS-30347

Change-Id: I55f350e55a5230756a65286b082d0266f84a8e23
Reviewed-on: https://gerrit.instructure.com/84798
Tested-by: Jenkins
Reviewed-by: Joel Hough <4816dabf8db1bc6cac35b3a24cab2ff844b5b0c7@instructure.com>
QA-Review: Deepeeca Soundarrajan <5bcd4b623f00b7fc2942cd1c65c245b30a48a61f@instructure.com>
Product-Review: Jeremy Stanley <b3f594e10a9edcf5413cf1190121d45078c62290@instructure.com>

--- app/coffeescripts/calendar/Calendar.coffee
+++ app/coffeescripts/calendar/Calendar.coffee
@@ -546,7 +546,7 @@
           start = now
         else
           start = fcUtil.clone(calendarDate)
-          start.date(start.date() - start.day())
+          start.date(start.date() - start.weekday())

       @setCurrentDate(start)
       @drawNowLine()


Make the ribbon load its data

Make the ribbon load its data

--- app/components/classifications-ribbon.cjsx
+++ app/components/classifications-ribbon.cjsx
@@ -1,6 +1,8 @@
 React = require 'react'

-module.exports = React.createClass
+ClassificationsRibbon = React.createClass
+  displayName: 'ClassificationsRibbon'
+
   getDefaultProps: ->
     projects: for i in [0...20]
       {project: "Project #{i}", classifications: Math.floor Math.random() * 100}
@@ -18,26 +20,34 @@

   render: ->
     if @props.projects.length is 0
-      No classifications yet
+
+        No classifications yet
+

     else
       totalClassifications = 0
       for {classifications} in @props.projects
-        totalClassifications += classifications
+        totalClassifications += classifications ? 1

       others = []

       lastX = 0
       <svg className="user-classifications-ribbon" width={@props.width} height={@props.height} viewBox="0 0 1 1" preserveAspectRatio="none">
         {for {project, classifications}, i in @props.projects
-          width = classifications / totalClassifications
+          width = (classifications ? 1) / totalClassifications
           if width < @props.cutoff
             others.push {project, classifications}
             continue
           else
             band = <rect key={project} fill={@getProjectColor project} stroke="none" x={lastX} y="0" width={width} height="1">
               <title>
-                {project}: {classifications}
+                {project}: {classifications ? '?'}
               </title>
             </rect>
             lastX += width
@@ -47,7 +57,60 @@
           othersWidth =
           <rect fill="gray" fillOpacity="0.5" stroke="none" x={lastX} y="0" width="1" height="1">
             <title>
-              {("#{project}: #{classifications}" for {project, classifications} in others).join '\n'}
+              {("#{project}: #{classifications ? '?'}" for {project, classifications} in others).join '\n'}
             </title>
           </rect>}
       </svg>
+
+module.exports = React.createClass
+  displayName: 'ClassificationsRibbonWrapper'
+
+  getDefaultProps: ->
+    user: null
+
+  getInitialState: ->
+    loading: false
+    projects: []
+
+  componentDidMount: ->
+    if @props.user is null and location.hash.indexOf '/dev/' isnt -1
+      zooAPI.type('users').get('3').then (user) =>
+        @getClassificationCounts user
+    else
+      @getClassificationCounts @props.user
+
+  componentWillReceiveProps: (nextProps) ->
+    unless nextProps.user is @props.user
+      @getClassificationCounts @props.user
+
+  getClassificationCounts: (user, _page = 1) ->
+    @setState loading: true
+    @getAllProjectPreferences(user).then (preferences) =>
+      projects = for preference in preferences
+        preference.get('project').catch =>
+          null
+      Promise.all(projects).then (projects) =>
+        counts = for i in  [0...preferences.length] when projects[i]?
+          project: projects[i].display_name
+          classifications: preferences[i].activity_count
+        @setState
+          loading: false
+          projects: counts
+
+  getAllProjectPreferences: (user, _page = 1, _collection = []) ->
+    user.get('project_preferences', page: _page).then (projectPreferences) =>
+      if projectPreferences.length is 0
+        projectPreferences
+      else
+        _collection.push projectPreferences...
+        meta = projectPreferences[0].getMeta()
+        if meta.page is meta.page_count
+          _collection
+        else
+          @getAllProjectPreferences user, meta.page + 1, _collection
+
+  render: ->
+    if @state.loading
+      Loading ribbon...
+    else
+      <ClassificationsRibbon {...@props} projects={@state.projects} />


re-enable unstable data hiding in point charts


revert to original CLSI proxy call

revert to original CLSI proxy call

due to problems with cost of authentication when byte-serving

--- services/web/app/coffee/Features/Compile/CompileController.coffee
+++ services/web/app/coffee/Features/Compile/CompileController.coffee
@@ -78,12 +78,7 @@
 			url = "#{compilerUrl}#{url}"
 			logger.log url: url, "proxying to CLSI"
 			oneMinute = 60 * 1000
-			# pass through If-* and Range headers for byte serving pdfs
-			# do not send any others, potential proxying loop if Host: is passed!
-			newHeaders = {}
-			for h, v of req.headers
-				newHeaders[h] = req.headers[h] if h.match /^(If-|Range)/i
-			proxy = request(url: url, method: req.method, timeout: oneMinute, headers: newHeaders)
+			proxy = request(url: url, method: req.method, timeout: oneMinute)
 			proxy.pipe(res)
 			proxy.on "error", (error) ->
 				logger.warn err: error, url: url, "CLSI proxy error"


add test for instanceFound hook

add test for instanceFound hook

--- test/hooks.coffee
+++ test/hooks.coffee
@@ -104,3 +104,19 @@

         hinoki.inject container, (a) ->
             test.fail()
+
+    'instanceFound': (test) ->
+        test.expect 3
+        container =
+            factories:
+                a: (b) -> b + 3
+            instances:
+                b: 5
+            hooks:
+                instanceFound: (chain, instance) ->
+                    test.deepEqual chain, ['b', 'a']
+                    test.equals instance, 5
+
+        hinoki.inject container, (a) ->
+            test.equals a, 8
+            test.done()


fix(specs) Fix AutoUpdateManager specs

fix(specs) Fix AutoUpdateManager specs

--- spec/auto-update-manager-spec.coffee
+++ spec/auto-update-manager-spec.coffee
@@ -6,6 +6,10 @@
     @nylasIdentityId = null
     @accounts = [{email_address: 'ben@nylas.com'},{email_address: 'mark@nylas.com'}]
     @specMode = true
+    @databaseReader =
+      getJSONBlob: => {
+        id: @nylasIdentityId
+      }
     @config =
       set: jasmine.createSpy('config.set')
       get: (key) =>
@@ -18,7 +22,7 @@

   describe "with attached commit version", ->
     it "correctly sets the feedURL", ->
-      m = new AutoUpdateManager("3.222.1-abc", @config, @specMode)
+      m = new AutoUpdateManager("3.222.1-abc", @config, @specMode, @databaseReader)
       spyOn(m, "setupAutoUpdater")

       {query} = url.parse(m.feedURL, true)
@@ -28,7 +32,7 @@

   describe "with no attached commit", ->
     it "correctly sets the feedURL", ->
-      m = new AutoUpdateManager("3.222.1", @config, @specMode)
+      m = new AutoUpdateManager("3.222.1", @config, @specMode, @databaseReader)
       spyOn(m, "setupAutoUpdater")
       {query} = url.parse(m.feedURL, true)
       expect(query.arch).toBe process.arch
@@ -37,7 +41,7 @@

   describe "when an update identity is not present", ->
     it "should use anonymous", ->
-      m = new AutoUpdateManager("3.222.1", @config, @specMode)
+      m = new AutoUpdateManager("3.222.1", @config, @specMode, @databaseReader)
       spyOn(m, "setupAutoUpdater")
       {query} = url.parse(m.feedURL, true)
       expect(query.id).toEqual('anonymous')
@@ -45,18 +49,18 @@
   describe "when an update identity is already set", ->
     it "should send it and not save any changes", ->
       @nylasIdentityId = "test-nylas-id"
-      m = new AutoUpdateManager("3.222.1", @config, @specMode)
+      m = new AutoUpdateManager("3.222.1", @config, @specMode, @databaseReader)
       spyOn(m, "setupAutoUpdater")
       {query} = url.parse(m.feedURL, true)
       expect(query.id).toEqual(@nylasIdentityId)

   describe "when an update identity is added", ->
     it "should update the feed URL", ->
-      m = new AutoUpdateManager("3.222.1", @config, @specMode)
+      m = new AutoUpdateManager("3.222.1", @config, @specMode, @databaseReader)
       spyOn(m, "setupAutoUpdater")
       {query} = url.parse(m.feedURL, true)
       expect(query.id).toEqual('anonymous')
       @nylasIdentityId = '1'
-      m._updateFeedURL()
+      m.updateFeedURL()
       {query} = url.parse(m.feedURL, true)
       expect(query.id).toEqual(@nylasIdentityId)


rename a machine spec

rename a machine spec

--- spec/javascripts/machine_spec.coffee
+++ spec/javascripts/machine_spec.coffee
@@ -6,7 +6,7 @@

 describe 'Machine', ->

-  it 'should Ajax fetch a list of modules when constructed', ->
+  it 'should Ajax fetch when constructed', ->
     spyOn jQuery, 'ajax'
     machine = new Machine()
     expect(jQuery.ajax).toHaveBeenCalled()


Clean up

Clean up

--- web/source/javascripts/angular-app.js.coffee
+++ web/source/javascripts/angular-app.js.coffee
@@ -13,7 +13,12 @@
 vDot = (v, w) -> _.sum(_.zipWith(v, w, (a, b) -> a*b))
 vNorm = (v) -> Math.sqrt(_.reduce(_.map(v, (e) -> e**2), _.add))
 euclideanDistance = (v, w) -> vNorm(vSub(v, w))
-d = euclideanDistance
+
+angle = (doc, other) ->
+  diff = vSub([doc.x, doc.y], [other.x, other.y])
+  unitDiff = vScale(1/vNorm(diff), diff)
+  alpha = Math.acos(vDot([0, 1], unitDiff)) / Math.PI * 180
+  alpha = if diff[0] <= 0 then alpha else 360 - alpha

 gaussian = (mean = 0, sigma = 1) -> (x) ->
   gaussianConstant = 1 / Math.sqrt(2 * Math.PI)
@@ -175,12 +180,6 @@
           duration = 500
           n = nodes.length

-          angle = (doc, other) ->
-            diff = vSub([doc.x, doc.y], [other.x, other.y])
-            unitDiff = vScale(1/vNorm(diff), diff)
-            alpha = Math.acos(vDot([0, 1], unitDiff)) / Math.PI * 180
-            alpha = if diff[0] <= 0 then alpha else 360 - alpha
-
           node
             .sort((doc, other) ->
               # sort by distance to selected
@@ -191,8 +190,6 @@
               .delay((doc, i) -> # calculate angle to basis vector
                 return 0 if doc == d
                 dist = euclideanDistance([xScale(d.x), yScale(d.y)], [xScale(doc.x), yScale(doc.y)])
-                # distDelay = d3.scale.linear().domain([0, minSide]).range([0, duration])(dist)
-                console.log dist, gaussian(outerRadius*3, 2)(dist)*1000
                 gaussian(outerRadius*3, 2)(dist)*1000 # works pretty well... FIXME to many magic numbers
               )
               .attr 'transform', (d) ->


move script tag body end

move script tag body end

--- fav.coffee
+++ fav.coffee
@@ -97,7 +97,6 @@
               a href: "http://twitter.com/${user.screen_name}", target:"_blank", ->
                 img src:"/user.png"
                 span class:"user", -> "user"
-    script src: '/fav-client.js', charset: 'utf-8'
     div id: "favs", ->
       header ->
         img class:"icon", src:"/favicon73x73.png"
@@ -128,32 +127,6 @@
         text "<!--[if lt IE 9]>"
         script src: '/html5.js', charset: 'utf-8'
         text "<![endif]-->"
-        script src: '/jquery-1.7.1.min.js', charset: 'utf-8'
-        script src: '/jquery.tmpl.min.js', charset: 'utf-8'
-        script src: '/jquery.bottom-1.0.js', charset: 'utf-8'
-        script src: '/jquery.masonry.min.js', charset: 'utf-8'
-        script src: '/jquery.meow.js', charset: 'utf-8'
-        script src: '/fancybox/jquery.fancybox-1.3.4.pack.js', charset: 'utf-8'
-        script src: '/spine.js', charset: 'utf-8'
-        script id: 'facebook-jssdk', src:'http://connect.facebook.net/ja_JP/all.js#xfbml=1'
-        coffeescript ->
-          _gaq = _gaq or []
-          _gaq.push [ "_setAccount", "UA-28457578-1" ]
-          _gaq.push [ "_trackPageview" ]
-          do ->
-            ga = document.createElement("script")
-            ga.type = "text/javascript"
-            ga.async = true
-            ga.src = (if "https:" is document.location.protocol then "https://ssl" else "http://www") + ".google-analytics.com/ga.js"
-            s = document.getElementsByTagName("script")[0]
-            s.parentNode.insertBefore ga, s
-
-        script charset: 'utf-8', ->
-          if @session?.twitter?
-            text "twitter = true;"
-          else
-            text "twitter = false;"
-          text "csrf=\"#{@csrf}\";"
       body ->
         header class:"global_header", ->
           h1 ->
@@ -177,3 +150,30 @@
             text "!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=\"//platform.twitter.com/widgets.js\";fjs.parentNode.insertBefore(js,fjs);}}(document,\"script\",\"twitter-wjs\");"
           text " "
           div class:"fb-like", "data-href":"http://favtile.com/", "data-send":"true", "data-width":"450", "data-show-faces":"false"
+        script src: '/jquery-1.7.1.min.js', charset: 'utf-8'
+        script src: '/jquery.tmpl.min.js', charset: 'utf-8'
+        script src: '/jquery.bottom-1.0.js', charset: 'utf-8'
+        script src: '/jquery.masonry.min.js', charset: 'utf-8'
+        script src: '/jquery.meow.js', charset: 'utf-8'
+        script src: '/fancybox/jquery.fancybox-1.3.4.pack.js', charset: 'utf-8'
+        script src: '/spine.js', charset: 'utf-8'
+        script id: 'facebook-jssdk', src:'http://connect.facebook.net/ja_JP/all.js#xfbml=1'
+        script src: '/fav-client.js', charset: 'utf-8'
+        coffeescript ->
+          _gaq = _gaq or []
+          _gaq.push [ "_setAccount", "UA-28457578-1" ]
+          _gaq.push [ "_trackPageview" ]
+          do ->
+            ga = document.createElement("script")
+            ga.type = "text/javascript"
+            ga.async = true
+            ga.src = (if "https:" is document.location.protocol then "https://ssl" else "http://www") + ".google-analytics.com/ga.js"
+            s = document.getElementsByTagName("script")[0]
+            s.parentNode.insertBefore ga, s
+
+        script charset: 'utf-8', ->
+          if @session?.twitter?
+            text "twitter = true;"
+          else
+            text "twitter = false;"
+          text "csrf=\"#{@csrf}\";"


Use relative require paths to src/ files

--- spec/project-replication-spec.coffee
+++ spec/project-replication-spec.coffee
@@ -1,8 +1,7 @@
 path = require 'path'
 {Site} = require 'telepath'
-fsUtils = require 'fs-utils'
-Project = require 'project'
-Git = require 'git'
+Project = require '../src/project'
+Git = require '../src/git'

 describe "Project replication", ->
   [doc1, doc2, project1, project2] = []


add process.argv which doesn't exist in testing

--- test/mocks.coffee
+++ test/mocks.coffee
@@ -5,6 +5,9 @@
 #
 # Note the @ making the variables package scoped
 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ###
+
+# when testing `process` is undefined, so, define it
+@process = argv:[]

 # store results for test to verify with test.equal
 @Result = {}