Bacon.fromPromise: call promise.abort() on unsub

--- src/Bacon.coffee
+++ src/Bacon.coffee
@@ -59,8 +59,11 @@
   , eventTransformer

 Bacon.fromPromise = (promise) ->
-  Bacon.fromCallback (handler) ->
+  Bacon.fromBinder (handler) ->
     promise.then(handler, (e) -> handler(new Error(e)))
+    -> promise.abort()
+  , (value) -> [value, end()]
+

 Bacon.noMore = ["<no-more>"]


CLI: making the 'server' command to log POST data in the console

--- cli/commands/server.coffee
+++ cli/commands/server.coffee
@@ -11,7 +11,7 @@
   source  = require('../source')
   package = require('../package')
   express = require('express')
-  server  = express.createServer()
+  server  = express.createServer(express.bodyParser())
   port    = args[0] || lovelyrc.port || 3000
   base    = lovelyrc.base
   minify  = false
@@ -84,6 +84,8 @@
         when 'swf' then return 'application/x-shockwave-flash'
         else            return 'text/html'

+    if req.method is 'POST'
+      console.log("\n POST:     ", JSON.stringify(req.body).grey)

     if filename = file_in(process.cwd())
       console.log("") if filename.substr(filename.length-4) is 'html'


Add Pretty Diff support for Mustache

Add Pretty Diff support for Mustache

--- src/beautifiers/prettydiff.coffee
+++ src/beautifiers/prettydiff.coffee
@@ -49,6 +49,7 @@
     EJS: true
     HTML: true
     Handlebars: true
+    Mustache: true
     Nunjucks: true
     XML: true
     SVG: true


:lipstick:

:lipstick:

--- src/atom/renderer.coffee
+++ src/atom/renderer.coffee
@@ -137,34 +137,35 @@
   buildLineForBufferRow: (bufferRow) ->
     @buildLinesForBufferRows(bufferRow, bufferRow)

-  buildLinesForBufferRows: (startRow, endRow) ->
-    recursiveBuildLinesForBufferRows = (startRow, endRow, startColumn, currentScreenLineLength=0) =>
-      return [] if startRow > endRow and not startColumn?
-
-      startColumn ?= 0
-      line = @highlighter.lineForRow(startRow).splitAt(startColumn)[1]
-
-      wrapColumn = @findWrapColumn(line.text, @maxLineLength - currentScreenLineLength)
-
-      for fold in @foldsForBufferRow(startRow)
-        if fold.start.column >= startColumn
-          if (fold.start.column - startColumn) > wrapColumn - foldPlaceholderLength
-            wrapColumn = Math.min(wrapColumn, fold.start.column - startColumn)
+  buildLinesForBufferRows: (startBufferRow, endBufferRow) ->
+    recursiveBuildLinesForBufferRows = (startBufferRow, endBufferRow, startBufferColumn, currentScreenLineLength=0) =>
+      return [] if startBufferRow > endBufferRow and not startBufferColumn?
+
+      startBufferColumn ?= 0
+      line = @highlighter.lineForRow(startBufferRow).splitAt(startBufferColumn)[1]
+
+      wrapScreenColumn = @findWrapColumn(line.text, @maxLineLength - currentScreenLineLength)
+
+      for fold in @foldsForBufferRow(startBufferRow)
+        if fold.start.column >= startBufferColumn
+          foldStartSceenColumn = fold.start.column - startBufferColumn
+          if (foldStartSceenColumn) > wrapScreenColumn - foldPlaceholderLength
+            wrapScreenColumn = Math.min(wrapScreenColumn, foldStartSceenColumn)
             break
-          prefix = line.splitAt(fold.start.column - startColumn)[0]
+          prefix = line.splitAt(foldStartSceenColumn)[0]
           placeholder = @buildFoldPlaceholder(fold)
           currentScreenLineLength = currentScreenLineLength + (prefix?.text.length ? 0) + foldPlaceholderLength
-          suffix = recursiveBuildLinesForBufferRows(fold.end.row, endRow, fold.end.column, currentScreenLineLength)
+          suffix = recursiveBuildLinesForBufferRows(fold.end.row, endBufferRow, fold.end.column, currentScreenLineLength)
           return _.compact _.flatten [prefix, placeholder, suffix]

-      if wrapColumn?
-        line = line.splitAt(wrapColumn)[0]
+      if wrapScreenColumn?
+        line = line.splitAt(wrapScreenColumn)[0]
         line.screenDelta = new Point(1, 0)
-        [line].concat recursiveBuildLinesForBufferRows(startRow, endRow, startColumn + wrapColumn)
+        [line].concat recursiveBuildLinesForBufferRows(startBufferRow, endBufferRow, startBufferColumn + wrapScreenColumn)
       else
-        [line].concat recursiveBuildLinesForBufferRows(startRow + 1, endRow)
-
-    recursiveBuildLinesForBufferRows(@foldStartRowForBufferRow(startRow), endRow)
+        [line].concat recursiveBuildLinesForBufferRows(startBufferRow + 1, endBufferRow)
+
+    recursiveBuildLinesForBufferRows(@foldStartRowForBufferRow(startBufferRow), endBufferRow)

   foldStartRowForBufferRow: (bufferRow) ->
     @bufferRowForScreenRow(@screenRowForBufferRow(bufferRow))


UpdateStackTemplate: take config into account when saving the stack template

--- client/admin/lib/views/stacks/updatestacktemplate.coffee
+++ client/admin/lib/views/stacks/updatestacktemplate.coffee
@@ -4,7 +4,7 @@
 module.exports = updateStackTemplate = (data, callback) ->

   { template, templateDetails, credential
-    title, stackTemplate, machines } = data
+    title, stackTemplate, machines, config } = data

   title     or= 'Default stack template'
   credentials = {}
@@ -13,12 +13,18 @@
     credentials[credential.provider] = [credential.identifier]

   if stackTemplate?.update
+
     dataToUpdate = if machines \
-      then {machines} else {title, template, credentials, templateDetails}
+      then { machines } else {
+        title, template, credentials, templateDetails, config
+      }
+
     stackTemplate.update dataToUpdate, (err) ->
       callback err, stackTemplate
+
   else
+
     remote.api.JStackTemplate.create {
-      title, template, credentials, templateDetails
+      title, template, credentials, templateDetails, config
     }, callback


notification: we now have notifications again

- this time in a more secure way.

--- client/landing/app/MainApp/notificationcontroller.coffee
+++ client/landing/app/MainApp/notificationcontroller.coffee
@@ -19,6 +19,7 @@

     @getSingleton('mainController').on "AccountChanged", =>
       @off 'NotificationHasArrived'
+      @notificationChannel?.off().unsubscribe()
       @setListeners()

   setListeners:->
@@ -27,7 +28,7 @@
       serviceType : 'notification'
       isExclusive : yes

-    @notificationChannel.on 'notification', (notification)=>
+    @notificationChannel.on 'message', (notification)=>
       @emit "NotificationHasArrived", notification
       @prepareNotification notification  if notification.contents


Fix variable scoping in examples:* gulp tasks

--- bokehjs/gulp/tasks/examples.coffee
+++ bokehjs/gulp/tasks/examples.coffee
@@ -15,7 +15,8 @@
 examples = ["anscombe", "burtin", "legends", "tap"]

 for example in examples
-  gulp.task "examples:#{example}", () -> compile(example)
+  do (example) ->
+    gulp.task "examples:#{example}", () -> compile(example)

 gulp.task "examples", ["scripts:build", "styles:build"], (cb) ->
   run(("examples:#{example}" for example in examples), cb)


change addAppRow to addApplication

change addAppRow to addApplication

--- client/app/collections/application.coffee
+++ client/app/collections/application.coffee
@@ -18,8 +18,8 @@
     onReset: =>
         @view.clearApps()
         @forEach (app) =>
-            @view.addAppRow app
+            @view.addApplication app

     # Add added app to the view app list.
     onAdd: (app) =>
-        @view.addAppRow app
+        @view.addApplication app


Revamp messages-list markup

Revamp messages-list markup

This commit contains the new markup for the messages list, currently
unstyled.

--- client/app/components/message-list.coffee
+++ client/app/components/message-list.coffee
@@ -244,57 +244,56 @@
             #             accountID: @props.accountID
             #             mailboxID: @props.mailboxID

-            # if @props.messages.count() is 0
-            #     if @props.fetching
-            #         p null, t 'list fetching'
-            #     else
-            #         p null, @props.emptyListMessage
-            # else
-            #     div null,
-            #         #p null, @props.counterMessage
-            #         MessageListBody
-            #             messages: @props.messages
-            #             settings: @props.settings
-            #             mailboxID: @props.mailboxID
-            #             messageID: @props.messageID
-            #             conversationID: @props.conversationID
-            #             conversationLengths: @props.conversationLengths
-            #             login: @props.login
-            #             edited: @state.edited
-            #             selected: @state.selected
-            #             allSelected: @state.allSelected
-            #             displayConversations: @props.displayConversations
-            #             isTrash: @props.isTrash
-            #             ref: 'listBody'
-            #             onSelect: (id, val) =>
-            #                 selected = _.clone @state.selected
-            #                 if val
-            #                     selected[id] = val
-            #                 else
-            #                     delete selected[id]
-            #                 if Object.keys(selected).length > 0
-            #                     newState =
-            #                         edited: true
-            #                         selected: selected
-            #                 else
-            #                     newState =
-            #                         allSelected: false
-            #                         edited: false
-            #                         selected: {}
-            #                 @setState newState
-
-            #         if @props.query.pageAfter isnt '-'
-            #             p className: 'text-center list-footer',
-            #                 if @props.fetching
-            #                     Spinner()
-            #                 else
-            #                     a
-            #                         className: 'more-messages'
-            #                         onClick: nextPage,
-            #                         ref: 'nextPage',
-            #                         t 'list next page'
-            #         else
-            #             p ref: 'listEnd', t 'list end'
+            if @props.messages.count() is 0
+                if @props.fetching
+                    p null, t 'list fetching'
+                else
+                    p null, @props.emptyListMessage
+            else
+                div className: 'mainContent',
+                    MessageListBody
+                        messages: @props.messages
+                        settings: @props.settings
+                        mailboxID: @props.mailboxID
+                        messageID: @props.messageID
+                        conversationID: @props.conversationID
+                        conversationLengths: @props.conversationLengths
+                        login: @props.login
+                        edited: @state.edited
+                        selected: @state.selected
+                        allSelected: @state.allSelected
+                        displayConversations: @props.displayConversations
+                        isTrash: @props.isTrash
+                        ref: 'listBody'
+                        onSelect: (id, val) =>
+                            selected = _.clone @state.selected
+                            if val
+                                selected[id] = val
+                            else
+                                delete selected[id]
+                            if Object.keys(selected).length > 0
+                                newState =
+                                    edited: true
+                                    selected: selected
+                            else
+                                newState =
+                                    allSelected: false
+                                    edited: false
+                                    selected: {}
+                            @setState newState
+
+                    if @props.query.pageAfter isnt '-'
+                        p className: 'text-center list-footer',
+                            if @props.fetching
+                                Spinner()
+                            else
+                                a
+                                    className: 'more-messages'
+                                    onClick: nextPage,
+                                    ref: 'nextPage',
+                                    t 'list next page'
+                    else
+                        p ref: 'listEnd', t 'list end'

     toggleEdited: ->
         if @state.edited
@@ -434,16 +433,14 @@
     render: ->
         message = @props.message
         flags = message.get('flags')
+
         classes = classer
             message: true
-            read: message.get 'isRead'
-            active: @props.isActive
-            edited: @props.edited
-            'unseen': flags.indexOf(MessageFlags.SEEN) is -1
-            'has-attachments': message.get 'hasAttachments'
-            'is-fav': flags.indexOf(MessageFlags.FLAGGED) isnt -1
-
-        isDraft   = message.get('flags').indexOf(MessageFlags.DRAFT) isnt -1
+            read:    message.get 'isRead'
+            active:  @props.isActive
+            edited:  @props.edited
+
+        isDraft = MessageFlags.DRAFT in flags

         if isDraft and not @props.isTrash
             action = 'edit'
@@ -460,10 +457,12 @@
                 action = 'message'
                 params =
                     messageID: message.get 'id'
+
         url = @buildUrl
             direction: 'second'
             action: action
             parameters: params
+
         if not @props.edited
             tag = a
         else
@@ -473,49 +472,63 @@
         date    = MessageUtils.formatDate message.get('createdAt'), compact
         avatar  = MessageUtils.getAvatar message
         text    = message.get('text')
-        preview = if text? then text.substr(0, 100) + "…" else ''

         li
-            className: classes
-            key: @props.key
-            'data-message-id': message.get('id')
+            className:              classes
+            key:                    @props.key
+            'data-message-id':      message.get('id')
             'data-conversation-id': message.get('conversationID')
-            draggable: not @props.edited
-            onClick: @onMessageClick
-            onDragStart: @onDragStart
-        ,
+            draggable:              not @props.edited
+            onClick:                @onMessageClick
+            onDragStart:            @onDragStart,
+
             tag
-                href: url,
-                className: 'wrapper',
-                'data-message-id': message.get('id'),
-                onClick: @onMessageClick,
-                onDoubleClick: @onMessageDblClick,
-                ref: 'target',
-                    div
-                        className: 'avatar-wrapper select-target',
-                        input
-                            ref: 'select'
-                            className: 'select select-target',
-                            type: 'checkbox',
-                            checked: @props.selected,
-                            onChange: @onSelect
-                        if avatar?
-                            img className: 'avatar', src: avatar
-                        else
-                            i className: 'fa fa-user'
-                    span className: 'participants', @getParticipants message
+                href:              url
+                className:         'wrapper'
+                'data-message-id': message.get('id')
+                onClick:           @onMessageClick
+                onDoubleClick:     @onMessageDblClick
+                ref:               'target'
+
+                div className: 'markers-wrapper',
+                    if MessageFlags.SEEN in flags
+                        i className: 'fa fa-circle-thin'
+                    else
+                        i className: 'fa fa-circle'
+                    if MessageFlags.FLAGGED in flags
+                        i className: 'fa fa-star'
+
+                div className: 'avatar-wrapper select-target',
+                    input
+                        ref:       'select'
+                        className: 'select select-target',
+                        type:      'checkbox',
+                        checked:   @props.selected,
+                        onChange:  @onSelect
+
+                    if avatar?
+                        img className: 'avatar', src: avatar
+                    else
+                        i className: 'avatar placeholder',
+                            message.get('from')[0].name[0]
+
+                div className: 'metas-wrapper',
+                    div className: 'participants',
+                        @getParticipants message
+                    div className: 'subject',
+                        message.get 'subject'
+                    div className: 'date',
+                        # TODO: use time-elements component here for the date
+                        date
+                    div className: 'extras',
+                        if message.get 'hasAttachments'
+                            i className: 'attachments fa fa-paperclip'
+                        if  @props.displayConversations and
+                            @props.conversationLengths > 1
+                                div className: 'badge conversation-length',
+                                    @props.conversationLengths
                     div className: 'preview',
-                        if @props.displayConversations and
-                           @props.conversationLengths > 1
-                            span className: 'badge conversation-length',
-                                @props.conversationLengths
-                        span className: 'title',
-                            message.get 'subject'
-                        p null, preview
-                    span className: 'hour', date
-                    span className: "flags",
-                        i className: 'attach fa fa-paperclip'
-                        i className: 'fav fa fa-star'
+                        text.substr(0, 1024)

     _doCheck: ->
         # please don't ask me why this **** react needs this


activity: new endpoints for channel creation with participants and sending message to channel

--- client/app/lib/socialapicontroller.coffee
+++ client/app/lib/socialapicontroller.coffee
@@ -616,6 +616,16 @@
       fnName             : 'listLikers'
       validateOptionsWith: ['id']

+    createChannelWithParticipants   : messageRequesterFn
+      fnName                        : 'createChannelWithParticipants'
+      validateOptionsWith           : ['body', 'recipients']
+      mapperFn                      : mapPrivateMessages
+
+    sendMessageToChannel : messageRequesterFn
+      fnName             : 'sendMessageToChannel'
+      validateOptionsWith: ['body', 'channelId']
+      mapperFn           : mapPrivateMessages
+
     initPrivateMessage   : messageRequesterFn
       fnName             : 'initPrivateMessage'
       validateOptionsWith: ['body', 'recipients']


fix MR comments

fix MR comments

--- client/landing/Login/AppController.coffee
+++ client/landing/Login/AppController.coffee
@@ -20,13 +20,16 @@

   handleVerifyRoute = ({params:{token}})->
     KD.singleton('appManager').open 'Login', (app) ->
-      if token
-        KD.remote.api.JPasswordRecovery.validate token, (err, isValid)=>
-          if err and err.short isnt 'redeemed_token'
-            KD.notify_ err.message
-          else if isValid
-            KD.notify_ "Thanks for confirming your email address"
-            KD.getSingleton('router').handleRoute "/Login"
+      return  unless token
+      KD.remote.api.JPasswordRecovery.validate token, (err, isValid)=>
+        return KD.notify_ err.message  if err
+        if isValid
+          KD.notify_ "Thanks for confirming your email address"
+          KD.getSingleton('router').handleRoute "/Login"
+        else
+          KD.notify_ "Token is not valid"
+          KD.getSingleton('router').handleRoute "/Login"
+
   handleRedeemRoute = ({params:{name, token}})->
     token = decodeURIComponent token
     KD.remote.api.JInvitation.byCode token, (err, invite)->


Undefined is a string apparently.

Undefined is a string apparently.

--- coffee/caffeine.coffee
+++ coffee/caffeine.coffee
@@ -82,7 +82,7 @@

 # watch for twitter button
 checkTwitterButton = ->
-  if typeof(twttr) == undefined
+  if typeof(twttr) == 'undefined'
     $('.twitter-share-button').hide()
   else
     $('.twitter-share-button').show()


removed test stuff

removed test stuff

--- src/coffee/delivery_and_return_sync.coffee
+++ src/coffee/delivery_and_return_sync.coffee
@@ -9,7 +9,7 @@
 .optimistExtras (o) ->
   o.describe('targetProject', 'Sphere.io credentials of the target project. Format: `prj-key:clientId:clientSecret`.')
   .alias('targetProject', 't')
-.messageCriteria 'type in ("DeliveryAdded", "ParcelAddedToDelivery") and resource(typeId="order" and id in ("71a18270-4ff4-45d3-9c53-43f6c25b6403","6b7a4af6-c12a-4a7b-a633-a3ed112fbe77","10a0f485-8f4e-4e78-a156-7e6d11d428c1","6667be73-0c00-42cf-aaa8-57abb2252012","7545e284-4d69-493c-87e6-296bb70929ca"))'
+.messageCriteria 'type in ("DeliveryAdded", "ParcelAddedToDelivery") and resource(typeId="order")'
 .build()
 .run (argv, stats, requestQueue) ->
   targetProject = util.parseProjectsCredentials argv.sourceProjects


tab className attribute becomes tabClassName

--- src/components/course-settings/roster.cjsx
+++ src/components/course-settings/roster.cjsx
@@ -62,7 +62,7 @@
           {period.name}
         </BS.OverlayTrigger>

-      <BS.Tab key={period.id} eventKey={index} title={name} className={className}>
+      <BS.Tab key={period.id} eventKey={index} title={name} tabClassName={className}>
         <PeriodRoster
         period={period}
         courseId={@props.courseId}


Fixed grammar in comments.

Fixed grammar in comments.

--- src/auto_complete.coffee
+++ src/auto_complete.coffee
@@ -25,7 +25,7 @@

   ##
   # Returns a list of possible auto-completions in the current channel.
-  # @return {Array<string>}
+  # @return {Array.<Completion>}
   ##
   _getPossibleCompletions: =>
     completions = []
@@ -35,7 +35,7 @@

   ##
   # Returns a list of commands to auto-complete.
-  # @return {Array<string>}
+  # @return {Array.<Completion>}
   ##
   _getCommandCompletions: ->
     cmds = @_context.userCommands.getCommands()
@@ -44,7 +44,8 @@
     return []

   ##
-  # Returns a list of nicknames in the current channel
+  # Returns a list of nicknames in the current channel.
+  # @return {Array.<Completion>}
   ##
   _getNickCompletions: ->
     chan = @_context.currentWindow.target
@@ -117,7 +118,7 @@
   class Completion

     ##
-    # Completion types can either be commands or nicknames
+    # Completion types can either be commands or nicknames.
     ##
     @CMD = 0
     @NICK = 1


JSSegmentedControl update

JSSegmentedControl update

--- JSKit/04_JSSegmentedControl.coffee
+++ JSKit/04_JSSegmentedControl.coffee
@@ -7,8 +7,8 @@
 	constructor:(@_dataarray)->
 		super(JSRectMake(0, 0, 120, 32))
 		@_bgColor = JSColor("clearColor")
+		@_textSize = 12
 		@_selectedSegmentIndex = -1
-		@_textSize = 12

 	setValue:(@_selectedSegmentIndex)->
 		if ($(@_viewSelector+"_radio").length)
@@ -24,14 +24,19 @@

 		tag = '
'
 		for i in [0...@_dataarray.length]
-			tag += '<input type="radio" id="'+@_objectID+'_radio_'+i+'" name="'+@_objectID+'_radio" value="'+i+'" />
'+@_dataarray[i]+'
'
+			tag += '<input type="radio" id="'+@_objectID+'_radio_'+i+'" name="'+@_objectID+'_radio" value="'+i+'" />
'+@_dataarray[i]+'
'
 		tag += '
'

 		if ($(@_viewSelector+"_radio").length)
 			$(@_viewSelector+"_radio").remove()
 		$(@_viewSelector).append(tag)
 		$(@_viewSelector+"_radio").buttonset()
-		$(@_viewSelector).unbind().bind 'tap', =>
-			@_selectedSegmentIndex = $("input[name='"+@_objectID+"_radio']:checked").val()
-			@action(@_self)
+		$(@_viewSelector).off()
+		$(@_viewSelector).on 'click', =>
+			@__select = @_selectedSegmentIndex
+			select = $("input:radio[name='"+@_objectID+"_radio']:checked").val()
+			if (select? && @__select != select)
+				@_selectedSegmentIndex = select
+				if (@action?)
+					@action(@_self)
 		@setValue(@_selectedSegmentIndex)


shellEditorView: expand minimized editor on change

--- coffee/src/views/shell_editor_view.coffee
+++ coffee/src/views/shell_editor_view.coffee
@@ -18,6 +18,7 @@

   defaults: => _.extend super,
     minimize: false
+    expandOnUpdate: true

   template: _.template '''
@@ -37,7 +38,7 @@
     if @options.minimize
       @minimize()

-    @on 'ShellEditor:ShellsUpdated', @_renderUpdates
+    @on 'ShellEditor:ShellsUpdated', @_onShellsUpdated

   _initializeModel: =>
@@ -200,3 +201,10 @@

   _onRemixerLinkChanged: (remixer, newlink) =>
     @trigger 'ShellEditor:ShellsUpdated'
+
+
+  _onShellsUpdated: =>
+    @_renderUpdates()
+
+    if @options.expandOnUpdate
+      @expand()


Define correct service options

Define correct service options

--- src/coffee/export.coffee
+++ src/coffee/export.coffee
@@ -50,8 +50,11 @@
         header.toIndex()
         header.toLanguageIndex()
         exportMapping = @_initMapping(header)
-        productsService = @client.productProjections.staged(staged).all()
-        productsService.byQueryString(@queryOptions.queryString, @queryOptions.isQueryEncoded) if @queryOptions.queryString
+        productsService = @client.productProjections
+        if @queryOptions.queryString
+          productsService.byQueryString(@queryOptions.queryString, @queryOptions.isQueryEncoded)
+        else
+          productsService.all().staged(staged)
         data = [
           @typesService.getAll @client
           @categoryService.getAll @client


Improve behavior when clicking on codemirror and suggestions -- Related to 1603

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -65,16 +65,20 @@
             'mousedown .suggestion_description': 'mouse_down_description'
             'click .suggestion_description': 'stop_propagation'
             'mouseup .suggestion_description': 'mouse_up_description'
+            'mousedown .suggestion_full_container': 'mouse_down_description'
+            'click .suggestion_full_container': 'stop_propagation'
+            'mousedown .CodeMirror': 'mouse_down_description'
+            'click .CodeMirror': 'stop_propagation'

         mouse_down_description: (event) =>
-            @click_on_description = true
+            @keep_suggestions_on_blur = true
             @stop_propagation event

         stop_propagation: (event) =>
             event.stopPropagation()

         mouse_up_description: (event) =>
-            @click_on_description = false
+            @keep_suggestions_on_blur = false
             @stop_propagation event

         start_resize_history: (event) =>
@@ -484,7 +488,7 @@
             $(window).mouseup @handle_mouseup
             $(window).mousedown @handle_mousedown
             @id_execution = 0
-            @click_on_description = false
+            @keep_suggestions_on_blur = false

             @render()

@@ -499,7 +503,7 @@
         handle_mousedown: (event) =>
             # $(window) caught a mousedown event, so it wasn't caught by $('.suggestion_description')
             # Let's hide the suggestion/description
-            @click_on_description = false
+            @keep_suggestions_on_blur = false
             @hide_suggestion_and_description()

         render: =>
@@ -582,7 +586,7 @@
         on_blur: =>
             @state.focus_on_codemirror = false
             # We hide the description only if the user isn't selecting text from a description.
-            if @click_on_description is false
+            if @keep_suggestions_on_blur is false
                 @hide_suggestion_and_description()

         # We have to keep track of a lot of things because web-kit browsers handle the events keydown, keyup, blur etc... in a strange way.


result size

result size

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -1,33 +1,16 @@
 # "Session is already running a query" 400 error when aborting a changefeed
 # views should not completely redraw every added row
-# close connection: no element found: probably due to firefox trying to parse the xhr response as xml
 # call to query_result.size and other code assumes that result is an array
 # table view shows no results when it means "no more results" or "no results yet"
 # abort button blinks when runnign a normal query
 # abort blinks constantly when running a changefeed
-# no element found when closing connection
 # "Possibly unhandled RqlRuntimeError. This HTTP connection is not open" when running a query after a changefeed query
-
-# refactor query_result

 # ticker shows throughout
 # move more results button into individual views
 # returned/displayed/skipped has extra comma and is generally ugly

 # round-trip time for cursors is inconsistent
-
-# ATN: change query nomenclature:
-# raw: text from the data explorer
-# clean: cleaned somehow. how?
-# query: a single query
-# query_group: multiple queries
-# TODO: standardize on result or results
-
-# container limit -> global constant
-
-# history load/remove buttons display is broken
-# compare look to old version, make sure nothing changed
-# test wrapper_scrollbar

 # Copyright 2010-2012 RethinkDB, all rights reserved.
 module 'DataExplorerView', ->
@@ -139,7 +122,10 @@
         size: =>
             switch @type
                 when 'value'
-                    return @value.length
+                    if @value instanceof Array
+                        return @value.length
+                    else
+                        return 1
                 when 'cursor'
                     return @results.length

@@ -3182,7 +3168,7 @@
             else if value_type is 'string'
                 if /^(http|https):\/\/[^\s]+$/i.test(value)
                     data['classname'] = 'jta_url'
-                else if /^[a-z0-9]+@[a-z0-9]+.[a-z0-9]{2,4}/i.test(value) # We don't handle .museum extension and special characters
+                else if /^[a-z0-9]+@[a-z0-9]+.[a-z0-9]{2,4}/i.test(value) # We don't haendle .museum extension and special characters
                     data['classname'] = 'jta_email'
                 else
                     data['classname'] = 'jta_string'
@@ -3336,6 +3322,7 @@
         template:
             Handlebars.templates['dataexplorer_result_profile-template']

+
         initialize: (args) =>
             ZeroClipboard.setDefaults
                 moviePath: 'js/ZeroClipboard.swf'
@@ -3378,7 +3365,7 @@
                     profile:
                         clipboard_text: JSON.stringify profile, null, 2
                         tree: @json_to_tree profile
-                        total_duration: 12345 # ATN: @metadata.execution_time_pretty
+                        total_duration: Utils.prettify_duration @parent.container.driver_handler.total_duration
                         server_duration: Utils.prettify_duration @compute_total_duration profile
                         num_shard_accesses: @compute_num_shard_accesses profile

@@ -3536,16 +3523,13 @@
                 @$el.html @template
                     limit_value: @container.limit
                     skip_value: @query_result.position
-                    execution_time: 12345 # ATN
-                    query: "query" # ATN
                     query_has_changed: args?.query_has_changed
                     show_more_data: has_more_data and not @container.state.cursor_timed_out
                     cursor_timed_out_template: (
                         @cursor_timed_out_template() if not @query_result.ended and @container.state.cursor_timed_out)
-                    execution_time_pretty: 12345 # ATN Utils.prettify_duration args.metadata.execution_time
-                    no_results: @query_result.ended and (
-                        @query_result.value is [] or @query_result.results is []) # ATN: move to QueryResult class
-                    num_results: 12345 # ATN
+                    execution_time_pretty: Utils.prettify_duration @container.driver_handler.total_duration
+                    no_results: @query_result.ended and @query_result.size() == 0
+                    num_results: @query_result.size()
                 @$('.execution_time').tooltip
                     for_dataexplorer: true
                     trigger: 'hover'


Allow objects to not have published_json

--- compiler/ContentAPI.coffee
+++ compiler/ContentAPI.coffee
@@ -232,9 +232,15 @@

         _returnData = (data) ->
             if data.map?
-                result = data.map (o) -> new Model(o.published_json)
+                result = data.map (o) ->
+                    if o.published_json
+                        return new Model(o.published_json)
+                    return new Model(o)
             else
-                result = new Model(data.published_json)
+                if data.published_json
+                    result = new Model(data.published_json)
+                else
+                    result = new Model(data)
             opts.callback(result)


cistrans: Don't require matching lower/upper case in gene symbol search

--- cistrans/cistrans.coffee
+++ cistrans/cistrans.coffee
@@ -89,12 +89,15 @@

   # probes for each gene
   probesByGene = {}
+  origGeneName = {}
   for probe of data.probes
     gene = data.probes[probe].gene
-    if probesByGene?[gene]
-      probesByGene[gene].push(probe)
-    else
-      probesByGene[gene] = [probe]
+    if gene != "" and gene != null
+      origGeneName[gene.toLowerCase()] = gene
+      if probesByGene?[gene]
+        probesByGene[gene].push(probe)
+      else
+        probesByGene[gene] = [probe]

   allgenes = (gene for gene of probesByGene)

@@ -761,7 +764,8 @@
       newSelection = allgenes[Math.floor(Math.random()*allgenes.length)]
     if newSelection != "" and newSelection != selectedGene
       selectedGene = newSelection
-      if probesByGene?[selectedGene]
+      if origGeneName?[selectedGene.toLowerCase()]
+        selectedGene = origGeneName[selectedGene.toLowerCase()]
         selectedProbes = probesByGene[selectedGene]
         if(selectedProbes.length > 1)
           selectedProbe = selectedProbes[Math.floor(Math.random()*selectedProbes.length)]


Peak Load: fixed popup link. Closes #1207

--- app/assets/javascripts/common_interface.coffee
+++ app/assets/javascripts/common_interface.coffee
@@ -121,7 +121,9 @@

     # Menu items
     #
-    $("#disable_peak_load_tracking").live 'click', -> disable_peak_load_tracking()
+    $("#disable_peak_load_tracking").live 'click', ->
+      if App.peak_load
+        App.peak_load.disable_peak_load_tracking()

   close_all_menus: ->
     $('a.menu_toggler').removeClass('menu-open')


Trigger cancel link when ESC is pressed inside input text field

--- app/assets/javascripts/content_items.js.coffee
+++ app/assets/javascripts/content_items.js.coffee
@@ -90,3 +90,7 @@
   OUT.contentItems.setAddFormArrowTo $("#add-content-item-tabs li.active a")

   $("#add-content-item-tabs li.active").removeClass("active")
+
+  $('form input[type="text"]').live "keyup", (event) ->
+    if event.keyCode == 27
+      $(this).parents("form").find("a.cancel").click()


Use loading in submit of comment

Use loading in submit of comment

--- core/app/backbone/views/evidence/add_comment_view.coffee
+++ core/app/backbone/views/evidence/add_comment_view.coffee
@@ -89,10 +89,12 @@

   enableSubmit: ->
     @submitting = false
+    @ui.submit.removeClass 'button-loading'
     @ui.submit.prop('disabled',false).text(Factlink.Global.t.post_argument)

   disableSubmit: ->
     @submitting = true
+    @ui.submit.addClass 'button-loading'
     @ui.submit.prop('disabled',true ).text('Posting...')

   _textModel: -> @__textModel ?= new Backbone.Factlink.SemiPersistentTextModel {},


lien fb

lien fb

--- app/assets/javascripts/_utils.js.coffee
+++ app/assets/javascripts/_utils.js.coffee
@@ -128,10 +128,7 @@
   $('#add-friend').on('click', ->
     options =
       method:       'send'
-      name:         'Sokoban.be'
-      description:  'Join me to play Sokoban, a great puzzle-game'
-      link:         'http://www.sokoban.be'
-      redirect_uri: 'http://www.sokoban.be'
+      link:         window.location

     FB.ui(options)


Get rid of js log output

Get rid of js log output

--- app/assets/javascripts/redcursor_app.js.coffee
+++ app/assets/javascripts/redcursor_app.js.coffee
@@ -102,10 +102,6 @@
         $(".bottom-link .arrow-up").hide()
         $(".bottom-link .arrow-down").show()

-      console.log 'scroll'
-      console.log e
-
-
   if $('#recent_acitivities.refresh').length
     setInterval refreshChannels, 15 * 1000


js state machine

js state machine

--- src/State.coffee
+++ src/State.coffee
@@ -246,7 +246,6 @@
     all: ->
       states

-
 Transition = (machine, event, from, to, params) ->
   self =
     perform: ->


#22 tweak columnize algorithm

#22 tweak columnize algorithm

--- app/assets/javascripts/global.js.coffee
+++ app/assets/javascripts/global.js.coffee
@@ -33,9 +33,11 @@
   # Columnize groups list
   $("ul.groups_list").each ->
     number_of_groups =  $(this).find("li").size()
-    number_of_columns = Math.ceil( number_of_groups / 30 )
-    number_of_columns++ if number_of_columns == 1
-    $(this).makeacolumnlists({ cols: number_of_columns })
+    if number_of_groups > 30
+       number_of_columns = Math.ceil( number_of_groups / 30 )
+    else
+       number_of_columns = Math.ceil( number_of_groups / 8 )
+    $(this).makeacolumnlists({ cols: number_of_columns }) if number_of_columns > 1

   # Admin table sorting (does not currently work)
   $("table#admin-table").tablesorter({ sortList: [[1,0]] })


Properly replace commits on SSE

Properly replace commits on SSE

--- app/assets/javascripts/stacks.js.coffee
+++ app/assets/javascripts/stacks.js.coffee
@@ -21,7 +21,7 @@

   onCommitUpdate = (message) ->
     loadFragment message, (id, commit) ->
-      $("#commit-#{id}").html($timeago(commit))
+      $("#commit-#{id}").replaceWith($timeago(commit))

   onCommitCreate = (message) ->
     loadFragment message, (id, commit) ->


add missing user_id parameter

add missing user_id parameter

--- app/coffee/HealthCheckController.coffee
+++ app/coffee/HealthCheckController.coffee
@@ -65,10 +65,10 @@
 						if err?
 							logger.log "Failed executing delete tags health check"
 						otherTags = (tag for tag in tags when tag._id isnt tag_id)
-						HealthCheck._removeOldTags otherTags, () ->
+						HealthCheck._removeOldTags user_id, otherTags, () ->
 							callback(err, res, body)

-	_removeOldTags: (tags, callback) ->
+	_removeOldTags: (user_id, tags, callback) ->
 		now = new Date()
 		getAge = (tag) ->
 			(now - ObjectId(tag._id).getTimestamp())


Turn stack traces back off by default.

Turn stack traces back off by default.

--- lib/context.coffee
+++ lib/context.coffee
@@ -64,7 +64,7 @@
   decontextify: (contextObj) ->
     db.findById contextObj?.id

-  run: (coffeeCode, extraArgs = [], sendOutput = false, stack = true) ->
+  run: (coffeeCode, extraArgs = [], sendOutput = false, stack = false) ->
     try
       code = coffee.compile coffeeCode, bare: true
       ctext = _.clone @context


Show version and help by default

Show version and help by default

--- src/beans.coffee
+++ src/beans.coffee
@@ -355,6 +355,7 @@
 run = ->
   args = process.argv.slice(2)
   if args.length == 0
+    version()
     help()
   else if commands[args[0]]?
     commands[args[0]][0](args.slice(1)...)


Typo.

Typo.

--- app/scripts/coffee/addons/Lately.coffee
+++ app/scripts/coffee/addons/Lately.coffee
@@ -45,7 +45,7 @@

   usage: () ->
     @output "Recently comlpeted tasks:
"
-    @output "lately+
"
+    @output "lately
"
     @output "
  generates a list of completed tasks during the last 7 days.

"
     @output "  Optional argument (integer) overrides number of days.
"
     @output "  Optional argument (String) project name.
"


Remove debug log

Remove debug log

--- app/src/wallet/sweep_private_key.coffee
+++ app/src/wallet/sweep_private_key.coffee
@@ -29,7 +29,6 @@

           txBuilder.sign(index, ecKey) for input, index in txBuilder.tx.ins
           txHex = txBuilder.build().toHex()
-          console.log(txHex)
           ledger.api.TransactionsRestClient.instance.postTransactionHex txHex, (txHash, error) =>
             completion.complete(txHash, error)
         else


Use jQuery.getJSON on config service

Use jQuery.getJSON on config service

--- app/static/script/service/config.coffee
+++ app/static/script/service/config.coffee
@@ -1,15 +1,15 @@
 'use strict'

 angular.module('app')
-  .factory 'config', ['$http', '$log', ($http, $log) ->
+  .factory 'config', ['$log', ($log) ->
     config = {}

     # TODO Use Gulp to generate config.js
-    $http.get('/config.json', {async: false, cache: true})
-      .success (response) =>
+    $.getJSON('/config.json', {async: false, cache: true})
+      .done (response) =>
         $log.debug 'Configuration loaded.'
         _.extend config, response
-      .error (response) ->
+      .fail (response) ->
         $log.error 'Could not load configuration!'

     config


fix scripts not reloading on save

fix scripts not reloading on save

--- app/templates/gulp/tasks/scripts.coffee
+++ app/templates/gulp/tasks/scripts.coffee
@@ -39,7 +39,7 @@
 #*------------------------------------*\
 #     $SCRIPTS WATCH
 #*------------------------------------*/
-gulp.task 'scripts:watch', ['scripts'],  (done) ->
+gulp.task 'scripts:watch', ['scripts'],  () ->
   global.browserSync.reload()


fix stub

fix stub

--- apps/home/components/top_rail/test/view.coffee
+++ apps/home/components/top_rail/test/view.coffee
@@ -46,6 +46,9 @@
       describe 'with personalized works', ->
         beforeEach (done) ->
           @metaphysics.returns Q.resolve { me: bidder_positions: [{
+            highest_bid: {
+              display_amount_dollars: '$5,000'
+            }
             sale_artwork:
               artwork:
                 title: "Hello World"


Fix bug to get always notified when all tests are completed

--- client/TestRunner.coffee
+++ client/TestRunner.coffee
@@ -38,12 +38,11 @@
     velocity.postResult velocityResult

-  onTestsCompleted: ()->
-    console.log "TestRunner: tests completed."
+  onTestsCompleted: ->
+    log.info "test-in-velocity: all tests completed."
     @running = false
     @results = {}
-    #Tracker.flush();
-    Meteor.connection._unsubscribeAll()
+#    Meteor.connection._unsubscribeAll()

 @testRunner = TestRunner.get()


Fix up scroll wheel tests

Fix up scroll wheel tests

(hadn't updated after cleaning up _trigger_event function)

--- bokehjs/test/test_common/test_ui_events.coffee
+++ bokehjs/test/test_common/test_ui_events.coffee
@@ -4,6 +4,7 @@
 sinon = require 'sinon'

 ui_events = utils.require "common/ui_events"
+scroll = utils.require "tool/gestures/wheel_zoom_tool"
 # Stub out _hammer_element as not used in testing
 sinon.stub(ui_events.prototype, "_hammer_element")

@@ -22,40 +23,24 @@
     @stopPropagation = sinon.spy(e, "stopPropagation")
     @e = e

-    active_scroll = {
-      scroll: {
-        active: true
-      }
-    }
-
-    @ui_event = new ui_events({
-      hit_area: $('canvas'),
-      tool_manager: {
-        gestures: active_scroll
-      }
-    })
+    @active_scroll = new scroll.Model
+    @ui_event = new ui_events

   describe "_trigger_scroll", ->

     it "should stopPropagation & preventDefault of event if scroll gesture is active", ->

-      @ui_event._trigger_event('scroll', true, @e)
+      @active_scroll.set('active', true)
+
+      @ui_event._trigger_event('scroll', @active_scroll, @e)
       expect(@stopPropagation.callCount).to.equal 1
       expect(@preventDefault.callCount).to.equal 1

     it "should not stopPropagation & preventDefault of event if scroll gesture is not active", ->

-      inactive_scroll = {
-        scroll: {}
-      }
-      ui_event = new ui_events({
-        hit_area: $('canvas'),
-        tool_manager: {
-          gestures: inactive_scroll
-        }
-      })
+      @active_scroll.set('active', false)

-      ui_event._trigger_event('scroll', undefined, @e)
+      @ui_event._trigger_event('scroll', @active_scroll, @e)
       expect(@stopPropagation.callCount).to.equal 0
       expect(@preventDefault.callCount).to.equal 0


Guard against null snapshot selectedRange

--- assets/inspector/undo_panel_view.coffee
+++ assets/inspector/undo_panel_view.coffee
@@ -36,5 +36,5 @@
   renderEntry: (entry) ->
     element = document.createElement("div")
     element.className = "trix-undo-entry"
-    element.textContent = "#{entry.description} #{entry.snapshot.selectedRange.inspect()}"
+    element.textContent = "#{entry.description} #{entry.snapshot.selectedRange?.inspect()}"
     element


add state attribute to Branch

add state attribute to Branch

--- assets/scripts/app/models/branch.coffee
+++ assets/scripts/app/models/branch.coffee
@@ -3,6 +3,7 @@
 @Travis.Branch = Travis.Model.extend Travis.Helpers,
   repoId:       DS.attr('number', key: 'repository_id')
   commitId:     DS.attr('number')
+  state:        DS.attr('string')
   number:       DS.attr('number')
   branch:       DS.attr('string')
   message:      DS.attr('string')


add test for many subsequent requests and many subsequent synchronouse requests

--- test/javascripts/turbolinks_test.coffee
+++ test/javascripts/turbolinks_test.coffee
@@ -64,7 +64,7 @@

   visit = ({options, url}, callback) ->
     $(document).one('page:load', (event) ->
-      setTimeout((-> callback(event)), 0)
+      setTimeout((-> callback(event) if callback), 0)
     )
     Turbolinks.visit('/' + url, options)

@@ -185,6 +185,33 @@
           assertLinks(['foo.css'])
           done()

+    it 'many subsequent navigations do not break head asset tracking', (done) ->
+      requestsToDo = 10
+
+      recursiveVisit = ->
+        visit url: 'twoLinksInHead', ->
+          visit url: 'singleLinkInHead', ->
+            assertLinks(['foo.css'])
+            requestsToDo -=1
+            return recursiveVisit() if requestsToDo > 0
+            done()
+
+      recursiveVisit()
+
+      it 'many subsequent synchronous navigations do not break head asset tracking', (done) ->
+        requestsToDo = 10
+
+        recursiveVisit = ->
+          visit url: 'twoLinksInHead'
+          visit url: 'singleLinkInHead'
+          visit url: 'twoLinksInHead', ->
+              assertLinks(['foo.css', 'bar.css'])
+              requestsToDo -=1
+              return recursiveVisit() if requestsToDo > 0
+              done()
+
+        recursiveVisit()
+
     describe 'transforms the current head to have the same links in the same order as the upstream document with minimal moves', ->
       it 'maintains order when moving from an empty head to a page with link nodes.', (done) ->
         linkTagInserted = sinon.spy()


cleanup

cleanup

--- js/src/auth/SearchCredentialService.coffee
+++ js/src/auth/SearchCredentialService.coffee
@@ -67,43 +67,21 @@
       @searchKeyGenerator  = new SearchKeyGenerator()
       @searchKeySerializer = new SearchKeySerializer()

-    getFhePrivateKey: ->
-      return @getCredential('FHE_PRIVATE_KEY')
-
-    getSearchPrivateKey: ->
-      return @getCredential('SEARCH_PRIVATE_KEY')
-
-    getClientHashFunction: ->
-      return @getCredential('CLIENT_HASH_FUNCTION')
-
-    # TODO fix notifier
-
-    # private
-    # =======
-
-    getCredential: (key) ->
-      Promise.resolve()
-      .then =>
-        @ensureCredentialsInitialized()
-      .then =>
-        @getAllCredentials()
-      .then (allCredentials) ->
-        log.info('allCredentials', allCredentials)
-        return allCredentials[key]
-
     # initializes keys if needed.
-    ensureCredentialsInitialized: ->
+    ensureCredentialsInitialized: ( notifier = -> ) ->
       Promise.resolve()
       .then =>
         @hasInitialized()
       .then (initialized) =>
         if !initialized
-          return @initializeCredentials()
+          return @initializeCredentials(notifier)
         else
           return Promise.resolve()

     getAllCredentials: ->
       Promise.resolve()
+      .then =>
+        @ensureCredentialsInitialized()
       .then =>
         credentialPromises = _.mapValues(CredentialType, (credentialType) =>
           loadCredential = credentialType.getter(@cryptoKeyStorageApi)
@@ -117,6 +95,9 @@
           else
             return @searchKeySerializer.decrypt(credential)
         )
+
+    # private
+    # =======

     hasInitialized: ->
       Promise.resolve()
@@ -135,19 +116,25 @@
           log.error("expected #{expectedLength} credentials but got #{credentials.length}")
           throw new Error 'credentials are in a partially initialized state'

-    initializeCredentials: ->
+    initializeCredentials: (notifier) ->
+      { clientKeys } = {}
+
       Promise.resolve()
       .then =>
         clientKeys = @searchKeyGenerator.generateClientKeys()
-        storePromises = _.mapValues(CredentialType, (credentialType) =>
-          @initializeCredential(credentialType, clientKeys)
-        )
-        Promise.props(storePromises)
+      .then =>
+        @initializeCredential(CredentialType.FHE_PRIVATE_KEY, clientKeys, notifier)
+      .then =>
+        @initializeCredential(CredentialType.SEARCH_PRIVATE_KEY, clientKeys, notifier)
+      .then =>
+        @initializeCredential(CredentialType.CLIENT_HASH_FUNCTION, clientKeys, notifier)

     initializeCredential: (credentialType, clientKeys) ->
       { uint8Key } = {}

       Promise.resolve()
+      .then ->
+        Promise.resolve(notifier(credentialType.stage))
       .then =>
         uint8Key           = credentialType.generator(clientKeys)
         encryptedKeyChunks = @searchKeySerializer.encrypt(uint8Key)


don't format empty article publication dates

--- client/controllers/articles.coffee
+++ client/controllers/articles.coffee
@@ -17,7 +17,9 @@
         key: "publishDate"
         label: "Publication Date"
         fn: (value, object, key) ->
-          return moment(value).format('MMM D, YYYY')
+          if value
+            return moment(value).format('MMM D, YYYY')
+          return ""
       }
     ]


Keyboard up/down/tap actions

Keyboard up/down/tap actions

--- client/src/ActionController.coffee
+++ client/src/ActionController.coffee
@@ -43,7 +43,7 @@
             y: position.y * screenSize.height
         @robot.moveMouse(moveTo.x, moveTo.y)

-    # down: up|down, button: left|right
+    # buttonAction: up|down|click|doubleClick, button: left|right
     mouseButton: (buttonAction, button) =>
         feedback = window.feedback
         if(buttonAction == 'up')
@@ -61,6 +61,17 @@
             @mouseState[button] = 'up'
         #window.feedback.mouseStatus button, @mouseState[button] # This shouldn't be here..

+    # action: up|down|tap
+    keyboard: (action, button) =>
+        feedback = window.feedback
+        if(action == 'up')
+            @robot.keyToggle button, action
+        else if(action == 'down')
+            @robot.keyToggle button, action
+        else if(action == 'tap')
+            @robot.keyTap button
+        return
+
     scrollMouse: (direction, magnitude = 50) =>
         if(direction == 'up' or direction == 'down')
             @robot.scrollMouse(magnitude, direction)
@@ -68,9 +79,9 @@
             console.log 'This aint 3d, man!'

     executeAction: (action) =>
-        console.log "Execute action: ", action
+        #console.log "Execute action: ", action
         cmd = @actions[action]
-        console.log "cmd: ", cmd
+        #console.log "cmd: ", cmd

         if(cmd.feedback?)
             if(cmd.feedback.audio?)
@@ -83,8 +94,9 @@
                 @mouseMove(@position)
             if(cmd.action == 'scroll')
                 @scrollMouse cmd.direction, cmd.magnitude
-        if(cmd.type == 'keyboardTest')
-            @keyboardTest(cmd.action)
+        if(cmd.type == 'keyboard')
+            if(cmd.action in ['up', 'down', 'tap'])
+                @keyboard cmd.action, cmd.button

     activateRecipe: (recipeName) =>
         recipe = @recipes[recipeName]


additional report_view tests

additional report_view tests

--- client/test/src/report_view.coffee
+++ client/test/src/report_view.coffee
@@ -55,3 +55,9 @@
 )

 test("Can navigate to Dashboard, Reports, Indicators and Bookmarks")
+
+test("I can use report period")
+test("I can use report author")
+test("I can use report image")
+test("I can use report introduction")
+test("I can use report conclusion")


fixes

fixes

--- lms/static/coffee/src/staff_grading/staff_grading.coffee
+++ lms/static/coffee/src/staff_grading/staff_grading.coffee
@@ -418,7 +418,7 @@

     if @state == state_error
-      @set_button_text(gettext('Try loading again')
+      @set_button_text(gettext('Try loading again'))
       show_action_button = true

     else if @state == state_grading


bugfix

bugfix

--- app/assets/javascripts/helpers/label_click_helper.js.coffee
+++ app/assets/javascripts/helpers/label_click_helper.js.coffee
@@ -1,4 +1,4 @@
 # labelClickHelper
 $(document).on "click", "label", ->
   if @attributes["for"]
-    $("input[name='#{@attributes["for"]["value"]}']").trigger "click"
+    $("input[name='#{@attributes["for"]["value"]}']").focus().trigger "click"


WIP: improve error_handler.js.coffee

WIP: improve error_handler.js.coffee

--- app/assets/javascripts/initializers/error_handler.js.coffee
+++ app/assets/javascripts/initializers/error_handler.js.coffee
@@ -6,8 +6,7 @@
     errorPath = '/errors'

     logError = (msg) ->
-      if window.teaspoonTesting == true
-        console.log("ERROR: " + msg)
+      console.log(new Error(msg).stack)

     container.register('logError:main', logError , instantiate: false)
     application.inject('route', 'logError', 'logError:main')
@@ -28,9 +27,11 @@
       else
         displayErrorMessage(error)

+    # TODO we should errornotify when a "real" ajax error happens!
     $(document).ajaxError (event, jqXHR, ajaxSettings, thrownError) ->
       {type, url} = ajaxSettings
       {status, statusText} = jqXHR
+      console.log('err', status, thrownError)

       # don't blow up in case of a 403 from rails
       return if status == 403


Add invoice type, fix caching recalling callback bug

--- snapbill.coffee
+++ snapbill.coffee
@@ -100,12 +100,15 @@

         return cache[typeName][objectData.id]

-    @load: (snapbill, filters, callback) ->
-        console.log snapbill
-        cached = snapbill.object_cache[@type][filters.id] if filters.id?
-        callback cached, null if cached?
+    @load: (snapbill, id, callback) ->
+        if id?.id?
+            id = id.id
+        if snapbill.object_cache[@type]?
+            cached = snapbill.object_cache[@type][id]
+            callback cached, null if cached?
+            return

-        snapbill.request "POST", "/v1/#{@type}/get", filters, (requestData, error) =>
+        snapbill.request "POST", "/v1/#{@type}/#{id}/get", {}, (requestData, error) =>
             if error?
                 callback null, error
             else
@@ -147,10 +150,22 @@
 Client.prototype.constructor = Client
 Client.constructor = Client

+class Invoice extends SnapbillObject
+    @type: 'invoice'
+
+    @list: (snapbill, client, params, callback) ->
+        params ?= {}
+        params.client_id = client.id
+        super snapbill, params, callback
+
+Invoice.prototype.constructor = Invoice
+Invoice.constructor = Invoice
+

 snapbill =
     User           :User
     Client         :Client
+    Invoice        :Invoice
     Snapbill       :Snapbill
     SnapbillObject :SnapbillObject


fixed location of comment

fixed location of comment

--- assets/lib/search.coffee
+++ assets/lib/search.coffee
@@ -58,8 +58,8 @@
     if matches.length > 0
       fakedate = (new Date).toISOString()

-      # Sort by date
       matches.sort (a, b) ->
+        # Sort by date
         dateA = pages[a].date || fakedate
         dateB = pages[b].date || fakedate


Updates atom packages

Updates atom packages

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -1,12 +1,12 @@
 "*":
   editor:
-    fontSize: 11
     showInvisibles: true
     showIndentGuide: true
     preferredLineLength: 100
     softWrap: true
     softWrapAtPreferredLineLength: true
     invisibles: {}
+    fontSize: 11
   core:
     ignoredNames: [
       ".git"
@@ -58,10 +58,10 @@
   "tool-bar":
     iconSize: "16px"
     position: "Left"
-".coffee.jsx.source":
+".js.jsx.source":
   editor:
     preferredLineLength: 100
-".js.jsx.source":
+".coffee.jsx.source":
   editor:
     preferredLineLength: 100
 ".diff.source":


fix atom workspace config to be atom 1.0 compliant

atom.workspaceView is no longer available

--- atom.symlink/init.coffee
+++ atom.symlink/init.coffee
@@ -1,6 +1,5 @@
 path = require 'path'

-atom.workspaceView.eachEditorView (editorView) ->
-  editor = editorView.getEditor()
+atom.workspace.observeTextEditors (editor) ->
   if path.extname(editor.getPath()) is '.md'
     editor.setSoftWrap(true)


jupyter: additional key:value store tests on backend

--- src/smc-project/jupyter/test/kernel.coffee
+++ src/smc-project/jupyter/test/kernel.coffee
@@ -163,4 +163,19 @@
                 expect(path).toBe('/test')
                 done(err)

+describe 'use the key:value store -- ', ->
+    kernel = undefined
+    @timeout(5000)

+    it 'creates a python2 kernel', ->
+        kernel = common.kernel('python2')
+
+    it 'tests setting the store', ->
+        kernel.store.set({a:5, b:7}, {the:'value'})
+        expect(kernel.store.get({b:7, a:5})).toEqual({the:'value'})
+
+    it 'tests deleting from the store', ->
+        kernel.store.delete({a:5, b:7})
+        expect(kernel.store.get({b:7, a:5})).toBe(undefined)
+
+


callback vs. cb

callback vs. cb

--- node_modules_koding/kite-amqp/lib/kite-amqp/index.coffee
+++ node_modules_koding/kite-amqp/lib/kite-amqp/index.coffee
@@ -13,20 +13,20 @@
     @waitingReplies = {}
     @kiteList = {}
     @commands =
-      'kite.who': (opts, cb)->
-        cb false, @getChannelNameForKite()
-
-      'kite.join': (opts, cb)->
+      'kite.who': (options, callback)->
+        callback false, @getChannelNameForKite()
+
+      'kite.join': (options, callback)->
         channelName = opts.args[0]
         kiteName    = opts.args[1]
         if not @kiteList[channelName]
           @kiteList[channelName] = []
         if kiteName not in @kiteList[channelName]
           @kiteList[channelName].push kiteName
-        if cb
-          cb false, true
-
-      'kite.consume': (opts, cb)=>
+        if callback
+          callback false, true
+
+      'kite.consume': (options, callback)=>
         @mergenData.lpop @getChannelNameForGenericName(), (err, val)=>
           if not val
             return
@@ -77,7 +77,7 @@
     @sendCommand(from, "reply-#{cmdid}", ['function not found', false])

-  queueCommand:(channel, cmd, args, cb=false)->
+  queueCommand:(channel, cmd, args, callback=null)->

     if not channel
       channel = @getChannelNameForGenericName()
@@ -102,7 +102,7 @@
       console.log "pushed to ", channel, datastr
       @sendCommand channel, "kite.consume", datastr

-  sendCommand: (channel, cmd, args, cb=false)->
+  sendCommand: (channel, cmd, args, callback=null)->
     # this broadcasts command to the said channel
     @callbackid++
     data = {


Store explicit soft wrap metadata in line index for expansion

It’s impossible to know whether a line is soft wrapped via spatial
information also, since a wrapped line segment might terminate in a fold
that extends to the beginning of another line. Explicit metadata makes
it easy to determine when expanding ranges.

--- src/display-layer.coffee
+++ src/display-layer.coffee
@@ -205,21 +205,16 @@

   expandBufferRangeToLineBoundaries: (range) ->
     @screenLineIterator.seekToBufferPosition(Point(range.start.row, 0))
-    while @screenLineIterator.getBufferStart().column isnt 0
-      break unless @screenLineIterator.moveToPredecessor()
+    while @screenLineIterator.isSoftWrappedAtStart()
+      @screenLineIterator.moveToPredecessor()
     startScreenRow = @screenLineIterator.getScreenRow()
     startBufferRow = @screenLineIterator.getBufferStart().row

-    @screenLineIterator.seekToBufferPosition(Point(range.end.row, 0))
-    while @screenLineIterator.moveToSuccessor()
-      if @screenLineIterator.getBufferStart().column is 0
-        @screenLineIterator.moveToPredecessor()
-        endScreenRow = @screenLineIterator.getScreenRow()
-        endBufferRow = @screenLineIterator.getBufferEnd().row
-        break
-
-    endScreenRow ?= @screenLineIterator.getScreenRow()
-    endBufferRow ?= @screenLineIterator.getBufferEnd().row
+    @screenLineIterator.seekToBufferPosition(Point(range.end.row, Infinity))
+    while @screenLineIterator.isSoftWrappedAtEnd()
+      @screenLineIterator.moveToSuccessor()
+    endScreenRow = @screenLineIterator.getScreenRow()
+    endBufferRow = @screenLineIterator.getBufferEnd().row

     {startScreenRow, endScreenRow, startBufferRow, endBufferRow}

@@ -359,7 +354,9 @@
           screenLines.push({
             screenExtent: tokensScreenExtent,
             bufferExtent: traversal(screenLineBufferEnd, screenLineBufferStart),
-            tokens
+            tokens,
+            softWrappedAtStart: lastWrapBufferColumn > 0,
+            softWrappedAtEnd: true
           })
           tokens = []
           tokensScreenExtent = 0
@@ -512,7 +509,9 @@
       screenLines.push({
         screenExtent: tokensScreenExtent,
         bufferExtent: traversal(Point(bufferRow, bufferColumn), screenLineBufferStart),
-        tokens
+        tokens,
+        softWrappedAtStart: lastWrapBufferColumn > 0,
+        softWrappedAtEnd: false
       })
       tokens = []
       screenLineWidth = 0


hotfix: public api url config

hotfix: public api url config

--- src/coffee/config.coffee
+++ src/coffee/config.coffee
@@ -2,4 +2,4 @@
   FC_API_URL:
     if process.env.MOCK
     then ''
-    else process.env.FC_API_URL or 'https://flak-cannon.kiln.wtf'
+    else process.env.PUBLIC_FC_API_URL or 'https://flak-cannon.kiln.wtf'


Add missing reference

Add missing reference

--- src/coffee/export.coffee
+++ src/coffee/export.coffee
@@ -41,7 +41,7 @@
     new ExportMapping(options)

   # return the correct product service in case query string is used or not
-  _getProductService: ->
+  _getProductService: (staged = true) ->
     productsService = @client.productProjections
     if @queryOptions.queryString
       productsService.byQueryString(@queryOptions.queryString, @queryOptions.isQueryEncoded)
@@ -68,7 +68,7 @@
           @channelService.getAll @client
           @customerGroupService.getAll @client
           @taxService.getAll @client
-          @_getProductService()
+          @_getProductService(staged)
         ]
         # TODO:
         # - use process to export products in batches


added new commands to the help file

added new commands to the help file

--- src/commands/help.coffee
+++ src/commands/help.coffee
@@ -18,6 +18,8 @@
       [-v, --verbose]               Output debug level logs
     render [env]                Render the CloudFormation template to STDERR
     update [env]                Update *only* the Lambda code for an environment
+    list                        List all deployed envs and their endpoints
+    test [env]                  Issue "npm test {env url}" after url lookup
     domain [subcommand] [env]   Manage your API's custom domain and edge cache.
       - domain publish [env]      Upserts a CloudFront distribution for your API
       - domain invalidate [env]   Invalidates your API's CloudFront edge cache


Set requires to global variables

Set requires to global variables

--- lib/helpers.coffee
+++ lib/helpers.coffee
@@ -1,3 +1,8 @@
+{Range} = require('atom')
+XRegExp = require('xregexp').XRegExp
+path = require 'path'
+child_process = require 'child_process'
+
 module.exports = Helpers =
   # Validates that the results passed to Linter Base by a Provider contain the
   #   information needed to display an issue.
@@ -7,7 +12,6 @@
     for result in results
       unless result.type
         throw new Error "Missing type field on Linter Response, Got: #{Object.keys(result)}"
-      {Range} = require('atom')
       result.range = Range.fromObject result.range if result.range?
       result.class = result.type.toLowerCase().replace(' ', '-')
       Helpers.validateResults(result.trace) if result.trace
@@ -29,7 +33,6 @@

   exec: (command, options = {stream: 'stdout'}) ->
     throw new Error "Nothing to execute." if not arguments.length
-    child_process = require 'child_process'
     return new Promise (resolve, reject) ->
       process = child_process.exec(command, options)
       data = []
@@ -46,8 +49,6 @@
   execFilePath: (command, filePath, options = {}) ->
     throw new Error "Nothing to execute." if not arguments.length
     throw new Error "No File Path to work with." if not filePath
-    path = require 'path'
-    child_process = require 'child_process'
     return new Promise (resolve, reject) ->
       file = path.basename(filePath)
       options.cwd = path.dirname(filePath) if not options.cwd
@@ -79,7 +80,6 @@
   # We place priority on `lineStart` and `lineEnd` over `line.`
   # We place priority on `colStart` and `colEnd` over `col.`
   parse: (data, regex, options = {baseReduction: 1}) ->
-    XRegExp = require('xregexp').XRegExp
     new Promise (resolve, reject) ->
       toReturn = []
       regex = XRegExp(regex)


Remove duplicate serialize method

Remove duplicate serialize method

--- lib/history.coffee
+++ lib/history.coffee
@@ -6,7 +6,6 @@
 class History
   constructor: (@miniEditor, @items=[]) ->
     @index = @items.length
-
     @miniEditor.on 'core:move-up', => @previous()
     @miniEditor.on 'core:move-down', => @next()

@@ -44,6 +43,3 @@
     @scratch = null
     @items.push(text)
     @index = @items.length - 1
-
-  serialize: ->
-    @items


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -274,12 +274,11 @@
       {min, max} = [r, g, b].extremes()

       hue = saturation = lightness = (max + min) / 2
-
-      if max == min
+      chroma = max - min
+
+      if chroma.abs() < 0.00001
         hue = saturation = 0
       else
-        chroma = max - min
-
         saturation =
           if lightness > 0.5
             chroma / (1 - lightness)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -635,6 +635,26 @@

     ###*
     A new color. The calling color mixed with `other` using `amount` as the mixing ratio. If amount is not passed, then the colors are mixed evenly.
+
+

+    red = Color(255, 0, 0)
+    yellow = Color(255, 255, 0)
+    anotherRed = Color(255, 0, 0)
+
+    # With no arguments the colors are mixed evenly
+    red.mixWith$(yellow)
+
+    # We have modified red in place to be orange
+    red.toString()
+    # => 'rgba(255, 128, 0, 1)'
+
+    # With this argument we are mixing the color 30% red and 70% yellow
+    anotherRed.mixWith$(yellow, 0.3)
+
+    # We have modified `anotherRed` in place to be somethingCloseToOrange
+    anotherRed.toString()
+    # => rgba(255, 179, 0, 1)
+

     @name mixWith$
     @methodOf Color#


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -201,7 +201,7 @@
     shiftHue$: (degrees) ->
       hsl = @toHsl()

-      hsl[0] = (hsl[0] + degrees).mod 360
+      hsl[0] = hsl[0] + degrees).mod 360

       [@r, @g, @b, @a] = hslToRgb(hsl)


ComputeController:invalidateCache also invalidate existing klient kite instances

--- client/Main/providers/computecontroller.coffee
+++ client/Main/providers/computecontroller.coffee
@@ -741,4 +741,7 @@
       return warn \
         "Unable to invalidate cache, machine not found with #{machineId}"

+    {kontrol} = KD.singletons
+
     KiteCache.unset machine.queryString
+    delete kontrol.kites?.klient?[machine.uid]


:memo: atomatigit.coffee documentations

:memo: atomatigit.coffee documentations

--- lib/atomatigit.coffee
+++ lib/atomatigit.coffee
@@ -7,6 +7,7 @@
     debug: false
     pre_commit_hook: ''

+  # Public: Package activation.
   activate: (state) ->
     @insertCommands()
     return @errorNoGitRepo() unless atom.project.getRepo()
@@ -16,23 +17,28 @@
       @repoView = new RepoView(@repo)
       @focus()

-  insertCommands: ->
-    atom.workspaceView.command 'atomatigit:show',  => @focus()
-    atom.workspaceView.command 'atomatigit:close', => @close()
-
+  # Public: Close the atomatigit pane.
   close: ->
     return @errorNoGitRepo() unless atom.project.getRepo()
     @repoView.detach() if @repoView?.hasParent()

+  # Public: Open (or focus) the atomatigit window.
   focus: ->
     return @errorNoGitRepo() unless atom.project.getRepo()
     atom.workspaceView.appendToRight(@repoView) unless @repoView?.hasParent()
     @repo.reload().then =>
       @repoView.focus()

+  # Internal: Destroy atomatigit instance.
   deactivate: ->
+    @repo.destroy()
     @repoView.destroy()
-    @repo.destroy()

+  # Internal: Display error message if the project is no git repository.
   errorNoGitRepo: ->
     new ErrorView(message: 'Project is no git repository!')
+
+  # Internal: Register package commands with atom.
+  insertCommands: ->
+    atom.workspaceView.command 'atomatigit:show',  => @focus()
+    atom.workspaceView.command 'atomatigit:close', => @close()


Draw grid child

Draw grid child

--- src/config/layout.coffee
+++ src/config/layout.coffee
@@ -16,7 +16,7 @@
       div {id: "controls"}
       div {id: "grid"}
       div {id: "inspector"}
-      draw world
+      draw world.find_child('grid')
     ]

   _LABEL: "layout"


app-updates: Fixed update progress notifications.

--- client/app/MainApp/kodingappscontroller.coffee
+++ client/app/MainApp/kodingappscontroller.coffee
@@ -201,11 +201,12 @@
       return @utils.versionCompare appVersion, "lt", @publishedApps[appName].manifest.version

   updateUserApp: (manifest, callback) ->
-    appName       = manifest.name
-    @notification = new KDNotificationView
-      type     : "mini"
-      title    : "Updating #{appName}: Deleting old app files"
-      duration : 120000
+    appName = manifest.name
+    unless @notification
+      @notification = new KDNotificationView
+        type        : "mini"
+        title       : "Updating #{appName}: Deleting old app files"
+        duration    : 10000

     folder = FSHelper.createFileFromPath manifest.path, "folder"
     folder.remove (err, res) =>


client: trigger revive for machine after machine setLabel called

--- client/app/lib/flux/environment/actions.coffee
+++ client/app/lib/flux/environment/actions.coffee
@@ -797,9 +797,12 @@

 setLabel = (machineUId, label) ->

+  {computeController} = kd.singletons
+
   new Promise (resolve, reject) ->
     fetchMachineByUId machineUId, (machine) ->
       machine.setLabel label, (err, newLabel) ->
+        computeController.triggerReviveFor machine._id
         return reject err  if err
         resolve newLabel


Show stack template content modal in template list

--- client/app/lib/stacks/stacktemplatelist.coffee
+++ client/app/lib/stacks/stacktemplatelist.coffee
@@ -7,6 +7,7 @@
 showError             = require 'app/util/showError'

 StackTemplateListItem = require './stacktemplatelistitem'
+StackTemplateContentModal = require './stacktemplatecontentmodal'

 module.exports = class StackTemplateList extends KDListView
@@ -46,15 +47,4 @@

   showItemContent: (item) ->

-    stackTemplate = item.getData()
-    content = JSON.stringify stackTemplate, null, 2
-    cred = hljs.highlight('json', content).value
-
-    new KDModalView
-      title          : stackTemplate.title
-      subtitle       : stackTemplate.modifiedAt
-      cssClass       : 'has-markdown content-modal'
-      height         : 500
-      overlay        : yes
-      overlayOptions : cssClass : 'second-overlay'
-      content        : "
#{cred}
"
+    new StackTemplateContentModal {}, item.getData()


video/model: add instance level helpers

video/model: add instance level helpers

--- client/app/lib/videocollaboration/model.coffee
+++ client/app/lib/videocollaboration/model.coffee
@@ -220,6 +220,27 @@

   ###*
+   * Parse the username from given connection and add a default subscriber
+   * without video to trigger view updates.
+   *
+   * @param {OT.Connection} connection
+   * @return {(undefined|object)}
+  ###
+  registerDefaultSubscriber: (connection) ->
+
+    { id, data } = connection
+    { nickname } = JSON.parse data
+
+    return  if nickname is getNick()
+
+    _subscriber = helper.defaultSubscriber nickname
+
+    @subscribers[id] = _subscriber
+
+    return _subscriber
+
+
+  ###*
    * Transforms given publisher instance from OpenTok into our own
    * `ParticipantType.Publisher` class instance and it registers it into model's
    * participant property.. It sets the model's stream instance to publisher's
@@ -522,6 +543,17 @@

   ###*
+   * @param {OT.Connection} connection
+   * @return {boolean}
+  ###
+  isMyConnection: (connection) ->
+
+    { nickname } = JSON.parse connection.data
+
+    return nickname is getNick()
+
+
+  ###*
    * Public access method for all participants. Just because internally the
    * subscribers are cached with `connectionId` instead of `nickname`s of
    * subscribers/publisher, we are first transforming the subscribers and merge
@@ -551,6 +583,22 @@

   ###*
+   * Set state to increment connection count.
+  ###
+  incrementConnectionCount: ->
+
+    @setState { connectionCount: @state.connectionCount + 1 }
+
+
+  ###*
+   * Set state to decrement connection count.
+  ###
+  decrementConnectionCount: ->
+
+    @setState { connectionCount: @state.connectionCount - 1 }
+
+
+  ###*
    * Check to see if participant with given nickname, and calls the callback
    * with result.
    *


video/model: add api and handler for muting participants

--- client/app/lib/videocollaboration/model.coffee
+++ client/app/lib/videocollaboration/model.coffee
@@ -144,6 +144,10 @@

     session.on 'signal:start', => @enableVideo { error: (err) => console.error err }

+    # this event only comes to the user who has been muted, so need to make a
+    # filtering here.
+    session.on 'signal:mute', => @setAudioState off
+

   subscribeToStream: (session, stream) ->

@@ -341,6 +345,20 @@

   ###*
+   * Action for muting a participant. Sends the signal, the rest will be
+   * handled by the `signal:mute` handler.
+   *
+   * @param {string} nickname
+  ###
+  muteParticipant: (nickname) ->
+
+    return  unless participant = @getParticipant nickname
+
+    @_service.sendMuteSignal @channel, participant, (err) ->
+      return console.log err  if err
+
+
+  ###*
    * Set audio state to given state.
    *
    * @param {boolean} state


add, modify some Named Entities (NEs)

add, modify some Named Entities (NEs)

--- options.coffee
+++ options.coffee
@@ -41,6 +41,7 @@
   ### type may be `tex` or `text` ###
   entities:
     'nl':           { type: 'tex',  value: '\\\\', }                  ### new line ###
+    'amp':          { type: 'tex',  value: '\\&', }                   ### ampersand ###
     'np':           { type: 'tex',  value: '\\null\\newpage{}', }     ### new page ###
     # 'par':          { type: 'tex',  value: '\n\n', }                  ### paragraph break ###
     'thinspace':    { type: 'tex',  value: '\\thinspace{}', }         ### thin space ###
@@ -51,12 +52,23 @@
     'geta':         { type: 'text', value: '〓', }                    ### Geta mark ###
     'MKTS':         { type: 'tex',  value: 'MKTS', }                  ### MKTS logo ###
     'TeX':          { type: 'tex',  value: '\\TeX{}', }               ### TeX logo ###
+    'LaTeX':        { type: 'tex',  value: '\\LaTeX{}', }             ### LaTeX logo ###
+    'identical':    { type: 'tex',  value: '{\\mktsFontfileHanamina{}≡}', }
+    'similar':      { type: 'tex',  value: '{\\mktsFontfileHanamina{}≋}', }
     'nbsp':         { type: 'tex',  value: '~', }                     ### Non-Breaking Space ###
     '~':            { type: 'tex',  value: '~', }                     ### Non-Breaking Space ###
+    '~~':           { type: 'tex',  value: '~~', }                    ### 2 Non-Breaking Spaces ###
+    '~~~~':         { type: 'tex',  value: '~~~~', }                  ### 4 Non-Breaking Spaces ###
+    '~~~~~~':       { type: 'tex',  value: '~~~~~~', }                ### 6 Non-Breaking Spaces ###
     'lt':           { type: 'tex',  value: '<', }                     ### Left Pointy Bracket ###
     'gt':           { type: 'tex',  value: '>', }                     ### Right Pointy Bracket ###
-    'ulsymbolr':    { type: 'tex', value: "\\makebox[\\mktsLineheight][r]{{\\mktstfPushRaise{-0.2}{-0.1}\\mktsFontfileHanamina{}◼}}", }
-    'ulsymbol':     { type: 'tex', value: "\\makebox[\\mktsLineheight][l]{{\\mktstfPushRaise{-0.2}{-0.1}\\mktsFontfileHanamina{}◼}}", }
+    'bt':           { type: 'tex',  value: '`', }                     ### backtick ###
+    '3lines':       { type: 'tex',  value: '{\\mktsFontfileHanamina{}☰}', }      ### 3 lines ###
+    'deleatur':     { type: 'tex',  value: '{\\mktsFontfileSunexta{}₰}', }        ### Deleatur/Denarius ###
+    'ulsymbolr':    { type: 'tex', value: "\\makebox[\\mktsLineheight][r]{{\\mktsFontfileLmromantenregular{}•}}", }
+    'ulsymbol':     { type: 'tex', value: "\\makebox[\\mktsLineheight][l]{{\\mktsFontfileLmromantenregular{}•}}", }
+    # 'ulsymbolr':    { type: 'tex', value: "\\makebox[\\mktsLineheight][r]{{\\mktstfPushRaise{-0.2}{-0.1}\\mktsFontfileHanamina{}◼}}", }
+    # 'ulsymbol':     { type: 'tex', value: "\\makebox[\\mktsLineheight][l]{{\\mktstfPushRaise{-0.2}{-0.1}\\mktsFontfileHanamina{}◼}}", }

     ### TAINT these are special-interest symbols that should be defined locally ###
     # 'Quasi':        { type: 'text', value: '𝕼', }
@@ -576,6 +588,7 @@
       '╪':      cmd: 'mktsStyleBoxDrawing'
       ### Arrows ###
       '⤾':      cmd: 'mktsFontfileDejavuserif'
+      '₰':      cmd: 'mktsFontfileSunexta'

       ### Ideographic description characters: ###
       '↻':         cmd: 'cnxJzr',      glyph: ''       # raise: -0.2,


update

update


fix initialize flow and add comment

fix initialize flow and add comment

--- coffee/controllers/mainCtrl.coffee
+++ coffee/controllers/mainCtrl.coffee
@@ -33,7 +33,7 @@
    initialize events.
   ###
   _initializeEvents = () ->
-    Log.debug "start initialize Event."
+    Log.debug "[4] initializeEvents start"
     DataAdapter.addEventListener DataAdapter.ACCOUNT_ADDED, _loadRedmine
     DataAdapter.addEventListener DataAdapter.PROJECTS_CHANGED, () ->
       Project.syncLocal(DataAdapter.getProjects())
@@ -41,7 +41,7 @@
       Ticket.syncLocal(DataAdapter.tickets)
     Option.onChanged('reportUsage', (e) -> Analytics.setPermission(e) )
     Option.onChanged('hideNonTicketProject',  _toggleProjectHidden)
-    Log.debug "finish initialize Event."
+    Log.debug "[4] initializeEvents success"

   ###
    load projects from redmine.
@@ -201,22 +201,27 @@
    initialize GoogleAnalytics.
   ###
   _initializeGoogleAnalytics = () ->
+    Log.debug "[1] initializeGoogleAnalytics start"
     Analytics.init {
       serviceName:   "RedmineTimeTracker"
       analyticsCode: "UA-32234486-7"
     }
     Analytics.sendView("/app/")
+    Log.debug "[1] initializeGoogleAnalytics success"

   ###
    initialize Data from chrome storage.
   ###
   _initializeDataFromChrome = () ->
-    Log.debug "start initialize data."
+    Log.debug "[2] initializeDataFromChrome start"
     Option.loadOptions()
       .then(_initializeAccount)
       .then(_initializeProject)
       .then(_initializeIssues)
-      .then(() -> Log.debug "finish initialize data.")
+      .then(() ->
+        Log.debug "[2] initializeDataFromChrome success."
+      , () ->
+        Log.warn "[2] initializeDataFromChrome failed")

   ###
    Initialize account from chrome storage.
@@ -232,23 +237,24 @@
    Initialize project from chrome storage.
   ###
   _initializeProject = () ->
-    Project.load().then (projects) ->
-      DataAdapter.addProjects(projects)
+    Project.load()
+      .then((projects) -> DataAdapter.addProjects(projects))

   ###
    Initialize issues status.
   ###
   _initializeIssues = () ->
-    Ticket.load().then (tickets) ->
-      DataAdapter.toggleIsTicketShow(tickets)
+    Ticket.load()
+      .then((tickets) -> DataAdapter.toggleIsTicketShow(tickets))

   ###
    initialize Data from Redmine.
   ###
   _initializeDataFromRedmine = () ->
+    Log.debug "[3] initializeDataFromRedmine start "
     accounts = DataAdapter.getAccount()
-    _loadRedmine(accounts)
-
+    $q.all(_loadRedmine(accounts))
+      .finally(-> Log.debug "[3] initializeDataFromRedmine success")

   ###
    set datasync event to chrome alarms.


lil train router, param mapper

lil train router, param mapper

--- collections/filter_artworks.coffee
+++ collections/filter_artworks.coffee
@@ -5,9 +5,23 @@
 { API_URL } = require('sharify').data

 module.exports = class FilterArtworks extends Artworks
+  defaults:
+    mapppedParams:
+      related_gene: 'gene_id'
+      gallery: 'partner_id'
+      institution: 'partner_id'
+
   url: "#{API_URL}/api/v1/filter/artworks"

-  sync: (method, collection, options)->
+  initialize: ( models, options = {} ) ->
+    { @mapppedParams } = _.defaults options, @defaults
+    super
+
+  sync: (method, collection, options) =>
+    for k, v of @mapppedParams
+      if val = options.data[k]
+        options.data[v] = val
+        delete options.data[k]
     options.data = decodeURIComponent qs.stringify(options.data, { arrayFormat: 'brackets' })
     super


Tests: Environments: Make pricing optional in addVM test.

--- tests/src/environments/environments.coffee
+++ tests/src/environments/environments.coffee
@@ -73,6 +73,15 @@
   return domainName

+clickCreateVMButton = (browser) ->
+
+  browser
+    .waitForElementVisible    '.env-modal.paid-plan', 25000
+    .click                    '.env-modal.paid-plan button'
+    .waitForElementVisible    'a[href="/IDE/koding-vm-1/my-workspace"]', 25000
+    .end()
+
+
 module.exports =

@@ -175,32 +184,37 @@

   addVM: (browser) ->

-    modalSelector = '.computeplan-modal.free-plan .kdmodal-inner'
+    modalSelector = '.computeplan-modal.free-plan'
     pricingPage   = '.content-page.pricing'

     helpers.beginTest(browser)
     helpers.waitForVMRunning(browser)
-    seeUpgradeModal(browser)
-
-    browser
-      .waitForElementVisible   modalSelector, 20000
-      .waitForElementVisible   modalSelector + ' a.custom-link-view span', 20000
-      .click                   modalSelector + ' a.custom-link-view span'
-      .waitForElementVisible   pricingPage, 25000
-      .waitForElementVisible   pricingPage + ' .plans .developer', 25000
-      .pause 2000
-      .click                   pricingPage + ' .plans .developer .plan-buy-button'
-
-    helpers.fillPaymentForm(browser)
-
-    browser.url helpers.getUrl() + '/IDE'
+
     clickAddVMButton(browser)

-    browser
-      .waitForElementVisible    '.env-modal.paid-plan', 25000
-      .click                    '.env-modal.paid-plan button'
-      .waitForElementVisible    'a[href="/IDE/koding-vm-1/my-workspace"]', 25000
-      .end()
+    browser.pause 5000 # wait to see the modal
+
+    browser.element 'css selector', modalSelector, (result) =>
+      if result.status is 0
+        console.log 'if de bu salak'
+        browser
+          .waitForElementVisible   modalSelector, 20000
+          .waitForElementVisible   modalSelector + ' a.custom-link-view span', 20000
+          .click                   modalSelector + ' a.custom-link-view span'
+          .waitForElementVisible   pricingPage, 25000
+          .waitForElementVisible   pricingPage + ' .plans .developer', 25000
+          .pause 2000
+          .click                   pricingPage + ' .plans .developer .plan-buy-button'
+
+        helpers.fillPaymentForm(browser)
+
+        browser.url helpers.getUrl() + '/IDE'
+        clickAddVMButton(browser)
+        clickCreateVMButton(browser)
+      else
+        console.log 'else de bu salak', result.status
+        clickCreateVMButton(browser)
+

   # this test depends addVM test.


Tests: Environments: Added makePaidVMAlwaysOn test.

--- tests/src/environments/environments.coffee
+++ tests/src/environments/environments.coffee
@@ -6,9 +6,12 @@
 modalSelector = '.activity-modal.vm-settings'

-openVmSettingsModal = (browser) ->
-
-  vmSelector = '.activity-sidebar a.running'
+openVmSettingsModal = (browser, vmName) ->
+
+  if not vmName
+    vmName = 'koding-vm-0'
+
+  vmSelector = '.activity-sidebar a[href="/IDE/' + vmName + '/my-workspace"].running'

   browser
     .waitForElementVisible   vmSelector, 20000
@@ -217,3 +220,25 @@

     browser.end()

+
+  # this test depends addVM and turnOnNewPaidVM tests.
+  makePaidVMAlwaysOn: (browser) ->
+
+    helpers.beginTest(browser)
+
+    openVmSettingsModal(browser, 'koding-vm-1')
+    clickMoreButtonInVMSettingsModal(browser)
+
+    browser
+      .waitForElementVisible    '.more-form .alwayson', 20000
+      .click                    '.more-form .alwayson .koding-on-off'
+      .pause                    1000
+      .refresh()
+      .waitForElementVisible    '[testpath=main-sidebar]', 25000
+
+    openVmSettingsModal(browser, 'koding-vm-1')
+    clickMoreButtonInVMSettingsModal(browser)
+
+    browser
+      .waitForElementVisible   '.more-form .alwayson .koding-on-off.on', 20000
+      .end()


Use demux to route eachchannel to their listener

--- coffee/src/parrot.coffee
+++ coffee/src/parrot.coffee
@@ -6,6 +6,13 @@
   @ASSETS_SERVER = "https://neighborparrot.com"
   @debug = false

+
+  # Local!! remove it in production!!
+  @brokerHost = "http://localhost:9000"
+  @WEBSOCKET_SERVER = "ws://localhost:9000"
+  @ASSETS_SERVER = "http://localhost:9000"
+
+
   # Parrot constructot
   # @param [String] channel name
   # @param [Function] onmessage: callback called when receive a message
@@ -13,6 +20,7 @@
   # @param [Function] onerror: callback called on error
   constructor: (params) ->
     @params = params
+    @listeners = {}
     @service = @params['service']
     @channel = @params['channel']
     @log "Creating a parrot with channel: #{@channel}"
@@ -37,6 +45,16 @@
   error: (msg) ->
     console.warn msg if @debug

+  bind: (channel, listener) ->
+    @listeners[channel] = listener
+
+  # Demultiplex messages.
+  # Route each message to each listener
+  demuxMessage: (message) ->
+    data = JSON.parse message.data
+    listener = @listeners[data.channel]
+    listener data
+
   #===============================================
   # Event Source
   #===============================================
@@ -47,7 +65,7 @@
     msg = event.data
     @log "Dispatching message: @{msg}"
     if msg.match("^data:")
-      @onmessage @parseMessage msg
+      @demuxMessage @parseMessage msg
       @log "Calling onmessage callout"
     else if msg.match("^open:") && @onopen
       @onopen()
@@ -71,7 +89,7 @@
       window.attachEvent 'onmessage', bounder
     # TODO: if Postmessage is not supported??

-  # TODO : remove this!
+  # TODO : refactor this!
   parseMessage: (msg) ->
     { data: msg.substring(5) }

@@ -132,7 +150,7 @@
       _this.onopen.call _this, e
     , false)
     @ws.addEventListener('message', (e) ->
-      _this.onmessage.call _this, e
+      _this.demuxMessage.call _this, e
     , false)
     @ws.addEventListener('error', (e) ->
       _this.onerror.call _this, e


Properly fire update events

Properly fire update events

--- lib/bootstrap-magic-client.coffee
+++ lib/bootstrap-magic-client.coffee
@@ -43,5 +43,13 @@
     $input = $('input',@)
     @endVal = $input.val()
     if @startVal isnt @endVal
-      BootstrapMagic.setOverride $input.attr('name'), @endVal
       $input.trigger 'change'
+
+Template._bootstrap_magic.events
+  'change input.bootstrap-magic-input' : (e) ->
+    console.log 'changed!'
+    $input = $(e.currentTarget)
+    BootstrapMagic.setOverride $input.attr('name'), $input.val()
+
+
+


Reverting back to working version.

Reverting back to working version.

--- chapter1/reader.coffee
+++ chapter1/reader.coffee
@@ -1,18 +1,12 @@
 {car, cdr, cons, nil, nilp, pairp, vectorToList, list} = require 'cons-lists/lists'
-{length} = require 'cons-lists/reduce'
 {inspect} = require "util"
+

 NEWLINES   = ["\n", "\r", "\x0B", "\x0C"]
 WHITESPACE = [" ", "\t"].concat(NEWLINES)

 EOF = new (class Eof)()
 EOO = new (class Eoo)()
-
-stringp = (o) -> o?.__type == 'string'
-
-errorp = (o) -> (pairp o) and (car o) == 'error'
-
-streq = (s, t) -> String(s) == String(t)

 class Source
   constructor: (@inStream) ->
@@ -38,24 +32,13 @@
 skipWS = (inStream) ->
   while inStream.peek() in WHITESPACE then inStream.next()

-invisibleProperty = (obj, name, value) ->
-  Object.defineProperty obj, name,
-    value: value
-    configurable: false
-    enumerable: false
-    writable: false
-  obj
-
-lineInfo = (obj, line, column) ->
-  invisibleProperty obj, '__position', {line: line, column: column}
-
 # (type, value, line, column) -> (node {type, value, line, column)}
-makeObj = (value, type, line, column) ->
-  lineInfo (invisibleProperty value, '__type', type), line, column
+makeObj = (type, value, line, column) ->
+  list(type, value, line, column)

 # msg -> (IO -> Node => Error)
 handleError = (message) ->
-  (line, column) -> lineInfo (cons "error", message), line, column
+  (line, column) -> makeObj('error', message, line, column)

 # IO -> Node => Comment
 readComment = (inStream) ->
@@ -64,26 +47,26 @@
     inStream.next()).join("")
   if not inStream.done()
     inStream.next()
-  makeObj (new String r), 'comment', line, column
+  makeObj 'comment', r, line, column

 # IO -> (Node => Literal => String) | Error
 readString = (inStream) ->
   [line, column] = inStream.position()
   inStream.next()
-  string = until streq(inStream.peek(), '"') or inStream.done()
-    if (streq inStream.peek(), '\\')
+  string = until inStream.peek() == '"' or inStream.done()
+    if inStream.peek() == '\\'
       inStream.next()
     inStream.next()
   if inStream.done()
     return handleError("end of file seen before end of string.")(line, column)
   inStream.next()
-  makeObj (new String (string.join '')), 'string', line, column
+  makeObj 'string', (string.join ''), line, column

 # (String) -> (Node => Literal => Number) | Nothing
 readMaybeNumber = (symbol) ->
-  if streq(symbol[0], '+')
+  if symbol[0] == '+'
     return readMaybeNumber symbol.substr(1)
-  if streq(symbol[0], '-')
+  if symbol[0] == '-'
     ret = readMaybeNumber symbol.substr(1)
     return if ret? then -1 * ret else undefined
   if symbol.search(/^0x[0-9a-fA-F]+$/) > -1
@@ -102,48 +85,43 @@
   symbol = (until (inStream.done() or inStream.peek() in tableKeys or inStream.peek() in WHITESPACE)
     inStream.next()).join ''
   number = readMaybeNumber symbol
-  if nilp number
-    return nil
   if number?
-    return makeObj (new Number number), 'number', line, column
-  makeObj (new String symbol), 'symbol', line, column
+    return makeObj 'number', number, line, column
+  makeObj 'symbol', symbol, line, column

 # (Delim, TypeName) -> IO -> (IO, node) | Error
-makeReadContainer = (delim, constructor) ->
+makeReadPair = (delim, type) ->
   # IO -> (IO, Node) | Error
   (inStream) ->
     inStream.next()
     skipWS inStream
     [line, column] = inStream.position()
-    if streq(inStream.peek(), delim)
+    if inStream.peek() == delim
       inStream.next()
-      return constructor(nil, line, column)
+      return makeObj(type, nil, line, column)

     # IO -> (IO, Node) | Error
     dotted = false
-    readInContainer = (inStream) ->
+    readEachPair = (inStream) ->
       [line, column] = inStream.position()
       obj = read inStream, true, null, true
-      if streq(inStream.peek(), delim)
+      if inStream.peek() == delim
         if dotted then return obj
-        return lineInfo (cons obj, nil), line, column
+        return cons obj, nil
       if inStream.done() then return handleError("Unexpected end of input")(line, column)
       if dotted then return handleError("More than one symbol after dot")
-      return obj if (errorp obj)
-      if streq(obj, ".")
+      return obj if (car obj) == 'error'
+      if (car obj) == 'symbol' and (car cdr obj) == '.'
         dotted = true
-        return readInContainer inStream
-      cons obj, readInContainer inStream
-
-    ret = readInContainer(inStream)
+        return readEachPair inStream
+      cons obj, readEachPair inStream
+
+    ret = makeObj type, readEachPair(inStream), line, column
     inStream.next()
     ret

 # Type -> (IO -> (IO, Node))
-#
-# Handles the quoted symbol things.
-#
 prefixReader = (type) ->
   # IO -> (IO, Node)
   (inStream) ->
@@ -152,7 +130,7 @@
     [line1, column1] = inStream.position()
     obj = read inStream, true, null, true
     return obj if (car obj) == 'error'
-    cons (makeObj (new String type), 'symbol', line1, column1), obj
+    makeObj "list", cons((makeObj("symbol", type, line1, column1)), cons(obj)), line, column

 # I really wanted to make anything more complex than a list (like an
 # object or a vector) something handled by a read macro.  Maybe in a
@@ -160,14 +138,12 @@

 readMacros =
   '"': readString
-  '(': makeReadContainer ')', (o, l, c) -> lineInfo o, l, c
+  '(': makeReadPair ')', 'list'
   ')': handleError "Closing paren encountered"
-  '[': makeReadContainer ']', (o, l, c) -> cons("vector", lineinfo o, l, c)
+  '[': makeReadPair ']', 'vector'
   ']': handleError "Closing bracket encountered"
-  '{': makeReadContainer '}', (o, l, c) ->
-    if length(o) % 2 != 0
-      return handleError "Records require an even number of items."
-    cons('record', lineinfo o, l, c)
+  '{': makeReadPair('}', 'record', (res) ->
+    res.length % 2 == 0 and true or mkerr "record key without value")
   '}': handleError "Closing curly without corresponding opening."
   "`": prefixReader 'back-quote'
   "'": prefixReader 'quote'
@@ -177,9 +153,9 @@

 # Given a stream, reads from the stream until a single complete lisp
 # object has been found and returns the object
+
 # IO -> Form
-read = (inStream, eofErrorP = false, eofError = EOF,
-        recursiveP = false, inReadMacros = null, keepComments = false) ->
+read = (inStream, eofErrorP = false, eofError = EOF, recursiveP = false, inReadMacros = null, keepComments = false) ->
   inStream = if inStream instanceof Source then inStream else new Source inStream
   inReadMacros = if InReadMacros? then inReadMacros else readMacros
   inReadMacroKeys = (i for i of inReadMacros)