start working on the ability to post to bangarang

--- src/hubot-bangarang-annotations.coffee
+++ src/hubot-bangarang-annotations.coffee
@@ -31,9 +31,45 @@

   msg.reply buf

+make_url = () ->
+  host = nconf.get("HUBOT_BANGARANG_ANNOTATIONS_HOST")
+  port = nconf.get("HUBOT_BANGARANG_ANNOTATIONS_PORT")
+  protocol = nconf.get("HUBOT_BANGARANG_ANNOTATIONS_PROTOCOL")
+
+  url = "#{protocol}://#{host}:#{port}/api"
+
+probably_unique_id = () ->
+  id = Math.random().toString(36).slice(2)
+
+timestamp = () ->
+  new Date().getTime()
+
+make_incident = (text) ->
+  id = probably_unique_id()
+  data =
+    event: "foo"
+    time: "#{timestamp}"
+    id: "#{id}",
+    active: true
+    escalation: "production-info"
+    description: "#{text}"
+    status: 0
+    metric: 0
+    tags: null
+
+  msg =
+    "#{id}": data
+
+create_annotation = (msg deployment, text) ->
+  url = make_url()
+  data = make_incident()
+  robot.http(url).header('Content-Type', 'application/json').post(data) (err, res, body) ->
+      if err
+        msg.reply "Could not create annotation #{err}"
+
 deployment_event = (msg, deployment) ->
   text = msg.message.text
-  msg.reply "#{deployment}: #{text}"
+  msg.reply "Adding annotation to #{deployment}"

 module.exports = (robot) ->


environments: Show a notification for guests

"this feature requires registration"

--- client/app/MainApp/sidebar/sidebarview.coffee
+++ client/app/MainApp/sidebar/sidebarview.coffee
@@ -127,7 +127,11 @@
       iconOnly  : yes
       iconClass : "cog"
       cssClass  : "clean-gray open-environment"
-      callback  :-> KD.getSingleton("appManager").open "Environments"
+      callback  :->
+        if KD.whoami().type is 'unregistered'
+          new KDNotificationView title: "This feature requires registration"
+        else
+          KD.getSingleton("appManager").open "Environments"

     @environmentsRefreshButton = new KDButtonView
       title     : "Refresh"


collaboration: controller add ending state for host

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -92,12 +92,6 @@
     return "#{hostName}.#{id}"

-  stopChatSession: ->
-
-    @chat.emit 'CollaborationEnded'
-    @chat = null
-
-
   whenRealtimeReady: (callback) ->

     if @rtm?.isReady
@@ -661,6 +655,26 @@

     @chat.emit 'CollaborationStarted'
     @statusBar.emit 'CollaborationStarted'
+
+
+  onCollaborationEnding: ->
+
+    @chat.settingsPane.endSession.disable()
+
+    sharedSuccessFn = =>
+      @chat.emit 'CollaborationEnded'
+      @chat = null
+      @modal?.destroy()
+
+    if @amIHost
+      @endCollaborationForHost
+        success: =>
+          # TODO: move this into its own method.
+          # @broadcastMessage { type: 'SessionEnded' }
+          @statusBar.emit 'CollaborationEnded'
+          sharedSuccessFn()
+          @cleanupCollaboration()
+          # kd.utils.defer @bound 'cleanupCollaboration'

   endCollaborationForHost: (callbacks) ->


Lookup for description property of error argument

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -961,7 +961,7 @@
     format = \
       switch typeof err
         when 'string' then err
-        when 'object' then err.message
+        when 'object' then err.message or err.description
         else args.join ' '

     argIndex = 0


included ~&%+ in the list of prefixes - ideally we should be pulling these from the 005 message

--- src/irc/server_response_handler.coffee
+++ src/irc/server_response_handler.coffee
@@ -28,7 +28,7 @@
       newNames = []
       for n in names.split(/\x20/)
         # TODO: read the prefixes and modes that they imply out of the 005 message
-        n = n.replace /^[@+]/, ''
+        n = n.replace /^[~&@%+]/, ''
         if n
           nameList[irc.util.normaliseNick n] = n
           newNames.push n


Add an integration test for a queries that returns lots of rows.

--- test/integration/connection-test.coffee
+++ test/integration/connection-test.coffee
@@ -24,7 +24,7 @@
     #console.log(message)
   )

-exports.execSql = (test) ->
+exports.execSimpleSql = (test) ->
   test.expect(12)

   connection = new Connection(config.server, config.userName, config.password, config.options, (err, loggedIn) ->
@@ -53,6 +53,34 @@
     test.strictEqual(byName.C1.value, 8)
     test.strictEqual(byName.C2.value, 'abc')
     test.strictEqual(byName.C3.value, 'def')
+  )
+
+  connection.on('debug', (message) ->
+    #console.log(message)
+  )
+
+exports.execSqlWithLotsOfRowsReturned = (test) ->
+  numberOfRows = 1000
+
+  test.expect(numberOfRows + 5)
+
+  connection = new Connection(config.server, config.userName, config.password, config.options, (err, loggedIn) ->
+    test.ok(!err)
+    test.ok(loggedIn)
+
+    connection.execSql("select top #{numberOfRows} object_id, name from sys.all_columns", (err, rowCount) ->
+      test.ok(!err)
+      test.strictEqual(rowCount, numberOfRows)
+      test.done()
+    )
+  )
+
+  connection.on('columnMetadata', (columnsMetadata) ->
+    test.strictEqual(columnsMetadata.length, 2)
+  )
+
+  connection.on('row', (columns) ->
+    test.strictEqual(columns.length, 2)
   )

   connection.on('debug', (message) ->


dialog 修复dimmer 覆盖弹出层的问题

dialog 修复dimmer 覆盖弹出层的问题

--- src/widget/layout/dialog.coffee
+++ src/widget/layout/dialog.coffee
@@ -176,10 +176,8 @@
 			})
 			if @_dimmerClose
 				$(_dimmerDom).on("click", ()=> @hide())
-			#			container = @_context or @_dom.parentNode
-			document.body.appendChild(_dimmerDom)
+			@_dom.parentNode.appendChild(_dimmerDom)
 			@_doms.modalLayer = _dimmerDom
-
 		$(_dimmerDom).css({
 			opacity: @get("modalOpacity")
 			zIndex: cola.floatWidget.zIndex()


Added new commands to help pane

Added new commands to help pane

--- background_scripts/commands.coffee
+++ background_scripts/commands.coffee
@@ -116,10 +116,6 @@
     , "LinkHints.activateModeWithQueue"
     , "goPrevious"
     , "goNext"
-    , "moveTabStart"
-    , "moveTabRight"
-    , "moveTabLeft"
-    , "moveTabEnd"
   ]

 defaultKeyMappings =
@@ -243,8 +239,8 @@
   goUp: ["Go up the URL hierarchy", { passCountToFunction: true }]

   # Manipulating tabs
-  moveTabEnd: ["Move current to the end", { background: true }]
-  moveTabStart: ["Move current to the start", { background: true }]
+  moveTabEnd: ["Move current tab to the end", { background: true }]
+  moveTabStart: ["Move current tab to the start", { background: true }]
   moveTabLeft: ["Move current tab to the right", { background: true }]
   moveTabRight: ["Move current tab to the left", { background: true }]
   nextTab: ["Go one tab right", { background: true }]


Update where the menu is removed in destruction.

Signed-off-by: Michael Diolosa <1fe9c163c3612846d8518a0042166a6cea333eed@gmail.com>

--- src/views.coffee
+++ src/views.coffee
@@ -403,9 +403,8 @@
     @filterFind(@_menu, ".#{@options.prevActionCssClass}").off('click')
     @filterFind(@_menu, ".#{@options.listItemActionCssClass}").off 'click'

+    @_menu.remove()
     @_controller.destroy()
-
-    @_menu.remove()

     @callbacks = null
     @templates = null


Wait for inquiry sync before showing success/error

--- components/inquiry_questionnaire/index.coffee
+++ components/inquiry_questionnaire/index.coffee
@@ -5,6 +5,20 @@
 FlashMessage::template = -> "#{@message}"
 InquiryQuestionnaireView = require './view.coffee'
 analytics = require './analytics.coffee'
+
+closeWithError = (modal) ->
+  modal.close ->
+    new FlashMessage
+      message: 'There has been an error. Click here to contact support@artsy.net'
+      href: 'mailto:support@artsy.net'
+      autoclose: false
+
+closeWithSuccess = (modal) ->
+  modal.close ->
+    new FlashMessage message: '
+      Your inquiry has been sent.

+      Thank you for completing your profile.
+    '

 module.exports = (options = {}) ->
   { user, inquiry } = options
@@ -32,11 +46,7 @@
         user.related().collectorProfile.findOrCreate()
       .then done
       .fail ->
-        modal.close ->
-          new FlashMessage
-            message: 'There has been an error. Click here to contact support@artsy.net'
-            href: 'mailto:support@artsy.net'
-            autoclose: false
+        closeWithError modal
       .done()

   # Abort by clicking 'nevermind'
@@ -46,14 +56,10 @@
   # End of complete flow
   questionnaire.state.on 'done', ->
     # Send the inquiry
-    inquiry.save()
-
-    # Simultaneously close the modal and
-    # display a success message, regardless
-    modal.close ->
-      new FlashMessage message: '
-        Your inquiry has been sent.

-        Thank you for completing your profile.
-      '
+    inquiry.save {},
+      error: ->
+        closeWithError modal
+      success: ->
+        closeWithSuccess modal

   modal


refactored firstYearOfDecade method

refactored firstYearOfDecade method

--- src/scripts/daterangepicker/util/moment-util.coffee
+++ src/scripts/daterangepicker/util/moment-util.coffee
@@ -19,11 +19,10 @@
       })

   @firstYearOfDecade: (date) ->
-    # tz = @parent.tz()
-    tz = 'UTC'
+    timeZone = date.tz()
     # we use current year here so that it's always in the middle of the calendar
-    currentYear = moment().tz(tz).year()
+    currentYear = moment().tz(timeZone).year()
     firstYear = currentYear - 4
     offset = Math.floor((date.year() - firstYear) / 9)
     year = firstYear + offset * 9
-    moment.tz([year], tz)
+    moment.tz([year], timeZone)


Edited gameState.coffee via GitHub

Edited gameState.coffee via GitHub

--- gameState.coffee
+++ gameState.coffee
@@ -404,8 +404,9 @@
     allCards = this.basicSupply.concat(tableau)
     supply = {}
     for card in allCards
-      card = c[card] ? card
-      supply[card] = card.startingSupply(this)
+      if c[card].startingSupply(this) > 0
+        card = c[card] ? card
+        supply[card] = card.startingSupply(this)
     supply

   #### Informational methods


Fix spelling error, closes #587

Fix spelling error, closes #587

--- app/talk/discussion-new-form.cjsx
+++ app/talk/discussion-new-form.cjsx
@@ -67,7 +67,7 @@
   render: ->

-
Create a disussion +

+
Create a discussion +

         {if not @props.boardId
           <PromiseRenderer promise={@props.subject.get('project')}>{(project) =>
             <PromiseRenderer promise={talkClient.type('boards').get(section: projectSection(project))}>{(boards) =>


remove paperclip file

remove paperclip file

--- src/views/base/decor/paperclip.coffee
+++ src/views/base/decor/paperclip.coffee
@@ -1,62 +0,0 @@
-paperclip = require "paperclip"
-type      = require "type-component"
-
-class PaperclipViewDecorator
-
-  ###
-  ###
-
-  constructor: (@view) ->
-    @view.once "render", @render
-    @view.once "remove", @remove
-    @view._define "paper"
-    @view.bind("paper").to(@_onTemplateChange).now()
-
-  ###
-  ###
-
-  _onTemplateChange: (template) =>
-
-    if type(template) isnt "function"
-      throw new Error "paper template must be a function for view \"#{@view.constructor.name}\""
-
-    @template = paperclip.template template
-
-    if @_rendered
-      @cleanup true
-      @render()
-
-
-  ###
-  ###
-
-  render: () =>
-    @_rendered = true
-    @content = undefined
-    return unless @template
-    @content = @template.bind @view
-    @content.section.show()
-    @view.section.append @content.section.toFragment()
-
-  ###
-  ###
-
-  remove: () =>
-    @cleanup()
-
-  ###
-  ###
-
-  cleanup: (hide) =>
-    @content?.unbind()
-    if hide
-      @content?.section.hide()
-
-  ###
-  ###
-
-  @getOptions : (view) -> view.__isView
-  @decorate   : (view, options) -> new PaperclipViewDecorator view, options
-
-
-module.exports = PaperclipViewDecorator


Enable analytics tracking on request quote view

--- app/views/RequestQuoteView.coffee
+++ app/views/RequestQuoteView.coffee
@@ -43,6 +43,8 @@
     if @trialRequests.size()
       @trialRequest = @trialRequests.first()
     me.setRole 'teacher'
+    if @trialRequest and @trialRequest.get('status') isnt 'submitted' and @trialRequest.get('status') isnt 'approved'
+      window.tracker?.trackEvent 'View Trial Request', category: 'Teachers', label: 'View Trial Request', ['Mixpanel']
     super()

   onSubmitForm: (e) ->
@@ -80,6 +82,7 @@

   onTrialRequestSubmit: ->
     @$('form, #form-submit-success').toggleClass('hide')
+    window.tracker?.trackEvent 'Submit Trial Request', category: 'Teachers', label: 'Trial Request', ['Mixpanel']

   onClickLoginButton: ->
     modal = new AuthModal({


Disabled frustum culling because of a bug

--- src/world.coffee
+++ src/world.coffee
@@ -163,7 +163,9 @@
       if !vbo
         continue
       [vec1, vec2] = makeBlockAABB x, y, z, CHUNK_SIZE
-      if frustum.testAABB(vec1, vec2) >= 0
+
+      # XXX: frustum culling is broken.  Check why
+      if true || frustum.testAABB(vec1, vec2) >= 0
         distance = vec3.subtract vec1, cameraPos
         rv.push vbo: vbo, distance: vec3.length(distance)


Update /clients/list look and alphabetize list

--- views/jsx/client/list.cjsx
+++ views/jsx/client/list.cjsx
@@ -24,13 +24,23 @@
     return

   render: ->
-

+

       {
         if @state.clients? and @state.clients.length > 0
-          @state.clients.map (client) ->
-            <ClientListItem
-              client={client}
-            />
+

+            {
+              @state.clients.sort (a, b) ->
+                if a.lastName  b.lastName
+                  return 1
+                return 0
+              @state.clients.map (client) ->
+                <ClientListItem
+                  client={client}
+                />
+            }
+

       }

@@ -41,28 +51,28 @@
     return state

   render: ->
-

-
All Clients

-

-

-

-

-

+

+

+

+

             {@props.client.firstName}
             {
               if @props.client.middleName?
-                " #{@props.client.middleName}."
+                " #{@props.client.middleName}"
             }
             {" #{@props.client.lastName}"}
-

-

-            View Details
-             Edit
-             Refer
-

+

+

+            {@props.client.phone}
+

-

-

+

+          View Details
+           Edit
+          Refer
+

+

+

 @Teresa.client.list =


Fix NM_CONNECTIVITY constants

Fix NM_CONNECTIVITY constants

Change-type: patch
Signed-off-by: Joseph Roberts <16a9a54ddf4259952e3c118c763138e83693d7fd@resin.io>

--- src/networkManager.coffee
+++ src/networkManager.coffee
@@ -16,8 +16,8 @@

 NM_STATE_CONNECTED_GLOBAL = 70
 NM_DEVICE_TYPE_WIFI = 2
-NM_CONNECTIVITY_LIMITED = 4
-NM_CONNECTIVITY_FULL = 5
+NM_CONNECTIVITY_LIMITED = 3
+NM_CONNECTIVITY_FULL = 4

 exports.start = ->
 	systemd.start('NetworkManager.service')


hubot: unbreak "with" keyword

hubot: unbreak "with" keyword

Previous commit did break "with" keyword. Ensure non-greedy repeat
operator to avoid that.


Apply review changes

Apply review changes

--- EndpointPuller.coffee
+++ EndpointPuller.coffee
@@ -29,7 +29,8 @@
     try
       log.enter('pull', arguments)
       delete @pullTimer if @pullTimer?
-      @httpOptions.params = @_buildParams()
+      #TODO clone @httpOptions
+      @httpOptions.params = @_getUrlParams()
       result = HTTP.get @endpoint, @httpOptions
       if @httpOptions.headers['If-Modified-Since']?
         # No new events related to the user
@@ -65,9 +66,9 @@
     finally
       log.return()

-  _buildParams: ()=>
+  _getUrlParams: ()=>
     try
-      log.fineEnter("_buildParams")
+      log.fineEnter("_getUrlParams")
       # Preserve old httpOptions params
       params = _.clone(@httpOptions.params || {})
       # Orverwrite with pullOptions.getUrlParams()


use project for adding nav links

use project for adding nav links

--- overrides.coffee
+++ overrides.coffee
@@ -28,6 +28,9 @@
 project.header.el.append("<meta name='viewport' content='width=600, user-scalable=no'>
                           <link rel='shortcut icon' href='favicon.ico' type='image/x-icon'>")

+project.header.addNavLink "http://talk.floatingforests.org", translate('site.talkLink')
+project.header.addNavLink "http://blog.floatingforests.org", translate('site.blogLink')
+
 footer.el.appendTo $("

").insertAfter(".stack-of-pages")

 ClassifyMetadata.init()
@@ -40,12 +43,6 @@
   account: 'UA-1224199-59'
   domain: 'floatingforests.org'

-addDirectNavLink = (name, url) ->
-  $(".readymade-site-links")
-    .append "#{name}"
-
-addDirectNavLink translate('site.talkLink'), "http://talk.floatingforests.org"
-addDirectNavLink translate('site.blogLink'), "http://blog.floatingforests.org/"

 $(".readymade-call-to-action").html translate 'site.callToAction'


Remove obsolete script

Remove obsolete script

Signed-off-by: Sonmez Kartal <d2d15c3d37396a5f2a09f8b42cf5f27d43a3fbde@koding.com>

--- deployment/envvar.coffee
+++ deployment/envvar.coffee
@@ -1,8 +0,0 @@
-traverse  = require 'traverse'
-
-module.exports.create = (KONFIG) ->
-  conf = {}
-  travis = traverse(KONFIG)
-  travis.paths().forEach (path) -> conf["KONFIG_#{path.join("_")}".toUpperCase()] = travis.get(path) unless typeof travis.get(path) is 'object'
-  conf.KITE_HOME = KONFIG.kiteHome  if KONFIG.kiteHome
-  return conf


fixed i18n code

fixed i18n code


(fix) more attempts at cross-protocol login

--- assets/js/services/visitor.coffee
+++ assets/js/services/visitor.coffee
@@ -15,7 +15,7 @@
       @session = {}
       @user = {}
       @logged_in = false
-
+
       @applySessionData(_plunker.session) if _plunker?.session

       $.cookie "plnk_session", @session.id,
@@ -23,10 +23,10 @@
         path: "/"

       self = @
-
+
       $rootScope.$watch ( -> self.user?.id ), (user) ->
         self.logged_in = !!user
-
+
       _handleOAuthSuccess = (auth) -> $rootScope.$apply ->
         console.log "AUTH", auth
         self.request = $http.post(self.session.user_url, { service: auth.service, token: auth.token })
@@ -36,30 +36,30 @@
         , (error) ->
           notifier.error "Login error", arguments...
           delete self.request
-
+
       _handleOAuthError = (error) -> $rootScope.$apply ->
         console.error "AUTH", error
         notifier.error "Authentication error", self, arguments...
-
+
       $window.addEventListener 'message', (message) =>
         if (typeof message.data == 'string' and matches = message.data.match(/^plunker\.auth\.(.*)/))
           try
             origin = (message.origin || message.originalEvent.origin).replace(/^https?:/, '')
-
+
             if origin != window.location.origin.replace(/^https?:/, '')
               return _handleOAuthError('Invalid OAuth source origin')
-
+
             payload = JSON.parse(matches[1]);
-
+
             if payload.auth then _handleOAuthSuccess(payload.auth)
             else if payload.error then _handleOAuthError(payload.error)
             else _handleOAuthError('Invalid OAuth payload.')
           catch error
             _handleOAuthError('Unable to parse OAuth payload.')
-
+
     isMember: -> !!@logged_in
     isLoading: -> !!@request or !!@loginWindow
-
+
     applySessionData: (data) ->
       angular.copy(data.user or {}, @user)
       angular.copy(data, @session)
@@ -70,22 +70,24 @@
       top = 0
       if (screenHeight > height)
           top = Math.round((screenHeight / 2) - (height / 2))
-
-      @loginWindow = window.open "#{url.www}/auth/github", "Sign in with Github", """
+
+      authHost = 'https:' + url.www.replace(/^https?:/, '')
+
+      @loginWindow = window.open "#{authHost}/auth/github", "Sign in with Github", """
         left=#{left},top=#{top},width=#{width},height=#{height},personalbar=0,toolbar=0,scrollbars=1,resizable=1
       """

       if @loginWindow then @loginWindow.focus()
-
+
     logout: ->
       self = @
-
+
       request = $http
         url: @session.user_url
         method: "DELETE"
-
+
       request.then (response) ->
         self.applySessionData(response.data)
       , (error) ->
         notifier.error "Error logging out", arguments...
-]+]


Fix test for key length to determine how to append uuid

--- src/podroller/core.coffee
+++ src/podroller/core.coffee
@@ -82,7 +82,7 @@
             if !req.param('uuid')
                 id = uuid.v4()
                 console.debug "Redirecting with UUID of #{ id }"
-                url = "#{ @options.redirect_url }#{req.originalUrl}" + (if Object.keys(req.query) > 0 then "&uuid=#{id}" else "?uuid=#{id}")
+                url = "#{ @options.redirect_url }#{req.originalUrl}" + (if Object.keys(req.query).length > 0 then "&uuid=#{id}" else "?uuid=#{id}")
                 res.redirect 302, url

             else


feat: add default OPTIONS pre-flight response for CORS

--- src/portals/portal.coffee
+++ src/portals/portal.coffee
@@ -28,7 +28,9 @@
         if request.headers['access-control-request-method']?
           response.setHeader 'Access-Control-Allow-Methods', request.headers['access-control-request-method']

-        response.end() if request.method is 'OPTIONS'
+        if request.method is 'OPTIONS'
+          @writeHead response, 200, response.headers
+          response.end()

       response


Force chunk size will ensure behavior is honored in last chunk

- chunk size may have been ignored for last chunk, allowing to exceed limits on some servers

--- web/service/upload.coffee
+++ web/service/upload.coffee
@@ -8,7 +8,10 @@
     flowOptions: () ->
       target: router.controllers.uploadChunk.route()
       method: 'octet'
-      chunkSize: 4194304
+      # chunkSize: 4194304
+      chunkSize: 2000000
+      # forceChunkSize: false
+      forceChunkSize: true
       simultaneousUploads: 3
       testChunks: false
       chunkRetryInterval: 5000


Activity: custom loader

Activity: custom loader

--- client/Social/Activity/activitylistcontroller.coffee
+++ client/Social/Activity/activitylistcontroller.coffee
@@ -10,6 +10,7 @@
     viewOptions.itemClass     or= options.itemClass
     options.view              or= new KDListView viewOptions, data
     options.startWithLazyLoader = yes
+    options.lazyLoaderOptions   = partial : ''
     options.showHeader         ?= no
     options.noItemFoundWidget or= new KDCustomHTMLView
       cssClass : "lazy-loader hidden"


Client: Activity: Updated renderChannelInfoContainer method. Container should be rendered if scrollContainer and reachedFirstMessage are exist

--- client/activity/lib/components/chatpane/index.coffee
+++ client/activity/lib/components/chatpane/index.coffee
@@ -55,8 +55,7 @@
     scrollContainer     = React.findDOMNode @refs.scrollContainer
     reachedFirstMessage = @props.thread.getIn(['flags', 'reachedFirstMessage'])

-    return null  unless scrollContainer or reachedFirstMessage
-
+    return null  unless scrollContainer and reachedFirstMessage

     <ChannelInfoContainer
       ref='ChannelInfoContainer'


activity: defer load more comments call

activity: defer load more comments call

--- client/activity/lib/components/postpane/index.coffee
+++ client/activity/lib/components/postpane/index.coffee
@@ -32,7 +32,8 @@
     return  if @props.thread.getIn ['flags', 'isMessagesLoading']

     from = @props.messages.first().get('createdAt')
-    ActivityFlux.actions.message.loadComments @message('id'), { from }
+    kd.utils.defer =>
+      ActivityFlux.actions.message.loadComments @message('id'), { from }

   render: ->


MemberItemView: Show the most prioritized role name in the UI.

--- client/admin/lib/views/members/memberitemview.coffee
+++ client/admin/lib/views/members/memberitemview.coffee
@@ -5,6 +5,15 @@
 KDListItemView         = kd.ListItemView
 KDCustomHTMLView       = kd.CustomHTMLView
 getFullnameFromAccount = require 'app/util/getFullnameFromAccount'
+
+# a user be a Member, Admin and Owner at the same time but in the UI we should
+# show the role name which has the most permissions.
+defaultRoles =
+  guest      : nicename : 'Guest',     priority : 0
+  member     : nicename : 'Member',    priority : 1
+  moderator  : nicename : 'Moderator', priority : 2
+  admin      : nicename : 'Admin',     priority : 3
+  owner      : nicename : 'Owner',     priority : 4

 module.exports = class MemberItemView extends KDListItemView
@@ -23,7 +32,7 @@

     @roleLabel = new KDCustomHTMLView
       cssClass : 'role'
-      partial  : "Member "
+      partial  : "#{@getUserRole()} "
       click    : =>
         @settings.toggleClass  'hidden'
         @roleLabel.toggleClass 'active'
@@ -31,7 +40,22 @@
     @createSettingsView()

+  getUserRole: ->
+
+    currentRole = defaultRoles.member
+    userRoles   = @getData().roles
+
+    # find the most prioritized role
+    for userRole in userRoles when role = defaultRoles[userRole]
+      if role.priority > currentRole.priority
+        currentRole = role
+
+    return currentRole.nicename
+
+
   createSettingsView: ->
+
+    userRoles = @getData().roles

     @settings  = new KDCustomHTMLView
       cssClass : 'settings hidden'


MemberItemView: Clear progess flag in error case.

--- client/admin/lib/views/members/memberitemview.coffee
+++ client/admin/lib/views/members/memberitemview.coffee
@@ -133,6 +133,8 @@

   handleError: (button, err) ->

+    @isInProgress = no
+
     if err?.message is 'Access denied'
       return global.location.href = '/Activity'


don't leave previous excuse.coffee users out in the cold

--- src/scripts/excuse.coffee
+++ src/scripts/excuse.coffee
@@ -48,7 +48,7 @@
 ]

 module.exports = (robot) ->
-  robot.respond /developer excuse(?: me)?/i, (msg) ->
+  robot.respond /developer excuse|excuse (?: me)?/i, (msg) ->
     robot.http("http://developerexcuses.com")
       .get() (err, res, body) ->
         matches = body.match /<a [^>]+>(.+)<\/a>/i
@@ -56,5 +56,5 @@
         if matches and matches[1]
           msg.send matches[1]

-  robot.respond /designer excuse(?: me)?/i, (msg) ->
+  robot.respond /designer excuse (?: me)?/i, (msg) ->
     msg.send msg.random(DESIGNER_EXCUSES)


Changed to robot.respond and added more phrases to listen out for

--- src/scripts/polite.coffee
+++ src/scripts/polite.coffee
@@ -13,6 +13,5 @@
 ]

 module.exports = (robot) ->
-  robot.hear /thanks/i, (msg) ->
+  robot.respond /(thanks|thank you|cheers|nice one)/i, (msg) ->
     msg.send msg.random responses
-


Add features and fix some bugs in remind.coffee

- Adds support for "remind me on <date/time> to <action>"
- Requires new npm packages:
  - moment, for duration/time formatting
  - lodash, for utility functions
  - chrono-node, for parsing natural language type dates (e.g. next Tuesday, Tomorrow at 3:00pm, etc.)

--- src/scripts/remind.coffee
+++ src/scripts/remind.coffee
@@ -1,99 +1,157 @@
 # Description:
-#   Forgetful? Add reminders
+#   Forgetful? Add reminders!
 #
 # Dependencies:
-#   None
+#   "chrono-node": "^0.1.10"
+#   "moment": "^2.8.1"
+#   "lodash": "^2.4.1"
 #
 # Configuration:
 #   None
 #
 # Commands:
-#   hubot remind me in <time> to <action> - Set a reminder in <time> to do an <action> <time> is in the format 1 day, 2 hours, 5 minutes etc. Time segments are optional, as are commas
+#   hubot remind me (on <date>|in <time>) to <action> - Set a reminder in <time> to do an <action> <time> is in the format 1 day, 2 hours, 5 minutes etc. Time segments are optional, as are commas
+#   hubot delete reminder <action> - Delete reminder matching <action> (exact match required)
+#   hubot show reminders
 #
 # Author:
 #   whitman
+#   jtwalters
+
+_ = require('lodash')
+moment = require('moment')
+chrono = require('chrono-node')
+timeoutIds = {}

 class Reminders
   constructor: (@robot) ->
     @cache = []
-    @current_timeout = null
+    @currentTimeout = null

-    @robot.brain.on 'loaded', =>
+    # Load reminders from brain, on loaded event
+    @robot.brain.on('loaded', =>
       if @robot.brain.data.reminders
-        @cache = @robot.brain.data.reminders
+        @cache = _.map(@robot.brain.data.reminders, (item) ->
+          new Reminder(item)
+        )
+        console.log("loaded #{@cache.length} reminders")
         @queue()
+    )
+
+    # Persist reminders to the brain, on save event
+    @robot.brain.on('save', =>
+      @robot.brain.data.reminders = @cache
+    )

   add: (reminder) ->
-    @cache.push reminder
-    @cache.sort (a, b) -> a.due - b.due
-    @robot.brain.data.reminders = @cache
+    @cache.push(reminder)
+    @cache.sort((a, b) -> a.due - b.due)
     @queue()

   removeFirst: ->
     reminder = @cache.shift()
-    @robot.brain.data.reminders = @cache
     return reminder

   queue: ->
-    clearTimeout @current_timeout if @current_timeout
-    if @cache.length > 0
-      now = new Date().getTime()
-      @removeFirst() until @cache.length is 0 or @cache[0].due > now
-      if @cache.length > 0
-        trigger = =>
-          reminder = @removeFirst()
-          @robot.reply reminder.msg_envelope, 'you asked me to remind you to ' + reminder.action
-          @queue()
-        # setTimeout uses a 32-bit INT
-        extendTimeout = (timeout, callback) ->
-          if timeout > 0x7FFFFFFF
-            @current_timeout = setTimeout ->
-              extendTimeout (timeout - 0x7FFFFFFF), callback
-            , 0x7FFFFFFF
-          else
-            @current_timeout = setTimeout callback, timeout
-        extendTimeout @cache[0].due - now, trigger
+    return if @cache.length is 0
+    now = (new Date).getTime()
+    trigger = =>
+      reminder = @removeFirst()
+      @robot.reply(reminder.msg_envelope, 'you asked me to remind you to ' + reminder.action)
+      @queue()
+    # setTimeout uses a 32-bit INT
+    extendTimeout = (timeout, callback) ->
+      if timeout > 0x7FFFFFFF
+        setTimeout(->
+          extendTimeout(timeout - 0x7FFFFFFF, callback)
+        , 0x7FFFFFFF)
+      else
+        setTimeout(callback, timeout)
+    reminder = @cache[0]
+    duration = reminder.due - now
+    duration = 0 if duration < 0
+    clearTimeout(timeoutIds[reminder])
+    timeoutIds[reminder] = extendTimeout(reminder.due - now, trigger)
+    console.log("reminder set with duration of #{duration}")

 class Reminder
-  constructor: (@msg_envelope, @time, @action) ->
-    @time.replace(/^\s+|\s+$/g, '')
+  constructor: (data) ->
+    {@msg_envelope, @action, @time, @due} = data

-    periods =
-      weeks:
-        value: 0
-        regex: "weeks?"
-      days:
-        value: 0
-        regex: "days?"
-      hours:
-        value: 0
-        regex: "hours?|hrs?"
-      minutes:
-        value: 0
-        regex: "minutes?|mins?"
-      seconds:
-        value: 0
-        regex: "seconds?|secs?"
+    if @time and !@due
+      @time.replace(/^\s+|\s+$/g, '')

-    for period of periods
-      pattern = new RegExp('^.*?([\\d\\.]+)\\s*(?:(?:' + periods[period].regex + ')).*$', 'i')
-      matches = pattern.exec(@time)
-      periods[period].value = parseInt(matches[1]) if matches
+      periods =
+        weeks:
+          value: 0
+          regex: "weeks?"
+        days:
+          value: 0
+          regex: "days?"
+        hours:
+          value: 0
+          regex: "hours?|hrs?"
+        minutes:
+          value: 0
+          regex: "minutes?|mins?"
+        seconds:
+          value: 0
+          regex: "seconds?|secs?"

-    @due = new Date().getTime()
-    @due += ((periods.weeks.value * 604800) + (periods.days.value * 86400) + (periods.hours.value * 3600) + (periods.minutes.value * 60) + periods.seconds.value) * 1000
+      for period of periods
+        pattern = new RegExp('^.*?([\\d\\.]+)\\s*(?:(?:' + periods[period].regex + ')).*$', 'i')
+        matches = pattern.exec(@time)
+        periods[period].value = parseInt(matches[1]) if matches

-  dueDate: ->
-    dueDate = new Date @due
-    dueDate.toLocaleString()
+      @due = (new Date).getTime()
+      @due += (
+        (periods.weeks.value * 604800) +
+        (periods.days.value * 86400) +
+        (periods.hours.value * 3600) +
+        (periods.minutes.value * 60) +
+        periods.seconds.value
+      ) * 1000
+
+  formatDue: ->
+    dueDate = new Date(@due)
+    duration = dueDate - new Date
+    if duration > 0 and duration < 86400000
+      'in ' + moment.duration(duration).humanize()
+    else
+      'on ' + moment(dueDate).format("dddd, MMMM Do YYYY, h:mm:ss a")

 module.exports = (robot) ->
+  reminders = new Reminders(robot)

-  reminders = new Reminders robot
+  robot.respond(/show reminders$/i, (msg) ->
+    text = ''
+    for reminder in reminders.cache
+      text += "#{reminder.action} #{reminder.formatDue()}\n"
+    msg.send(text)
+  )

-  robot.respond /remind me in ((?:(?:\d+) (?:weeks?|days?|hours?|hrs?|minutes?|mins?|seconds?|secs?)[ ,]*(?:and)? +)+)to (.*)/i, (msg) ->
-    time = msg.match[1]
-    action = msg.match[2]
-    reminder = new Reminder msg.envelope, time, action
-    reminders.add reminder
-    msg.send 'I\'ll remind you to ' + action + ' on ' + reminder.dueDate()
+  robot.respond(/delete reminder (.+)$/i, (msg) ->
+    query = msg.match[1]
+    prevLength = reminders.cache.length
+    reminders.cache = _.reject(reminders.cache, {action: query})
+    reminders.queue()
+    msg.send("Deleted reminder #{query}") if reminders.cache.length isnt prevLength
+  )
+
+  robot.respond(/remind me (in|on) (.+?) to (.*)/i, (msg) ->
+    type = msg.match[1]
+    time = msg.match[2]
+    action = msg.match[3]
+    options =
+      msg_envelope: msg.envelope,
+      action: action
+      time: time
+    if type is 'on'
+      # parse the date (convert to timestamp)
+      due = chrono.parseDate(time).getTime()
+      if due.toString() isnt 'Invalid Date'
+        options.due = due
+    reminder = new Reminder(options)
+    reminders.add(reminder)
+    msg.send "I'll remind you to #{action} #{reminder.formatDue()}"
+  )


Switch to dropboxuser URLs

Switch to dropboxuser URLs

--- src/scripts/shipit.coffee
+++ src/scripts/shipit.coffee
@@ -28,10 +28,10 @@
   "http://www.zmescience.com/wp-content/uploads/2010/09/squirrel.jpg",
   "http://img70.imageshack.us/img70/4853/cutesquirrels27rn9.jpg",
   "http://img70.imageshack.us/img70/9615/cutesquirrels15ac7.jpg",
-  "http://dl.dropbox.com/u/602885/github/sniper-squirrel.jpg",
+  "https://dl.dropboxusercontent.com/u/602885/github/sniper-squirrel.jpg",
   "http://1.bp.blogspot.com/_v0neUj-VDa4/TFBEbqFQcII/AAAAAAAAFBU/E8kPNmF1h1E/s640/squirrelbacca-thumb.jpg",
-  "http://dl.dropbox.com/u/602885/github/soldier-squirrel.jpg",
-  "http://dl.dropbox.com/u/602885/github/squirrelmobster.jpeg",
+  "https://dl.dropboxusercontent.com/u/602885/github/soldier-squirrel.jpg",
+  "https://dl.dropboxusercontent.com/u/602885/github/squirrelmobster.jpeg",
   "http://f.cl.ly/items/0S1M2d1h0I132S082A05/flying-squirrel.gif"
 ]


Added extra data handing to queryFetch

Added extra data handing to queryFetch

--- src/server/session.coffee
+++ src/server/session.coffee
@@ -219,7 +219,7 @@
             callback null, {a:'ack'}

       when 'qfetch'
-        agent.queryFetch index, req.q, (err, results) ->
+        agent.queryFetch index, req.q, (err, results, extra) ->
           return callback err if err

           for r in results
@@ -227,7 +227,7 @@
               r.snapshot = r.data
             delete r.data

-          callback null, id:qid, data:results
+          callback null, id:qid, data:results, extra:extra

       when 'qsub'
         agent.query index, req.q, qopts, (err, emitter) ->


ADDED search by skills.

ADDED search by skills.

--- app/assets/javascripts/angular/controllers/SearchCtrl.coffee
+++ app/assets/javascripts/angular/controllers/SearchCtrl.coffee
@@ -28,12 +28,5 @@
       filteredUsers.push user if isMatch == true
     filteredUsers

-  $scope.getMasters = (users) ->
-    masters = []
-    angular.forEach users, (user) ->
-      masters.push user if user.status.match("master")
-      return
-    masters
-
 ]


[grunt] update comment

[grunt] update comment

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -167,7 +167,7 @@
   ]

   ###
-  A task for scss and js linting
+  A task for scss linting
   ###
   grunt.registerTask 'test', [
     'scsslint:build'


start background flush after http server has started

--- services/document-updater/app.coffee
+++ services/document-updater/app.coffee
@@ -143,11 +143,12 @@
 if !module.parent # Called directly
 	app.listen port, host, ->
 		logger.info "Document-updater starting up, listening on #{host}:#{port}"
+		if Settings.continuousBackgroundFlush
+			logger.info "Starting continuous background flush"
+			DeleteQueueManager.startBackgroundFlush()
+
 module.exports = app

 for signal in ['SIGINT', 'SIGHUP', 'SIGQUIT', 'SIGUSR1', 'SIGUSR2', 'SIGTERM', 'SIGABRT']
 	process.on signal, shutdownCleanly(signal)

-if Settings.continuousBackgroundFlush
-	logger.info "Starting continuous background flush"
-	DeleteQueueManager.startBackgroundFlush()


Allow task to ask for GitHub API token

Allow task to ask for GitHub API token

--- tasks/update-atom-shell-task.coffee
+++ tasks/update-atom-shell-task.coffee
@@ -7,21 +7,34 @@
   {spawn, mkdir, rm, cp} = require('./task-helpers')(grunt)

   accessToken = null
-  getTokenFromKeychain = ->
+  getTokenFromKeychain = (callback) ->
     accessToken ?= process.env['ATOM_ACCESS_TOKEN']
+    if accessToken
+      callback(null, accessToken)
+      return
+
+    spawn {cmd: 'security', args: ['-q', 'find-generic-password', '-ws', 'GitHub API Token']}, (error, result, code) ->
+      accessToken = result unless error?
+      callback(error, accessToken)

   callAtomShellReposApi = (path, callback) ->
-    options =
-      url: "https://api.github.com/repos/atom/atom-shell#{path}"
-      proxy: process.env.http_proxy || process.env.https_proxy
-      headers:
-        authorization: "token #{getTokenFromKeychain()}"
-        accept: 'application/vnd.github.manifold-preview'
-    request options, (error, response, body) ->
-      if not error?
-        body = JSON.parse(body)
-        error = new Error(body.message) if response.statusCode != 200
-      callback(error, response, body)
+    getTokenFromKeychain (error, accessToken) ->
+      if error
+        callback(error)
+        return
+
+      options =
+        url: "https://api.github.com/repos/atom/atom-shell#{path}"
+        proxy: process.env.http_proxy || process.env.https_proxy
+        headers:
+          authorization: "token #{accessToken}"
+          accept: 'application/vnd.github.manifold-preview'
+
+      request options, (error, response, body) ->
+        if not error?
+          body = JSON.parse(body)
+          error = new Error(body.message) if response.statusCode != 200
+        callback(error, response, body)

   findReleaseIdFromAtomShellVersion = (version, callback) ->
     callAtomShellReposApi '/releases', (error, response, data) ->
@@ -60,7 +73,7 @@
   isAtomShellVersionCached = (version) ->
     grunt.file.isFile(getCachePath(version), 'version')

-  downloadAtomShell = (version, url, callback) ->
+  getDownloadOptions = (version, url, callback) ->
     options =
       url: url
       followRedirect: false
@@ -69,30 +82,41 @@
     # Only set headers for GitHub host, the url could also be a S3 link and
     # setting headers for it would make the request fail.
     if require('url').parse(url).hostname is 'api.github.com'
-      options.headers =
-        authorization: "token #{getTokenFromKeychain()}"
-        accept: 'application/octet-stream'
+      getTokenFromKeychain (error, accessToken) ->
+        options.headers =
+          authorization: "token #{accessToken}"
+          accept: 'application/octet-stream'

-    inputStream = request(options)
-    inputStream.on 'response', (response) ->
-      if response.statusCode is 302
-        # Manually handle redirection so headers would not be sent for S3.
-        downloadAtomShell(version, response.headers.location, callback)
-      else if response.statusCode is 200
-        grunt.log.writeln("Downloading atom-shell version #{version.cyan}")
-        cacheDirectory = getCachePath(version)
-        rm(cacheDirectory)
-        mkdir(cacheDirectory)
-        cacheFile = path.join(cacheDirectory, 'atom-shell.zip')
-        outputStream = fs.createWriteStream(cacheFile)
-        outputStream.on 'close', -> callback(null, cacheFile)
-        inputStream.pipe(outputStream)
-      else
-        if response.statusCode is 404
-          grunt.log.error("atom-shell #{version.cyan} not found")
+        callback(error, options)
+    else
+      callback(null, options)
+
+  downloadAtomShell = (version, url, callback) ->
+    getDownloadOptions version, url, (error, options) ->
+      if error
+        callback(error)
+        return
+
+      inputStream = request(options)
+      inputStream.on 'response', (response) ->
+        if response.statusCode is 302
+          # Manually handle redirection so headers would not be sent for S3.
+          downloadAtomShell(version, response.headers.location, callback)
+        else if response.statusCode is 200
+          grunt.log.writeln("Downloading atom-shell version #{version.cyan}")
+          cacheDirectory = getCachePath(version)
+          rm(cacheDirectory)
+          mkdir(cacheDirectory)
+          cacheFile = path.join(cacheDirectory, 'atom-shell.zip')
+          outputStream = fs.createWriteStream(cacheFile)
+          outputStream.on 'close', -> callback(null, cacheFile)
+          inputStream.pipe(outputStream)
         else
-          grunt.log.error("atom-shell #{version.cyan} request failed")
-        callback(false)
+          if response.statusCode is 404
+            grunt.log.error("atom-shell #{version.cyan} not found")
+          else
+            grunt.log.error("atom-shell #{version.cyan} request failed")
+          callback(false)

   downloadAtomShellOfVersion = (version, callback) ->
     findReleaseIdFromAtomShellVersion version, (error, releaseId) ->


KodingKite: instead keeping calls in cache, keep promise resolvers to keep chain

--- client/Main/kite/kodingkite.coffee
+++ client/Main/kite/kodingkite.coffee
@@ -21,6 +21,12 @@

     @waitingCalls = []
     @waitingPromises = []
+
+    @_kiteInvalidError =
+      name    : "KiteInvalid"
+      message : "Kite is invalid. This kite (#{name}) not exists
+                 or there is a problem with connection."
+

   extractInfoFromWsEvent = (event)->
     {reason, code, wasClean, timestamp, type} = event
@@ -59,24 +65,31 @@

     unless @invalid

+      _resolve = null
+      _args    = null
+
       promise = new Promise (resolve, reject)=>

-        (@waitForConnection [rpcMethod, [params], callback]).then (args)=>
-          console.info "# CONNECTED TO #{name} SENDING #{rpcMethod}... "
-          resolve @transport?.tell args...
+        _resolve = resolve
+        _args    = [rpcMethod, [params], callback]
+
+        @waitForConnection _args
+
+          .then (args)=>
+            console.info "# CONNECTED TO #{name} SENDING #{rpcMethod}... "
+            resolve @transport?.tell args...
+
+          .catch =>
+            reject @_kiteInvalidError

       unless @_state is CONNECTED
-        @waitingPromises.push promise
+        @waitingPromises.push [_resolve, _args]

       promise

     else

-      Promise.reject
-        name    : "KiteInvalid"
-        message : "Kite is invalid. This kite (#{name}) not exists
-                   or there is a problem with connection."
-        err     : @_invalid
+      Promise.reject @_kiteInvalidError

   @createMethod = (ctx, { method, rpcMethod }) ->


Fix JS exception when closing an event, refs #405

--- client/app/lib/popover_view.coffee
+++ client/app/lib/popover_view.coffee
@@ -19,8 +19,9 @@

     close: ->
-        @$popover.remove()
-        @$popover = null
+        if @$popover?
+            @$popover.remove()
+            @$popover = null
         @target.data 'popover', undefined
         @remove()


apiNotify was only being called on correct submissions!

--- web/coffee/problems.coffee
+++ web/coffee/problems.coffee
@@ -15,10 +15,10 @@
   .done (data) ->
     if data['status'] is 1
       loadProblems()
-      apiNotify(data)
       setTimeout( ->
         $("div[data-target='#" + input.data("pid") + "']").click()
       , 100)
+    apiNotify data

 addProblemReview = (e) ->
   e.preventDefault()


Fix behavior of addColumns, add addColumn

--- src/sql/sql-select.coffee
+++ src/sql/sql-select.coffee
@@ -35,6 +35,10 @@
     addFrom: (table, a) -> a.push(table)

     addColumns: (columns...) ->
+        @addColumn(col) for col in columns
+        return @
+
+    addColumn: (columns...) ->
         @columns.push(columns)
         return @


- [grunt] disabled online mode detection (faster startup) for browserSync task; specified concrete files for browserSync, otherwise it fails to inject them properly

--- tasks/grunt-browser-sync.coffee
+++ tasks/grunt-browser-sync.coffee
@@ -11,6 +11,7 @@
         notify: true
         watchTask: true
         debugInfo: true
+        online: false
         ghostMode:
           clicks: true
           links: true
@@ -20,6 +21,10 @@
           baseDir: '<%= path.build.root %>'
       files:
         src: [
-          '<%= path.build.root %>/{,**/}*'
-          '!<%= path.build.styles %>/{,**/}*.css.map'
+          '<%= path.build.root %>/{,**/}*.js'
+          '<%= path.build.root %>/{,**/}*.css'
+          '<%= path.build.root %>/{,**/}*.html'
+          '<%= path.build.root %>/{,**/}*.{png,jpg,jpeg,gif,svg,ico}'
+          '<%= path.build.root %>/{,**/}*.{xml,txt}'
+          '<%= path.build.root %>/{,**/}*.{eot,ttf,woff}'
         ]


Minor: code cleanup

Minor: code cleanup

--- client/finder/AppController.coffee
+++ client/finder/AppController.coffee
@@ -10,7 +10,7 @@

     super options, data

-  createFileFromPath:(rest...)-> FSHelper.createFileFromPath rest...
+  createFileFromPath: -> FSHelper.createFileFromPath arguments...

   create: (options = {}) ->
     options.useStorage       ?= yes


zoom to widest possible streetview angle on location

--- web/js/views/map_location_marker.coffee
+++ web/js/views/map_location_marker.coffee
@@ -38,7 +38,7 @@

     onStreetViewClicked: =>
       @map.getStreetView().setPosition(@location)
-      @map.getStreetView().setPov(heading: @heading, pitch: 0, zoom: 1)
+      @map.getStreetView().setPov(heading: @heading, pitch: 0, zoom: 0)
       @map.getStreetView().setVisible(true)

     onRemove: ->


Model disposal: Reject Deferred, delete _callbacks

--- coffee/chaplin/models/model.coffee
+++ coffee/chaplin/models/model.coffee
@@ -37,13 +37,19 @@
       # Unbind all global event handlers
       @unsubscribeAllEvents()

-      # Remove all event handlers
+      # Remove all event handlers on this module
       @off()
+
+      # If the model is a Deferred, reject it
+      # This does nothing if it was resolved before
+      @reject?()

       # Remove the collection reference and attributes
       properties = [
-        'collection', 'attributes', '_escapedAttributes', '_previousAttributes',
-        '_silent', '_pending'
+        'collection',
+        'attributes', '_escapedAttributes', '_previousAttributes',
+        '_silent', '_pending',
+        '_callbacks'
       ]
       delete this[prop] for prop in properties


Like previous but better

Like previous but better

--- coffee/react-gui/components.coffee
+++ coffee/react-gui/components.coffee
@@ -174,34 +174,24 @@
       dangerouslySetInnerHTML: {__html: "→"}})

-LC.React.ZoomButtons = React.createClass
-  displayName: 'ZoomButtons'
-  render: ->
-    {div} = React.DOM
-    {lc} = @props
-    (div {className: 'lc-zoom'},
-      createZoomButton(lc, 'out')(),
-      createZoomButton(lc, 'in')(),
-    )
-
-
-createZoomButton = (lc, inOrOut) -> React.createClass
+createZoomButtonComponent = (inOrOut) -> React.createClass
   displayName: if inOrOut == 'in' then 'ZoomInButton' else 'ZoomOutButton'

   getState: -> {
     isEnabled: switch
-      when inOrOut == 'in' then lc.scale < 4.0
-      when inOrOut == 'out' then  lc.scale > 0.6
+      when inOrOut == 'in' then @props.lc.scale < 4.0
+      when inOrOut == 'out' then  @props.lc.scale > 0.6
   }
   getInitialState: -> @getState()
   componentDidMount: ->
     @subscriber = => @setState @getState()
-    lc.on 'zoom', @subscriber
+    @props.lc.on 'zoom', @subscriber
   componentWillUnmount: ->
-    lc.removeEventListener('zoom', @subscriber)
-
-  render: ->
-    {div} = React.DOM
+    @props.lc.removeEventListener('zoom', @subscriber)
+
+  render: ->
+    {div} = React.DOM
+    {lc} = @props

     className = "lc-zoom-#{inOrOut} " + React.addons.classSet
       'toolbar-button': true
@@ -216,6 +206,16 @@
       when inOrOut == 'in' then '+'
       when inOrOut == 'out' then '-'
     )
+
+
+ZoomOutButton = createZoomButtonComponent('out')
+ZoomInButton = createZoomButtonComponent('in')
+LC.React.ZoomButtons = React.createClass
+  displayName: 'ZoomButtons'
+  render: ->
+    {div} = React.DOM
+    {lc} = @props
+    (div {className: 'lc-zoom'}, ZoomOutButton({lc}), ZoomInButton({lc}))

 LC.React.ClearButton = React.createClass


if project not exists, don't add undefined value.

--- coffee/services/dataAdapter.coffee
+++ coffee/services/dataAdapter.coffee
@@ -333,7 +333,9 @@

       # find
       starred = []
-      @_filteredData.map((a) -> starred.add(a.projects?.filter((n) -> n.show is Const.SHOW.SHOW)))
+      @_filteredData.map((a) ->
+        if not a.projects then return
+        starred.add(a.projects.filter((n) -> n.show is Const.SHOW.SHOW)))
       if starred.length is 0 then return

       # update


Made navigateAppend idempotent

Made navigateAppend idempotent


Revert "Social: sendNotificationToAdmins notification creation way is changed"

This reverts commit fb617135490a26980a7e7ccf9f770561b36d5139.

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -339,15 +339,22 @@
     @on 'MemberAdded', (member)->
       @constructor.emit 'MemberAdded', { group: this, member }
       unless @slug is 'guests'
-        @sendNotificationToAdmins 'join', member
-
+        @sendNotificationToAdmins 'GroupJoined',
+          actionType : 'groupJoined'
+          actorType  : 'member'
+          subject    : ObjectRef(this).data
+          member     : ObjectRef(member).data
         @broadcast 'MemberJoinedGroup',
           member : ObjectRef(member).data

     @on 'MemberRemoved', (member)->
       @constructor.emit 'MemberRemoved', { group: this, member }
       unless @slug is 'guests'
-        @sendNotificationToAdmins 'leave', member
+        @sendNotificationToAdmins 'GroupLeft',
+          actionType : 'groupLeft'
+          actorType  : 'member'
+          subject    : ObjectRef(this).data
+          member     : ObjectRef(member).data
         @broadcast 'MemberLeftGroup',
           member : ObjectRef(member).data

@@ -1382,36 +1389,27 @@
         -> callback null
       ]

-  sendNotificationToAdmins: (type, member)->
-    @createSocialApiChannelId (err) =>
-      return  if err
-
-      @fetchAdmins (err, admins)=>
-        return  if err or not admins.length
-
-        SocialNotification = require '../socialapi/notification'
-        switch type
-          when "join"
-            fn = SocialNotification.joinGroup
-          when "leave"
-            fn = SocialNotification.leaveGroup
-          else
-            return
-
-        data = {}
-        data.name = @slug
-        data.account = member
-
-        data.admins = []
-        index = 0
+  sendNotificationToAdmins: (event, contents)->
+    @fetchAdmins (err, admins)=>
+      unless err
+        relationship =  {
+          as         : event,
+          sourceName : contents.subject.constructorName,
+          sourceId   : contents.subject.id,
+          targetName : contents.member.constructorName,
+          targetId   : contents.member.id,
+        }
+
+        contents.relationship = relationship
+        contents.origin       = contents.subject
+        contents.origin.slug  = @slug
+        contents.actorType    = event
+        contents[event]       = contents.member
+
+        next = -> queue.next()
         queue = admins.map (admin) =>=>
-          admin.createSocialApiId (err, adminId) ->
-            return queue.next()  if err or not adminId
-            data.admins.push adminId
-            queue.next()
-
-        queue.push ->
-          fn data, (err) -> console.error err  if err
+          contents.recipient = admin
+          @notify admin, event, contents, next

         daisy queue


code formatting

code formatting

--- workers/social/lib/social/traits/slugifiable.coffee
+++ workers/social/lib/social/traits/slugifiable.coffee
@@ -25,9 +25,9 @@
         if err then callback err
         else
           nextCount = names
-            .map((nm)->
+            .map (nm)->
               [d] = (/\d+$/.exec nm) ? [0]
-              [+d, nm])
+              [+d, nm]
             .sort ([a], [b])->
               a > b
             .pop()


CoffeeScript 1.3.x compatibility

CoffeeScript 1.3.x compatibility

--- extension/blogsiple/main.coffee
+++ extension/blogsiple/main.coffee
@@ -9,7 +9,7 @@
   makeDir: (dir, cb) ->
     fs.stat dir, (err, stat) =>
       return cb() unless err
-      fs.mkdir dir, 0777, cb
+      fs.mkdir dir, 0o0777, cb

   makeDirs: ->
     @makeDir "#{__dirname}/static", =>


[fix] Clean remaining diff markup after rebase

--- server/controllers/konnectors.coffee
+++ server/controllers/konnectors.coffee
@@ -130,10 +130,6 @@
         req.konnector.updateFieldValues { accounts: accounts }, (err) ->
             return next err if err

-<<<<<<< HEAD
-            res.redirect '../../..' + \
-              "/#/category/#{req.konnector.category}/#{req.konnector.slug}"
-=======
             res.status(200).send """<!DOCTYPE html>
 <html>
 <head>
@@ -152,4 +148,3 @@
 </body>
 </html>
 """
->>>>>>> 963e648... Konnector Orange mesinfos (#573)


Make sure url's are wrapped in anchors in description

--- extras/Studio.framer/app.coffee
+++ extras/Studio.framer/app.coffee
@@ -207,17 +207,24 @@
 			y: verticalPosition + 16

 	_renderDescription: ->
+
+		# See if there are any url's in the description and wrap them in anchor tags
+		urlify = (text) ->
+		  urlRegex = /(https?:\/\/[^\s]+)/g
+		  text.replace urlRegex, (url) ->
+		    '' + url + ''
+
 		@description = new ShareLayer
 			parent: @info
 			y: @credentials.maxY + 10
-			html: @shareInfo.description
+			html: urlify(@shareInfo.description)
 			style:
 				lineHeight: "1.5"
 				wordWrap: "break-word"

 		descriptionSize = Utils.textSize(
 			@shareInfo.description,
-			{fontSize: "14px", fontFamily: "Roboto", lineHeight: "1.5"},
+			{fontSize: "14px", fontFamily: "Roboto", lineHeight: "1.5", wordWrap: "break-word"},
 			{width: "#{@description.width}"}
 		)
 		@description.height = descriptionSize.height


whitespace change

whitespace change

--- js/lib/fid.coffee
+++ js/lib/fid.coffee
@@ -37,10 +37,8 @@
       @_default_patcher = new Patcher()
     @_default_patcher

-  @patch: (args...) ->
-    @default_patcher().patch(args...)
+  @patch: (args...) -> @default_patcher().patch(args...)

-  @diff: (args...) ->
-    @default_patcher().diff(args...)
+  @diff: (args...) -> @default_patcher().diff(args...)

 window.Fid = Fid


Forgot to include removeAttr tests in last commit

--- test/tb/lib/ti/attributes_test.coffee
+++ test/tb/lib/ti/attributes_test.coffee
@@ -1,4 +1,5 @@
 helpers = require '../../../helpers'
+_ = require 'underscore'

 { assert } = helpers.chai

@@ -14,41 +15,64 @@

     @$coll = $ [@el1, @el2]

-  it 'should be able to set an attribute on a collection of views', ->
+  describe 'attr', ->

-    @$coll.attr 'left', 10
+    it 'should be able to set an attribute on a collection of views', ->

-    assert.equal @el1.left, 10
-    assert.equal @el2.left, 10
+      @$coll.attr 'left', 10

-  it 'should be able to access an attribute on the first element of a view collection', ->
+      assert.equal @el1.left, 10
+      assert.equal @el2.left, 10

-    @el1.left = 10
+    it 'should be able to access an attribute on the first element of a view collection', ->

-    assert.equal @$coll.attr('left'), 10
+      @el1.left = 10

-  it 'should be able to set multiple attributes on a view', ->
+      assert.equal @$coll.attr('left'), 10

-    @$coll.attr
-      left: 10
-      right: 20
+    it 'should be able to set multiple attributes on a view', ->

-    assert.equal @el1.left, 10
-    assert.equal @el1.right, 20
+      @$coll.attr
+        left: 10
+        right: 20

-    assert.equal @el2.left, 10
-    assert.equal @el2.right, 20
+      assert.equal @el1.left, 10
+      assert.equal @el1.right, 20

-  it 'should map the "class" attribute to _class', ->
+      assert.equal @el2.left, 10
+      assert.equal @el2.right, 20

-    $(@el1).attr
-      class: 'someClass'
+    it 'should map the "class" attribute to _class', ->

-    $(@el2).attr 'class', 'someOtherClass'
+      $(@el1).attr
+        class: 'someClass'

-    assert.equal @el1._class, 'someClass'
-    assert.equal @el2._class, 'someOtherClass'
-    assert.equal $(@el1).attr('class'), 'someClass'
+      $(@el2).attr 'class', 'someOtherClass'
+
+      assert.equal @el1._class, 'someClass'
+      assert.equal @el2._class, 'someOtherClass'
+      assert.equal $(@el1).attr('class'), 'someClass'
+
+  describe 'removing attributes', ->
+
+    beforeEach ->
+
+      @$el = $(@el1)
+
+      @$el.attr
+        someAttr: 'someValue'
+
+    it 'should allow removal of an attribute by setting an empty value in attr', ->
+
+      @$el.attr 'someAttr', null
+
+      assert.isFalse _.has @el1, 'someAttr'
+
+    it 'should allow removal of an attribute via removeAttr', ->
+
+      @$el.removeAttr 'someAttr'
+
+      assert.isFalse _.has @el1, 'someAttr'

   describe 'addClass', ->


fix profilePicture bug

fix profilePicture bug

--- server/publish/profilePicture.coffee
+++ server/publish/profilePicture.coffee
@@ -1,3 +1,5 @@
+import { Pictures } from '/imports/api/pictures/pictures.coffee'
+
 Meteor.publish 'profilePicture', (userId) ->

 	if userId == @userId


tweak default datatable column order

tweak default datatable column order

--- server/webapp/views/DataTable.coffee
+++ server/webapp/views/DataTable.coffee
@@ -40,11 +40,33 @@
         for name,value of (row.doc.meta ? {})
           row.doc['_'+name] = value
         delete row.doc.meta
+        row.doc['_type'] = row.doc['type']
+        delete row.doc['type']
         for name,value of row.doc
           names[name] = true
       cnames = for name,value of names
         name
-      cnames.sort()
+      cnames.sort (a,b) ->
+        console.log "compare #{a} #{b}"
+        if a and (not b)
+          -1
+        else if (not a) and b
+          1
+        else if a.charAt(0) == '_' and not (b.charAt(0) == '_')
+          1
+        else if not (a.charAt(0) == '_') and b.charAt(0) == '_'
+          -1
+        else if a=='_userID'
+          -1
+        else if b=='_userID'
+          1
+        else if (typeof a)=='string'
+          a.localeCompare b
+        else if (typeof a)=='number'
+          a-b
+        else
+          String(a).localeCompare String(b)
+
       table = document.createElement 'TABLE'
       thead = table.createTHead()
       htr = thead.insertRow -1


replace keyIdentifier with key due to depreciation

--- src/ui/views/input.coffee
+++ src/ui/views/input.coffee
@@ -84,8 +84,8 @@
                     ta.parentNode.style.height = (ta.offsetHeight + 24) + 'px'
                     messages.scrollToBottom() if messages?
             , onkeydown: (e) ->
-                if (e.metaKey or e.ctrlKey) and e.keyIdentifier == 'Up' then action 'selectNextConv', -1
-                if (e.metaKey or e.ctrlKey) and e.keyIdentifier == 'Down' then action 'selectNextConv', +1
+                if (e.metaKey or e.ctrlKey) and e.key == 'Up' then action 'selectNextConv', -1
+                if (e.metaKey or e.ctrlKey) and e.key == 'Down' then action 'selectNextConv', +1
                 unless isModifierKey(e)
                     if e.keyCode == 27
                         e.preventDefault()
@@ -104,8 +104,8 @@
                         e.target.value = ''
                         autosize.update e.target
                     if e.target.value == ''
-                        if e.keyIdentifier is "Up" then historyWalk e.target, -1
-                        if e.keyIdentifier is "Down" then historyWalk e.target, +1
+                        if e.key is "Up" then historyWalk e.target, -1
+                        if e.key is "Down" then historyWalk e.target, +1
                 action 'lastkeydown', Date.now() unless isAltCtrlMeta(e)
             , onkeyup: (e) ->
                 #check for emojis after pressing space


[Profile Page][Graph] : added race id into tempRes

--- workers/social/lib/social/models/graph/graph.coffee
+++ workers/social/lib/social/models/graph/graph.coffee
@@ -82,7 +82,7 @@
                 tempRes[i] = res
                 fin()
             else
-              tempRes.push res
+              tempRes[i] = res
               fin()
     , =>
       {groupName, groupId} = options.group if options.group?


Add one more debug line, and remove old code

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -1352,7 +1352,7 @@
               else
                 quantity = vmQuantity - createdVMs[planCode]
               for i in [1..quantity] when quantity >= 1
-                # console.log "Creating #{planCode}"
+                console.log "Creating #{planCode}"
                 options     =
                   planCode  : planCode
                   usage     : {cpu: 1, ram: 1, disk: 1}
@@ -1367,31 +1367,6 @@
     else
       callback new KodingError "No such VM type: #{data.type}"

-
-    #   {connection:{delegate}, context:{group}} = client
-    # debit$: permit 'commission resources',
-    #   success: (client, data, callback)-> @debitResource client, 'user', data, callback
-
-    # debitGroup$: permit 'change bundle',
-    #   success: (client, data, callback)-> @debitResource client, 'group', data, callback
-
-    # debitResource: (client, type, data, callback)->
-    #   {connection:{delegate}, context:{group}} = client
-
-    #   # TODO: Check payment here
-    #   console.log "Client", delegate._id
-    #   console.log "Group", group
-
-    #   options     =
-    #     usage     : data.usage
-    #     hostname  : data.hostname
-    #     type      : type
-    #     account   : delegate
-    #     groupSlug : group
-
-    #   JVM = require '../vm'
-    #   JVM.createVm options, callback
-
   fetchOrSearchInvitationRequests: permit 'send invitations',
     success: (client, status, timestamp, requestLimit, search, callback)->
       status   = $in: status                if Array.isArray status


Fixed WebGL renderer when using closure compiler.

--- emulator/frontend/renderers/webgl-renderer.coffee
+++ emulator/frontend/renderers/webgl-renderer.coffee
@@ -36,7 +36,7 @@
 class WebGLRenderer

     @isSupported: ->
-        window.WebGLRenderingContext?
+        window["WebGLRenderingContext"]?

     constructor: (@canvas) ->
         @initWebGL()


groupinvitation: add filter for multiuse

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -1395,9 +1395,18 @@
   fetchInvitationsByStatus: permit 'send invitations',
     success: (client, options, callback)->
       JInvitation = require '../invitation'
+      if options.type is "InvitationCode"
+        type   = "multiuse"
+        status = if options.showResolved then ["active" , "redeemed"] else ['active']
+      else
+        type   = "admin"
+        status = if options.showResolved then ["sent", "redeemed"] else ["sent"]
+
       JInvitation.some
-        status:
-          $in: if options.showResolved then ["sent", "redeemed"] else ["sent"]
+        group  : @slug
+        type   : type
+        status :
+          $in  : status
         , options
         , callback


add /health_check/redis route back in

add /health_check/redis route back in

--- services/document-updater/app.coffee
+++ services/document-updater/app.coffee
@@ -71,7 +71,16 @@
 	else
 		res.send('document updater is alive')

+
 docUpdaterRedisClient = require("redis-sharelatex").createClient(Settings.redis.documentupdater)
+app.get "/health_check/redis", (req, res, next) ->
+	docUpdaterRedisClient.healthCheck (error) ->
+		if error?
+			logger.err {err: error}, "failed redis health check"
+			res.send 500
+		else
+			res.send 200
+
 app.get "/health_check/redis_cluster", (req, res, next) ->
 	docUpdaterRedisClient.healthCheck (error) ->
 		if error?


JGroup: remove -> destroy: use async.series, improve the existing logic

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -1,3 +1,4 @@
+async          = require 'async'
 { Module }     = require 'jraphical'
 { difference } = require 'underscore'

@@ -230,7 +231,7 @@
           (signature String, Function)
         transferOwnership:
           (signature String, Function)
-        remove:
+        destroy:
           (signature Function)
         addSubscription:
           (signature String, Function)
@@ -1343,61 +1344,70 @@
       if err then callback err
       else oldAddOwner.call this, target, options, callback

-  remove_ = @::remove
-  remove: secure (client, callback) ->
-    JName = require '../name'
-
-    @fetchOwner (err, owner) =>
-      return callback err if err
-      unless owner.getId().equals client.connection.delegate.getId()
-        return callback new KodingError 'You must be the owner to perform this action!'
-
-      removeHelper = (model, err, callback, queue) ->
-        return callback err if err
-        return queue.next() unless model
+  destroy    : permit
+    advanced : [
+      { permission: 'edit own groups', validateWith : Validators.own }
+      { permission: 'edit groups',     superadmin   : yes }
+    ]
+    success  : (client, callback) ->
+
+      removeHelper = (model, err, callback, next) ->
+        return callback err  if err
+        return next()  unless model
+
         model.remove (err) ->
           return callback err if err
-          queue.next()
-
-      removeHelperMany = (klass, models, err, callback, queue) ->
-        return callback err if err
-        return queue.next() if not models or models.length < 1
+          next()
+
+      removeHelperMany = (klass, models, err, callback, next) ->
+        return callback err  if err
+        return next()  if not models or models.length < 1
+
         ids = (model._id for model in models)
         klass.remove ({ _id: { $in: ids } }), (err) ->
-          return callback err if err
-          queue.next()
-
-      daisy queue = [
-        => JName.one { name:@slug }, (err, name) ->
-          removeHelper name, err, callback, queue
-
-        => @fetchPermissionSet (err, permSet) ->
-          removeHelper permSet, err, callback, queue
-
-        => @fetchDefaultPermissionSet (err, permSet) ->
-          removeHelper permSet, err, callback, queue
-
-        => @fetchMembershipPolicy (err, policy) ->
-          removeHelper policy, err, callback, queue
-
-        => @fetchInvitations (err, requests) ->
-          JInvitation = require '../invitation'
-          removeHelperMany JInvitation, requests, err, callback, queue
-
-        => @fetchTags (err, tags) ->
-          JTag = require '../tag'
-          removeHelperMany JTag, tags, err, callback, queue
-
-        =>
+          return callback err  if err
+          next()
+
+      JName = require '../name'
+
+      async.series [
+        (next) =>
+          JName.one { name: @slug }, (err, name) ->
+            removeHelper name, err, callback, next
+
+        (next) =>
+          @fetchPermissionSet (err, permSet) ->
+            removeHelper permSet, err, callback, next
+
+        (next) =>
+          @fetchDefaultPermissionSet (err, permSet) ->
+            removeHelper permSet, err, callback, next
+
+        (next) =>
+          @fetchMembershipPolicy (err, policy) ->
+            removeHelper policy, err, callback, next
+
+        (next) =>
+          @fetchInvitations (err, requests) ->
+            JInvitation = require '../invitation'
+            removeHelperMany JInvitation, requests, err, callback, next
+
+        (next) =>
+          @fetchTags (err, tags) ->
+            JTag = require '../tag'
+            removeHelperMany JTag, tags, err, callback, next
+
+        (next) =>
           @constructor.emit 'GroupDestroyed', this
-          queue.next()
-
-        => remove_.call this, (err) ->
-          return callback err if err
-          queue.next()
-
+          next()
+
+        (next) =>
+          @remove (err) ->
+            return callback err if err
+            next()
+      ],
         -> callback null
-      ]
+

   sendNotificationToAdmins: (event, contents) ->
     @fetchAdmins (err, admins) =>


ScriptBlock: Code alignment.

ScriptBlock: Code alignment.

--- workers/social/lib/social/render/scriptblock.coffee
+++ workers/social/lib/social/render/scriptblock.coffee
@@ -34,8 +34,8 @@
     userAccount          = JSON.stringify delegate
     userMachines         = JSON.stringify userMachines
     userWorkspaces       = JSON.stringify userWorkspaces
-    userEnvironmentData = JSON.stringify userEnvironmentData
-    userId = JSON.stringify userId
+    userEnvironmentData  = JSON.stringify userEnvironmentData
+    userId               = JSON.stringify userId

     """
     <script type="text/javascript">


Update versions even if tag fetch failed

--- src/upgrade/sensor.coffee
+++ src/upgrade/sensor.coffee
@@ -6,18 +6,19 @@
 exports = routine()
 .s ->
   if expired() and read()
+    do touch
     []
   else
     false
 .a ->
-  write latest()
+  try write latest()
   fetch.versions()
   return
 .$()

 # Is file old?
 expired = ->
-  !path().ok 1000*60*60*24*3
+  !path().ok 1000*60*60*24*0.9

 # Path to file with latest version
 path = ->
@@ -102,3 +103,10 @@
     v.N = v.n or k
     result[v.k = k.toLowerCase()] = v
   result
+
+touch = ->
+  try
+    path()
+      .open 8, true
+      .Write '\n'
+  return


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/function_extensions.coffee
+++ test/function_extensions.coffee
@@ -24,7 +24,8 @@
 test "#once", ->
   score = 0

-  addScore =
+  addScore = ->
+

   onceScore = ->
     ( ->


remove condition on NODE_ENV

remove condition on NODE_ENV

--- config/initializers/logging.coffee
+++ config/initializers/logging.coffee
@@ -1,16 +1,12 @@
 module.exports = (compound) ->
     console.log = (text) ->
-        if process.env.NODE_ENV is "production"
-            compound.logger.write text
+        compound.logger.write text

     console.info = (text) ->
-        if process.env.NODE_ENV is "production"
-            compound.logger.write text
+        compound.logger.write text

     console.err = (text) ->
-        if process.env.NODE_ENV is "production"
-            compound.logger.write text
+        compound.logger.write text

     console.warn = (text) ->
-        if process.env.NODE_ENV is "production"
-            compound.logger.write text
+        compound.logger.write text


This should be it now :)

This should be it now :)

--- config/initializers/pub_sub.coffee
+++ config/initializers/pub_sub.coffee
@@ -4,14 +4,19 @@

 config = betturl.parse(process.env.REDIS_URL || {})

+create_client = () ->
+  client = redback.createClient(config.port, config.host)
+  client.auth(config.auth.password)
+  return client
+
 Caboose.app.channels = {
-  command: redback.createClient(config.port, config.host).createChannel('commands')
+  command: create_client().createChannel('commands')
 }

 return unless Caboose.command is 'server'

 channels = {
-  command: redback.createClient(config.port, config.host).createChannel('commands').subscribe()
+  command: create_client().createChannel('commands').subscribe()
 }

 _(channels).each (channel, type) ->


Change currently-focused to active

Change currently-focused to active

--- src/workspace-view.coffee
+++ src/workspace-view.coffee
@@ -249,17 +249,16 @@
   # Public: Focuses the next pane by id.
   focusNextPane: -> @model.activateNextPane()

-  # Public: Focuses the pane directly above the currently-focused pane.
+  # Public: Focuses the pane directly above the active pane.
   focusPaneAbove: -> @panes.focusPaneAbove()

-  # Public: Focuses the pane directly below the currently-focused pane.
+  # Public: Focuses the pane directly below the active pane.
   focusPaneBelow: -> @panes.focusPaneBelow()

-  # Public: Focuses the pane directly to the left of the currently-focused pane.
+  # Public: Focuses the pane directly to the left of the active pane.
   focusPaneOnLeft: -> @panes.focusPaneOnLeft()

-  # Public: Focuses the pane directly to the right of the currently-focused
-  # pane.
+  # Public: Focuses the pane directly to the right of the active pane.
   focusPaneOnRight: -> @panes.focusPaneOnRight()

   # Public:


account: remove fake credit card partial

--- client/Account/views/paymenthistorylistitem.coffee
+++ client/Account/views/paymenthistorylistitem.coffee
@@ -18,7 +18,6 @@

         #{data.amount / 100}
         #{dateFormat(data.periodEnd, 'mmm dd, yyyy')}
-        **** #{data.paymentMethod?.last4}

     """


handle failed compile due to validation error


CLEANUP: removed old code in Runtime/Iteration


Highlight Vim modelines

Highlight Vim modelines

--- grammars/viml.cson
+++ grammars/viml.cson
@@ -43,6 +43,7 @@
 			{include: "#todo"}
 			{include: "#folds"}
 			{include: "#comments"}
+			{include: "#modelines"}
 			{include: "#pathname"}
 			{include: "#strings"}
 			{include: "#hashbang"}
@@ -130,9 +131,27 @@

 	commentInnards:
 		patterns: [
+			{include: "#modelines"}
 			{include: "#folds"}
 			{include: "#todo"}
 		]
+
+
+	# Vim modelines
+	modelines:
+		patterns: [{
+
+			# Using "vim:set"
+			name: "string.other.modeline.viml"
+			patterns: [include: "$self"]
+			begin: "(?:(?:\\s|^)vi(?:m[<=>]?\\d+|m)?|[\\t\\x20]ex):\\s*(?=set?\\s)"
+			end:   ":|$"
+		},{
+			name: "string.other.modeline.viml"
+			patterns: [include: "$self"]
+			begin: "(?:(?:\\s|^)vi(?:m[<=>]?\\d+|m)?|[\\t\\x20]ex):"
+			end: "$"
+		}]

 	# Fold markers, with possible labels


Decrease the score for function substitutions which often result from currying


Fiiiiiiiiiiiiiiix

Fiiiiiiiiiiiiiiix

--- core.coffee
+++ core.coffee
@@ -84,7 +84,7 @@
 				per_page: 100
 		, (err, response, body) ->
 			return console.error "Error at fetchRepoCommits for repo #{repo.fullName}", err, response, body if err?
-			items = JSON.parse(body)
+			items = JSON.parse(body) ? []
 			if items.length > 0
 				if date isnt ''
 					items.shift()
@@ -120,7 +120,7 @@
 		, (err, response, body) ->
 			return console.error "Error at fetchCommit #{commit.sha}", err, response, body if err?
 			changes = []
-			items = JSON.parse(body).files
+			items = JSON.parse(body).files ? []
 			async.each items, (item, callback) ->
 				fileName = item.filename
 				changesMade = item.changes


Strengthen `trim` test to test it with writes that start or end mid-cell.

--- test/trim.coffee
+++ test/trim.coffee
@@ -141,7 +141,12 @@
      Test 8 ,," 800000,800000 ",""""
      Test 9 ,," 900000,900000 ",""""
     '''
-    parser.write i for i in buffer
+    # Intentionally create writes that get break in the middle of cells.
+    for i in [0...10]
+      if buffer.length > 18
+        parser.write buffer.substr 0, 18
+        buffer = buffer.substr 18
+    parser.write buffer
     parser.end()

 describe 'no trim', ->


Test text replacement on rpi cmdline.txt

--- tests/e2e.coffee
+++ tests/e2e.coffee
@@ -204,6 +204,20 @@
 			imagefs.read(output).then(utils.extract).then (contents) ->
 				utils.expect(contents, 'Elementum ipsum dolor sit amet\n')

+wary.it 'should replace cmdline.txt in a raspberry pi partition',
+	raspberrypi: RASPBERRYPI
+, (images) ->
+
+	cmdline =
+		image: images.raspberrypi
+		partition:
+			primary: 1
+		path: '/cmdline.txt'
+
+	imagefs.replace(cmdline, 'lpm_enable=0', 'lpm_enable=1').then(utils.waitStream).then ->
+		imagefs.read(cmdline).then(utils.extract).then (contents) ->
+			utils.expect(contents, 'dwc_otg.lpm_enable=1 console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 root=/dev/mmcblk0p2 rootfstype=ext4 rootwait \n')
+
 wary.it 'should replace a file in a raspberry pi partition with a regex',
 	raspberrypi: RASPBERRYPI
 	lorem: LOREM


* Correct treatment of conflicts and synchronization when data has been locally changed and is being server synched

--- app/assets/javascripts/backbone.rails_store.js.coffee
+++ app/assets/javascripts/backbone.rails_store.js.coffee
@@ -758,7 +758,7 @@
       serverModel.trigger('refresh:done', serverModel)
       return serverModel
     else
-      # Must check if conflicts
+      # Must check for conflicts
       serverData = storedModel.parse(model)
       syncAttr = storedModel.syncAttributes
       serverChangedAttr = storedModel.syncChangedAttributes(serverData)
@@ -777,6 +777,9 @@
         storedModel.trigger('change', storedModel)
         @reportSync(storedModel)
         storedModel.syncAttributes = _.extend(syncAttr, noConflictAttr)
+        localChangedAttr = storedModel.syncChangedAttributes(storedModel.attributes)
+        if localChangedAttr
+          @_registerModelChange(storedModel)
         if hasConflict
           storedModel.trigger("refresh:conflict", storedModel)
           return storedModel
@@ -785,7 +788,7 @@
       else if serverChangedAttr
         storedModel.set(serverChangedAttr, {storeSync:true, silent:true})
         storedModel.trigger('change', storedModel)
-      @reportSync(storedModel)
+        @reportSync(storedModel)

       storedModel.trigger('refresh:done', storedModel)
       return storedModel
@@ -798,7 +801,7 @@
     unless model.railsClass
       return false
     modelType = @getModelType(model)
-    unless _.isEqual model.syncAttributes, model.attributes
+    if model.syncChangedAttributes(model.attributes)
       @_changedModels[modelType] = [] unless @_changedModels[modelType]
       @_changedModels[modelType].push(model)
       @_changedModels[modelType] = _.uniq(@_changedModels[modelType])
@@ -931,19 +934,6 @@
     @_store.refresh options

   ###
-    changedAttributes - override to treat relations
-  ###
-  changedAttributes: (attr) ->
-    changedAttr = super
-    if attr? and changedAttr
-      _.each changedAttr, (value, key) =>
-        # TODO: Should iterate over hasMany to check if it has changed
-        if @belongsTo[key] or @hasOne[key] or @hasMany[key] or @hasAndBelongsToMany[key]
-          delete changedAttr[key]
-      changedAttr = false if _.isEmpty(changedAttr)
-    return changedAttr
-
-  ###
     setPersistent - change persistent model state

     Models marked as persistent are not released from Store upon releaseAll call
@@ -980,13 +970,17 @@
   ###
   syncChangedAttributes: (attr) ->
     changed = {}
-    _.each attr, (value, key) =>
+    parsedAttr = @_parseAttributeModifiers(attr)
+    _.each parsedAttr, (value, key) =>
       unless (@belongsTo[key] or @hasMany[key] or @hasAndBelongsToMany[key] or @hasOne[key])
         if _.isArray(@syncAttributes[key]) and _.isArray(value)
           diff = _.difference(@syncAttributes[key], value)
           if diff.length
             changed[key] = attr[key]
-        else if @syncAttributes[key] != attr[key]
+        else if value instanceof Date
+          unless (@syncAttributes[key] instanceof Date and Date.equals(@syncAttributes[key], value))
+            changed[key] = attr[key]
+        else if @syncAttributes[key] != value
           changed[key] = attr[key]
       else
         changed[key] = attr[key]


:art: Clean up the specs

:art: Clean up the specs

--- spec/soft-wrap-indicator-spec.coffee
+++ spec/soft-wrap-indicator-spec.coffee
@@ -1,31 +1,27 @@
+SoftWrapIndicator = require '../lib/soft-wrap-indicator'
+
 describe 'SoftWrapIndicator', ->
-  [softWrapIndicator] = []
+  [indicator] = []

   beforeEach ->
     workspaceElement = atom.views.getView(atom.workspace)
     jasmine.attachToDOM(workspaceElement)

-    waitsForPromise ->
-      atom.packages.activatePackage('status-bar')
-
-    waitsForPromise ->
-      atom.packages.activatePackage('language-javascript')
-
-    waitsForPromise ->
-      atom.packages.activatePackage('language-gfm')
-
-    waitsForPromise ->
-      atom.packages.activatePackage('soft-wrap-indicator')
+    waitsForPromise -> atom.packages.activatePackage('status-bar')
+    waitsForPromise -> atom.packages.activatePackage('language-javascript')
+    waitsForPromise -> atom.packages.activatePackage('language-gfm')
+    waitsForPromise -> atom.packages.activatePackage('soft-wrap-indicator')

     runs ->
-      softWrapIndicator = document.querySelector('soft-wrap-indicator')
-      expect(softWrapIndicator).toExist()
+      indicator = SoftWrapIndicator.view
+      expect(indicator).toExist()

-  it 'has the text "Wrap"', ->
-    expect(softWrapIndicator.wrapLink.textContent).toBe 'Wrap'
+  describe 'when no editor is open', ->
+    it 'has the text "Wrap"', ->
+      expect(indicator.wrapLink.textContent).toBe 'Wrap'

-  it 'hides the indicator when there is no open editor', ->
-    expect(softWrapIndicator).toBeHidden()
+    it 'hides the indicator when there is no open editor', ->
+      expect(indicator).toBeHidden()

   describe 'when an editor is open', ->
     [editor] = []
@@ -38,37 +34,37 @@
         editor = atom.workspace.getActiveTextEditor()

     it 'shows the indicator', ->
-      expect(softWrapIndicator).not.toBeHidden()
+      expect(indicator).not.toBeHidden()

     it 'is not lit when the grammar is not soft wrapped', ->
-      expect(softWrapIndicator.wrapLink.classList.contains('lit')).toBeFalsy()
+      expect(indicator.wrapLink.classList.contains('lit')).toBeFalsy()

     it 'is lit when the grammar is soft wrapped', ->
       waitsForPromise ->
         atom.workspace.open('sample.md')

       runs ->
-        expect(softWrapIndicator.wrapLink.classList.contains('lit')).toBeTruthy()
+        expect(indicator.wrapLink.classList.contains('lit')).toBeTruthy()

     it 'is lit when the soft wrap setting is changed', ->
       editor.toggleSoftWrapped()

-      expect(softWrapIndicator.wrapLink.classList.contains('lit')).toBeTruthy()
+      expect(indicator.wrapLink.classList.contains('lit')).toBeTruthy()

     it 'is lit when the grammar is changed to a soft wrapped grammar', ->
       grammar = atom.grammars.grammarForScopeName('source.gfm')
       editor.setGrammar(grammar)

-      expect(softWrapIndicator.wrapLink.classList.contains('lit')).toBeTruthy()
+      expect(indicator.wrapLink.classList.contains('lit')).toBeTruthy()

     describe 'when clicked', ->
       it 'toggles the soft wrap value', ->
         spyOn(editor, 'toggleSoftWrapped')
-        softWrapIndicator.click()
+        indicator.click()
         expect(editor.toggleSoftWrapped).toHaveBeenCalled()

   describe 'when the package is deactivated', ->
     it 'removes the indicator', ->
       atom.packages.deactivatePackage('soft-wrap-indicator')

-      expect(softWrapIndicator.parentElement).toBeNull()
+      expect(indicator.parentElement).toBeNull()


Unbreak frontend polling

Unbreak frontend polling

--- common/lib/xmodule/xmodule/js/src/capa/display.coffee
+++ common/lib/xmodule/xmodule/js/src/capa/display.coffee
@@ -40,8 +40,9 @@
   poll: =>
     $.postWithPrefix "#{@url}/problem_get", (response) =>
       @el.html(response.html)
-      @executeProblemScripts()
-      @bind()
+      @executeProblemScripts () =>
+        @setupInputTypes()
+        @bind()

       @queued_items = @$(".xqueue")
       if @queued_items.length == 0


storage: in safe move, don't re-issue the zfs rename command if one is already running...

--- salvus/storage.coffee
+++ salvus/storage.coffee
@@ -2366,25 +2366,38 @@
             if opts.safe
                 f = filesystem(opts.project_id)
                 t = filesystem("DELETED-#{cassandra.now()}-#{opts.project_id}")
-                dbg("safely delete dataset (really just rename to #{t}")
+                dbg("safely delete dataset (really just rename to #{t})")
                 execute_on
-                    host    : opts.host
-                    command : "sudo zfs rename #{f} #{t}"
-                    timeout : 300
+                    host          : opts.host
+                    command       : "ps ax | grep 'zfs rename' | grep #{opts.project_id}"
+                    timeout       : 120
+                    err_on_exit   : false
+                    err_on_stderr : false
                     cb      : (err, output) ->
                         if err
-                            if output?.stderr? and output.stderr.indexOf('does not exist') != -1
-                                does_not_exist = true
-                                err = undefined
-                        if err or does_not_exist
-                            cb(err)
+                            cb("unable to connect to '#{opts.host}'")
+                        else if output.stdout.indexOf(opts.project_id) != -1
+                            dbg("already renaming -- left from previous attempt, since zfs is filling up with work...")
+                            cb("unable to delete data set due zfs being too busy")
                         else
-                            dbg("unset the mountpoint, or we'll have trouble later")
                             execute_on
                                 host    : opts.host
-                                command : "sudo zfs set mountpoint=none #{t}"
-                                timeout : 120
-                                cb      : cb
+                                command : "sudo zfs rename #{f} #{t}"
+                                timeout : 360
+                                cb      : (err, output) ->
+                                    if err
+                                        if output?.stderr? and output.stderr.indexOf('does not exist') != -1
+                                            does_not_exist = true
+                                            err = undefined
+                                    if err or does_not_exist
+                                        cb(err)
+                                    else
+                                        dbg("unset the mountpoint, or we'll have trouble later")
+                                        execute_on
+                                            host    : opts.host
+                                            command : "sudo zfs set mountpoint=none #{t}"
+                                            timeout : 120
+                                            cb      : cb
             else
                 dbg("unsafely destroying dataset")
                 # I don't want to ever,ever do this, so...
@@ -3131,10 +3144,64 @@

-
-
-
-
-
-
-
+############################################
+# Projects that are stored on a given node
+# In case we have to recover a node from scratch
+# for some reason, it is useful to be able to get a list
+# of the project_id's of projects that are supposed
+# to be available on that node according to
+# consistent hashing.
+#   x={}; s.projects_on_node(host:'10.1.2.4',cb:(e,t)->x.t=t)
+#   fs.writeFileSync('projects-day',x.t.join('\n'))
+############################################
+
+filter_by_host = (projects, host) ->
+    v = (x[0] for x in projects when host in _.flatten(locations(project_id:x[0])))
+    v.sort()
+    return v
+
+
+exports.all_projects_on_host = (opts) ->
+    opts = defaults opts,
+        host : required  # ip address
+        cb   : required  # cb(err, [list of project id's])
+    database.select
+        table   : 'projects'
+        columns : ['project_id']
+        limit   : 100000   # TODO: stupidly slow
+        cb      : (err, projects) ->
+            if err
+                opts.cb(err); return
+            winston.debug("got #{projects.length} projects")
+            v = filter_by_host(projects, opts.host)
+            winston.debug("of these,#{v.length} are on '#{opts.host}'")
+            opts.cb(undefined, v)
+
+#  x={}; s.projects_on_node(host:'10.1.2.4',cb:(e,t)->x.t=t)
+#   fs.writeFileSync('projects-day',x.t.join('\n'))
+
+exports.recent_projects_on_host = (opts) ->
+    opts = defaults opts,
+        host : required  # ip address
+        time : required  # 'short', 'day', 'week', 'month'
+        cb   : required  # cb(err, [list of project id's])
+    database.select
+        table   : 'recently_modified_projects'
+        columns : ['project_id']
+        limit   : 100000   # TODO: stupidly slow
+        where   : {ttl:opts.time}
+        cb      : (err, projects) ->
+            if err
+                opts.cb(err); return
+            winston.debug("got #{projects.length} projects")
+            v = filter_by_host(projects, opts.host)
+            winston.debug("of these,#{v.length} are on '#{opts.host}'")
+            opts.cb(undefined, v)
+
+
+
+
+
+
+
+


Added spec for default delay

Added spec for default delay

--- test.coffee
+++ test.coffee
@@ -252,6 +252,41 @@
               result.should.equal 'ruff!'
               done()

+    describe 'when yielding in order without any delay specified', ->
+
+      result = null
+      beforeEach (done) ->
+        liar = create_liar [
+          function_name: 'query'
+          yields_in_order: [
+            {
+              argument_1:
+                value: '47 ninjas'
+            }
+          ]
+        ]
+
+        liar.query (r) ->
+          result = r
+
+        done()
+
+      it 'should not have yielded after 49ms', (done) ->
+
+        setTimeout ->
+          should.not.exist result
+          done()
+        , 10
+
+      it 'should have yielded after 50ms', (done) ->
+
+        setTimeout ->
+          result.should.equal '47 ninjas'
+          done()
+        ,50
+
+
+
   it 'should support on_value for callback arguments', (done) ->

     liar = create_liar [


Increase time for checking new posts

Increase time for checking new posts

--- javascripts/background.coffee
+++ javascripts/background.coffee
@@ -277,7 +277,7 @@
 chrome.runtime.onInstalled.addListener ->
   chrome.alarms.create "update_posts",
     when: Date.now() + 1000
-    periodInMinutes: 1.0
+    periodInMinutes: 5.0

 chrome.storage.local.get 'posts_count': {}, (items) ->
   postsCount = items.posts_count


pointer to profile menu during forum editing showing more settings

--- @client/profile_menu.coffee
+++ @client/profile_menu.coffee
@@ -36,6 +36,11 @@

     light_background = loc.url != '/' || is_light_background()

+
+    edit_forum = fetch 'edit_forum'
+
+
+
     DIV
       id: 'user_nav'
       style: _.defaults {}, (customization('profile_menu_style') or {}), (@props.style or {}),
@@ -44,6 +49,8 @@
         right: 30
         fontSize: 26
         top: 17
+
+

       if current_user.logged_in
         DropMenu
@@ -167,7 +174,36 @@

               translator "auth.log_in", "Log in"
-
+
+      if edit_forum.editing
+        is_light = is_light_background()
+        color = if is_light then 'black' else 'white'
+        DIV
+          style:
+            fontSize: 14
+            fontWeight: 600
+            color: color
+            display: 'flex'
+            alignItems: 'center'
+            flexDirection: 'column'
+          SVG
+            style:
+              # position: 'absolute'
+              top: 75
+              display: 'block'
+              transform: "scale(-1, 1)"
+            width: 14
+            height: 43
+            viewBox: "0 0 67 204"
+
+            dangerouslySetInnerHTML: __html: """
+              <g fill="none">
+                <path stroke-width="4.761904761904762" stroke="#{color}" d="M1.62120606,0.112317888 C1.62120606,0.112317888 -3.81550783,47.7673271 15.7617242,109.624892 C35.3389562,171.482458 65.9279782,203.300407 65.9279782,203.300407">
+                </path>
+              </g>"""
+
+          "More settings available"
+
   bitcoinVerification: ->
     current_user = fetch('/current_user')
     subdomain = fetch('/subdomain')


fix for ckeditor

fix for ckeditor

--- app/assets/javascripts/rails_admin/ra.widgets.coffee
+++ app/assets/javascripts/rails_admin/ra.widgets.coffee
@@ -150,11 +150,7 @@

     goCkeditors = ->
       $('form [data-richtext=ckeditor]').not('.ckeditored').each (index, domEle) ->
-        options = $(this).data
-        if instance = window.CKEDITOR.instances[this.id]
-          instance.destroy(true)
-
-        window.CKEDITOR.replace(this, options['options'])
+        window.CKEDITOR.replace(this, $(this).data['options'])
         $(this).addClass('ckeditored')

     $editors = $('form [data-richtext=ckeditor]').not('.ckeditored')


Do not touch the methods var, just filter the selector to get the selected method

--- app/assets/javascripts/store/spree_conekta.js.coffee
+++ app/assets/javascripts/store/spree_conekta.js.coffee
@@ -7,8 +7,8 @@
     currentMethod: null

     constructor: (@form)->
-      @methods       = @form.find 'input[name="order[payments_attributes][][payment_method_id]"]:checked'
-      @currentMethod = @methods.val()
+      @methods       = @form.find 'input[name="order[payments_attributes][][payment_method_id]"]'
+      @currentMethod = @methods.filter(':checked').val()
       @listenMethodChange()
       @listenSubmit()


Adding internationalization to date selector

--- app/coffee/modules/common/custom-field-values.coffee
+++ app/coffee/modules/common/custom-field-values.coffee
@@ -118,10 +118,10 @@
         template: templateFn
     }

-module.directive("tgCustomAttributesValues", ["$tgTemplate", "$tgStorage", CustomAttributesValuesDirective])
-
-
-CustomAttributeValueDirective = ($template, $selectedText, $compile) ->
+module.directive("tgCustomAttributesValues", ["$tgTemplate", "$tgStorage", "$translate", CustomAttributesValuesDirective])
+
+
+CustomAttributeValueDirective = ($template, $selectedText, $compile, $translate) ->
     template = $template.get("custom-attributes/custom-attribute-value.html", true)
     templateEdit = $template.get("custom-attributes/custom-attribute-value-edit.html", true)

@@ -158,9 +158,40 @@
                       selectedDate = date
                 onOpen: =>
                     $el.picker.setDate(selectedDate) if selectedDate?
-                firstDay: 1
-                format: 'DD MMM YYYY'
-                )
+                i18n: {
+                    previousMonth: $translate.instant("COMMON.PICKERDATE.PREV_MONTH"),
+                    nextMonth:  $translate.instant("COMMON.PICKERDATE.NEXT_MONTH"),
+                    months: [$translate.instant("COMMON.PICKERDATE.MONTHS.JAN"),
+                             $translate.instant("COMMON.PICKERDATE.MONTHS.FEB"),
+                             $translate.instant("COMMON.PICKERDATE.MONTHS.MAR"),
+                             $translate.instant("COMMON.PICKERDATE.MONTHS.APR"),
+                             $translate.instant("COMMON.PICKERDATE.MONTHS.MAY"),
+                             $translate.instant("COMMON.PICKERDATE.MONTHS.JUN"),
+                             $translate.instant("COMMON.PICKERDATE.MONTHS.JUL"),
+                             $translate.instant("COMMON.PICKERDATE.MONTHS.AUG"),
+                             $translate.instant("COMMON.PICKERDATE.MONTHS.SEP"),
+                             $translate.instant("COMMON.PICKERDATE.MONTHS.OCT"),
+                             $translate.instant("COMMON.PICKERDATE.MONTHS.NOV"),
+                             $translate.instant("COMMON.PICKERDATE.MONTHS.DEC")],
+                    weekdays: [$translate.instant("COMMON.PICKERDATE.WEEK_DAYS.SUN"),
+                               $translate.instant("COMMON.PICKERDATE.WEEK_DAYS.MON"),
+                               $translate.instant("COMMON.PICKERDATE.WEEK_DAYS.TUE"),
+                               $translate.instant("COMMON.PICKERDATE.WEEK_DAYS.WED"),
+                               $translate.instant("COMMON.PICKERDATE.WEEK_DAYS.THU"),
+                               $translate.instant("COMMON.PICKERDATE.WEEK_DAYS.FRI"),
+                               $translate.instant("COMMON.PICKERDATE.WEEK_DAYS.SAT")],
+                    weekdaysShort: [$translate.instant("COMMON.PICKERDATE.WEEK_DAYS_SHORT.SUN"),
+                                    $translate.instant("COMMON.PICKERDATE.WEEK_DAYS_SHORT.MON"),
+                                    $translate.instant("COMMON.PICKERDATE.WEEK_DAYS_SHORT.TUE"),
+                                    $translate.instant("COMMON.PICKERDATE.WEEK_DAYS_SHORT.WED"),
+                                    $translate.instant("COMMON.PICKERDATE.WEEK_DAYS_SHORT.THU"),
+                                    $translate.instant("COMMON.PICKERDATE.WEEK_DAYS_SHORT.FRI"),
+                                    $translate.instant("COMMON.PICKERDATE.WEEK_DAYS_SHORT.SAT")]
+                },
+                isRTL: $translate.instant("COMMON.PICKERDATE.IS_RTL") == "true",
+                firstDay: parseInt($translate.instant("COMMON.PICKERDATE.FIRST_DAY_OF_WEEK"), 10),
+                format: $translate.instant("COMMON.PICKERDATE.FORMAT")
+              )

         isEditable = ->
             permissions = $scope.project.my_permissions
@@ -218,4 +249,4 @@
         restrict: "AE"
     }

-module.directive("tgCustomAttributeValue", ["$tgTemplate", "$selectedText", "$compile", CustomAttributeValueDirective])
+module.directive("tgCustomAttributeValue", ["$tgTemplate", "$selectedText", "$compile", "$translate", CustomAttributeValueDirective])


Reordered alerts in legend

Reordered alerts in legend

--- muleview/app/controller/AlertsReportController.coffee
+++ muleview/app/controller/AlertsReportController.coffee
@@ -83,29 +83,27 @@
         ans.push "s" if subtract > 1
     ans.join("")

-  alertTypes:
-    "critical_low":
+  alertComponents: [
+      name: "critical_high"
+      label: "Critical High"
+      color: "red"
+    ,
+      name: "warning_high"
+      label: "Warning High"
+      color: "orange"
+    ,
+      name: "warning_low"
+      label: "Warning Low"
+      color: "orange"
+    ,
+      name: "critical_low"
       label: "Critical Low"
       color: "red"
-
-    "warning_low":
-      label: "Warning Low"
-      color: "yellow"
-
-    "warning_high":
-      label: "Warning High"
-      color: "red"
-
-    "critical_high":
-      label: "Critical High"
-      color: "yellow"
+  ]

   toGraphArray: () ->
-    for key, attributes of @alertTypes
-      name: key
-      label: attributes.label
-      color: attributes.color
-      value: @get(key)
+    for cmp in @alertComponents
+      Ext.apply({value: @get(cmp.name)}, cmp)

 # ==== Store =====================================================
 Ext.define "Muleview.store.AlertsStore",