beginning to use the new models

beginning to use the new models

--- client/landing/app/Applications/Environments.kdapplication/views/FirewallMapperView.coffee
+++ client/landing/app/Applications/Environments.kdapplication/views/FirewallMapperView.coffee
@@ -24,23 +24,22 @@
     @filterListController.showLazyLoader()
     @ruleListController.showLazyLoader()

-    domain.fetchProxyFilters (err, response)=>
+    KD.remote.api.JProxyFilter.fetchFiltersByContext (err, filters)=>
       if err
         @filterListController.hideLazyLoader()
-        @ruleListController.hideLazyLoader()
-
+        #@ruleListController.hideLazyLoader()
+
         notifyMsg = if err is "not found"
         then "You don't have any filters set for this domain."
         else "An error occured while fetching the filters. Please try again."

         @actionOrdersButton.hide() if err is "not found"
-
         return new KDNotificationView
           title : notifyMsg
           type  : "top"

-
-      @iniateControllerListItems domain, response
+      @filterListController.instantiateListItems filters
+

     @fwRuleFormView = new FirewallFilterFormView delegate:this, {domain}

@@ -66,12 +65,12 @@

     @ruleListScrollView.addSubView @ruleListController.getView()

-    @on "newRuleCreated", (item) =>
+    @on "newFilterCreated", (item) =>
       @filterListController.addItem item

     # this event is on rule list controller because
     # both allow & deny buttons live on FirewallfilterListItemView.
-    @filterListController.getListView().on "behaviorCreated", (item) =>
+    @ruleListController.getListView().on "newRuleCreated", (item) =>
       @ruleListController.addItem item

   iniateControllerListItems:(domain, response)->
@@ -157,11 +156,11 @@
           index   : "0"
       , (err, response)=>
         if not err
-          delegate.emit "behaviorCreated", {domainName:data.domainName, ruleName:data.ruleName, action:behavior}
+          delegate.emit "newRuleCreated", {domainName:data.domainName, ruleName:data.ruleName, action:behavior}

   pistachio:->
     """
-    {div.fl.fw-li-rule{ #(match)}}
+
 {{ #(name) }} - {{ #(match) }}

       {{> @allowButton }}
       {{> @denyButton }}
@@ -261,10 +260,10 @@
 class FirewallFilterFormView extends KDCustomHTMLView

   constructor:(options={}, data)->
-    options.cssClass = "rule-form-view"
+    options.cssClass = "filter-form-view"
     super options, data

-    @filterNameInput  = new KDInputView {tooltip: {title: "Enter a name for the rule.", placement:"bottom"}}
+    @filterNameInput  = new KDInputView {tooltip: {title: "Enter a name for the filter.", placement:"bottom"}}
     @filterInput      = new KDInputView
       tooltip :
         title     : "You can enter IP, IP Range or a country name. (ie: 192.168.1.1/24 or China)"
@@ -281,31 +280,32 @@
     filterType  = if @filterInput.getValue().match /[0-9+]/ then "ip" else "country"
     filterName  = @filterNameInput.getValue()
     filterMatch = @filterInput.getValue()
-    domain      = @getData().domain
-    domainName  = domain.domain
     delegate    = @getDelegate()

-    domain.createProxyFilter {filterInfo:{type:filterType, match:filterMatch}}, (err, response)->
-      if err
-        return new KDNotificationView
-          title : "An error occured while performing your action. Please try again."
-          type  : "top"
-      delegate.emit "newFilterCreated",
-        domainName  : domainName
-        filterName  : response.Name
-        filterMatch : response.Match
+    KD.remote.api.JProxyFilter.createFilter
+      name  : filterName
+      type  : filterType
+      match : filterMatch
+    , (err, filter)->
+      unless err
+        delegate.emit "newFilterCreated", {filterName, filterMatch}
+
+      return new KDNotificationView
+        title : "An error occured while performing your action. Please try again."
+        type  : "top"
+

   viewAppended: JView::viewAppended

   pistachio:->
     """

-      Rule Name:
-      {{> @ruleNameInput }}
+      Filter Name:
+      {{> @filterNameInput }}

-      Rule:
-      {{> @ruleInput }}
+      Match:
+      {{> @filterInput }}
       {{> @addButton }}


still refactoring the models and the related parts of the ui

--- client/landing/app/Applications/Environments.kdapplication/views/FirewallMapperView.coffee
+++ client/landing/app/Applications/Environments.kdapplication/views/FirewallMapperView.coffee
@@ -38,7 +38,11 @@
           title : notifyMsg
           type  : "top"

+      for filter in filters
+        filter.domainName = domain.domain
+
       @filterListController.instantiateListItems filters
+      @filterListController.showLazyLoader()

     @fwRuleFormView = new FirewallFilterFormView delegate:this, {domain}
@@ -72,37 +76,6 @@
     # both allow & deny buttons live on FirewallfilterListItemView.
     @ruleListController.getListView().on "newRuleCreated", (item) =>
       @ruleListController.addItem item
-
-  iniateControllerListItems:(domain, response)->
-    ###
-    ruleKeys         = Object.keys(response.rules)
-    actionKeys       = Object.keys(response.filterList)
-    responseRules    = response.rules
-    responsefilterList = response.filterList
-    filterList         = []
-    ruleList       = []
-
-    for key in ruleKeys
-      filterList.push
-        domainName : domain.domain
-        ruleName   : responseRules[key].Name
-        match      : responseRules[key].Match
-        type       : responseRules[key].Type
-
-    for key in actionKeys
-      ruleList.push
-        domainName : domain.domain
-        ruleName   : responsefilterList[key].RuleName
-        action     : responsefilterList[key].Action
-    ###
-
-
-
-
-    @filterListController.instantiateListItems filterList
-    @filterListController.hideLazyLoader()
-    @ruleListController.instantiateListItems ruleList
-    @ruleListController.hideLazyLoader()

   updateActionOrders:(domain)->
     for item, index in @ruleListController.itemsOrdered
@@ -126,37 +99,41 @@

     @allowButton = new KDButtonView
       title    : "Allow"
-      callback : => @createBehavior 'allow'
+      callback : => @createRule 'allow'

     @denyButton = new KDButtonView
       title    : "Deny"
-      callback : => @createBehavior 'deny'
+      callback : => @createRule 'deny'

     @deleteButton = new KDButtonView
-      title : "Delete"
-      viewOptions:
-        cssClass : 'delete-button'
+      title       : "Delete"
+      viewOptions :
+        cssClass  : 'delete-button'
+      callback    : => @deleteFilter()

   viewAppended:->
     @unsetClass 'kdview'
     @setTemplate @pistachio()
     @template.update()

-  createBehavior:(behavior)->
+  createRule:(behavior)->
     data = @getData()
     delegate = @getDelegate()

     KD.remote.api.JDomain.one {domainName:data.domainName}, (err, domain)->
       return console.log err if err
-      domain.createRuleBehavior
-        ruleName   : data.ruleName
-        behaviorInfo :
-          enabled : "yes"
-          action  : behavior
-          index   : "0"
-      , (err, response)=>
-        if not err
-          delegate.emit "newRuleCreated", {domainName:data.domainName, ruleName:data.ruleName, action:behavior}
+      params =
+        domain    : domain.getId()
+        action    : behavior
+        match     : data.match
+      domain.createProxyRule params, (err, rule)->
+        return console.log err if err
+        delegate.emit "newRuleCreated", {domainName:data.domainName, ruleName:data.ruleName, action:behavior}
+
+  deleteFilter:->
+    data = @getData()
+    KD.remote.api.JProxyFilter.remove {_id:data.getId()}, (err)=>
+      unless err then @destroy()

   pistachio:->
     """
@@ -287,8 +264,9 @@
       type  : filterType
       match : filterMatch
     , (err, filter)->
+      console.log err, filter
       unless err
-        delegate.emit "newFilterCreated", {filterName, filterMatch}
+        delegate.emit "newFilterCreated", {name:filterName, match:filterMatch}

       return new KDNotificationView
         title : "An error occured while performing your action. Please try again."


Removed whitespace

Removed whitespace

--- src/handlers/hops/ListHandler.coffee
+++ src/handlers/hops/ListHandler.coffee
@@ -14,7 +14,7 @@

 	render: (results, user, pagination) ->
 		return @renderer.page
-			title: " Hops - Ingredients"
+			title: "Hops - Ingredients"
 			navigationState: 'ingredients'
 			user: user
 			html: @renderer.render


 Merge branch 'master.paymentLoginFix' into 'master'

Master.Payment Login Fix

--- client/Social/Activity/AppController.coffee
+++ client/Social/Activity/AppController.coffee
@@ -54,6 +54,8 @@
     @lastTo                = null
     @lastFrom              = Date.now()
     @lastQuery             = null
+
+    KD.singletons.dock.getView().show()

     # if @mainController.appIsReady then @putListeners()
     # else @mainController.on 'AppIsReady', => @putListeners()


Teambuilder: subtle bugfixes with events.

--- client/app/js/views/teambuilder_view.coffee
+++ client/app/js/views/teambuilder_view.coffee
@@ -22,6 +22,7 @@
     # TODO: Save these to something more global
     @speciesList = (name for name, data of SpeciesData)
     @itemList = _(name for name, data of ItemData).sort() # todo: filter irrelevant items
+    @selected = 0

     @listenTo(@collection, 'add', @renderPokemon)
     @listenTo(@collection, 'change:ivs', @renderStats)
@@ -41,8 +42,8 @@

     @render()

-  clickPokemon: (ev) =>
-    $listItem = $(ev.target)
+  clickPokemon: (e) =>
+    $listItem = $(e.currentTarget)
     index = @$('.pokemon_list li').index($listItem)
     @setSelectedIndex(index)

@@ -66,25 +67,25 @@
     # PokeBattle.socket.send('save team', teamJson)
     window.localStorage.setItem('team', JSON.stringify(teamJSON))

-  changeSpecies: (ev) =>
-    $list = $(ev.target)
+  changeSpecies: (e) =>
+    $list = $(e.currentTarget)
     @getSelectedPokemon().set("name", $list.val())

   changeNature: (e) =>
     $list = $(e.currentTarget)
     @getSelectedPokemon().set("nature", $list.val())

-  changeAbility: (ev) =>
-    $list = $(ev.target)
+  changeAbility: (e) =>
+    $list = $(e.currentTarget)
     @getSelectedPokemon().set("ability", $list.val())

-  changeItem: (ev) =>
-    $list = $(ev.target)
+  changeItem: (e) =>
+    $list = $(e.currentTarget)
     @getSelectedPokemon().set("item", $list.val())

-  changeIv: (ev) =>
+  changeIv: (e) =>
     # todo: make changeIv and changeEv DRY
-    $input = $(ev.target)
+    $input = $(e.currentTarget)
     stat = $input.data("stat")
     value = parseInt($input.val())
     if isNaN(value) || value > 31 || value < 0
@@ -93,9 +94,9 @@
     pokemon = @getSelectedPokemon()
     pokemon.setIv(stat, value)

-  changeEv: (ev) =>
+  changeEv: (e) =>
     # todo: make changeIv and changeEv DRY
-    $input = $(ev.target)
+    $input = $(e.currentTarget)
     stat = $input.data("stat")
     value = parseInt($input.val())
     value = 252  if isNaN(value) || value > 252 || value < 0


Make window.global available on ArticleList

--- client/components/article_list/index.coffee
+++ client/components/article_list/index.coffee
@@ -6,6 +6,7 @@
 #   articles: []
 # }

+window.global = window
 _ = require 'underscore'
 React = require 'react'
 { input, div, a, h1, h2 } = React.DOM


:racehorse: Implement efficient marker updates

--- lib/linter-views.coffee
+++ lib/linter-views.coffee
@@ -10,7 +10,7 @@
     @state = @linter.state
     @subscriptions = new CompositeDisposable
     @messages = []
-    @markers = []
+    @markers = new Map()
     @panel = new BottomPanel().prepare()
     @bottomContainer = new BottomContainer().prepare(@linter.state)
     @bottomBar = null
@@ -34,10 +34,10 @@
     @subscriptions.add @bottomContainer.onDidChangeTab =>
       @renderPanelMessages()

-  render: (messages) ->
+  render: ({added, removed, messages}) ->
     @messages = @classifyMessages(messages)
     @renderPanelMessages()
-    @renderPanelMarkers()
+    @renderPanelMarkers({added, removed})
     @renderBubble()
     @renderCount()

@@ -103,13 +103,13 @@
       messages = @messages.filter (message) -> message.currentLine
     @panel.updateMessages messages, @state.scope is 'Project'

-  renderPanelMarkers: ->
-    @removeMarkers()
+  renderPanelMarkers: ({added, removed}) ->
+    @removeMarkers(removed)
     activeEditor = atom.workspace.getActiveTextEditor()
     return unless activeEditor
-    @messages.forEach (message) =>
+    added.forEach (message) =>
       return unless message.currentFile
-      @markers.push marker = activeEditor.markBufferRange message.range, {invalidate: 'inside'}
+      @markers.set(message.key, marker = activeEditor.markBufferRange message.range, {invalidate: 'inside'})
       activeEditor.decorateMarker(
         marker, type: 'line-number', class: "linter-highlight #{message.class}"
       )
@@ -122,9 +122,12 @@
       item: @bottomContainer,
       priority: -100

-  removeMarkers: ->
-    @markers.forEach (marker) -> try marker.destroy()
-    @markers = []
+  removeMarkers: (messages = @messages) ->
+    messages.forEach((message) =>
+      marker = @markers.get(message.key)
+      try marker.destroy()
+      @markers.delete(message.key)
+    )

   removeBubble: ->
     @bubble?.destroy()


forgot to push messages

forgot to push messages

--- lib/linter-views.coffee
+++ lib/linter-views.coffee
@@ -56,7 +56,7 @@
     return unless @messagesLine.length
     return unless @showBubble
     for msg in @messagesLine
-      @linter.messenger.message
+      @renderedMessages.push @linter.messenger.message
         severity: msg.class
         text: msg.text
         html: msg.html


Fix priority

Fix priority

--- lib/linter-views.coffee
+++ lib/linter-views.coffee
@@ -119,7 +119,7 @@
   attachBottom: (statusBar) ->
     @bottomBar = statusBar.addLeftTile
       item: @bottomContainer,
-      priority: -100
+      priority: 10

   removeMarkers: ->
     @markers.forEach (marker) -> try marker.destroy()


minimap doesn't have to re-scale

minimap doesn't have to re-scale

--- lib/minimap-view.coffee
+++ lib/minimap-view.coffee
@@ -112,9 +112,6 @@
     # offset minimap
     @offset top: @editorView.offset().top

-    # reset size of minimap layer
-    @resetMinimapTransform()
-
     # get rects
     @editorViewRect = @getEditorViewClientRect()
     @scrollViewRect = @getScrollViewClientRect()
@@ -124,8 +121,6 @@
     @miniOverlayer.css
       width: @scrollViewRect.width
       height: @editorViewRect.height
-
-    @transform @miniWrapper[0], @minimapScale

     setImmediate => @updateScroll()


:beer: improve flash further comfortable for human

- When flashing, trim starting newline if trimmed result form  linewise
range
- Skip mergeIntersectingRanges until I re-encounter the situation I
need this

--- lib/misc-command.coffee
+++ lib/misc-command.coffee
@@ -7,7 +7,6 @@

 {
   pointIsAtEndOfLine
-  mergeIntersectingRanges
   sortRanges
 } = require './utils'

@@ -37,13 +36,20 @@
 class Undo extends MiscCommand
   @extend()

+  # Trim starting new-line-corresponding range if it transformed range become linewise range.
+  # this is special accomodation for flashing intuitively for human when `y y p` then `undo`, `redo`
+  trimStartingNewLine: (range) ->
+    {start, end} = range
+    if (end.column is 0) and (start.row + 1 isnt end.row) and pointIsAtEndOfLine(@editor, start)
+      new Range([start.row + 1, 0], end)
+    else
+      range
+
   withTrackingChanges: (fn) ->
     newRanges = []
     oldRanges = []

-    disposable = @editor.getBuffer().onDidChange (event) ->
-      {oldRange, newRange, oldText, newText} = event
-
+    disposable = @editor.getBuffer().onDidChange ({oldRange, newRange}) ->
       if newRange.containsRange(oldRange)
         newRanges.push(newRange)
         return
@@ -58,9 +64,6 @@
     fn()

     disposable.dispose()
-
-    newRanges = mergeIntersectingRanges(newRanges)
-    oldRanges = mergeIntersectingRanges(oldRanges)

     if range = sortRanges(_.compact([newRanges[0], _.last(oldRanges)]))[0]
       @vimState.mark.setRange('[', ']', range)
@@ -68,9 +71,10 @@
         @editor.setCursorBufferPosition(range.start)

     if settings.get('flashOnUndoRedo')
+      trimStartingNewLine = @trimStartingNewLine.bind(this)
       @onDidFinishOperation =>
-        @vimState.flash(oldRanges, type: 'removed')
-        @vimState.flash(newRanges, type: 'added')
+        @vimState.flash(newRanges.map(trimStartingNewLine), type: 'removed')
+        @vimState.flash(oldRanges.map(trimStartingNewLine), type: 'added')

   execute: ->
     @withTrackingChanges =>


Fix link handler

Fix link handler

--- lib/package-card.coffee
+++ lib/package-card.coffee
@@ -70,7 +70,8 @@
     @loadCachedMetadata()

     @packageMessage.on 'click', 'a', (e) ->
-      if href = this.getAttribute('href') and href.startsWith('atom:')
+      href = this.getAttribute('href')
+      if href?.startsWith('atom:')
         atom.workspace.open(href)
         false


Pass in event

Pass in event

--- lib/package-card.coffee
+++ lib/package-card.coffee
@@ -77,7 +77,7 @@
         event.stopPropagation()
         @parents('.settings-view').view()?.showPanel(@pack.name, {back: opts?.back, pack: @pack})

-    @installButton.on 'click', =>
+    @installButton.on 'click', (event) =>
       event.stopPropagation()
       @install()


stack-editor: fix reinit logic for reinit button

--- client/stack-editor/lib/editor/index.coffee
+++ client/stack-editor/lib/editor/index.coffee
@@ -706,7 +706,18 @@

   handleReinit: ->
-    kd.singletons.computeController.reinitStack()
+    stacks = kd.singletons.reactor.evaluateToJS ['StacksStore']
+    { stackTemplate } = @getData()
+
+    foundStack = null
+    Object.keys(stacks).forEach (key) ->
+      stack = stacks[key]
+      if stack.baseStackId is stackTemplate._id
+        foundStack = stack
+
+    return  unless foundStack
+
+    kd.singletons.computeController.reinitStack foundStack

   handleGenerateStack: ->


Search all panes when going to definition

--- lib/python-tools.coffee
+++ lib/python-tools.coffee
@@ -100,6 +100,7 @@
           options =
             initialLine: line
             initialColumn: column
+            searchAllPanes: true

           atom.workspace.open(first_def['path'], options).then((editor) ->
             # scroll to top first to get it centered correctly


Support for directions leftToRight is default, topToBottom optional

--- components/slideshow/kdslideshowview.coffee
+++ components/slideshow/kdslideshowview.coffee
@@ -4,7 +4,10 @@

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

-    options.cssClass = KD.utils.curry 'kd-slide', options.cssClass
+    options.cssClass      = KD.utils.curry 'kd-slide', options.cssClass
+    options.animation    ?= 'move'         # can be move or rotate
+    options.direction    ?= 'leftToRight'  # can be leftToRight or topToBottom
+    options.touchEnabled ?= yes

     super options, data

@@ -12,12 +15,30 @@
     @_coordsY  = []
     @_currentX = 0

-    hammer = Hammer @getElement()
+    {animation, direction, touchEnabled} = @getOptions()

-    hammer.on "swipeleft",  @bound 'nextPage'
-    hammer.on "swiperight", @bound 'previousPage'
-    hammer.on "swipeup",    @bound 'nextSubPage'
-    hammer.on "swipedown",  @bound 'previousSubPage'
+    topToBottom = [["#{animation}FromTop" , "#{animation}FromBottom"],
+                   ["#{animation}ToBottom", "#{animation}ToTop"]]
+    leftToRight = [["#{animation}FromLeft", "#{animation}FromRight"],
+                   ["#{animation}ToRight" , "#{animation}ToLeft"]]
+
+    if direction is 'topToBottom'
+      @xcoordAnimations = topToBottom
+      @ycoordAnimations = leftToRight
+      touchCallbacks    = ['nextSubPage', 'previousSubPage',
+                           'nextPage'   , 'previousPage']
+    else
+      @xcoordAnimations = leftToRight
+      @ycoordAnimations = topToBottom
+      touchCallbacks    = ['nextPage'   , 'previousPage',
+                           'nextSubPage', 'previousSubPage']
+
+    if touchEnabled
+      hammer = Hammer @getElement()
+      hammer.on "swipeleft",  @bound touchCallbacks[0]
+      hammer.on "swiperight", @bound touchCallbacks[1]
+      hammer.on "swipeup",    @bound touchCallbacks[2]
+      hammer.on "swipedown",  @bound touchCallbacks[3]

   addPage:(page)->
     @addSubView page
@@ -54,21 +75,20 @@
     index = Math.min pages.length - 1, Math.max 0, pageIndex
     return if current is index

-    direction = index < current
-    anim = (@getOption 'animation') or 'move'
+    direction = if index < current then 0 else 1

     if coord is X_COORD
       currentPage = pages[current][@_coordsY[current]]
       newPage     = pages[index][@_coordsY[index]]
       @_currentX  = index
-      newPage.move     if direction then "#{anim}FromLeft" else "#{anim}FromRight"
-      currentPage.move if direction then "#{anim}ToRight"  else "#{anim}ToLeft"
+      newPage.move     @xcoordAnimations[0][direction]
+      currentPage.move @xcoordAnimations[1][direction]
     else
       currentPage = pages[current]
       newPage     = pages[index]
       @_coordsY[@_currentX] = index
-      newPage.move     if direction then "#{anim}FromTop"  else "#{anim}FromBottom"
-      currentPage.move if direction then "#{anim}ToBottom" else "#{anim}ToTop"
+      newPage.move     @ycoordAnimations[0][direction]
+      currentPage.move @ycoordAnimations[1][direction]

     @emit 'CurrentPageChanged', x: @_currentX, y:@_coordsY[@_currentX]


Split up bracket/quote text object functions

--- lib/text-objects.coffee
+++ lib/text-objects.coffee
@@ -12,20 +12,24 @@

 class SelectInsideQuotes extends TextObject
   constructor: (@editor, @char) ->
+
+  findOpeningQuote: (pos) ->
+
+    pos = pos.copy()
+    while pos.row >= 0
+      line = @editor.lineForBufferRow(pos.row)
+      pos.column = line.length - 1 if pos.column == -1
+      while pos.column >= 0
+        if line[pos.column] == @char
+          return pos if pos.column == 0 or line[pos.column - 1] != '\\'
+        -- pos.column
+      pos.column = -1
+      -- pos.row
+
   select: ->
-    start = @editor.getCursorBufferPosition().copy()

-    return [false] unless (=>
-      while start.row >= 0
-        startLine = @editor.lineForBufferRow(start.row)
-        start.column = startLine.length - 1 if start.column == -1
-        while start.column >= 0
-          if startLine[start.column] == @char
-            return true if start.column == 0 or startLine[start.column - 1] != '\\'
-          -- start.column
-        start.column = -1
-        -- start.row
-    )()
+    start = @findOpeningQuote(@editor.getCursorBufferPosition())
+    return [false] unless start?

     ++ start.column  # skip the opening quote

@@ -50,24 +54,28 @@

 class SelectInsideBrackets extends TextObject
   constructor: (@editor, @beginChar, @endChar) ->
+
+  findOpeningBracket: (pos) ->
+
+    pos = pos.copy()
+    depth = 0
+    while pos.row >= 0
+      line = @editor.lineForBufferRow(pos.row)
+      pos.column = line.length - 1 if pos.column == -1
+      while pos.column >= 0
+        switch line[pos.column]
+          when @endChar then ++ depth
+          when @beginChar
+            return pos if -- depth < 0
+        -- pos.column
+      pos.column = -1
+      -- pos.row
+
   select: ->
-    start = @editor.getCursorBufferPosition().copy()
-    depth = 0
-
-    return [false] unless (=>
-      while start.row >= 0
-        startLine = @editor.lineForBufferRow(start.row)
-        start.column = startLine.length - 1 if start.column == -1
-        while start.column >= 0
-          switch startLine[start.column]
-            when @endChar then ++ depth
-            when @beginChar
-              return true if -- depth < 0
-          -- start.column
-        start.column = -1
-        -- start.row
-    )()
-
+
+    start = @findOpeningBracket(@editor.getCursorBufferPosition())
+    return [false] unless start?
+
     ++ start.column  # skip the opening bracket

     end = start.copy()


Fix typo in Package Activation Panel

Fix typo in Package Activation Panel

The number of activated packages which took longer than 5ms to activate
is now displayed correctly.

--- lib/timecop-view.coffee
+++ lib/timecop-view.coffee
@@ -55,7 +55,7 @@
     @packageActivationPanel.addPackages(packages, 'activateTime')
     @packageActivationPanel.summary.text """
       Activated #{count} packages in #{time}ms.
-      #{_.pluralize(packages.lenght, 'package')} took longer than 5ms to activate.
+      #{_.pluralize(packages.length, 'package')} took longer than 5ms to activate.
     """

   showLoadedThemes: ->


Fix variable name mismatch.

Fix variable name mismatch.

--- content_scripts/vimium_frontend.coffee
+++ content_scripts/vimium_frontend.coffee
@@ -839,7 +839,7 @@
 findAndFollowRel = (value) ->
   relTags = ["link", "a", "area"]
   for tag in relTags
-    elements = document.getElementsByTagName(relTag)
+    elements = document.getElementsByTagName(tag)
     for element in elements
       if (element.hasAttribute("rel") && element.rel == value)
         followLink(element)


Remove redundant code, add a new listener to be more consistent

--- content_scripts/vimium_frontend.coffee
+++ content_scripts/vimium_frontend.coffee
@@ -179,8 +179,6 @@
 # Initialization tasks that must wait for the document to be ready.
 #
 initializeOnDomReady = ->
-  registerFrame()
-
   enterInsertModeIfElementIsFocused() if isEnabledForUrl

   # Tell the background page we're in the dom ready state.
@@ -190,8 +188,7 @@
   chrome.runtime.sendMessage(
     handler: "registerFrame"
     frameId: frameId
-    is_top: window.top == window.self
-    total: frames.length + 1)
+    is_top: window.top == window.self)

 # Unregister the frame if we're going to exit.
 unregisterFrame = ->
@@ -1062,6 +1059,7 @@
       state.onUpdate(value)

 initializePreDomReady()
+window.addEventListener("DOMContentLoaded", registerFrame)
 window.addEventListener("DOMContentLoaded", initializeOnDomReady)
 window.addEventListener("unload", unregisterFrame)


Fix race condition on the options page (better still). a

--- content_scripts/vimium_frontend.coffee
+++ content_scripts/vimium_frontend.coffee
@@ -1140,9 +1140,9 @@
     window.addEventListener "scroll", @onScroll

 initializePreDomReady()
-window.addEventListener("DOMContentLoaded", registerFrame)
-window.addEventListener("unload", unregisterFrame)
 DomUtils.documentReady initializeOnDomReady
+DomUtils.documentReady registerFrame
+window.addEventListener "unload", unregisterFrame

 window.onbeforeunload = ->
   chrome.runtime.sendMessage(


fix submit bug due to multiple instances of same id on term page

--- cwod_site/media/js/capwords-new.coffee
+++ cwod_site/media/js/capwords-new.coffee
@@ -464,7 +464,6 @@
                 window.cw.annotation_line_coords['term'] = []

                 jQuery.getJSON (imgUrl + '&chof=json'), (data) ->
-
                     copy_coords = (c) ->
                         if c.name is 'line0'
                             window.cw.annotation_line_coords['term'].push (jQuery.extend true, [], c.coords)
@@ -866,7 +865,6 @@
             startYear = cw.minMonth.slice(0, 4)
             endYear = cw.maxMonth.slice(0, 4)

-            console.log "slidin' to #{startYear}, #{endYear}"
             $("#slider-range").slider("option", "values", [startYear, endYear])
             cw.limit cw.minMonth, cw.maxMonth

@@ -875,7 +873,8 @@

     readLegislatorHistory: ->
         hash = History.getState().hash.split('?')[1]
-        data = {}
+        data = {congress: 112}
+        $("#congress").val(112)
         if hash
             pieces = hash.split '&'
             chamber = party = congress = state = undefined
@@ -887,33 +886,34 @@
         this.legislatorSearch(data)

     readTermDetailPageHistory: ->
+        # TODO: range does not properly update
+        this.minMonth = "199601"
+        this.maxMonth = "201112"
         if typeof History.getState().hash is 'undefined'
             return
         hash = History.getState().hash.split('?')[1]
-        if not hash
-            return
-        pieces = [x.split '=' for x in hash.split '&'][0]
-        for piece in pieces
-            [k, v] = piece
-            if k == 'start'
-                this.minMonth = v
-                this.start_date = this.dateFromMonth(this.minMonth)
-
-            else if k == 'end'
-                this.maxMonth = v
-                this.end_date = this.dateFromMonth(this.maxMonth)
-
-#            else if k == 'pt' and v == 'true'
-#                $('#overallTimeline').hide(->
-#                    $('#partyTimelineSelect').attr('checked', true)
-#                    $('#partyTimeline').show()
-#                )
-
+        if hash
+            pieces = [x.split '=' for x in hash.split '&'][0]
+            for piece in pieces
+                [k, v] = piece
+                if k == 'start'
+                    this.minMonth = v
+                    this.start_date = this.dateFromMonth(this.minMonth)
+
+                else if k == 'end'
+                    this.maxMonth = v
+                    this.end_date = this.dateFromMonth(this.maxMonth)
+
+               # else if k == 'pt' and v == 'true'
+               #     $('#overallTimeline').hide(->
+               #         $('#partyTimelineSelect').attr('checked', true)
+               #         $('#partyTimeline').show()
+               #     )

         if this.minMonth or this.maxMonth
             startYear = this.minMonth.slice(0, 4)
             endYear = this.maxMonth.slice(0, 4)
-            $("#slider-range").slider("option", "values", [startYear, endYear])
+            $("#slider-range").slider("values", [startYear, endYear])

             this.limit this.minMonth, this.maxMonth

@@ -1119,7 +1119,7 @@
     if window.location.pathname.match /^\/legislator\/?$/
         cw.readLegislatorHistory()

-    else if window.location.pathname.match /^term\/[.+]\/?/
+    else if window.location.pathname.match /^\/term\/.+\/?$/
         cw.readTermDetailPageHistory()
         cw.populateTermDetailPage termDetailTerm

@@ -1148,7 +1148,7 @@
         e.preventDefault()
         $('.toggleSearchCompare').slideToggle 'fast', 'swing'

-    $('#compareBtn').live 'click', (e) ->
+    $('#compareTermBtn').live 'click', (e) ->
         e.preventDefault()
         word = $(this).find('em').text()
         $('#terma').val word
@@ -1219,7 +1219,6 @@

     if not _($('#slider-range')).isEmpty()
         d = new Date()
-
         if cw.minMonth and cw.maxMonth
             startYear = cw.minMonth.slice(0, 4)
             endYear = cw.maxMonth.slice(0, 4)


use object_create instead of object_new, get rid of the \'s to escape quotes when creating our fake literal in the 'delete' handling in visitUnaryExpression, and also handle 'undefined' literals properly.

--- lib/compiler.coffee
+++ lib/compiler.coffee
@@ -185,7 +185,7 @@
                 @ejs = {
                         personality:           module.getOrInsertExternalFunction "__ejs_personality_v0",           int32Type, [int32Type, int32Type, int64Type, int8PointerType, int8PointerType]

-                        object_new:            module.getOrInsertExternalFunction "_ejs_object_new",                EjsValueType, [EjsValueType]
+                        object_create:         module.getOrInsertExternalFunction "_ejs_object_create",             EjsValueType, [EjsValueType]
                         arguments_new:         module.getOrInsertExternalFunction "_ejs_arguments_new",             EjsValueType, [int32Type, EjsValueType.pointerTo()]
                         array_new:             module.getOrInsertExternalFunction "_ejs_array_new",                 EjsValueType, [int32Type]
                         number_new:            only_reads_memory module.getOrInsertExternalFunction "_ejs_number_new",                EjsValueType, [llvm.Type.getDoubleTy()]
@@ -339,7 +339,7 @@
                                 pname = prop.value

                         debug.log "createPropertyStore #{obj}[#{pname}]"
-
+
                         c = irbuilder.createGlobalStringPtr pname, "strconst"
                         @createCall @ejs.object_setprop_utf8, [obj, c, rhs], "propstore_#{pname}"

@@ -917,7 +917,7 @@
                                 fake_literal = {
                                         type: syntax.Literal
                                         value: n.argument.property.name
-                                        raw: "\"#{n.argument.property.name}\""
+                                        raw: "'#{n.argument.property.name}'"
                                 }
                                 return @createCall callee, [(@visitOrNull n.argument.object), (@visit fake_literal)], "result"
                         else
@@ -1036,7 +1036,7 @@

                 proto = @createPropertyLoad ctor, { name: "prototype" }, false

-                thisArg = @createCall @ejs.object_new, [proto], "objtmp"
+                thisArg = @createCall @ejs.object_create, [proto], "objtmp"

                 argv.push ctor                                                      # %closure
                 argv.push thisArg                                                   # %this
@@ -1145,7 +1145,7 @@
                 rv

         visitObjectExpression: (n) ->
-                obj = @createCall @ejs.object_new, [@loadNullEjsValue()], "objtmp"
+                obj = @createCall @ejs.object_create, [@loadNullEjsValue()], "objtmp"
                 for property in n.properties
                         val = @visit property.value
                         key = property.key
@@ -1170,6 +1170,9 @@
                 if n.value is null
                         debug.log "literal: null"
                         return @loadNullEjsValue() # this isn't properly typed...  dunno what to do about this here
+                else if n.value is undefined
+                        debug.log "literal: undefined"
+                        return @loadUndefinedEjsValue() # this isn't properly typed...  dunno what to do about this here
                 else if typeof n.raw is "string" and (n.raw[0] is '"' or n.raw[0] is "'")
                         debug.log "literal string: #{n.value}"


Content model: don’t return `dzi` until `ready`.

--- lib/content._coffee
+++ lib/content._coffee
@@ -118,6 +118,12 @@
     # TODO: We should save our DZIs with their own custom ID (and store that
     # here in our database), to support cache purge/refresh/etc.
     get dzi: ->
+        # Our database files have the DZI info every for image, but we haven't
+        # necessarily copied every image's DZI *files*, so we shouldn't return
+        # the `dzi` property until we do. Fortunately, we track whether we've
+        # copied the files or not with the `ready` property.
+        return if not @ready
+
         raw = @_data.dzi
         return raw if not raw
         clone = {}


Fixes #576

Fixes #576

initialize package if no repo is opened initially but later added

--- lib/git-plus.coffee
+++ lib/git-plus.coffee
@@ -87,7 +87,8 @@
     @subscriptions = new CompositeDisposable
     repos = atom.project.getRepositories().filter (r) -> r?
     if repos.length is 0
-      @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:init', => GitInit().then => @activate()
+      atom.project.onDidChangePaths (paths) => @activate()
+      @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:init', => GitInit().then(@activate)
     else
       contextMenu()
       @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:menu', -> new GitPaletteView()


add  to all other platforms' compilation string.

--- lib/grammars.coffee
+++ lib/grammars.coffee
@@ -135,14 +135,14 @@
         args: (context) ->
           code = context.getCode(true)
           tmpFile = GrammarUtils.createTempFileWithCode(code, ".cpp")
-          ["-c", "g++ -Wall -include stdio.h -include iostream '" + tmpFile + "' -o /tmp/cpp.out && /tmp/cpp.out"]
+          ["-c", "g++ -std=c++14 -Wall -include stdio.h -include iostream '" + tmpFile + "' -o /tmp/cpp.out && /tmp/cpp.out"]
       "File Based":
         command: "bash"
-        args: (context) -> ["-c", "g++ -Wall -include stdio.h -include iostream '" + context.filepath + "' -o /tmp/cpp.out && /tmp/cpp.out"]
+        args: (context) -> ["-c", "g++ -std=c++14 -Wall -include stdio.h -include iostream '" + context.filepath + "' -o /tmp/cpp.out && /tmp/cpp.out"]
     else if GrammarUtils.OperatingSystem.isWindows() and GrammarUtils.OperatingSystem.release().split(".").slice -1 >= '14399'
       "File Based":
         command: "bash"
-        args: (context) -> ["-c", "g++ -Wall -include stdio.h -include iostream '/mnt/" + path.posix.join.apply(path.posix, [].concat([context.filepath.split(path.win32.sep)[0].toLowerCase()], context.filepath.split(path.win32.sep).slice(1))).replace(":", "") + "' -o /tmp/cpp.out && /tmp/cpp.out"]
+        args: (context) -> ["-c", "g++ -std=c++14 -Wall -include stdio.h -include iostream '/mnt/" + path.posix.join.apply(path.posix, [].concat([context.filepath.split(path.win32.sep)[0].toLowerCase()], context.filepath.split(path.win32.sep).slice(1))).replace(":", "") + "' -o /tmp/cpp.out && /tmp/cpp.out"]

   Clojure:
     "Selection Based":


Remove superfluous zombie include

Remove superfluous zombie include

--- server/test/integration/indicators.coffee
+++ server/test/integration/indicators.coffee
@@ -7,7 +7,6 @@
 Q = require('q')
 sinon = require('sinon')
 passportStub = require 'passport-stub'
-Browser = require("zombie")

 Indicator = require('../../models/indicator').model
 Page = require('../../models/page').model


Tests for up.request() error handling

Tests for up.request() error handling

--- spec_app/spec/javascripts/up/proxy_spec.js.coffee
+++ spec_app/spec/javascripts/up/proxy_spec.js.coffee
@@ -98,6 +98,70 @@
             expect(response.url).toMatchURL('/url')
             done()

+      describe 'error handling', ->
+
+        it 'rejects with up.Failed when there was a network error', (done) ->
+          request = up.request('/url')
+
+          u.task =>
+            @lastRequest().responseError()
+
+            promiseState(request).then (result) ->
+              expect(result.state).toEqual('rejected')
+              expect(result.value.name).toEqual('up.Failed')
+              expect(result.value.name).toEqual('up.Failed')
+              done()
+
+        it 'rejects with a non-ok up.Response when the server sends a 404 status code', (done) ->
+          request = up.request('/url')
+
+          u.task =>
+            @respondWith('text', status: 404)
+
+            promiseState(request).then (result) ->
+              expect(result.state).toEqual('rejected')
+              expect(result.value).toEqual(jasmine.any(up.Response))
+              expect(result.value.status).toEqual(404)
+              expect(result.value.ok).toEqual(false)
+              done()
+
+        it 'rejects with a non-ok up.Response when the server sends a 500 status code', (done) ->
+          request = up.request('/url')
+
+          u.task =>
+            @respondWith('text', status: 500)
+
+            promiseState(request).then (result) ->
+              expect(result.state).toEqual('rejected')
+              expect(result.value).toEqual(jasmine.any(up.Response))
+              expect(result.value.status).toEqual(500)
+              expect(result.value.ok).toEqual(false)
+              done()
+
+
+        it 'rejects with AbortError when the request was aborted', (done) ->
+          request = up.request('/url')
+
+          u.task =>
+            up.proxy.abort(request)
+
+            promiseState(request).then (result) ->
+              expect(result.state).toEqual('rejected')
+              expect(result.value.name).toEqual('AbortError')
+              done()
+
+        it 'rejects with AbortError when the request times out', (done) ->
+          request = up.request('/url')
+
+          u.task =>
+            jasmine.clock().install() # required by responseTimeout()
+            @lastRequest().responseTimeout()
+
+            promiseState(request).then (result) ->
+              expect(result.state).toEqual('rejected')
+              expect(result.value.name).toEqual('AbortError')
+              done()
+
       describe 'when the server responds with an X-Up-Method header', ->

         it 'updates the { method } property in the response object', (done) ->
@@ -758,7 +822,28 @@
               'up:proxy:recover'
             ])

-        it 'emits up:proxy:recover if a request failed fatally'
+        it 'emits up:proxy:recover if a request failed fatally', asyncSpec (next) ->
+          up.proxy.config.slowDelay = 10
+
+          next =>
+            @request = up.request(url: '/foo')
+
+          next.after 50, =>
+            expect(@events).toEqual([
+              'up:proxy:load',
+              'up:proxy:slow'
+            ])
+
+          next =>
+            @lastRequest().responseError()
+
+          next =>
+            expect(@events).toEqual([
+              'up:proxy:load',
+              'up:proxy:slow',
+              'up:proxy:fatal',
+              'up:proxy:recover'
+            ])

     describe 'up.ajax', ->


Expose getNextQueryFromRegexMatches on window

--- content_scripts/vimium_frontend.coffee
+++ content_scripts/vimium_frontend.coffee
@@ -757,7 +757,7 @@
       DomUtils.isDOMDescendant(findModeAnchorNode, document.activeElement))
     DomUtils.simulateSelect(document.activeElement)

-getNextQueryFromRegexMatches = (stepSize) ->
+window.getNextQueryFromRegexMatches = (stepSize) ->
   # find()ing an empty query always returns false
   return "" unless findModeQuery.regexMatches


Use RegExp to escape special characters for plain find queries

--- content_scripts/vimium_frontend.coffee
+++ content_scripts/vimium_frontend.coffee
@@ -560,19 +560,9 @@
   # from the internal representation used by window.find.
   else
     # escape all special characters, so RegExp just parses the string 'as is'.
-    parsedNonRegexQuery = findModeQuery.parsedQuery.replace("\\", "\\\\")
-                                                   .replace("^", "\\^")
-                                                   .replace("$", "\\$")
-                                                   .replace("*", "\\*")
-                                                   .replace("+", "\\+")
-                                                   .replace("?", "\\?")
-                                                   .replace("(", "\\(")
-                                                   .replace(")", "\\)")
-                                                   .replace("|", "\\|")
-                                                   .replace("{", "\\{")
-                                                   .replace("}", "\\}")
-                                                   .replace("[", "\\[")
-                                                   .replace("]", "\\]")
+    # Taken from http://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex
+    escapeRegExp = /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g
+    parsedNonRegexQuery = findModeQuery.parsedQuery.replace(escapeRegExp, (char) -> "\\" + char)
     pattern = new RegExp(parsedNonRegexQuery, "g" + (if findModeQuery.ignoreCase then "i" else ""))
     text = document.body.innerText
     findModeQuery.matchCount = text.match(pattern)?.length


Catch undefined reference to handledKeydownEvents.

--- content_scripts/vimium_frontend.coffee
+++ content_scripts/vimium_frontend.coffee
@@ -463,7 +463,7 @@
       event.srcElement.blur()
     exitInsertMode()
     DomUtils.suppressEvent event
-    handledKeydownEvents.push event
+    KeydownEvents.push event

   else if (findMode)
     if (KeyboardUtils.isEscape(event))


Projects: ace added to projects and allowed apps

--- projects.coffee
+++ projects.coffee
@@ -74,6 +74,12 @@
     script        : "website/js/app.terminal.#{KODING_VERSION}.js"
     sourceMapRoot : "Terminal/"

+  Ace             :
+    files         : "client/Ace/includes.coffee"
+    style         : "website/css/app.ace.#{KODING_VERSION}.css"
+    script        : "website/js/app.ace.#{KODING_VERSION}.js"
+    sourceMapRoot : "Ace/"
+
 bundles       =

   # Social      :


Remove single file parse. It is unnecessary because the code later scans all feature files

--- lib/provider.coffee
+++ lib/provider.coffee
@@ -3,6 +3,7 @@

 PROPERTY_PREFIX_PATTERN = /(?:^|\[|\(|,|=|:|\s)\s*((?:And|Given|Then|When)\s(?:[a-zA-Z]+\.?){0,2})$/
 PATH_CONFIG_KEY = 'cucumber-autocomplete.path'
+CUCUMBER_KEYWORDS_PATTERN = /(Given|And|When|Then)(.*)/g

 module.exports =
   selector: '.source.feature, .feature'
@@ -20,15 +21,11 @@
     return [] unless @matchCucumberKeyword(line)

     results = []
-    regex = /(Given|And|When|Then)(.*)/g
-    while (myRegexArray = regex.exec(file)) != null
-      results.push({"text":myRegexArray[2].replace /^\s+|\s+$/g, ""})
-
     for feature in fs.readdirSync("#{@rootDirectory()}#{@featuresDirectory()}")
       continue unless /.feature/.test(feature)
       data = fs.readFileSync "#{@rootDirectory()}#{@featuresDirectory()}/#{feature}", 'utf8'
-      while (myRegexArray2 = regex.exec(data)) != null
-        results.push({"text":myRegexArray2[2].replace /^\s+|\s+$/g, ""})
+      while (myRegexArray = CUCUMBER_KEYWORDS_PATTERN.exec(data)) != null
+        results.push({"text":myRegexArray[2].replace /^\s+|\s+$/g, ""})

     return results


:penguin: Match resources folder case insensitively

This will handle the resources path on Linux

--- lib/reporter.coffee
+++ lib/reporter.coffee
@@ -35,7 +35,7 @@
       for line in coffeestack.convertStackTrace(error.stack).split('\n')
         if match = atLinePattern.exec(line)
           stacktrace.push
-            file: match[3].replace(/(.*[\/\\]Resources[\/\\])/, '')
+            file: match[3].replace(/(.*[\/\\][rR]esources[\/\\])/, '')
             method: match[2].replace(/^(HTMLDocument|HTML[^\.]*Element|Object)\./, '')
             columnNumber: parseInt(match[5])
             lineNumber: parseInt(match[4])


Removed backwards compatibility hack.

Removed backwards compatibility hack.

--- lib/sequence.coffee
+++ lib/sequence.coffee
@@ -240,5 +240,4 @@
 # --------------------------------------------------------------------

 exports ?= this.pazy ?= {}
-exports.Sequence = seq # quick hack for backward compatibility
 exports.seq = seq


Better code style

Better code style

--- lib/simulate.coffee
+++ lib/simulate.coffee
@@ -1,46 +1,25 @@
-####
-# Usage: Pass response body as argument with extra key-value below.
-# poi_is_combined = Boolean # 連合艦隊？
-# poi_is_water = Boolean    # 水上打撃部隊=true, 空母機動部隊=false
-# poi_is_night = Boolean    # 夜戦？
-# poi_sortie_fleet = [int, ...] # api_ship_id
-# poi_sortie_equipment = [[int, ...], ...]  # api_slotitem_id
-# poi_combined_fleet = [int, ...]
-# poi_combined_equipment = [[int, ...], ...]
-####
-
 {Ship, ShipOwner, Attack, AttackType, HitType, Stage, StageType} = require './common'

 # Map from api id of attack type to AttackType
-# 特殊砲撃　0=通常, 1=レーザー攻撃, 2=連撃, 3=カットイン(主砲/副砲), 4=カットイン(主砲/電探), 5=カットイン(主砲/徹甲), 6=カットイン(主砲/主砲)
-AttackTypeMap = [
-  "Normal",
-  "Laser",
-  "Double",
-  "Primary_Secondary_CI",
-  "Primary_Radar_CI",
-  "Primary_AP_CI",
-  "Primary_Primary_CI",
-  "Primary_Torpedo_CI",
-  "Torpedo_Torpedo_CI"
-]
+# 特殊砲撃 0=通常, 1=レーザー攻撃, 2=連撃, 3=カットイン(主砲/副砲), 4=カットイン(主砲/電探), 5=カットイン(主砲/徹甲), 6=カットイン(主砲/主砲)
+AttackTypeMap =
+  0: AttackType.Normal
+  1: AttackType.Laser
+  2: AttackType.Double
+  3: AttackType.Primary_Secondary_CI
+  4: AttackType.Primary_Radar_CI
+  5: AttackType.Primary_AP_CI
 # Map from api id of night attack type to day attack type id
-# NightID DayID Type
-# 0       0     通常攻撃
-# 1       2     連撃
-# 2       7     カットイン(主砲/魚雷)
-# 3       8     カットイン(魚雷/魚雷)
-# 4       3     カットイン(主砲/副砲)
-# 5       6     カットイン(主砲/主砲)
-NightAttackTypeMap = [
-  0,
-  2,
-  7,
-  8,
-  3,
-  6
-]
+# 夜戦攻撃種別 0=通常攻撃, 1=連撃, 2=カットイン(主砲/魚雷), 3=カットイン(魚雷/魚雷), 4=カットイン(主砲/副砲), 5=カットイン(主砲/主砲)
+NightAttackTypeMap =
+  0: AttackType.Normal
+  1: AttackType.Double
+  2: AttackType.Primary_Torpedo_CI
+  3: AttackType.Torpedo_Torpedo_CI
+  4: AttackType.Primary_Secondary_CI
+  5: AttackType.Primary_Primary_CI
+

 checkRepairItem = (sortieShip) ->
   if sortieShip.nowHP <= 0
@@ -64,7 +43,7 @@
       critical = []
       critical.push if kouku.api_ecl_flag[i] == 1 then HitType.Critical else if damage == 0 then HitType.Miss else HitType.Hit
       enemyShip[i - 1].nowHP -= damage
-      list.push new Attack AttackType[AttackTypeMap[0]], null, enemyShip[i - 1], enemyShip[i - 1].maxHP, enemyShip[i - 1].nowHP, dmg, critical
+      list.push new Attack AttackType.Normal, null, enemyShip[i - 1], enemyShip[i - 1].maxHP, enemyShip[i - 1].nowHP, dmg, critical
   if kouku.api_fdam?
     for damage, i in kouku.api_fdam
       continue if (kouku.api_fbak_flag[i] <= 0 && kouku.api_frai_flag[i] <= 0) || i == 0
@@ -75,7 +54,7 @@
       critical.push if kouku.api_fcl_flag[i] == 1 then HitType.Critical else if damage == 0 then HitType.Miss else HitType.Hit
       sortieShip[i - 1].nowHP -= damage
       useItem = checkRepairItem sortieShip[i - 1]
-      list.push new Attack AttackType[AttackTypeMap[0]], null, sortieShip[i - 1], sortieShip[i - 1].maxHP, sortieShip[i - 1].nowHP, dmg, critical, useItem
+      list.push new Attack AttackType.Normal, null, sortieShip[i - 1], sortieShip[i - 1].maxHP, sortieShip[i - 1].nowHP, dmg, critical, useItem
   # test log
   #console.log list
   list
@@ -96,7 +75,7 @@
       else
         critical.push HitType.Miss
       enemyShip[i - 1].nowHP -= damage
-      list.push new Attack AttackType[AttackTypeMap[0]], null, enemyShip[i - 1], enemyShip[i - 1].maxHP, enemyShip[i - 1].nowHP, dmg, critical
+      list.push new Attack AttackType.Normal, null, enemyShip[i - 1], enemyShip[i - 1].maxHP, enemyShip[i - 1].nowHP, dmg, critical
   else if support.api_support_hourai?
     for damage, i in support.api_support_hourai.api_damage
       continue unless 1 <= i <= 6
@@ -111,7 +90,7 @@
       else
         critical.push HitType.Miss
       enemyShip[i - 1].nowHP -= damage
-      list.push new Attack AttackType[AttackTypeMap[0]], null, enemyShip[i - 1], enemyShip[i - 1].maxHP, enemyShip[i - 1].nowHP, dmg, critical
+      list.push new Attack AttackType.Normal, null, enemyShip[i - 1], enemyShip[i - 1].maxHP, enemyShip[i - 1].nowHP, dmg, critical
   list

 #api_opening_atack	：開幕雷撃戦 *スペルミスあり、注意
@@ -142,7 +121,7 @@
       else
         critical.push HitType.Miss
     enemyShip[target - 1].nowHP -= damage
-    list.push new Attack AttackType[AttackTypeMap[0]], sortieShip[i - 1], enemyShip[target - 1], enemyShip[target - 1].maxHP, enemyShip[target - 1].nowHP, dmg, critical
+    list.push new Attack AttackType.Normal, sortieShip[i - 1], enemyShip[target - 1], enemyShip[target - 1].maxHP, enemyShip[target - 1].nowHP, dmg, critical
   # 雷撃ターゲット
   for target, i in raigeki.api_erai
     continue if target <= 0
@@ -160,7 +139,7 @@
         critical.push HitType.Miss
     sortieShip[target - 1].nowHP -= damage
     useItem = checkRepairItem sortieShip[target - 1]
-    list.push new Attack AttackType[AttackTypeMap[0]], enemyShip[i - 1], sortieShip[target - 1], sortieShip[target - 1].maxHP, sortieShip[target - 1].nowHP, dmg, critical, useItem
+    list.push new Attack AttackType.Normal, enemyShip[i - 1], sortieShip[target - 1], sortieShip[target - 1].maxHP, sortieShip[target - 1].nowHP, dmg, critical, useItem
   # test log
   #console.log list
   list
@@ -188,18 +167,18 @@
     target = hougeki.api_df_list[i][0] - 1
     attackType = 0
     if !isNight
-      attackType = hougeki.api_at_type[i]
+      attackType = AttackTypeMap[hougeki.api_at_type[i]]
     else
       attackType = NightAttackTypeMap[hougeki.api_sp_list[i]]
     if target < 6
       # api_cl_list		：クリティカルフラグ 0=ミス, 1=命中, 2=クリティカル　命中(0ダメージ)も存在する？
       sortieShip[target].nowHP -= totalDamage
       useItem = checkRepairItem sortieShip[target]
-      list.push new Attack AttackType[AttackTypeMap[attackType]], enemyShip[damageFrom - 6], sortieShip[target], sortieShip[target].maxHP, sortieShip[target].nowHP, dmg, critical, useItem
+      list.push new Attack attackType, enemyShip[damageFrom - 6], sortieShip[target], sortieShip[target].maxHP, sortieShip[target].nowHP, dmg, critical, useItem
     else
       # api_cl_list		：クリティカルフラグ 0=ミス, 1=命中, 2=クリティカル　命中(0ダメージ)も存在する？
       enemyShip[target - 6].nowHP -= totalDamage
-      list.push new Attack AttackType[AttackTypeMap[attackType]], sortieShip[damageFrom], enemyShip[target - 6], enemyShip[target - 6].maxHP, enemyShip[target - 6].nowHP, dmg, critical
+      list.push new Attack attackType, sortieShip[damageFrom], enemyShip[target - 6], enemyShip[target - 6].maxHP, enemyShip[target - 6].nowHP, dmg, critical
   # test log
   #console.log list
   list


format create room message

format create room message

--- lib/sm-slack.coffee
+++ lib/sm-slack.coffee
@@ -58,7 +58,7 @@
       token: @botToken
       channel: channelId
       as_user: false
-      # username: @robot.name
+      username: @robot.name

     SlackApi.chat.postMessage _.merge(detail, opts)


Use dispatchEvent instead of trigger

Use dispatchEvent instead of trigger

--- lib/tab-view.coffee
+++ lib/tab-view.coffee
@@ -35,7 +35,9 @@
       @mouseEnterSubscription.dispose()
       @hasBeenMousedOver = true
       @updateTooltip()
-      @trigger 'mouseenter' # Trigger again so the tooltip shows
+
+      # Trigger again so the tooltip shows
+      @element.dispatchEvent(new CustomEvent('mouseenter', bubbles: true))

     @mouseEnterSubscription = dispose: =>
       @element.removeEventListener('mouseenter', onMouseEnter)


Tests: IDE: Layout: Fixed openDrawingBoard test.

--- client/test/lib/ide/layout.coffee
+++ client/test/lib/ide/layout.coffee
@@ -50,12 +50,22 @@

   openDrawingBoard: (browser) ->

+    handleSelector     = '.kdtabhandle.drawing'
+    activePaneSelector = '.kdtabpaneview.drawing.active .drawing-pane'
+
     helpers.beginTest(browser)
     helpers.waitForVMRunning(browser)

-    layoutHelpers.openMenuAndClick(browser, '.new-drawing-board')
+    browser.elements 'css selector', handleSelector, (result) ->
+      if result.value.length > 0
+        console.log(' ✔ A drawing board is already opened. Ending test...')
+        browser.end()
+      else
+        layoutHelpers.openMenuAndClick(browser, '.new-drawing-board')

-    browser
-      .pause 4000
-      .waitForElementVisible   '.pane-wrapper .kdsplitview-panel.panel-1 .drawing-pane .drawing-board-toolbar', 20000 # Assertion
-      .end()
+        browser
+          .pause 4000
+          .waitForElementVisible handleSelector + '.active', 20000
+          .waitForElementVisible activePaneSelector, 20000 # Assertion
+          .waitForElementVisible activePaneSelector + ' .drawing-board-toolbar', 20000 # Assertion
+          .end()


Remove trailing spaces

Remove trailing spaces

--- server/app/utils.coffee
+++ server/app/utils.coffee
@@ -23,9 +23,9 @@
       t = t + s.substring n - i - 1, n - i
     s = t
     s
-
+
   decimalToAnother @, 8
-
+

 Function::swiss = (parent, names...)->
   for name in names
@@ -33,7 +33,7 @@
   @

 class utils
-
+
   rules = [
     [/(matr|vert|ind)ix|ex$/gi  , '$1ices'  ]
     [/(m)an$/gi                 , '$1en'    ]
@@ -82,15 +82,15 @@
     'deer'
     'news'
   ]
-
+
   regExpEscape:(text)->
       text.replace /[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"
-
+
   getPathRegExp:(path)->
     # we coopt the express "Router" class (private API) for our own sinister purposes:
     {Route} = require 'express'
     new Route(null, path).regexp
-
+
   pluralize:(str) ->
     # original author: TJ Holowaychuk (extracted from Ext.js)
     # ported to coffeescript for inclusion here.
@@ -102,7 +102,7 @@

   capitalize:(str)->
     str[0].toUpperCase()+str.substr 1
-
+
   toCamelCase:(str)->
     str.replace /([\-][a-z])/g, ($1)-> $1.toUpperCase().replace /\-/g, ''

@@ -115,7 +115,7 @@
   now: ->     Date.now()

   uniqid: ->  Date.now() + Math.floor Math.random()*110000
-
+
   randomBase62Hash:(length=8, ensureLength=yes)->
     # TODO: this function was ported from an answer on stackoverflow, and must
     #       perform wretchedly.  Also, it would seem likely that it is not
@@ -132,7 +132,7 @@
         hash #TODO: wtf is going on that this line is needed!? (but it is)

     hash.substr 0, length
-
+
   filename:(path) -> new RegExp("(/?([^\/]+))+").exec(path).pop()

   dirname:(path) -> "dirname_test"
@@ -145,26 +145,26 @@

   nicename:(name) ->
     name.toLowerCase().replace /[^a-z0-9]+/g, '-'
-
+
   makeTeaser:(long, options={})->
     return unless long
-
+
     minLength = options.minLength or 450
     maxLength = options.maxLength or 600
-
+
     return long if long < minLength or long < maxLength
-
+
     long = long.substr 0, maxLength
-
+
     # prefer to end the teaser at the end of a sentence (a period).
     # failing that prefer to end the teaser at the end of a word (a space).
     candidate = @tryToShorten(long, '.') or @tryToShorten long, ' ', '...'
-
+
     if candidate?.length > minLength
       candidate
     else
       long
-
+
   tryToShorten:(long, optimalBreak, suffix)->
     unless ~ long.indexOf optimalBreak then no
     else
@@ -182,7 +182,7 @@

   getNextGuestUsername:->
     'guest'
-
+
   traverseAndStripTags:(obj, allowed)->
     self = @
     traverse(obj).map (value, key)->
@@ -200,7 +200,7 @@
 (->
   Path = require 'path'

-  Path.toDropbox = (path)->
+  Path.toDropbox = (path)->
     path = if path then @normalize path else ''
     path = path.substr 1 if path.length > 0 and path[0] is '/'
     path
@@ -211,9 +211,9 @@

   calls the provided function with the first filename that is
   similar to filepath, but that does not exist in the filesystem.
-  generate new filename candidates by appending an underscore "_",
+  generate new filename candidates by appending an underscore "_",
   and the next integer.
-  ###
+  ###
   Path.makeSafe = (filepath, callback)->
     _checkPath filepath, callback
   ###
@@ -259,7 +259,7 @@
           run path + '/' + newFolder
         else
           callback()
-
+
     run dirs.shift()

 )()


Add usertracker access for home

Add usertracker access for home

--- server/lib/token.coffee
+++ server/lib/token.coffee
@@ -331,6 +331,7 @@
         "alarm": "authorized"
         "event": "authorized"
         "userpreference": "authorized"
+        "usetracker": "authorized"
         "cozyinstance": "authorized"
         "encryptedkeys": "authorized"
         "stackapplication": "authorized"


rudimentary query language

rudimentary query language

--- classes/event-box.coffee
+++ classes/event-box.coffee
@@ -1,19 +1,36 @@
-search = (searchString, event) ->
-	for property, value of event
-		if (typeof value == "string")
-			if (value.toLowerCase().indexOf(searchString.toLowerCase()) > -1)
+search = (searchObject, event) ->
+	if ((typeof searchObject == "string") && (typeof event == "string"))
+		if (event.toLowerCase().indexOf(searchObject.toLowerCase()) > -1)
+			return true
+		else return false
+
+	else if ((typeof searchObject == "string") && (typeof event == "object"))
+		for key, val of event
+			if search(searchObject, val)
 				return true
-		else if (typeof value == "object")
-			if search(searchString, value)
-				return true
+
+	else if ((typeof searchObject == "object") && (typeof event == "object"))
+		for key, val of searchObject
+			if (!search(val, event[key]))
+				return false
+		return true
+
 	return false

 EventBox = React.createClass
 	render: ->
 		results = []
+		searchObject = {}
+		searchArray = this.props.search.split(" ")
+		for searchArrayItem in searchArray
+			if searchArrayItem.indexOf(":") > -1
+				searchObject[searchArrayItem.split(":")[0]] = searchArrayItem.split(":")[1]
 		# do search, dump in results
+		if (searchArray.length == 0)
+			searchObject = this.props.search
+		console.log searchObject
 		for event in events
-			if search(this.props.search, event)
+			if search(searchObject, event)
 				results.push(event)
 		# sort results
 		results.sort (a, b) ->
@@ -37,6 +54,6 @@
 Buttons = React.createClass
 	render: ->
 		React.createElement "div", className: "row",
-				React.createElement "i", {className: "fa fa-filter fa-3x", style: {"cursor": "pointer", "padding": 15}, onClick: this.handleClick}
+			React.createElement "i", {className: "fa fa-filter fa-3x", style: {"cursor": "pointer", "padding": 15}, onClick: this.handleClick}
 	handleClick: ->
 		this.props.onFilterClick()


Home: minor fix for an unclosed html tag

--- client/Intro/home.coffee
+++ client/Intro/home.coffee
@@ -110,7 +110,7 @@

             {{> @registerForm}}
-
{{> @githubLink}}

+
{{> @githubLink}}


Fixed support for providing beforeInsert() hook for Collections.copy().

--- src/Collections.coffee
+++ src/Collections.coffee
@@ -174,9 +174,9 @@
     insert = (srcDoc) ->
       df = Q.defer()
       if beforeInsert
-        result = beforeInsert(srcDoc)
-        return if result == false
-      dest.insert srcDoc, (err, result) -> if err then df.reject(err) else df.resolve(result)
+        srcDoc = beforeInsert(srcDoc)
+        return if srcDoc == false
+      dest.insert srcDoc, Promises.toCallback(df)
       df.promise
     # Collection2 may not allow inserting a doc into a collection with a predefined _id, so we
     # store a map of src to dest IDs. If a copied doc is removed in the destination, this will
@@ -187,8 +187,7 @@
     insertWithMap = (srcDoc) ->
       id = getDestId(srcDoc._id)
       return if dest.findOne(_id: id)
-      insert(srcDoc).then (insertId) ->
-        idMap[srcDoc._id] = insertId
+      insert(srcDoc).then (insertId) -> idMap[srcDoc._id] = insertId

     @getCursor(src).forEach (doc) ->
       insertPromises.push(insertWithMap(doc))


Add logging for debug

Add logging for debug

--- lib/arrivals.coffee
+++ lib/arrivals.coffee
@@ -17,6 +17,7 @@
   _.each res.data,(i)->
     event_time = moment(i.EVENT_TIME).unix()
     next_arr = moment(i.NEXT_ARR,'hh:mm:ss A').unix()
+    console.log("Next arrival time processed as:",next_arr)
     Arrivals.update {
       train_id: i.TRAIN_ID
       station: i.STATION


Fix a typo in a comment

Fix a typo in a comment

--- coffee/lib/router.coffee
+++ coffee/lib/router.coffee
@@ -30,7 +30,7 @@

     # Route a given URL path manually, return whether a route matched
     # This looks quite like Backbone.History::loadUrl but it
-    # accepted an absolute URL with a leading slash (e.g. /foo)
+    # accepts an absolute URL with a leading slash (e.g. /foo)
     # and passes the changeURL param to the callback function
     route: (path) =>
       #console.debug 'Router#route', path


player: ensure isNew means editable

player: ensure isNew means editable

--- coffee/src/player.coffee
+++ coffee/src/player.coffee
@@ -45,6 +45,7 @@

   editable: (editable) =>
     if editable?
+      editable = editable or @acornModel.isNew()
       @view.editable editable
     @view.editable()


set defaults if args aren't available

set defaults if args aren't available

--- coach/src/user/login-gateway.cjsx
+++ coach/src/user/login-gateway.cjsx
@@ -35,7 +35,7 @@
     isActive: ->
       Boolean( CURRENT_WINDOW and not CURRENT_WINDOW.closed )

-    openWindow: (windowImpl, options) ->
+    openWindow: (windowImpl = window, options = {type: 'login'}) ->
       width  = Math.min(1000, windowImpl.screen.width - 20)
       height = Math.min(800, windowImpl.screen.height - 30)
       options = ["toolbar=no", "location=" + (if windowImpl.opera then "no" else "yes"),


translate youtube script

translate youtube script

--- scripts/youtube.coffee
+++ scripts/youtube.coffee
@@ -2,9 +2,9 @@
 #   Messing around with the YouTube API.
 #
 # Commands:
-#   hubot youtube me <query> - Searches YouTube for the query and returns the video embed link.
+#   hubot видео мне <query> - Возвращает линк на топовый результат поиска <query> на YouTube
 module.exports = (robot) ->
-  robot.respond /(youtube|yt)( me)? (.*)/i, (msg) ->
+  robot.respond /(youtube|yt|видео)( мне)? (.*)/i, (msg) ->
     query = msg.match[3]
     robot.http("http://gdata.youtube.com/feeds/api/videos")
       .query({
@@ -18,7 +18,7 @@
         videos = videos.feed.entry

         unless videos?
-          msg.send "No video results for \"#{query}\""
+          msg.send "Не нашлось роликов по запросу \"#{query}\""
           return

         video  = msg.random videos


bug introduced by refactoring.

bug introduced by refactoring.

--- src/service/simple_dispatcher.coffee
+++ src/service/simple_dispatcher.coffee
@@ -42,7 +42,7 @@
       throw "No such resource: #{match.resource_type}"

   default_error_handler: (error, response) ->
-    response.writeHead status,
+    response.writeHead error.status,
       "Content-Type": "application/json"
     response.end JSON.stringify(error)


including pdfjs

including pdfjs

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -5,7 +5,9 @@
 #= require ic-ajax.js
 #= require_self
 #= require theoj
-
+#= require pdfjs-1.0.68-dist/build/pdf.js
+#= require pdfjs-1.0.68-dist/text_layer_builder.js
+#= require pdfjs-1.0.68-dist/ui_utils.js

 # for more details see: http://emberjs.com/guides/application/
 window.ajax = ic.ajax


bar chart: fixed initial bar position

bar chart: fixed initial bar position

--- app/assets/javascripts/d3/stacked_bar.coffee
+++ app/assets/javascripts/d3/stacked_bar.coffee
@@ -72,15 +72,13 @@
       @y = d3.scale.linear().range([0, @height]).domain([0, 7])
       @inverted_y = d3.scale.linear().range([@height, 0]).domain([0, 7])

+      # there we go
       rect = @svg.selectAll('rect.serie')
         .data(stacked_data, (s) -> s.id)
         .enter().append('svg:rect')
         .attr('class', 'serie')
         .attr("width", @x.rangeBand() * 0.5)
-        .attr('x', (d, i) =>
-          year = if i == 0 then 'start_year' else 'end_year'
-          @x(App.settings.get(year)) + 10
-          )
+        .attr('x', (s) => @x(s.x) + 10)
         .attr('y', @height)
         .style('fill', (d) => d.color)


[i18n] tg-2674 #ready-for-test

[i18n] tg-2674 #ready-for-test

--- app/coffee/modules/backlog/lightboxes.coffee
+++ app/coffee/modules/backlog/lightboxes.coffee
@@ -43,8 +43,10 @@

         submit = debounce 2000, (event) =>
             event.preventDefault()
-
             target = angular.element(event.currentTarget)
+            prettyDate = $translate.instant("COMMON.PICKERDATE.FORMAT")
+
+            submitButton = $el.find(".submit-button")
             form = $el.find("form").checksley()

             if not form.validate()
@@ -57,13 +59,15 @@
             broadcastEvent = null

             if createSprint
-                newSprint.estimated_start = moment(newSprint.estimated_start).format("YYYY-MM-DD")
-                newSprint.estimated_finish = moment(newSprint.estimated_finish).format("YYYY-MM-DD")
+                newSprint.estimated_start = moment(newSprint.estimated_start, prettyDate).format("YYYY-MM-DD")
+                newSprint.estimated_finish = moment(newSprint.estimated_finish,prettyDate).format("YYYY-MM-DD")
                 promise = $repo.create("milestones", newSprint)
                 broadcastEvent = "sprintform:create:success"
             else
-                newSprint.setAttr("estimated_start", moment(newSprint.estimated_start).format("YYYY-MM-DD"))
-                newSprint.setAttr("estimated_finish", moment(newSprint.estimated_finish).format("YYYY-MM-DD"))
+                newSprint.setAttr("estimated_start",
+                                  moment(newSprint.estimated_start, prettyDate).format("YYYY-MM-DD"))
+                newSprint.setAttr("estimated_finish",
+                                  moment(newSprint.estimated_finish, prettyDate).format("YYYY-MM-DD"))
                 promise = $repo.save(newSprint)
                 broadcastEvent = "sprintform:edit:success"

@@ -171,8 +175,6 @@
                 $el.find(".last-sprint-name").addClass("disappear")
             else
                 $el.find(".last-sprint-name").removeClass("disappear")
-
-        submitButton = $el.find(".submit-button")

         $el.on "submit", "form", submit


update shiptag to 17 summer event

update shiptag to 17 summer event


fix simple-blocks deps

fix simple-blocks deps

--- app/templates/app/scripts/coffee/main.coffee
+++ app/templates/app/scripts/coffee/main.coffee
@@ -17,6 +17,7 @@
     "sp-utils-bootstrapmodal":"#{VENDOR_PATH}/sp-utils-bootstrapmodal/build/BootstrapModal"
     "sp-utils-paginatecollection":"#{VENDOR_PATH}/sp-utils-paginatecollection/build/PaginateCollection"
     "cookies": "#{VENDOR_PATH}/cookies-js/src/cookies"
+    "simple-blocks": "#{VENDOR_PATH}/simple-blocks/dist/simpleblocks"
     #bootstrap see bootstrap.coffee file to include all deps
     'bootstrap/affix':"#{VENDOR_PATH}/bootstrap-sass-official/assets/javascripts/bootstrap/affix"
     'bootstrap/alert':"#{VENDOR_PATH}/bootstrap-sass-official/assets/javascripts/bootstrap/alert"


Redoing Rewriter#add_implicit_parentheses to be more robust.

--- rewriter.coffee
+++ rewriter.coffee
@@ -109,38 +109,36 @@
   # deal with them.
   add_implicit_parentheses: ->
     stack: [0]
-    calls:  0
-    parens: 0
-    start_parens: 0
+    close_calls: (i) =>
+      for tmp in [0...stack[stack.length - 1]]
+        @tokens.splice(i, 0, ['CALL_END', ')', @tokens[i][2]])
+      size: stack[stack.length - 1] + 1
+      stack[stack.length - 1]: 0
+      size
     @scan_tokens (prev, token, post, i) =>
       tag: token[0]
-      switch tag
-        when 'CALL_START' then calls: + 1
-        when 'CALL_END'   then calls: - 1
-        when '('          then parens: + 1
-        when ')'          then parens: - 1
-        when 'INDENT'     then stack.push 0
-        when 'OUTDENT'
-          last: stack.pop()
-          stack[stack.length - 1]: + last
+      stack[stack.length - 2]: + stack.pop() if tag is 'OUTDENT'
       open: stack[stack.length - 1] > 0
-      if !post? or (start_parens > parens) or (start_parens is parens and include IMPLICIT_END, tag)
-        return 1 if tag is 'INDENT' and prev and include IMPLICIT_BLOCK, prev[0]
-        return 1 if tag is 'OUTDENT' and token.generated
-        if open or tag is 'INDENT'
-          idx: if tag is 'OUTDENT' then i + 1 else i
-          stack_pointer: if tag is 'INDENT' then 2 else 1
-          for tmp in [0...stack[stack.length - stack_pointer]]
-            @tokens.splice(idx, 0, ['CALL_END', ')', token[2]])
-          size: stack[stack.length - stack_pointer] + 1
-          stack[stack.length - stack_pointer]: 0
+      if prev and include(IMPLICIT_FUNC, prev[0]) and include(IMPLICIT_CALL, tag)
+        @tokens.splice i, 0, ['CALL_START', '(', token[2]]
+        stack[stack.length - 1]: + 1
+        stack.push 0 if include(EXPRESSION_START, tag)
+        return 2
+      if include(EXPRESSION_START, tag)
+        if tag is 'INDENT' and !token.generated and open and not (prev and include(IMPLICIT_BLOCK, prev[0]))
+          size: close_calls(i)
+          stack.push 0
           return size
-      return 1 unless prev and include(IMPLICIT_FUNC, prev[0]) and include(IMPLICIT_CALL, tag)
-      calls: 0
-      start_parens: if tag is '(' then parens - 1 else parens
-      @tokens.splice i, 0, ['CALL_START', '(', token[2]]
-      stack[stack.length - 1]: + 1
-      return 2
+        stack.push 0
+        return 1
+      if open and !token.generated and (!post or include(IMPLICIT_END, tag))
+        size: close_calls(i)
+        stack.pop() if tag isnt 'OUTDENT' and include EXPRESSION_END, tag
+        return size
+      if tag isnt 'OUTDENT' and include EXPRESSION_END, tag
+        stack[stack.length - 2]: + stack.pop()
+        return 1
+      return 1

   # Because our grammar is LALR(1), it can't handle some single-line
   # expressions that lack ending delimiters. The **Rewriter** adds the implicit
@@ -152,7 +150,9 @@
         post[0] isnt 'INDENT' and
         not (token[0] is 'ELSE' and post[0] is 'IF')
       starter: token[0]
-      @tokens.splice i + 1, 0, ['INDENT', 2, token[2]]
+      indent: ['INDENT', 2, token[2]]
+      indent.generated: true
+      @tokens.splice i + 1, 0, indent
       idx: i + 1
       parens: 0
       while true
@@ -280,7 +280,7 @@
 IMPLICIT_BLOCK: ['->', '=>', '{', '[', ',']

 # Tokens that always mark the end of an implicit call for single-liners.
-IMPLICIT_END:   ['IF', 'UNLESS', 'FOR', 'WHILE', 'TERMINATOR', 'INDENT', 'OUTDENT']
+IMPLICIT_END:   ['IF', 'UNLESS', 'FOR', 'WHILE', 'TERMINATOR', 'INDENT'].concat EXPRESSION_END

 # Single-line flavors of block expressions that have unclosed endings.
 # The grammar can't disambiguate them, so we insert the implicit indentation.


merging/improving @rolftimmermans fix/tests for #2007

--- rewriter.coffee
+++ rewriter.coffee
@@ -167,7 +167,7 @@

     @scanTokens (token, i, tokens) ->
       tag     = token[0]
-      noCall  = yes if tag in ['CLASS', 'IF', 'FOR']
+      noCall  = yes if tag in ['CLASS', 'IF', 'FOR', 'WHILE']
       [prev, current, next] = tokens[i - 1 .. i + 1]
       callObject  = not noCall and tag is 'INDENT' and
                     next and next.generated and next[0] is '{' and


Added UpToDate autocompletion engine

Added UpToDate autocompletion engine

--- background_scripts/completion_engines.coffee
+++ background_scripts/completion_engines.coffee
@@ -162,6 +162,17 @@

   parse: (xhr) ->
     suggestion.value for suggestion in JSON.parse(xhr.responseText).data.items
+
+class UpToDate extends BaseEngine
+  constructor: ->
+    super
+      engineUrl: "https://www.uptodate.com/services/app/contents/search/autocomplete/json?term=%s&limit=10"
+      regexps: "^https?://www\\.uptodate\\.com/"
+      example:
+        searchUrl: "https://www.uptodate.com/contents/search?search=%s&searchType=PLAIN_TEXT&source=USER_INPUT&searchControl=TOP_PULLDOWN&autoComplete=false"
+        keyword: "upto"
+
+  parse: (xhr) -> JSON.parse(xhr.responseText).data.searchTerms

 # A dummy search engine which is guaranteed to match any search URL, but never produces completions.  This
 # allows the rest of the logic to be written knowing that there will always be a completion engine match.
@@ -183,6 +194,7 @@
   AmazonJapan
   Webster
   Qwant
+  UpToDate
   DummyCompletionEngine
 ]


components-gtk: ScalePixbuf: don't try/catch when sending data

--- components-gtk/ScalePixbuf.coffee
+++ components-gtk/ScalePixbuf.coffee
@@ -11,9 +11,11 @@
   c.inPorts.add 'width',
     datatype: 'int'
     description: 'Width to scale to'
+    required: yes
   c.inPorts.add 'height',
     datatype: 'int'
     description: 'Height to scale to'
+    required: yes

   c.outPorts.add 'pixbuf',
     datatype: 'object'
@@ -29,6 +31,8 @@
   , (pixbuf, groups, out) ->
     try
       pix = pixbuf.scale_simple c.params.width, c.params.height, GdkPixbuf.InterpType.BILINEAR
-      out.send pix
     catch e
       c.error e, groups
+      return
+    out.send pix
+    return


Update LiveScript executable

Update LiveScript executable

--- lib/grammars.coffee
+++ lib/grammars.coffee
@@ -142,10 +142,10 @@

   LiveScript:
     "Selection Based":
-      command: "livescript"
-      args: (context)  -> ['-e', context.getCode()]
-    "File Based":
-      command: "livescript"
+      command: "lsc"
+      args: (context)  -> ['-e', context.getCode()]
+    "File Based":
+      command: "lsc"
       args: (context) -> [context.filepath]

   Lua:


Removing duplication on class naming

Removing duplication on class naming

--- mousetrap_wrapper.coffee
+++ mousetrap_wrapper.coffee
@@ -1,6 +1,6 @@
 window.MousetrapWrapper = {}

-MousetrapWrapper.Base = class Base
+class MousetrapWrapper.Base
   constructor: (@lib=Mousetrap) ->

   events: -> throw new Error 'Events method not implemented'
@@ -33,5 +33,3 @@

   unbindCallbackFor: (event) ->
     @lib.unbind event[0]
-
-class asd extends Base


Default to master branch

Default to master branch

--- components/Router.coffee
+++ components/Router.coffee
@@ -54,6 +54,7 @@
       routeData.route = 'github'
       [owner, repo] = urlParts.splice 0, 2
       routeData.repo = "#{owner}/#{repo}"
+      routeData.branch = 'master'
       return routeData unless urlParts.length
       if urlParts[0] is 'tree'
         # Opening a particular branch


Update the breadcrumb directive to use the new router and use the actual design.

--- src/FileManager/BreadcrumbDirective.coffee
+++ src/FileManager/BreadcrumbDirective.coffee
@@ -1,30 +1,34 @@
 angular.module('fileManager')
-.directive('breadcrumb', ($location)->
+.directive('breadcrumb', ($stateParams)->
   return {
     restrict: 'E'
-    scope: true
-    template: '
' +
-                '
' +
-                  '{{piece.value}}' +
-                  '{{piece.value}}' +
-                '
' +
-              '
'
+    template: """
+

+

+
+
{{ piece.value }}

+

+
+

+              """
+
     link: (scope, element, attrs) ->
-        #scope.$on('$routeChangeSuccess', ->
-        path = $location.path()
-
-        breadcrumb = path.split('/').splice(2)
+      scope.$watch($stateParams, ->
+        path = "/#{$stateParams.path}"
+        path = path.split('/')

         result = []
         link   = ''
-        for piece in breadcrumb
-          link += '/' + piece
-          add=
-            link: link.replace('#', '%23')
-            value: piece
-          result.push(add)
+        for piece in path
+          if piece isnt ''
+            link += '/' + piece
+            add=
+              path: link.replace('#', '%23')
+              value: piece
+            result.push(add)

         scope.breadcrumb = result
-        #)
+      )
+
   }
-)+)


Fix upgrade auth detection

Fix upgrade auth detection

--- extension/chrome/js/src/content.coffee
+++ extension/chrome/js/src/content.coffee
@@ -96,16 +96,20 @@
 		@isAuthorisationErrorPage: (contents) ->
 			/require.*?authorization/i.test contents

-		isAuthorised: (resp) ->
+		checkAuthorised: (resp) ->
+			url = @getServiceURL()
+			log 'checking if the user authorised', url
 			$.ajax
-				url: 			@getServiceURL()
+				url: 			url
 				dataType: 		'json'
 				data:			action: 'status'
 				success:		(data, textStatus, jqXHR) =>
+									log '...yes'
 									resp true
 									return
 				error:			(jqXHR, textStatus, errorThrown) =>
-									resp M.isAuthorisationErrorPage jqXHR.responseText
+									isError = M.isAuthorisationErrorPage jqXHR.responseText
+									resp !isError
 									return

 		firstInstall: (version) ->
@@ -115,8 +119,8 @@

 		upgradeInstall: (oldVersion, newVersion) ->
 			log 'upgrade from', oldVersion, newVersion
-			@isAuthorised (yesno) =>
-				@gettingStarted action: 'setupNoSchedule' unless yesno
+			@checkAuthorised (authorised) =>
+				@gettingStarted action: 'setupNoSchedule' unless authorised
 				return
 			return


Tweak string-to-hue

Tweak string-to-hue

--- app/components/classifications-ribbon.cjsx
+++ app/components/classifications-ribbon.cjsx
@@ -12,11 +12,12 @@

   getProjectColor: (name) ->
     characters = name.split ''
-    code = [0, characters...].reduce (code, character) ->
-      return code + character.charCodeAt 0
-    # Square the number so that e.g. "a" and "b" aren't so close.
-    hue = Math.pow(code, 2) % 360
-    "hsl(#{hue}, 100%, 50%)"
+    hue = [0, characters...].reduce (code, character) ->
+      # Square the number so that e.g. "a" and "b" aren't so close.
+      return code + Math.pow character.charCodeAt(0), 2
+    saturation = 50 + (hue % 50)
+    hue %= 360
+    "hsl(#{hue}, #{saturation}%, 50%)"

   render: ->
     if @props.projects.length is 0


Better regex to replace link tags

Better regex to replace link tags

--- models/block.coffee
+++ models/block.coffee
@@ -56,8 +56,7 @@
     _.unescape title

   getStrippedHTML: ->
-    # fake link formatting
-    output = @get('content_html')?.replace('<a', '<strong').replace('</a', '</strong')
+    @get('content_html')?.replace(/(<\s*\/?\s*)a(\s*([^>]*)?\s*>)/gi, '$1strong$2')

   contentOrDescription: ->
     @get('content') || @get('description')


:bug: fix

:bug: fix

--- lib/operator.coffee
+++ lib/operator.coffee
@@ -65,7 +65,7 @@
     return if @isMode('visual')
     return unless @flashTarget
     return unless settings.get('flashOnOperate')
-    return @getName() in settings.get('flashOnOperateBlacklist')
+    return if @getName() in settings.get('flashOnOperateBlacklist')

     highlightRanges @editor, ranges,
       class: 'vim-mode-plus-flash'


minor cleanup

minor cleanup

--- lib/operator.coffee
+++ lib/operator.coffee
@@ -263,16 +263,13 @@
         @occurrenceSelected = true
         @mutationManager.setCheckpoint('did-select-occurrence')

-    if haveSomeNonEmptySelection(@editor) or @target.name is "Empty"
+    if @targetSelected = haveSomeNonEmptySelection(@editor) or @target.name is "Empty"
       @emitDidSelectTarget()
       @flashChangeIfNecessary()
       @trackChangeIfNecessary()
-      @targetSelected = true
-      true
     else
       @emitDidFailSelectTarget()
-      @targetSelected = false
-      false
+    return @targetSelected

   restoreCursorPositionsIfNecessary: ->
     return unless @restorePositions


tidied up soccer

tidied up soccer

--- src/SoccerState.coffee
+++ src/SoccerState.coffee
@@ -3,8 +3,9 @@
     @game.controller.init()
     @createdAt = Date.now()
     # to prevent most redundant ball spawns
-    @ballSpawnDelaytimer = @game.generator.pick([0, 500, 1000, 1500, 2000, 2500])
+    @ballSpawnDelaytimer = @game.generator.pick([0, 100, 200, 300, 400, 500])
     @ballSpawned = false
+    @theBallId = null

   create: ->
     pitch = @game.add.sprite(@game.world.centerX, @game.world.centerY, 'soccer')
@@ -45,13 +46,18 @@
     @text.setText(@game.controller.buffer)

     # see who is dribbling the ball
-    balls = @game.entityManager.getEntitiesOfType("Ball")
-    return if balls.length == 0
+    ball = @game.entityManager.getEntitiesOfType("Ball")[0]
+    return if !ball

-    possessorId = balls[0].possessorId
+    # if a new ball has appeared on the scene we'd better clear the possessors
+    if @theBallId != ball.id
+      @possessors = []
+    @theBallId = ball.id
+
+    possessorId = ball.possessorId

     if Date.now() - @createdAt > 3000
-      # give people a change to read the intro
+      # give people a chance to read the intro
       avatar = @game.entityManager.entities[possessorId]
       if avatar
         @status.setText("#{avatar.skin} has the ball!")


Update the public queries list when queries are edited and saved

--- src/coffee/cilantro/ui/workflows/workspace.coffee
+++ src/coffee/cilantro/ui/workflows/workspace.coffee
@@ -43,6 +43,16 @@
                 view: @data.view

             if c.isSupported('2.2.0')
+                # When the queries are synced we need to manually update the
+                # public queries collection so that any changes to public
+                # queries are reflected there. Right now, this is done lazily
+                # rather than checking if the changed model is public or had
+                # its publicity changed. If this becomes too slow we can
+                # perform these checks but for now this is snappy enough.
+                @data.queries.on 'sync', (model, resp, options) =>
+                    @public_queries.currentView.collection.fetch()
+                    @public_queries.currentView.collection.reset()
+
                 @public_queries.show new @regionViews.queries
                     queryModalRegion: @queryModal
                     collection: @data.public_queries


fixed a small bug in generating stylesheet from theme

--- app/front/js/services/theme_service.coffee
+++ app/front/js/services/theme_service.coffee
@@ -101,7 +101,7 @@
         italic    = @is_font_style('italic', rule)
         underline = @is_font_style('underline', rule)
         if rule.scope
-          rules = rule.scope.split(',').map (r) -> r.trim().split(' ').map((x) -> ".#{x}").join(' ')
+          rules = rule.scope.split(',').map (r) -> r.trim().split(/\s+/).map((x) -> ".#{x}").join(' ')
           rules.each (r) ->
             styles += "#{r}{"
             styles += "color:#{fg_color};" if fg_color


Pass external site url into client even for existing factlinks to avoid race condition

--- app/js/jail_iframe/initializers/xdm.coffee
+++ app/js/jail_iframe/initializers/xdm.coffee
@@ -11,6 +11,6 @@

 FactlinkJailRoot.openFactlinkModal = (id) ->
   FactlinkJailRoot.openModalOverlay()
-  FactlinkJailRoot.factlinkCoreEnvoy 'showFactlink', id
+  FactlinkJailRoot.factlinkCoreEnvoy 'showFactlink', id, FactlinkJailRoot.siteUrl()

 FactlinkJailRoot.$sidebarFrame = $(iframe)


User info update

User info update

--- lib/restivus.coffee
+++ lib/restivus.coffee
@@ -205,9 +205,28 @@
     put: (collection) ->
       put:
         action: ->
-          entityIsUpdated = collection.update @urlParams.id, $set: profile: @bodyParams
+          rules =
+            sex: Match.Optional(Match.OneOf('male', 'female'))
+            portrait: Match.Optional(String)
+            username: Match.Optional(String)
+          check @bodyParams, rules
+          if @bodyParams.username
+            user = collection.findOne username: @bodyParams.username
+            if user
+              ret =
+                statusCode: 404
+                body:
+                  status: "fail"
+                  code: 1002
+                  message: "Username already exists"
+              return ret
+          $set = {}
+          $set['username'] = @bodyParams.username if  @bodyParams.username
+          $set['profile.sex'] = @bodyParams.sex if  @bodyParams.sex
+          $set['profile.portrait'] = @bodyParams.portrait if  @bodyParams.portrait
+          entityIsUpdated = collection.update @urlParams.id, $set: $set
           if entityIsUpdated
-            entity = collection.findOne @urlParams.id, fields: profile: 1
+            entity = collection.findOne @urlParams.id, fields: profile: 1, username:1
             {status: "success", data: entity}
           else
             statusCode: 404


change config order to show important provider first

--- lib/settings.coffee
+++ lib/settings.coffee
@@ -100,17 +100,11 @@

   # Per providers settings
   # -------------------------
-  AtomScan: newProviderConfig(
-    caseSensitivityForSearchTerm:
-      default: 'smartcase'
-      enum: ['smartcase', 'sensitive', 'insensitive']
-    searchWholeWord: false
-  )
+  GitDiffAll: newProviderConfig()
+  Fold: newProviderConfig()
+  Symbols: newProviderConfig()
+  Linter: newProviderConfig()
   Bookmarks: newProviderConfig()
-  Fold: newProviderConfig()
-  GitDiff: newProviderConfig()
-  GitDiffAll: newProviderConfig()
-  Lines: newProviderConfig()
   Scan: newProviderConfig(
     searchWholeWord:
       default: false
@@ -125,8 +119,6 @@
       Search term is first word of query, is used as search term
       """
   )
-
-  Linter: newProviderConfig()
   Search: newProviderConfig(
     caseSensitivityForSearchTerm:
       default: 'smartcase'
@@ -153,4 +145,11 @@
         Case sensitive/word only: `ag --nocolor --column -s -w PATTERN`<br>
       """
   )
-  Symbols: newProviderConfig()
+  AtomScan: newProviderConfig(
+    caseSensitivityForSearchTerm:
+      default: 'smartcase'
+      enum: ['smartcase', 'sensitive', 'insensitive']
+    searchWholeWord: false
+  )
+  Lines: newProviderConfig()
+  GitDiff: newProviderConfig()


Fixups

Fixups

--- src/application.coffee
+++ src/application.coffee
@@ -413,6 +413,9 @@
 	updateStrategies[strategy](options)

 getRemoteApps = (apiKey) ->
+	appIds = _.map(config.multivisor.apps, (app) -> app.appId)
+	if appIds.length == 1
+		appIds = appIds[0]
 	cachedResinApi.get
 		resource: 'application'
 		options:
@@ -423,13 +426,13 @@
 			]
 			filter:
 				commit: $ne: null
-				id: _.map(config.multivisor.apps, (app) -> app.appId)
+				id: appIds
 		customOptions:
 			apikey: apiKey

 getEnvAndFormatRemoteApps = (deviceIds, remoteApps, uuids, apiKey) ->
 	Promise.map remoteApps, (app) ->
-		getEnvironment(app.id, deviceId[app.id], apiKey)
+		getEnvironment(app.id, deviceIds[app.id], apiKey)
 		.then (environment) ->
 			app.environment_variable = environment
 			utils.extendEnvVars(app.environment_variable, uuids[app.id])
@@ -581,7 +584,8 @@
 			console.log('Scheduling another update attempt due to failure: ', delayTime, err)
 			setTimeout(update, delayTime, force)
 		.finally ->
-			device.updateState(app.appId, status: 'Idle')
+			_.map config.multivisor.apps, (app) ->
+				device.updateState(app.appId, status: 'Idle')
 			if updateStatus.state is UPDATE_REQUIRED
 				# If an update is required then schedule it
 				setTimeout(update, 1, updateStatus.forceNext)


use channel prefix for differentiating with app logs

--- src/application.coffee
+++ src/application.coffee
@@ -28,11 +28,11 @@

 	knex('config').select('value').where(key: 'uuid').then ([ uuid ]) ->
 		uuid = uuid.value
-		channel = "device-#{uuid}-logs"
+		channel = "device-#{uuid}-"

 		# Redefine original function
-		publish = (message, isMeta=false) ->
-			pubnub.publish({ channel, message, isMeta })
+		publish = (message, channelPrefix="logs") ->
+			pubnub.publish({ channel + channelPrefix, message })

 		# Replay queue now that we have initialised the publish function
 		publish(args...) for args in publishQueue
@@ -40,7 +40,7 @@
 	return -> publishQueue.push(arguments)

 exports.logSupervisorEvent = logSupervisorEvent = (message) ->
-	publish(message, true)
+	publish(message, "meta-logs")

 exports.kill = kill = (app) ->
 	logSupervisorEvent( 'Killing application ' + app.imageId )


Temporary workaround for #1842.

Temporary workaround for #1842.

--- app/views/play/level/tome/SpellView.coffee
+++ app/views/play/level/tome/SpellView.coffee
@@ -257,6 +257,7 @@
     # TODO: Lock default indent for an empty line?
     return unless LevelOptions[@options.level.get('slug')]?.lockDefaultCode or CampaignOptions?.getOption?(@options?.level?.get?('slug'), 'lockDefaultCode')
     return unless @spell.source is @spell.originalSource or force
+    return if @spell.language in ['javascript', 'lua', 'clojure', 'io']  # Only works for languages without closing delimeters on blocks currently

     console.info 'Locking down default code.'


Initial work on fixing nesting

Initial work on fixing nesting

--- lib/tin-foil.coffee
+++ lib/tin-foil.coffee
@@ -1,8 +1,8 @@
 TinFoilCollection = require './tin-foil-collection'
 TinFoilMap = require './tin-foil-map'

-keywords = ['extend', 'mixin', 'set', 'get', 'prop', 'hasDefinition', 'add', 'compile', '_mapDefinitionAliases', '_mapDefinitionAlias',
-            '_mapNestedAliases', '_mapTinFoilCollectionAliases', '_mapTinFoilMapAliases']
+keywords = ['extend', 'mixin', 'set', 'get', 'prop', 'hasDefinition', 'add', 'compile', '_mapDefinitionAliases', '_mapNormalAlias',
+            '_mapNestedAliases', '_mapTinFoilCollectionAlias', '_mapTinFoilMapAlias']

 class TinFoil

@@ -59,7 +59,6 @@
     # Special types
     if type.isTinFoil
       type = type.extend()
-#      defaultValue = new type

     if type is TinFoilCollection or type is TinFoilMap
       defaultValue = new type
@@ -106,55 +105,52 @@
     aliases = definition.aliases

     if type?.isTinFoil
-      @_mapTinFoilAliases type, prefix
+      method = @_mapTinFoilAlias

     else if type == TinFoilCollection
-      @_mapTinFoilCollectionAliases definition, prefix
+      method = @_mapTinFoilCollectionAlias

     else if type == TinFoilMap
-      @_mapTinFoilMapAliases definition, prefix
+      method = @_mapTinFoilMapAlias

     else
-      @_mapDefinitionAlias name, "#{prefix}#{alias}" for alias in aliases
+      method = @_mapNormalAlias

-  @_mapDefinitionAlias: (name, alias) ->
+    method.call this, definition, "#{prefix}#{alias}" for alias in aliases
+    this
+
+  @_mapNormalAlias: (definition, alias) ->
     # TODO: Check alias name against keywords
     # TODO: Should we allow overriding aliases?
     @[alias] = (val) ->
-      definition = @definition name
-      throw Error "Definition [#{name}] not found" unless definition
-      @define name,
-        type: definition.type
+      def = @definition definition.name
+      throw Error "Definition [#{name}] not found" unless def
+      @define definition.name,
+        type: def.type
         defaultValue: val
-        aliases: definition.aliases.slice(0)
+        aliases: def.aliases.slice(0)
         false
       this

     this

-  @_mapTinFoilAliases: (type, prefix = '') ->
+  @_mapTinFoilAlias: (definition, alias) ->
     for own name, definition of type.definitions
       @_mapDefinitionAliases name, "#{prefix}#{name}"

     this

-  @_mapTinFoilCollectionAliases: (definition, prefix = '') ->
-    collection = definition.defaultValue
-
-    for alias in definition.aliases
-      @["#{prefix}#{alias}"] = (val) =>
-        collection.add val
-        this
+  @_mapTinFoilCollectionAlias: (definition, alias) ->
+    @[alias] = (val) =>
+      definition.defaultValue.add val
+      this

     this

-  @_mapTinFoilMapAliases: (definition, prefix = '') ->
-    map = definition.defaultValue
-
-    for alias in definition.aliases
-      @["#{prefix}#{alias}"] = (key, value) =>
-        map.add key, value
-        this
+  @_mapTinFoilMapAlias: (definition, alias) ->
+    @[alias] = (key, value) =>
+      definition.defaultValue.add key, value
+      this

     this


Closes #1064. Dot accesses force the next token to be an identifier.

--- lexer.coffee
+++ lexer.coffee
@@ -80,7 +80,8 @@
       @token 'OWN', id
       return id.length
     forcedIdentifier = colon or
-      (prev = last @tokens) and not prev.spaced and prev[0] in ['.', '?.', '@', '::']
+      (prev = last @tokens) and (prev[0] in ['.', '?.'] or
+      not prev.spaced and prev[0] in ['@', '::'])
     tag = 'IDENTIFIER'

     if id in JS_KEYWORDS or


config: update neo4j host (sjc-production)

--- config/main.sjc-production.coffee
+++ config/main.sjc-production.coffee
@@ -51,8 +51,8 @@
     enabled     : no
     port        : 1337
   neo4j         :
-    read        : "http://172.16.3.14"
-    write       : "http://172.16.3.14"
+    read        : "http://172.16.3.16"
+    write       : "http://172.16.3.16"
     port        : 7474
   mongo         : mongo
   mongoKontrol  : mongoKontrol


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/camera_fade.coffee
+++ src/camera_fade.coffee
@@ -16,7 +16,6 @@
   fadeInDefaults =
     color: 'black'
     duration: 30
-

   fadeOutDefaults =


Refactored the auth module to make it more testable

--- src/client/auth.coffee
+++ src/client/auth.coffee
@@ -1,21 +1,55 @@
 define ->

+  # Holds the state of the authentication process
   authenticating = false

   (app) ->
-    sandbox = app.sandbox
-    core    = app.core

-    initialize: ->
-      sandbox.authenticating = ->
-        authenticating
+    # Starts the login process
+    # @throws Error with invalid providerName
+    # @returns {Promise|false}
+    login = (providerName, opts, callback=->)->
+      return module.isAuthenticating() if module.isAuthenticating()

-      authComplete = ->
+      throw 'The provider name must be a String' unless _.isString(providerName)
+      providerName = providerName.toLowerCase()
+
+      authenticating = module.sandbox.data.deferred()
+      authenticating.providerName = providerName
+      authenticating.done callback if _.isFunction(callback)
+
+      auth_url = module.auth_url(app.config, providerName, opts)
+      module.authHelper(auth_url)
+
+      authenticating
+
+
+
+    # Starts the logout process
+    # @returns {Promise}
+    # @TODO Misses a `dfd.fail`
+    logout = (callback=->)->
+      module.core.setCurrentUser(false)
+      api = module.sandbox.data.api;
+      dfd = api('logout')
+      dfd.done ->
+        api.model('me').clear()
+        api.model('me').trigger('change')
+        api.model.clearAll()
+        callback() if _.isFunction(callback)
+      dfd
+
+
+
+    # Generates the callback executed on successful authentication
+    authCompleteCallbackFactory = (providerName)->
+      ()->
+        isAuthenticating = module.isAuthenticating()
+        return unless isAuthenticating && isAuthenticating.state() == 'pending'
+        providerName = isAuthenticating.providerName
+        dfd = isAuthenticating
         try
-          return unless authenticating && authenticating.state() == 'pending'
-          providerName = authenticating.providerName
-          dfd = authenticating
-          me = sandbox.data.api.model('me')
+          me = module.sandbox.data.api.model('me')
           dfd.done -> me.trigger('change')
           me.fetch(silent: true).then(dfd.resolve, dfd.reject)
         catch err
@@ -24,38 +58,32 @@
           authenticating = false

-      core.mediator.on "hull.authComplete", authComplete

-      sandbox.login = (providerName, opts, callback=->)->
+    #
+    # Module Definition
+    #

-        return if authenticating
+    sandbox: app.sandbox
+    core: app.core
+    isAuthenticating: -> authenticating
+    location: document.location
+    authHelper: (path)->
+      window.open(path, "_auth", 'location=0,status=0,width=990,height=600')
+    auth_url: (config, provider, opts)->
+      auth_params = opts || {}
+      auth_params.app_id        = config.appId
+      auth_params.callback_url  = config.callback_url || module.location.toString()
+      auth_params.auth_referer  = module.location.toString()

-        providerName = providerName && providerName.toLowerCase()
-        return unless providerName
+      "#{config.orgUrl}/auth/#{provider}?#{$.param(auth_params)}"

-        authenticating = sandbox.data.deferred()
-        authenticating.providerName = providerName
-        authenticating.done callback if _.isFunction(callback)
+    initialize: ->
+      # Are we authenticating the user ?
+      sandbox.authenticating = module.isAuthenticating

-        auth_url = "#{app.config.orgUrl}/auth/#{providerName.toLowerCase()}"
-        auth_params = opts || {}
-        auth_params.app_id        = app.config.appId
-        auth_params.callback_url  = app.config.callback_url || document.location.toString()
+      # Tell the world that the login process has ended
+      module.core.mediator.on "hull.authComplete", authCompleteCallbackFactory

-        auth_params.auth_referer  = document.location.toString()
-        auth_url += "?#{$.param(auth_params)}"
+      sandbox.login = login

-        window.open(auth_url, "_auth", 'location=0,status=0,width=990,height=600')
-
-        authenticating
-
-
-      sandbox.logout = (callback=->)->
-        core.setCurrentUser(false)
-        dfd = sandbox.data.api('logout')
-        dfd.done ->
-          sandbox.data.api.model('me').clear()
-          sandbox.data.api.model('me').trigger('change')
-          sandbox.data.api.model.clearAll()
-          callback() if _.isFunction(callback)
-        dfd
+      sandbox.logout = logout