gitlab.coffeeのtagをpushした時のURLをunderlineなしに変更

--- gitlab.coffee
+++ gitlab.coffee
@@ -228,12 +228,12 @@
           if /^0+$/.test(hook.before)
             # this is a new branch or tag.
             message.push("#{@sm.bold(hook.user_name)} pushed a new #{label} (#{@sm.bold(value)}) to #{@sm.bold(hook.repository.name)}")
-            message.push("#{@sm.underline(hook.repository.homepage + '/commit/' + hook.after)}")
+            message.push("#{hook.repository.homepage + '/commit/' + hook.after}")
           else
             if /^0+$/.test(hook.after)
               # branch or tag is deleted.
               message.push("#{@sm.bold(hook.user_name)} deleted #{label} (#{@sm.bold(value)}) to #{@sm.bold(hook.repository.name)}")
-              message.push("#{@sm.underline(hook.repository.homepage + '/commit/' + hook.before)}")
+              message.push("#{hook.repository.homepage + '/commit/' + hook.before}")
             else
               # normal push event.
               branch     = hook.ref.split("/")[2..].join("/")


Remove dispose method and disposed variable completely.

--- modules/color-picker/main/index.coffee
+++ modules/color-picker/main/index.coffee
@@ -186,7 +186,7 @@
         circleMoved(pos)

       dragObject = (elem, parent, min, max, startCallback, moveCallback, endCallback) ->
-        cursorStartPos = elementStartPos = dragging = listening = disposed = null
+        cursorStartPos = elementStartPos = dragging = listening =  null

         if min isnt null and max isnt null
           temp = min.min(max)
@@ -195,7 +195,7 @@

         dragStart = (e) ->
           e = e.event
-          if dragging or not listening or disposed then return
+          if dragging or not listening then return
           dragging = true
           if startCallback isnt null then startCallback(e, elem)
           cursorStartPos = absoluteCursorPosition(e)
@@ -206,7 +206,7 @@

         dragGo = (e) ->
           e = e.event
-          if !dragging or disposed then return
+          if !dragging then return
           newPos = absoluteCursorPosition(e)
           newPos = newPos.add(elementStartPos).subtract(cursorStartPos)
           newPos = newPos.bound(min, max)
@@ -220,26 +220,20 @@
           cancelEvent(e)

         dragStop = ->
-          if not dragging or disposed then return true
+          if not dragging then return true
           hx.select(document).off 'pointermove', 'hx.color-picker', dragGo
           hx.select(document).off 'pointerup', 'hx.color-picker', dragStopHook
           cursorStartPos = elementStartPos = null;
           if endCallback isnt null then endCallback(elem)
           dragging = false

-        dispose = ->
-          if disposed then return
-          @StopListening(true)
-          elem = parent = min = max = startCallback = moveCallback = endCallback = null
-          disposed = true
-
         StartListening = ->
-          if listening or disposed then return
+          if listening then return
           listening = true
           parent.on 'pointerdown', 'hx.color-picker', dragStart

         StopListening = ->
-          if not listening or disposed then return
+          if not listening then return
           parent.off 'pointerdown', 'hx.color-picker', dragStart
           if stopCurrentDragging and dragging then dragStop()


add download links to docs

add download links to docs

--- backbone.analytics.coffee
+++ backbone.analytics.coffee
@@ -5,6 +5,11 @@
 # agnostic. Depends on Underscore.js and Backbone.js
 #
 # Created by Andrew Terris [@aterris](http://twitter.com/aterris)
+#
+#### Downloads
+#
+# * [Development](https://raw.github.com/aterris/backbone.analytics/master/backbone.analytics.js)
+# * [Production](https://raw.github.com/aterris/backbone.analytics/master/backbone.analytics.min.js)
 #
 #### Example
 #


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/point.coffee
+++ src/point.coffee
@@ -628,6 +628,7 @@
   ###*
   Generate a random point on the unit circle.

+
   ###
   Point.random = ->


Echo the quote before quoting.

Echo the quote before quoting.

--- src/quote.coffee
+++ src/quote.coffee
@@ -164,7 +164,11 @@
       else
         ts = moment(line.timestamp).format('h:mm A D MMM YYYY')
         "[#{ts}] #{line.speaker}: #{line.text}"
-    quote = "\n" + processed.join("\n") + "\n"
+    quote = processed.join("\n")
+
+    msg.send quote
+
+    quote = "\n" + quote + "\n"

     fs.appendFile quotefilePath, quote, ->
       msg.reply "Quote added."


Range.sniff() now detects a serialised range with empty string as start property

--- src/range.coffee
+++ src/range.coffee
@@ -3,7 +3,7 @@
 Range.sniff = (r) ->
   if r.commonAncestorContainer?
     new Range.BrowserRange(r)
-  else if r.start and typeof r.start is "string"
+  else if typeof r.start is "string"
     new Range.SerializedRange(r)
   else if r.start and typeof r.start is "object"
     new Range.NormalizedRange(r)


temporarily disabling issues events

temporarily disabling issues events

--- assets/coffeescript/recent_activity.coffee
+++ assets/coffeescript/recent_activity.coffee
@@ -63,13 +63,16 @@
       releaseLink = e.payload.release.html_url
       events.push("
#{ dateStr }
new release  for #{ repoName } - #{tagName}

")

-    if e.type is "IssuesEvent"
-      repoName = e.repo.name.split('/').pop()
-      if repoName isnt "pupil-tasks" and repoName isnt "owner-tasks"
-        actionType = e.payload.action
-        issueLink = e.payload.issue.html_url
-        issueNumber = e.payload.issue.number
-        events.push("
#{ dateStr }
issue #{ actionType } at #{ repoName + '/#' + issueNumber} - #{ e.payload.issue.title.truncate(60,true) }

")
+    # if e.type is "IssuesEvent"
+    #   repo = e.repo.name
+    #   console.log e
+    #   console.log repo
+    #   if repo isnt "pupil-labs/pupil-tasks" and repo isnt "pupil-labs/owner-tasks"
+    #     actionType = e.payload.action
+    #     issueLink = e.payload.issue.html_url
+    #     issueNumber = e.payload.issue.number
+    #     repoName = repo.split('/').pop()
+    #     events.push("
#{ dateStr }
issue #{ actionType } at #{ repoName + '/#' + issueNumber} - #{ e.payload.issue.title.truncate(60,true) }

")

   eventString = events.join('')
   $("#Home-activity-list").html("#{ eventString }")


added parantheses to avoid confusion

added parantheses to avoid confusion

--- src/robot.coffee
+++ src/robot.coffee
@@ -234,7 +234,7 @@
     app.use express.bodyParser()
     app.use express.static stat if stat

-    @server = app.listen process.env.PORT || 8080 if httpd
+    @server = app.listen(process.env.PORT || 8080) if httpd
     @router = app

     herokuUrl = process.env.HEROKU_URL


Comment fix.

Comment fix.

--- src/scope.coffee
+++ src/scope.coffee
@@ -81,7 +81,7 @@
   assign: (name, value) ->
     @variables[name] = value: value, assigned: true

-  # Gets around a bug in IE where it won't enumerate of redefined prototype properties.
+  # Gets around a bug in IE where it won't enumerate over redefined prototype properties.
   allVariables: ->
     vars = [v, k] for v, k of @variables
     if v.__defineGetter__ then vars


Stub in auth support

Stub in auth support

--- src/sensu.coffee
+++ src/sensu.coffee
@@ -9,6 +9,8 @@
 #   HUBOT_SENSU_API_USERNAME - Username for the sensu api basic auth. Not used if blank/unset
 #   HUBOT_SENSU_API_PASSWORD - Password for the sensu api basic auth. Not used if blank/unset
 #   HUBOT_SENSU_API_ALLOW_INVALID_CERTS - Allow self signed and invalid certs. Default:false
+#   HUBOT_SENSU_ROLES - using the auth script, what role has access to this.
+#                       only supports one role right now.
 #
 # Commands:
 #   hubot sensu info - show sensu api info
@@ -26,13 +28,15 @@
 #   Checks endpoint not implemented (http://docs.sensuapp.org/0.12/api/checks.html) -- also note /check/request is deprecated in favor of /request
 #   Aggregates endpoint not implemented (http://docs.sensuapp.org/0.12/api/aggregates.html)
 #
-# Author:
+# Authors:
 #   Justin Lambert - jlambert121
+#   Josh Beard
 #

 config =
   sensu_api: process.env.HUBOT_SENSU_API_URL
   allow_invalid_certs: process.env.HUBOT_SENSU_API_ALLOW_INVALID_CERTS
+  sensu_roles: process.env.HUBOT_SENSU_ROLE
 moment = require('moment')

 if config.allow_invalid_certs
@@ -61,14 +65,21 @@
 #### Info methods ####
 ######################
   robot.respond /sensu help/i, (msg) ->
+    unless robot.auth.hasRole(msg.envelope.user, config.sensu_roles)
+      msg.send "Access denied."
+      return
+
     cmds = robot.helpCommands()
     cmds = (cmd for cmd in cmds when cmd.match(/(sensu)/))
     msg.send cmds.join("\n")

   robot.respond /sensu info/i, (msg) ->
-    validateVars
-    credential = createCredential()
-
+    unless robot.auth.hasRole(msg.envelope.user, config.sensu_roles)
+      msg.send "Access denied."
+      return
+    validateVars
+    credential = createCredential()
+
     req = robot.http(config.sensu_api + '/info', http_options)
     if credential
       req = req.headers(Authorization: credential)
@@ -92,6 +103,9 @@
 #### Stash methods ####
 #######################
   robot.respond /(?:sensu)? stashes/i, (msg) ->
+    unless robot.auth.hasRole(msg.envelope.user, config.sensu_roles)
+      msg.send "Access denied."
+      return
     validateVars
     credential = createCredential()
     req = robot.http(config.sensu_api + '/stashes', http_options)
@@ -115,6 +129,9 @@
         msg.send output.sort().join('\n')

   robot.respond /(?:sensu)? silence (?:http\:\/\/)?([^\s\/]*)(?:\/)?([^\s]*)?(?: for (\d+)(\w))?/i, (msg) ->
+    unless robot.auth.hasRole(msg.envelope.user, config.sensu_roles)
+      msg.send "Access denied."
+      return
     # msg.match[1] = client
     # msg.match[2] = event (optional)
     # msg.match[3] = duration (optional)
@@ -170,6 +187,9 @@
           msg.send "API returned an error for path silence/#{path}\ndata: #{JSON.stringify(data)}\nresponse:#{res.statusCode}: #{body}"

   robot.respond /(?:sensu)? remove stash (?:http\:\/\/)?(.*)/i, (msg) ->
+    unless robot.auth.hasRole(msg.envelope.user, config.sensu_roles)
+      msg.send "Access denied."
+      return
     validateVars

     stash = msg.match[1]
@@ -195,6 +215,9 @@
 #### Client methods ####
 ########################
   robot.respond /sensu clients/i, (msg) ->
+    unless robot.auth.hasRole(msg.envelope.user, config.sensu_roles)
+      msg.send "Access denied."
+      return
     validateVars
     credential = createCredential()
     req = robot.http(config.sensu_api + '/clients', http_options)
@@ -218,6 +241,9 @@
           msg.send output.sort().join('\n')

   robot.respond /sensu client (?:http\:\/\/)?(.*)( history)/i, (msg) ->
+    unless robot.auth.hasRole(msg.envelope.user, config.sensu_roles)
+      msg.send "Access denied."
+      return
     validateVars
     client = msg.match[1]

@@ -249,6 +275,9 @@

   # get client info (not history)
   robot.respond /sensu client (?:http\:\/\/)?(.*)/i, (msg) ->
+    unless robot.auth.hasRole(msg.envelope.user, config.sensu_roles)
+      msg.send "Access denied."
+      return
     validateVars
     client = msg.match[1]
     # ignore if user asks for history
@@ -274,6 +303,9 @@

   robot.respond /(?:sensu)? remove client (?:http\:\/\/)?(.*)/i, (msg) ->
+    unless robot.auth.hasRole(msg.envelope.user, config.sensu_roles)
+      msg.send "Access denied."
+      return
     validateVars
     client= msg.match[1]

@@ -297,6 +329,9 @@
 #### Event methods ####
 #######################
   robot.respond /sensu events(?: for (?:http\:\/\/)?(.*))?/i, (msg) ->
+    unless robot.auth.hasRole(msg.envelope.user, config.sensu_roles)
+      msg.send "Access denied."
+      return
     validateVars
     if msg.match[1]
       client = '/' + msg.match[1]
@@ -328,6 +363,9 @@
         msg.send output.sort().join('\n')

   robot.respond /(?:sensu)? resolve event (?:http\:\/\/)?(.*)(?:\/)(.*)/i, (msg) ->
+    unless robot.auth.hasRole(msg.envelope.user, config.sensu_roles)
+      msg.send "Access denied."
+      return
     validateVars
     client = msg.match[1]


Store real name in Hubot brain

Store real name in Hubot brain

--- src/slack.coffee
+++ src/slack.coffee
@@ -67,7 +67,7 @@
       if id is user.name then delete @robot.brain.data.users[user.id]

   userChange: (user) =>
-    newUser = {name: user.name, email_address: user.profile.email}
+    newUser = {name: user.name, real_name: user.real_name, email_address: user.profile.email}
     if user.id of @robot.brain.data.users
       for key, value of @robot.brain.data.users[user.id]
         unless key of newUser


fixed mobile click-open-safari

fixed mobile click-open-safari

--- app/assets/javascripts/general.coffee
+++ app/assets/javascripts/general.coffee
@@ -79,7 +79,6 @@

   mobileClick: (id) ->
     elem = $(id)
-    return if elem.hasClass("shown task-link")
     elem.click (e) ->
       e.preventDefault()
       window.location.href = e.currentTarget.href;


home.coffee removed

home.coffee removed

--- app/assets/javascripts/home.js.coffee
+++ app/assets/javascripts/home.js.coffee
@@ -1,3 +0,0 @@
-# Place all the behaviors and hooks related to the matching controller here.
-# All this logic will automatically be available in application.js.
-# You can use CoffeeScript in this file: http://coffeescript.org/


Remove some JS that is no longer used

Remove some JS that is no longer used

--- app/assets/javascripts/main.js.coffee
+++ app/assets/javascripts/main.js.coffee
@@ -36,12 +36,6 @@

   # Show/Hide the profile menu when hovering the account box
   $('.account-box').hover -> $(this).toggleClass('hover')
-
-  $("#projects-list .project").live 'click', (e) ->
-    if e.target.nodeName isnt "A" and e.target.nodeName isnt "INPUT"
-      location.href = $(this).attr("url")
-      e.stopPropagation()
-      false

   # Focus search field by pressing 's' key
   $(document).keypress (e) ->


Issue #2: update language snippets in the Atom plugin

--- tools/atom-plugin/language-micro/snippets/language-micro.cson
+++ tools/atom-plugin/language-micro/snippets/language-micro.cson
@@ -2,6 +2,9 @@
 	'function':
 		'prefix': 'fn'
 		'body': 'fn$1($2)$3;$4'
+	'assignment':
+		'prefix': 'le'
+		'body': 'let $1 $2;$3'
 	'condition':
 		'prefix': 'if'
 		'body': 'if $1\n\tfn()$2;\n\tfn()$3;$4'


Fix ending

Fix ending

--- app/scripts/lazerscripts/lunch.coffee
+++ app/scripts/lazerscripts/lunch.coffee
@@ -248,13 +248,10 @@
       @placeSquad Game.Scripts.LunchBossStage1
       @checkpoint @sequence(
         @setScenery 'Skyline'
-        @gainHeight(300, duration: 0)
+        @gainHeight(600, duration: 0)
         @setSpeed 150
-        @wait 4000
-        => Crafty('RiggedExplosion').trigger('BigExplosion')
-
-      )
-      @chapterTitle(4, 'Game Over')
+      )
+      @chapterTitle(4, 'Congratulations!')
     )


[fix] events queue

[fix] events queue

--- app/scripts/views/layouts/main.coffee
+++ app/scripts/views/layouts/main.coffee
@@ -39,9 +39,9 @@
     initViews: ->
       @views.location_form = new LocationFormView
         model: @models.area
+      @views.location_title = new BackgroundImageView
+        model: @models.area
       @views.location_title = new LocationTitleView
-        model: @models.area
-      @views.location_title = new BackgroundImageView
         model: @models.area

     onRender: ->


Post permalinks always include entity

Post permalinks always include entity

--- lib/assets/javascripts/helpers/routes.js.coffee
+++ lib/assets/javascripts/helpers/routes.js.coffee
@@ -26,10 +26,7 @@
         else
           params.entity
       when 'post'
-        if params.entity == TentStatus.config.meta.content.entity
-          "/posts/#{encodeURIComponent params.post_id}"
-        else
-          "/posts/#{encodeURIComponent params.entity}/#{encodeURIComponent params.post_id}"
+        "/posts/#{encodeURIComponent params.entity}/#{encodeURIComponent params.post_id}"

   fullPath: (path) ->
     (TentStatus.config.PATH_PREFIX || '').replace(/\/$/, '') + path


Upgrade cherry pick select branch

Upgrade cherry pick select branch

--- lib/views/cherry-pick-select-branch-view.coffee
+++ lib/views/cherry-pick-select-branch-view.coffee
@@ -1,4 +1,5 @@
-{$$, BufferedProcess, SelectListView} = require 'atom'
+{BufferedProcess} = require 'atom'
+{$$, SelectListView} = require 'atom-space-pen-views'

 git = require '../git'
 StatusView = require './status-view'
@@ -9,11 +10,19 @@

   initialize: (items, @currentHead) ->
     super
-    @addClass 'overlay from-top'
+    @show()
     @setItems items

-    atom.workspaceView.append this
     @focusFilterEditor()
+
+  show: ->
+    @panel ?= atom.workspace.addModalPanel(item: this)
+    @panel.show()
+
+  cancelled: -> @hide()
+
+  hide: ->
+    @panel?.hide()

   viewForItem: (item) ->
     $$ ->


Fix TerminalPane until we support for multiple vms

--- client/Workspace/panes/terminalpane.coffee
+++ client/Workspace/panes/terminalpane.coffee
@@ -8,19 +8,29 @@
     super options, data

     @createWebTermView()
-    @webterm.on "WebTermConnected", (@remote) =>
-      @emit "WebtermCreated"
-      @onWebTermConnected()

   createWebTermView: ->
-    @webterm           = new WebTermView
-      cssClass         : "webterm"
-      advancedSettings : no
-      delegate         : this
-      mode             : @getMode()

-    @webterm.connectToTerminal()
-    # WebTermView.setTerminalTimeout null, 15000, handler, handler
+    KD.singletons.vmController.fetchDefaultVm (err, vm)=>
+
+      @webterm           = new WebTermView {
+        cssClass         : "webterm"
+        advancedSettings : no
+        delegate         : this
+        mode             : @getMode()
+        vm
+      }
+
+      @addSubView @header
+      @addSubView @webterm
+
+      @webterm.connectToTerminal()
+
+      @webterm.on "WebTermConnected", (@remote) =>
+        @emit "WebtermCreated"
+        @onWebTermConnected()
+
+      # WebTermView.setTerminalTimeout null, 15000, handler, handler

   getMode: -> "create"

@@ -41,8 +51,8 @@
         @runCommand command
         @triedAgain = yes

-  pistachio: ->
-    """
-      {{> @header}}
-      {{> @webterm}}
-    """+  # pistachio: ->
+  #   """
+  #     {{> @header}}
+  #     {{> @webterm}}
+  #   """


Code refactoring

Code refactoring

--- client/ide/lib/views/idewebtermview.coffee
+++ client/ide/lib/views/idewebtermview.coffee
@@ -16,5 +16,9 @@

     super

-    kd.utils.wait 400, => @triggerFitToWindow()  if @getOption('mode') is 'shared'
+    if @getOption('mode') is 'shared'

+      # Execute it after 400ms. Because it needs to wait dom changes.
+      kd.utils.wait 400, =>
+        @triggerFitToWindow()
+


ActiveAdmin.modal_dialog fire events before_open and after_open

--- app/assets/javascripts/active_admin/lib/modal_dialog.js.coffee
+++ app/assets/javascripts/active_admin/lib/modal_dialog.js.coffee
@@ -28,8 +28,14 @@
     [wrapper, elem, opts, type, klass] = [] # unset any temporary variables

   html += "</ul></form>"
-  $(html).appendTo('body').dialog
+
+  form = $(html).appendTo('body')
+  $('body').trigger 'modal_dialog:before_open', [form]
+
+  form.dialog
     modal: true
+    open: (event, ui) ->
+      $('body').trigger 'modal_dialog:after_open', [form]
     dialogClass: 'active_admin_dialog'
     buttons:
       OK: ->


Add store and statechart to the App component

--- app/assets/javascripts/rglossa/react/components/app.jsx.coffee
+++ app/assets/javascripts/rglossa/react/components/app.jsx.coffee
@@ -1,11 +1,25 @@
+#= require ../store
+#= require ../statechart
 #= require ./top/navbar
 #= require ./centre/main_area

 ###* @jsx React.DOM ###

+models = [
+  'corpora'
+]
+
+states =
+  start: {}
+  results: {}
+
 window.App = React.createClass
+  getInitialState: ->
+    store: new Store(models, (store) => @setState(store: store))
+    statechart: new Statechart('Main', states, 'start', (sc) => @setState(statechart: sc))
+
   render: ->
     `<span>
-      <Navbar/>
-      <MainArea/>
+      <Navbar />
+      <MainArea statechart={this.state.statechart} />
     </span>`


Don't render work weeks if the project is new

--- app/assets/javascripts/views/staffplans/project_view.js.coffee
+++ app/assets/javascripts/views/staffplans/project_view.js.coffee
@@ -52,8 +52,12 @@
         id: "project_#{@model.id}"
       )
       .html( Mustache.to_html( @templates.project, @templateData() ) )
-      .find( '.months-and-weeks' )
-      .html @work_weeks.render().el
+
+    unless @model.isNew()
+      $( @el )
+        .find( '.months-and-weeks' )
+        .html @work_weeks.render().el
+
     @

   templates:


myteam/teamsettings: member cant change team logo by clicking logo

--- client/home/lib/myteam/components/hometeamsettings/view.coffee
+++ client/home/lib/myteam/components/hometeamsettings/view.coffee
@@ -9,7 +9,7 @@

   onClickLogo: (event) ->

-    @input.click()
+    @input.click()  if @props.canEdit

   render: ->


selector: don't emit an invalid CSS selector

--- components/inputs/KDInputRadioGroup.coffee
+++ components/inputs/KDInputRadioGroup.coffee
@@ -51,7 +51,7 @@
     # @getDomElement().find("input[value='#{value}']").parent().addClass('checked')
     @getDomElement().find("input[value='#{value}']").attr "checked","checked"
     @getDomElement().find(".kd-radio-holder").removeClass 'active'
-    @getDomElement().find(".kd-radio-holder.#{value}").addClass 'active'
+    @getDomElement().find(".kd-radio-holder.#{value}").addClass 'active'  if value

   getInputElements:->
     @getDomElement().find('input')


call not apply

call not apply

--- src/spine.coffee
+++ src/spine.coffee
@@ -1,7 +1,7 @@
 Events =
   bind: (ev, callback) ->
-    evs   = ev.split(" ")
-    calls = @hasOwnProperty("_callbacks") and @_callbacks or= {}
+    evs   = ev.split(' ')
+    calls = @hasOwnProperty('_callbacks') and @_callbacks or= {}

     for name in evs
       calls[name] or= []
@@ -11,7 +11,7 @@
   trigger: (args...) ->
     ev = args.shift()

-    list = @hasOwnProperty("_callbacks") and @_callbacks?[ev]
+    list = @hasOwnProperty('_callbacks') and @_callbacks?[ev]
     return false unless list

     for callback in list
@@ -41,20 +41,20 @@
 Log =
   trace: true

-  logPrefix: "(App)"
+  logPrefix: '(App)'

   log: (args...) ->
     return unless @trace
-    return if typeof console is "undefined"
+    return if typeof console is 'undefined'
     if @logPrefix then args.unshift(@logPrefix)
     console.log(args...)
     @

-moduleKeywords = ["included", "extended"]
+moduleKeywords = ['included', 'extended']

 class Module
   @include: (obj) ->
-    throw("include(obj) requires obj") unless obj
+    throw('include(obj) requires obj') unless obj
     for key, value of obj when key not in moduleKeywords
       @::[key] = value

@@ -63,7 +63,7 @@
     @

   @extend: (obj) ->
-    throw("extend(obj) requires obj") unless obj
+    throw('extend(obj) requires obj') unless obj
     for key, value of obj when key not in moduleKeywords
       @[key] = value

@@ -99,7 +99,7 @@

   @find: (id) ->
     record = @records[id]
-    throw("Unknown record") unless record
+    throw('Unknown record') unless record
     record.clone()

   @exists: (id) ->
@@ -116,7 +116,7 @@
       record.id           or= guid()
       @records[record.id] = record

-    @trigger("refresh")
+    @trigger('refresh')
     @

   @select: (callback) ->
@@ -171,23 +171,23 @@
     @find(id).destroy()

   @change: (callbackOrParams) ->
-    if typeof callbackOrParams is "function"
-      @bind("change", callbackOrParams)
+    if typeof callbackOrParams is 'function'
+      @bind('change', callbackOrParams)
     else
-      @trigger("change", callbackOrParams)
+      @trigger('change', callbackOrParams)

   @fetch: (callbackOrParams) ->
-    if typeof callbackOrParams is "function"
-      @bind("fetch", callbackOrParams)
+    if typeof callbackOrParams is 'function'
+      @bind('fetch', callbackOrParams)
     else
-      @trigger("fetch", callbackOrParams)
+      @trigger('fetch', callbackOrParams)

   @toJSON: ->
     @recordsValues()

   @fromJSON: (objects) ->
     return unless objects
-    if typeof objects is "string"
+    if typeof objects is 'string'
       objects = JSON.parse(objects)
     if isArray(objects)
       (new @(value) for value in objects)
@@ -239,12 +239,12 @@
   save: ->
     error = @validate()
     if error
-      @trigger("error", @, error)
+      @trigger('error', @, error)
       return false

-    @trigger("beforeSave", @)
+    @trigger('beforeSave', @)
     if @newRecord then @create() else @update()
-    @trigger("save", @)
+    @trigger('save', @)
     @

   updateAttribute: (name, value) ->
@@ -264,11 +264,11 @@
     @save()

   destroy: ->
-    @trigger("beforeDestroy", @)
+    @trigger('beforeDestroy', @)
     delete @constructor.records[@id]
     @destroyed = true
-    @trigger("destroy", @)
-    @trigger("change", @, "destroy")
+    @trigger('destroy', @)
+    @trigger('change', @, 'destroy')
     @unbind()
     @

@@ -301,45 +301,45 @@
   # Private

   update: ->
-    @trigger("beforeUpdate", @)
+    @trigger('beforeUpdate', @)
     records = @constructor.records
     records[@id].load @attributes()
     clone = records[@id].clone()
-    @trigger("update", clone)
-    @trigger("change", clone, "update")
+    @trigger('update', clone)
+    @trigger('change', clone, 'update')

   create: ->
-    @trigger("beforeCreate", @)
+    @trigger('beforeCreate', @)
     @id          = guid() unless @id
     @newRecord   = false
     records      = @constructor.records
     records[@id] = @dup(false)
     clone        = records[@id].clone()
-    @trigger("create", clone)
-    @trigger("change", clone, "create")
+    @trigger('create', clone)
+    @trigger('change', clone, 'create')

   bind: (events, callback) ->
     @constructor.bind events, binder = (record) =>
       if record && @eql(record)
         callback.apply(@, arguments)
-    @constructor.bind "unbind", unbinder = (record) =>
+    @constructor.bind 'unbind', unbinder = (record) =>
       if record && @eql(record)
         @constructor.unbind(events, binder)
-        @constructor.unbind("unbind", unbinder)
+        @constructor.unbind('unbind', unbinder)
     binder

   trigger: ->
     @constructor.trigger(arguments...)

   unbind: ->
-    @trigger("unbind", @)
+    @trigger('unbind', @)

 class Controller extends Module
   @include Events
   @include Log

   eventSplitter: /^(\w+)\s*(.*)$/
-  tag: "div"
+  tag: 'div'

   constructor: (options) ->
     @options = options
@@ -351,36 +351,38 @@
     @el = $(@el)

     @el.addClass(@className) if @className
+
+    @destroy -> @el.remove()

     @events = @constructor.events unless @events
     @elements = @constructor.elements unless @elements
-
+
     @delegateEvents() if @events
     @refreshElements() if @elements
+
     super

-  destroy: ->
-    @trigger "destroy"
+  destroy: (callback) ->
+    if typeof callback is 'function'
+      @bind 'destroy', callback
+    else
+      @trigger 'destroy'

   $: (selector) -> $(selector, @el)

   delegateEvents: ->
     for key, method of @events
-      unless typeof(method) is "function"
+      unless typeof(method) is 'function'
         method = @proxy(@[method])

       match      = key.match(@eventSplitter)
       eventName  = match[1]
       selector   = match[2]

-      if selector is ""
+      if selector is ''
         @el.bind(eventName, method)
-        @bind "destroy", ->
-          @el.unbind(eventName, method)
       else
         @el.delegate(selector, eventName, method)
-        @bind "destroy", ->
-          @el.undelegate(selector, eventName, method)

   refreshElements: ->
     for key, value of @elements
@@ -416,14 +418,14 @@

 $ = @jQuery or @Zepto or (element) -> element

-unless typeof Object.create is "function"
+unless typeof Object.create is 'function'
   Object.create = (o) ->
     Func = ->
     Func.prototype = o
     new Func()

 isArray = (value) ->
-  Object::toString.call(value) is "[object Array]"
+  Object::toString.call(value) is '[object Array]'

 makeArray = (args) ->
   Array.prototype.slice.call(args, 0)
@@ -440,7 +442,7 @@
 Spine = @Spine   = {}
 module?.exports  = Spine

-Spine.version    = "2.0.0"
+Spine.version    = '2.0.1'
 Spine.isArray    = isArray
 Spine.$          = $
 Spine.Events     = Events
@@ -451,7 +453,7 @@

 # Global events

-Module.extend.apply(Spine, Events)
+Module.extend.call(Spine, Events)

 # JavaScript compatability


Get subcomments to work with resizable text area

--- core/app/backbone/views/sub_comments/react_sub_comments.coffee
+++ core/app/backbone/views/sub_comments/react_sub_comments.coffee
@@ -1,17 +1,13 @@
 ReactSubCommentsAdd = React.createClass
   getInitialState: ->
-    text: ''
     phase: 'new'
-
-  updateText: (e)->
-    @setState text: e.target.value

   submit: ->
     return if @state.phase == 'submitting'
     @setState phase: 'submitting'

     model = new SubComment
-      content: $.trim(@state.text)
+      content: $.trim(@refs.text_area.state.text)
       created_by: currentUser.toJSON()

     if model.isValid()
@@ -19,7 +15,7 @@
       model.save {},
         success: =>
           @setState phase: 'new'
-          @setState text: ''
+          @refs.text_area.updateText ''
         error: =>
           @props.addToCollection.remove(model)
           @setState phase: 'new'
@@ -39,12 +35,12 @@
           'Posting...'

     R.div className: 'sub-comment-add spec-sub-comments-form',
-      R.textarea
-        className: "text_area_view",
+      ReactTextArea
         placeholder: 'Leave a reply'
         onChange: @updateText
-        ref: 'textarea'
-        value: @state.text
+        defaultValue: ''
+        ref: 'text_area'
+        onSubmit: @submit
       submit_button


indentation fix

indentation fix

--- angular-inview.coffee
+++ angular-inview.coffee
@@ -71,8 +71,8 @@
         removeWindowInViewItem item
   ]

-	# ## in-view-container directive
-	.directive 'inViewContainer', ->
+  # ## in-view-container directive
+  .directive 'inViewContainer', ->
       # Use this as an attribute or a class to mark a scrollable container holding
       # `in-view` directives as children.
       restrict: 'AC'
@@ -105,143 +105,143 @@
 # The collectin of all in-view items. Items are object with the structure:
 # ```
 # {
-# 	element: <angular.element>,
-# 	offset: <number>,
-# 	wasInView: <bool>,
-# 	callback: <funciton>
+#   element: <angular.element>,
+#   offset: <number>,
+#   wasInView: <bool>,
+#   callback: <funciton>
 # }
 # ```
 _windowInViewItems = []
 addWindowInViewItem = (item) ->
-	_windowInViewItems.push item
-	do bindWindowEvents
+  _windowInViewItems.push item
+  do bindWindowEvents
 removeWindowInViewItem = (item) ->
-	_windowInViewItems = (i for i in _windowInViewItems when i isnt item)
-	do unbindWindowEvents
+  _windowInViewItems = (i for i in _windowInViewItems when i isnt item)
+  do unbindWindowEvents

 # List of containers controllers
 _containersControllers = []
 trackInViewContainer = (controller) ->
-	_containersControllers.push controller
-	do bindWindowEvents
+  _containersControllers.push controller
+  do bindWindowEvents
 untrackInViewContainer = (container) ->
-	_containersControllers = (c for c in _containersControllers when c isnt container)
-	do unbindWindowEvents
+  _containersControllers = (c for c in _containersControllers when c isnt container)
+  do unbindWindowEvents

 # ### Events handler management
 _windowEventsHandlerBinded = no
 windowEventsHandler = (event) ->
-	c.checkInView(event) for c in _containersControllers
-	windowCheckInView(event) if _windowInViewItems.length
+  c.checkInView(event) for c in _containersControllers
+  windowCheckInView(event) if _windowInViewItems.length
 bindWindowEvents = ->
-	# The bind to window events will be added only if actually needed.
-	return if _windowEventsHandlerBinded
-	_windowEventsHandlerBinded = yes
-	angular.element(window).bind 'checkInView click ready wheel mousewheel DomMouseScroll MozMousePixelScroll resize scroll touchmove mouseup', windowEventsHandler
+  # The bind to window events will be added only if actually needed.
+  return if _windowEventsHandlerBinded
+  _windowEventsHandlerBinded = yes
+  angular.element(window).bind 'checkInView click ready wheel mousewheel DomMouseScroll MozMousePixelScroll resize scroll touchmove mouseup', windowEventsHandler
 unbindWindowEvents = ->
-	# All the window bindings will be removed if no directive requires to be checked.
-	return unless _windowEventsHandlerBinded
-	return if _windowInViewItems.length or _containersControllers.length
-	_windowEventsHandlerBinded = no
-	angular.element(window).unbind 'checkInView click ready wheel mousewheel DomMouseScroll MozMousePixelScroll resize scroll touchmove mouseup', windowEventsHandler
+  # All the window bindings will be removed if no directive requires to be checked.
+  return unless _windowEventsHandlerBinded
+  return if _windowInViewItems.length or _containersControllers.length
+  _windowEventsHandlerBinded = no
+  angular.element(window).unbind 'checkInView click ready wheel mousewheel DomMouseScroll MozMousePixelScroll resize scroll touchmove mouseup', windowEventsHandler

 # ### InView checks
 # This method will call the user defined callback with the proper parameters if neccessary.
 triggerInViewCallback = (event, item, inview, isTopVisible, isBottomVisible) ->
-	if inview
-		elOffsetTop = getBoundingClientRect(item.element[0]).top + window.pageYOffset
-		inviewpart = (isTopVisible and isBottomVisible and 'neither') or (isTopVisible and 'top') or (isBottomVisible and 'bottom') or 'both'
-		# The callback will be called only if a relevant value has changed.
-		# However, if the element changed it's position (for example if it has been
-		# pushed down by dynamically loaded content), the callback will be called anyway.
-		unless item.wasInView and item.wasInView == inviewpart and elOffsetTop == item.lastOffsetTop
-			item.lastOffsetTop = elOffsetTop
-			item.wasInView = inviewpart
-			item.callback event, yes, inviewpart
-	else if item.wasInView
-		item.wasInView = no
-		item.callback event, no
+  if inview
+    elOffsetTop = getBoundingClientRect(item.element[0]).top + window.pageYOffset
+    inviewpart = (isTopVisible and isBottomVisible and 'neither') or (isTopVisible and 'top') or (isBottomVisible and 'bottom') or 'both'
+    # The callback will be called only if a relevant value has changed.
+    # However, if the element changed it's position (for example if it has been
+    # pushed down by dynamically loaded content), the callback will be called anyway.
+    unless item.wasInView and item.wasInView == inviewpart and elOffsetTop == item.lastOffsetTop
+      item.lastOffsetTop = elOffsetTop
+      item.wasInView = inviewpart
+      item.callback event, yes, inviewpart
+  else if item.wasInView
+    item.wasInView = no
+    item.callback event, no

 # The main function to check if the given items are in view relative to the provided container.
 checkInView = (items, container, event) ->
-	# It first calculate the viewport.
-	viewport =
-		top: 0
-		bottom: getViewportHeight()
-	# Restrict viewport if a container is specified.
-	if container and container isnt window
-		bounds = getBoundingClientRect container
-		# Shortcut to all item not in view if container isn't itself.
-		if bounds.top >= viewport.bottom or bounds.bottom < viewport.top
-			triggerInViewCallback(event, item, false) for item in items
-			return
-		# Actual viewport restriction.
-		viewport.top = bounds.top if bounds.top > viewport.top
-		viewport.bottom = bounds.bottom if bounds.bottom < viewport.bottom
-	# Calculate inview status for each item.
-	for item in items
-		# Get the bounding top and bottom of the element in the viewport.
-		element = item.element[0]
-		bounds = getBoundingClientRect element
-		# Apply offset.
-		boundsTop = bounds.top + if offsetIsPercentage(item.offset) then getOffsetFromPercentage(bounds, item.offset) else parseInt(item.offset?[0] ? item.offset)
-		boundsBottom = bounds.bottom + if offsetIsPercentage(item.offset) then getOffsetFromPercentage(bounds, item.offset) else parseInt(item.offset?[1] ? item.offset)
-		# Calculate parts in view.
-		if boundsTop < viewport.bottom and boundsBottom >= viewport.top
-			triggerInViewCallback(event, item, true, boundsBottom > viewport.bottom, boundsTop < viewport.top)
-		else
-			triggerInViewCallback(event, item, false)
+  # It first calculate the viewport.
+  viewport =
+    top: 0
+    bottom: getViewportHeight()
+  # Restrict viewport if a container is specified.
+  if container and container isnt window
+    bounds = getBoundingClientRect container
+    # Shortcut to all item not in view if container isn't itself.
+    if bounds.top >= viewport.bottom or bounds.bottom < viewport.top
+      triggerInViewCallback(event, item, false) for item in items
+      return
+    # Actual viewport restriction.
+    viewport.top = bounds.top if bounds.top > viewport.top
+    viewport.bottom = bounds.bottom if bounds.bottom < viewport.bottom
+  # Calculate inview status for each item.
+  for item in items
+    # Get the bounding top and bottom of the element in the viewport.
+    element = item.element[0]
+    bounds = getBoundingClientRect element
+    # Apply offset.
+    boundsTop = bounds.top + if offsetIsPercentage(item.offset) then getOffsetFromPercentage(bounds, item.offset) else parseInt(item.offset?[0] ? item.offset)
+    boundsBottom = bounds.bottom + if offsetIsPercentage(item.offset) then getOffsetFromPercentage(bounds, item.offset) else parseInt(item.offset?[1] ? item.offset)
+    # Calculate parts in view.
+    if boundsTop < viewport.bottom and boundsBottom >= viewport.top
+      triggerInViewCallback(event, item, true, boundsBottom > viewport.bottom, boundsTop < viewport.top)
+    else
+      triggerInViewCallback(event, item, false)

 # ### Utility functions

 # Checks if the provided offset value is a percentage or not
 offsetIsPercentage = (offset) ->
-	typeof offset is 'string' and offset.slice(-1) is '%'
+  typeof offset is 'string' and offset.slice(-1) is '%'

 # Calculates the offset in pixels based on the percentage provided
 getOffsetFromPercentage = (bounds, offsetPercentage) ->
-	percentage = offsetPercentage.substring 0, offsetPercentage.length - 1
-	(bounds.bottom - bounds.top) *  (percentage / 100)
+  percentage = offsetPercentage.substring 0, offsetPercentage.length - 1
+  (bounds.bottom - bounds.top) *  (percentage / 100)

 # Returns the height of the window viewport
 getViewportHeight = ->
-	height = window.innerHeight
-	return height if height
-	mode = document.compatMode
-	if mode or not $?.support?.boxModel
-		height = if mode is 'CSS1Compat' then document.documentElement.clientHeight else document.body.clientHeight
-	height
+  height = window.innerHeight
+  return height if height
+  mode = document.compatMode
+  if mode or not $?.support?.boxModel
+    height = if mode is 'CSS1Compat' then document.documentElement.clientHeight else document.body.clientHeight
+  height

 # Polyfill for `getBoundingClientRect`
 getBoundingClientRect = (element) ->
-	return element.getBoundingClientRect() if element.getBoundingClientRect?
-	top = 0
-	el = element
-	while el
-		top += el.offsetTop
-		el = el.offsetParent
-	parent = element.parentElement
-	while parent
-		top -= parent.scrollTop if parent.scrollTop?
-		parent = parent.parentElement
-	return {
-		top: top
-		bottom: top + element.offsetHeight
-	}
+  return element.getBoundingClientRect() if element.getBoundingClientRect?
+  top = 0
+  el = element
+  while el
+    top += el.offsetTop
+    el = el.offsetParent
+  parent = element.parentElement
+  while parent
+    top -= parent.scrollTop if parent.scrollTop?
+    parent = parent.parentElement
+  return {
+    top: top
+    bottom: top + element.offsetHeight
+  }

 # Debounce a function.
 debounce = (f, t) ->
-	timer = null
-	(args...)->
-		clearTimeout timer if timer?
-		timer = setTimeout (-> f(args...)), (t ? 100)
+  timer = null
+  (args...)->
+    clearTimeout timer if timer?
+    timer = setTimeout (-> f(args...)), (t ? 100)

 # The main function to perform in-view checks on all items.
 windowCheckInView = (event) ->
-	i.customDebouncedCheck() for i in _windowInViewItems when i.customDebouncedCheck?
-	checkInView (i for i in _windowInViewItems when not i.customDebouncedCheck?), null, event
+  i.customDebouncedCheck() for i in _windowInViewItems when i.customDebouncedCheck?
+  checkInView (i for i in _windowInViewItems when not i.customDebouncedCheck?), null, event

 if typeof define is 'function' && define.amd
-	define(['angular'], angularInviewModule)
+  define(['angular'], angularInviewModule)
 else if typeof module isnt 'undefined' && module && module.exports
-	module.exports = angularInviewModule+  module.exports = angularInviewModule


text color receives CB.Color

text color receives CB.Color

--- src/view/label_view.js.coffee
+++ src/view/label_view.js.coffee
@@ -42,7 +42,10 @@
   @property "textColor",
     set: (newTextColor) ->
       @_textColor = newTextColor
-      @css("color", newTextColor)
+      if newTextColor.class == CB.Color
+        @css("color", newTextColor.toCss('rgba'))
+      else
+        @css("color", newTextColor)

   @property "textAlignment",
     set: (newValue) ->


Don't request_render() twice during plot initialization

--- bokehjs/src/coffee/common/plot.coffee
+++ bokehjs/src/coffee/common/plot.coffee
@@ -102,7 +102,6 @@
         })

       @unpause()
-      @request_render()

       logger.debug("PlotView initialized")


New wiki toc behavior

New wiki toc behavior

--- resources/assets/coffee/_classes/wiki.coffee
+++ resources/assets/coffee/_classes/wiki.coffee
@@ -112,6 +112,45 @@
     $('.js-wiki-toc').append $mainToc

+  positionTocBottom: =>
+    el = @floatToc[0]
+
+    return if el._position == 'bottom'
+
+    el._position = 'bottom'
+    el.style.position = 'absolute'
+    el.style.top = 'auto'
+    el.style.bottom = 0
+    el.style.left = 0
+    el.style.width = 'auto'
+
+
+  positionTocDefault: =>
+    el = @floatToc[0]
+
+    return if el._position == 'default'
+
+    el._position = 'default'
+    el.style.position = 'absolute'
+    el.style.top = 0
+    el.style.bottom = 'auto'
+    el.style.left = 0
+    el.style.width = 'auto'
+
+
+  positionTocFloating: (containerRect) =>
+    el = @floatToc[0]
+
+    return if el._position == 'floating'
+
+    el._position = 'floating'
+    el.style.position = 'fixed'
+    el.style.top = 0
+    el.style.bottom = 'auto'
+    el.style.left = "#{containerRect.left}px"
+    el.style.width = "#{containerRect.width}px"
+
+
   setTitle: =>
     $title = @$content.find('h1').first()

@@ -136,32 +175,21 @@
     return if !@floatToc[0]?

     # not floating
-    if target != 'wiki-toc'
-      @floatToc[0].style.position = 'absolute'
-      @floatToc[0].style.top = 0
-      @floatToc[0].style.bottom = 'auto'
-      @floatToc[0].style.left = 0
-      @floatToc[0].style.width = 'auto'
-      return
+    return @positionTocDefault() if target != 'wiki-toc'

     containerRect = @floatTocContainer[0].getBoundingClientRect()
     rect = @floatToc[0].getBoundingClientRect()

-    # reached bottom
-    if containerRect.bottom < rect.height
-      @floatToc[0].style.position = 'absolute'
-      @floatToc[0].style.top = 'auto'
-      @floatToc[0].style.bottom = 0
-      @floatToc[0].style.left = 0
-      @floatToc[0].style.width = 'auto'
-      return
-
-    # floating
-    @floatToc[0].style.position = 'fixed'
-    @floatToc[0].style.top = 0
-    @floatToc[0].style.bottom = 'auto'
-    @floatToc[0].style.left = "#{containerRect.left}px"
-    @floatToc[0].style.width = "#{containerRect.width}px"
+    if rect.height > window.innerHeight
+      # Toc longer than window, enforce default position
+      @positionTocDefault()
+    else
+      if containerRect.bottom < rect.height
+        # reached bottom
+        @positionTocBottom()
+      else
+        # floating
+        @positionTocFloating(containerRect)

   updateLocaleLinks: =>


Close the current data layer before opening another

--- src/views/service-cart.coffee
+++ src/views/service-cart.coffee
@@ -81,9 +81,9 @@
             @_selectLayer $(ev.currentTarget).data('layer')
         selectDataLayerInput: (ev) ->
             value = $(ev.currentTarget).prop('value')
+            app.commands.execute 'removeDataLayer', p13n.get 'data_layer'
             if value == p13n.get 'data_layer'
                 $(ev.currentTarget).prop 'checked', false
-                app.commands.execute 'removeDataLayer', value
             else
                 app.commands.execute 'addDataLayer', value
             p13n.toggleDataLayer value


Remove duplicate services from unit details.

--- src/views/unit-details.coffee
+++ src/views/unit-details.coffee
@@ -189,9 +189,13 @@
             data.feedback_count = @model.feedbackList.length
             data.collapsed = @collapsed || false

-            serviceIds = _.pluck data.services, 'id'
-            data.services = _.filter data.services, (s) =>
-                s.identical_to not in serviceIds
+            rx = (acc, service) =>
+                oRef = service.ontologyword_reference
+                acc[oRef] = (acc[oRef] or []).concat service
+                acc
+
+            servicesByOntologywordReference = _.reduce data.services, rx, {}
+            data.services = _.map servicesByOntologywordReference, (s) => s[0]

             data


Hide events section when no events are available.

--- src/views/unit-details.coffee
+++ src/views/unit-details.coffee
@@ -160,8 +160,11 @@

         renderEvents: (events) ->
             if events?
-                @eventsRegion.show new EventListView
-                    collection: events
+                if events.isEmpty()
+                    @$el.find('.section.events-section').hide()
+                else
+                    @eventsRegion.show new EventListView
+                        collection: events

         showMoreEvents: (event) ->
             event.preventDefault()


Create nodes instead of a document fragment

--- src/trix/views/text_view.coffee
+++ src/trix/views/text_view.coffee
@@ -10,9 +10,8 @@
     @text = @object
     {@textConfig} = @options

-  render: ->
-    @element = document.createDocumentFragment()
-
+  createNodes: ->
+    nodes = []
     @text.eachPiece (piece) =>
       return if piece.hasAttribute("blockBreak")
       [@previousPiece, @previousAttributes] = [@currentPiece, @currentAttributes]
@@ -34,15 +33,14 @@
         afterElement = @createCursorTargetForPosition(piece.position + 1)

       if parentHref
-        @element.insertBefore(beforeElement, @element.lastChild) if beforeElement?
-        @element.lastChild.appendChild(element)
-        @element.appendChild(afterElement) if afterElement?
+        nodes.splice(nodes.length - 2, beforeElement) if beforeElement?
+        nodes[nodes.length - 1].appendChild(element)
+        nodes.push(afterElement) if afterElement?
       else
-        @element.appendChild(beforeElement) if beforeElement?
-        @element.appendChild(element)
-        @element.appendChild(afterElement) if afterElement?
-
-    @element
+        nodes.push(beforeElement) if beforeElement?
+        nodes.push(element)
+        nodes.push(afterElement) if afterElement?
+    nodes

   createElementForCurrentPiece: ->
     for key, value of @currentAttributes when config = Trix.attributes[key]


Forgot to check event type as well

Forgot to check event type as well

--- ui/scripts/framework/view.coffee
+++ ui/scripts/framework/view.coffee
@@ -79,7 +79,7 @@
     delegatedEvent: (e) ->
         for query, methodName of @domEvents or {}
             [key, selector] = query.match(eventRegExp).slice(1)
-            if e.target.matches(selector)
+            if key is e.type and e.target.matches(selector)
                 @[methodName].call(this, e)


export button open the text in a new tab, fix #4

--- src/scripts/editor/EditorMenu.coffee
+++ src/scripts/editor/EditorMenu.coffee
@@ -41,4 +41,8 @@
         e.preventDefault()
         # Alternative to have nice looking json string.
         data = JSON.stringify(self.tweenTime.data, json_replacer, 2)
-        console.log data
+        a = document.createElement('a')
+        a.target = '_blank'
+        blob = new Blob([data], {"type": "text/plain;charset=utf-8"})
+        a.href = (window.URL || webkitURL).createObjectURL(blob)
+        a.click()


temporary solution for generating data for tests #66

--- src/server/models/BuildModels.coffee
+++ src/server/models/BuildModels.coffee
@@ -1,6 +1,7 @@
 models = {Game: null, Players: [], PlayerGames: []}

 db = require('./db')
+fs = require('fs')
 Sequelize = require('sequelize')

 module.exports = (paper, done)->
@@ -38,6 +39,7 @@
           models.Game = game
           models.Players = players
           models.PlayerGames = playerGames
+          fs.writeFile('paper.json', JSON.stringify(paper, null, 4), -> )
           done(null, models))
         .error((err)->done(err, null))


changing tp499 to POST from CREATE

changing tp499 to POST from CREATE

--- dkobo/koboform/static/js/xlform/XLF.view.surveyApp.coffee
+++ dkobo/koboform/static/js/xlform/XLF.view.surveyApp.coffee
@@ -160,7 +160,7 @@
     data = JSON.stringify(body: @survey.toCSV())
     $.ajax
       url: "/koboform/survey_preview/"
-      method: "CREATE"
+      method: "POST"
       data: data
       headers:
         "X-CSRFToken": $('meta[name="csrf-token"]').attr('content')


add "destroy" method

add "destroy" method

Add a public destroy method which will unbind event listeners

--- keypress.coffee
+++ keypress.coffee
@@ -98,7 +98,7 @@
             @_defaults[property] = @_defaults[property] or value

         # Attach handlers to element
-        element = element or document.body
+        @element = element or document.body

         attach_handler = (target, event, handler) ->
             if target.addEventListener
@@ -106,19 +106,34 @@
             else if target.attachEvent
                 target.attachEvent "on#{event}", handler

-        attach_handler element, "keydown", (e) =>
+            handler
+
+        @keydown_event = attach_handler @element, "keydown", (e) =>
             e = e or window.event
             @_receive_input e, true
             @_bug_catcher e
-        attach_handler element, "keyup", (e) =>
+
+        @keyup_event = attach_handler @element, "keyup", (e) =>
             e = e or window.event
             @_receive_input e, false
-        attach_handler window, "blur", =>
+
+        @blur_event = attach_handler window, "blur", =>
             # Assume all keys are released when we can't catch key events
             # This prevents alt+tab conflicts
             for key in @_keys_down
                 @_key_up key, {}
             @_keys_down = []
+
+    destroy: () ->
+        remove_handler = (target, event, handler) ->
+            if target.removeEventListener
+                target.removeEventListener event, handler
+            else if target.removeEvent
+                target.removeEvent "on#{event}", handler
+
+        remove_handler @element, "keydown", @keydown_event
+        remove_handler @element, "keyup", @keyup_event
+        remove_handler window, "blur", @blur_event

     # Helper Methods


Fixed message sending coffeescript

Fixed message sending coffeescript

--- static/coffee/services.coffee
+++ static/coffee/services.coffee
@@ -197,10 +197,11 @@
       data.append('text', text)
       data.append('urns', urns)
       data.append('contacts', contacts)
-      data.append('case', _case)
+      if _case
+        data.append('case', _case.id)
       $http.post('/message/send/', data, DEFAULT_POST_OPTS)
       .success (data) =>
-        callback(data.broadcast_id)
+        callback()

     #----------------------------------------------------------------------------
     # Processes incoming messages


stub out unwanted dependency in unit tests

--- services/clsi/test/unit/coffee/CompileManagerTests.coffee
+++ services/clsi/test/unit/coffee/CompileManagerTests.coffee
@@ -18,6 +18,7 @@
 			"child_process": @child_process = {}
 			"./CommandRunner": @CommandRunner = {}
 			"./DraftModeManager": @DraftModeManager = {}
+			"./TikzManager": @TikzManager = {}
 			"fs": @fs = {}
 		@callback = sinon.stub()

@@ -55,6 +56,7 @@
 			@OutputFileFinder.findOutputFiles = sinon.stub().callsArgWith(2, null, @output_files)
 			@OutputCacheManager.saveOutputFiles = sinon.stub().callsArgWith(2, null, @build_files)
 			@DraftModeManager.injectDraftMode = sinon.stub().callsArg(1)
+			@TikzManager.needsOutputFile = sinon.stub().returns(false)

 		describe "normally", ->
 			beforeEach ->


Comments out onExit test

Comments out onExit test

--- test/callbacks.coffee
+++ test/callbacks.coffee
@@ -60,19 +60,19 @@
           c.path is "/testPath").length, 1, "cookie must be in phantom.cookies"

-    "which, when you call exit()":
-      topic: t (ph) ->
-        countdown = null
-
-        exitPromise.then =>
-          clearTimeout countdown
-          @callback null, 'success'
-
-        ph.exit()
-
-        countdown = setTimeout =>
-          @callback 'timeout'
-        , 1000
-
-      "runs the onExit callback within 1000ms": (status) ->
-        assert.equal status, 'success'
+    # "which, when you call exit()":
+    #   topic: t (ph) ->
+    #     countdown = null
+    #
+    #     exitPromise.then =>
+    #       clearTimeout countdown
+    #       @callback null, 'success'
+    #
+    #     ph.exit()
+    #
+    #     countdown = setTimeout =>
+    #       @callback 'timeout'
+    #     , 1000
+    #
+    #   "runs the onExit callback within 1000ms": (status) ->
+    #     assert.equal status, 'success'


no longer submits response when content does not change

--- vendor/assets/javascripts/hallo_rails.js.coffee
+++ vendor/assets/javascripts/hallo_rails.js.coffee
@@ -19,11 +19,14 @@
     options = $el.data('editable-options') || context.default_options
     $.extend options, { plugins: plugins }
     $el.hallo options
+    $el.data('original-content', $el.html())
     $el.click ->
       $(this).focus()     # required for firefox as otherwise when clicking on edit overlay element doesn't focus

   $('body').on "hallodeactivated", '.editable', ->
     $el = $(this)
+
+    return if $el.data('original-content') == $el.html()

     data = {}
     method_data = {}
@@ -42,6 +45,7 @@
       beforeSend: ->
         $el.addClass('hallo_updating')
       success: ->
+        $el.data('original-content', $el.html())
         $el.removeClass('hallo_updating')
       error: (jqXHR, textStatus, errorThrown) ->
         $el.trigger('hallo:error', [jqXHR.responseText])


JVerificationToken:confirmByPin: ignore TTL for verify-account actions

--- workers/social/lib/social/models/verificationtoken.coffee
+++ workers/social/lib/social/models/verificationtoken.coffee
@@ -37,6 +37,10 @@
           ['update-email', 'verify-account', 'test-verify']
         ]

+  isAlive = (confirmation)->
+    # 20 Min. default TTL for tokens
+    Math.round((Date.now()-confirmation.createdAt)/60000) < 20
+
   @requestNewPin = (options, callback)->

     {action, email, subject, user, firstName, resendIfExists} = options
@@ -53,18 +57,15 @@

       @one {username, action}, (err, confirmation)=>

-        if confirmation
-          createdAt = Math.round((Date.now()-confirmation.createdAt)/60000)
+        if confirmation and isAlive confirmation
+          if resendIfExists
+            confirmation.sendEmail {subject, firstName, action}, callback
+          else
+            callback if err then err else new PINExistsError \
+              "PIN exists and not expired,
+              try again after 20 min for new PIN."

-          if createdAt < 20 # min
-            if resendIfExists
-              confirmation.sendEmail {subject, firstName, action}, callback
-            else
-              callback if err then err else new PINExistsError \
-                "PIN exists and not expired,
-                try again after 20 min for new PIN."
-
-            return
+          return

         # Remove all waiting pins for given action and email
         @remove {username, action}, (err, count)->
@@ -95,7 +96,11 @@
       return callback err  if err

       if confirmation
-        callback null, Math.round((Date.now()-confirmation.createdAt)/60000) < 20
+
+        # Ignore the default TTL for `verify-account` actions ~ GG
+        confirmed = isAlive confirmation
+        callback null, if action is 'verify-account' then yes else confirmed
+
         confirmation.remove()
       else
         callback null, false


Deal with the API max for now :/

Deal with the API max for now :/

--- SingularityUI/app/collections/LogLines.coffee
+++ SingularityUI/app/collections/LogLines.coffee
@@ -18,7 +18,7 @@
     serverMax: 65536

     requestLengthGrowthFactor: 1.75
-    maxRequestLength: @::baseRequestLength * 100
+    maxRequestLength: @::serverMax # @::baseRequestLength * 100

     # Request a larger chunk at start
     initialRequestLength: @::baseRequestLength * 3


DataDog: increment: add helper for metrics - WIP

--- workers/social/lib/social/models/datadog.coffee
+++ workers/social/lib/social/models/datadog.coffee
@@ -14,6 +14,7 @@
     sharedMethods      :
       static           :
         sendEvent      : (signature Object, Function)
+        increment      : (signature Object, Function)

   {api_key, app_key}   = KONFIG.datadog
   DogApi               = new dogapi {
@@ -26,6 +27,13 @@
       text             : "VM start failed for user: %nickname%"
       notify           : "@slack-alerts"
       tags             : ["user:%nickname%", "context:vms"]
+
+  Metrics     =
+    KloudInfo :
+      metric  : "kloud.info"
+      tags    : ["user:%nickname%", "context:kloud"]
+      type    : "gauge"
+

   tagReplace = (sourceTag, nickname)->

@@ -73,3 +81,32 @@
         err = new KodingError "Failed"

       callback err
+
+
+  @increment = secure (client, data, callback = ->)->
+
+    {connection:{delegate}} = client
+
+    unless delegate.type is 'registered'
+      return callback new KodingError "Not allowed"
+
+    {metric, logs, points} = data
+
+    metric = Metrics[metric]
+
+    unless metric
+      return callback new KodingError "Unknown metric"
+
+    {nickname} = delegate.profile
+    points    ?= 1
+
+    metric.points = [[Date.now()/1000, points]]
+    metric.tags   = tagReplace metric.tags, nickname
+
+    DogApi.add_metrics series:[metric], (err)->
+
+      if err?
+        console.error "[DataDog] Failed to create event:", err
+        err = new KodingError "Failed"
+
+      callback err


DataDog:increment: various fixes

DataDog:increment: various fixes

--- workers/social/lib/social/models/datadog.coffee
+++ workers/social/lib/social/models/datadog.coffee
@@ -14,12 +14,14 @@
     sharedMethods      :
       static           :
         sendEvent      : (signature Object, Function)
-        increment      : (signature Object, Function)
+        increment      : (signature String, Function)
+

   {api_key, app_key}   = KONFIG.datadog
   DogApi               = new dogapi {
     api_key, app_key
   }
+

   Events               =
     MachineStateFailed :
@@ -28,11 +30,14 @@
       notify           : "@slack-alerts"
       tags             : ["user:%nickname%", "context:vms"]

-  Metrics     =
-    KloudInfo :
-      metric  : "kloud.info"
-      tags    : ["user:%nickname%", "context:kloud"]
-      type    : "gauge"
+
+  Metrics      =
+    KloudInfo  :
+      metric   : "kloud.info.gauge"
+      tags     : ["user:%nickname%"]
+    KlientInfo :
+      metric   : "klient.info.gauge"
+      tags     : ["user:%nickname%"]

   tagReplace = (sourceTag, nickname)->
@@ -83,14 +88,12 @@
       callback err

-  @increment = secure (client, data, callback = ->)->
+  @increment = secure (client, metric, callback = ->)->

-    {connection:{delegate}} = client
+    { connection: { delegate } } = client

     unless delegate.type is 'registered'
       return callback new KodingError "Not allowed"
-
-    {metric, logs, points} = data

     metric = Metrics[metric]

@@ -98,12 +101,11 @@
       return callback new KodingError "Unknown metric"

     {nickname} = delegate.profile
-    points    ?= 1

-    metric.points = [[Date.now()/1000, points]]
+    metric.points = [[Date.now()/1000, 1]]
     metric.tags   = tagReplace metric.tags, nickname

-    DogApi.add_metrics series:[metric], (err)->
+    DogApi.add_metrics series: [metric], (err)->

       if err?
         console.error "[DataDog] Failed to create event:", err


payment/bongo: expose updateGroupCreditCard method

--- workers/social/lib/social/models/payment.coffee
+++ workers/social/lib/social/models/payment.coffee
@@ -30,6 +30,8 @@
           (signature Function)
         updateCreditCard  :
           (signature Object, Function)
+        updateGroupCreditCard:
+          (signature Object, Function)
         canChangePlan     :
           (signature Object, Function)
         logOrder          :
@@ -214,6 +216,34 @@
       url = "#{socialProxyUrl}/payments/creditcard/update"

       post url, data, callback
+
+
+  @updateGroupCreditCard = (group, data, callback) ->
+
+    return callback new KodingError 'No such group'  unless group
+
+    requiredParams = ['token', 'provider']
+
+    validateParams requiredParams, data, (err) ->
+      return callback err  if err
+
+      data.groupId = group._id
+      url = "#{socialProxyUrl}/payments/group/creditcard/update"
+
+      post url, data, callback
+
+
+  @updateGroupCreditCard$ = secure (client, data, callback) ->
+
+    slug = client?.context?.group
+
+    return callback new KodingError 'No such group'  unless slug
+
+    JGroup = require './group'
+    JGroup.one { slug }, (err, group) ->
+      return callback err  if err
+      Payment.updateGroupCreditCard group, data, callback
+

   @canChangePlan = secure (client, data, callback) ->
     requiredParams = [ 'planTitle' ]


BPE: Adding blank filters alerts the user

--- app/assets/javascripts/admin/bulk_product_update.js.coffee
+++ app/assets/javascripts/admin/bulk_product_update.js.coffee
@@ -237,6 +237,8 @@
           else if confirm("'#{filter.predicate.name}' filter already exists on column '#{filter.property.name}'. Replace it?")
             $scope.currentFilters[existingfilterIndex] = filter
             $scope.fetchProducts()
+      else
+        alert("Please ensure all filter fields are filled in before adding a filter.")

     $scope.removeFilter = (filter) ->
       index = $scope.currentFilters.indexOf(filter)


[MatrixView] Bind to render on collection resets.

--- app/assets/javascripts/cartilage/views/list_view.js.coffee
+++ app/assets/javascripts/cartilage/views/list_view.js.coffee
@@ -25,6 +25,7 @@
     @itemView = options["itemView"]

     # Bind to collection
+    @collection.bind "reset", @render # TODO Don't re-render the entire view for removals
     @collection.bind "add", @addModel
     @collection.bind "remove", @render # TODO Don't re-render the entire view for removals


fixed loading order to fix backbone.queryparams

--- core/app/backbone/factlink.backbone.coffee
+++ core/app/backbone/factlink.backbone.coffee
@@ -1,7 +1,9 @@
 #= require backbone
-#= require backbone.marionette
+#  queryparams patches router, so should be loaded before marionette,
+#     which subclasses the router
 #= require backbone.queryparams
 #= require backbone.paginator
+#= require backbone.marionette
 #= require_tree ../templates
 #= require_self


enable/disable attribution

enable/disable attribution

--- src/modules/attribution.coffee
+++ src/modules/attribution.coffee
@@ -3,7 +3,7 @@

 class Scribe.Attribution
-  constructor: (@editor, @authorId, color) ->
+  constructor: (@editor, @authorId, color, enabled = false) ->
     @editor.on(Scribe.Editor.PRE_EVENT, (eventName, delta) =>
       if eventName == Scribe.Editor.events.TEXT_CHANGE
         _.each(delta.ops, (op) =>
@@ -14,10 +14,18 @@
         )
     )
     this.addAuthor(@authorId, color)
+    this.enable() if enabled

   addAuthor: (id, color) ->
     styles = {}
-    styles[".editor .author-#{id}"] = { "background-color": "#{color}" }
+    styles[".editor.attribution .author-#{id}"] = { "background-color": "#{color}" }
     @editor.renderer.addStyles(styles)

+  enable: ->
+    @editor.root.classList.add('attribution')
+
+  disable: ->
+    @editor.root.classList.remove('attribution')
+
+
 module.exports = Scribe


Delete existing asset before uploading new one

--- build/tasks/publish-build-task.coffee
+++ build/tasks/publish-build-task.coffee
@@ -34,7 +34,9 @@
             return done(error) if error?
             getAtomDraftRelease (error, release) ->
               return done(error) if error?
-              uploadAsset(release, done)
+              deleteExistingAsset release, (error) ->
+                return done(error) if error?
+                uploadAsset(release, done)

 logError = (message, error, details) ->
   grunt.log.error(message)


Log directory

Log directory

--- build/tasks/publish-build-task.coffee
+++ build/tasks/publish-build-task.coffee
@@ -52,6 +52,7 @@
 zipApps = (buildDir, assets, callback) ->
   zip = (directory, sourceName, assetName, callback) ->
     if process.platform is 'win32'
+      console.log directory
       options = {cwd: directory, maxBuffer: Infinity}
       child_process.exec "C:/psmodules/7z.exe -r #{assetName} #{sourceName}", options, (error, stdout, stderr) ->
         if error?


Zip built app on Windows

Zip built app on Windows

--- build/tasks/publish-build-task.coffee
+++ build/tasks/publish-build-task.coffee
@@ -8,10 +8,17 @@
 request = require 'request'

 grunt = null
-assets = [
-  {assetName: 'atom-mac.zip', sourceName: 'Atom.app'}
-  {assetName: 'atom-mac-symbols.zip', sourceName: 'Atom.breakpad.syms'}
-]
+
+if process.platform is 'darwin'
+  assets = [
+    {assetName: 'atom-mac.zip', sourceName: 'Atom.app'}
+    {assetName: 'atom-mac-symbols.zip', sourceName: 'Atom.breakpad.syms'}
+  ]
+else
+  assets = [
+    {assetName: 'atom-windows.zip', sourceName: 'Atom'}
+  ]
+
 commitSha = process.env.JANKY_SHA1
 token = process.env.ATOM_ACCESS_TOKEN
 defaultHeaders =
@@ -22,20 +29,20 @@
   grunt = gruntObject

   grunt.registerTask 'publish-build', 'Publish the built app', ->
-    return unless process.platform is 'darwin'
-    return if process.env.JANKY_SHA1 and process.env.JANKY_BRANCH isnt 'master'
+    return unless process.platform is 'win32'
+    # return if process.env.JANKY_SHA1 and process.env.JANKY_BRANCH isnt 'master'

     done = @async()
     buildDir = grunt.config.get('atom.buildDir')

     zipApps buildDir, assets, (error) ->
       return done(error) if error?
-      getAtomDraftRelease (error, release) ->
-        return done(error) if error?
-        assetNames = (asset.assetName for asset in assets)
-        deleteExistingAssets release, assetNames, (error) ->
-          return done(error) if error?
-          uploadAssets(release, buildDir, assets, done)
+      # getAtomDraftRelease (error, release) ->
+      #   return done(error) if error?
+      #   assetNames = (asset.assetName for asset in assets)
+      #   deleteExistingAssets release, assetNames, (error) ->
+      #     return done(error) if error?
+      #     uploadAssets(release, buildDir, assets, done)

 logError = (message, error, details) ->
   grunt.log.error(message)
@@ -44,11 +51,18 @@

 zipApps = (buildDir, assets, callback) ->
   zip = (directory, sourceName, assetName, callback) ->
-    options = {cwd: directory, maxBuffer: Infinity}
-    child_process.exec "zip -r --symlinks #{assetName} #{sourceName}", options, (error, stdout, stderr) ->
-      if error?
-        logError("Zipping #{sourceName} failed", error, stderr)
-      callback(error)
+    if process.platform is 'win32'
+      options = {cwd: directory, maxBuffer: Infinity}
+      child_process.exec "7z -r #{assetName} #{sourceName}", options, (error, stdout, stderr) ->
+        if error?
+          logError("Zipping #{sourceName} failed", error, stderr)
+        callback(error)
+    else
+      options = {cwd: directory, maxBuffer: Infinity}
+      child_process.exec "zip -r --symlinks #{assetName} #{sourceName}", options, (error, stdout, stderr) ->
+        if error?
+          logError("Zipping #{sourceName} failed", error, stderr)
+        callback(error)

   tasks = []
   for {assetName, sourceName} in assets


Let's be a little smarter on when to refresh

--- admin/static/coffee/ui_components/list.coffee
+++ admin/static/coffee/ui_components/list.coffee
@@ -53,9 +53,12 @@
         on_add: (model, collection) =>
             # When an element is added to the collection, create a view for it
             # Make sure the model is relevant for this list before we add it
+            need_to_render = false
             if @filter model
+                need_to_render = true
                 @add_element model
-            @render()
+            if need_to_render is true
+                @render()

         on_remove: (model, collection) =>
             # Make sure the model is relevant for this list before we remove it


Adding a few possible options

Adding a few possible options

--- bin/gitwalk.coffee
+++ bin/gitwalk.coffee
@@ -14,8 +14,11 @@
 program
   .version pkginfo.version
   .usage '[options] <expr...> <proc> <cmd...>'
-  .option '-a, --async', 'Process the repositories asynchronously in parallel'
-  .option '-r, --regex', 'Use regular expressions instead of minimax matching'
+  #.option '-a, --async', 'Process the repositories asynchronously in parallel'
+  #.option '-r, --regex', 'Use regular expressions instead of minimax matching'
+  #.option '-c, --clear-cache', 'Clear the cache and exit'
+  #.option '-l, --logging-level', 'Force logging level'
+  #.option '-m, --match-only', 'Only print the list of matching repos'
   .parse process.argv

 expressions = []


Reimplement the primary routing method

Reimplement the primary routing method


Add missing ship tag for Light Scyk

Add missing ship tag for Light Scyk

--- coffeescripts/cards-fr.coffee
+++ coffeescripts/cards-fr.coffee
@@ -1605,6 +1605,7 @@
         '''Merchant One''':
             text: '''C-ROC Cruiser only.%LINEBREAK%Your upgrade bar 1 additional %CREW% upgrade icon and 1 additional %TEAM% upgrade icon and loses 1 %CARGO% upgrade icon.'''
         '''"Light Scyk" Interceptor''':
+            ship: "Intercepteur M3-A"
             name: 'Intercepteur "Scyk Léger"'
             text: '''M3-A Interceptor only.%LINEBREAK%Toutes les cartes de dégâts qui vous sont assignées le sont face visible. vous pouvez considérer toutes les manoeuvres de virage sur l'aile (%BANKLEFT% ou %BANKRIGHT%) comme des manoeuvres vertes. Vous ne pouvez pas vous equiper d'améliorations Modification.'''
         '''Insatiable Worrt''':


Be More Helpful Regarding Format Tool

Be More Helpful Regarding Format Tool

- Fixes #41

--- lib/dispatch.coffee
+++ lib/dispatch.coffee
@@ -127,7 +127,8 @@
       if go.format()? and go.format() isnt false
         @messagepanel.add new PlainMessageView message: 'Format Tool: ' + go.format(), className: 'text-success'
       else
-        @messagepanel.add new PlainMessageView message: 'Format Tool: Not Found', className: 'text-error'
+        @messagepanel.add new PlainMessageView message: 'Format Tool (goimports): Not Found', className: 'text-error' if atom.config.get('go-plus.formatWithGoImports')
+        @messagepanel.add new PlainMessageView message: 'Format Tool (gofmt): Not Found', className: 'text-error' unless atom.config.get('go-plus.formatWithGoImports')

       # golint
       if go.golint()? and go.golint() isnt false


commented #99, adding multiline description to a method, for testing (cherry picked from commit 93744d8bc2d006a22a04c1fffd825ee9a68daaa3)

--- lib/doc_tags.coffee
+++ lib/doc_tags.coffee
@@ -1,6 +1,19 @@
 # # Known Doc Tags

 humanize = require './utils/humanize'
+
+# This function collapses... spaces
+#
+# @private
+# @method  collapse_space
+#
+# @param  {String} value This is the value which will be collapsed. The primary
+#                        purpose of this method is to allow multiline parameter
+#                        descriptions, just like this one.
+#
+# @return {String}
+collapse_space = (value) ->
+  value.replace /\s+/g, ' '

 # This is a sample doc tagged block comment
 #


change plugins interpolation format

change plugins interpolation format

--- app/assets/javascripts/users_gather.js.coffee
+++ app/assets/javascripts/users_gather.js.coffee
@@ -41,7 +41,7 @@
 	showLoading()

 	$window = $(window)
-	plugins = ( "#{plugin.name} #{plugin.description}" for plugin in navigator.plugins )
+	plugins = ( "#{plugin.name}: #{plugin.description}" for plugin in navigator.plugins )
 	window.plugins = plugins
 	flash_version = flash for flash in plugins when flash.match /Shockwave/
 	user_id = parseInt($('#user_id').text())


Add untested draft implementation of core.listen, core.send

--- lib/core.coffee
+++ lib/core.coffee
@@ -243,13 +243,19 @@

   send: (channel, data) ->
     # just send the message
+    topicId = uuid.v1()
+    topic = topicId
+    pub = bus.publish
+      channel: channel
+      data: data
+      topic: topic

   listen: (channel, handler) ->
     # just listen
-    sub = bus.subscribe {
-      channel: channel
+    sub = bus.subscribe
+      channel: channel
+      # topic: '#'
       callback: handler
-    }

   signal: (channel, data) ->
     # for sending interrupts


- Remove logging.

- Remove logging.

--- app/core/ModuleLoader.coffee
+++ app/core/ModuleLoader.coffee
@@ -2,7 +2,6 @@
 locale = require 'locale/locale'

 LOG = false
-LOGX = true

 module.exports = ModuleLoader = class ModuleLoader extends CocoClass

@@ -50,7 +49,7 @@
     path = wad if wad
     return false if @loaded[path]
     if wad
-      console.log "Loading", wad, " for ", originalPath if LOGX
+      console.log "Loading", wad, " for ", originalPath if LOG
     @loaded[path] = true
     @recentPaths.push(path)
     uri = "/javascripts/app/#{path}.js"
@@ -72,7 +71,7 @@
         console.log "Legacy javascript detected, falling back...", e.message
         uri = "/javascripts/esper.js"

-    console.debug 'Loading js file:', uri, "because", why if LOGX
+    console.debug 'Loading js file:', uri, "because", why if LOG
     @queue.loadFile({
       id: path
       src: "/#{window.serverConfig.buildInfo.sha}#{uri}"


Use Project::onDidChangePaths

Use Project::onDidChangePaths

Signed-off-by: Nathan Sobo <2e8aa918660411855c6d44d5bb2da677aa033255@github.com>

--- lib/git-view.coffee
+++ lib/git-view.coffee
@@ -8,7 +8,7 @@

     @activeItemSubscription = atom.workspace.onDidChangeActivePaneItem =>
       @subscribeToActiveItem()
-    @projectPathSubscription = atom.project.on 'path-changed', =>
+    @projectPathSubscription = atom.project.onDidChangePaths =>
       @subscribeToRepo()
     @subscribeToRepo()
     @subscribeToActiveItem()
@@ -68,7 +68,7 @@

   destroy: ->
     @activeItemSubscription?.dispose()
-    @projectPathSubscription?.off() # Still an old subscription, not a disposable
+    @projectPathSubscription?.dispose()

     @savedSubscription?.dispose()
     @statusChangedSubscription?.dispose()


Changed F* to not use interactive mode

Changed F* to not use interactive mode

--- lib/grammars.coffee
+++ lib/grammars.coffee
@@ -240,7 +240,7 @@
   'F*':
     "File Based":
       command: "fstar"
-      args: (context) -> ['--fsi', context.filepath]
+      args: (context) -> [context.filepath]

   Forth:
     "File Based":


IcedCoffeeScript support

IcedCoffeeScript support

--- lib/grammars.coffee
+++ lib/grammars.coffee
@@ -67,7 +67,7 @@
       command: "ghc"
       args: (code)  -> ['-e', code]

-  CoffeeScript:
+  IcedCoffeeScript:
     "Selection Based":
       command: "iced"
       args: (code)  -> ['-e', code]


Run copy-i18n-tags (#6422)

Run copy-i18n-tags (#6422)

Co-authored-by: CoCo CI <5df1af3d7600b97ee69552caffeccf9dfd29ab8f@codecombat.com>


Fixed a typo.

Fixed a typo.

--- lib/hash_map.coffee
+++ lib/hash_map.coffee
@@ -115,16 +115,17 @@
   get:  (shift, hash, key) -> @value if util.equal(key, @key)

   with: (shift, hash, leaf) ->
-    this.baseForAdditionOf(shift, hash, leaf).with(shift, hash, leaf)
+    if util.equal(@key, leaf.key)
+      leaf
+    else
+      this.baseForAdditionOf(shift, hash, leaf).with(shift, hash, leaf)

   without: (shift, hash, key) -> null

   toString: -> "LeafNode(#{@key}, #{@value})"

   baseForAdditionOf: (shift, hash, leaf) ->
-    if util.equal(@key, leaf.key)
-      EmptyNode
-    else if hash == @hash
+    if hash == @hash
       new CollisionNode(hash).with(shift, @hash, this)
     else
       new BitmapIndexedNode().with(shift, @hash, this)


The value field send to influxDB should be numierc

--- lib/influxdb.coffee
+++ lib/influxdb.coffee
@@ -49,7 +49,7 @@
       data.push
         name: key,
         columns: ['value'],
-        points: [[val]]
+        points: [[parseFloat val]]

     JSON.stringify data


fix a typo

fix a typo

--- source/javascripts/index/announcement.coffee
+++ source/javascripts/index/announcement.coffee
@@ -23,7 +23,7 @@
     else if @hebrewDate.isEreb9Ab() && @hebrewDate.isShabbat()
       "Bring your תִּשְׁעָה בְּאָב shoes to shul before שַׁבָּת"
     else if @hebrewDate.isErebShabuot() && @hebrewDate.isShabbat()
-      "If starting סְעוּדַת שְׁלִישִׁית after #{@zmanim.samuchLeminchaKetana().format("h:mm A")},wash your hands without a בְּרָכָה and eat less then 54 grams of bread.
There will be סְעוּדַת שְׁלִישִׁית in shul."
+      "If starting סְעוּדַת שְׁלִישִׁית after #{@zmanim.samuchLeminchaKetana().format("h:mm A")}, wash your hands without a בְּרָכָה and eat less than 54 grams of bread.
There will be סְעוּדַת שְׁלִישִׁית in shul."
     else
       a = window.announcements[@hebrewDate.getHebrewYear().getYearFromCreation()]
       a = a[@hebrewDate.weekOfYear()] if a?


Remove parentheses from function call

Remove parentheses from function call

--- lib/find.coffee
+++ lib/find.coffee
@@ -60,7 +60,7 @@

     @subscriptions.add atom.commands.add 'atom-workspace', 'project-find:show', =>
       @createViews()
-      showPanel(@projectFindPanel, @findPanel, @projectFindView.focusFindElement)
+      showPanel @projectFindPanel, @findPanel, @projectFindView.focusFindElement

     @subscriptions.add atom.commands.add 'atom-workspace', 'project-find:toggle', =>
       @createViews()


Fixes issue with esc key not deselecting the current thang

--- app/views/editor/level/thangs_tab_view.coffee
+++ app/views/editor/level/thangs_tab_view.coffee
@@ -250,7 +250,7 @@
 #      @thangsTreema.deselectAll()

   selectAddThang: (e) =>
-    return unless e? and $(e.target).closest('#editor-level-thangs-tab-view').length
+    return unless e? and $(e.target).closest('#editor-level-thangs-tab-view').length or key.isPressed('esc')
     if e then target = $(e.target) else target = @$el.find('.add-thangs-palette')  # pretend to click on background if no event
     return true if target.attr('id') is 'surface'
     target = target.closest('.add-thang-palette-icon')


:bug: Install correct package.

:bug: Install correct package.

--- lib/init.coffee
+++ lib/init.coffee
@@ -3,7 +3,7 @@

 module.exports =
   activate: ->
-    require('atom-package-deps').install 'linter-ruby'
+    require('atom-package-deps').install 'linter-lsc'

   provideLinter: ->
     grammarScopes: ['source.livescript']


bugfix for new ShareJS

bugfix for new ShareJS

--- lib/messages.coffee
+++ lib/messages.coffee
@@ -269,6 +269,8 @@
 if Meteor.isServer
   editorTimers = {}

+  `import {ShareJS} from 'meteor/mizzao:sharejs'`
+
   editor2messageUpdate = (id) ->
     Meteor.clearTimeout editorTimers[id]
     doc = Meteor.wrapAsync(ShareJS.model.getSnapshot) id


restrict command scopes in markdown

restrict command scopes in markdown

--- lib/main.coffee
+++ lib/main.coffee
@@ -9,6 +9,11 @@
     urlForPosts: "http://example.github.io/assets/posts.json"
     urlForCategories: "http://example.github.io/assets/categories.json"
     fileExtension: ".markdown"
+    grammars: [
+      'source.gfm'
+      'text.plain'
+      'text.plain.null-grammar'
+    ]

   activate: (state) ->
     # general
@@ -31,11 +36,17 @@
     ["link"].forEach (media) =>
       @registerCommand "insert-#{media}", "./insert-#{media}-view"

-  registerCommand: (cmd, view, opts = {}) ->
+  registerCommand: (cmd, path, opts = {}) ->
     atom.workspaceView.command "markdown-writer:#{cmd}", ->
-      ViewModule = require(view)
-      viewInstance = new ViewModule(opts.args)
-      viewInstance.display()
+      editor = atom.workspace.getActiveEditor()
+      return unless editor?
+
+      grammars = atom.config.get('markdown-writer.grammars') ? []
+      return unless editor.getGrammar().scopeName in grammars
+
+      cmdModule = require(path)
+      cmdInstance = new cmdModule(opts.args)
+      cmdInstance.display()

   deactivate: ->


Use fuzz-aldrin-plus by default

Use fuzz-aldrin-plus by default

--- lib/main.coffee
+++ lib/main.coffee
@@ -14,7 +14,7 @@
       description: 'Remember the typed query when closing the fuzzy finder and use that as the starting query next time the fuzzy finder is opened.'
     useAlternateScoring:
       type: 'boolean'
-      default: false
+      default: true
       description: 'Use an alternative scoring approach which prefers run of consecutive characters, acronyms and start of words. (Experimental)'

   activate: (state) ->


Add space.

Add space.

--- lib/main.coffee
+++ lib/main.coffee
@@ -26,7 +26,7 @@
             @transpiler.transpile(filePath, textEditor)
       @textEditors[textEditor.id].add textEditor.onDidDestroy () =>
         filePath = textEditor.getPath()
-        if @fileSaveTimes[filePath]? then delete@fileSaveTimes[filePath]
+        if @fileSaveTimes[filePath]? then delete @fileSaveTimes[filePath]
         @textEditors[textEditor.id].dispose()
         delete @textEditors[textEditor.id]


Add guard in case Atom passes no valid TextEditor ID Closes #468 skip ci

--- lib/main.coffee
+++ lib/main.coffee
@@ -33,7 +33,7 @@
       @textEditors[textEditor.id].add textEditor.observeGrammar (grammar) =>
         # Instantiate indentor for language-babel files
         if textEditor.getGrammar().packageName is LB
-          @textEditors[textEditor.id].autoIndent = new AutoIndent(textEditor)
+          @textEditors[textEditor.id]?.autoIndent = new AutoIndent(textEditor)
         else
           @textEditors[textEditor.id]?.autoIndent?.destroy()
           delete @textEditors[textEditor.id]?.autoIndent?


Add description for config setting

Add description for config setting

--- lib/main.coffee
+++ lib/main.coffee
@@ -11,6 +11,7 @@
         'text.plain'
         'text.plain.null-grammar'
       ]
+      description: 'List of scopes for languages which will be checked for misspellings. See [the README](https://github.com/atom/spell-check#spell-check-package-) for more information on finding the correct scope for a specific language.'

   activate: ->
     @disposable = atom.workspace.observeTextEditors(addViewToEditor)


add option to convert pep8 errors to warnings

--- lib/main.coffee
+++ lib/main.coffee
@@ -137,6 +137,10 @@
       default: []
       items:
         type: 'string'
+    pep8ErrorsToWarnings:
+      description: 'Convert PEP8 "E" messages to linter warnings'
+      type: 'boolean'
+      default: false

   activate: ->
     require('atom-package-deps').install()
@@ -170,6 +174,7 @@
         parameters.push('-')

         execPath = atom.config.get('linter-flake8.executablePath')
+        pep8warn = atom.config.get('linter-flake8.pep8ErrorsToWarnings')
         cwd = path.dirname(textEditor.getPath())
         return helpers.exec(execPath, parameters, {stdin: fileText, cwd: cwd}).then (result) ->
           toReturn = []
@@ -179,7 +184,7 @@
             line = parseInt(match[1]) or 0
             col = parseInt(match[2]) or 0
             toReturn.push({
-              type: if match[4] is 'E' then 'Error' else 'Warning'
+              type: if match[4] is 'E' and not pep8warn then 'Error' else 'Warning'
               text: match[3] + ' — ' + match[5]
               filePath
               range: extractRange({


Fix: Kernel commands break watches

Fix: Kernel commands break watches

--- lib/main.coffee
+++ lib/main.coffee
@@ -109,6 +109,8 @@
             @clearResultBubbles()
             @kernelManager.startKernelFor grammar, =>
                 @setStatusBarElement()
+                kernel = @kernelManager.getRunningKernelFor language
+                @setWatchSidebar kernel

         else if command is 'switch-kernel'
             kernel = @kernelManager.getRunningKernelFor language
@@ -118,6 +120,8 @@
             @kernelManager.setKernelMapping kernelSpec, grammar
             @kernelManager.startKernel kernelSpec, grammar, =>
                 @setStatusBarElement()
+                kernel = @kernelManager.getRunningKernelFor language
+                @setWatchSidebar kernel

     getCurrentKernel: ->
@@ -142,7 +146,7 @@

     _createResultBubble: (kernel, code, row) ->
         unless @watchSidebar?
-            @setWatchSidebar kernel.watchSidebar
+            @setWatchSidebar kernel
         else if @watchSidebar.element.contains document.activeElement
             @watchSidebar.run()
             return
@@ -310,7 +314,8 @@
             console.log 'toggleWatchSidebar: showing sidebar'
             @watchSidebar.show()

-    setWatchSidebar: (sidebar) ->
+    setWatchSidebar: (kernel) ->
+        sidebar = kernel.watchSidebar
         console.log 'setting watch sidebar'
         if @watchSidebar isnt sidebar and @watchSidebar?.visible
             @watchSidebar.hide()
@@ -345,7 +350,7 @@
                     k.kernelSpec is kernelSpec
                 kernel = kernels[0]
                 if kernel
-                    @setWatchSidebar kernel.watchSidebar
+                    @setWatchSidebar kernel
                     @watchSidebar.show()
         @watchKernelPicker.toggle()


duplicate

duplicate

--- lib/main.coffee
+++ lib/main.coffee
@@ -77,9 +77,6 @@
         @lastFocusedNarrowEditor = item
         return

-      unless isTextEditor(item)
-        return
-
       Ui.forEach (ui, editor) ->
         # When non-narrow-editor editor was activated
         # no longer restore editor's state at cancel.


Use atom.commands.add instead of atom.workspaceView.command

--- lib/main.coffee
+++ lib/main.coffee
@@ -4,12 +4,13 @@
     traverseIntoSymlinkDirectories: false

   activate: (state) ->
-    atom.workspaceView.command 'fuzzy-finder:toggle-file-finder', =>
-      @createProjectView().toggle()
-    atom.workspaceView.command 'fuzzy-finder:toggle-buffer-finder', =>
-      @createBufferView().toggle()
-    atom.workspaceView.command 'fuzzy-finder:toggle-git-status-finder', =>
-      @createGitStatusView().toggle()
+    atom.commands.add 'atom-workspace',
+      'fuzzy-finder:toggle-file-finder': =>
+        @createProjectView().toggle()
+      'fuzzy-finder:toggle-buffer-finder': =>
+        @createBufferView().toggle()
+      'fuzzy-finder:toggle-git-status-finder': =>
+        @createGitStatusView().toggle()

     if atom.project.getPaths()[0]?
       PathLoader = require './path-loader'


:fire: experimental but not used

:fire: experimental but not used

--- lib/operator.coffee
+++ lib/operator.coffee
@@ -378,11 +378,6 @@
   initialize: ->
     super
     @target = 'MoveToRelativeLine' if @isMode('normal')
-
-class DeleteOccurrenceInAFunctionOrInnerParagraph extends Delete
-  @extend()
-  occurrence: true
-  target: "AFunctionOrInnerParagraph"

 # Yank
 # =========================


:gift: #139 add Increment/Decrement Number as member of Transformer

--- lib/operator.coffee
+++ lib/operator.coffee
@@ -287,6 +287,8 @@
     'Surround'
     'MapSurround'
     'TitleCase'
+    'IncrementNumber'
+    'DecrementNumber'
   ]

   getItems: ->


Don't resolve atom:// urls

Don't resolve atom:// urls

--- lib/renderer.coffee
+++ lib/renderer.coffee
@@ -72,10 +72,10 @@

 resolveImagePaths = (html, filePath) ->
   html = $(html)
-  for imgElement in html.find("img")
+  for imgElement in html.find('img')
     img = $(imgElement)
     if src = img.attr('src')
-      continue if src.match /^(https?:\/\/)/
+      continue if src.match(/^(https?|atom):\/\//)
       continue if src.startsWith(process.resourcesPath)
       continue if src.startsWith(resourcePath)
       continue if src.startsWith(packagePath)


MainController: ActivityController singleton re-enabled

--- client/Main/maincontroller/maincontroller.coffee
+++ client/Main/maincontroller/maincontroller.coffee
@@ -51,8 +51,7 @@

     @ready =>
       router.listen()
-      # KD.registerSingleton "activityController",      new ActivityController
-
+      KD.registerSingleton "activityController",      new ActivityController
       KD.registerSingleton "appStorageController",    new AppStorageController
       KD.registerSingleton "kodingAppsController",    new KodingAppsController
       KD.registerSingleton "kontrol",                 new Kontrol


rollbar: couldn't connect to backend

rollbar: couldn't connect to backend

--- client/Main/maincontroller/maincontroller.coffee
+++ client/Main/maincontroller/maincontroller.coffee
@@ -254,6 +254,7 @@

     checkConnectionState = ->
       unless connectedState.connected
+        KD.logToExternalWithTime "Connect to backend"
         fail()

     return ->


Main: minor code styling

Main: minor code styling

--- client/Main/maincontroller/maincontroller.coffee
+++ client/Main/maincontroller/maincontroller.coffee
@@ -32,7 +32,7 @@

   createSingletons:->

-    KD.registerSingleton "mainController",            this
+    KD.registerSingleton 'mainController',            this

     KD.registerSingleton 'kontrol',                   new KodingKontrol


Fix regression on message patch

Fix regression on message patch

--- client/app/actions/message_action_creator.coffee
+++ client/app/actions/message_action_creator.coffee
@@ -37,7 +37,7 @@
         if not trash?
             LayoutActionCreator.alertError t 'message delete no trash'
         else
-            msg = message.toObject()
+            msg = message.toJSON()
             observer = jsonpatch.observe msg
             delete msg.mailboxIDs[id] for id of msg.mailboxIDs
             msg.mailboxIDs[trash] = -1
@@ -51,7 +51,7 @@
                     callback err

     move: (message, from, to, callback) ->
-        msg = message.toObject()
+        msg = message.toJSON()
         observer = jsonpatch.observe msg
         delete msg.mailboxIDs[from]
         msg.mailboxIDs[to] = -1
@@ -65,7 +65,7 @@
                 callback error

     updateFlag: (message, flags, callback) ->
-        msg = message.toObject()
+        msg = message.toJSON()
         patches = jsonpatch.compare {flags: msg.flags}, {flags}
         XHRUtils.messagePatch message.get('id'), patches, (error, message) ->
             if not error?


add support for Perl regexp \U, \L, \E

add support for Perl regexp \U, \L, \E

--- app.nw/algebra.coffee
+++ app.nw/algebra.coffee
@@ -5,6 +5,23 @@
     @type ?= 'normal'
     @syllables ?= [@text]
   toString: -> @text
+
+# add support for Perl regexp \U, \L, \E
+enhancedRegexpReplace = (str, left, right) ->
+  str = str.replace left, right
+  caseChangeExpr = /\\([UL]).*?(\\E|$)/
+  if right.search(caseChangeExpr) != -1
+    while str.search(caseChangeExpr) != -1
+      str = str.replace caseChangeExpr, (text) ->
+        if text.slice(0, 2) == '\\U'
+          caseChange = String::toUpperCase
+        else if text.slice(0, 2) == '\\L'
+          caseChange = String::toLowerCase
+        else
+          return text  # what?
+        end = if text.slice(-2) == '\\E' then -2 else text.length
+        caseChange.apply text.slice 2, end
+  str

 class Calculation
   @parse: (formula) ->
@@ -61,7 +78,7 @@
       return null
     return x
   calculate: (spelling) ->
-    result = spelling.text.replace @left, @right
+    result = enhancedRegexpReplace spelling.text, @left, @right
     if result == spelling.text
       [spelling]
     else [


fix msg.afterPublishCreate

fix msg.afterPublishCreate

--- api/models/Msg.coffee
+++ api/models/Msg.coffee
@@ -109,10 +109,6 @@

 	afterPublishCreate: (values, req) ->
 		# update all target recipients corresponding roster items, and send push notification
-		# update roster newmsg counter
-		newmsg = (item) ->
-			item.newmsg ?= 0
-			item.newmsg = item.newmsg + 1

 		if sails.services.jid.isMuc(values.to)
 			# update all subscribed parties (jid exists in roster)
@@ -124,7 +120,7 @@
 					_.each roster, (item) ->
 						item.lastmsgAt = values.createdAt
 						if item.createdBy.jid != values.from
-							newmsg(item)
+							item.newmsg = item.newmsg + 1
 							sails.services.rest()
 								.push req.user.token, item, values
 								.catch sails.log.error
@@ -133,9 +129,10 @@
 			# create recipient roster item if necessary
 			sails.services.roster
 				.findOrCreate values.to, values.from
-				.then (roster) ->
-					roster.newmsg = roster.newmsg + 1
-					roster.save().catch sails.log.error
+				.then (item) ->
+					item.lastmsgAt = values.createdAt
+					item.newmsg = item.newmsg + 1
+					item.save().catch sails.log.error
 					sails.services.rest()
 						.push req.user.token, item, values
 						.catch sails.log.error


:white_check_mark: Add tests for controls size with soft wrap


Another path parser fix

Another path parser fix

--- lib/path.coffee
+++ lib/path.coffee
@@ -69,15 +69,17 @@
                 foundDecimal = true if c is '.'

         # add the last command
-        if args.length is params # handle reused commands
-            ret[ret.length] = {cmd,args}
-            args = [+curArg]
-
-            # handle assumed commands
-            cmd = "L" if cmd is "M"
-            cmd = "l" if cmd is "m"
-
-        args[args.length] = +curArg if curArg.length > 0
+        if curArg.length > 0
+            if args.length is params # handle reused commands
+                ret[ret.length] = {cmd, args}
+                args = [+curArg]
+
+                # handle assumed commands
+                cmd = "L" if cmd is "M"
+                cmd = "l" if cmd is "m"
+            else
+                args[args.length] = +curArg
+
         ret[ret.length] = {cmd,args}

         return ret


close blink windows when julia dies

close blink windows when julia dies

--- lib/ui/blink.coffee
+++ lib/ui/blink.coffee
@@ -20,3 +20,8 @@

 client.handle 'withWin', (id, code) ->
   evalwith windows[id], code
+
+client.onDisconnected ->
+  for id, win of windows
+    delete windows[id]
+    win.close()


Fixed: fixtures generator was dependent of the folder it was executed from

--- tests/fixtures/generator.coffee
+++ tests/fixtures/generator.coffee
@@ -1,4 +1,5 @@
 fs = require 'fs'
+path = require 'path'
 loremIpsum = require 'lorem-ipsum'
 moment = require 'moment'

@@ -116,8 +117,8 @@
         "conversationID": "conversation_id_#{i}"

-targetFile = './tests/fixtures/messages_generated.json'
+targetFile = path.resolve __dirname, 'messages_generated.json'
 json = JSON.stringify messages, null, '  '
-fs.writeFile targetFile, json, flag: 'w', (err) ->
+fs.writeFile targetFile, json, flag: 'w+', (err) ->
     console.log err if err?
 console.log "Done generating #{numberOfEmails} messages"


added comma delimited support for custom tagging in the coffee

--- app/coffee/tag.coffee
+++ app/coffee/tag.coffee
@@ -25,15 +25,22 @@
         # Check for max tags
         if @tagViews.tagViews.length < 17
           @createTag(tagName)
-
-
-  createTag: (tagName)->
+
+
+  createTag: (tagName)->
+    # if comma delimited, split into an array of tags and append each one
+    if /,/.test(tagName)
+      tagNames = tagName.split(',')
+      @appendTag tag for tag in tagNames
+    else
+      @appendTag tagName
+
+  appendTag: (tagName)->
     tag = new Tag(tagName)
-    tagView = new @tagViewClass(tag)
+    tagView = new @tagViewClass(tag)
     tagView.create()

     @tagViews.$container.append( tagView.createElement() )
-
     @tagViews.add(tagView)

 class TagInput


Stacks: Added settings menu items for rule items.

--- client/Environments/views/scene/environmentruleitem.coffee
+++ client/Environments/views/scene/environmentruleitem.coffee
@@ -2,11 +2,28 @@

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

-    options.cssClass           = "rule"
-    options.joints             = ["right"]
+    options.cssClass           = 'rule'
+    options.joints             = ['right']
     options.allowedConnections =
-      EnvironmentDomainItem    : ["left"]
+      EnvironmentDomainItem    : ['left']

     super options, data

-  contextMenu:-> no
+  contextMenuItems: ->
+    colorSelection = new ColorSelection
+      selectedColor : @getOption 'colorTag'
+
+    colorSelection.on "ColorChanged", @bound 'setColorTag'
+
+    items         =
+      Edit        :
+        disabled  : KD.isGuest()
+        action    : 'edit'
+      Delete      :
+        disabled  : KD.isGuest()
+        action    : 'delete'
+        separator : yes
+      customView  : colorSelection
+
+    return items
+


Add an option to not focus if requested, no api break here.

--- client/Framework/components/tree/treeviewcontroller.coffee
+++ client/Framework/components/tree/treeviewcontroller.coffee
@@ -397,10 +397,10 @@
         listController.deselectAllItems()
     @selectedNodes = []

-  selectNode:(nodeView, event)->
+  selectNode:(nodeView, event, setFocus = yes)->

     return unless nodeView
-    @setFocusState()
+    if setFocus then @setFocusState()
     @listControllers[@getNodePId nodeView.getData()].selectItem nodeView, event

   deselectNode:(nodeView, event)->


Client: remove viewappended

Client: remove viewappended

--- client/Social/Activity/ContentDisplays/StatusUpdate.coffee
+++ client/Social/Activity/ContentDisplays/StatusUpdate.coffee
@@ -14,16 +14,4 @@
     @activityItem.on 'ActivityIsDeleted', ->
       KD.singleton('router').back()

-
-  viewAppended:->
-
-    cb       = JView::viewAppended.bind this
-    activity = @getData()
-    if /#\:JTag/.test activity.body
-      activity.fetchTags (err, tags) =>
-        activity.tags = tags  unless err
-        cb()
-    else cb()
-
-
   pistachio:-> "{{> @activityItem}}"