Removed setting the impact of OpinionatersEvidence when fetching Opinionaters, as we don't need it and are deprecating that way of fetching anyway

--- core/app/backbone/models/evidence.coffee
+++ core/app/backbone/models/evidence.coffee
@@ -7,16 +7,11 @@
   initialize: (attributes, options) ->
     @_fact_id = options.fact_id ? @collection.fact.id

+    @on 'change:users', =>
+      @opinionaters().reset @get('users')
+
   opinionaters: ->
-    return @_opinionaters if @_opinionaters?
-
-    @_opinionaters = new InteractorsPage @get('users') ? [],
+    @_opinionaters ?= new InteractorsPage @get('users') ? [],
       fact_id: @_fact_id
       type: @get('type')
       perPage: 7
-    @_opinionaters.on 'reset', =>
-      @set impact: @_opinionaters.impact
-    @on 'change:users', =>
-      @_opinionaters.reset @get('users')
-
-    @_opinionaters


Fixed naming

Fixed naming

--- core/app/backbone/views/fact_view.coffee
+++ core/app/backbone/views/fact_view.coffee
@@ -38,7 +38,7 @@

   regions:
     factBaseView: '.fact-base-region'
-    factBottomView: '.fact-bottom-region'
+    linkRegion: '.fact-bottom-region'
     poparrowRegion: '.js-region-poparrow'

   initialize: (opts) ->
@@ -47,12 +47,12 @@

   onRender: ->
     @factBaseView.show new FactBaseView(model: @model)
-    @factBottomView.show @newFactBottomView()
+    @linkRegion.show @newLinkView()

     if Factlink.Global.signed_in
       @setPoparrow()

-  newFactBottomView: ->
+  newLinkView: ->
     new FactProxyLinkView
       model: @model


#1559 - [KBT prod] '+' button disappears when user clicks out of first question label box

--- jsapp/xlform_model_view/view.rowSelector.coffee
+++ jsapp/xlform_model_view/view.rowSelector.coffee
@@ -51,11 +51,11 @@
         $(window).on 'keydown.cancel_add_question',  (evt) =>
           # user presses the escape key
           if evt.which == 27
-            @hide()
+            @shrink()

         $('body').on 'mousedown.cancel_add_question', (evt) =>
           if $(evt.target).closest('.line.expanded').length == 0
-            @hide()
+            @shrink()

       else
         $(window).on 'keydown.cancel_add_question',  (evt) =>
@@ -86,6 +86,8 @@

     shrink: ->
       # click .js-close-row-selector
+      $(window).off 'keydown.cancel_add_question'
+      $('body').off 'mousedown.cancel_add_question'
       @line.find("div").eq(0).fadeOut 250, =>
         @line.empty()
       @line.parents(".survey-editor__null-top-row").removeClass "expanded"
@@ -96,8 +98,6 @@
     hide: ->
       @button.removeClass('btn--hidden')
       @line.empty().removeClass("expanded").css "height": 0
-      $(window).off 'keydown.cancel_add_question'
-      $('body').off 'mousedown.cancel_add_question'
       @line.parents(".survey-editor__null-top-row")
           .removeClass("expanded")
           .addClass("survey-editor__null-top-row--hidden")


Error if in steam overlay

Error if in steam overlay

--- client/os.coffee
+++ client/os.coffee
@@ -1,8 +1,6 @@
 Template.osmessage.rendered = ->
   apv = navigator.appVersion
-  console.log apv
-  if !apv? || apv.indexOf("Win") is -1
-    console.log "wrong os"
+  if apv? && apv.indexOf("Win") is -1
     notification =
       title: "OS Not Supported"
       text: "D2Moddin currently only works on Windows."
@@ -11,4 +9,12 @@
       hide: false
       closer: false
     $.pnotify notification
-    console.log notification
+  if apv? && apv.indexOf("GameOverlay") isnt -1
+    notification =
+      title: "Steam Overlay Not Supported"
+      text: "This site won't work with the steam overlay browser."
+      type: "error"
+      sticker: false
+      hide: false
+      closer: false
+    $.pnotify notification


Analytics ID

Analytics ID

--- atom/.atom/config.cson
+++ atom/.atom/config.cson
@@ -21,3 +21,5 @@
     coloured: false
   "markdown-preview":
     useGitHubStyle: true
+  "exception-reporting":
+    userId: "01c5d91d-38f2-dfb7-3a4c-12341a62271e"


:lipstick:

:lipstick:

--- lib/tabs-to-spaces.coffee
+++ lib/tabs-to-spaces.coffee
@@ -22,10 +22,12 @@
   # Returns the {Number} of spaces represented.
   countSpaces: (text) ->
     count = 0
-    for i in [0..text.length]
-      switch text[i]
+    tabLength = @editor.getTabLength()
+
+    for ch in text
+      switch ch
         when ' ' then count += 1
-        when '\t' then count += @editor.getTabLength()
+        when '\t' then count += tabLength

     count


don't do a merge from right to left

don't do a merge from right to left

The idea is to keep consistent with the idea that 0 is to the left and 1 to the
right, the node where the merge starts becomes 0.
Maybe instead this should just reverse the sense of the merge if the order is
wrong?

--- tree2.coffee
+++ tree2.coffee
@@ -783,10 +783,12 @@
       for n in @nodes
         if n.isHit(pos)
           if n isnt @merging.node
-            @mergeNodes(@merging.node, n)
-            @merging = null
-
-            return true
+            if @merging.node.x < n.x
+              @mergeNodes(@merging.node, n)
+              @merging = null
+              return true
+            else
+              console.log('abort merge from right to left')

       @merging = null


Passed forward the error response in a ajax call failure.

--- lib/calatrava/templates/web/app/source/bridge.coffee
+++ lib/calatrava/templates/web/app/source/bridge.coffee
@@ -64,9 +64,9 @@
     success: (response) ->
       goToTop()
       options.success(response)
-    error: () ->
+    error: (response) ->
       showLoader()
-      options.failure() if options.failure?
+      options.failure(response) if options.failure?
     complete: hideLoader

 calatrava.bridge.web.page = (pageName, proxyId) ->


Windows copy/paste

Windows copy/paste

--- lib/views/terminal.coffee
+++ lib/views/terminal.coffee
@@ -18,11 +18,18 @@
     @term.open(this.get(0))
     @term.write('Connecting...\r')

-    this.on 'keydown', (e) =>
-      if e.which == 67 && e.metaKey
-        Clipboard.writeText(getSelection().toString())
-      else if e.which == 86 && e.metaKey
-        @term.emit 'data', Clipboard.readText()
+    if !!process.platform.match(/win/)
+      @term.on 'keydown', (e) =>
+        if e.which == 67 && e.shiftKey && e.ctrlKey
+          Clipboard.writeText(getSelection().toString())
+        else if e.which == 86 && e.shiftKey && e.ctrlKey
+          @term.emit 'data', Clipboard.readText()
+    else
+      this.on 'keydown', (e) =>
+        if e.which == 67 && e.metaKey
+          Clipboard.writeText(getSelection().toString())
+        else if e.which == 86 && e.metaKey
+          @term.emit 'data', Clipboard.readText()

     @applyEditorStyling()
     @handleEvents()


Expose jquery frame el too

Expose jquery frame el too

--- js-library/app/js/jail_iframe/classes/control_iframe.coffee
+++ js-library/app/js/jail_iframe/classes/control_iframe.coffee
@@ -4,6 +4,7 @@
     @el = document.createElement('iframe');
     #need to append to outer document before we can access frame document.
     FactlinkJailRoot.$factlinkCoreContainer.append(@el)
+    @$el = $(@el)
     @doc = @el.contentWindow.document;
     @doc.open()
     #need doctype to avoid quirks mode
@@ -17,4 +18,4 @@
     return unless @el
     @doc = null
     @el.parentElement?.removeChild(@el)
-    @el = null
+    @$el = @el = null


activity: unnecessary variable declaration

--- client/Social/Activity/messagepane.coffee
+++ client/Social/Activity/messagepane.coffee
@@ -15,14 +15,13 @@
       lastToFirst   : yes  if type is 'message'
     @createInputWidget()

-    @listView = @listController.getView()

   viewAppended: ->

     @addSubView @input  if @input
-    @addSubView @listView
+    @addSubView @listController.getView()
     @populate()


a regression fixed

a regression fixed

--- client/app/CommonViews/DNDUploader.coffee
+++ client/app/CommonViews/DNDUploader.coffee
@@ -61,7 +61,7 @@
             origin  : "external"
             filename: file.name
             instance: fsFile
-            content : readEvent.target.multipleItems
+            content : readEvent.target.result
           , event, readEvent

           @reset() if file is lastFile


oauth: remove console.log

oauth: remove console.log

--- client/app/MainApp/oauthcontroller.coffee
+++ client/app/MainApp/oauthcontroller.coffee
@@ -16,7 +16,6 @@
     mainController = KD.getSingleton "mainController"
     if err then notify err
     else
-      console.log "authCompleted", provider
       mainController.emit "ForeignAuthCompleted", provider

   notify = (err)->


Fix message deletion confirmation setting, refs #161

--- client/app/components/message-list.coffee
+++ client/app/components/message-list.coffee
@@ -295,7 +295,8 @@
             alertError t 'list mass no message'
         else
             if @props.settings.get 'displayConversation'
-                if window.confirm(t 'list delete conv confirm', smart_count: selected.length)
+                if (not @props.settings.get('messageConfirmDelete')) or
+                window.confirm(t 'list delete conv confirm', smart_count: selected.length)
                     selected.forEach (id) =>
                         message = @props.messages.get id
                         conversationID = message.get 'conversationID'
@@ -305,7 +306,8 @@
                             else
                                 window.cozyMails.messageNavigate()
             else
-                if window.confirm(t 'list delete confirm', smart_count: selected.length)
+                if (not @props.settings.get('messageConfirmDelete')) or
+                window.confirm(t 'list delete confirm', smart_count: selected.length)
                     MessageActionCreator.delete selected

     onMove: (args) ->


Admin: Integrations: Decode query string in find method.

--- client/app/lib/helpers/integration.coffee
+++ client/app/lib/helpers/integration.coffee
@@ -1,3 +1,4 @@
+kd           = require 'kd'
 remote       = require('app/remote').getInstance()
 doXhrRequest = require 'app/util/doXhrRequest'

@@ -19,6 +20,8 @@
   list (err, items) ->

     return callback err  if err
+
+    query = decodeURIComponent query

     for item in items when item.name is query or item.title is query
       integration = item


Fixed the "refresh feed" functionality.

Fixed the "refresh feed" functionality.

--- app/assets/javascripts/ng-services/ng-ReadSvc.js.coffee
+++ app/assets/javascripts/ng-services/ng-ReadSvc.js.coffee
@@ -71,12 +71,12 @@
     # Refresh a feed and load its unread entries
     #--------------------------------------------
     refresh_feed: ->
-      openEntrySvc.unset()
+      entriesPaginationSvc.reset_entries()
       $rootScope.loading_entries = true

       $http.put("/feeds/#{currentFeedSvc.get().id}.json")
       .success (data)->
-        load_feed currentFeedSvc.get(), false
+        load_entries()
       .error ->
         $rootScope.loading_entries = false
         timerFlagSvc.start 'error_refreshing_feed'


remove hack to get around parse error - api returns correct type now

--- app/components/edit-bucket-page/edit-bucket-page.coffee
+++ app/components/edit-bucket-page/edit-bucket-page.coffee
@@ -9,8 +9,6 @@
     Records.buckets.findOrFetchById(bucketId).then (bucket) ->
       $scope.bucket = bucket
       $scope.bucketLoaded = true
-      # temp hack to get around target form number validation
-      $scope.bucket.target = parseInt $scope.bucket.target

     $scope.cancel = () ->
       $location.path("/buckets/#{bucketId}")


Simplified check

Simplified check

--- src/js/util/loaded_message.coffee
+++ src/js/util/loaded_message.coffee
@@ -5,5 +5,5 @@
   loaded_message.render()

 $(window).on 'factlink.factsLoaded', ->
-  if Factlink? and FactlinkConfig? and FactlinkConfig.client == 'bookmarklet'
+  if FactlinkConfig?.client == 'bookmarklet'
     showLoadedMessage()


domains: indefinite article

domains: indefinite article

--- client/landing/app/Applications/Environments.kdapplication/views/domains/domaincreationform.coffee
+++ client/landing/app/Applications/Environments.kdapplication/views/domains/domaincreationform.coffee
@@ -5,7 +5,7 @@

   domainOptions = [
     { title : "Create a subdomain",     value : "subdomain" }
-    { title : "Register new domain",    value : "new"}
+    { title : "Register a new domain",    value : "new"}
     { title : "Use an existing domain", value : "existing", disabled : yes }
   ]


add hx-pie-chart class to all pie charts

--- modules/plot/main/coffee/charts/PieChart.coffee
+++ modules/plot/main/coffee/charts/PieChart.coffee
@@ -18,7 +18,9 @@

   constructor: (@selector, options) ->
     super
+
     hx.select @selector
+      .classed 'hx-pie-chart', true
       .on 'resize', 'hx.plot', => @render()

     hx.component.register(@selector, this)


Don't look for elements before they load in admin user list

Fixes FOO-2716
flag=none

Global variables and some code in a class initializer tried
to access DOM elements and then act on them possibly before
they were even available. Removing the globals and only
initializing the class after DOM ready fixes it all up.

Test plan:
* On the main branch, see the ticket for steps to reproduce
  including adding some throttling and verify that you can
  reproduce the broken behavior (for me, it was pretty easy
  to make it happen)
* Then switch to this commit and try reproducing again
* The "add admin" box should only show the Continue and
  Cancel buttons initially, with the others showing up only
  after changes are made.
* Try exercising the functionality on that page by adding
  and removing some account admins... everything should
  still work.

Change-Id: I458ce353d99bb9b31fa40b589de23b0042f4f0c0
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/288882
Tested-by: Service Cloud Jenkins <9144042a601061f88f1e1d7a1753ea3e2972119d@instructure.com>
Reviewed-by: Ahmad Amireh <a53a33601b8dd9d06ae9e50f1f30fbe957aba866@instructure.com>
QA-Review: Charley Kline <05950d7e90ba5e3da323378368c22b3ffaaf6647@instructure.com>
Product-Review: Charley Kline <05950d7e90ba5e3da323378368c22b3ffaaf6647@instructure.com>

--- ui/features/user_lists/jquery/index.coffee
+++ ui/features/user_lists/jquery/index.coffee
@@ -17,6 +17,7 @@

 import INST from 'browser-sniffer'
 import {useScope as useI18nScope} from '@canvas/i18n'
+import ready from '@instructure/ready'
 import $ from 'jquery'
 import '@canvas/jquery/jquery.ajaxJSON'
 import '@canvas/forms/jquery/jquery.instructure_forms'
@@ -30,33 +31,31 @@

 I18n = useI18nScope('user_lists')

-$user_lists_processed_person_template = $("#user_lists_processed_person_template").removeAttr("id").detach()
-$user_list_no_valid_users = $("#user_list_no_valid_users")
-$user_list_with_errors = $("#user_list_with_errors")
-$user_lists_processed_people = $("#user_lists_processed_people")
-$user_list_duplicates_found = $("#user_list_duplicates_found")
-$form = $("#enroll_users_form")
-$enrollment_blank = $("#enrollment_blank").removeAttr("id").hide()
-user_lists_path = $("#user_lists_path").attr("href")
-
 UL = INST.UserLists =
   init: ->
+    UL.$form = $("#enroll_users_form")
+    UL.$enrollment_blank = $("#enrollment_blank").removeAttr("id").hide()
+    UL.$user_lists_processed_person_template = $("#user_lists_processed_person_template").removeAttr("id").detach()
+    UL.$user_list_no_valid_users = $('#user_list_no_valid_users')
+    UL.$user_list_with_errors = $('#user_list_with_errors')
+    UL.$user_list_duplicates_found = $('#user_list_duplicates_found')
     UL.showTextarea()

-    $form.find(".cancel_button").click(->
+    UL.$form.find(".cancel_button").click ->
       $(".add_users_link").show()
-      $form.hide()
-    ).end().find(".go_back_button").click(UL.showTextarea).end().find(".verify_syntax_button").click((e) ->
+      UL.$form.hide()
+    UL.$form.find(".go_back_button").click(UL.showTextarea)
+    UL.$form.find(".verify_syntax_button").click (e) ->
       e.preventDefault()
       UL.showProcessing()
-      $.ajaxJSON user_lists_path, "POST", $form.getFormData(), UL.showResults
-    ).end().submit (event) ->
+      $.ajaxJSON $("#user_lists_path").attr("href"), "POST", UL.$form.getFormData(), UL.showResults
+    UL.$form.submit (event) ->
       event.preventDefault()
       event.stopPropagation()
-      $form.find(".add_users_button").text(I18n.t("adding_users", "Adding Users...")).attr "disabled", true
-      $.ajaxJSON $form.attr("action"), "POST", $form.getFormData(), UL.success, UL.failure
-
-    $form.find("#enrollment_type").change(->
+      UL.$form.find(".add_users_button").text(I18n.t("adding_users", "Adding Users...")).attr "disabled", true
+      $.ajaxJSON UL.$form.attr("action"), "POST", UL.$form.getFormData(), UL.success, UL.failure
+
+    UL.$form.find("#enrollment_type").change(->
       $("#limit_privileges_to_course_section_holder").showIf $(this).find(':selected').data("isAdmin")?
     ).change()

@@ -79,7 +78,7 @@
               UL.updateCounts()

   success: (enrollments) ->
-    $form.find(".user_list").val ""
+    UL.$form.find(".user_list").val ""
     UL.showTextarea()
     return false  if not enrollments or not enrollments.length
     already_existed = 0
@@ -106,33 +105,34 @@
     $.flashError I18n.t("users_adding_failed", "Failed to enroll users")

   showTextarea: ->
-    $form.find(".add_users_button, .go_back_button, #user_list_parsed").hide()
-    $form.find(".verify_syntax_button, .cancel_button, #user_list_textarea_container").show().removeAttr "disabled"
-    $form.find(".verify_syntax_button").attr("disabled", false).text I18n.t("buttons.continue", "Continue...")
-    $user_list = $form.find(".user_list").removeAttr('disabled').loadingImage('remove').focus()
+    UL.$form.find(".add_users_button, .go_back_button, #user_list_parsed").hide()
+    UL.$form.find(".verify_syntax_button, .cancel_button, #user_list_textarea_container").show().removeAttr "disabled"
+    UL.$form.find(".verify_syntax_button").attr("disabled", false).text I18n.t("buttons.continue", "Continue...")
+    $user_list = UL.$form.find(".user_list").removeAttr('disabled').loadingImage('remove').focus()
     $user_list.select() if $user_list.is(':visible') # .select() blows up in IE9 + jQuery 1.7.2 on invisible elements

   showProcessing: ->
-    $form.find(".verify_syntax_button").attr("disabled", true).text I18n.t("messages.processing", "Processing...")
-    $form.find(".user_list").attr("disabled", true).loadingImage()
+    UL.$form.find(".verify_syntax_button").attr("disabled", true).text I18n.t("messages.processing", "Processing...")
+    UL.$form.find(".user_list").attr("disabled", true).loadingImage()

   showResults: (userList) ->
-    $form.find(".add_users_button, .go_back_button, #user_list_parsed").show()
-    $form.find(".add_users_button").attr("disabled", false).focus().text I18n.t("add_n_users",
+    $user_lists_processed_people = $('#user_lists_processed_people')
+    UL.$form.find(".add_users_button, .go_back_button, #user_list_parsed").show()
+    UL.$form.find(".add_users_button").attr("disabled", false).focus().text I18n.t("add_n_users",
       one: "OK Looks Good, Add This 1 User"
       other: "OK Looks Good, Add These %{count} Users"
     ,
       count: userList.users.length
     )
-    $form.find(".verify_syntax_button, .cancel_button, #user_list_textarea_container").hide()
-    $form.find(".user_list").removeAttr("disabled").loadingImage "remove"
+    UL.$form.find(".verify_syntax_button, .cancel_button, #user_list_textarea_container").hide()
+    UL.$form.find(".user_list").removeAttr("disabled").loadingImage "remove"
     $user_lists_processed_people.html("").show()
     if not userList or not userList.users or not userList.users.length
-      $user_list_no_valid_users.appendTo $user_lists_processed_people
-      $form.find(".add_users_button").hide()
+      UL.$user_list_no_valid_users.appendTo $user_lists_processed_people
+      UL.$form.find(".add_users_button").hide()
     else
       if userList.errored_users and userList.errored_users.length
-        $user_list_with_errors.appendTo($user_lists_processed_people).find(".message_content").text I18n.t("user_parsing_errors",
+        UL.$user_list_with_errors.appendTo($user_lists_processed_people).find(".message_content").text I18n.t("user_parsing_errors",
           one: "There was 1 error parsing that list of users."
           other: "There were %{count} errors parsing that list of users."
         ,
@@ -144,14 +144,14 @@
           count: userList.users.length
         )
       if userList.duplicates and userList.duplicates.length
-        $user_list_duplicates_found.appendTo($user_lists_processed_people).find(".message_content").text I18n.t("duplicate_users",
+        UL.$user_list_duplicates_found.appendTo($user_lists_processed_people).find(".message_content").text I18n.t("duplicate_users",
           one: "1 duplicate user found, duplicates have been removed."
           other: "%{count} duplicate user found, duplicates have been removed."
         ,
           count: userList.duplicates.length
         )
       $.each userList.users, ->
-        userDiv = $user_lists_processed_person_template.clone(true).fillTemplateData(data: this).appendTo($user_lists_processed_people)
+        userDiv = UL.$user_lists_processed_person_template.clone(true).fillTemplateData(data: this).appendTo($user_lists_processed_people)
         userDiv.addClass("existing-user").attr "title", I18n.t("titles.existing_user", "Existing user")  if @user_id
         userDiv.show()

@@ -180,7 +180,7 @@
     already_existed = true
     unless $("#enrollment_" + enrollment.id).length
       already_existed = false
-      $enrollment = $enrollment_blank.clone(true).fillTemplateData(
+      $enrollment = UL.$enrollment_blank.clone(true).fillTemplateData(
         textValues: [ "name", "membership_type", "email", "enrollment_id" ]
         id: "enrollment_" + enrollment.id
         hrefValues: [ "id", "user_id", "pseudonym_id", "communication_channel_id" ]
@@ -199,5 +199,6 @@
     UL.updateCounts()
     if already_existed then 1 else 0

-$ UL.init
+ready -> $(UL.init)
+
 export default UL


[grunt] use correct term `domain` instead of `locale` for domain-specific `gettext` functions

--- tasks/grunt-nunjucks-2-html.coffee
+++ tasks/grunt-nunjucks-2-html.coffee
@@ -290,13 +290,13 @@
             i18n.dgettext(currentLocale, string)

           ###*
-           * Load string from specified locale
-           * @param {string} locale = currentLocale Locale, from which string should be loaded
+           * Load string from specified domain
+           * @param {string} domain = currentLocale Domain, from which string should be loaded
            * @param {string} string                 String, which should be loaded
            * @return {string} Translated string into specified locale
           ###
-          env.addGlobal 'dgettext', (locale = currentLocale, string) ->
-            i18n.dgettext(locale, string)
+          env.addGlobal 'dgettext', (domain = currentLocale, string) ->
+            i18n.dgettext(domain, string)

           ###*
           * Load plural string from current locale
@@ -309,15 +309,15 @@
             i18n.dngettext(currentLocale, string, stringPlural, count)

           ###*
-           * Load plural string from specified locale
-           * @param {string} locale = currentLocale Locale, from which string should be loaded
+           * Load plural string from specified domain
+           * @param {string} domain = currentLocale Domain, from which string should be loaded
            * @param {string} string                 String, which should be loaded
            * @param {string} stringPlural           Plural form of string
            * @param {number} count                  Count for detecting correct plural form
            * @return {string} Pluralized and translated into specified loca stringle
           ###
-          env.addGlobal 'dngettext', (locale = currentLocale, string, stringPlural, count) ->
-            i18n.dngettext(locale, string, stringPlural, count)
+          env.addGlobal 'dngettext', (domain = currentLocale, string, stringPlural, count) ->
+            i18n.dngettext(domain, string, stringPlural, count)

           ###*
           * Load string of specific context from current locale
@@ -329,14 +329,14 @@
             i18n.dpgettext(currentLocale, context, string)

           ###*
-           * Load string of specific context from specified locale
-           * @param {string} locale = currentLocale Locale, from which string should be loaded
+           * Load string of specific context from specified domain
+           * @param {string} domain = currentLocale Domain, from which string should be loaded
            * @param {string} context                Context of curret string
            * @param {string} string                 String, which should be loaded
            * @return {string} Translated string into specified locale
           ###
-          env.addGlobal 'dpgettext', (locale = currentLocale, context, string) ->
-            i18n.dpgettext(locale, context, string)
+          env.addGlobal 'dpgettext', (domain = currentLocale, context, string) ->
+            i18n.dpgettext(domain, context, string)

           ###*
           * Load plural string of specific context from current locale
@@ -350,16 +350,16 @@
             i18n.dnpgettext(currentLocale, context, string, stringPlural, count)

           ###*
-           * Load plural string of specific context from specified locale
-           * @param {string} locale = currentLocale Locale, from which string should be loaded
+           * Load plural string of specific context from specified domain
+           * @param {string} domain = currentLocale Domain, from which string should be loaded
            * @param {string} context                Context of curret string
            * @param {string} string                 String, which should be loaded
            * @param {string} stringPlural           Plural form of string
            * @param {number} count                  Count for detecting correct plural form
            * @return {string} Pluralized and translated into specified loca stringle
           ###
-          env.addGlobal 'dnpgettext', (locale = currentLocale, context, string, stringPlural, count) ->
-            i18n.dnpgettext(locale, context, string, stringPlural, count)
+          env.addGlobal 'dnpgettext', (domain = currentLocale, context, string, stringPlural, count) ->
+            i18n.dnpgettext(domain, context, string, stringPlural, count)

           # =======
           # Filters


fix encodeURI to handle apostrophes in filenames

--- tasks/invalidate_cloudfront.coffee
+++ tasks/invalidate_cloudfront.coffee
@@ -2,6 +2,9 @@
     'use strict'
     _ = grunt.util._
     AWS = require 'aws-sdk'
+
+    rfc3986EncodeURI = (str) ->
+      encodeURI(str).replace /[!'()*]/g, escape

     grunt.registerMultiTask "invalidate_cloudfront", "Invalidates Cloudfront files", ->
         options = @options(
@@ -47,9 +50,9 @@
                     checkForCompletion()
                 else
                     done(true)
-
+
         cf = new AWS.CloudFront(new AWS.Config({accessKeyId:options.key, secretAccessKey: options.secret, region:options.region}))
-        filelist = ('/' + encodeURI(grunt.template.process(items.dest)) for items in this.files)
+        filelist = ('/' + rfc3986EncodeURI(grunt.template.process(items.dest)) for items in this.files)
         grunt.log.writeflags(filelist, 'Invalidating '+filelist.length+' files')

         # List Current Invalidations


add this

add this

--- communist.coffee
+++ communist.coffee
@@ -35,6 +35,7 @@
 Socialist = (fun)->
 	_func = fun
 	@send = (data..., cb) ->
+		@self = {}
 		@self.send = (m)->
 			cb.apply @, [null, m]
 		try


Fix broken app (how come it wasn't broken for derek?)

--- src/app/app.coffee
+++ src/app/app.coffee
@@ -16,7 +16,6 @@
   .directive('budgetBanner', window.Cobudget.Directives.BudgetBanner)
   .directive('navBar', window.Cobudget.Directives.NavBar)
   .directive('tabBar', window.Cobudget.Directives.TabBar)
-  .service('Bucket', window.Cobudget.Resources.Bucket)

   .config(
     ($stateProvider, $urlRouterProvider) ->


Changed newpie width to fit 6x4 layout

Changed newpie width to fit 6x4 layout

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -17,7 +17,7 @@
         $(@node).children("svg").remove();

         # Config values here
-        width = 700                             # Width of the SVG area
+        width = 600                             # Width of the SVG area
         height = 400                            # Height of the SVG area
         radius = Math.min(width, height) / 2    # Calculated min dimension of the SVG area
         radiuso = 130                           # Outer radius of the pie


Agent pie: start/end angle pie transition work

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -167,10 +167,11 @@
         path.enter().append('path').attr('class', 'pie').attr('fill', (d, i) -> color i)

         path.transition().duration(1000).attrTween('d', (d,i) ->
-            theOldDataInPie = window.oldPieData[myInstanceDataId] ? piedata
+            theOldDataInPie = window.oldPieData[myInstanceDataId]
             console.log('theOldDataInPie in the tween',theOldDataInPie)
             #console.log('@oldPieData in the tween',@oldPieData[@instanceDataId])
-            console.log('window.oldPieData in the tween',window.oldPieData[myInstanceDataId])
+            console.log('window.oldPieData[myInstanceDataId] in the tween',window.oldPieData[myInstanceDataId])
+            console.log('window.oldPieData in the tween',window.oldPieData)
             #@testData = @testData + 1000
             #tD = @testData + 1000
             #console.log('tD in the tween',tD)


Agent pie: working on label collisions

Agent pie: working on label collisions

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -284,7 +284,7 @@
                 d.x = d.l + bbox.width / 2 + 5
                 d.r = d.l + bbox.width + 10
             else
-                console.log 'no conflicts for ', d, ' ', myInstanceDataId
+                console.log 'no conflicts for ', myInstanceDataId, ' ', d

             ### add this label to the quadtree, so it will show up as a conflict
                for future labels.


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -588,7 +588,7 @@
       #bbox.width and bbox.height will
       #describe the size of the label text
       #labelRadius = radius - 20
-      labelRadius = radiuso * 1.4
+      labelRadius = radiuso * 1.3
       d.x = Math.sin(a) * labelRadius
       d.l = d.x - bbox.width / 2 - 2
       d.r = d.x + bbox.width / 2 + 2


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -4,477 +4,207 @@

   onData: (data) ->
     $(@node).fadeOut().fadeIn()
+    @buildPieStructure
     @render(data.data)

-  renderX: (data) ->
-    if !data
-      data = @get("value")
-    if !data
+  buildPieStructure: ->
+    @width = 300
+    @height = 300
+    @radius = Math.min(@width, @height) / 2
+    @color = d3.scale.category20()
+    @pie = d3.layout.pie().sort(null)
+    @arc = d3.svg.arc().innerRadius(@radius - 100).outerRadius(@radius - 50)
+    @svg = d3.select('#theamazingpie').append('svg').attr('width', @width).attr('height', @height).append('g').attr('transform', 'translate(' + @width / 2 + ',' + @height / 2 + ')')
+    return
+
+  oldPieData: ''
+
+  pieTween: (d, i) ->
+    `var i`
+    that = this
+    theOldDataInPie = theAmazingPie.oldPieData
+    # Interpolate the arcs in data space
+    s0 = undefined
+    e0 = undefined
+    if theOldDataInPie[i]
+      s0 = theOldDataInPie[i].startAngle
+      e0 = theOldDataInPie[i].endAngle
+    else if !theOldDataInPie[i] and theOldDataInPie[i - 1]
+      s0 = theOldDataInPie[i - 1].endAngle
+      e0 = theOldDataInPie[i - 1].endAngle
+    else if !theOldDataInPie[i - 1] and theOldDataInPie.length > 0
+      s0 = theOldDataInPie[theOldDataInPie.length - 1].endAngle
+      e0 = theOldDataInPie[theOldDataInPie.length - 1].endAngle
+    else
+      s0 = 0
+      e0 = 0
+    i = d3.interpolate({
+      startAngle: 0
+      endAngle: 0
+    },
+      startAngle: d.startAngle
+      endAngle: d.endAngle)
+    (t) ->
+      b = i(t)
+      theAmazingPie.arc b
+
+  removePieTween: (d, i) ->
+    `var i`
+    that = this
+    s0 = 2 * Math.PI
+    e0 = 2 * Math.PI
+    i = d3.interpolate({
+      startAngle: d.startAngle
+      endAngle: d.endAngle
+    },
+      startAngle: s0
+      endAngle: e0)
+    (t) ->
+      b = i(t)
+      theAmazingPie.arc b
+
+  update: (dataSet) ->
+    console.log 'update pie', dataSet
+    that = this
+    @piedata = @pie(data[0].value)
+    console.log 'update pie', @pieData
+    #create a marker element if it doesn't already exist
+    defs = @svg.select('defs')
+    if defs.empty()
+      defs = @svg.append('defs')
+    marker = defs.select('marker#circ')
+    if marker.empty()
+      defs.append('marker').attr('id', 'circ').attr('markerWidth', 6).attr('markerHeight', 6).attr('refX', 3).attr('refY', 3).append('circle').attr('cx', 3).attr('cy', 3).attr 'r', 3
+    #Create/select <g> elements to hold the different types of graphics
+    #and keep them in the correct drawing order
+    pathGroup = @svg.select('g.piePaths')
+    if pathGroup.empty()
+      pathGroup = @svg.append('g').attr('class', 'piePaths')
+    pointerGroup = @svg.select('g.pointers')
+    if pointerGroup.empty()
+      pointerGroup = @svg.append('g').attr('class', 'pointers')
+    labelGroup = @svg.select('g.labels')
+    if labelGroup.empty()
+      labelGroup = @svg.append('g').attr('class', 'labels')
+    @path = pathGroup.selectAll('path.pie').data(@piedata)
+    #console.log("path",this.path.empty())
+    @path.enter().append('path').attr('class', 'pie').attr 'fill', (d, i) ->
+      that.color i
+    @path.transition().duration(1500).attrTween 'd', that.pieTween
+    @path.exit().transition().duration(300).attrTween('d', that.removePieTween).remove()
+    labels = labelGroup.selectAll('text').data(@piedata.sort((p1, p2) ->
+      p1.startAngle - p2.startAngle
+    ))
+    labels.enter().append('text').attr 'text-anchor', 'middle'
+    labels.exit().remove()
+    labelLayout = d3.geom.quadtree().extent([
+      [
+        -that.width
+        -that.height
+      ]
+      [
+        that.width
+        that.height
+      ]
+    ]).x((d) ->
+      d.x
+    ).y((d) ->
+      d.y
+    )([])
+    #create an empty quadtree to hold label positions
+    maxLabelWidth = 0
+    maxLabelHeight = 0
+    labels.text((d) ->
+      # Set the text *first*, so we can query the size
+      # of the label with .getBBox()
+      d.value
+    ).each((d, i) ->
+      # Move all calculations into the each function.
+      # Position values are stored in the data object
+      # so can be accessed later when drawing the line
+
+      ### calculate the position of the center marker ###
+
+      a = (d.startAngle + d.endAngle) / 2
+      #trig functions adjusted to use the angle relative
+      #to the "12 o'clock" vector:
+      d.cx = Math.sin(a) * (that.radius - 75)
+      d.cy = -Math.cos(a) * (that.radius - 75)
+
+      ### calculate the default position for the label,
+         so that the middle of the label is centered in the arc
+      ###
+
+      bbox = @getBBox()
+      #bbox.width and bbox.height will
+      #describe the size of the label text
+      labelRadius = that.radius - 20
+      d.x = Math.sin(a) * labelRadius
+      d.l = d.x - bbox.width / 2 - 2
+      d.r = d.x + bbox.width / 2 + 2
+      d.y = -Math.cos(a) * (that.radius - 20)
+      d.b = d.oy = d.y + 5
+      d.t = d.y - bbox.height - 5
+
+      ### check whether the default position
+         overlaps any other labels
+      ###
+
+      conflicts = []
+      labelLayout.visit (node, x1, y1, x2, y2) ->
+        #recurse down the tree, adding any overlapping
+        #node is the node in the quadtree,
+        #node.point is the value that we added to the tree
+        #x1,y1,x2,y2 are the bounds of the rectangle that
+        #this node covers
+        if x1 > d.r + maxLabelWidth / 2 or x2 < d.l - maxLabelWidth / 2 or y1 > d.b + maxLabelHeight / 2 or y2 < d.t - maxLabelHeight / 2
+          return true
+        #don't bother visiting children or checking this node
+        p = node.point
+        v = false
+        h = false
+        if p
+          #p is defined, i.e., there is a value stored in this node
+          h = p.l > d.l and p.l <= d.r or p.r > d.l and p.r <= d.r or p.l < d.l and p.r >= d.r
+          #horizontal conflict
+          v = p.t > d.t and p.t <= d.b or p.b > d.t and p.b <= d.b or p.t < d.t and p.b >= d.b
+          #vertical conflict
+          if h and v
+            conflicts.push p
+          #add to conflict list
+        return
+      if conflicts.length
+        console.log d, ' conflicts with ', conflicts
+        rightEdge = d3.max(conflicts, (d2) ->
+          `var maxLabelHeight`
+          `var maxLabelWidth`
+          d2.r
+        )
+        d.l = rightEdge
+        d.x = d.l + bbox.width / 2 + 5
+        d.r = d.l + bbox.width + 10
+      else
+        console.log 'no conflicts for ', d
+
+      ### add this label to the quadtree, so it will show up as a conflict
+         for future labels.
+      ###
+
+      labelLayout.add d
+      maxLabelWidth = Math.max(maxLabelWidth, bbox.width + 10)
+      maxLabelHeight = Math.max(maxLabelHeight, bbox.height + 10)
       return
-
-    $(@node).children(".title").text($(@node).attr("data-title"))
-    $(@node).children(".more-info").text($(@node).attr("data-moreinfo"))
-    $(@node).children(".updated-at").text(@get('updatedAtMessage'))
-
-    width = 750 #width
-    height = 450 #height
-    radiuso = 135 #outer radius
-    radiusi = 90 #inner radius
-    labelRadius = 180
-
-    #color = d3.scale.ordinal()
-      #.domain([1,10])
-    #  .domain([0,2])
-      #.range( ['#222222','#555555','#777777','#999999','#bbbbbb','#dddddd','#ffffff'] )
-      #.range( ['#222222','#333333','#444444','#555555','#666666','#777777','#888888','#999999','#aaaaaa'] )
-    #  .range( ['#111111','#222222','#333333','#444444','#555555','#666666','#777777','#888888','#999999','#aaaaaa','#bbbbbb','#cccccc'] )
-    color = d3.scale.category20()
-
-    $(@node).children("svg").remove();
-
-    vis = d3.select(@node).append("svg:svg")
-      .data([data])
-        .attr("width", width)
-        .attr("height", height)
-      .append("svg:g")
-        .attr("transform", "translate(" + width/2 + "," + height/2 + ")")
-
-    arc = d3.svg.arc()
-      .outerRadius(radiuso)
-      .innerRadius(radiusi)
-
-    pie = d3.layout.pie().value((d) -> d.value)
-
-    arcs = vis.selectAll("g.slice")
-      .data(pie)
-      .enter().append("svg:g").attr("class", "slice")
-
-    defs = arcs.append("defs")
-    filter = defs.append("filter")
-        .attr("id","dropshadow")
-    filter.append("feGaussianBlur")
-        .attr("in","SourceAlpha")
-        .attr("stdDeviation","1.1")
-        .attr("result","blur")
-    filter.append("feOffset")
-        .attr("in","blur")
-        .attr("dx",2)
-        .attr("dy",2)
-        .attr("result","offsetBlur")
-    feMerge = filter.append("feMerge")
-    feMerge.append("feMergeNode")
-        .attr("in","offsetBlur")
-    feMerge.append("feMergeNode")
-        .attr("in","SourceGraphic")
-
-    arcs.append("svg:path").attr("fill", (d, i) -> color i)
-      .attr("fill-opacity", 0.7).attr("d", arc)
-      .attr("filter","url(#dropshadow)")
-
-    sum=0
-    for val in data
-      sum += val.value
-
-    if !sum
-      arcs.append("svg:text").attr("class","cusipie-label")
-        .attr('fill', "#fff")
-        .attr("text-anchor", "middle").text("No Results")#.attr('font-size', mFontSize + 'px')
-    else
-      arcs.append("svg:text").attr("class","cusipie-sum")
-        .attr('fill', "#fff")
-        .attr("text-anchor", "middle")
-        .attr("alignment-baseline", "mathematical")
-        .attr('font-size', radiusi*1.3 + 'px')
-        .attr('font-weight', 'bold')
-        .text(sum)
-        .attr("filter","url(#dropshadow)")
-
-      arcs.append("svg:text").attr("transform", (d, i) ->
-        #procent_val = Math.round(data[i].value/sum * 100)
-        #d.innerRadius = (radiuso * (100-procent_val)/100) - radiuso/2 #45  #45=max text size/2
-        #d.innerRadius = 0 #(radiuso * (100-procent_val)/100) - radiuso/2 #45  #45=max text size/2
-        #d.outerRadius = radiuso
-        #"translate(" + arc.centroid(d) + ")")
-
-        c = arc.centroid(d)
-        x = c[0]
-        y = c[1]
-        mx = 1.7
-        my = 1
-        h = Math.sqrt(x*x + y*y)
-        r = labelRadius
-        if x <= 0
-            r = -r
-        if x < 0 and y < 0 and x > -radiusi and x/y < 1.2
-            x = x * mx
-            y = y * my
-        return "translate(" + (x/h * labelRadius) + "," + (y/h * labelRadius) + ")"
-        #return "translate(" + r + "," + y + ")"#(y/h * labelRadius) + ")"
-        #return "translate(" + ((x/h * labelRadius)+r/4) + "," + y*my + ")"#(y/h * labelRadius) + ")"
-        #return "translate(" + (x+r/2)*mx + "," + y*my + ")"
-        #return "translate(" + (x/h * labelRadius)*mx + "," + (y/h * labelRadius)*my + ")"
-        )
-
-        .append("svg:tspan").attr("class","cusipie-label")
-
-        .attr('fill', "#fff")
-        .attr("text-anchor", "middle")
-        .text((d, i) ->
-          if data[i].value != 0
-            #data[i].label
-            data[i].label + " - " + data[i].value
-        )
-        #.attr('font-size', mFontSize + 'px')
-        .attr("filter","url(#dropshadow)")
-        .append('svg:tspan').attr("class","cusipie-label-percent")
-        .attr('x', 0)
-        .attr('dy', '.9em')
-        #.attr('font-size', '70%')
-        .text((d,i) ->
-          if data[i].value != 0
-            #data[i].value + ' (' + Math.round(data[i].value/sum * 100) + '%)'
-            '(' + Math.round(data[i].value/sum * 100) + '%)'
-        )
-
-  pieTween: (d, i) ->
-
-        i = d3.interpolate({startAngle: 0,endAngle: 0}, {startAngle: d.startAngle,endAngle: d.endAngle})
-
-        return (t) ->
-            b = i(t);
-            return arc(b)
-
-  renderX3: (data) ->
-
-        if !data
-          data = @get("value")
-        if !data
-          return
-
-        $(@node).children(".title").text($(@node).attr("data-title"))
-        $(@node).children(".more-info").text($(@node).attr("data-moreinfo"))
-        $(@node).children(".updated-at").text(@get('updatedAtMessage'))
-
-        # Build pie
-
-        width = 750
-        height = 450
-        radiuso = 135 #outer radius
-        radiusi = 90 #inner radius
-        radius = Math.min(width, height) / 2 #palette radius min
-        labelRadius = 180
-
-        color = d3.scale.category20()
-
-        $(@node).children("svg").remove();
-
-        arc = d3.svg.arc().outerRadius(radius)
-        pie = d3.layout.pie().value((d) -> d.value)
-
-        svg = d3.select(@node).append("svg:svg")
-          .data([data])
-            .attr("width", width)
-            .attr("height", height)
-          .append("svg:g")
-            .attr("transform", "translate(" + radius + "," + radius + ")")
-
-        arcs = svg.selectAll("g.slice")
-          .data(pie)
-          .enter().append("svg:g").attr("class", "slice")
-
-        arcs.append("svg:path").attr("fill", (d, i) -> color i)
-          .attr("fill-opacity", 0.4).attr("d", arc)
-
-  render: (data) ->
-
-        if !data
-          data = @get("value")
-        if !data
-          return
-
-
-        $(@node).children(".title").text($(@node).attr("data-title"))
-        $(@node).children(".more-info").text($(@node).attr("data-moreinfo"))
-        $(@node).children(".updated-at").text(@get('updatedAtMessage'))
-
-        # Build pie
-
-        width = 750
-        height = 450
-        radiuso = 135 #outer radius
-        radiusi = 90 #inner radius
-        radius = Math.min(width, height) / 2 #palette radius min
-        labelRadius = 180
-
-        color = d3.scale.category20()
-
-        $(@node).children("svg").remove();
-
-        svg = d3.select(@node).append("svg")
-            .attr("width", width)
-            .attr("height", height)
-            .append("g")
-            .attr("transform", "translate(" + width/2 + "," + height/2 + ")")
-
-        pie = d3.layout.pie()
-            #.sort(null)
-
-        arc = d3.svg.arc()
-          .outerRadius(radiuso)
-          .innerRadius(radiusi)
-
-        piedata = pie(data[0].value)
-        #console.log("update pie", piedata)
-
-        # end Build pie
-
-        #create a marker element if it doesn't already exist
-        defs = svg.select("defs")
-        if defs.empty()
-            defs = svg.append("defs")
-
-        marker = defs.select("marker#circ")
-        if marker.empty()
-            defs.append("marker")
-            .attr("id", "circ")
-            .attr("markerWidth", 6)
-            .attr("markerHeight", 6)
-            .attr("refX", 3)
-            .attr("refY", 3)
-            .append("circle")
-            .attr("cx", 3)
-            .attr("cy", 3)
-            .attr("r", 3)
-
-        #Create/select <g> elements to hold the different types of graphics
-        #and keep them in the correct drawing order
-        pathGroup = svg.select("g.piePaths")
-        if pathGroup.empty()
-            pathGroup = svg.append("g").attr("class", "piePaths")
-
-        pointerGroup = svg.select("g.pointers")
-        if pointerGroup.empty()
-            pointerGroup = svg.append("g").attr("class", "pointers")
-
-        labelGroup = svg.select("g.labels")
-        if labelGroup.empty()
-            labelGroup = svg.append("g").attr("class", "labels")
-
-        path = pathGroup.selectAll("path.pie")
-            .data(piedata)
-
-        path.enter().append("path")
-            .attr("class", "pie")
-            .attr("fill",(d, i) -> return color i)
-
-        #path.transition()
-        #    .duration(1500)
-        #    .attrTween("d", @pieTween);
-
-        #path.exit()
-        #    .transition()
-        #    .duration(300)
-        #    .attrTween("d", @removePieTween)
-        #    .remove();
-
-        path.transition()
-            .duration(1500)
-            #.attrTween("d", pieTween)
-            .attrTween("d", (d,i) ->
-                console.log("d",d)
-                console.log("i",i)
-                i = d3.interpolate({startAngle: 0, endAngle: 0}, {startAngle: d.startAngle, endAngle: d.endAngle})
-                return (t) ->
-                    b = i(t)
-                    return arc(b)
-            )
-
-        path.exit()
-            .transition()
-            .duration(300)
-            .attrTween("d", (d,i) ->
-                i = d3.interpolate({startAngle: d.startAngle,endAngle: d.endAngle},{startAngle: 2 * Math.PI,endAngle: 2 * Math.PI})
-                return (t) ->
-                    b = i(t)
-                    return arc(b)
-            )
-            .remove()
-
-        labels = labelGroup.selectAll("text")
-            .data(piedata.sort((p1,p2) -> return p1.startAngle - p2.startAngle))
-        labels.enter()
-            .append("text")
-            .attr("text-anchor", "middle")
-        labels.exit()
-            .remove()
-
-        labelLayout = d3.geom.quadtree()
-            .extent([[-width,-height], [width,height] ])
-            .x((d) -> return d.x)
-            .y((d) -> return d.y)
-            ([]) #create an empty quadtree to hold label positions
-
-        maxLabelWidth = 0
-        maxLabelHeight = 0
-
-        labels.text((d) ->
-            # Set the text *first*, so we can query the size
-            # of the label with .getBBox()
-            return d.value
-        )
-        .each((d, i) ->
-            # Move all calculations into the each function.
-            # Position values are stored in the data object
-            # so can be accessed later when drawing the line
-
-            # calculate the position of the center marker
-            a = (d.startAngle + d.endAngle) / 2
-
-            #trig functions adjusted to use the angle relative
-            #to the "12 o'clock" vector:
-            d.cx = Math.sin(a) * (radius - 75)
-            d.cy = -Math.cos(a) * (radius - 75)
-
-            # calculate the default position for the label,
-            #   so that the middle of the label is centered in the arc
-            bbox = this.getBBox()
-            #bbox.width and bbox.height will
-            #describe the size of the label text
-            labelRadius = radius - 20
-            d.x =  Math.sin(a) * (labelRadius)
-            d.l = d.x - bbox.width / 2 - 2
-            d.r = d.x + bbox.width / 2 + 2
-            d.y = -Math.cos(a) * (radius - 20)
-            d.b = d.oy = d.y + 5
-            d.t = d.y - bbox.height - 5
-
-            # check whether the default position
-            #   overlaps any other labels
-            conflicts = []
-            #console.log("labelLayout",labelLayout)
-            labelLayout.visit( (node, x1, y1, x2, y2) ->
-                #recurse down the tree, adding any overlapping
-                #node is the node in the quadtree,
-                #node.point is the value that we added to the tree
-                #x1,y1,x2,y2 are the bounds of the rectangle that
-                #this node covers
-
-                #1. left edge of node is to the right of right edge of label
-                #2. right edge of node is to the left of left edge of label
-                #3. top (minY) edge of node is greater than the bottom of label
-                #4. bottom (maxY) edge of node is less than the top of label
-                if  (x1 > d.r + maxLabelWidth/2) or (x2 < d.l - maxLabelWidth/2) or (y1 > d.b + maxLabelHeight/2) or (y2 < d.t - maxLabelHeight/2 )
-                    return true #don't bother visiting children or checking this node
-
-                p = node.point
-                v = false
-                h = false
-                if p #p is defined, i.e., there is a value stored in this node
-                    h =  ( ((p.l > d.l) and (p.l <= d.r)) or ((p.r > d.l) and (p.r <= d.r)) or ((p.l < d.l) and (p.r >=d.r) ) ) #horizontal conflict
-                    v =  ( ((p.t > d.t) and (p.t <= d.b)) or ((p.b > d.t) and (p.b <= d.b)) or ((p.t < d.t) and (p.b >=d.b) ) ) #vertical conflict
-                    if h and v
-                        conflicts.push(p) #add to conflict list
-            )
-
-            if conflicts.length
-                console.log(d, " conflicts with ", conflicts);
-                rightEdge = d3.max(conflicts, (d2) ->
-                    return d2.r
-                )
-
-                d.l = rightEdge
-                d.x = d.l + bbox.width / 2 + 5
-                d.r = d.l + bbox.width + 10
-
-            else console.log("no conflicts for ", d)
-
-            # add this label to the quadtree, so it will show up as a conflict
-            #   for future labels.
-            labelLayout.add( d )
-            maxLabelWidth = Math.max(maxLabelWidth, bbox.width+10)
-            maxLabelHeight = Math.max(maxLabelHeight, bbox.height+10)
-        )
-        .transition() #we can use transitions now!
-        .attr("x", (d) ->
-                    return d.x
-                )
-                .attr("y", (d) ->
-                    return d.y
-                )
-
-        pointers = pointerGroup.selectAll("path.pointer")
-            .data(piedata)
-        pointers.enter()
-            .append("path")
-            .attr("class", "pointer")
-            .style("fill", "none")
-            .style("stroke", "black")
-            .attr("marker-end", "url(#circ)")
-        pointers.exit().remove()
-
-        pointers.transition().attr("d", (d) ->
-            if d.cx > d.l
-                return "M" + (d.l+2) + "," + d.b + "L" + (d.r-2) + "," + d.b + " " + d.cx + "," + d.cy
-            else
-                return "M" + (d.r-2) + "," + d.b + "L" + (d.l+2) + "," + d.b + " " + d.cx + "," + d.cy
-
-        )
-
-  renderX5: (data) ->
-        #console.log("update pie", data);
-
-        if !data
-          data = @get("value")
-        if !data
-          return
-
-        piedata = [data]
-
-        $(@node).children(".title").text($(@node).attr("data-title"))
-        $(@node).children(".more-info").text($(@node).attr("data-moreinfo"))
-        $(@node).children(".updated-at").text(@get('updatedAtMessage'))
-
-        # Build pie
-
-        width = 750
-        height = 450
-        radiuso = 135 #outer radius
-        radiusi = 90 #inner radius
-        radius = Math.min(width, height) / 2 #palette radius min
-        labelRadius = 180
-
-        color = d3.scale.category20()
-
-        $(@node).children("svg").remove();
-
-        #svg = d3.select(@node).append("svg:svg")
-        #    .data([data])
-        #    .attr("width", width)
-        #    .attr("height", height)
-        #    .append("svg:g")
-        #    .attr("transform", "translate(" + width/2 + "," + height/2 + ")")
-        svg = d3.select(@node).append("svg")
-            .data(piedata)
-            .attr("width", width)
-            .attr("height", height)
-            .append("g")
-            .attr("transform", "translate(" + width/2 + "," + height/2 + ")")
-
-        pie = d3.layout.pie()
-            #.sort(null)
-            .value((d) -> d.value)
-
-        arc = d3.svg.arc()
-          .outerRadius(radiuso)
-          .innerRadius(radiusi)
-
-        arcs = svg.selectAll("g.slice")
-            .data(pie)
-            .enter().append("svg:g").attr("class", "slice")
-
-        arcs.append("svg:path").attr("fill", (d, i) -> color i)
-            .attr("fill-opacity", 0.4).attr("d", arc)
-
-        # end Build pie
-        #console.log("update pie", data);
-
+    ).transition().attr('x', (d) ->
+      d.x
+    ).attr 'y', (d) ->
+      d.y
+    pointers = pointerGroup.selectAll('path.pointer').data(@piedata)
+    pointers.enter().append('path').attr('class', 'pointer').style('fill', 'none').style('stroke', 'black').attr 'marker-end', 'url(#circ)'
+    pointers.exit().remove()
+    pointers.transition().attr 'd', (d) ->
+      if d.cx > d.l
+        'M' + d.l + 2 + ',' + d.b + 'L' + d.r - 2 + ',' + d.b + ' ' + d.cx + ',' + d.cy
+      else
+        'M' + d.r - 2 + ',' + d.b + 'L' + d.l + 2 + ',' + d.b + ' ' + d.cx + ',' + d.cy
+    @oldPieData = @piedata
+    return


Safari test

Safari test

--- extras/CactusFramer/static/app.coffee
+++ extras/CactusFramer/static/app.coffee
@@ -15,85 +15,23 @@

 layer.draggable.enabled = true
 layer.draggable.momentum = true
-# layer.draggable.bounce = true
-# layer.draggable.constraints = constraintLayer.size
-# layer.draggable.overdrag = true
-# layer.draggable.overdragScale = 0.1
+layer.draggable.bounce = true
+layer.draggable.constraints = constraintLayer.size
+layer.draggable.overdrag = true
+layer.draggable.overdragScale = 0.1
 # # layer.draggable.lockDirection = true

-# # layer.draggable.momentumOptions =
-# # 	friction:

-class ScrollComponent extends Layer
+# scroll = new ScrollComponent width:200, height:window.innerHeight
+# scroll.content.draggable.horizontal = false
+# h = 100

-	constructor: ->
-		super
+# for i in [0..300]
+# 	new Layer
+# 		y: i * h
+# 		width: scroll.width
+# 		height: h
+# 		superLayer: scroll.content
+# 		backgroundColor: Utils.randomColor(.5)

-		@content = new Layer
-			width: @width
-			height: @height
-			superLayer:@
-		@content.draggable.enabled = true
-		@content.draggable.momentum = true
-		@_updateContent()
-
-		@content.on "change:subLayers", @_updateContent
-
-		@scrollWheelSpeedMultiplier = 0.10
-
-		@on "mousewheel", @_onMouseWheel
-
-	_updateContent: =>
-
-		contentFrame = @content.contentFrame()
-		contentFrame.width  = @width  if contentFrame.width  < @width
-		contentFrame.height = @height if contentFrame.height < @height
-		@content.frame = contentFrame
-
-		@content.draggable.constraints =
-			x: -contentFrame.width  + @width
-			y: -contentFrame.height + @height
-			width: 	contentFrame.width  + contentFrame.width  - @width
-			height: contentFrame.height + contentFrame.height - @height
-
-	_onMouseWheel: (event) =>
-
-		@content.animateStop()
-
-		{minX, maxX, minY, maxY} = @content.draggable._calculateConstraints(@content.draggable.constraints)
-
-		point =
-			x: Utils.clamp(@content.x + (event.wheelDeltaX * @scrollWheelSpeedMultiplier), minX, maxX)
-			y: Utils.clamp(@content.y + (event.wheelDeltaY * @scrollWheelSpeedMultiplier), minY, maxY)
-
-		@content.point = point
-
-	@define "scrollPoint",
-		get: ->
-			point =
-				x: @content.x
-				y: @content.y
-		set: (point) ->
-			@content.animateStop()
-			@content.point = point
-
-	@define "scrollRect",
-		get: ->
-			rect = @point
-			rect.width = @width
-			rect.height = @height
-
-
-scroll = new ScrollComponent width:200, height:window.innerHeight
-scroll.content.draggable.horizontal = false
-h = 100
-
-for i in [0..300]
-	new Layer
-		y: i * h
-		width: scroll.width
-		height: h
-		superLayer: scroll.content
-		backgroundColor: Utils.randomColor(.5)
-


Terminal: when vm is created/removed notification is sent to owner

--- workers/social/lib/social/models/vm.coffee
+++ workers/social/lib/social/models/vm.coffee
@@ -419,6 +419,7 @@
                   JDomain.ensureDomainSettingsForVM {
                     account, vm, type, nickname, group: groupSlug, stack
                   }
+                  account.sendNotification "VMCreated"
                   if type is 'group'
                     @addVmUsers user, vm, group, ->
                       callback null, vm
@@ -598,6 +599,8 @@
     kallback = (subscription) =>
       @remove (err) =>
         return callback err  if err
+
+        account.sendNotification "VMRemoved"

         errs = []


Some fixes for mapp app

Some fixes for mapp app

--- src/public/scripts/app.map.coffee
+++ src/public/scripts/app.map.coffee
@@ -151,14 +151,14 @@

 			@node.onReceive
 				'player.died': ( id ) =>
-					if player = _(@players).find( ( p ) -> p.id = id )
+					if player = _(@players).find( ( p ) -> p.id is id )
 						player.die()
 						@players = _(@players).without(player)
 				'player.update': ( id, info, timestamp ) =>
-					if player = _(@players).find( ( p ) -> p.id = id )
+					if player = _(@players).find( ( p ) -> p.id is id )
 						player.applyInfo(info)
 					else
-						player = new Player(@)
+						player = new Player(@, id)
 						player.applyInfo(info)
 						@players.push(player)

@@ -201,7 +201,7 @@

 	class Player

-		constructor: ( @app ) ->
+		constructor: ( @app, @id ) ->
 			@targetPosition = new Three.Vector3()

 			geometry = new Three.SphereGeometry(2, 6, 8)


Test for real inputs.

Test for real inputs.

--- normr/tests/tests.coffee
+++ normr/tests/tests.coffee
@@ -6,4 +6,10 @@

     assert.ok ($blab.normr(A)-correct).norm2() < nm.epsilon

+test "real input", (assert) ->

+    A = [[0, 2], [3, 4]]
+    correct = [[0, 1], [0.6, 0.8]]
+    y = complex($blab.normr(A)-correct, A*0) # fudge for norm2
+    assert.ok y.norm2() < nm.epsilon
+


fix bootstraping

fix bootstraping

--- src/binding.coffee
+++ src/binding.coffee
@@ -639,7 +639,7 @@
             option =
                 skip_attr: ['al-app', 'al:app', 'data-al-app']

-            ctrlName = element.getAttribute('al-app') or element.getAttribute ('al:app') or element.getAttribute 'data-al-app'
+            ctrlName = element.getAttribute('al-app') or element.getAttribute('al:app') or element.getAttribute('data-al-app')
             if ctrlName
                 option.attachDirective =
                     'al-ctrl': ctrlName


Documentation

Documentation

--- src/bounded.coffee
+++ src/bounded.coffee
@@ -78,6 +78,23 @@
   The bounds method returns infomation about the location
   of the object and its dimensions with optional offsets.

+

+    player = CoreObject
+      x: 3
+      y: 6
+      width: 2
+      height: 2
+
+    player.include(Bounded)
+
+    player.bounds()
+    => {x: 3, y: 6, width: 2, height: 2}
+
+    player.bounds(7, 4)
+    => {x: 10, y: 10, width: 2, height: 2}
+
+

+
   @name bounds
   @methodOf Bounded#


Set default authors to an empty array

Set default authors to an empty array

--- src/scripts/models/content.coffee
+++ src/scripts/models/content.coffee
@@ -15,6 +15,7 @@
     defaults:
       title: 'Untitled'
       content: 'No content'
+      authors: []

     parse: (response, options) ->
       # jQuery can not build a jQuery object with <head> or <body> tags,
@@ -32,9 +33,7 @@
     defaults:
       title: 'Untitled Book'
       pages: 1
-      author:
-        name: 'Unknown'
-        email: '#'
+      authors: []
       currentPage: new CurrentPage()

     toJSON: () ->


Added --beep option to command, to warn user on compilation failure with --watch

--- src/command.coffee
+++ src/command.coffee
@@ -44,6 +44,7 @@
   ['-t', '--tokens',          'print out the tokens that the lexer/rewriter produce']
   ['-v', '--version',         'display the version number']
   ['-w', '--watch',           'watch scripts for changes and rerun commands']
+  [      '--beep',            'beep (print \\007) on compilation errors with --watch']
 ]

 # Top-level objects shared by all the functions.
@@ -136,7 +137,7 @@
   catch err
     CoffeeScript.emit 'failure', err, task
     return if CoffeeScript.listeners('failure').length
-    return printLine err.message if o.watch
+    return printLine err.message + (if opts.beep then '\007' else '') if o.watch
     printWarn err instanceof Error and err.stack or "ERROR: #{err}"
     process.exit 1


don't expose everything as a context function

--- src/context.coffee
+++ src/context.coffee
@@ -37,6 +37,19 @@
   handle_any_object = (object) ->
     @object object
     true
+
+  # TODO make this configurable
+  result_handlers =[
+    ignored,
+    handle_cmd,
+    handle_renderable,
+    handle_using_extension
+    handle_any_object
+  ]
+
+  display_object = (ctx, object) ->
+    for handler in result_handlers
+      return if handler.call ctx, object

   collect_extension_points = (context, extension_point) ->
@@ -133,15 +146,18 @@
     else
       renderable_component {renderable}

+  create_nested_renderable_context = (ctx) ->
+    component = component_list()
+    ctx.create_nested_context
+      renderable_list_builder: component
+
+  # creates a nested context, adds it to the renderable list, and applies the function to it
+  nested_item = (ctx, fn, args...) ->
+    nested_context = create_nested_renderable_context ctx
+    ctx.add_component nested_context.renderable_list_builder
+    nested_context.apply_to fn, args
+
   create_run_context = (extra_contexts) ->
-    result_handlers =[
-      ignored,
-      handle_cmd,
-      handle_renderable,
-      handle_using_extension
-      handle_any_object
-    ]
-
     asyncs = new Bacon.Bus

     run_context_prototype = _.extend {}, extra_contexts...,
@@ -201,7 +217,7 @@
         $div = $('<div/>')
         if contents?
           if _.isFunction contents
-            return @nested_item contents
+            return nested_item @, contents
           else
             $div.append contents
         @add_rendered $div
@@ -219,28 +235,13 @@
           o._lead_render = -> nested_context.apply_to fn
         o

-      create_nested_renderable_context: ->
-        component = component_list()
-        @create_nested_context
-          renderable_list_builder: component
-
       create_nested_context: (overrides) ->
         nested_context = _.extend create_new_run_context(@), overrides

       detached:  (fn, args) ->
-        nested_context = @create_nested_renderable_context()
+        nested_context = create_nested_renderable_context @
         nested_context.apply_to fn, args
         nested_context.renderable_list_builder
-
-      # creates a nested context, adds it to the renderable list, and applies the function to it
-      nested_item: (fn, args...) ->
-        nested_context = @create_nested_renderable_context()
-        @add_component nested_context.renderable_list_builder
-        nested_context.apply_to fn, args
-
-      display_object: (object) ->
-        for handler in result_handlers
-          return if handler.call @, object

       value: (value) -> _lead_context_fn_value: value

@@ -258,7 +259,7 @@
           else
             "#{ms} ms"

-        promise = @nested_item fn
+        promise = nested_item @, fn

         asyncs.push 1
         promise.finally =>
@@ -325,7 +326,7 @@
       previous_running_context_binding = running_context_binding
       running_context_binding = run_context
       result = fn.apply run_context
-      run_context.display_object result
+      display_object run_context, result
     finally
       running_context_binding = previous_running_context_binding


Pass --verbose flag to npm if set for apm install

`apm install` can now forward the `--verbose` flag to `npm`,
forming the `npm install --verbose` command under the hood.

This prints a lot of valuable information, which can be used
to debug what happens when users try to install packages
and dependencies.

(Prints the versions of node-gyp, Python, Visual Studio and Electron
that are being used, among other things.)

--- src/install.coffee
+++ src/install.coffee
@@ -69,6 +69,7 @@
     installArgs.push('--silent') if options.argv.silent
     installArgs.push('--quiet') if options.argv.quiet
     installArgs.push('--production') if options.argv.production
+    installArgs.push('--verbose') if options.argv.verbose

     if vsArgs = @getVisualStudioFlags()
       installArgs.push(vsArgs)


Pass pack into error message helper

Pass pack into error message helper

--- src/install.coffee
+++ src/install.coffee
@@ -167,11 +167,11 @@
           @logFailure()

         error = "#{stdout}\n#{stderr}"
-        error = @getGitErrorMessage() if error.indexOf('code ENOGIT') isnt -1
+        error = @getGitErrorMessage(pack) if error.indexOf('code ENOGIT') isnt -1

         callback(output)

-  getGitErrorMessage: ->
+  getGitErrorMessage: (pack) ->
     message = """
       Failed to install #{pack.name} because Git was not found.


hubot: allow multiple match arguments

hubot: allow multiple match arguments


fixed hear text

fixed hear text

--- scripts/TM-Github.coffee
+++ scripts/TM-Github.coffee
@@ -41,7 +41,7 @@
     request.post options, (error, response, data)->
       res.reply 'Request sent, take a look at https://ci.appveyor.com/project/DinisCruz/tm-4-0-build where a new build should have just started'
       res.reply 'In a bit, the compiled node_modules will be at the https://github.com/tm-build/TM_4_0_Windows repo'
-
+
   robot.respond /deploy to azure/i,(res) ->
     res.reply "About to trigger a deploy to azure..."
     options =
@@ -55,6 +55,10 @@
       res.reply 'Request sent, take a look at https://ci.appveyor.com/project/DinisCruz/tm-ci-deploy where a new build will have just started'
       res.reply 'In a bit, the deployed website will be at https://tm-qa-6.azurewebsites.net'

+  robot.hear /TM_4_0_Design.Dev(.*)commits/i, (res)->
+    res.reply 'New commit to TM_4_0_Design/Dev detected ...'
+    res.reply '...time to deploy to azure'
+
   robot.hear /show(.*)tm graph/i,(res) ->
     res.reply 'http://4.bp.blogspot.com/-7g82Ltxt7wI/VLqbd1CLOyI/AAAAAAAAKcc/kisGogPKcmo/s1600/Screen%2BShot%2B2014-11-02%2Bat%2B16.49.16.png'
     res.reply 'http://2.bp.blogspot.com/-PUi2erHHCKA/VLqbmSw69pI/AAAAAAAAKeY/fWA8nqSam8k/s1600/Screen%2BShot%2B2014-11-18%2Bat%2B10.57.13.png'


To CallBack

To CallBack

--- scripts/amazon-ec.coffee
+++ scripts/amazon-ec.coffee
@@ -31,7 +31,7 @@

     this.operationHelper = new OperationHelper(config)

-  search: (msg, query, isKindle, month, page) ->
+  search: (query, isKindle, month, page, callback, nothingCallBack) ->

     binding = if isKindle then 'kindle' else 'not kindle'

@@ -56,15 +56,19 @@
       items = baseResult.Item

       if totalResults is 0
-        msg.send "#{query}は最近リリースされてないよー"
+        nothingCallBack
         return

       for item in items
         baseItem = item.ItemAttributes[0]
-        msg.send "#{baseItem.Title[0]}が見つかったよー。¥n発売日は #{if isKindle then baseItem.ReleaseDate[0] else baseItem.PublicationDate[0]}だよー¥n#{item.DetailPageURL[0]}"
+        callback {
+          title: baseItem.Title[0],
+          releaseDate: if isKindle then baseItem.ReleaseDate[0] else baseItem.PublicationDate[0],
+          url: item.DetailPageURL[0]
+        }

       if page < totalPages and page < 10
-        setTimeout(this.search, 5000, msg, query,isKindle,month, page + 1)
+        setTimeout(this.search, 5000, query,isKindle,month, page + 1,callback, nothingCallBack)

     ).catch((err) ->
       console.log("error:", err)
@@ -76,7 +80,9 @@
 newReleaseSearch = (msg,query,isKindle) ->
   monthBeforeLast = moment().add(-2,'M').format('MM-YYYY')
   # 検索の実行
-  amazonSearch.search(msg, query, isKindle ,monthBeforeLast, 1)
+  amazonSearch.search(query, isKindle ,monthBeforeLast, 1,
+    ((item) -> msg.send "#{item.title}が見つかったよー。¥n発売日は #{item.releaseDate}だよー¥n#{item.url}"),
+    (() -> msg.send "#{query}は最近リリースされてないよー"))

 module.exports = (robot) ->


github-op: accept GitHub account with upper case.

--- scripts/github-op.coffee
+++ scripts/github-op.coffee
@@ -95,7 +95,7 @@
               if nowip && pull.title.indexOf("[WIP]") > -1
                 continue
               mentioned = []
-              pull.body.replace /\[ \]\s*(@[a-z0-9_-]+)/g, (match, nick) ->
+              pull.body.replace /\[ \]\s*(@[a-z0-9_-]+)/gi, (match, nick) ->
                 mentioned.push(nick)
                 match
               mentioned = mentioned.map (nick) ->


Added handling of multiple sketchClasses

--- scripts/reportTab.coffee
+++ scripts/reportTab.coffee
@@ -9,10 +9,17 @@

 class RecordSet

-  constructor: (@data, @tab) ->
+  constructor: (@data, @tab, @sketchClassId) ->

   toArray: () ->
-    _.map @data.value[0].features, (feature) ->
+    if @sketchClassId
+      data = _.find @data.value, (v) =>
+        v.features?[0]?.attributes?['SC_ID'] is @sketchClassId
+      unless data
+        throw "Could not find data for sketchClass #{@sketchClassId}"
+    else
+      data = @data.value[0]
+    _.map data.features, (feature) ->
       feature.attributes

   raw: (attr) ->
@@ -166,15 +173,10 @@
     _.filter results, (result) ->
       result.paramName not in ['ResultCode', 'ResultMsg']

-  recordSet: (dependency, paramName, sketchClassId) ->
+  recordSet: (dependency, paramName, sketchClassId=false) ->
     unless dependency in @dependencies
       throw new Error "Unknown dependency #{dependency}"
-    if sketchClassId
-      dep = _.find @allResults, (result) ->
-        result.get('name') is dependency and
-          result.get('sketchClass') is sketchClassId
-    else
-      dep = @reportResults.find (r) -> r.get('serviceName') is dependency
+    dep = @reportResults.find (r) -> r.get('serviceName') is dependency
     unless dep
       console.log @reportResults.models
       throw new Error "Could not find results for #{dependency}."
@@ -183,29 +185,7 @@
     unless param
       console.log dep.get('data').results
       throw new Error "Could not find param #{paramName} in #{dependency}"
-    rs = new RecordSet(param, @)
-    rs.sketchClass = dep.get('sketchClass')
-    rs
-
-  recordSets: (dependency, paramName) ->
-    unless dependency in @dependencies
-      throw new Error "Unknown dependency #{dependency}"
-    deps = _.filter @allResults, (result) -> result.get('name') is dependency
-    unless deps.length
-      console.log @allResults
-      throw new Error "Could not find results for #{dependency}."
-    params = []
-    for dep in deps
-      param = _.find dep.get('data').results, (param) ->
-        param.paramName is paramName
-      unless param
-        console.log dep.get('data').results
-        throw new Error "Could not find param #{paramName} in #{dependency}"
-      rs = new RecordSet(param, @)
-      rs.sketchClass = dep.get('sketchClass')
-      params.push rs
-    return params
-
+    new RecordSet(param, @, sketchClassId)

   enableTablePaging: () ->
     @$('[data-paging]').each () ->


nobody saw this

nobody saw this

--- lib/adapters/tesla/TeslaVehicleNode.coffee
+++ lib/adapters/tesla/TeslaVehicleNode.coffee
@@ -107,7 +107,7 @@
       P: 'parked'
       D: 'driving'
       R: 'driving'
-    }[status]
+    }[shiftStatus]
     if !status? then @log 'warn', "Unknown shift_status: " +
       JSON.stringify(shiftStatus)
     status


added new backend url

added new backend url

--- app/scripts/app.coffee
+++ app/scripts/app.coffee
@@ -3,7 +3,7 @@
 if window.location.host.match /angellist\.magnetar\.lxc.*/
   api_host = "angellist.magnetar.lxc:3000"
 else
-  api_host = "piratesinn-startups-api.yggdrasil.d27y.com"
+  api_host = "thepiratesinn-startups-api.ada.catalyst-zero.com"

 angular.module('configuration', [])
   .value("API", api_host)


activity: change chat sidebar to render with message link

--- client/activity/lib/components/channelmessageslist/index.coffee
+++ client/activity/lib/components/channelmessageslist/index.coffee
@@ -4,6 +4,8 @@
 immutable     = require 'immutable'
 formatContent = require 'app/util/formatContent'
 emojify       = require 'emojify.js'
+MessageLink   = require 'activity/components/publicchannelmessagelink'
+MessageBody   = require 'activity/components/common/messagebody'

 module.exports = class ChannelMessagesList extends React.Component

@@ -21,8 +23,8 @@

     @props.messages.slice(0, @props.maxCount).map (message) ->
       <li key={message.get 'id'} className="ChannelMessagesList-messageItem">
-        <Link
-          href="/Channels/Public/#{message.get 'slug'}"
+        <MessageLink
+          message={message}
           dangerouslySetInnerHTML={__html: helper.renderBody message}
         />
       </li>


Missed the class rename

Missed the class rename

--- resources/assets/lib/register-components.coffee
+++ resources/assets/lib/register-components.coffee
@@ -85,7 +85,7 @@
 core.reactTurbolinks.register 'chat-icon', true, (el) ->
   createElement ChatIcon, type: el.dataset.type

-core.reactTurbolinks.register 'notification-icon', true, (el) ->
+core.reactTurbolinks.register 'main-notification-icon', true, (el) ->
   createElement MainNotificationIcon, type: el.dataset.type

 core.reactTurbolinks.register 'notification-widget', true, (el) ->


properly fix the issues that hsy partly worked around for starting compute daemon

--- src/smc-hub/compute-server.coffee
+++ src/smc-hub/compute-server.coffee
@@ -19,12 +19,13 @@
 #
 ###############################################################################

-#################################################################
-#
-# compute-server -- a node.js server that provides a TCP server
-# that is used by the hubs to organize projects.
-#
-#################################################################
+require('coffee-cache')
+
+###
+
+compute-server -- runs on the compute nodes; is also imported as a module
+
+###

 CONF = '/projects/conf'
 SQLITE_FILE = undefined
@@ -1236,8 +1237,10 @@
 # Command line interface
 ###########################

-if not program._name
-    program._name = 'compute'  # so this module is possible to import with latest node version
+try
+    program.parse(process.argv)  # so that program._name gets set if being run as a script
+catch e
+    program._name = 'xxx'  # so this module is possible to import with latest node version, yet program.parse works below.

 program.usage('[start/stop/restart/status] [options]')
     .option('--pidfile [string]',        'store pid in this file', String, "#{CONF}/compute.pid")


Never update FileNew selector

Never update FileNew selector

--- src/smc-webapp/project_files.cjsx
+++ src/smc-webapp/project_files.cjsx
@@ -1408,6 +1408,10 @@

     getDefaultProps: ->
         file_search : ''
+
+    // Rendering doesnt rely on props...
+    shouldComponentUpdate: ->
+        false

     new_file_button_types : ['ipynb', 'sagews', 'tex', 'term',  'x11', 'rnw', 'rtex', 'rmd', 'md', 'tasks', 'course', 'sage', 'py', 'sage-chat']


improve share defn

improve share defn

--- src/smc-webapp/project_files.cjsx
+++ src/smc-webapp/project_files.cjsx
@@ -1564,7 +1564,6 @@

             Shared items are visible to anybody.
-            <Space/> You can stop sharing items at any time.

     render_share: ->
@@ -1579,6 +1578,11 @@
                 parent_is_public = true
         show_social_media = require('./customize').commercial and single_file_data.is_public

+            <Row>
+                <Col sm=8 style={color:'#666', fontSize:'12pt'}>
+                    {@render_share_defn()}
+                </Col>
+            </Row>
             <Row>
                 <Col sm=4 style={color:'#666'}>

Description

@@ -1624,11 +1628,6 @@
                     {<ButtonToolbar>
                         {@render_social_buttons(single_file)}
                     </ButtonToolbar> if show_social_media}
-                </Col>
-            </Row>
-            <Row>
-                <Col sm=8 style={color:'#666', fontSize:'12pt'}>
-                    {@render_share_defn()}
                 </Col>
             </Row>


Don't show any rows counts if none are available.

--- community/browser/lib/visualization/components/queryPlan.coffee
+++ community/browser/lib/visualization/components/queryPlan.coffee
@@ -321,14 +321,17 @@
                   x = d.source.x + operatorWidth / 2
                   y = d.source.y + d.source.height + operatorDetailHeight
                   source = d.source
-                  [key, caption] = if source.Rows?
-                    ['Rows', 'row']
+                  if source.Rows? or source.EstimatedRows?
+                    [key, caption] = if source.Rows?
+                      ['Rows', 'row']
+                    else
+                      ['EstimatedRows', 'estimated row']
+                    [
+                      { x: x, y: y, text: formatNumber(source[key]) + ' ', anchor: 'end' }
+                      { x: x, y: y, text: plural(caption, source[key]), anchor: 'start' }
+                    ]
                   else
-                    ['EstimatedRows', 'estimated row']
-                  [
-                    { x: x, y: y, text: formatNumber(source[key]) + ' ', anchor: 'end' }
-                    { x: x, y: y, text: plural(caption, source[key]), anchor: 'start' }
-                  ]
+                    []
                 selections: (enter, update) ->
                   enter
                   .append('text')


Add missed answer label in "single" summary

--- app/classifier/tasks/single.cjsx
+++ app/classifier/tasks/single.cjsx
@@ -12,7 +12,7 @@

{@props.task.question}

         {if @props.annotation.answer?
-          @props.task.answers[@props.annotation.answer]
+          @props.task.answers[@props.annotation.answer].label
         else
           'No answer'}


Don't store user lastOnline on client side.

--- app/client/js/users/store.coffee
+++ app/client/js/users/store.coffee
@@ -116,13 +116,14 @@
   fromJson: (json) ->
     @newSong = @instanceFromJson app.songs.Song, json.newSong
     @songs = @asArray(json.songs || []).map @instanceFromJson app.songs.Song
-    @user = json.user
+    @user = @authUserToAppUser json.user

   ###*
     @param {Object} authUser Auth user.
     @return {Object} user App user.
   ###
   authUserToAppUser: (authUser) ->
+    createdAt: authUser.createdAt
     displayName: authUser.displayName
     id: authUser.id
     provider: authUser.provider


Fixed invalid menu config

Fixed invalid menu config

--- menus/coffee-compile.cson
+++ menus/coffee-compile.cson
@@ -6,11 +6,12 @@

 'menu': [
   {
-    submenu: 'Packages'
+    label: 'Packages'
     submenu: [
       label: 'Coffee Compile'
       submenu: [
-        { label: 'Compile', label: 'coffee-compile:compile' }
+        label: 'Compile'
+        command: 'coffee-compile:compile'
       ]
     ]
   }


use the same App model on server and client

--- server/middleware.coffee
+++ server/middleware.coffee
@@ -1,6 +1,6 @@
 logger = require('./lib/logger')
 async = require('async')
-App = require('../shared/app')
+App = require(rendr.entryPath + '/app')

 # start request timer
 exports.startRequest = ->


Sidebar: Use NavigationWorkspaceItem class for sidebar workspace list controller.

--- client/Social/Activity/sidebar/sidebarmachinebox.coffee
+++ client/Social/Activity/sidebar/sidebarmachinebox.coffee
@@ -11,7 +11,8 @@
     @addSubView @machineItem = new NavigationMachineItem {}, machine

     listController = new KDListViewController
-      itemClass    : SidebarWorkspaceItem
+      itemClass    : NavigationWorkspaceItem
+      itemOptions  : { machine }

     for ws in data.workspaces
       listController.addItem ws


Activity: fixed private message pane height calculation

was assuming windowHeight should be its reference but since we started using privatemessages in collabarotion chat modal that reference should be itself or its parent.

--- client/Social/Activity/views/privatemessage/pane.coffee
+++ client/Social/Activity/views/privatemessage/pane.coffee
@@ -44,8 +44,8 @@

     headerHeight = @participantsView.getHeight()
     inputHeight  = @input.getHeight()
-    windowHeight = window.innerHeight
-    @scrollView.setHeight windowHeight - inputHeight - headerHeight
+    paneHeight   = @getHeight()
+    @scrollView.setHeight paneHeight - inputHeight - headerHeight
     @scrollDown()
     @_windowDidResize()


Activity: scroll issues part 3 fixed scrolling down while browsing

--- client/Social/Activity/views/privatemessage/pane.coffee
+++ client/Social/Activity/views/privatemessage/pane.coffee
@@ -120,8 +120,11 @@

     return  if message.account._id is KD.whoami()._id

+    wasAtBottom = @isPageAtBottom()
     item = @prependMessage message, @listController.getItemCount()
-    @scrollDown item
+
+    @scrollDown item  if wasAtBottom
+

   prependMessage: (message, index) ->


Remove log

Remove log

--- client/controllers/events/eventResolvedIncidents.coffee
+++ client/controllers/events/eventResolvedIncidents.coffee
@@ -162,7 +162,6 @@
     incidentType = @incidentType.get()
     differentialIncidents = @differentialIncidents.get()
     # Toggle the incident type if there are no matching incidents
-    console.log differentialIncidents
     if not _.findWhere(differentialIncidents, type: incidentType)
       if differentialIncidents.length >= 1
         if incidentType == 'cases'


cleaner metric adding

cleaner metric adding

--- oriDomi.coffee
+++ oriDomi.coffee
@@ -202,24 +202,19 @@
     @_elStyle = root.getComputedStyle @el

     # Save the original CSS display of the target. If `none`, assume `block`.
-    @displayStyle = elStyle.display
+    @displayStyle = @_elStyle.display
     if @displayStyle is 'none'
       @displayStyle = 'block'

-    # Calculate the element's total width by adding all horizontal dimensions.
-    @width = parseInt(elStyle.width, 10) +
-             parseInt(elStyle.paddingLeft, 10) +
-             parseInt(elStyle.paddingRight, 10) +
-             parseInt(elStyle.borderLeftWidth, 10) +
-             parseInt(elStyle.borderRightWidth, 10)
-
-    # Find the total height in a similar manner.
-    @height = parseInt(elStyle.height, 10) +
-              parseInt(elStyle.paddingTop, 10) +
-              parseInt(elStyle.paddingBottom, 10) +
-              parseInt(elStyle.borderTopWidth, 10) +
-              parseInt(elStyle.borderBottomWidth, 10)
-
+    # To calculate the full dimensions of the element, create arrays of relevant metric keys.
+    xMetrics = ['width', 'paddingLeft', 'paddingRight', 'borderLeftWidth', 'borderRightWidth']
+    yMetrics = ['height', 'paddingTop', 'paddingBottom', 'borderTopWidth', 'borderBottomWidth']
+
+    # Add up values for width and height using `_getMetric()`.
+    @width = 0
+    @height = 0
+    @width += @_getMetric metric for metric in xMetrics
+    @height += @_getMetric metric for metric in yMetrics

     # Calculate the panel width and panel height by dividing the total width and
     # height by the requested number of panels in each axis.
@@ -239,7 +234,7 @@
     # Create object literals to store panels and stages.
     @panels = {}
     @stages = {}
-    # Create a stage div to serve as a prototype.
+    # Create a stage div to serve as a prototype.
     stage = document.createElement 'div'
     # The stage should occupy the full width and height of the target element.
     stage.style.width = @width + 'px'
@@ -514,6 +509,11 @@
         @panels[@lastAnchor][0].addEventListener css.transitionEnd, onTransitionEnd, false

+  # `_getMetric` returns an integer of pixels for a style key.
+  _getMetric: (metric) ->
+    parseInt @_elStyle[metric], 10
+
+
   # `_transform` returns a `rotate3d` transform string based on the anchor and angle.
   _transform: (angle, fracture) ->
     switch @lastAnchor


simplify touch toggling

simplify touch toggling

--- oridomi.coffee
+++ oridomi.coffee
@@ -750,14 +750,12 @@
     if toggle
       return if @_touchEnabled
       listenFn = 'addEventListener'
-      @_touchEnabled = true
     else
       return unless @_touchEnabled
       listenFn = 'removeEventListener'
-      @_touchEnabled = false
-
+
+    @_touchEnabled = toggle
     @_setCursor()
-
     # Array of event type pairs.
     eventPairs = [['TouchStart', 'MouseDown'], ['TouchEnd', 'MouseUp'],
                   ['TouchMove', 'MouseMove'], ['TouchLeave', 'MouseLeave']]


fix generate posix name resource

fix generate posix name resource

--- package.coffee
+++ package.coffee
@@ -231,10 +231,10 @@
   devtools_page: "devTools.html"

   icons:
-    "16": "logo16.png"
-    "32": "logo32.png"
-    "48": "logo48.png"
-    "128": "logo128.png"
+    "16": "Logo16.png"
+    "32": "Logo32.png"
+    "48": "Logo48.png"
+    "128": "Logo128.png"

 manifestJson = JSON.stringify(manifest, null, '  ') + '\n'
 require('fs').writeFileSync "chromeExtension/manifest.json", manifestJson


Labeling fixes

Labeling fixes

--- bot.iced
+++ bot.iced
@@ -180,7 +180,7 @@
 				return bot.sendMessage chatterID, "You must register before you can add funds. Do this by sending '+register'."

 			balance = user.funds
-			bot.sendMessage chatterID, "You currently have #{balance} DOGE to tip with"
+			bot.sendMessage chatterID, "You currently have #{balance} DOGE"
 		when "+history"
 			await checkIfRegistered chatterID, defer(registered, user)
 			if registered is undefined
@@ -190,7 +190,7 @@
 			history = user.history
 			history = history.splice history.length - 10, history.length # Last 10 transactions in the history
 			message = "#{user.name}, here are your last 10 transactions:\n"
-			message += "Current balance: #{user.funds}\n"
+			message += "Current balance: #{user.funds} DOGE\n"
 			for item in history by -1 # Go backwards
 				switch item.type
 					when "add"
@@ -318,7 +318,27 @@
 			if err
 				console.error err
 				return bot.sendMessage chatterID, "The database ran into an error"
-
+		when "+help"
+			# Return a list of help
+			helpMessage = """
+			Hello there. I'm dogetippingbot.
+			New to Dogecoin? Visit the official page: http://www.dogecoin.com
+
+			Commands:
+				+register - Notify the bot that you exist. You will be added to the database.
+				+add <AMOUNT> doge - Sets up a Moolah request to add funds to your tipping account
+				+balance - Check the amount of DOGE in your account
+				+history - Display your current balance and a list of your 10 most recent transactions
+				+withdraw <ADDRESS> <AMOUNT|all> doge - Withdraw funds in your account to the specified address
+				+tip <STEAM NAME> <AMOUNT|all> doge - Send a Steam user a tip. Currently, this will fail if they haven't registered with the bot
+				+help - This help dialog
+
+			Find a bug? Want a feature? File an issue at https://github.com/petschekr/SteamDogeTipBot/issues or submit a pull request
+			Need anything else? Email me at <petschekr@gmail.com>
+			"""
+			bot.sendMessage chatterID, helpMessage
+		else
+			bot.sendMessage chatterID, "I couldn't understand your request. Send me '+help' for a list of available commands and functions."

 bot.on "friend", (steamID, Relationship) ->
 	if pendingInvites.indexOf(steamID) isnt -1


Use the database for adding funds

Use the database for adding funds

--- bot.iced
+++ bot.iced
@@ -7,7 +7,6 @@
 DogeTipGroupID = "103582791435182182"

 pendingInvites = []
-pendingPayments = []

 # Connect to the database
 await MongoClient.connect "mongodb://192.168.1.115:27017/dogebot", defer(err, db)
@@ -74,13 +73,16 @@
 				bot.sendMessage chatterID, "Reply '+add <AMOUNT> doge' to add dogecoins to your account"
 				bot.sendMessage chatterID, "Tip users with '+tip <STEAM NAME> <AMOUNT> doge'"
 		when "+add"
-			previousAdds = no
-			for payment in pendingPayments
-				if payment.steamID is chatterID
-					previousAdds = yes
-					break
-			if previousAdds
-				return bot.sendMessage chatterID, "You already have an +add request pending"
+			await checkIfRegistered chatterID, defer(registered)
+			if registered is undefined
+				return bot.sendMessage chatterID, "The database ran into an error"
+			unless registered
+				return bot.sendMessage chatterID, "You must register before you can add funds. Do this by sending '+register'."
+
+			await Users_collection.findOne {id: chatterID}, defer(err, user)
+			if user
+				for transaction in user.history
+					if transaction.status is "pending" then return bot.sendMessage chatterID, "You already have an +add request pending"

 			amount = message.split(" ")[1]
 			amount = parseInt amount, 10
@@ -93,7 +95,7 @@
 					"guid": moolah.guid
 					"currency": "DOGE"
 					"amount": amount
-					"product": "Add Tipping Money"
+					"product": "Add Funds"
 					"return": ""
 			requester options, (error, response, body) ->
 				unless !error and response.statusCode is 200
@@ -104,13 +106,18 @@
 				catch e
 					return bot.sendMessage chatterID, "Moolah returned invalid JSON"
 				bot.sendMessage chatterID, "Visit #{body.url} or send #{body.amount} #{body.currency} to #{body.address}"
-				# TEMPORARY; USE A IPN CALLBACK
+				# TEMPORARY; USE AN IPN CALLBACK
 				bot.sendMessage chatterID, "Send '+finishadd' to finish the adding process"
-				pendingPayments.push {
-					"steamID": chatterID
-					"amount": amount
+
+				fundTx =
+					"type": "add"
+					"amount": body.amount
+					"status": "pending"
 					"tx": body.tx
-				}
+				await Users_collection.update {id: chatterID}, {$push:{history: fundTx}}, {w:1}, defer(err)
+				if err
+					console.error err
+					bot.sendMessage chatterID, "The database ran into an error"
 		when "+finishadd"
 			for payment in pendingPayments
 				if payment.steamID is chatterID


For testing we need to require these libraries explicitly...

TODO:  Figure out brunch run vs testing requirement system.

--- app/javascripts/node-list.coffee
+++ app/javascripts/node-list.coffee
@@ -1,5 +1,5 @@
-# _ = require('lodash')
-# log = require('loglevel');
+_ = require('lodash')
+log = require('loglevel');

 # GraphPrimitive is the basis for the Node and Link classes.
 # They share a common ID generation mechanism mostly.


Fix waitIsOpen

Fix waitIsOpen

--- bootstrap/utils.coffee
+++ bootstrap/utils.coffee
@@ -72,8 +72,8 @@
         , options.timeout
         cmd = "while [[ ! `bash -c 'echo > /dev/tcp/#{server.host}/#{server.port}'` ]] && [[ ! -f /tmp/#{rand} ]]; do sleep 2; done;"
       else
-        # cmd = "while ! bash -c 'echo > /dev/tcp/#{server.host}/#{server.port}'; do sleep 2; done;"
-        cmd = "bash -c 'echo > /dev/tcp/#{server.host}/#{server.port}'"
+        cmd = "while ! bash -c 'echo > /dev/tcp/#{server.host}/#{server.port}'; do sleep 2; done;"
+        # cmd = "bash -c 'echo > /dev/tcp/#{server.host}/#{server.port}'"
       ctx.log "Wait for #{server.host} #{server.port}"
       ctx.emit 'wait', server.host, server.port
       ctx.execute


Convert standup to hash

Convert standup to hash

--- scripts/standup.coffee
+++ scripts/standup.coffee
@@ -20,22 +20,25 @@
 # Author:
 #   <github username of the original script author>
 module.exports = (robot) ->
-  robot.brain.set 'standup', null
+  robot.brain.set 'standup', {}

   robot.hear /\/standup$/i, (msg) ->
     standup = robot.brain.get('standup')
-    if !standup
+    if Object.keys(standup).length == 0
       msg.send "No standup recorded yet."
     else
-      msg.send standup
+      msg.send standup[msg.message.user.name]

   robot.hear /\/standup (.*)$/i, (msg) ->
-    status = msg.message.user.name + ": " + msg.match[1] + "\n"
+    status = msg.message.room + ": " + msg.match[1] + "\n"
     standup = robot.brain.get('standup')
-    if !standup
-      standup = status
-    else
-      standup += status
-
+    standup[msg.message.user.name] = status
     robot.brain.set 'standup', standup
     msg.send "Standup recorded."
+
+# next - turn standup into hash
+#iterate across standup hash for status printout
+#multi-dimensional - user and room as keys
+# if user & room has already reported, replace status. otherwise add
+# command to clear room standup
+# command to get full standups list


Add standup beta

Add standup beta

--- scripts/standup.coffee
+++ scripts/standup.coffee
@@ -24,20 +24,18 @@

   robot.hear /\/standup$/i, (msg) ->
     standup = robot.brain.get('standup')
-    if Object.keys(standup).length == 0
-      msg.send "No standup recorded yet."
+    if !standup[msg.message.room]
+      msg.send "No standup recorded yet in this room."
     else
-      for user,status of standup
-        msg.send user + " - " + standup[user]
+      for user,status of standup[msg.message.room]
+        msg.send user + " - " + status

   robot.hear /\/standup (.*)$/i, (msg) ->
-    status = msg.message.room + ": " + msg.match[1] + "\n"
+    status = msg.match[1] + "\n"
     standup = robot.brain.get('standup')
-    standup[msg.message.user.name] = status
+    standup[msg.message.room] = {
+      "#{msg.message.user.name}": status
+    }
     robot.brain.set 'standup', standup
     msg.send "Standup recorded."

-#multi-dimensional - user and room as keys
-# if user & room has already reported, replace status. otherwise add
-# command to clear room standup
-# command to get full standups list


Got rid of crazy way of restarting loadinglooper trier things, and added a local vs global based system. Hacky but worky is shiny.

--- filemanager/static/script/thumbloader.coffee
+++ filemanager/static/script/thumbloader.coffee
@@ -1,32 +1,20 @@

 class loadinglooper
-    alive: false
-    reserect: false
-    beginify: () ->
-        if ! this.alive
-            this.alive=true
-            this.goloopy()
-        else
-            this.alive=false
-            this.reserect=true
-    dying: () ->
-       if this.reserect
-          this.reserect = false
-          this.alive = true
-          this.goloopy()
-
+    loopID: 0
+
     goloopy: () ->
+        myID = thumbloader.loadinglooper.loopID
         trier.attempt({
             until: () ->
-                return ! thumbloader.loadinglooper.alive
+                console.log "HI"
+                return ! (myID==thumbloader.loadinglooper.loopID and thumbloader.alive())
             ,
             action: () ->
                 console.log "The Action is done!!! "
+                console.log "My id is #{myID}"
+                console.log "loop id is #{thumbloader.loadinglooper.loopID}"
                 thumbloader.comparifier()
-            ,
-            pass: () ->
-                thumbloader.loadinglooper.dying()
             ,
             interval: -1000,
             limit: -1
@@ -59,7 +47,8 @@

     register: (pk, gallery) ->
         this.datalist.push([pk,gallery])
-        this.loadinglooper.beginify()
+        this.loadinglooper.loopID++
+        this.loadinglooper.goloopy()
         console.log this.datalist

     comparifier: () ->


Simplify component

Simplify component

Not sure what those arrays are doing there.

--- resources/assets/coffee/react/modding-profile/events.coffee
+++ resources/assets/coffee/react/modding-profile/events.coffee
@@ -15,18 +15,14 @@
           div className: 'modding-profile-list__empty', osu.trans('users.show.extra.none')
         else
           div className: 'beatmapset-events beatmapset-events--profile',
-            [
-              el DiscussionEvents,
-                events: @props.events
-                key: 'events'
-                users: @props.users
+            el DiscussionEvents,
+              events: @props.events
+              users: @props.users

-              a
-                className: 'modding-profile-list__show-more'
-                key: 'show-more'
-                href: laroute.route('beatmapsets.events.index', user: @props.user.id),
-                osu.trans('users.show.extra.events.show_more')
-            ]
+            a
+              className: 'modding-profile-list__show-more'
+              href: laroute.route('beatmapsets.events.index', user: @props.user.id),
+              osu.trans('users.show.extra.events.show_more')

   typeForTranslation: (event) =>


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -109,7 +109,7 @@
   player.meter
     borderColor: 'brown'
     color: 'pink'
-    font"
+    font: ''
     name: 'health'
     position:
       x: 5


updated source

updated source

--- src/ng-iban.coffee
+++ src/ng-iban.coffee
@@ -72,11 +72,16 @@
     restrict: 'A'
     require: 'ngModel'
     link: (scope, elem, attrs, ctrl) ->
+      parseIban = (value) ->
+        if value? then value.toUpperCase().replace /\s/g, '' else undefined
+
       isValidIban = (value) ->
+        return true if not value?
+
         A = 'A'.charCodeAt 0
         Z = 'Z'.charCodeAt 0

-        iban = value.toUpperCase().replace /\s/g, ''
+        iban = parseIban(value)
         return false if attrs.ngIban isnt '' and (not !!(attrs.ngIban of countries) or not countries[attrs.ngIban].test iban)

         iban = iban.substr(4) + iban.substr 0, 4
@@ -93,10 +98,10 @@
         parseInt(remainder, 10) %% 97 is 1

       ctrl.$parsers.unshift (value) ->
-        valid = if value? then isValidIban value else true
+        valid = isValidIban value
         ctrl.$setValidity 'iban', valid
         if valid
-          parsed = if value? then value.toUpperCase().replace /\s/g, '' else undefined
+          parsed = parseIban value
           if parsed isnt value
             ctrl.$setViewValue parsed
             ctrl.$render()
@@ -104,6 +109,11 @@
         else undefined

       ctrl.$formatters.unshift (value) ->
-        valid = if value? then isValidIban value else true
+        valid = isValidIban value
         ctrl.$setValidity 'iban', valid
-        value
+        if valid
+          parsed = parseIban value
+          if parsed isnt value
+            scope[attrs.ngModel] = parsed
+          parsed
+        else value