slack: invitations: invites first working version.

--- client/admin/lib/views/invitations/slackinviteview.coffee
+++ client/admin/lib/views/invitations/slackinviteview.coffee
@@ -2,7 +2,7 @@
 kd            = require 'kd'
 whoami        = require 'app/util/whoami'
 SlackUserItem = require './slackuseritem'
-
+async         = require 'async'

 titleDecorator = (channel) -> if channel.is_group then channel.name else "##{channel.name}"

@@ -24,7 +24,7 @@
   USERS_URL     = "#{location.origin}/api/social/slack/users"
   CHANNELS_URL  = "#{location.origin}/api/social/slack/channels"
   MESSAGING_URL = "#{location.origin}/api/social/slack/message"
-  ICON_URL      = "#{location.origin}/a/images/logos/notify_logo.png"
+  ICON_URL      = "https://koding.com/a/images/logos/notify_logo.png"

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

@@ -98,11 +98,20 @@
     wrapper.addSubView @inviteChannel = new kd.ButtonView
       cssClass : 'solid medium green invite-members'
       title    : titleHelper @counts.general, '#general'
+      callback : =>
+
+        return  unless @users
+
+        channelMemberIds = @allChannels[select.getValue()].members
+        recipients       = @users.filter (user) -> user.id in channelMemberIds
+
+        @sendMessages recipients
+

   createIndividualInviter: (users) ->

-    users = users.filter (user) -> user.id isnt SLACKBOT_ID
+    @users = users = users.filter (user) -> user.id isnt SLACKBOT_ID

     @wrapper.addSubView new kd.CustomHTMLView
       tagName : 'h3'
@@ -137,38 +146,80 @@
       @inviteIndividual.enable()
       @inviteIndividual.setTitle title

+    @on 'InvitationsAreSent', ->
+      listController.getListItems()
+        .filter (item) -> item.checkBox.getValue()
+        .forEach (item) -> item.checkBox.setValue off

     list.addSubView @inviteIndividual = new kd.ButtonView
       cssClass : 'solid medium green invite-members fr'
       title    : 'Invite selected members'
       disabled : yes
-      callback : => @prepareMessages getSelectedMembers listController
-
-
-  prepareMessages: (recipients) ->
-
-
-
-    recipients.forEach (recipient) ->
-      $.ajax
-        url              : MESSAGING_URL
-        method           : 'POST'
-        headers          :
-          Accept         : 'application/json'
-          'Content-Type' : 'application/json'
-        data             : JSON.stringify
-          channel        : recipient.id
-          text           : 'Invite ulan!'
-          params         :
-            as_user      : no
-            username     : whoami().profile.firstName
-            icon_url     : ICON_URL
-            icon_emoji   : ':cihangir:'
-        success: ->
-          console.log 'success'
-        error: ->
-          console.log 'error'
-
-
-
-  sendMessage: (message) ->
+      callback : => @sendMessages getSelectedMembers listController
+
+
+  sendMessages: (recipients) ->
+
+    invitations = recipients.map ({profile}) ->
+      email     : profile.email
+      firstName : profile.first_name
+      lastName  : profile.last_name
+
+    kd.remote.api.JInvitation.create
+      invitations : invitations
+      returnCodes : yes
+      noEmail     : yes
+    , (err, res) =>
+
+      return new kd.NotificationView title : 'Something went wrong, please try again!'  if err
+
+      invites = {}
+
+      res.forEach (invite) ->
+        invites[invite.email.toLowerCase()] =
+          code           : invite.code
+          alreadyInvited : invite.alreadyInvited
+
+
+      queue = recipients.map (recipient) -> (fin) ->
+
+        return  unless invite = invites[recipient.profile.email.toLowerCase()]
+
+        inviter = whoami().profile.firstName
+        group   = kd.singletons.groupsController.getCurrentGroup()
+        title   = group.title or group.slug
+        link    = "#{location.origin}/Invitation/#{invite.code}"
+        msg     = "#{inviter} has invited you to #{title} team on Koding!"
+
+        $.ajax
+          url              : MESSAGING_URL
+          method           : 'POST'
+          headers          :
+            Accept         : 'application/json'
+            'Content-Type' : 'application/json'
+          data             : JSON.stringify
+            channel        : recipient.id
+            text           : ''
+            params         :
+              as_user      : no
+              username     : "#{inviter} from Koding!"
+              icon_url     : ICON_URL
+              attachments  : [{
+                color      : '#5373A1'
+                title      : msg
+                title_link : link
+                text       : "Click the link below to join #{title}. \n #{link}"
+                thumb_url  : ICON_URL
+                fallback   : "#{msg} #{link}"
+              }]
+
+          success: -> fin()
+          error: -> fin 'error'
+
+      async.parallel queue, (err, res) =>
+        # make this smarter
+        return new kd.NotificationView title: 'There were some errors'  if err
+
+        new kd.NotificationView title: 'All invitations are sent!'
+
+        @emit 'InvitationsAreSent'


add some manager API.

add some manager API.

--- src/renderer/xmail.coffee
+++ src/renderer/xmail.coffee
@@ -143,7 +143,106 @@
   # Returns the absolute path to `~/.atom`.
   getConfigDirPath: ->
     @constructor.getConfigDirPath()
-
+
+  # Public: Get the load settings for the current window.
+  #
+  # Returns an {Object} containing all the load setting key/value pairs.
+  getLoadSettings: ->
+    @constructor.getLoadSettings()
+
+  ###
+  Section: Managing The Atom Window
+  ###
+
+  # Essential: Get the size of current window.
+  #
+  # Returns an {Object} in the format `{width: 1000, height: 700}`
+  getSize: ->
+    [width, height] = @getCurrentWindow().getSize()
+    {width, height}
+
+  # Essential: Set the size of current window.
+  #
+  # * `width` The {Number} of pixels.
+  # * `height` The {Number} of pixels.
+  setSize: (width, height) ->
+    @getCurrentWindow().setSize(width, height)
+
+  # Essential: Get the position of current window.
+  #
+  # Returns an {Object} in the format `{x: 10, y: 20}`
+  getPosition: ->
+    [x, y] = @getCurrentWindow().getPosition()
+    {x, y}
+
+  # Essential: Set the position of current window.
+  #
+  # * `x` The {Number} of pixels.
+  # * `y` The {Number} of pixels.
+  setPosition: (x, y) ->
+    ipc.send('call-window-method', 'setPosition', x, y)
+
+  # Extended: Get the current window
+  getCurrentWindow: ->
+    @constructor.getCurrentWindow()
+
+  # Extended: Move current window to the center of the screen.
+  center: ->
+    ipc.send('call-window-method', 'center')
+
+  # Extended: Focus the current window.
+  focus: ->
+    ipc.send('call-window-method', 'focus')
+    $(window).focus()
+
+  # Extended: Show the current window.
+  show: ->
+    ipc.send('call-window-method', 'show')
+
+  # Extended: Hide the current window.
+  hide: ->
+    ipc.send('call-window-method', 'hide')
+
+  # Extended: Reload the current window.
+  reload: ->
+    ipc.send('call-window-method', 'restart')
+
+  # Extended: Returns a {Boolean} true when the current window is maximized.
+  isMaximixed: ->
+    @getCurrentWindow().isMaximized()
+
+  maximize: ->
+    ipc.send('call-window-method', 'maximize')
+
+  # Extended: Is the current window in full screen mode?
+  isFullScreen: ->
+    @getCurrentWindow().isFullScreen()
+
+  # Extended: Set the full screen state of the current window.
+  setFullScreen: (fullScreen=false) ->
+    ipc.send('call-window-method', 'setFullScreen', fullScreen)
+    if fullScreen
+      document.body.classList.add("fullscreen")
+    else
+      document.body.classList.remove("fullscreen")
+
+  # Extended: Toggle the full screen state of the current window.
+  toggleFullScreen: ->
+    @setFullScreen(not @isFullScreen())
+
+  # Restore the window to its previous dimensions and show it.
+  #
+  # Also restores the full screen and maximized state on the next tick to
+  # prevent resize glitches.
+  displayWindow: ->
+    dimensions = @restoreWindowDimensions()
+    @show()
+
+    setImmediate =>
+      @focus()
+      @setFullScreen(true) if @workspace?.fullScreen
+      @maximize() if dimensions?.maximized and process.platform isnt 'darwin'
+
   # Get the dimensions of this window.
   #
   # Returns an {Object} with the following keys:
@@ -222,44 +321,3 @@
     workspaceElement = @views.getView(@workspace)
     backgroundColor = window.getComputedStyle(workspaceElement)['background-color']
     window.localStorage.setItem('atom:window-background-color', backgroundColor)
-
-  # Essential: Get the size of current window.
-  #
-  # Returns an {Object} in the format `{width: 1000, height: 700}`
-  getSize: ->
-    [width, height] = @getCurrentWindow().getSize()
-    {width, height}
-
-  # Essential: Set the size of current window.
-  #
-  # * `width` The {Number} of pixels.
-  # * `height` The {Number} of pixels.
-  setSize: (width, height) ->
-    @getCurrentWindow().setSize(width, height)
-
-  # Essential: Get the position of current window.
-  #
-  # Returns an {Object} in the format `{x: 10, y: 20}`
-  getPosition: ->
-    [x, y] = @getCurrentWindow().getPosition()
-    {x, y}
-
-  # Essential: Set the position of current window.
-  #
-  # * `x` The {Number} of pixels.
-  # * `y` The {Number} of pixels.
-  setPosition: (x, y) ->
-    ipc.send('call-window-method', 'setPosition', x, y)
-
-  # Extended: Get the current window
-  getCurrentWindow: ->
-    @constructor.getCurrentWindow()
-
-  # Extended: Move current window to the center of the screen.
-  center: ->
-    ipc.send('call-window-method', 'center')
-
-  # Extended: Focus the current window.
-  focus: ->
-    ipc.send('call-window-method', 'focus')
-    $(window).focus()


Use 500 error response to indicate error


Apps: Added screenshots to raw manifest file.

--- client/app/MainApp/kodingappscontroller.coffee
+++ client/app/MainApp/kodingappscontroller.coffee
@@ -757,6 +757,7 @@
         type        : "tab"
       icns          :
         "128"       : "./resources/icon.128.png"
+      screenshots   : []
       menu          : []
       fileTypes     : []


fix email sync

fix email sync

--- client/app/views/register/import_google.coffee
+++ client/app/views/register/import_google.coffee
@@ -70,11 +70,24 @@
             'scrollbars=1'
             'resizable=1'
         ].join(',')
-        oauthUrl = "https://accounts.google.com/o/oauth2/auth?scope=https%3A%2F\
-        %2Fwww.googleapis.com%2Fauth%2Fcalendar.readonly%20https%3A%2F%2Fpicasa\
-        web.google.com%2Fdata%2F%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fco\
-        ntacts.readonly%20email%20profile&response_type=code&client_id=26064585\
-        0650-2oeufakc8ddbrn8p4o58emsl7u0r0c8s.apps.googleusercontent.com&redire\
-        ct_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob"
+
+        scopes = [
+            'https://www.googleapis.com/auth/calendar.readonly'
+            'https://picasaweb.google.com/data/'
+            'https://www.googleapis.com/auth/contacts.readonly'
+            'email'
+            'https://mail.google.com/'
+            'profile'
+        ].join(' ')
+
+
+        clientID = '260645850650-2oeufakc8ddbrn8p4o58emsl7u0r0c8s'
+        clientID += '.apps.googleusercontent.com'
+
+        oauthUrl = "https://accounts.google.com/o/oauth2/auth"
+        oauthUrl += '?scope=' + encodeURIComponent(scopes)
+        oauthUrl += '&response_type=code'
+        oauthUrl += '&client_id=' + clientID
+        oauthUrl += '&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob'
         @popup = window.open oauthUrl, 'Google OAuth',opts
         @changeStep 'pastecode'


HomeStackImport: open stack editor automatically

--- client/home/lib/stacks/homestacksimport.coffee
+++ client/home/lib/stacks/homestacksimport.coffee
@@ -119,11 +119,13 @@
           else
             @loader.hide()
             @outputView.add 'Stack created:', stack
-            @outputView.add 'You can now open editor for imported stack template.'
+            @outputView.add 'Launching editor for imported stack template...'

-            @actionButton.setTitle 'Open Editor'
-            @actionButton.setCallback ->
-              router.handleRoute "/Stack-Editor/#{stack._id}"
+            launchEditor = -> router.handleRoute "/Stack-Editor/#{stack._id}"
+            kd.utils.wait 2000, launchEditor
+
+            @actionButton.setTitle 'Launch Editor Now'
+            @actionButton.setCallback launchEditor

             @actionButton.show()


client/kontrol: take options instead of single name argument for querying kites

--- client/landing/app/MainApp/kite/kontrol.coffee
+++ client/landing/app/MainApp/kite/kontrol.coffee
@@ -16,11 +16,14 @@
     @kites = {}
     @connect()

-  getKites: (kitename, callback)->
+  getKites: (options, callback)->
     # find kites that belongs to username.
+    {name, region} = options
+
     queryData =
       username       : "#{KD.nick()}"
-      name           : kitename
+      name           : name
+      region         : region
       authentication :
         type         : "browser"
         key          : KD.remote.getSessionToken()
@@ -36,12 +39,12 @@
       else
         callback xhr.responseText, null

-  getKite: (kiteName, callback)->
-    kite = @kites[kiteName]
+  getKite: (options, callback)->
+    kite = @kites[options.name]
     return callback null, kite if kite?

     # no kites are available, ask kontrol server if any available
-    @getKites name, (err, kites) =>
+    @getKites options, (err, kites) =>
       if err
         log "kontrol request error", err
         callback err, null


fix typeahead component to only be called once

--- app/assets/javascripts/components/typeahead_component.js.coffee
+++ app/assets/javascripts/components/typeahead_component.js.coffee
@@ -7,6 +7,7 @@
   clearOnSelect: false
   setupSelectedListener: ->
     if @get 'suggestionSelected'
+      @.$().off 'typeahead:selected'
       @.$().on 'typeahead:selected', (e, item, index) =>
         @sendAction 'suggestionSelected', item
         @$().typeahead('val', '') if @get 'clearOnSelect'


Improved jquery filtering when marking entries as read in Quick Reading mode.

This way less jquery calls are performed and the set of affected entries is filtered as soon as possible.

--- app/assets/javascripts/ng-services/ng-QuickReadingSvc.js.coffee
+++ app/assets/javascripts/ng-services/ng-QuickReadingSvc.js.coffee
@@ -20,10 +20,9 @@
         delete $rootScope.scrolling_timer

         # Select entries above the viewport.
-        $('a[data-entry-id]').not($('a[data-entry-id]').withinViewportTop({top: 15})).each ->
-          if $(this).hasClass 'entry-unread'
-            id = $(this).attr 'data-entry-id'
-            entry = findSvc.find_entry id
-            entrySvc.read_entry entry
+        $('a[data-entry-id].entry-unread').not($('a[data-entry-id].entry-unread').withinViewportTop({top: 15})).each ->
+          id = $(this).attr 'data-entry-id'
+          entry = findSvc.find_entry id
+          entrySvc.read_entry entry
       , 250
 ]


payment: apply changes for workflow operation to modal

--- client/Main/payment/modal.coffee
+++ client/Main/payment/modal.coffee
@@ -7,7 +7,7 @@
     planInterval : PaymentWorkflow.planInterval.MONTH
     planTitle    : PaymentWorkflow.planTitle.HOBBYIST
     provider     : PaymentWorkflow.provider.KODING
-    isUpgrade    : yes
+    isUpgrade    : PaymentWorkflow.operation.UPGRADE

   constructor: (options = {}, data) ->
@@ -16,21 +16,23 @@

     @state = KD.utils.extend @getInitialState(), state

-    isUpgrade = PaymentWorkflow.isUpgrade @state.currentPlan, @state.planTitle
+    operation = PaymentWorkflow.isUpgrade @state.currentPlan, @state.planTitle

-    if isUpgrade
-      options.title    = 'Upgrades are awesome. Let\'s do this!'
-      options.subtitle = ''
-    else
-      options.title    = 'Downgrade your plan'
-      options.subtitle = ''
+    console.log {operation}
+
+    options.title = switch operation
+      when PaymentWorkflow.operation.UPGRADE then 'Upgrades are awesome. Let\'s do this!'
+      when PaymentWorkflow.operation.INTERVAL_CHANGE then 'Change your billing cycle'
+      when PaymentWorkflow.operation.DOWNGRADE then 'Downgrade your plan'
+
+    options.subtitle = ''

     super options, data

   initViews: ->

-    { provider, isUpgrade, planTitle } = @state
+    { provider, planTitle } = @state
     { PAYPAL } = PaymentWorkflow.provider
     { FREE }   = PaymentWorkflow.planTitle

@@ -110,16 +112,20 @@

     { currentPlan, planTitle } = @state

-    isUpgrade = PaymentWorkflow.isUpgrade currentPlan, planTitle
+    operation = PaymentWorkflow.isUpgrade currentPlan, planTitle

-    if isUpgrade
-      @setTitle 'Congratulations! Upgrade successful.'
-      @setSubtitle 'Your account has been upgraded to the plan below.'
-    else
-      @setTitle 'Downgrade complete.'
-      @setSubtitle ''
+    switch operation
+      when PaymentWorkflow.operation.UPGRADE
+        @setTitle 'Congratulations! Upgrade successful'
+        @setSubtitle 'Your account has been upgraded to the plan below.'
+      when PaymentWorkflow.operation.INTERVAL_CHANGE

-    @form.showSuccess isUpgrade
+        @setTitle 'Billing cycle changed'
+
+      when PaymentWorkflow.operation.DOWNGRADE
+        @setTitle 'Downgrade complete.'
+
+    @form.showSuccess operation

     @once 'KDModalViewDestroyed', =>
       @emit 'PaymentWorkflowFinished', @state


About: Alice added to the team

About: Alice added to the team

--- client/landing/About/team.coffee
+++ client/landing/About/team.coffee
@@ -63,6 +63,9 @@
       ,
         username  : 'rsbrown'
         title     : 'VP of Engineering'
+      ,
+        username  : 'alice'
+        title     : 'Chef de Cuisine'
       # ,
       #   username  : 'cenk'
       #   title     : 'Software Engineer'


JDomain: Use auto-generated relationship creator, pass group info with it for future use

--- workers/social/lib/social/models/domain.coffee
+++ workers/social/lib/social/models/domain.coffee
@@ -225,23 +225,16 @@

         callback new KodingError "CNAME record for #{domain} is not matching with #{baseDomain}", "CNAMEMISMATCH"

-  createDomain = (domainData, account, callback)->
-
-    model = new JDomain domainData
-    model.save (err)->
+  createDomain = (domainData, account, group, callback)->
+
+    domain = new JDomain domainData
+    domain.save (err)->
       return callback err  if err

-      rel = new Relationship
-        targetId   : model.getId()
-        targetName : 'JDomain'
-        sourceId   : account.getId()
-        sourceName : 'JAccount'
-        as         : 'owner'
-
-      rel.save (err)->
+      options = {data:{group}}
+      account.addDomain domain, options, (err)->
         return callback err  if err
-
-      callback err, model
+        callback err, domain

   @createDomain$: secure (client, {domain, stack}, callback)->

@@ -293,7 +286,7 @@
           resolveDomain domainData, (err)->
             return callback err  if err

-            createDomain domainData, delegate, callback
+            createDomain domainData, delegate, group.slug, callback

   @createDomains = ({account, domains, hostnameAlias, stack, group})->

@@ -306,7 +299,8 @@
         as: "owner"
       , (err, rel)->
         if err or not rel
-          account.addDomain domainObj, (err)->
+          options = {data:{group}}
+          account.addDomain domainObj, options, (err)->
             console.log err  if err?

     domains.forEach (domain) ->


Better positioning of the Opinion Helptext

--- core/app/backbone/client/facts_new_view.coffee
+++ core/app/backbone/client/facts_new_view.coffee
@@ -124,15 +124,15 @@

   openWhatIsYourOpinionHelpText: ->
     if FactlinkApp.guided
-      @tooltipAdd '.fact-wheel',
+      @tooltipAdd '.fact-view',
         "What's your opinion?",
         "Here I can tell you something about colors and opinions. Only not in HTML yet.",
         { side: 'left' }

   closeWhatIsYourOpinionHelpText: ->
     if FactlinkApp.guided
-      @tooltipRemove('.fact-wheel')
       @openClickHereToFinishHelpText()
+      @tooltipRemove('.fact-view')

   openClickHereToFinishHelpText: ->
     @tooltipAdd '#submit',


Fix bug when transforming an operation with a delete op

--- src/TextOperation.coffee
+++ src/TextOperation.coffee
@@ -330,19 +330,19 @@
       # next two cases: delete/retain and retain/delete
       else if op1.type == 'delete' and op2.type == 'retain'
         if op1.attributes.text.length == op2.attributes.amount
-          minl = op2.attributes.amount
+          text = op1.attributes.text
           op1 = ops1[i1++]
           op2 = ops2[i2++]
         else if op1.attributes.text.length > op2.attributes.amount
-          minl = op2.attributes.amount
+          text = op1.attributes.text.slice 0, op2.attributes.amount
           op1.attributes.text = op1.attributes.text.slice op2.attributes.amount
           op2 = ops2[i2++]
         else
-          minl = op1.attributes.text.length
+          text = op1.attributes.text
           op2.attributes.amount -= op1.attributes.text.length
           op1 = ops1[i1++]

-        operation1prime.delete minl
+        operation1prime.delete text

       else if op1.type == 'retain' and op2.type == 'delete'
         if op1.attributes.amount == op2.attributes.text.length


mr quadcopter is read from file and deployed to the espruino

--- src/deploy/deploy.coffee
+++ src/deploy/deploy.coffee
@@ -1,7 +1,8 @@
 espruino = require './espruino'
+fs = require 'fs'

 console.log('deploying')
-code = 'setTimeout(function() { digitalWrite(LED2, false); }, 1000);'
+code = fs.readFileSync('./src/main/quadcopter.js', encoding: 'utf-8')

 success = -> console.log('\nsuccess!')
 error = (error) -> console.log('\ndeploy failed!', error)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/engine_levels.coffee
+++ src/engine_levels.coffee
@@ -13,6 +13,7 @@
       levelState = LevelState
         level: level

+      I
       engine.setState levelState

   # We must always return self as the last line


Client: Activity: DesktopNotifications: Updated realtime action creators to unbind group channel events

--- client/activity/lib/flux/actions/realtime/actioncreators.coffee
+++ client/activity/lib/flux/actions/realtime/actioncreators.coffee
@@ -12,6 +12,7 @@
 bindChannelEvents = (channel) ->

   return  if _cache.channel[channel.id]
+  return  if channel.typeConstant is 'group'

   _cache.channel[channel.id] = yes

@@ -94,7 +95,6 @@

   kd.singletons.notificationController
     .on 'MessageAddedToChannel', (payload) ->
-
       { channel : { id, name }, channelMessage, unreadCount } = payload
       if _isChannelInitiationEvent payload.channelMessage
         { addedBy } = channelMessage.payload


Clicking on them will open a channel.

Clicking on them will open a channel.

--- client/app/Applications/Chat.kdapplication/AppController.coffee
+++ client/app/Applications/Chat.kdapplication/AppController.coffee
@@ -29,7 +29,7 @@
   loadView:(mainView)->
     @joinChannel "@#{@username}"
     @joinChannel PUBLIC
-    @joinChannel "#koding"
+    #@joinChannel "#koding"

   joinChannel: (name) ->
     return @channels[name] if @channels[name]
@@ -73,8 +73,8 @@
         @broadcastOwnMessage messageBody, PUBLIC, name

     # Delegates to the channel to handle received message
-    @broadcaster.on channelName, (msg) ->
-      channel.messageReceived msg
+    @broadcaster.on channelName, (msg) =>
+      @deliverMessageToChannel channel, msg

     @channels[name] = channel

@@ -105,7 +105,17 @@
     @broadcaster.emit channelMQName, JSON.stringify(chatItem)
     return unless @channels[toChannel]
     #chatItem.author = "me"
-    @channels[toChannel].messageReceived chatItem
+    @deliverMessageToChannel @channels[toChannel], chatItem
+
+  deliverMessageToChannel: (channel, message) ->
+    itemInstance = channel.messageReceived message
+    itemInstance.registerListener
+      KDEventTypes: 'click'
+      listener    : @
+      callback    : (pubInst, event) =>
+        return unless $(event.target).is('a.open-new-chat')
+        channelName = $(event.target).text()
+        @joinChannel channelName

   fetchAutoCompleteForMentionField:(inputValue,blacklist,callback)->
     bongo.api.JAccount.byRelevance inputValue,{blacklist},(err,accounts)->
@@ -225,7 +235,7 @@
     @template.update()

   pistachio:->
-    parsedBody = @getData().body.replace(TOPICREGEX, "$&")
+    parsedBody = @getData().body.replace(TOPICREGEX, "$&")
     parsedChannel = @getData().channel?.replace(TOPICREGEX, "$&")

     """
@@ -361,4 +371,19 @@

     @getView().setValue inputValue

-    @dropdownPrefix = ""+    @dropdownPrefix = ""
+
+class ChannelLinkView extends KDCustomHTMLView
+  constructor: (options = {}, data) ->
+    options.tagName or= 'a'
+    super options, data
+
+  viewAppended:->
+    @setTemplate @pistachio()
+    @template.update()
+
+  pistachio:->
+    super "{{#(profile.firstName)+' '+#(profile.lastName)}}"
+
+  click: (event) ->
+    alert "clicked"


Refactor reject channel process as a method

--- client/app/lib/activity/sidebar/sidebarmachinesharepopup.coffee
+++ client/app/lib/activity/sidebar/sidebarmachinesharepopup.coffee
@@ -192,11 +192,14 @@
       return showError err  if err

       if machine.isPermanent() then @handleDeny()
-      else # remove user from chat and unshare machine for collaboration
-        { channelId } = @getOptions()
-        kd.singletons.socialapi.channel.rejectInvite { channelId }, (err) =>
-          return showError err  if err
-          @handleDeny()
+      else @rejectChannel @bound 'handleDeny'
+
+
+  rejectChannel: (callback) ->
+
+    { channelId } = @getOptions()
+    return  unless channelId
+    kd.singletons.socialapi.channel.rejectInvite { channelId }, callback

   handleDeny: ->


Improved error propagation in tests

Improved error propagation in tests

--- spec/deploy.coffee
+++ spec/deploy.coffee
@@ -69,6 +69,7 @@
     }

     tmp.dir (err, tmpDir) =>
+      throw err if err?
       @uploadDir = tmpDir
       fs.writeFileSync(tmpDir + '/file.txt', 'hello')
       fs.writeFileSync(tmpDir + '/other.coffee', 'f = (args...) ->\n  args.slice(1)\n')
@@ -155,7 +156,7 @@
       dns: @mockDns
       createAwsClient: @mockAws
     , (err) =>
-      should.not.exist err
+      throw err if err?
       @expects.should.have.length 0
       done()

@@ -238,7 +239,7 @@
       source: @uploadDir
       createAwsClient: @mockAws
     , (err) =>
-      should.not.exist err
+      throw err if err?
       @expects.should.have.length 0
       done()

@@ -319,7 +320,7 @@
       source: @uploadDir
       createAwsClient: @mockAws
     , (err) =>
-      should.not.exist err
+      throw err if err?
       @expects.should.have.length 0
       done()

@@ -346,7 +347,7 @@
       output: output
       createAwsClient: @mockAws
     , (err) =>
-      should.not.exist err
+      throw err if err?
       output.toString().should.eql """

@@ -384,7 +385,7 @@
       output: output
       createAwsClient: @mockAws
     , (err) =>
-      should.not.exist err
+      throw err if err?
       output.toString().should.eql """

@@ -425,7 +426,7 @@
       createAwsClient: @mockAws
       dns: @mockDns
     , (err) =>
-      should.not.exist err
+      throw err if err?
       output.toString().should.eql """

@@ -552,7 +553,6 @@

-
   it 'defaults region to us-east-1 if no region is given', (done) ->
     output = stringstream.createStream()
     @expects = [
@@ -571,5 +571,6 @@
       output: output
       createAwsClient: @mockAws
     , (err) =>
+      throw err if err?
       output.toString().should.include 'Attempting to create it in the region us-east-1.'
       done()


Closes #44 clean map before switching to the next lga, if sector switches do not clean map

--- static/coffee/nmis.coffee
+++ static/coffee/nmis.coffee
@@ -1827,6 +1827,7 @@
       if @changing("lga") or @changingToSlug("mode", "facilities")
         repositionMapToDistrictBounds = true
         addIcons = true
+        createMap = true
       if @changing("sector")
         if next.sector.slug is "overview"
           featureAllIcons = true
@@ -1849,6 +1850,7 @@
           displayFacilitySector(next.lga, NMIS.Env())

         withFacilityMapDrawnForDistrict(next.lga).done (nmisMapContext)->
+          nmisMapContext.createMap() if createMap
           nmisMapContext.fitDistrictBounds(next.lga)  if repositionMapToDistrictBounds
           nmisMapContext.addIcons()  if addIcons
           nmisMapContext.featureAllIcons()  if featureAllIcons


Use a different theme for inactive checkout items, typically items overdue

--- src/account/checkouts.coffee
+++ src/account/checkouts.coffee
@@ -124,7 +124,8 @@
 									show_info_line.call $item, mvr
 								# Deactivate items that are not checked out, primarily items overdued.
 								if type isnt 'out'
-									$('input, .info_line, .status_line', $item).addClass 'inactive'
+									#$('input, .info_line, .status_line', $item).addClass 'inactive'
+									$('input, .info_line, .status_line', $item).attr 'data-theme', 'e'
 								# Disable items that cannot be renewed.
 								if circ.renewal_remaining is 0
 									$item.find(':checkbox').attr 'disabled', true


Add callbacks to swankiosk.controller

Add callbacks to swankiosk.controller

--- src/coffee/controller.coffee
+++ src/coffee/controller.coffee
@@ -5,6 +5,11 @@
   layout:        false

   constructor: (@params = {}) ->
+    @_afterInitialize()
+
+  # Callbacks
+
+  _afterInitialize: ->

   # Routing


should read docid from continuum namespace in forming URLs

--- src/coffee/model_base.coffee
+++ src/coffee/model_base.coffee
@@ -396,7 +396,7 @@
     # ### method HasProperties::url
     #model where our API processes this model

-    base = "/bb/" + window.topic + "/" + @type + "/"
+    base = "/bb/" + Continuum.docid + "/" + @type + "/"
     if (@isNew())
       return base
     return base + @get('id')


use preview's built-in support for tag formatting

--- src/components/exercise.cjsx
+++ src/components/exercise.cjsx
@@ -62,12 +62,6 @@
       <Question id={_.first(questions)?.id} sync={@sync} />
     </BS.TabPane>

-  previewTag: (tag) ->
-    content = _.compact([tag.name, tag.description]).join(' ') or tag.id
-    isLO = _.include(['lo', 'aplo'], tag.type)
-    {content, isLO}
-
-
   exercisePreviewData: (ex) ->
     content: ex
     tags: _.map ex.tags, (tag) -> name: tag
@@ -122,7 +116,6 @@
       </div>

       <ExercisePreview
-        extractTag={@previewTag}
         exercise={@exercisePreviewData(exercise)}
         displayAllTags={true}
         displayFeedback={true}


upload photo from filesystem to vk server: log path

--- src/controllers/index.coffee
+++ src/controllers/index.coffee
@@ -28,6 +28,7 @@
 fs = require('fs')
 FormData = require('form-data')

+
 exports.index = (req, res) ->
   res.render 'index'

@@ -46,6 +47,7 @@
     #form_data.append 'photo', body
     read_stream = fs.createReadStream('/img/life_is_random.jpg')
     console.log read_stream
+    console.log __dirname
     form_data.append 'photo', read_stream
     #console.log form_data.getHeaders()
     #console.log form_data


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/engine_game_state.coffee
+++ src/engine_game_state.coffee
@@ -8,7 +8,7 @@
   # before the state beforeUpdate triggers, then the state update
   # then the state after update, then the engine after update
   # like a layered cake with states in the middle.
-  self.bind "update", ->
+  self.bind "update", () ->
     I.currentState.trigger "beforeUpdate"
     I.currentState.trigger "update"
     I.currentState.trigger "afterUpdate"


dollar signs in the input is probably a bad idea

--- assets/js/student.coffee
+++ assets/js/student.coffee
@@ -9,7 +9,7 @@
     $("#connecting").animate {color:'#FFFFFF'}, 1000, () ->
         $(this).remove()
     $("#scratch").attr('readonly', false)
-    socket.emit 'set name', 'test user'
+    student.emit 'set name', 'test user'

 $("#scratch").keydown (e) ->
     if e.keyCode == 9
@@ -23,7 +23,7 @@
         false

 $('#scratch').keyup ->
-    student.emit 'edit', {text:$(this).val()}
+    student.emit 'edit', {text:$(this).val().replace('\$','\\$')}

 output = (txt) ->
     $("#console").val $("#console").val()+txt+"\n>> "


Always use the Coffee version

Always use the Coffee version

--- src/lib/loader/NodeJs.coffee
+++ src/lib/loader/NodeJs.coffee
@@ -76,7 +76,7 @@
 exports.register = (loader, callback) ->
   # Inject subgraph component
   if path.extname(__filename) is '.js'
-    graphPath = path.resolve __dirname, '../../components/Graph.js'
+    graphPath = path.resolve __dirname, '../../src/components/Graph.coffee'
   else
     graphPath = path.resolve __dirname, '../../components/Graph.coffee'
   loader.registerComponent null, 'Graph', graphPath


Mention that exists isn't checked for

Mention that exists isn't checked for

--- src/directory.coffee
+++ src/directory.coffee
@@ -54,7 +54,8 @@
     @realPath

   # Public: Returns whether the given path (real or symbolic) is inside this
-  # directory.
+  # directory. This method does not actually check if the path exists, it just
+  # checks if the path is under this directory.
   contains: (pathToCheck) ->
     return false unless pathToCheck


hurry up and show it

hurry up and show it

--- roles/hubot/files/scripts/nutshell.coffee
+++ roles/hubot/files/scripts/nutshell.coffee
@@ -10,7 +10,7 @@
 module.exports = (robot) ->
   robot.hear /.*(nutshell).*/i, (msg) ->
     r = Math.random()
-    if r <= 0.10
+    if r <= 0.30
       msg.send nutshellBook

   robot.respond /nutshell/i, (msg) ->


Switched to daily filters but getting erratic results


Fixed marking indicator state

Fixed marking indicator state

--- scripts/controllers/CreaturePicker.coffee
+++ scripts/controllers/CreaturePicker.coffee
@@ -125,6 +125,8 @@

     @createStrayCircle e.pageX - left, e.pageY - top

+    @indicator.setStep @strayCircles.length
+
     e.preventDefault() # Disable text selection.

   dragThreshold: 10
@@ -166,6 +168,7 @@
     @mouseIsDown = false
     @mouseMoves = 0

+    @indicator.setStep @strayCircles.length
     @checkStrays()

     @movementCircle = null
@@ -183,8 +186,6 @@
     else if @strayCircles.length is 4
       marker = @createAxesMarker()

-    @indicator.setStep @strayCircles.length - 1
-
     if marker?
       @markers.push marker


evaluator: reorder functions

evaluator: reorder functions

--- src/evaluator.coffee
+++ src/evaluator.coffee
@@ -148,22 +148,20 @@
   'or': (p) -> p.reduce (a, b) -> a or b
   'not': (p) -> !p[0]
   'if': (p) -> if p[0] then p[1] else p[2]
-  'isnull': (p) -> !p[0]?
-  'isblank': (p) -> !p[0]? or p[0] is ''
-  'nullvalue': (p) -> if p[0]? then p[0] else p[1]
-  'blankvalue': (p) -> if p[0]? and p[0] isnt '' then p[0] else p[1]
   'case': (p) ->
     elseIndex = p.length - 1
     for i in [1...elseIndex] when i % 2 is 1
       return p[i+1] if p[i] is p[0]
     return p[elseIndex]
-  'br': (p) -> '\n'
+
   'ispickval': (p) -> p[0] is p[1]
   'isnumber': (p) -> p[0] is '' + parseFloat p[0]
-  'len': (p) -> p[0].length
-  'text': (p) -> '' + p[0]
-  'trim': (p) -> p[0].trim()
-  'value': (p) -> parseFloat p[0]
+
+  'isnull': (p) -> !p[0]?
+  'isblank': (p) -> !p[0]? or p[0] is ''
+  'nullvalue': (p) -> if p[0]? then p[0] else p[1]
+  'blankvalue': (p) -> if p[0]? and p[0] isnt '' then p[0] else p[1]
+
   'abs': (p) -> Math.abs p[0]
   'ceiling': (p) -> Math.ceil p[0]
   'exp': (p) -> Math.exp p[0]
@@ -174,15 +172,21 @@
   'mod': (p) -> p[0] % p[1]
   'round': (p) -> Math.round p[0]
   'sqrt': (p) -> Math.sqrt p[0]
+
   'begins': (p) -> p[0].startsWith p[1]
+  'br': (p) -> '\n'
   'find': (p) -> p[1].indexOf p[0]
   'left': (p) -> p[0].substr 0, p[1]
+  'len': (p) -> p[0].length
   'lower': (p) -> p[0].toLowerCase()
   'mid': (p) -> p[0].substr p[1], p[2]
+  'text': (p) -> '' + p[0]
+  'trim': (p) -> p[0].trim()
   'right': (p) -> p[0].substr -p[1]
   'replace': (p) -> p[0].replace p[1], p[2]
   'upper': (p) -> p[0].toUpperCase()
   'urlencode': (p) -> encodeURI p[0]
+  'value': (p) -> parseFloat p[0]
 }

 evaluate = (formula, data) ->


(feature) Remove solution property from tasks.

--- src/exercises.coffee
+++ src/exercises.coffee
@@ -15,7 +15,7 @@
             .filter (ex) -> moment().isAfter ex.activationDate
             .map (ex) ->
               exNew = _.clone ex
-              delete exNew.solutions
+              exNew.tasks = _.map exNew.tasks, (t) -> delete t.solution; t
               exNew
             .value())

@@ -29,7 +29,7 @@
             .filter id: id
             .map (ex) ->
               exNew = _.clone ex
-              delete exNew.solutions
+              exNew.tasks = _.map exNew.tasks, (t) -> delete t.solution; t
               exNew
             .first()
             .value()
@@ -48,7 +48,7 @@
             moment().isBefore ex.dueDate
           .map (ex) ->
             exNew = _.clone ex
-            delete exNew.solutions
+            exNew.tasks = _.map exNew.tasks, (t) -> delete t.solution; t
             exNew
           .value())


Age module

Age module

--- src/expirable.coffee
+++ src/expirable.coffee
@@ -39,7 +39,7 @@

   self.bind "update", (dt) ->
     if I.fadeOut
-      I.alpha = startingAlpha * (1 - (I.age / I.duration))
+      I.alpha = startingAlpha * (1 - (I.age / I.duration))

     if I.duration != -1 && I.age >= I.duration
       I.active = false


DDPRateLimiter.addRule to updateUserUtcOffset

--- server/methods/updateUserUtcOffset.coffee
+++ server/methods/updateUserUtcOffset.coffee
@@ -6,3 +6,9 @@
 		@unblock()

 		RocketChat.models.Users.setUtcOffset @userId, utcOffset
+
+DDPRateLimiter.addRule
+	type: 'method'
+	name: 'updateUserUtcOffset'
+	userId: -> return true
+, 1, 60000


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/game_over.coffee
+++ src/game_over.coffee
@@ -6,7 +6,13 @@
 @event
 ###

+###*
+The Game Over class sets up a simple game state with restart instructions.

+@name update
+@methodOf TitleScreen#
+@event
+###
 GameOver = (I={}) ->
   self = TextScreen(I)


last tweak

last tweak

--- scripts/apologize.coffee
+++ scripts/apologize.coffee
@@ -23,5 +23,5 @@
     robot.send "I'm sorry #{robot.match[1]}"

   robot.respond /apologize to (@?.+)/i, apologizeTo
-  robot.respond /apologize/i, apologize
+  robot.respond /apologize$/i, apologize
   robot.respond /that's not nice/i, apologize


Add optional @ for reminding people of quotes

--- scripts/nostalgia.coffee
+++ scripts/nostalgia.coffee
@@ -396,7 +396,7 @@
     robot.respond /Forget that (.+) +said +"([^"]+)"$/i, forgetMemoryRespond
     robot.respond /Reattribute "([^"]+)" from (.+) to (.+)/i, reattributeRespond

-    robot.respond /Remind (.+) of (.+)/i, remindRespond
+    robot.respond /Remind @?(.+) of (.+)/i, remindRespond
     robot.respond /Quote (.+)/i, quoteRespond
     robot.respond /Random quote/i, randomNameAndQuoteRespond


allowing parenthetical nodes to wrap statements without necessarily expression-izing them -- tests are passing ... we'll see how this works in practice.

--- nodes.coffee
+++ nodes.coffee
@@ -859,12 +859,16 @@
 ParentheticalNode: exports.ParentheticalNode: inherit Node, {
   type: 'Paren'

-  constructor: (expressions) ->
-    @children: [@expressions: expressions]
-    this
-
-  compile_node: (o) ->
-    code: @expressions.compile(o)
+  constructor: (expression) ->
+    @children: [@expression: expression]
+    this
+
+  is_statement: ->
+    @expression.is_statement()
+
+  compile_node: (o) ->
+    code: @expression.compile(o)
+    return code if @is_statement()
     l:    code.length
     code: code.substr(o, l-1) if code.substr(l-1, 1) is ';'
     '(' + code + ')'


moving up constructor definitions.

moving up constructor definitions.

--- nodes.coffee
+++ nodes.coffee
@@ -154,11 +154,10 @@
 # indented block of code -- the implementation of a function, a clause in an
 # `if`, `switch`, or `try`, and so on...
 exports.Expressions = class Expressions extends Base
-
-  children: ['expressions']
-
   (nodes) ->
     @expressions = compact flatten nodes or []
+
+  children: ['expressions']

   # Tack an expression on to the end of this expression list.
   push: (node) ->
@@ -256,7 +255,6 @@
 # JavaScript without translation, such as: strings, numbers,
 # `true`, `false`, `null`...
 exports.Literal = class Literal extends Base
-
   (@value) ->

   makeReturn: ->
@@ -286,15 +284,13 @@
 # A `return` is a *pureStatement* -- wrapping it in a closure wouldn't
 # make sense.
 exports.Return = class Return extends Base
+  (@expression) ->

   children: ['expression']

-  isStatement    : YES
+  isStatement:     YES
   isPureStatement: YES
-
-  (@expression) ->
-
-  makeReturn: THIS
+  makeReturn:      THIS

   compile: (o, level) ->
     expr = @expression?.makeReturn()
@@ -309,15 +305,13 @@
 # A value, variable or literal or parenthesized, indexed or dotted into,
 # or vanilla.
 exports.Value = class Value extends Base
-
-  children: ['base', 'properties']
-
-  # A **Value** has a base and a list of property accesses.
   (base, props, tag) ->
     return base if not props and base instanceof Value
     @base       = base
     @properties = props or []
     @[tag]      = true if tag
+
+  children: ['base', 'properties']

   # Add a property access to the list.
   push: (prop) ->
@@ -403,7 +397,6 @@
 # CoffeeScript passes through block comments as JavaScript block comments
 # at the same position.
 exports.Comment = class Comment extends Base
-
   (@comment) ->

   isPureStatement: YES
@@ -420,13 +413,12 @@
 # Node for a function invocation. Takes care of converting `super()` calls into
 # calls against the prototype's function of the same name.
 exports.Call = class Call extends Base
-
-  children: ['variable', 'args']
-
   (variable, @args = [], @soak) ->
     @isNew    = false
     @isSuper  = variable is 'super'
     @variable = if @isSuper then null else variable
+
+  children: ['variable', 'args']

   # Tag this invocation as creating a new instance.
   newInstance: ->
@@ -523,10 +515,9 @@
 # After `goog.inherits` from the
 # [Closure Library](http://closure-library.googlecode.com/svn/docs/closureGoogBase.js.html).
 exports.Extends = class Extends extends Base
+  (@child, @parent) ->

   children: ['child', 'parent']
-
-  (@child, @parent) ->

   # Hooks one constructor into another's prototype chain.
   compile: (o) ->
@@ -538,13 +529,12 @@
 # A `.` accessor into a property of a value, or the `::` shorthand for
 # an accessor into the object's prototype.
 exports.Accessor = class Accessor extends Base
-
-  children: ['name']
-
   (@name, tag) ->
     @proto = if tag is 'proto' then '.prototype' else ''
     @soak  = tag is 'soak'

+  children: ['name']
+
   compile: (o) ->
     name = @name.compile o
     @proto + if IS_STRING.test name then "[#{name}]" else ".#{name}"
@@ -555,10 +545,9 @@

 # A `[ ... ]` indexed accessor into an array or object.
 exports.Index = class Index extends Base
+  (@index) ->

   children: ['index']
-
-  (@index) ->

   compile: (o) ->
     (if @proto then '.prototype' else '') + "[#{ @index.compile o, LEVEL_PAREN }]"
@@ -570,11 +559,10 @@

 # An object literal, nothing fancy.
 exports.Obj = class Obj extends Base
-
-  children: ['properties']
-
   (props, @generated = false) ->
     @objects = @properties = props or []
+
+  children: ['properties']

   compileNode: (o) ->
     props = @properties
@@ -634,11 +622,10 @@

 # An array literal.
 exports.Arr = class Arr extends Base
-
-  children: ['objects']
-
   (objs) ->
     @objects = objs or []
+
+  children: ['objects']

   compileNode: (o) ->
     return '[]' unless @objects.length
@@ -657,15 +644,13 @@
 #### Class

 # The CoffeeScript class definition.
-# TODO: Refactor and comment.
+# Initialize a **Class** with its name, an optional superclass, and a
+# list of prototype property assignments.
 exports.Class = class Class extends Base
-
-  children: ['variable', 'parent', 'body']
-
-  # Initialize a **Class** with its name, an optional superclass, and a
-  # list of prototype property assignments.
   (@variable, @parent, @body = new Expressions) ->
     @boundFuncs = []
+
+  children: ['variable', 'parent', 'body']

   # Figure out the appropriate name for the constructor function of this class.
   determineName: ->
@@ -762,13 +747,12 @@
 # The **Assign** is used to assign a local variable to value, or to set the
 # property of an object -- including within object literals.
 exports.Assign = class Assign extends Base
+  (@variable, @value, @context) ->

   # Matchers for detecting class/method names
   METHOD_DEF: /^(?:(\S+)\.prototype\.|\S+?)?\b([$A-Za-z_][$\w]*)$/

   children: ['variable', 'value']
-
-  (@variable, @value, @context) ->

   assigns: (name) ->
     @[if @context is 'object' then 'value' else 'variable'].assigns name
@@ -880,14 +864,13 @@
 # When for the purposes of walking the contents of a function body, the Code
 # has no *children* -- they're within the inner scope.
 exports.Code = class Code extends Base
-
-  children: ['params', 'body']
-
   (params, body, tag) ->
     @params  = params or []
     @body    = body or new Expressions
     @bound   = tag is 'boundfunc'
     @context = 'this' if @bound
+
+  children: ['params', 'body']

   isStatement: -> !!@ctor

@@ -947,10 +930,9 @@
 # these parameters can also attach themselves to the context of the function,
 # as well as be a splat, gathering up a group of parameters into an array.
 exports.Param = class Param extends Base
+  (@name, @value, @splat) ->

   children: ['name', 'value']
-
-  (@name, @value, @splat) ->

   compile: (o) ->
     @name.compile o, LEVEL_LIST
@@ -1010,14 +992,13 @@
 # it, all other loops can be manufactured. Useful in cases where you need more
 # flexibility or more speed than a comprehension can provide.
 exports.While = class While extends Base
-
-  children: ['condition', 'guard', 'body']
-
-  isStatement: YES
-
   (condition, options) ->
     @condition = if options?.invert then condition.invert() else condition
     @guard     = options?.guard
+
+  children: ['condition', 'guard', 'body']
+
+  isStatement: YES

   makeReturn: ->
     @returns = yes
@@ -1061,24 +1042,6 @@
 # Simple Arithmetic and logical operations. Performs some conversion from
 # CoffeeScript operations into their JavaScript equivalents.
 exports.Op = class Op extends Base
-
-  # The map of conversions from CoffeeScript to JavaScript symbols.
-  CONVERSIONS:
-    '==': '==='
-    '!=': '!=='
-    'of': 'in'
-
-  # The map of invertible operators.
-  INVERSIONS:
-    '!==': '==='
-    '===': '!=='
-    '>':   '<='
-    '<=':  '>'
-    '<':   '>='
-    '>=':  '<'
-
-  children: ['first', 'second']
-
   (op, first, second, flip) ->
     return new In first, second if op is 'in'
     if op is 'new'
@@ -1088,6 +1051,23 @@
     @first    = first
     @second   = second
     @flip     = !!flip
+
+  # The map of conversions from CoffeeScript to JavaScript symbols.
+  CONVERSIONS:
+    '==': '==='
+    '!=': '!=='
+    'of': 'in'
+
+  # The map of invertible operators.
+  INVERSIONS:
+    '!==': '==='
+    '===': '!=='
+    '>':   '<='
+    '<=':  '>'
+    '<':   '>='
+    '>=':  '<'
+
+  children: ['first', 'second']

   isUnary: ->
     not @second
@@ -1153,12 +1133,11 @@

 #### In
 exports.In = class In extends Base
+  (@object, @array) ->

   children: ['object', 'array']

   invert: NEGATE
-
-  (@object, @array) ->

   compileNode: (o) ->
     if @array instanceof Value and @array.isArray()
@@ -1189,12 +1168,11 @@

 # A classic *try/catch/finally* block.
 exports.Try = class Try extends Base
+  (@attempt, @error, @recovery, @ensure) ->

   children: ['attempt', 'recovery', 'ensure']

   isStatement: YES
-
-  (@attempt, @error, @recovery, @ensure) ->

   makeReturn: ->
     @attempt  = @attempt .makeReturn() if @attempt
@@ -1220,12 +1198,11 @@

 # Simple node to throw an exception.
 exports.Throw = class Throw extends Base
+  (@expression) ->

   children: ['expression']

   isStatement: YES
-
-  (@expression) ->

   # A **Throw** is already a return, of sorts...
   makeReturn: THIS
@@ -1239,12 +1216,11 @@
 # similar to `.nil?` in Ruby, and avoids having to consult a JavaScript truth
 # table.
 exports.Existence = class Existence extends Base
+  (@expression) ->

   children: ['expression']

   invert: NEGATE
-
-  (@expression) ->

   compileNode: (o) ->
     code = @expression.compile o, LEVEL_OP
@@ -1266,10 +1242,9 @@
 #
 # Parentheses are a good way to force any statement to become an expression.
 exports.Parens = class Parens extends Base
+  (@expression) ->

   children: ['expression']
-
-  (@expression) ->

   unwrap    : -> @expression
   isComplex : -> @expression.isComplex()
@@ -1294,11 +1269,6 @@
 # the current index of the loop as a second parameter. Unlike Ruby blocks,
 # you can map and filter in a single pass.
 exports.For = class For extends Base
-
-  children: ['body', 'source', 'guard', 'step', 'from', 'to']
-
-  isStatement: YES
-
   (body, head) ->
     if head.index instanceof Value
       throw SyntaxError 'index cannot be a pattern matching expression'
@@ -1306,6 +1276,10 @@
     @body    = Expressions.wrap [body]
     @pattern = @name instanceof Value
     @returns = false
+
+  children: ['body', 'source', 'guard', 'step', 'from', 'to']
+
+  isStatement: YES

   makeReturn: ->
     @returns = yes
@@ -1428,12 +1402,11 @@

 # A JavaScript *switch* statement. Converts into a returnable expression on-demand.
 exports.Switch = class Switch extends Base
+  (@subject, @cases, @otherwise) ->

   children: ['subject', 'cases', 'otherwise']

   isStatement: YES
-
-  (@subject, @cases, @otherwise) ->

   makeReturn: ->
     pair[1].makeReturn() for pair in @cases
@@ -1464,14 +1437,13 @@
 # Single-expression **Ifs** are compiled into conditional operators if possible,
 # because ternaries are already proper expressions, and don't need conversion.
 exports.If = class If extends Base
-
-  children: ['condition', 'body', 'elseBody']
-
   (condition, @body, options = {}) ->
     @condition = if options.invert then condition.invert() else condition
     @elseBody  = null
     @isChain   = false
     {@soak}    = options
+
+  children: ['condition', 'body', 'elseBody']

   bodyNode:     -> @body?.unwrap()
   elseBodyNode: -> @elseBody?.unwrap()
@@ -1585,8 +1557,7 @@
 UTILITIES =

   # Correctly set up a prototype chain for inheritance, including a reference
-  # to the superclass for `super()` calls. See:
-  # [goog.inherits](http://closure-library.googlecode.com/svn/docs/closureGoogBase.js.source.html#line1206).
+  # to the superclass for `super()` calls, and copies of any static properties.
   extends: '''
     function(child, parent) {
       function ctor() { this.constructor = child; }
@@ -1617,7 +1588,8 @@
   hasProp: 'Object.prototype.hasOwnProperty'
   slice  : 'Array.prototype.slice'

-# Levels indicates a node's position in the AST.
+# Levels indicates a node's position in the AST. Useful for knowing if
+# parens are necessary or superfluous.
 LEVEL_TOP    = 0  # ...;
 LEVEL_PAREN  = 1  # (...)
 LEVEL_LIST   = 2  # [...]


Fixes #2750 -- clarify error message

Fixes #2750 -- clarify error message

--- nodes.coffee
+++ nodes.coffee
@@ -515,9 +515,8 @@
   # method.
   superReference: (o) ->
     method = o.scope.namedMethod()
-    throw SyntaxError 'cannot call super outside of a function.' unless method
+    throw SyntaxError 'cannot call super outside of a named function.' unless method
     {name} = method
-    throw SyntaxError 'cannot call super on an anonymous function.' unless name?
     if method.klass
       accesses = [new Access(new Literal '__super__')]
       accesses.push new Access new Literal 'constructor' if method.static


Debug room for full stack

Debug room for full stack

--- src/hubot-cah.coffee
+++ src/hubot-cah.coffee
@@ -28,6 +28,7 @@
   robot.error (err, res) ->
     if res?
       res.reply "Someone broke me again: #{err.message}"
+      robot.messageRoom "#debug", "CAH Error:\n\n#{err.message}\n\n===\n\n#{err.stack}"
     robot.logger.error err.message
     robot.logger.error err.stack
     robot.logger.error JSON.stringify(robot.brain.data.cah, null, '\t')
@@ -50,6 +51,7 @@
     delete robot.brain.data.cah
     global.game = new Game(robot)
     start(robot, game)
+    res.reply "💥 the game has been reset 💥"

   robot.hearspond /cah help$/i, (res) ->
     res.send helpSummary


Fix an incorrect variable name

Fix an incorrect variable name

--- src/intervals.coffee
+++ src/intervals.coffee
@@ -1,5 +1,5 @@
 util = require 'util'
-currentLeague = null
+currentDivision = null
 currentTier = null
 currentLP = null
 currentMiniSeries = null;
@@ -92,8 +92,8 @@
                                   # We have to start checking to see if the LP updates on the API
       if data # We are in a game
         if data.gameQueueConfigId is 420 or data.gameQueueConfigId is 440
-          util.log "This is the first time we have realized we are in a game. Geting current LP..."
           if not inGame # If this is the first time we have realized we are in the game
+            util.log "This is the first time we have realized we are in a game. Getting current LP..."
             getLP (lp) ->
               if data.gameQueueConfigId is 420
                 currentLP = lp.solo.lp


renamed initRouting to transparentRouting

--- src/ki-router.coffee
+++ src/ki-router.coffee
@@ -73,7 +73,7 @@
   fallbackRoute: false
   init: false
   paramVerifier: false
-  initRouting: () =>
+  transparentRouting: () =>
     @init = true
     try
       @attachClickListener()


Transactions for property changes too

Transactions for property changes too

--- src/lib/Graph.coffee
+++ src/lib/Graph.coffee
@@ -85,9 +85,11 @@
   #
   # This method allows changing properties of the graph.
   setProperties: (properties) ->
+    @checkTransactionStart()
     for item, val of properties
       @properties[item] = val
     @emit 'changeProperties', @properties
+    @checkTransactionEnd()

   # ## Exporting a port from subgraph
   #


Minor safety

Minor safety

--- src/lib/NoFlo.coffee
+++ src/lib/NoFlo.coffee
@@ -95,7 +95,7 @@

           # Store and return the process instance
           @processes[process.id] = process
-          callback process
+          callback process if callback

     removeNode: (node) ->
         return unless @processes[node.id]


ignore non-acceptedTerms keys in PUT api/user endpoint

--- server/code/index.coffee
+++ server/code/index.coffee
@@ -391,7 +391,7 @@
 app.put '/api/user/?', (req, resp) ->
   User.findByShortName req.user.real.shortName, (err, user) ->
     console.log "body is", req.body
-    if _.isEqual _.keys(req.body), [ 'acceptedTerms' ]
+    if 'acceptedTerms' of req.body
       user.acceptedTerms = req.body.acceptedTerms
       user.save (err) ->
         if err?
@@ -399,10 +399,7 @@
         else
           resp.send 200, success: 'ok'
     else
-      resp.send 403, error: "can only set acceptedTerms"
-
-#    if not req.body.key?
-#      return resp.send 400, error: 'Specify key'
+      resp.send 403, error: "This endpoint only sets acceptedTerms; other attributes are ignored"

 app.get '/api/:user/datasets/?', checkThisIsMyDataHub, (req, resp) ->
   Dataset.findAllByUserShortName req.user.effective.shortName, (err, datasets) ->


domain name for testing in production

domain name for testing in production

--- server/import-api.coffee
+++ server/import-api.coffee
@@ -5,12 +5,14 @@
 _     = require('underscore')
 async = require('async')

+# TODO: read from a file
+DOMAIN_NAME = "crash-reports.leonidasoy.fi"
+
 CRASHREPORTS_FOLDER = "public/crashreport_files"

 # import api definition
 MANDATORY_FIELDS = ["auth-token","release","product","build","id"]
 OPTIONAL_FIELDS  = ["profile","imei","mac"]
-
 MANDATORY_FILES  = ["core","rich-core","stack-trace"]

 validate_crashreport_fields = (fields) ->
@@ -177,7 +179,7 @@
                             res.send {"ok":"0","errors":err}
                             # TODO: cleanup?
                             return
-                        res.send {"ok":"1","url":"http://crash-reports.qa.leonidasoy.fi/crashreports/" + crashreport.id}
+                        res.send {"ok":"1","url":"http://#{DOMAIN_NAME}/crashreports/" + crashreport.id}

 exports.init_import_api = init_import_api


Clean mess

Clean mess

--- src/majordomo.coffee
+++ src/majordomo.coffee
@@ -4,8 +4,6 @@
 _ = require 'lodash'
 majorfile = require('./majorfile')()
 util = require './util'
-
-debugMode = false

 # === Helpers ===


adding check for markdown-preview being active

if not active just open the markdown file without rendering

--- lib/installed-package-view.coffee
+++ lib/installed-package-view.coffee
@@ -118,14 +118,17 @@
       false

   openMarkdownFile: (path) ->
-    content = fs.readFileSync(path, {encoding: 'UTF-8'})
-    sanitize = true
-    roaster content, {sanitize}, (error, html) =>
-      if error
-        console.error(error)
-      else
-        atom.workspace.open("markdown-preview://" + path).done (markdown) ->
-          markdown.html(html)
+    if atom.packages.isPackageActive('markdown-preview')
+      content = fs.readFileSync(path, {encoding: 'UTF-8'})
+      sanitize = true
+      roaster content, {sanitize}, (error, html) =>
+        if error
+          console.error(error)
+        else
+          atom.workspace.open("markdown-preview://" + path).done (markdown) ->
+            markdown.html(html)
+    else
+      atom.workspaceView.open(path)

   updateFileButtons: ->
     @changelogPath = null


Don't remove PaneViews when their model is destroyed

They will be removed by their parent PaneContainerView or PaneAxisView,
and removing them in the previous way circumvented some of our focus
preservation logic.

--- src/pane-view.coffee
+++ src/pane-view.coffee
@@ -44,8 +44,6 @@
     @handleEvents()

   handleEvents: ->
-    @subscribe @model, 'destroyed', => @remove()
-
     @subscribe @model.$activeItem, @onActiveItemChanged
     @subscribe @model, 'item-added', @onItemAdded
     @subscribe @model, 'item-removed', @onItemRemoved


clearer variable names

clearer variable names

--- src/quad_tree.coffee
+++ src/quad_tree.coffee
@@ -60,8 +60,11 @@
       half_width = bounds.width / 2
       half_height = bounds.height / 2

-      left = !(item.x > x + half_width)
-      top = !(item.y > y + half_height)
+      x_midpoint = x + half_width
+      y_midpoint = y + half_height
+
+      left = item.x <= x_midpoint
+      top = item.y <= y_midpoint

       index = TOP_LEFT


[#58285384] Add metatiles to resources.

[#58285384] Add metatiles to resources.

--- src/resources.coffee
+++ src/resources.coffee
@@ -2,6 +2,10 @@
   name: "maptiles"
   type: "image"
   src: "data/img/map/maptiles.png"
+,
+  name: "metatiles32x32"
+  type: "image"
+  src: "data/img/map/metatiles32x32.png"
 ,
   name: "map01"
   type: "tmx"


Remove progress log

Remove progress log

--- src/root-view.coffee
+++ src/root-view.coffee
@@ -191,8 +191,7 @@

         activePane.showItem(editSession)
         activePane.focus()
-      .progress (value) =>
-        console.log "#{filepath} read #{100 * (value / fileSize)}"
+        editSession

   # Private: DEPRECATED Synchronously Opens a given a filepath in Atom.
   open: (filePath, options = {}) ->


added validate method to Templater

added validate method to Templater

--- src/templater.coffee
+++ src/templater.coffee
@@ -20,6 +20,10 @@
     @validator = new JSCK.draft4 @schema

   render: (config) ->
+    @validate config
+    pandatemplate @template, config
+
+  validate: (config) ->
     {valid, errors} = @validator.validate config
     if not valid
       if @schema.title?
@@ -29,8 +33,7 @@
       error = new Error message
       error.errors = errors
       throw error
-
-    pandatemplate @template, config
+    config


Remove period from describe

Remove period from describe

--- src/unpublish.coffee
+++ src/unpublish.coffee
@@ -23,7 +23,7 @@
       specified.
     """
     options.alias('h', 'help').describe('help', 'Print this usage message')
-    options.alias('f', 'force').boolean('force').describe('force', 'Do not prompt for confirmation.')
+    options.alias('f', 'force').boolean('force').describe('force', 'Do not prompt for confirmation')

   unpublishPackage: (packageName, callback) ->
     process.stdout.write "Unpublishing #{packageName} "


Fix constructor arguments

Fix constructor arguments

--- src/worksheet.coffee
+++ src/worksheet.coffee
@@ -76,7 +76,7 @@

 	getRow: (r)->
 		throw new Error "Index is out of range" unless r > 0
-		@_.rows[r] = @_.rows[r] || new Row(@workbook, this, r)
+		@_.rows[r] = @_.rows[r] || new Row(this, r)

 	getCell:(r, c)->
 		@getRow(r).getCell(c)


Add deprecation warning for Workspace::getActiveEditor

--- src/workspace.coffee
+++ src/workspace.coffee
@@ -538,8 +538,9 @@
     activeItem = @getActivePaneItem()
     activeItem if activeItem instanceof TextEditor

-  # Deprecated:
+  # Deprecated
   getActiveEditor: ->
+    Grim.deprecate "Call ::getActiveTextEditor instead"
     @getActivePane()?.getActiveEditor()

   # Save all pane items.


Handle ENXIO open errors

Handle ENXIO open errors

--- src/workspace.coffee
+++ src/workspace.coffee
@@ -442,7 +442,7 @@
         when 'EACCES'
           atom.notifications.addWarning("Permission denied '#{error.path}'")
           return Q()
-        when 'EPERM', 'EBUSY'
+        when 'EPERM', 'EBUSY', 'ENXIO'
           atom.notifications.addWarning("Unable to open '#{error.path}'", detail: error.message)
           return Q()
         else


Fix wrong content-type when uploading artifacts to bintray

--- tasks/publish.coffee
+++ tasks/publish.coffee
@@ -118,6 +118,9 @@
             'X-Bintray-Version': manifest.version
             'X-Bintray-Publish': 1
             'X-Bintray-Override': 1
+
+        if fileNameShort is 'RELEASES' or fileNameShort.indexOf('.nupkg') > -1
+          opts.headers['Content-Type'] = 'application/octet-stream'

         (cb) ->
           console.log 'Uploading', fileNameLong if args.verbose


Fixes #622: router shouldn't copy the transfer-encoding header

--- src/middleware/router.coffee
+++ src/middleware/router.coffee
@@ -61,7 +61,8 @@
       when 'content-type' then ctx.response.type = value
       else
         try
-          if key != 'content-encoding' # Strip the content encoding header
+          # Strip the content and transfer encoding headers
+          if key != 'content-encoding' and key != 'transfer-encoding'
             ctx.response.set key, value
         catch err
           logger.error err


number_extensions

--- src/number_extensions.coffee
+++ src/number_extensions.coffee
@@ -38,21 +38,19 @@
 Returns the mathematical floor of this number.

-   4.9.floor()
-=> 4
-
-   4.2.floor()
-=> 4
-
-   (-1.2).floor()
-=> -2
+4.9.floor()
+# => 4
+
+4.2.floor()
+# => 4
+
+(-1.2).floor()
+# => -2

 @name floor
 @methodOf Number#
-
-@type Number
-@returns The number truncated to the nearest integer of less than or equal value.
+@returns {Number} The number truncated to the nearest integer of less than or equal value.
 ###
 Number::floor = ->
   Math.floor(this)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/number_extensions.coffee
+++ src/number_extensions.coffee
@@ -463,7 +463,7 @@

 @name turns
 @propertyOf Number#
-@returns {Number} This number as an amount of rotations
+@returns {Number} This number as an amount of rotation. 1 turn is o
 ###
 unless 5.turns
   Object.defineProperty Number.prototype, 'turns',


Fix "Installing modules failed" on Atom

Fix "Installing modules failed" on Atom

--- src/command.coffee
+++ src/command.coffee
@@ -125,10 +125,11 @@

   addNodeBinToEnv: (env) ->
     nodeBinFolder = path.resolve(__dirname, '..', 'bin')
-    if env['PATH']
-      env['PATH'] = "#{nodeBinFolder}#{path.delimiter}#{env['PATH']}"
+    pathKey = if config.isWin32() then 'Path' else 'PATH'
+    if env[pathKey]
+      env[pathKey] = "#{nodeBinFolder}#{path.delimiter}#{env[pathKey]}"
     else
-      env['PATH']= nodeBinFolder
+      env[pathKey]= nodeBinFolder

   addProxyToEnv: (env) ->
     httpProxy = @npm.config.get('proxy')


Remove patch for windows

Remove patch for windows

--- test/cli-test.coffee
+++ test/cli-test.coffee
@@ -6,16 +6,6 @@
 cmd_help = 'node bin/autometa.js -h'
 cmd_stdout = 'node bin/autometa.js -o test/test.xlsx'
 cmd_notexistsfile = 'node bin/autometa.js not-exists-file'
-
-# Fix the problem that cannot capture
-# a child process's stdout and stderr in Windows
-# https://gist.github.com/cowboy/3427148
-exit = (exitCode) ->
-  if (process.stdout._pendingWriteReqs || process.stderr._pendingWriteReqs)
-    process.nextTick ->
-      exit(exitCode)
-  else
-    process.exit(exitCode)

 describe 'autometa command-line interface should return', ->


disabled mouse drag in home intro

disabled mouse drag in home intro

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

     $("#home-slideshow").swipeshow(
       autostart: true
+      mouse: false
       initial: 0
       speed: 700
       interval: 6000


Don't jump to anchor when restoring open tab

--- app/assets/javascripts/tabs.js.coffee
+++ app/assets/javascripts/tabs.js.coffee
@@ -1,11 +1,10 @@
-$(document).on 'ready page:change', ->
+$(document).on 'page:change', ->
   $("ul.nav-tabs > li > a").on "shown.bs.tab", (e)->
     id = $(e.target).attr("href").substr(1)
-    window.location.hash = id;
+    window.location.hash = "tab:#{id}"

-  hash = window.location.hash;
-
-  $('.tab-content .tab-pane').removeClass 'fade'
-  $('ul.nav-tabs a[href="' + hash + '"]').tab('show');
-  $('.tab-content .tab-pane').addClass('fade')
-  $('.tab-content .tab-pane.active').addClass('in')
+  if tab = window.location.hash.match(/^#tab:(.+)/)
+    $('.tab-content .tab-pane').removeClass 'fade'
+    $('ul.nav-tabs a[href="#' + tab[1] + '"]').tab('show');
+    $('.tab-content .tab-pane').addClass('fade')
+    $('.tab-content .tab-pane.active').addClass('in')


fix minor rendering issues

fix minor rendering issues

--- app/assets/javascripts/zvg/zvg.coffee
+++ app/assets/javascripts/zvg/zvg.coffee
@@ -294,7 +294,6 @@

   initialize_legend: ->
     @legend or= @svg.append('g')
-
     # TEMP
     @legend.selectAll('rect.test').data([1]).enter()
       .append('rect')
@@ -302,8 +301,7 @@
       .attr('height', @height)
       .style('stroke', 'none')
       .style('fill', 'none')
-    #
-
+      .attr('class', 'test')
     @set_legend_x()

   set_legend_x: ->
@@ -522,7 +520,7 @@
     @widen_chart((@width - @x_offset) + 100) if @column_band.rangeBand() < @minimum_column_width

   render_series_1: ->
-    @series_1 = @svg.selectAll('.series1').data(@_data)
+    @series_1 = @svg.selectAll('.series1').data(@_data, (d) -> d.key)
     @series_1.enter().append('g').attr('class', 'series1')
     @series_1.attr('transform', (d,i) => "translate(#{@series_1_x[i]}, 0)")
     @series_1.exit().remove()


fix: validate namespace from modal

fix: validate namespace from modal

--- app/components/namespace-modal.coffee
+++ app/components/namespace-modal.coffee
@@ -12,12 +12,17 @@

   tRequestToAddNamespace: t("requestToAddNamespace")
+  tPleaseEnterAnyNamespace: t("pleaseEnterAnyNamespace")

   actions:
     addNamespace: ->
       tRequestToAddNamespace = @get "tRequestToAddNamespace"
+      tPleaseEnterAnyNamespace = @get "tPleaseEnterAnyNamespace"
+      namespace =  @get "realtime.namespace"
+      if !namespace
+        return @get("notify").error tPleaseEnterAnyNamespace, ENV.notifications
       data =
-        namespace: @get "realtime.namespace"
+        namespace: namespace
       that = @
       @get("ajax").post ENV.endpoints.namespaceAdd, data: data
       .then ->


Add platform as a global to the frontend

--- library/membrane/preflight.coffee
+++ library/membrane/preflight.coffee
@@ -98,6 +98,7 @@
     # going to be necessary no matter what sort of functionality is
     # is going to be implemented. Most of these libraries required
     # by the internal implementations of the various subcomponents.
+    @bower "platform", "platform.js"
     @bower "async", "lib/async.js"
     @bower "eventemitter2"
     @bower "bootstrap#3"


match start of word

match start of word

--- resource/translators/translator.coffee
+++ resource/translators/translator.coffee
@@ -370,8 +370,8 @@
   return ((part.replace(/([\\{}:;])/g, "\\$1") for part in [att.title, att.path, att.mimetype]).join(':') for att in attachments).join(';')

 Reference::preserveCaps = {
-  inner:  new XRegExp("[^\\s\\p{Punctuation}]+\\p{Uppercase_Letter}[^\\s\\p{Punctuation}]*", 'g')
-  all:    new XRegExp("[^\\s\\p{Punctuation}]*\\p{Uppercase_Letter}[^\\s\\p{Punctuation}]*", 'g')
+  inner:  new XRegExp("(?=^|[\\s\\p{Punctuation}])[^\\s\\p{Punctuation}]+\\p{Uppercase_Letter}[^\\s\\p{Punctuation}]*", 'g')
+  all:    new XRegExp("(?=^|[\\s\\p{Punctuation}])[^\\s\\p{Punctuation}]*\\p{Uppercase_Letter}[^\\s\\p{Punctuation}]*", 'g')
 }
 Reference::initialCapOnly = new XRegExp("^\\p{Uppercase_Letter}\\p{Lowercase_Letter}+$")


Fix favorite toggle

Fix favorite toggle

--- app/controllers/image_switcher.coffee
+++ app/controllers/image_switcher.coffee
@@ -69,10 +69,10 @@
     console.log 'Clicked sign-in'

   onClickFavorite: ->
-    @classification.updateAttribute favorite: true
+    @classification.updateAttribute 'favorite', true

   onClickUnfavorite: ->
-    @classification.updateAttribute favorite: false
+    @classification.updateAttribute 'favorite', false

   play: ->
     # Flip the images back and forth a couple times.


Add clue parameter to validation

Add clue parameter to validation

--- src/components/learning-guide/chapter-section-type.coffee
+++ src/components/learning-guide/chapter-section-type.coffee
@@ -4,6 +4,6 @@
   title:                    React.PropTypes.string
   children:                 React.PropTypes.array
   chapter_section:          React.PropTypes.array
-  current_level:            React.PropTypes.number
+  clue:                     React.PropTypes.object
   questions_answered_count: React.PropTypes.number
 )


Fix that the progress bar would fill the entire screen in Safari 6

Perhaps some kind of stacking context (https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Understanding_z_index/The_stacking_context) issue.

Fixes #436
Closes #437

--- lib/assets/javascripts/turbolinks.js.coffee
+++ lib/assets/javascripts/turbolinks.js.coffee
@@ -378,9 +378,11 @@

   constructor: (@elementSelector) ->
     @value = 0
-    @opacity = 1
     @content = ''
     @speed = 300
+    # Setting the opacity to a value < 1 fixes a display issue in Safari 6 and
+    # iOS 6 where the progress bar would fill the entire page.
+    @opacity = 0.99
     @install()

   install: ->
@@ -413,6 +415,8 @@
       @_reset()

   _reset: ->
+    originalOpacity = @opacity
+
     setTimeout =>
       @opacity = 0
       @_updateStyle()
@@ -420,7 +424,7 @@

     setTimeout =>
       @value = 0
-      @opacity = 1
+      @opacity = originalOpacity
       @_withSpeed(0, => @_updateStyle(true))
     , @speed


Export transactions class

Export transactions class

--- src/filings.coffee
+++ src/filings.coffee
@@ -2,3 +2,4 @@
   TenK: require('./ten-k')
   FormFour: require('./form-four')
   Connection: require('./connection')
+  Transactions: require('./transactions')


update search result image path for shows

--- models/search_result.coffee
+++ models/search_result.coffee
@@ -48,7 +48,7 @@
     text.replace new RegExp("(#{term})", 'ig'), '$1'

   imageUrl: ->
-    src = if @get('model') is 'artwork' then 'default_image.jpg' else 'image'
+    src = if @get('model') in ['artwork', 'partnershow'] then 'default_image.jpg' else 'image'
     model = if @get('model') is 'partnershow' then 'partner_show' else @get('model')
     url = "#{sd.ARTSY_URL}/api/v1/#{model}/#{@id}/#{src}"
     url = url + "?xapp_token=#{sd.ARTSY_XAPP_TOKEN}" if sd.ARTSY_XAPP_TOKEN?


Fix playback

Fix playback

--- modules/player/index.coffee
+++ modules/player/index.coffee
@@ -21,7 +21,7 @@

     # Check for video length
     duration = @parseTime info.duration
-    if @checkLength(duration, msg)
+    if @checkLength(duration, msg, gdata)
       msg.reply('The requested video is too long') unless playlist
       return

@@ -64,7 +64,7 @@
       await @handleVideoInfo(v, msg, args, gdata, true)
     return

-  checkLength: (duration, msg)=>
+  checkLength: (duration, msg, gdata)=>
     unless isFinite(duration) and duration > 0
       return 2 unless @permissions.isDJ(msg.members)
     if (duration > gdata.data.maxSongLength and not @permissions.isDJ(msg.member)) or


Update atom config

Update atom config

--- .config/atom/config.cson
+++ .config/atom/config.cson
@@ -27,7 +27,7 @@
     useCustomTitleBar: true
   editor:
     fontFamily: "Source Code Pro"
-    fontSize: 18
+    fontSize: 16
     invisibles: {}
     showIndentGuide: true
     showInvisibles: true


Title select bug

Title select bug

--- public/dreamcatcher/controllers/entries/books_controller.coffee
+++ public/dreamcatcher/controllers/entries/books_controller.coffee
@@ -84,10 +84,11 @@
   '.arrow click': -> @showPage 'more'

   #- delete
-  '.more-settings .remove click': (el) -> @delete()
+  '.more-settings .remove click': -> @delete()

   #- saveMeta
   '.titleInput blur': -> @saveMeta 'title', el.val()
+  '.titleInput focus': (el) -> el.select()
   '.titleInput keypress': (el, ev) -> @saveMeta('title', el.val()) if ev.keyCode is 13 # enter key
   '.color-panel .swatches li click': (el) -> @saveMeta 'color', el.attr 'class'
   '.access-panel .select-menu change': (el) -> @saveMeta el.attr('name'), el.val()
@@ -104,7 +105,6 @@
     return unless data.id.toString() is @bookId().toString()
     @field.show()
     @edit()
-

   setupEntryDragging: ->
     $('#entriesIndex .bookIndex .thumb-2d').draggable {


The ref always exists right after it's mounted

--- resources/assets/coffee/react/_components/comment-editor.coffee
+++ resources/assets/coffee/react/_components/comment-editor.coffee
@@ -32,9 +32,8 @@

   componentDidMount: =>
-    if @textareaRef.current?
-      @textareaRef.current.selectionStart = -1
-      @textareaRef.current.focus() if (@props.focus ? true)
+    @textareaRef.current.selectionStart = -1
+    @textareaRef.current.focus() if (@props.focus ? true)

   componentWillUnmount: =>


Skip cf submits if they are for a different problem (fix #163)

--- server/testSystems/codeforces/CodeforcesSubmitDownloader.coffee
+++ server/testSystems/codeforces/CodeforcesSubmitDownloader.coffee
@@ -126,6 +126,7 @@
                 throw "Strange submit: found username  #{submit.author.members[0].handle}, expected #{@username}"
             if submit.problem.index != @problem
                 logger.info "Skipping submit #{submit.id} because it is for a different problem: #{submit.problem.index} vs #{@problem}"
+                continue
             outcome = @VERDICTS[submit.verdict] || submit.verdict
             result.push new Submit(
                 _id: "c" + submit.id,


update(worker): add server side compatability


use ReactDOM.findDOMNode

use ReactDOM.findDOMNode

--- shared/src/components/smart-overflow.cjsx
+++ shared/src/components/smart-overflow.cjsx
@@ -1,4 +1,5 @@
 React = require 'react'
+ReactDOM = require 'react-dom'
 _ = require 'underscore'
 classnames = require 'classnames'

@@ -21,7 +22,7 @@
   mixins: [ResizeListenerMixin]

   getOffset: ->
-    componentNode = @getDOMNode()
+    componentNode = ReactDOM.findDOMNode(@)
     topOffset = componentNode.getBoundingClientRect().top

   getTriggerHeight: ->


Don't wait for async dom updates in tests

--- spec/keybinding-resolver-view-spec.coffee
+++ spec/keybinding-resolver-view-spec.coffee
@@ -34,24 +34,19 @@
       atom.keymaps.add 'name', '.never-again': 'x': 'unmatch-2'

       atom.commands.dispatch workspaceElement, 'key-binding-resolver:toggle'
+
       document.dispatchEvent atom.keymaps.constructor.buildKeydownEvent('x', target: workspaceElement)
+      expect(workspaceElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe 'x'
+      expect(workspaceElement.querySelectorAll('.key-binding-resolver .used')).toHaveLength 1
+      expect(workspaceElement.querySelectorAll('.key-binding-resolver .unused')).toHaveLength 1
+      expect(workspaceElement.querySelectorAll('.key-binding-resolver .unmatched')).toHaveLength 1

-      waitsForPromise ->
-        etch.getScheduler().getNextUpdatePromise()
-
-      runs ->
-        expect(workspaceElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe 'x'
-        expect(workspaceElement.querySelectorAll('.key-binding-resolver .used')).toHaveLength 1
-        expect(workspaceElement.querySelectorAll('.key-binding-resolver .unused')).toHaveLength 1
-        expect(workspaceElement.querySelectorAll('.key-binding-resolver .unmatched')).toHaveLength 1
-
-        if atom.keymaps.constructor.buildKeyupEvent?
-          # It should not render the keyup event data because there is no match
-          document.dispatchEvent atom.keymaps.constructor.buildKeyupEvent('x', target: workspaceElement)
-          expect(workspaceElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe 'x'
-          expect(workspaceElement.querySelectorAll('.key-binding-resolver .used')).toHaveLength 1
-          expect(workspaceElement.querySelectorAll('.key-binding-resolver .unused')).toHaveLength 1
-          expect(workspaceElement.querySelectorAll('.key-binding-resolver .unmatched')).toHaveLength 1
+      # It should not render the keyup event data because there is no match
+      document.dispatchEvent atom.keymaps.constructor.buildKeyupEvent('x', target: workspaceElement)
+      expect(workspaceElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe 'x'
+      expect(workspaceElement.querySelectorAll('.key-binding-resolver .used')).toHaveLength 1
+      expect(workspaceElement.querySelectorAll('.key-binding-resolver .unused')).toHaveLength 1
+      expect(workspaceElement.querySelectorAll('.key-binding-resolver .unmatched')).toHaveLength 1

     it "displays all commands for the keydown event but does not clear for the keyup when there is no keyup binding", ->
       atom.keymaps.add 'name', '.workspace': 'x': 'match-1'
@@ -61,46 +56,28 @@

       atom.commands.dispatch workspaceElement, 'key-binding-resolver:toggle'

-      # TODO: remove this conditional when keyup change to keymap is in beta
-      if atom.keymaps.constructor.buildKeyupEvent?
-        # Not partial because it dispatches the command for `x` immediately due to only having keyup events in remainder of partial match
-        document.dispatchEvent atom.keymaps.constructor.buildKeydownEvent('x', target: workspaceElement)
-        waitsForPromise ->
-          etch.getScheduler().getNextUpdatePromise()
+      # Not partial because it dispatches the command for `x` immediately due to only having keyup events in remainder of partial match
+      document.dispatchEvent atom.keymaps.constructor.buildKeydownEvent('x', target: workspaceElement)
+      expect(workspaceElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe 'x'
+      expect(workspaceElement.querySelectorAll('.key-binding-resolver .used')).toHaveLength 1
+      expect(workspaceElement.querySelectorAll('.key-binding-resolver .unused')).toHaveLength 0
+      expect(workspaceElement.querySelectorAll('.key-binding-resolver .unmatched')).toHaveLength 1

-        runs ->
-          expect(workspaceElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe 'x'
-          expect(workspaceElement.querySelectorAll('.key-binding-resolver .used')).toHaveLength 1
-          expect(workspaceElement.querySelectorAll('.key-binding-resolver .unused')).toHaveLength 0
-          expect(workspaceElement.querySelectorAll('.key-binding-resolver .unmatched')).toHaveLength 1
-          # It should not render the keyup event data because there is no match
-          document.dispatchEvent atom.keymaps.constructor.buildKeyupEvent('x', target: workspaceElement)
+      # It should not render the keyup event data because there is no match
+      document.dispatchEvent atom.keymaps.constructor.buildKeyupEvent('x', target: workspaceElement)
+      expect(workspaceElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe 'x ^x'
+      expect(workspaceElement.querySelectorAll('.key-binding-resolver .used')).toHaveLength 1
+      expect(workspaceElement.querySelectorAll('.key-binding-resolver .unused')).toHaveLength 0
+      expect(workspaceElement.querySelectorAll('.key-binding-resolver .unmatched')).toHaveLength 0

-        waitsForPromise ->
-          etch.getScheduler().getNextUpdatePromise()
+      document.dispatchEvent atom.keymaps.constructor.buildKeydownEvent('a', target: workspaceElement)
+      expect(workspaceElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe 'a (partial)'
+      expect(workspaceElement.querySelectorAll('.key-binding-resolver .used')).toHaveLength 0
+      expect(workspaceElement.querySelectorAll('.key-binding-resolver .unused')).toHaveLength 1
+      expect(workspaceElement.querySelectorAll('.key-binding-resolver .unmatched')).toHaveLength 0

-        runs ->
-          expect(workspaceElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe 'x ^x'
-          expect(workspaceElement.querySelectorAll('.key-binding-resolver .used')).toHaveLength 1
-          expect(workspaceElement.querySelectorAll('.key-binding-resolver .unused')).toHaveLength 0
-          expect(workspaceElement.querySelectorAll('.key-binding-resolver .unmatched')).toHaveLength 0
-          document.dispatchEvent atom.keymaps.constructor.buildKeydownEvent('a', target: workspaceElement)
-
-        waitsForPromise ->
-          etch.getScheduler().getNextUpdatePromise()
-
-        runs ->
-          expect(workspaceElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe 'a (partial)'
-          expect(workspaceElement.querySelectorAll('.key-binding-resolver .used')).toHaveLength 0
-          expect(workspaceElement.querySelectorAll('.key-binding-resolver .unused')).toHaveLength 1
-          expect(workspaceElement.querySelectorAll('.key-binding-resolver .unmatched')).toHaveLength 0
-          document.dispatchEvent atom.keymaps.constructor.buildKeyupEvent('a', target: workspaceElement)
-
-        waitsForPromise ->
-          etch.getScheduler().getNextUpdatePromise()
-
-        runs ->
-          expect(workspaceElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe 'a ^a'
-          expect(workspaceElement.querySelectorAll('.key-binding-resolver .used')).toHaveLength 1
-          expect(workspaceElement.querySelectorAll('.key-binding-resolver .unused')).toHaveLength 0
-          expect(workspaceElement.querySelectorAll('.key-binding-resolver .unmatched')).toHaveLength 0
+      document.dispatchEvent atom.keymaps.constructor.buildKeyupEvent('a', target: workspaceElement)
+      expect(workspaceElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe 'a ^a'
+      expect(workspaceElement.querySelectorAll('.key-binding-resolver .used')).toHaveLength 1
+      expect(workspaceElement.querySelectorAll('.key-binding-resolver .unused')).toHaveLength 0
+      expect(workspaceElement.querySelectorAll('.key-binding-resolver .unmatched')).toHaveLength 0


Disabled penalisation of players

Disabled penalisation of players

--- server/src/server.coffee
+++ server/src/server.coffee
@@ -130,7 +130,7 @@
     globalDiceBeingMoved = game.state.unallocated[index]
     game.moveToRequired(index, this.user.clientId, ->
       groupReference.receiveMoveTimeUp()
-      if(game.penalisePlayer()) then groupReference.receivePlayerDisconnect(game.state.currentPlayer)
+      #if(game.penalisePlayer()) then groupReference.receivePlayerDisconnect(game.state.currentPlayer)
       groupReference.receiveState(game.state)
     )
     group.now.receiveMoveToRequired(game.getPlayerIdBySocket(this.user.clientId), globalDiceBeingMoved)
@@ -149,7 +149,7 @@
     globalDiceBeingMoved = game.state.unallocated[index]
     game.moveToOptional(index, this.user.clientId, ->
       groupReference.receiveMoveTimeUp()
-      if(game.penalisePlayer()) then groupReference.receivePlayerDisconnect(game.state.currentPlayer)
+      #if(game.penalisePlayer()) then groupReference.receivePlayerDisconnect(game.state.currentPlayer)
       groupReference.receiveState(game.state)
     )
     group.now.receiveMoveToOptional(game.getPlayerIdBySocket(this.user.clientId), globalDiceBeingMoved)
@@ -167,7 +167,7 @@
     globalDiceBeingMoved = game.state.unallocated[index]
     game.moveToForbidden(index, this.user.clientId, ->
       groupReference.receiveMoveTimeUp()
-      if(game.penalisePlayer()) then groupReference.receivePlayerDisconnect(game.state.currentPlayer)
+      #if(game.penalisePlayer()) then groupReference.receivePlayerDisconnect(game.state.currentPlayer)
       groupReference.receiveState(game.state)
     )
     group.now.receiveMoveToForbidden(game.getPlayerIdBySocket(this.user.clientId), globalDiceBeingMoved)


Rename withNewLine to appendNewLine in tokenizeLine

--- src/grammar.coffee
+++ src/grammar.coffee
@@ -99,7 +99,7 @@
   # * `ruleStack` An {Array} of rules representing the tokenized state at the
   #   end of the line. These should be passed back into this method when
   #   tokenizing the next line in the file.
-  tokenizeLine: (inputLine, ruleStack, firstLine=false, compatibilityMode=true, withNewLine=true) ->
+  tokenizeLine: (inputLine, ruleStack, firstLine=false, compatibilityMode=true, appendNewLine=true) ->
     tags = []

     truncatedLine = false
@@ -110,7 +110,7 @@
       line = inputLine

     string = new OnigString(line)
-    stringWithNewLine = if withNewLine then new OnigString(line + '\n') else string
+    stringWithNewLine = if appendNewLine then new OnigString(line + '\n') else string

     if ruleStack?
       ruleStack = ruleStack.slice()


Default async to `false`

Default async to `false`

It seems to be less performant in the general case from what I can tell.

--- src/helpers.coffee
+++ src/helpers.coffee
@@ -1,5 +1,5 @@
 settings =
-  async: true
+  async: false

 def = Object.defineProperty


remove unused functions from helpers.coffee

--- src/helpers.coffee
+++ src/helpers.coffee
@@ -2,7 +2,6 @@

 helpers =
     $: (id) -> document.querySelector id if document?
-    $$: (id) -> document.querySelectorAll id if document?

     # suppose max - min is the size of interval (one cycle)
     between: (min, max, val) ->


Add ?AHHHHH

Add ?AHHHHH

--- src/js/main.coffee
+++ src/js/main.coffee
@@ -7,7 +7,10 @@
 Mat4 = require 'gl-matrix-mat4'
 Teapot = require 'teapot'

-WIREFRAME = location.hash.split('#')[1] == 'wireframe'
+POINTS = location.search == '?points'
+WIREFRAME = location.search == '?wireframe'
+SOLID = location.search == '?solid'
+AHHHHH = location.search == '?AHHHHH'

 # http://blogs.msdn.com/b/davrous/archive/2013/06/13/tutorial-series-learning-how-to-write-a-3d-soft-engine-from-scratch-in-c-typescript-or-javascript.aspx

@@ -150,6 +153,14 @@

   drawScanLine: (x1, x2, y, z, color) ->
     [x1, x2] = [x2, x1] if x1 > x2
+
+    if AHHHHH
+      color[2] = color[2] * Math.cos(y % 10 + tick / 2) + 0.3
+      color[1] = color[1] * Math.sin((x1 + x2) / 2 + tick / 8)
+
+      # x1 += Math.tan(Math.floor(y/10 % 2) + tick / 10) * 8
+      # x2 += Math.tan(Math.floor(y/10 % 2) + tick / 10) * 8
+
     for x in [(x1 | 0)..(x2 | 0)] by 1
       @drawPoint(Vec3.fromValues(x, y, z), color)

@@ -174,18 +185,18 @@
       Mat4.multiply(transformMatrix, projectionMatrix, viewMatrix)
       Mat4.multiply(transformMatrix, transformMatrix, worldMatrix)

-      ### Points only ###
-      # for vertex in mesh.vertices
-      #   @drawPoint @project(vertex, transformMatrix)
-
       ### Naive line test ###
       # for vertex, i in mesh.vertices[0...-1]
-      #   point0 = @project(mesh.vertices[i+0], transformMatrix)
-      #   point1 = @project(mesh.vertices[i+1], transformMatrix)
+      #   point0 = @project(mesh.vertices[i+0], transformMatrix, worldMatrix, Vec3.create())
+      #   point1 = @project(mesh.vertices[i+1], transformMatrix, worldMatrix, Vec3.create())
       #   @drawLine(point0, point1)

-      ### Wireframe, Bresenham's ###
-      if WIREFRAME
+      ## Points only ###
+      if POINTS
+        for vertex in mesh.vertices
+          @drawPoint(@project(vertex, transformMatrix, worldMatrix, Vec3.create()).screen)
+        ### Wireframe, Bresenham's ###
+      else if WIREFRAME
         for face, faceIndex in mesh.faces
           vertexA = mesh.vertices[face[0]]
           vertexB = mesh.vertices[face[1]]
@@ -200,10 +211,17 @@
           @drawBLine(projectedC.screen, projectedA.screen)
       else
         for face, faceIndex in mesh.faces
-          vertexA = mesh.vertices[face[0]]
-          vertexB = mesh.vertices[face[1]]
-          vertexC = mesh.vertices[face[2]]
+          vertexA = mesh.vertices[face[0]].slice()
+          vertexB = mesh.vertices[face[1]].slice()
+          vertexC = mesh.vertices[face[2]].slice()
           normal = mesh.normals[faceIndex]
+
+          if AHHHHH
+            # displacement
+            vertexA[1] = vertexA[1] + Math.sin((vertexA[0]*1.5 + tick)/10) * 3.1
+            vertexB[1] = vertexB[1] + Math.sin((vertexB[0]*1.5 + tick)/10) * 3.1
+            vertexC[1] = vertexC[1] + Math.sin((vertexC[0]*1.5 + tick)/10) * 3.1
+

           projectedA = @project(vertexA, transformMatrix, worldMatrix, normal)
           projectedB = @project(vertexB, transformMatrix, worldMatrix, normal)
@@ -218,6 +236,8 @@
           Vec3.normalize(light1, light1)

           grayVal = Math.max(0, Vec3.dot(projectedA.normal, light1))
+          if SOLID
+            grayVal = 1
           color = Vec4.fromValues(grayVal, grayVal, grayVal, 1)
           @drawTriangle(projectedA.screen, projectedB.screen, projectedC.screen, color)

@@ -292,6 +312,7 @@
   [ 4, 5, 6 ]
   [ 4, 6, 7 ]
 ]
+# cubeMesh.computeNormals()
 # meshes.push cubeMesh

 mesh = new Mesh('Teapot')
@@ -303,8 +324,12 @@
 camera.position = Vec3.fromValues(0, 0, 80)
 camera.target = Vec3.fromValues(0, 0, 0)

+# mesh.rotation = [0.3, 0.3, 0.3]
+
+tick = 0

 render = ->
+  tick += 1
   device.clear()

   # X
@@ -314,6 +339,7 @@
   # Z
   # mesh.rotation[2] += 0.01

+  # cubeMesh.rotation[1] += 0.02
   device.render(camera, meshes)
   device.present()


library: Fix only replacing first instance of #MARKER

--- src/library.coffee
+++ src/library.coffee
@@ -3,7 +3,7 @@

 replaceMarker = (str, marker, value) ->
   marker = '#'+marker.toUpperCase()
-  str.replace(marker, value)
+  str.replace(new RegExp(marker,  'g'), value)

 replaceVariables = (str, variables) ->
   for marker, value of variables


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -110,6 +110,7 @@
     borderColor: 'brown'
     color: 'pink'
     font: '30px Comic Sans'
+    height: 20
     name: 'health'
     position:
       x: 5


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -32,7 +32,7 @@
 </pre></code>

 Metered module
-@name Bounded
+@name Mo
 @module
 @constructor
 @param {Object} I Instance variables


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -97,6 +97,7 @@

   ###*
   Configures a meter to be drawn
+
   The position of this game object. By default it is the top left point.
   Redefining the center method will change the relative position.


code reuse, removes an underscore dependency

--- src/methods.coffee
+++ src/methods.coffee
@@ -1,7 +1,5 @@
 "use strict"

-# pull in external modules
-_ = require '../vendor/_.js'
 util = require './util'
 opcodes = require './opcodes'
 attributes = require './attributes'
@@ -43,7 +41,7 @@
   reflector: (rs, success_fn, failure_fn) ->
     # note: sig is the generic type parameter (if one exists), not the full
     # field type.
-    sig = _.find(@attrs, (a) -> a.name == "Signature")?.sig
+    sig = @get_attribute("Signature")?.sig

     create_obj = (clazz_obj, type_obj) =>
       new JavaObject rs, rs.get_bs_class('Ljava/lang/reflect/Field;'), {
@@ -100,16 +98,16 @@
       else
         # micro-optimization for registerNatives and initIDs, don't even bother making a function
         @code = null
-    else
+    else if not @access_flags.abstract
       @has_bytecode = true
-      @code = _.find(@attrs, (a) -> a.name == 'Code')
+      @code = @get_attribute 'Code'

   reflector: (rs, is_constructor=false, success_fn, failure_fn) ->
     typestr = if is_constructor then 'Ljava/lang/reflect/Constructor;' else 'Ljava/lang/reflect/Method;'
-    exceptions = _.find(@attrs, (a) -> a.name == 'Exceptions')?.exceptions ? []
-    anns = _.find(@attrs, (a) -> a.name == 'RuntimeVisibleAnnotations')?.raw_bytes
-    adefs = _.find(@attrs, (a) -> a.name == 'AnnotationDefault')?.raw_bytes
-    sig =  _.find(@attrs, (a) -> a.name == 'Signature')?.sig
+    exceptions = @get_attribute('Exceptions')?.exceptions ? []
+    anns = @get_attribute('RuntimeVisibleAnnotations')?.raw_bytes
+    adefs = @get_attribute('AnnotationDefault')?.raw_bytes
+    sig =  @get_attribute('Signature')?.sig
     obj = {}

     clazz_obj = @cls.get_class_object(rs)


fixing mode for RandomAccessFile

fixing mode for RandomAccessFile

also fixes the associated test

--- src/natives.coffee
+++ src/natives.coffee
@@ -1000,9 +1000,12 @@
       RandomAccessFile: [
         o 'open(Ljava/lang/String;I)V', (rs, _this, filename, mode) ->
             filepath = filename.jvm2js_str()
-            # TODO: actually look at the mode
-            rs.async_op (resume_cb, except_cb) ->
-              fs.open filepath, 'r+', (e, fd) ->
+            mode_str = switch mode
+              when 1 then 'r'
+              when 2 then 'r+'
+              when 4,8 then 'rs+'
+            rs.async_op (resume_cb, except_cb) ->
+              fs.open filepath, mode_str, (e, fd) ->
                 if e?
                   if e.code == 'ENOENT'
                     except_cb -> rs.java_throw rs.get_bs_class('Ljava/io/FileNotFoundException;'), "Could not open file #{filepath}"


fixing ldc2_w bug

fixing ldc2_w bug

--- src/opcodes.coffee
+++ src/opcodes.coffee
@@ -57,6 +57,7 @@
       jvm_str = rs.get_obj(rs.string_redirect(val,@cls))
       val = rs.set_obj({'type':'java/lang/Class','name':rs.jvm2js_str(jvm_str)})
     rs.push val
+    rs.push null if @name is 'ldc2_w'

 class root.BranchOpcode extends root.Opcode
   constructor: (name, params={}) ->


Take both the start and end delimiter into account when tokenizing a text template. [#219]

--- src/parsers.coffee
+++ src/parsers.coffee
@@ -33,7 +33,6 @@
   @parse: (template, delimiters) ->
     tokens = []
     length = template.length
-    delimiterOffset = delimiters[1].length
     index = 0
     lastIndex = 0

@@ -47,11 +46,11 @@
         if index > 0 and lastIndex < index
           tokens.push type: @types.text, value: template.slice lastIndex, index

-        lastIndex = index + 2
+        lastIndex = index + delimiters[0].length
         index = template.indexOf delimiters[1], lastIndex

         if index < 0
-          substring = template.slice lastIndex - 2
+          substring = template.slice lastIndex - delimiters[1].length
           lastToken = tokens[tokens.length - 1]

           if lastToken?.type is @types.text
@@ -61,8 +60,8 @@

           break

-        value = template.slice(lastIndex, index).trim()
+        value = template.slice(lastIndex, index)
         tokens.push type: @types.binding, value: value
-        lastIndex = index + delimiterOffset
+        lastIndex = index + delimiters[1].length

     tokens


subheader elements might not exist

subheader elements might not exist

--- resources/assets/coffee/react/beatmaps/search-panel.coffee
+++ resources/assets/coffee/react/beatmaps/search-panel.coffee
@@ -44,11 +44,11 @@
     $(document).on 'sticky-header:sticking.search-panel', @setHeaderPinned
     $(document).on 'turbolinks:before-cache.search-panel', () =>
       # componentWillUnmount is called too late for Beatmaps.
-      @breadcrumbsElement.removeChild @breadcrumbsPortal
-      @contentElement.removeChild @contentPortal
-
-    @breadcrumbsElement.appendChild @breadcrumbsPortal
-    @contentElement.appendChild @contentPortal
+      @breadcrumbsElement?.removeChild @breadcrumbsPortal
+      @contentElement?.removeChild @contentPortal
+
+    @breadcrumbsElement?.appendChild @breadcrumbsPortal
+    @contentElement?.appendChild @contentPortal

   componentDidUpdate: (prevProps, prevState) =>
@@ -62,10 +62,10 @@

   render: =>
     div null,
-      if window.stickyHeader.breadcrumbsElement()?
+      if @breadcrumbsElement?
         ReactDOM.createPortal @renderBreadcrumbs(), @breadcrumbsPortal

-      if window.stickyHeader.contentElement()?
+      if @contentElement?
         ReactDOM.createPortal @renderStickyContent(), @contentPortal

       div


buggin me

buggin me

--- src/require.coffee
+++ src/require.coffee
@@ -74,7 +74,7 @@
   OSX.NSFileManager.defaultManager.fileExistsAtPath path

 __read = (path) ->
-  OSX.NSString.stringWithContentsOfFile path
+  OSX.NSString.stringWithContentsOfFile(path).toString()

 __modules = {}


Sidebar: Use timer to prevent bounds.

Sidebar: Use timer to prevent bounds.

--- client/app/lib/mainview.coffee
+++ client/app/lib/mainview.coffee
@@ -127,6 +127,7 @@

   createSidebar: ->

+    timer = null
     @setClass 'with-sidebar'

     @addSubView @aside = new KDCustomHTMLView
@@ -138,8 +139,9 @@
         testpath : 'main-sidebar'
       mouseenter : =>
         if @isSidebarCollapsed
-          kd.utils.wait 200, => @toggleHoverSidebar()
+          timer = kd.utils.wait 200, => @toggleHoverSidebar()
       mouseleave : =>
+        kd.utils.killWait timer  if timer
         @toggleHoverSidebar()  if @hasClass 'hover'

     entryPoint = globals.config.entryPoint


Workspace: Added workspace related files to includes.coffee.

--- client/landing/includes.coffee
+++ client/landing/includes.coffee
@@ -215,6 +215,7 @@

   "app/CommonViews/markdownmodal.coffee",

+  # FATIH
   "app/CommonViews/fatih/plugins/fatihpluginabstract.coffee",
   "app/CommonViews/fatih/plugins/fatihlistitem.coffee",
   "app/CommonViews/fatih/plugins/fatihfilelistitem.coffee",
@@ -225,8 +226,19 @@
   "app/CommonViews/fatih/fatihprefpane.coffee",
   "app/CommonViews/fatih/fatih.coffee",

+  # INTRODUCTION TOOLTIP
   "app/CommonViews/introductiontooltip/introductiontooltip.coffee",
   "app/CommonViews/introductiontooltip/introductiontooltipcontroller.coffee",
+
+  # WORKSPACE
+  "app/CommonViews/workspace/panes/pane.coffee",
+  "app/CommonViews/workspace/panes/editorpane.coffee",
+  "app/CommonViews/workspace/panes/previewpane.coffee",
+  "app/CommonViews/workspace/panes/terminalpane.coffee",
+  "app/CommonViews/workspace/panes/videopane.coffee",
+  "app/CommonViews/workspace/panel/panel.coffee",
+  "app/CommonViews/workspace/workspace.coffee",
+

   "app/CommonViews/ModalViewWithTerminal.coffee",