allowing chaining of property accesses by indentation level (really nice for Node and jQuery work) ticket #221

--- lexer.coffee
+++ lexer.coffee
@@ -196,27 +196,29 @@
     @i    += indent.length
     next_character: @chunk.match(MULTI_DENT)[4]
     prev: @prev(2)
+    size: indent.match(LAST_DENTS).reverse()[0].match(LAST_DENT)[1].length
     no_newlines: next_character is '.' or (@value() and @value().match(NO_NEWLINE) and prev and (prev[0] isnt '.') and not @value().match(CODE))
-    return @suppress_newlines(indent) if no_newlines
-    size: indent.match(LAST_DENTS).reverse()[0].match(LAST_DENT)[1].length
-    return @newline_token(indent) if size is @indent
-    if size > @indent
+    if size is @indent
+      return @suppress_newlines(indent) if no_newlines
+      return @newline_token(indent)
+    else if size > @indent
+      return @suppress_newlines(indent) if no_newlines
       diff: size - @indent
       @token 'INDENT', diff
       @indents.push diff
     else
-      @outdent_token @indent - size
+      @outdent_token @indent - size, no_newlines
     @indent: size
     true

   # Record an oudent token or tokens, if we're moving back inwards past
   # multiple recorded indents.
-  outdent_token: (move_out) ->
+  outdent_token: (move_out, no_newlines) ->
     while move_out > 0 and @indents.length
       last_indent: @indents.pop()
       @token 'OUTDENT', last_indent
       move_out -= last_indent
-    @token 'TERMINATOR', "\n" unless @tag() is 'TERMINATOR'
+    @token 'TERMINATOR', "\n" unless @tag() is 'TERMINATOR' or no_newlines
     true

   # Matches and consumes non-meaningful whitespace.


Removed debug.

Removed debug.

--- web/coffee/classroom.coffee
+++ web/coffee/classroom.coffee
@@ -49,7 +49,6 @@
         ga('send', 'event', 'Group', 'LoadTeacherGroupInformation', 'Success')
         for group in groups
           if group.name == groupName
-            console.log teamData.data, teamData.data.length
             $(tabBody).html renderTeamSelection({teams: teamData.data, groupName: groupName, owner: group.owner})
         $(".team-visualization-enabler").on "click", (e) ->
           tid = $(e.target).data("tid")


Don't listen to a channel twice

Don't listen to a channel twice

--- server/index.coffee
+++ server/index.coffee
@@ -3,10 +3,10 @@
 clients = {}
 Meteor.publish 'channel', (channel, nick) ->
     if channel && nick
-        listen channel, nick
+        if !hasOwnProperty.call clients, nick
+            listen channel, nick
         Messages.find(channel : channel)
 listen = (channel, nick) ->
-    console.log(channel, nick)
     client = clients[nick] = new irc.Client 'chat.freenode.net', nick, {
         port : 6665
         channels : [channel]


fix: remove box controller

fix: remove box controller

--- app/controllers/boxes.coffee
+++ app/controllers/boxes.coffee
@@ -1,10 +0,0 @@
-module.exports = App.BoxesController = Ember.ArrayController.extend
-  create: ->
-    newBox = @store.createRecord 'box',
-      ip: window.IP
-      id: @escapeIp(window.IP)
-    newBox.save()
-
-  escapeIp: (ip) ->
-    return false if !ip
-    ip.replace(/\./g, ',')


better add method

better add method

--- src/woodentrack.coffee
+++ src/woodentrack.coffee
@@ -54,20 +54,16 @@

 	# Add piece to track.
 	# Use start transform to specify position/orientation of piece.
-	# If no start provided, the last piece's exit connection will be used.
+	# If no start provided, the cursor connection will be used.
 	# If no pieces in track, the default transform is used.
 	# TODO: check piece connections for collisions and bail if there are any
 	add: (piece, start = null) ->
-		section =
-			if start?
-				@_createSection start
-			else
-				if @_sections.length>0
-					@_sections[@_sections.length-1]
-				else
-					@_createSection()
-		piece.setSection section
-		@_firePieceAdded piece
+		if start? or @connections().length==0
+			section = @_createSection start
+			piece.setSection section
+			@_firePieceAdded piece
+		else
+			@connect piece, @cursor()

 	# connection (code) where the next piece will be added
 	cursor: ->


Social: refactor social api id creation method to create announcement channel too

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -270,7 +270,9 @@
       body          : String
       # channelId for mapping social API
       # to internal usage
-      socialApiChannelId: String
+      socialApiChannelId             : String
+      # channel id for announcements of a group
+      socialApiAnnouncementChannelId : String
       avatar        : String
       slug          :
         type        : String
@@ -1637,15 +1639,17 @@
         console.warn "Group #{@slug} member #{account.profile.nickname} VM is not created: #{err}"  if err
         callback()

-  createSocialApiChannelId: (callback) ->
-    if @socialApiChannelId
-      return callback null, @socialApiChannelId
+  createSocialApiChannels: (callback) ->
+
+    if @socialApiChannelId and @socialApiAnnouncementChannelId
+      return callback null, {@socialApiChannelId, @socialApiAnnouncementChannelId}
+
     @fetchOwner (err, owner)=>
-      return callback err if err
+      return callback err if err?
       unless owner
         return callback { message: "Owner not found for #{@slug} group" }
       owner.createSocialApiId (err, socialApiId)=>
-        return callback err if err
+        return callback err if err?
         # required data for creating a channel
         privacy = if @slug is "koding" then "public" else "private"

@@ -1654,8 +1658,17 @@
           privacyConstant: privacy

         @createGroupChannel options, (err, groupChannelId) =>
-          return callback err if err
-          return callback null, groupChannelId
+          return callback err if err?
+
+          @createAnnouncementChannel options, (err, announcementChannelId) =>
+            return callback err if err?
+
+            return callback null, {
+              # channel id for #public - used as group channel
+              socialApiChannelId: groupChannelId,
+              # channel id for #koding - used for announcements
+              socialApiAnnouncementChannelId:announcementChannelId
+            }

   createGroupChannel:(options, callback)->
@@ -1665,10 +1678,17 @@

     return @createSocialAPIChannel options, callback

+  createAnnouncementChannel:(options, callback)->
+    options.name = @slug
+    options.varName = "socialApiAnnouncementChannelId"
+    options.typeConstant = "announcement"
+
+    return @createSocialAPIChannel options, callback
+
   createSocialAPIChannel:(options, callback)->
     {varName, name, typeConstant, creatorId, privacyConstant} = options

-    return @[varName]  if @[varName]
+    return callback null, @[varName]  if @[varName]

     defaultChannel =
       name            : name or @slug


Notes about broadcast on an unknown group

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -453,10 +453,15 @@
   @broadcast = (groupSlug, event, message)->
     if groupSlug isnt "koding" or event isnt "MemberJoinedGroup"
       @one {slug : groupSlug }, (err, group)=>
-        if err then console.error err
-        unless group then console.error "unknown group #{groupSlug}"
+        console.error err  if err
+        unless group
+          # console.trace()
+          # At some point this error happens with groupSlug as 'undefined'
+          # Tried to trace but failed, maybe its important ~ GG
+          console.error "unknown group #{groupSlug}"
         else if group.privacy isnt "private" and group.visibility isnt "hidden"
           @oldBroadcast.call this, "koding", event, message
+
     @oldBroadcast.call this, groupSlug, event, message

   changeMemberRoles: permit 'grant permissions',


group: add signature annotations

group: add signature annotations

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -7,7 +7,7 @@

   {Relationship} = require 'jraphical'

-  {Inflector, ObjectId, ObjectRef, secure, daisy, race, dash} = require 'bongo'
+  {Inflector, ObjectId, ObjectRef, secure, daisy, race, dash, signature} = require 'bongo'

   JPermissionSet = require './permissionset'
   {permit}       = JPermissionSet
@@ -73,77 +73,143 @@
       ]
     sharedMethods   :
       static        : [
-        'one'
-        'create'
-        'each'
-        'count'
-        'byRelevance'
-        'someWithRelationship'
-        '__resetAllGroups'
-        'fetchMyMemberships'
-        '__importKodingMembers'
-        'suggestUniqueSlug'
+        one:
+          (signature Object, Function)
+        create:
+          (signature Object, Function)
+        each: [
+          (signature Object, Object, Function)
+          (signature Object, Object, Object, Function)
+        ]
+        count: [
+          (signature Function)
+          (signature Object, Function)
+        ]
+        byRelevance:[
+          (signature String, Function)
+          (signature String, Object, Function)
+        ]
+        someWithRelationship:
+          (signature Object, Object, Function)
+        __resetAllGroups:
+          (signature Function)
+        fetchMyMemberships: [
+          (signature [ObjectId], Function)
+          (signature [ObjectId], String, Function)
+        ]
+        __importKodingMembers:
+          (signature Function)
+        suggestUniqueSlug: [
+          (signature String, Function)
+          (signature String, Number, Function)
+        ]
       ]
       instance      : [
-        'join'
-        'leave'
-        'modify'
-        'fetchPermissions'
-        'createRole'
-        'updatePermissions'
-        'fetchMembers'
-        'fetchRoles'
-        'fetchMyRoles'
-        'fetchUserRoles'
-        'changeMemberRoles'
-        'canOpenGroup'
-        'canEditGroup'
-        'fetchMembershipPolicy'
-        'modifyMembershipPolicy'
-        'requestAccess'
-        'setReadme'
-        'addCustomRole'
-        'resolvePendingRequests'
-        'fetchVocabulary'
-        'fetchMembershipStatuses'
-        'setBackgroundImage'
-        'removeBackgroundImage'
-        'fetchAdmin'
-        'inviteByEmail'
-        'inviteByEmails'
-        'kickMember'
-        'transferOwnership'
-        'fetchRolesByClientId'
-        'fetchInvitationsFromGraph'
-        'countInvitationsFromGraph'
-        'fetchMembersFromGraph'
-        'remove'
-        'bulkApprove'
-        'fetchNewestMembers'
-        'countMembers'
-        'checkPayment'
-        'makePayment'
-        'updatePayment'
-        'setPaymentInfo'
-        'fetchPaymentInfo'
-        'checkUserBalance'
-        'makeExpense'
-        'getUserExpenses'
-        'getAllExpenses'
-        'fetchTransactions'
-        'fetchBundle'
-        'updateBundle'
-        # 'addProduct'
-        # 'deleteProduct'
-        'fetchProducts'
-        'createVM'
-        'canCreateVM'
-        'vmUsage'
-        'saveInviteMessage'
-        'redeemInvitation'
-        'fetchPaymentMethod'
-        'linkPaymentMethod'
-        'unlinkPaymentMethod'
+        join: [
+          (signature Function)
+          (signature Object, Function)
+        ]
+        leave:[
+          (signature Function)
+          (signature Object, Function)
+        ]
+        modify:
+          (signature Object, Function)
+        fetchPermissions: [
+          (signature Function)
+          (signature Object, Function)
+        ]
+        createRole:
+          (signature Object, Function)
+        updatePermissions:
+          (signature Object, Function)
+        fetchMembers: [
+          (signature Function)
+          (signature Object, Function)
+          (signature Object, Object, Function)
+        ]
+        fetchRoles: [
+          (signature Function)
+          (signature Object, Function)
+          (signature Object, Object, Function)
+        ]
+        fetchMyRoles:
+          (signature Function)
+        fetchUserRoles: [
+          (signature Function)
+          (signature [ObjectId], Function)
+        ]
+        changeMemberRoles:
+          (signature ObjectId, [String], Function)
+        canOpenGroup:
+          (signature Function)
+        canEditGroup:
+          (signature Function)
+        fetchMembershipPolicy:
+          (signature Function)
+        modifyMembershipPolicy:
+          (signature Object, Function)
+        requestAccess:
+          (signature Function)
+        addCustomRole:
+          (signature Object, Function)
+        resolvePendingRequests:
+          (signature Function)
+        fetchMembershipStatuses:
+          (signature Function)
+        fetchAdmin:
+          (signature Function)
+        inviteByEmail:
+          (signature String, Object, Function)
+        inviteByEmails:
+          (signature [String], Object, Function)
+        kickMember:
+          (signature ObjectId, Function)
+        transferOwnership:
+          (signature ObjectId, Function)
+        fetchRolesByClientId: [
+          (signature Function)
+          (signature String, Function)
+        ]
+        fetchInvitationsFromGraph:
+          (signature String, Object, Function)
+        countInvitationsFromGraph:
+          (signature String, Object, Function)
+        fetchMembersFromGraph:
+          (signature Object, Function)
+        remove:
+          (signature Function)
+        bulkApprove:
+          (signature Number, Object, Function)
+        fetchNewestMembers: [
+          (signature Function)
+          (signature Object, Function)
+          (signature Object, Object, Function)
+        ]
+        countMembers:
+          (signature Function)
+        makePayment:
+          (signature Object, Function)
+        # # addProduct:
+        # (signature)
+        # # deleteProduct:
+        # (signature)
+        fetchProducts: [
+          (signature Function)
+          (signature Object, Function)
+        ]
+        saveInviteMessage: [
+          (signature String, String)
+          (signature String, String, Function)
+        ]
+        redeemInvitation:
+          (signature String, Function)
+        fetchPaymentMethod:
+          (signature Function)
+        linkPaymentMethod:
+          (signature String, Function)
+        unlinkPaymentMethod:
+          (signature String, Function)
       ]
     schema          :
       title         :
@@ -302,37 +368,6 @@
                     koding.approveMember account, ->
                       console.log "added member: #{account.profile.nickname}"

-  setBackgroundImage: permit 'edit groups',
-    success:(client, type, value, callback=->)->
-      if type is 'customImage'
-        operation =
-          $set: {}
-          $addToSet : {}
-        operation.$addToSet['customize.background.customImages'] = value
-      else if type is 'customColor'
-        operation =
-          $set: {}
-          $addToSet : {}
-        operation.$addToSet['customize.background.customColors'] = value
-      else
-        operation = $set : {}
-
-      operation.$set["customize.background.customType"] = type
-
-      if type in ['defaultImage','defaultColor','customColor','customImage']
-        operation.$set["customize.background.customValue"] = value
-
-      @update operation, callback
-
-  removeBackgroundImage: permit 'edit groups',
-    success:(client, type, value, callback=->)->
-      if type is 'customImage'
-        @update {$pullAll: 'customize.background.customImages': [value]}, callback
-      else if type is 'customColor'
-        @update {$pullAll: 'customize.background.customColors': [value]}, callback
-      else
-        console.log 'Nothing to remove'
-
   @render        :
     loggedIn     :
       kodingHome : require '../../render/loggedin/kodinghome'
@@ -1306,94 +1341,6 @@

         daisy queue

-  updateBundle: permit 'change bundle',
-    success: (client, formData, callback = (->)) ->
-      @fetchBundle (err, bundle) =>
-        return callback err  if err
-        { overagePolicy, sharedVM, allocation } = formData
-        bundle.update $set: {overagePolicy, sharedVM, allocation}, callback
-
-  createBundle: (data, callback)->
-    @fetchBundle (err, bundle)=>
-      return callback err                               if err
-      return callback new KodingError 'Bundle exists!'  if bundle?
-
-      JGroupBundle = require '../bundle/groupbundle'
-      bundle = new JGroupBundle data
-      bundle.save (err)=>
-        return callback err  if err
-        @addBundle bundle, (err)-> callback err, unless err then bundle
-
-  fetchBundle$: permit 'commission resources',
-    success: (client, rest...) -> @fetchBundle rest...
-
-  setPaymentInfo: permit 'manage payment methods',
-    success: (client, data, callback) ->
-      # TODO: Give credits to existing users
-      JPaymentGroup = require '../payment/group'
-      JPaymentGroup.setPaymentInfo client, this, data, callback
-
-  fetchPaymentInfo: permit 'manage payment methods',
-    success: (client, callback)->
-      JPaymentGroup = require '../payment/group'
-      JPaymentGroup.fetchPaymentMethod this, callback
-
-  checkUserBalance: secure (client, data, callback)->
-    @fetchBundle (err, bundle)=>
-      return callback err        if err
-      return callback new KodingError 'unable to fetch group bundle'  unless bundle
-      return callback null, 0,0  if bundle.allocation is 0
-
-      @getUserExpenses client, data, (err, expenses)->
-        return callback null  if err
-        callback bundle.allocation, expenses
-
-  getAllExpenses: secure (client, data, callback)->
-    JVM   = require '../vm'
-    JUser = require '../user'
-    JUser.fetchUser client, (err, user)=>
-      return callback err  if err
-      # TODO: Get VM price from JPaymentPlan
-      JVM.some
-        planOwner : "group_#{@_id}"
-        vmType    : 'expensed'
-      , {planCode: 1, users: 1}, callback
-
-  getUserExpenses: secure (client, data, callback)->
-    JVM   = require '../vm'
-    JUser = require '../user'
-    JUser.fetchUser client, (err, user)=>
-      return callback err  if err
-      # TODO: Get VM price from JPaymentPlan
-      JVM.some
-        planOwner: "group_#{@_id}"
-        vmType   : 'expensed'
-        users    : { $elemMatch: id: user.getId(), owner: yes }
-      , {planCode: 1}, callback
-
-  makeExpense: secure (client, data, callback)->
-    JPaymentPlan = require '../payment/plan'
-    JPaymentPlan.one code: data.plan, (err, plan)=>
-      return callback err  if err
-      @checkUserBalance client, data, (err, limit, balance)=>
-        return callback err  if err
-        console.log limit, balance, plan.feeAmount
-        if limit >= balance + plan.feeAmount
-          @chargeGroup client, data, callback
-        else
-          callback new KodingError "You don't have enough balance"
-
-  makePayment: permit 'make payments',
-    success: (client, data, callback)->
-      @chargeGroup client, data, callback
-
-  chargeGroup: secure (client, data, callback)->
-    data.plan   ?= @payment.plan
-    JPaymentPlan = require '../payment/plan'
-    JPaymentPlan.one code: data.plan, (err, plan)=>
-      return callback err  if err
-      plan.subscribeGroup this, data, callback
-
   # addProduct: permit 'manage products',
   #   success: (client, data, callback)->
   #     JPaymentGroup = require '../payment/group'
@@ -1403,21 +1350,6 @@
   #   success: (client, data, callback)->
   #     JPaymentGroup = require '../payment/group'
   #     JPaymentGroup.deletePlan this, data, callback
-
-  checkPayment: (callback)->
-    JPaymentSubscription = require '../payment/subscription'
-    JPaymentSubscription.getGroupSubscriptions this, callback
-
-  fetchTransactions: (callback)->
-    JPaymentGroup = require '../payment/group'
-    JPaymentGroup.fetchTransactions this, callback
-
-  vmUsage: secure ({connection:{delegate}}, callback)->
-    @fetchBundle (err, bundle)=>
-      return callback err  if err
-      return callback new KodingError 'unable to fetch group bundle'  unless bundle
-
-      bundle.checkUsage delegate, this, callback

   checkVmType: (data, callback) ->
     unless data.type in ['user', 'group', 'expensed']
@@ -1441,20 +1373,6 @@
       else
         callback new KodingError 'Unable to fetch group bundle'

-  canCreateVM: secure ({ connection:{ delegate }}, data, callback)->
-    @checkVmType data, (err) =>
-      return callback err  if err
-      @fetchOrCreateBundle (err, bundle) =>
-        return callback err  if err
-        bundle.canCreateVM delegate, this, data, callback
-
-  createVM: secure ({connection:{delegate}}, data, callback)->
-    @checkVmType data, (err) =>
-      return callback err  if err
-      @fetchOrCreateBundle (err, bundle) =>
-        return callback err  if err
-        bundle.createVM delegate, this, data, callback
-
   countMembers: secure (client, callback)->
     {Member} = require "../graph"
     Member.fetchMemberCount {groupId:@_id, client:client}, callback
@@ -1485,15 +1403,15 @@
       @fetchOrCountInvitations client, type, 'count', options, (err, result)=>
         return callback err, result?[0]?.count

-  _fetchMembersFromGraph:(client, options, callback)->
+  fetchMembersFromGraph:(client, options, callback)->
     options.groupId = @getId()
     options.client  = client
     {Member} = require '../graph'
     Member.fetchMemberList options, (err, results)=>
       callback err, results

-  fetchMembersFromGraph: permit 'list members',
-    success: @::_fetchMembersFromGraph
+  fetchMembersFromGraph$: permit 'list members',
+    success: (client, rest...) -> @fetchMembersFromGraph client, rest...

   @each$ = (selector, options, callback)->
     selector.visibility = 'visible'


server: make index.html laod faster

server: make index.html laod faster

--- workers/social/lib/social/render/scriptblock.coffee
+++ workers/social/lib/social/render/scriptblock.coffee
@@ -1,3 +1,5 @@
+{dash} = require 'bongo'
+
 module.exports = (options = {}, callback)->
   encoder = require 'htmlencode'

@@ -82,20 +84,6 @@
     #{if argv.t then "<script src=\"/a/js/tests.js\"></script>" else ''}
     """

-  kallback = ->
-    {delegate} = options.client.connection
-
-    if 'function' is typeof delegate?.fetchSubscriptions
-      selector = {}
-      fetchOptions = targetOptions: selector :{ tags: $nin: ["nosync"] }
-
-      delegate.fetchSubscriptions selector, fetchOptions, (err, subscriptions)->
-        if subscriptions and subscriptions.length
-          usePremiumBroker = yes
-        callback null, createHTML()
-    else
-      callback null, createHTML()
-
   generateScript = ->
     selector =
       partialType : "HOME"
@@ -105,30 +93,65 @@
     else
       selector.isActive  = yes

+    queue = [
+      ->
+        bongoModels.JCustomPartials.one selector, (err, partial)->
+          console.log ">>>>>>>>> partial", partial
+          customPartial = partial.data  if not err and partial
+          queue.fin()
+      ->
+        bongoModels.JGroup.one {slug : slug or 'koding'}, (err, group) ->
+          console.log ">>>>>>>>> group", group
+          console.log err if err
+
+          bongoModels.JReferralCampaign.one isActive: yes, (err, campaignData_)->
+            console.log ">>>>>>>>> JReferralCampaign", campaignData_
+
+            if not err and campaignData_ and campaignData_.data
+              campaignData = campaignData_.data
+
+            if group
+              currentGroup = group
+
+            queue.fin()
+      ->
+        bongoModels.JWorkspace.fetch client, {}, (err, workspaces) ->
+          console.log err  if err
+          userWorkspaces = workspaces or []
+          queue.fin()
+      ->
+        bongoModels.JMachine.some$ client, {}, (err, machines) ->
+          console.log err  if err
+          userMachines = machines or []
+          queue.fin()
+    ]
+
+    dash queue, -> callback null, createHTML()
+
     # add custom partials into body
-    bongoModels.JCustomPartials.one selector, (err, partial)->
-      customPartial = partial.data  if not err and partial
+    # bongoModels.JCustomPartials.one selector, (err, partial)->
+    #   customPartial = partial.data  if not err and partial

-      bongoModels.JGroup.one {slug : slug or 'koding'}, (err, group) ->
-        console.log err if err
+    #   bongoModels.JGroup.one {slug : slug or 'koding'}, (err, group) ->
+    #     console.log err if err

         # add custom partial into referral campaign
-        bongoModels.JReferralCampaign.one isActive: yes, (err, campaignData_)->
+    #     bongoModels.JReferralCampaign.one isActive: yes, (err, campaignData_)->

-          if not err and campaignData_ and campaignData_.data
-            campaignData = campaignData_.data
+    #       if not err and campaignData_ and campaignData_.data
+    #         campaignData = campaignData_.data

-          if group
-            currentGroup = group
+    #       if group
+    #         currentGroup = group

-          bongoModels.JWorkspace.fetch client, {}, (err, workspaces) ->
-            console.log err  if err
-            userWorkspaces = workspaces or []
+    #       bongoModels.JWorkspace.fetch client, {}, (err, workspaces) ->
+    #         console.log err  if err
+    #         userWorkspaces = workspaces or []

-            bongoModels.JMachine.some$ client, {}, (err, machines) ->
-              console.log err  if err
-              userMachines = machines or []
-              kallback()
+    #         bongoModels.JMachine.some$ client, {}, (err, machines) ->
+    #           console.log err  if err
+    #           userMachines = machines or []
+    #           callback null, createHTML()

   socialApiCacheFn = require '../cache/socialapi'


Update es-ES.coffee (#5730)

Update es-ES.coffee (#5730)

Co-authored-by: Valentin Bryukhanov <53545457af642b0f8712bb2cc004f120ce4436f4@gmail.com>

--- app/locale/es-ES.coco.coffee
+++ app/locale/es-ES.coco.coffee
@@ -156,6 +156,11 @@
     view_progress: "Ver mi progreso"
     go_to_courses: "Ir a mis cursos"
     want_coco: "¿Quieres CodeCombat en tu escuela?"
+    educator: "Educador"
+    student: "Estudiante"
+
+  nav:
+    educators: "Estudiantes"
 #    our_coding_programs: "Our Coding Programs"
 #    codecombat: "CodeCombat"
 #    ozaria: "Ozaria"
@@ -163,9 +168,6 @@
 #    ozaria_blurb: "An adventure game and Computer Science program where students master the lost magic of coding to save their world. Recommended for educators and students."
 #    try_codecombat: "Try CodeCombat"
 #    try_ozaria: "Try Ozaria"
-
-  nav:
-    educators: "Maestros"
     follow_us: "Síguenos"
     general: "General"
     map: "Mapa"
@@ -453,6 +455,7 @@
     coming_soon: "¡Próximamente!"
     continue: "Continuar" # When used as an action verb, like "Continue forward"
     next: "Siguiente"
+#    default_code: "Default Code"
     default_code: "Código por defecto"
     loading: "Cargando..."
     overview: "Vista global"
@@ -481,7 +484,7 @@
     save_changes: "Guardar cambios"
     required_field: "Campo requerido" # {change}
     submit: "Enviar"
-    replay: "Repetir"
+    replay: "Repetición"
     complete: "Completar"

   general:
@@ -616,8 +619,8 @@
     reload_really: "¿Estás seguro que quieres reiniciar el nivel?"
     reload_confirm: "Recargarlo todo"
 #    restart_really: "Are you sure you want to restart the level? You'll loose all the code you've written."
-    restart_confirm: "Sí, Reiniciar"
-#    test_level: "Test Level"
+    restart_confirm: "Si, reiniciar"
+    test_level: "Nivel de prueba"
     victory: "Victoria"
     victory_title_prefix: "¡"
     victory_title_suffix: " Completado!"
@@ -634,7 +637,7 @@
     victory_experience_gained: "XP Conseguida"
     victory_gems_gained: "Gemas Conseguidas"
     victory_new_item: "Nuevo artículo"
-    victory_new_hero: "Nuevo heroe"
+    victory_new_hero: "Nuevo Héroe"
     victory_viking_code_school: "Jolines, el nivel que acabas de pasar era difícil! Si todavía no eres un desarrollador de software, deberías serlo. Acabas de conseguir una aceptación por vía rápida con la Escuela Vikinga de Có, donde tú puedes llevar tus habilidades al siguiente nivel y convertirte en un desarrollador web profesional en 14 semanas."
     victory_become_a_viking: "Convertirse en un vikingo"
     victory_no_progress_for_teachers: "El progreso no se guarda para los profesores. Pero, puedes añadir una cuenta de estudiante a tu clase para ti."
@@ -840,8 +843,8 @@
     difficulty: "Dificultad"
     gold_collected: "Oro conseguido"
     survival_time: "Sobrevivió"
-#    defeated: "Enemies Defeated"
-#    code_length: "Lines of Code"
+    defeated: "Enemigos Derrotados"
+    code_length: "Líneas de código"
 #    score_display: "__scoreType__: __score__"

   inventory:
@@ -871,9 +874,9 @@
     prompt_button: "Ingresa a la tienda"
     recovered: "Las gemas compradas con anterioridad han sido recuperadas. Por favor, actualiza la página."
     price: "x{{gems}} / mes"
-#    buy_premium: "Buy Premium"
-    purchase: "Compra"
-#    purchased: "Purchased"
+    buy_premium: "Comprar Premium"
+    purchase: "Comprar"
+    purchased: "Comprado"

 #  subscribe_for_gems:
 #    prompt_title: "Not Enough Gems!"
@@ -1035,7 +1038,7 @@
     restricted_to_certain_heroes: "Solo ciertos héroes pueden jugar este nivel."

   char_customization_modal:
-    heading: "Personalizar tu héroe"
+    heading: "Personalizar tu héroeo"
     body: "Cuerpo"
     name_label: "Nombre del héroe"
     hair_label: "Color de pelo"


Fix type in global

Fix type in global

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -1492,7 +1492,7 @@
     jsNoop()

 ms.global = ms_global = (ctx, call) ->
-  call.tea = typeConstant 'JS'
+  call.tea = toConstrained typeConstant 'JS'
   assignCompile ctx, call, (jsValue "window")

 callJsMethodCompile = (ctx, call) ->


Change view main directory

Change view main directory

--- Gulpfile.coffee
+++ Gulpfile.coffee
@@ -27,9 +27,9 @@
     .pipe gulp.dest 'build/assets/images/'

 gulp.task 'html', ['compile:clean'], ->
-  gulp.src 'app/assets/views/**/*.html'
-    .pipe changed 'build/assets/views'
-    .pipe gulp.dest 'build/assets/views'
+  gulp.src 'app/views/**/*.html'
+    .pipe changed 'build/views'
+    .pipe gulp.dest 'build/views'

 gulp.task 'eco', ['compile:clean'], ->
   gulp.src 'app/assets/views/**/*.ect'


Fix Travis build.

Fix Travis build.

--- Gulpfile.coffee
+++ Gulpfile.coffee
@@ -196,4 +196,4 @@
 		.pipe buffer()
 		.pipe coveralls()

-gulp.task 'travis', seq 'check', 'build', 'docs', 'nodeunit', 'jscoverage-report', 'coveralls', 'force-exit'
+gulp.task 'travis', seq 'check', 'build', 'nodeunit', 'jscoverage-report', 'coveralls', 'force-exit'


:arrow_up: add notroute option to Route component

--- src/renderer/components/route-component.coffee
+++ src/renderer/components/route-component.coffee
@@ -12,12 +12,21 @@
 *** Usage ***

 -- General routing
-Only rendered child component which has @props.route equals to current routing.
+Only rendered child component which has @props.route equal to current routing.

 Example:
 <Route>
   <Index route='index' />
   <About route='about' />
+</Route>
+
+
+-- Inverse routing
+Only rendered child component which has @props.route not equal to current routing.
+
+Example:
+<Route>
+  <Header notroute='index' />
 </Route>

@@ -85,14 +94,15 @@
           if default_route? && default_fragment?
             @context.ctx.routeAction.navigate(fragment, {replace: true, silent: true})
           React.Children.map @props.children, (child) =>
-            if child.props.route?
+            if child.props.route? || child.props.notroute?
               match = false
               route_arr = route.split(':')
-              child.props.route.split(':').forEach (r, i) ->
+              (child.props.route || child.props.notroute).split(':').forEach (r, i) ->
                 if r == route_arr[i]
                   match = true
                 else
                   match = false
+              match = !match if child.props.notroute?
               if match
                 if @props.addStyle?
                   React.cloneElement child,


Add 'appendOnce' method

Add 'appendOnce' method

--- src/scripts/helpers/backbone/views/base.coffee
+++ src/scripts/helpers/backbone/views/base.coffee
@@ -17,6 +17,15 @@

     append: (view) ->
       @appendAs('div', view)
+
+    appendOnce: (options) ->
+      if not (_.find @views, (view) -> options.view instanceof view.constructor)
+        if options.as.charAt(0) is '.'
+          @appendAs("div class=\"#{options.as.substr(1)}\"", options.view)
+        else if options.as.charAt(0) is '#'
+          @appendAs("div id=\"#{options.as.substr(1)}\"", options.view)
+        else
+          @appendAs(options.as, options.view)

     appendAs: (type, view) ->
       @$el = @parent.$el
@@ -76,6 +85,7 @@
       return data

     _render: () ->
+      _.each @regions, (region) -> region.empty()
       @updateTitle()
       @renderDom()


adding quotes around the urls

adding quotes around the urls

--- src/scripts/helpers/backbone/views/base.coffee
+++ src/scripts/helpers/backbone/views/base.coffee
@@ -93,7 +93,7 @@
     addCanonicalMetaDataToDerivedCopies: () ->
       parentId = @getTemplateData().parentId
       relCanonical = "canonical"
-      canonicalUrl = "<link rel= #{relCanonical} href=//#{location.hostname}/contents/#{parentId}/>"
+      canonicalUrl = "<link rel= #{relCanonical} href=\"//#{location.hostname}/contents/#{parentId}/\">"
       headTag = $('head')
       canonical = $("link[rel^= #{relCanonical}]")
       canonical.remove()


Fix manage project page

Fix manage project page

--- projects/static/coffee/manage.coffee
+++ projects/static/coffee/manage.coffee
@@ -9,6 +9,7 @@
     collection.fetch
         data:
             limit: 0
+            fetch: true
         success: (collection) ->
             view = new app.views.ManageProjectsView
                 el: $('.js-manage-projects')


wrong key

wrong key

--- jasmine-harness.coffee
+++ jasmine-harness.coffee
@@ -7,8 +7,8 @@
 tap = require './spec/lib/jasmine.tap_reporter.js'

 # expose helpers
-attach require('./spec/SpecHelper')
-attach require('./spec/Mock')
+expose require('./spec/SpecHelper')
+expose require('./spec/Mock')

 # insert test files here
 # require('./spec/BaconSpec')


Remove overwritten `hasImage`

Remove overwritten `hasImage`

--- models/auction_lot.coffee
+++ models/auction_lot.coffee
@@ -13,10 +13,6 @@

   href: (artist) ->
     "/artist/#{artist.id}/auction-result/#{@id}"
-
-  # @return {Boolean}
-  hasImage: ->
-    @get('image_url') isnt '/assets/shared/missing_image.png'

   # @return {Boolean}
   hasDimensions: ->


wrap: fix tests

wrap: fix tests

--- test/misc.wrap.coffee
+++ test/misc.wrap.coffee
@@ -11,17 +11,17 @@
         option_a: 'a', option_b: 'b'
         -> #do sth
       ]
-      goptions.should.eql parallel: true
+      goptions.should.eql parallel: 1
       options.should.eql option_a: 'a', option_b: 'b'
       callback.should.be.a.Function

     it 'accept 3 arguments', ->
       [options, goptions, callback] = wrap.args [
         {option_a: 'a', option_b: 'b'}
-        {parallel: 1}
+        {parallel: 2}
         -> #do sth
       ]
-      goptions.should.eql parallel: 1
+      goptions.should.eql parallel: 2
       options.should.eql option_a: 'a', option_b: 'b'
       callback.should.be.a.Function

@@ -29,8 +29,8 @@
       [options, goptions, callback] = wrap.args [
         option_a: 'a', option_b: 'b'
         -> #do sth
-      ], parallel: 1
-      goptions.parallel.should.equal 1
+      ], parallel: 2
+      goptions.parallel.should.equal 2
       options.should.eql option_a: 'a', option_b: 'b'
       callback.should.be.a.Function


Simplify modulo tests

Simplify modulo tests

--- test/operators.coffee
+++ test/operators.coffee
@@ -331,12 +331,9 @@

 test "modulo operator", ->
   check = (a, b, expected) ->
-    res = a %% b
-    # Don't use eq because it treats 0 as different to -0.
-    ok res == expected or isNaN(res) and isNaN(expected),
-      "expected #{a} %%%% #{b} to be #{expected}"
+    eq expected, a %% b, "expected #{a} %%%% #{b} to be #{expected}"
   check 0, 1, 0
-  check 0, -1, 0
+  check 0, -1, -0
   check 1, 0, NaN
   check 1, 2, 1
   check 1, -2, -1


fixing wrong tests

fixing wrong tests

--- test/spec/data.coffee
+++ test/spec/data.coffee
@@ -17,7 +17,7 @@
         Data.set(e, 'key', 'bar')
         assert.equal Data.get(e, 'key'), 'bar', "Data changed to bar in key"

-        assert.equal Data.get(e), {key: 'bar'}, "Get all data"
+        assert.deepEqual Data.get(e), {key: 'bar'}, "Get all data"

     test 'Element storage data', ->
         el = $(document.createElement('div'))
@@ -27,7 +27,7 @@
         assert.equal el.data('key'), 'foo', "Data foo in key"

         el.data('key', {fizz: 1, buzz: 2})
-        assert.equal el.data('key'), {fizz: 1, buzz: 2}, "Data changed to object in key"
+        assert.deepEqual el.data('key'), {fizz: 1, buzz: 2}, "Data changed to object in key"

     test 'List storage data', ->
         el = $([


Removed the authentication package

Removed the authentication package

--- src/app/app.coffee
+++ src/app/app.coffee
@@ -1,7 +1,6 @@
 angular.module 'webapp', [
   'ui.bootstrap'
   'ui.router'
-  'webapp.auth'
   'webapp.header'
   'webapp.pages'
   'webapp-templates'


removed dead code

removed dead code

--- neptune-namespaces.coffee
+++ neptune-namespaces.coffee
@@ -34,10 +34,6 @@

 {max} = Math

-# log "opts", opts
-
-# [nodePath, neptuneNamespacesPath, targetPaths...] = process.argv
-
 run = (targetPaths) ->

   todoList = for targetPath in targetPaths


Amend dropdowns to match by starting term

--- app/assets/javascripts/components/dropdown.js.coffee
+++ app/assets/javascripts/components/dropdown.js.coffee
@@ -56,11 +56,7 @@

   componentDidMount: ->
     data = []
-    if @state.name in ['trading_partner', 'country_of_origin']
-      data = @state.data.map (value) ->
-        id: value[0]
-        text: value[1]
-    else if @state.name == 'taxon_name'
+    if @state.name == 'taxon_name'
       @select2TaxonConcept()
       return
     else
@@ -70,7 +66,9 @@
     $("##{@state.name}_dropdown").select2({
       placeholder: @state.placeholder,
       data: data
-      sorter: @sortByText
+      matcher: (params, data) =>
+        return @matchStart(params, data)
+
     })

   componentDidUpdate: ->
@@ -109,9 +107,8 @@
           more: more
     })

-  sortByText: (results) ->
-    query = $('.select2-search__field').val().toUpperCase()
-    results.sort (a, b) ->
-      firstPos = a.text.toUpperCase().indexOf(query.toUpperCase())
-      secondPos = b.text.toUpperCase().indexOf(query.toUpperCase())
-      return firstPos - secondPos
+  matchStart: (params, data) ->
+    params.term = params.term || ''
+    if data.text.toUpperCase().indexOf(params.term.toUpperCase()) == 0
+      return data
+    return false


bounded

bounded

--- src/bounded.coffee
+++ src/bounded.coffee
@@ -69,16 +69,16 @@
   with the bounds passed in.

-    player = Core
-      x: 4
-      y: 6
-      width: 20
-      height: 20
-
-    player.include(Bounded)
-
-    player.collides({x: 5, y: 7, width: 20, height: 20})
-    => true
+  player = Core
+    x: 4
+    y: 6
+    width: 20
+    height: 20
+
+  player.include(Bounded)
+
+  player.collides({x: 5, y: 7, width: 20, height: 20})
+  => true

   @name collides


Replaced msg with res; and added module name to logger

--- src/mail.coffee
+++ src/mail.coffee
@@ -38,24 +38,24 @@
     #
     # Hubot commands
     #
-    robot.respond /unmail\s?(.*)/i, (msg) ->
+    robot.respond /unmail\s?(.*)/i, (res) ->
         deleted = 0

         # TODO: Detect and delete empty nodes
         DeleteByRecipient = (recipient) ->
             if mails = GetMail()[recipient.toLowerCase()]
                 for mail, index in mails by -1
-                    if mail[0] is msg.message.user.name.toLowerCase()
+                    if mail[0] is res.message.user.name.toLowerCase()
                         mails.splice index, 1
                         ++deleted

         # Delete using a specified recipient
-        if recipient = msg.match[1]
+        if recipient = res.match[1]
             DeleteByRecipient recipient
             if deleted is 0
-                msg.reply "There are no outbound mail sent by you towards #{recipient}."
+                res.reply "There are no outbound mail sent by you towards #{recipient}."
             else
-                msg.reply "#{deleted} of your mail(s) towards #{recipient} has been deleted."
+                res.reply "#{deleted} of your mail(s) towards #{recipient} has been deleted."
                 robot.brain.save()

         # Delete all from sender / command executor
@@ -64,34 +64,34 @@
                 DeleteByRecipient recipient

             if deleted is 0
-                msg.reply "There are no outbound mail sent by you."
+                res.reply "There are no outbound mail sent by you."
             else
-                msg.reply "#{deleted} of your mail(s) has been deleted."
+                res.reply "#{deleted} of your mail(s) has been deleted."
                 robot.brain.save()

-    robot.respond /mail (\S+) (.+)/i, (msg) ->
-        [_command, recipient, message] = msg.match
-        sender = msg.message.user.name.toLowerCase()
+    robot.respond /mail (\S+) (.+)/i, (res) ->
+        [_command, recipient, message] = res.match
+        sender = res.message.user.name.toLowerCase()
         recipient = recipient.toLowerCase()

         if sender is recipient
-            msg.reply "Are you sure you want to send a mail to yourself? Sad."
+            res.reply "Are you sure you want to send a mail to yourself? Sad."
         else if recipient is robot.name.toLowerCase()
-            msg.reply "Thanks, but no thanks! I do not need any mail."
+            res.reply "Thanks, but no thanks! I do not need any mail."
         else
             try
                 GetMail()[recipient] or= []
                 GetMail()[recipient].push [sender, moment().unix(), message]
                 robot.brain.save()
-                msg.reply "Your mail has been prepared for #{recipient}."
+                res.reply "Your mail has been prepared for #{recipient}."
             catch error
-                robot.logger.error error
+                robot.logger.error "hubot-mail: #{error}"

     #
     # Hubot events
     #
-    robot.enter (msg) ->
-        DeliverMail msg
+    robot.enter (res) ->
+        DeliverMail res

-    robot.hear /./i, (msg) ->
-        DeliverMail msg+    robot.hear /./i, (res) ->
+        DeliverMail res


Nuke navbar

Nuke navbar

--- js/inner/navbar.coffee
+++ js/inner/navbar.coffee
@@ -1,63 +0,0 @@
-CI.inner.Navbar = class Navbar extends CI.inner.Obj
-  constructor: (@selected, @build) -> # we're sharing these observables with the VM
-    @build_branch = @komp =>
-      if @build() then @build().branch()
-
-    # unpack selected
-    @crumbs = @komp => @selected().crumbs
-    @page = @komp => @selected().page
-    @username = @komp => @selected().username
-    @project = @komp => @selected().project
-    @project_name = @komp => @selected().project_name
-    @sel_branch = @komp => @selected().branch
-    @build_num = @komp => @selected().build_num
-
-    @branch = @komp => @sel_branch() or "..."
-
-    @build_branch.subscribe (new_val) =>
-      sel = @selected()
-      sel.branch = new_val
-      @selected(sel)
-
-    @project_path = @komp =>
-      if @username() and @project()
-        @projectPath(@username(), @project())
-
-    @branch_name = @komp =>
-      if @branch()?
-        CI.stringHelpers.trimMiddle(@branch(), 45)
-
-    @branch_path = @komp =>
-      @projectPath(@username(), @project(), @branch())
-
-    @project_settings_name = @komp => "Edit settings"
-
-    @project_settings_path = @komp => @project_path() + '/edit'
-
-    @build_path = @komp =>
-      "#{@project_path()}/#{@build_num()}"
-
-    @build_name = @komp =>
-      "build: #{@build_num()}"
-
-    @org_name = @komp => @username()
-    @org_path = @komp => "/gh/#{@username()}"
-
-    @org_settings_name = @komp => "Organization settings"
-    @org_settings_path = @komp => "/gh/organizations/#{@username()}/settings"
-
-    @label_class = (page) =>
-      @komp =>
-        'label-active': @page() is page
-
-  crumb_path: (crumb) =>
-    @["#{crumb}_path"]
-
-  crumb_name: (crumb) =>
-    @["#{crumb}_name"]
-
-  projectPath: (username, project, branch) ->
-    project_name = "#{username}/#{project}"
-    path = "/gh/#{project_name}"
-    path += "/tree/#{encodeURIComponent(branch)}" if branch?
-    path


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -18,7 +18,7 @@

     width = 400 #width
     height = 400 #height
-    radius = 100 #radius
+    radius = 300 #radius

     color = d3.scale.ordinal()
       #.domain([1,10])


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -99,7 +99,7 @@
         y = c[1]
         h = Math.sqrt(x*x + y*y)
         #return "translate(" + (x/h * labelRadius) + "," + (y/h * labelRadius) + ")"
-        return "translate(" + labelRadius + "," + y + ")"#(y/h * labelRadius) + ")"
+        return "translate(" + (x >= 0 ? -labelRadius : labelRadius) + "," + y + ")"#(y/h * labelRadius) + ")"
         )
         .attr('fill', "#fff")
         .attr("text-anchor", "middle").text((d, i) ->


Agent pie: test globalizing variables

Agent pie: test globalizing variables

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -1,6 +1,12 @@
 class Dashing.FullpieAgent extends Dashing.Widget

     @accessor 'data'
+
+    ready: ->
+        @container()
+
+    container: ->
+        return $(@node).parent()

     onData: (data) ->
         #$(@node).fadeOut().fadeIn()
@@ -11,7 +17,7 @@
         # Remove any previous svg
         $(@node).children('svg').remove()
         # Define the container
-        container = $(@node).parent()
+        container = @container#$(@node).parent()

         #
         # CONFIG ZONE


Fix signature.

Fix signature.

--- src/control.coffee
+++ src/control.coffee
@@ -224,7 +224,7 @@

         _fetchServiceUnits: (service, filters, cancelToken) ->
             unitList = new models.UnitList [], pageSize: PAGE_SIZE, setComparator: true
-            unitList.filters = filters
+            if filters? then unitList.filters = filters
             unitList.setFilter 'service', service.id

             # MunicipalityIds come from explicit query parameters
@@ -286,7 +286,7 @@

         setService: (service, cancelToken) ->
             @services.set []
-            @addService service, null, cancelToken
+            @addService service, {}, cancelToken

         _search: (query, filters, cancelToken) ->
             sm.withDeferred (deferred) =>


Fix travel guide lockup bug when location accuracy not enough

--- src/p13n.coffee
+++ src/p13n.coffee
@@ -178,9 +178,10 @@
             catch e
                 return false

-        _handleLocation: (pos, positionObject) =>
+        _handleLocation: (pos, positionObject, successCallback, failureCallback) =>
             if pos.coords.accuracy > 10000
                 @trigger 'position_error'
+                failureCallback?()
                 return
             unless positionObject?
                 positionObject = new models.CoordinatePosition { isDetected: true, isPending: false }
@@ -193,6 +194,7 @@
                 @trigger 'position', positionObject
                 if not @get 'location_requested'
                     @set 'location_requested', true
+                successCallback?()
             if appSettings.user_location_delayed
                 setTimeout cb, 3000
             else
@@ -379,12 +381,11 @@
                 enableHighAccuracy: false
                 timeout: 30000
             navigator.geolocation.getCurrentPosition ((pos) =>
-                @_handleLocation(pos, positionModel)
-                successCallback?()
+                @_handleLocation(pos, positionModel, successCallback, failureCallback)
             ),  () =>
-                failureCallback?()
                 @trigger 'position_error'
                 @set 'location_requested', false
+                failureCallback?()
             , posOpts

         set: (attr, val) ->


Add custom notification for EINTR

Add custom notification for EINTR

--- src/pane.coffee
+++ src/pane.coffee
@@ -698,6 +698,8 @@
       addWarningWithPath('Unable to save file: Operation not supported on socket')
     else if error.code is 'EIO'
       addWarningWithPath('Unable to save file: I/O error writing file')
+    else if error.code is 'EINTR'
+      addWarningWithPath('Unable to save file: Interrupted system call')
     else if errorMatch = /ENOTDIR, not a directory '([^']+)'/.exec(error.message)
       fileName = errorMatch[1]
       atom.notifications.addWarning("Unable to save file: A directory in the path '#{fileName}' could not be written to")


Do not keep file contents in localFiles.

--- src/pipe.coffee
+++ src/pipe.coffee
@@ -97,8 +97,8 @@

             if digest isnt file.hash
                 file.hash = digest
-                file.content = content
-                BasePipe::change.call @, file
+                sendfile = @modifyFile file, "content", content
+                BasePipe::change.call @, sendfile
             else
                 null
         .then (res) =>


make sure testwatch keeps running if a test fails

--- Gulpfile.coffee
+++ Gulpfile.coffee
@@ -67,6 +67,7 @@
     }
     .on 'error', (e) ->
       if e.name is 'SyntaxError' then gutil.log e.stack else gutil.log e.message
+      @.emit 'end'

 # this is also ugly but it works
 gulp.task 'coverage', [ 'test' ], ->


Add pattern-matching for Dyalog class fields

--- grammars/apl.cson
+++ grammars/apl.cson
@@ -419,7 +419,6 @@
 		patterns: [{
 			begin: "(?<=\\s|^)((:)Class)\\s+('[^']*'?|[A-Za-z_∆⍙]+[\\w∆⍙¯]*)\\s*((:)\\s*(?:('[^']*'?|[A-Za-z_∆⍙]+[\\w∆⍙¯]*)\\s*)?)?(.*?)$"
 			end:   "(?<=\\s|^)((:)EndClass)(?=\\b)"
-			patterns: [{include: "$self"}]
 			beginCaptures:
 				0: {name: "meta.class.apl"}
 				1: {name: "keyword.control.class.apl"}
@@ -432,6 +431,32 @@
 			endCaptures:
 				1: {name: "keyword.control.class.apl"}
 				2: {name: "punctuation.definition.class.apl"}
+			patterns: [{
+
+				# Fields
+				name: "meta.field.apl"
+				begin: "(?<=\\s|^)(:)Field(?=\\s)"
+				end:   "(('[^']*'?|[A-Za-z_∆⍙]+[\\w∆⍙¯]*))\\s*(←.*)?$"
+				beginCaptures:
+					0: {name: "keyword.control.field.apl"}
+					1: {name: "punctuation.definition.field.apl"}
+				endCaptures:
+					1: {name: "entity.name.type.apl"}
+					2: {patterns: [{include: "#strings"}]}
+					3: {name: "entity.other.initial-value.apl", patterns: [{include: "#main"}]}
+				patterns: [{
+
+					# Access type (private/public)
+					name: "storage.modifier.access.${1:/downcase}.apl"
+					match: "(?<=\\s|^)(Public|Private)(?=\\s|$)"
+				}, {
+
+					# Shared/instanced field modifier
+					name: "storage.modifier.${1:/downcase}.apl"
+					match: "(?<=\\s|^)(Shared|Instance)(?=\\s|$)"
+				}]
+
+			}, {include: "$self"}]
 		}]


update patterns

update patterns


Adding support for "remote set-url"

Adding support for "remote set-url"

--- src/repo.coffee
+++ src/repo.coffee
@@ -187,6 +187,17 @@
     , (err, stdout, stderr) ->
       callback err

+  # Public: Add a remote URL.
+  #
+  # name     - String name of the remote.
+  # url      - String url of the remote.
+  # callback - Receives `(err)`
+  #
+  remote_add_url: (name, url, callback) ->
+    @git "remote set-url", {}, ["--add", name, url]
+    , (err, stdout, stderr) ->
+      callback err
+
   # Public: Remove a remote.
   #
   # name     - String name of the remote.


set key to iterator value

set key to iterator value

section may be duplicated

--- tutor/src/components/related-content-link.cjsx
+++ tutor/src/components/related-content-link.cjsx
@@ -24,9 +24,9 @@

       Comes from
-      {for content in @props.content
+      {for content, i in @props.content
         section = @sectionFormat(content.chapter_section)
-        <BrowseTheBook key={section} unstyled section={section}
+        <BrowseTheBook key={i} unstyled section={section}
           courseId={@props.courseId} onlyShowBrowsable={false} tabIndex={-1}
         >


change the url

change the url

--- src/horsify.coffee
+++ src/horsify.coffee
@@ -7,8 +7,8 @@

 module.exports = (robot) ->

-  robot.respond /horsify me (.*) (\w+)?/i, (msg) ->
-    horseface = "http://horseface.me/horsify?url="
+  robot.respond /horsify me (.*)/i, (msg) ->
+    horseface = "http://horsify.me/horsify?url="
     imagery = msg.match[1]

     if imagery.match /^https?:\/\//i


Add custom ENOGIT message

Add custom ENOGIT message

--- src/install.coffee
+++ src/install.coffee
@@ -185,7 +185,29 @@
           fs.removeSync(installDirectory)
           @logFailure()

-        callback("#{stdout}\n#{stderr}")
+        output = "#{stdout}\n#{stderr}"
+        if output.indexOf('code ENOGIT') isnt -1
+          output = """"
+            Failed to install #{pack.name} because Git was not found.
+
+            The #{pack.name} package has module dependencies that require Git to be installed.
+            You need install Git and make it available on your PATH environment variable in order
+            to install this package.
+          """
+          switch process.platform
+            when 'win32'
+              output += """
+
+                You can install Git by downloading and installing GitHub for Windows.
+                https://windows.github.com
+              """
+            when 'linux'
+              output += """
+
+                You can install Git from your distributions's package manager.
+              """
+
+        callback(output)

   getVisualStudioFlags: ->
     if vsVersion = config.getInstalledVisualStudioFlag()


Well done me. @ is kind of important

Well done me. @ is kind of important

--- src/teak.coffee
+++ src/teak.coffee
@@ -155,7 +155,7 @@
     }
     @postSignedRequest("/me/can_post.json", params, (jqXHR) =>
       carrotResponse = $.parseJSON(jqXHR.responseText)
-      uiCallbackHandler(carrotResponse.code == 200)
+      @uiCallbackHandler(carrotResponse.code == 200)
     )

   popupFeedPost: (objectInstanceId, objectProperties, callback, postMethod) ->
@@ -183,10 +183,10 @@
             if fbResponse and fbResponse.post_id
               @ajaxPost("#{@scheme}://parsnip.gocarrot.com/feed_dialog_post", {platform_id: carrotResponse.post_id})

-            uiCallbackHandler(carrotResponse, fbResponse)
+            @uiCallbackHandler(carrotResponse, fbResponse)
         )
       else
-        uiCallbackHandler(carrotResponse)
+        @uiCallbackHandler(carrotResponse)

   reportNotificationClick: (notifId, callback) ->
     @ajaxPost("#{@scheme}://parsnip.gocarrot.com/notification_click", {user_id: @udid, platform_id: notifId})
@@ -197,7 +197,7 @@
         response = $.parseJSON(jqXHR.responseText).response;
         if response.cascade && response.cascade.method == "sendRequest"
           @sendRequest(response.cascade.arguments.request_id, response.cascade.arguments.opts)
-        uiCallbackHandler(response)
+        @uiCallbackHandler(response)
     )

   # Available opts: object_type, object_id, object_properties, filters, suggestions, exclude_ids, max_recipients, data
@@ -234,10 +234,10 @@
               for receivingUser in fbResponse.to
                 @ajaxPost("#{@scheme}://parsnip.gocarrot.com/request_send", {platform_id: carrotResponse.request_id, posting_user_id: @udid, user_id: receivingUser})

-            uiCallbackHandler(carrotResponse, fbResponse)
+            @uiCallbackHandler(carrotResponse, fbResponse)
         )
       else
-        uiCallbackHandler(carrotResponse)
+        @uiCallbackHandler(carrotResponse)

   acceptRequest: (requestId, callback) ->
@@ -246,7 +246,7 @@
         response = $.parseJSON(jqXHR.responseText).response;
         if response.cascade && response.cascade.method == "sendRequest"
           @sendRequest(response.cascade.arguments.request_id, response.cascade.arguments.opts)
-        uiCallbackHandler(response)
+        @uiCallbackHandler(response)
     )

   getSignedRequest: (endpoint, query_params, callback) ->


addresses #1082 in JS driver

addresses #1082 in JS driver

--- drivers/javascript/ast.coffee
+++ drivers/javascript/ast.coffee
@@ -161,6 +161,46 @@
     info: ar () -> new Info {}, @
     sample: ar (count) -> new Sample {}, @, count

+    # Database operations
+
+    tableCreate: aropt (tblName, opts) -> new TableCreate opts, @, tblName
+    tableDrop: ar (tblName) -> new TableDrop {}, @, tblName
+    tableList: ar(-> new TableList {}, @)
+
+    table: aropt (tblName, opts) -> new Table opts, @, tblName
+
+    # Table operations
+
+    get: ar (key) -> new Get {}, @, key
+
+    getAll: (keysAndOpts...) ->
+        # Default if no opts dict provided
+        opts = {}
+        keys = keysAndOpts
+
+        # Look for opts dict
+        perhapsOptDict = keysAndOpts[keysAndOpts.length - 1]
+        if perhapsOptDict and
+                ((perhapsOptDict instanceof Object) and not (perhapsOptDict instanceof TermBase))
+            opts = perhapsOptDict
+            keys = keysAndOpts[0...(keysAndOpts.length - 1)]
+
+        new GetAll opts, @, keys...
+
+    # For this function only use `exprJSON` rather than letting it default to regular
+    # `expr`. This will attempt to serialize as much of the document as JSON as possible.
+    # This behavior can be manually overridden with either direct JSON serialization
+    # or ReQL datum serialization by first wrapping the argument with `r.expr` or `r.json`.
+    insert: aropt (doc, opts) -> new Insert opts, @, rethinkdb.exprJSON(doc)
+    indexCreate: varar(1, 2, (name, defun) ->
+        if defun?
+            new IndexCreate {}, @, name, funcWrap(defun)
+        else
+            new IndexCreate {}, @, name
+        )
+    indexDrop: ar (name) -> new IndexDrop {}, @, name
+    indexList: ar () -> new IndexList {}, @
+
 class DatumTerm extends RDBVal
     args: []
     optargs: {}
@@ -315,45 +355,9 @@
     tt: "DB"
     st: 'db'

-    tableCreate: aropt (tblName, opts) -> new TableCreate opts, @, tblName
-    tableDrop: ar (tblName) -> new TableDrop {}, @, tblName
-    tableList: ar(-> new TableList {}, @)
-
-    table: aropt (tblName, opts) -> new Table opts, @, tblName
-
 class Table extends RDBOp
     tt: "TABLE"
     st: 'table'
-
-    get: ar (key) -> new Get {}, @, key
-
-    getAll: (keysAndOpts...) ->
-        # Default if no opts dict provided
-        opts = {}
-        keys = keysAndOpts
-
-        # Look for opts dict
-        perhapsOptDict = keysAndOpts[keysAndOpts.length - 1]
-        if perhapsOptDict and
-                ((perhapsOptDict instanceof Object) and not (perhapsOptDict instanceof TermBase))
-            opts = perhapsOptDict
-            keys = keysAndOpts[0...(keysAndOpts.length - 1)]
-
-        new GetAll opts, @, keys...
-
-    # For this function only use `exprJSON` rather than letting it default to regular
-    # `expr`. This will attempt to serialize as much of the document as JSON as possible.
-    # This behavior can be manually overridden with either direct JSON serialization
-    # or ReQL datum serialization by first wrapping the argument with `r.expr` or `r.json`.
-    insert: aropt (doc, opts) -> new Insert opts, @, rethinkdb.exprJSON(doc)
-    indexCreate: varar(1, 2, (name, defun) ->
-        if defun?
-            new IndexCreate {}, @, name, funcWrap(defun)
-        else
-            new IndexCreate {}, @, name
-        )
-    indexDrop: ar (name) -> new IndexDrop {}, @, name
-    indexList: ar () -> new IndexList {}, @

     compose: (args, optargs) ->
         if @args[0] instanceof Db


Allow # inside initial packets

Allow # inside initial packets

--- src/lib/Fbp.coffee
+++ src/lib/Fbp.coffee
@@ -20,7 +20,7 @@
             @currentLine++ if char is "\n"

             # Commenting support. Ignore everything from # to newline
-            if char is '#'
+            if char is '#' and @currentElement isnt "initial"
                 @currentElement = "comment"
                 continue
             if @currentElement is "comment"


Refactor router checks to support testing

This is needed to be able to stub the routers

--- lib/activeroute.coffee
+++ lib/activeroute.coffee
@@ -1,5 +1,4 @@
-ir = Package['iron:router']
-fr = Package['meteorhacks:flow-router']
+fr = ir = null

 checkArgument = (arg) ->
   try
@@ -8,6 +7,8 @@
     throw new Error errorMessages.invalidArgument

 checkRouterPackages = ->
+  fr ?= Package['meteorhacks:flow-router']
+  ir ?= Package['iron:router']
   throw new Error errorMessages.noSupportedRouter unless ir or fr

 errorMessages =


document utility functions

document utility functions

--- src/tour.coffee
+++ src/tour.coffee
@@ -93,6 +93,9 @@

     dest

+  ###
+  # Builds an unique id for all DOM elements
+  ###
   buildID = do (idndx = 0) ->
     (prefix) -> "tourjs-#{if prefix then "#{prefix}-" else ''}#{++idndx}"

@@ -117,11 +120,20 @@
     height: window.innerHeight or document.documentElement.clientHeight
     width: window.innerWidth or document.documentElement.clientWidth

+  ###
+  # Checks if parameter is a function
+  ###
   isFunction = (f) -> typeof f is 'function'

+  ###
+  # Logs messages in the console when available (IE)
+  ###
   log = (message, type = 'warn') ->
     console[type](message) if console and console[type]

+  ###
+  # Renders SVGs in the DOM
+  ###
   renderSVG = (selector, options = {}) ->
     vector = VECTORS.querySelector(selector).cloneNode true
     vector.setAttribute 'class', 'tourjs-shape'
@@ -129,6 +141,9 @@
       vector.setAttribute 'transform', options.transform
     vector

+  ###
+  # Waits for DOM elements to be present
+  ###
   waitFor = (selector, timeout = 500, done) ->
     found = document.querySelector(selector)?
     error = false


Fix checking for coerce on types

Fix checking for coerce on types

--- src/type.coffee
+++ src/type.coffee
@@ -829,7 +829,7 @@

 # Coerce to a type.
 Cast::transformNode = (o) ->
-  return @computedType.coerce?(@expr)
+  return @computedType.coerce?(@expr) or @expr

 # Add `SP` computations and bring in heap views if needed.
 Code::transformNode = (o) ->
@@ -929,7 +929,7 @@
     return inlineMemcpy o, (if lval.isDeref?() then lval.first else @variable), @value, lty
   if lty instanceof PointerType and @context and (au = alignmentUnits lty.base) isnt 1
     @value = new Op('*', @value, new Value new Literal au)
-  else if lty.coerce
+  else if lty.coerce?
     @value = lty.coerce @value
   return


another update for t_frank and t_george

another update for t_frank and t_george

--- test/files/8_multikey.iced
+++ test/files/8_multikey.iced
@@ -16,16 +16,16 @@
 # the recipient's keys, and that the implicit `id` passes against fingerprint
 # assertions.

-frank_assertions = 'fingerprint://CD59215942691491 && fingerprint://D55DC088769D9ECD'
+frank_assertions = 'fingerprint://C6759000770E0C4D && fingerprint://DEFA06E52543544F'
 frank_subkeys = [
-  '6C73B0FE19AFFD34'
-  '897C73A9F116B425'
+  '5BDED63232F876C7'
+  '15B45E78C3852457'
 ]

-george_assertions = 'fingerprint://B9F004D1DB8867DB && fingerprint://B666072556732C93'
+george_assertions = 'fingerprint://84C091DD53BF666B && fingerprint://604556E877D86C22'
 george_subkeys = [
-  'B5FC0B1866F4132F'
-  'FC4CA7C9BDD7C348'
+  'A7F041D5A5F18962'
+  'B7C24335E23D3320'
 ]

 me = null


:art: Remove _ from linter-plus

:art: Remove _ from linter-plus

--- lib/linter-plus.coffee
+++ lib/linter-plus.coffee
@@ -13,38 +13,38 @@
     @commands = new Commands this

     # Private Stuff
-    @_subscriptions = new CompositeDisposable
-    @_emitter = new Emitter
-    @_editorLinters = new Map
-    @_messagesProject = new Map # Values set in editor-linter and consumed in views.render
-    @_linters = new Set # Values are pushed here from Main::consumeLinter
+    @subscriptions = new CompositeDisposable
+    @emitter = new Emitter
+    @editorLinters = new Map
+    @messagesProject = new Map # Values set in editor-linter and consumed in views.render
+    @linters = new Set # Values are pushed here from Main::consumeLinter

-    @_subscriptions.add atom.config.observe 'linter.showErrorInline', (showBubble) =>
+    @subscriptions.add atom.config.observe 'linter.showErrorInline', (showBubble) =>
       @views.setShowBubble(showBubble)
-    @_subscriptions.add atom.config.observe 'linter.showErrorPanel', (showPanel) =>
+    @subscriptions.add atom.config.observe 'linter.showErrorPanel', (showPanel) =>
       @views.setShowPanel(showPanel)
-    @_subscriptions.add atom.config.observe 'linter.underlineIssues', (underlineIssues) =>
+    @subscriptions.add atom.config.observe 'linter.underlineIssues', (underlineIssues) =>
       @views.setUnderlineIssues(underlineIssues)
-    @_subscriptions.add atom.config.observe 'linter.lintOnFly', (value) =>
+    @subscriptions.add atom.config.observe 'linter.lintOnFly', (value) =>
       @lintOnFly = value
-    @_subscriptions.add atom.project.onDidChangePaths =>
+    @subscriptions.add atom.project.onDidChangePaths =>
       @commands.lint()
-    @_subscriptions.add atom.workspace.onDidChangeActivePaneItem =>
+    @subscriptions.add atom.workspace.onDidChangeActivePaneItem =>
       @commands.lint()

-    @_subscriptions.add atom.workspace.observeTextEditors (editor) =>
+    @subscriptions.add atom.workspace.observeTextEditors (editor) =>
       currentEditorLinter = new EditorLinter @, editor
-      @_editorLinters.set editor, currentEditorLinter
-      @_emitter.emit 'observe-editor-linters', currentEditorLinter
+      @editorLinters.set editor, currentEditorLinter
+      @emitter.emit 'observe-editor-linters', currentEditorLinter
       currentEditorLinter.lint false
       editor.onDidDestroy =>
         currentEditorLinter.destroy()
-        @_editorLinters.delete editor
+        @editorLinters.delete editor

   addLinter: (linter) ->
     try
       if(Helpers.validateLinter(linter))
-        @_linters.add(linter)
+        @linters.add(linter)
     catch err
       atom.notifications.addError("Invalid Linter: #{err.message}", {
         detail: err.stack,
@@ -53,7 +53,7 @@

   deleteLinter: (linter) ->
     return unless @hasLinter(linter)
-    @_linters.delete(linter)
+    @linters.delete(linter)
     if linter.scope is 'project'
       @deleteProjectMessages(linter)
     else
@@ -63,42 +63,42 @@
     @views.render()

   hasLinter: (linter) ->
-    @_linters.has(linter)
+    @linters.has(linter)

   getLinters: ->
-    @_linters
+    @linters

   onDidChangeProjectMessages: (callback)->
-    @_emitter.on 'did-change-project-messages', callback
+    @emitter.on 'did-change-project-messages', callback

   getProjectMessages: ->
-    @_messagesProject
+    @messagesProject

   setProjectMessages: (linter, messages) ->
-    @_messagesProject.set(linter, Helpers.validateResults(messages))
-    @_emitter.emit 'did-change-project-messages', @_messagesProject
+    @messagesProject.set(linter, Helpers.validateResults(messages))
+    @emitter.emit 'did-change-project-messages', @messagesProject
     @views.render()

   deleteProjectMessages: (linter) ->
-    @_messagesProject.delete(linter)
-    @_emitter.emit 'did-change-project-messages', @_messagesProject
+    @messagesProject.delete(linter)
+    @emitter.emit 'did-change-project-messages', @messagesProject
     @views.render()

   getActiveEditorLinter: ->
     return @getEditorLinter atom.workspace.getActiveTextEditor()

   getEditorLinter: (editor) ->
-    return @_editorLinters.get editor
+    return @editorLinters.get editor

   eachEditorLinter: (callback) ->
-    @_editorLinters.forEach(callback)
+    @editorLinters.forEach(callback)

   observeEditorLinters: (callback) ->
     @eachEditorLinter callback
-    @_emitter.on 'observe-editor-linters', callback
+    @emitter.on 'observe-editor-linters', callback

   deactivate: ->
-    @_subscriptions.dispose()
+    @subscriptions.dispose()
     @eachEditorLinter (linter) ->
       linter.destroy()
     @views.destroy()


Adding a comment to identify a problem

Adding a comment to identify a problem

--- lib/promiseSftp.coffee
+++ lib/promiseSftp.coffee
@@ -343,7 +343,7 @@
           continue
         result = result
         .then () =>
-          @mkdir(currPath, false, attributes)
+          @mkdir(currPath, false, attributes) # this is totes broken, currPath is the final dir by the time it runs :(. can haz issues?
         .catch (err) ->
           if err.code != ERROR_CODES.FAILURE && err.code != ERROR_CODES.FILE_ALREADY_EXISTS
             throw err


use tempfile with shorter name

use tempfile with shorter name

--- lib/remote-atom.coffee
+++ lib/remote-atom.coffee
@@ -21,7 +21,7 @@
             @parse_chunk(chunk)

     make_tempfile: ()->
-        @tempfile = path.join(os.tmpdir(), randomstring(20), @remoteAddress, @token)
+        @tempfile = path.join(os.tmpdir(), randomstring(10), @token)
         console.log "[ratom] create #{@tempfile}"
         dirname = path.dirname(@tempfile)
         mkdirp.sync(dirname)


Fix #402 - Use TextEditor#getLastCursor over TextEditor#getCursor

--- lib/script-view.coffee
+++ lib/script-view.coffee
@@ -100,7 +100,7 @@
     # Selection and Line Number Based runs both benefit from knowing the current line
     # number
     unless argType == 'File Based'
-      cursor = editor.getCursor()
+      cursor = editor.getLastCursor()
       codeContext.lineNumber = cursor.getScreenRow() + 1

     return codeContext


Cleaned up more from last commit removing unnecessary default arguments

--- lib/script-view.coffee
+++ lib/script-view.coffee
@@ -20,7 +20,7 @@

   initialize: (serializeState) ->
     # Bind commands
-    atom.workspaceView.command "script:run", => @start(configure='hide')
+    atom.workspaceView.command "script:run", => @start()
     atom.workspaceView.command "script:run-options", => @runOptions()
     atom.workspaceView.command "script:toggle-options", => @toggleConfigureOptions()
     atom.workspaceView.command "script:save-options", => @saveOptions()
@@ -35,7 +35,7 @@

   serialize: ->

-  start: (configure) ->
+  start: ->
     # Get current editor
     editor = atom.workspace.getActiveEditor()

@@ -44,7 +44,7 @@
       return

     @resetView()
-    @toggleConfigureOptions(command=configure)
+    @toggleConfigureOptions(command='hide')
     info = @setup(editor)
     if info then @run(info.command, info.args)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -125,7 +125,7 @@

   @name meter
   @methodOf Metered#
-  @param {Object} options The meter configu
+  @param {Object} options The meter configuration options
   ###
   meter: (options={}) ->
     Object.reverseMerge options,


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -170,8 +170,9 @@
   player.include Metered

   player.meter 'health'
+    show: false

-  player.
+  player.showMet
   </pre></code>

   @name meter


removing unused execute functions

removing unused execute functions

--- src/opcodes.coffee
+++ src/opcodes.coffee
@@ -517,11 +517,11 @@
   51: new root.ArrayLoadOpcode 'baload'
   52: new root.ArrayLoadOpcode 'caload'
   53: new root.ArrayLoadOpcode 'saload'
-  54: new root.StoreVarOpcode 'istore', { execute: (rs) -> rs.put_cl(@var_num,rs.pop()) }
-  55: new root.StoreVarOpcode 'lstore', { execute: (rs) -> rs.put_cl2(@var_num,rs.pop2()) }
-  56: new root.StoreVarOpcode 'fstore', { execute: (rs) -> rs.put_cl(@var_num,rs.pop()) }
-  57: new root.StoreVarOpcode 'dstore', { execute: (rs) -> rs.put_cl2(@var_num,rs.pop2()) }
-  58: new root.StoreVarOpcode 'astore', { execute: (rs) -> rs.put_cl(@var_num,rs.pop()) }
+  54: new root.StoreVarOpcode 'istore'
+  55: new root.StoreVarOpcode 'lstore'
+  56: new root.StoreVarOpcode 'fstore'
+  57: new root.StoreVarOpcode 'dstore'
+  58: new root.StoreVarOpcode 'astore'
   59: new root.StoreOpcode 'istore_0'
   60: new root.StoreOpcode 'istore_1'
   61: new root.StoreOpcode 'istore_2'


LRSView now retains scroll position when withdrawing and re-adding.

--- src/view.coffee
+++ src/view.coffee
@@ -277,6 +277,8 @@
 		@

 	withdraw: ->
+		scrollTop = @el.scrollTop
+
 		parentNode = @el.parentNode
 		nextSibling = @el.nextSibling
 		@el.parentNode.removeChild(@el)
@@ -286,6 +288,8 @@
 				parentNode.insertBefore(@el, nextSibling)
 			else
 				parentNode.appendChild(@el)
+
+			@el.scrollTop = scrollTop

 	remove: ->
 		@el.parentNode.removeChild(@el)


change to Keyword tutorial task

change to Keyword tutorial task

--- tutorial-steps.coffee
+++ tutorial-steps.coffee
@@ -31,7 +31,7 @@
     image: 'tutorial/continue.jpg'
     alt: 'Tag all the illustrations'
     content: '''
-                 Add keywords to describe each illustration overall and separate each term with semicolons. You can find examples with the Help () button. Keywords can be drawn from the text or be words you would find helpful if searching for images online. Keywords make images more findable.
+                 Add keywords to describe the highlighted illustration. Separate each term with a semicolon. You can find examples with the Help () button. Keywords can be drawn from the text or be words you would find helpful if searching for images online. Keywords make images more findable.
                  '''

   }]


[fix] Upgrade deprecated express API. Fixes #703

--- server/controllers/konnectors.coffee
+++ server/controllers/konnectors.coffee
@@ -87,7 +87,7 @@
     import: (req, res, next) ->
         # Don't run a new import if an import is already running.
         if req.konnector.isImporting
-            res.send 400, message: 'konnector is importing'
+            res.status(400).send message: 'konnector is importing'
         else

             # Extract date information.


Ensure "globals" exists.

Ensure "globals" exists.

--- src/runWith.coffee
+++ src/runWith.coffee
@@ -19,7 +19,7 @@
     -> @cdExists = fsExists containingDir
     -> throw new Error "Supplied cwd #{cwd} does not exist." unless @cwdExists
     -> throw new Error "Supplied containing dir #{containingDir} does not exist." unless @cdExists
-    -> @config = if @configFilePath then require @configFilePath else {}
+    -> @config = if @configFilePath then require @configFilePath else globals:{}
     -> @templateDir = detectSeries templateDirs, fsExists
     -> throw new Error "Cannot find template in #{templateDirs.join ", or "}." unless @templateDir
     -> log "Using template #{@templateDir}."


changed special keybind to bind to html instead of body as body was losing focus and the browser was getting meta events that the editor should have.

--- editor.coffee
+++ editor.coffee
@@ -1,6 +1,6 @@
 #members
 @input = $('#input')
-@body = $('body')
+@html = $('html')
 @debugDiv = $('#debug')
 @editor = ace.edit "input"
 @output = ace.edit "output"
@@ -154,7 +154,7 @@
 		        )
 		    )
 		)
-		@body.append dialog
+		@html.append dialog
 		dialog.center()

@@ -228,7 +228,7 @@
 		@timer = null
 	@timer = setTimeout ( ->compile() ), 500

-@body.bind 'keypress keydown', specialKeyBind
+@html.bind 'keypress keydown', specialKeyBind

 getConfig = (cb) ->
 	sendReq {action:"getConfig"}, 'POST', (data) ->


updated v1_loader to work with new tags

updated v1_loader to work with new tags

--- src/art/engine/file/v1_loader.coffee
+++ src/art/engine/file/v1_loader.coffee
@@ -88,9 +88,9 @@
     pego: -> new Element
     art_stencil_shape: -> new Element
     art_bitmap: (tag, loader) ->
-      bitmap_id = tag.attributes["bitmap_id"]
+      bitmap_id = tag.attrs["bitmap_id"]
       bitmap = loader.bitmaps[bitmap_id]
-      new Elements.Bitmap bitmap:bitmap
+      new Elements.BitmapElement bitmap:bitmap

   # decode all bitmaps through a seriese of asynchronous callBacks
   # after all is done, callBack is called with the processed bitmaps object (hash of IDs to ArtBitmaps)
@@ -109,8 +109,8 @@
   decodeBitmapsTag: (bitmapsTag, callBack) ->
     bitmaps = {}
     for tag in bitmapsTag.tags
-      id = tag.attributes["bitmap_id"] | 0
-      bitmapData = tag.attributes["pixel_data"]
+      id = tag.attrs["bitmap_id"] | 0
+      bitmapData = tag.attrs["pixel_data"]
       bitmaps[id] = bitmapData

     @decodeBitmaps bitmaps, Object.keys(bitmaps), 0, callBack
@@ -154,7 +154,7 @@

     for child in childrenTag.tags
       # route["normal"].push @createElementFromTag child, context, parent
-      route[child.attributes.stack_mode || "normal"].push @createElementFromTag child, parent
+      route[child.attrs.stack_mode || "normal"].push @createElementFromTag child, parent

     parent.setChildren children

@@ -186,12 +186,12 @@
         @log "WARNING: unsupported layout #{layoutModes[layout]} for #{channel} loc=#{location} size=#{size}"

   decodeLayout: (object, tag, parent) ->
-    xLayout = (tag.attributes.x_layout_mode || layoutModes.leftAddWidthFixed) | 0
-    yLayout = (tag.attributes.y_layout_mode || layoutModes.leftAddWidthFixed) | 0
-    xVal = (tag.attributes.x_val || 0) - 0
-    yVal = (tag.attributes.y_val || 0) - 0
-    wVal = (tag.attributes.w_val || 0) - 0
-    hVal = (tag.attributes.h_val || 0) - 0
+    xLayout = (tag.attrs.x_layout_mode || layoutModes.leftAddWidthFixed) | 0
+    yLayout = (tag.attrs.y_layout_mode || layoutModes.leftAddWidthFixed) | 0
+    xVal = (tag.attrs.x_val || 0) - 0
+    yVal = (tag.attrs.y_val || 0) - 0
+    wVal = (tag.attrs.w_val || 0) - 0
+    hVal = (tag.attrs.h_val || 0) - 0

     location = {}
     size = {}
@@ -207,12 +207,12 @@

   createElementFromTag: (tag, parent = null) ->
     object = @createElement tag
-    object.axis = point(tag.attributes.handle || point(.5, .5))  # default for V1
+    object.axis = point(tag.attrs.handle || point(.5, .5))  # default for V1

     @decodeLayout object, tag, parent

     userProperties = {}
-    for k, v of tag.attributes
+    for k, v of tag.attrs
       switch k
         when "name" then object.name = v
         when "matrix" then object.elementToParentMatrix = matrix v


even better, just is it anything

even better, just is it anything

--- website/script.coffee
+++ website/script.coffee
@@ -4,8 +4,7 @@
 setUp=()->
 	$('#cont').html "
loading...
"
 setUp()
-type = typeof window.Worker
-if type in ['function','object']
+unless typeof window.Worker is 'undefined'
 	setUp()
 	communist.ajax('README.md',(d)->
 		d.slice(66)


[#121420841] Improved streaming of data between Sage and CODAP.

We found that Sage modeler was (a) issuing createCases requests to CODAP sometimes sending zero cases, and (b) updating a row in the
parent table unnecessarily. The value that was being updated was the count of the child cases, a value that could be captured with a formula. So, we change the definition of the attribute to include the formula and comment out the update altogether.

There is still a lot of unnecessary chatter between Sage and CODAP, the removal of which could improve things some more. For example, CODAP
 sends many clearUndo and clearRedo requests, that it should not. But, on a reasonable machine, CODAP can keep up with graph updates, while a
 run is executing and the slider is being manipulated.

--- src/code/models/codap-connect.coffee
+++ src/code/models/codap-connect.coffee
@@ -57,6 +57,7 @@
               {
                 name: tr '~CODAP.SIMULATION.STEPS'
                 type: 'numeric'
+                formula: 'count(Steps)'
                 description: tr '~CODAP.SIMULATION.STEPS.DESCRIPTION'
                 precision: 0
               }
@@ -75,7 +76,7 @@
       action: 'openCase'
       args: {
         collection: 'Simulation',
-        values: [null, 0]
+        values: [null, null]
       }
     }, (result) =>
       if result?.success
@@ -132,24 +133,25 @@
       _.each frame.nodes, (n) -> sample.push n.value
       sample

-    # Send the data
-    @codapPhone.call
-      action: 'createCases'
-      args: {
-        collection: 'Samples',
-        parent: @currentCaseID,
-        values: sampleData
-      }
+    # Send the data, if any
+    if sampleData.length > 0
+      @codapPhone.call
+        action: 'createCases'
+        args: {
+          collection: 'Samples',
+          parent: @currentCaseID,
+          values: sampleData
+        }

     # Update the parent case with the current number of steps
-    @stepsInCurrentCase += sampleData.length
-    @codapPhone.call
-      action: 'updateCase'
-      args: {
-        collection: 'Simulation',
-        caseID: @currentCaseID
-        values: [null, @stepsInCurrentCase]
-      }
+#    @stepsInCurrentCase += sampleData.length
+#    @codapPhone.call
+#      action: 'updateCase'
+#      args: {
+#        collection: 'Simulation',
+#        caseID: @currentCaseID
+#        values: [null, @stepsInCurrentCase]
+#      }

   _sendUndoToCODAP: ->
     @codapPhone.call


#713: destructuring assignment is no longer statement and correctly returns RHS value

--- nodes.coffee
+++ nodes.coffee
@@ -247,7 +247,7 @@
   # Break and continue must be treated as pure statements -- they lose their
   # meaning when wrapped in a closure.
   isStatement: ->
-    @value is 'break' or @value is 'continue' or @value is 'debugger'
+    @value in ['break', 'continue', 'debugger']
   isPureStatement: LiteralNode::isStatement

   isComplex: NO
@@ -822,26 +822,18 @@
   isValue: ->
     @variable instanceof ValueNode

-  makeReturn: ->
-    if @isStatement()
-      return new Expressions [this, new ReturnNode(@variable)]
-    else
-      super()
-
-  isStatement: ->
-    @isValue() and (@variable.isArray() or @variable.isObject())
-
   # Compile an assignment, delegating to `compilePatternMatch` or
   # `compileSplice` if appropriate. Keep track of the name of the base object
   # we've been assigned to, for correct internal references. If the variable
   # has not been seen yet within the current scope, declare it.
   compileNode: (o) ->
+    if isValue = @isValue()
+      return @compilePatternMatch(o) if @variable.isArray() or @variable.isObject()
+      return @compileSplice(o) if @variable.isSplice()
     top    = del o, 'top'
-    return   @compilePatternMatch(o) if @isStatement(o)
-    return   @compileSplice(o) if @isValue() and @variable.isSplice()
     stmt   = del o, 'asStatement'
     name   = @variable.compile(o)
-    end    = if @isValue() then @variable.last.replace(@LEADING_DOT, '') else name
+    end    = if isValue then @variable.last.replace(@LEADING_DOT, '') else name
     match  = name.match(@PROTO_ASSIGN)
     proto  = match and match[1]
     if @value instanceof CodeNode
@@ -849,7 +841,7 @@
       @value.proto = proto if proto
     val = @value.compile o
     return "#{name}: #{val}" if @context is 'object'
-    o.scope.find name unless @isValue() and (@variable.hasProperties() or @variable.namespaced)
+    o.scope.find name unless isValue and (@variable.hasProperties() or @variable.namespaced)
     val = "#{name} = #{val}"
     return "#{@tab}#{val};" if stmt
     if top or @parenthetical then val else "(#{val})"
@@ -859,37 +851,49 @@
   # See the [ECMAScript Harmony Wiki](http://wiki.ecmascript.org/doku.php?id=harmony:destructuring)
   # for details.
   compilePatternMatch: (o) ->
-    valVar        = o.scope.freeVariable 'ref'
-    value         = if @value.isStatement(o) then ClosureNode.wrap(@value) else @value
-    assigns       = ["#{@tab}#{valVar} = #{ value.compile(o) };"]
-    o.top         = true
-    o.asStatement = true
-    splat         = false
-    for obj, i in @variable.base.objects
+    if (value = @value).isStatement o then value = ClosureNode.wrap value
+    {objects} = @variable.base
+    return value.compile o unless objects.length
+    if (isObject = @variable.isObject()) and objects.length is 1
+      # Unroll simplest cases: `{v} = x` -> `v = x.v`
+      if (obj = objects[0]) instanceof AssignNode
+        {variable: {base: idx}, value: obj} = obj
+      else
+        idx = obj
+      value = new ValueNode value unless value instanceof ValueNode
+      accessClass = if IDENTIFIER.test idx.value then AccessorNode else IndexNode
+      value.properties.push new accessClass idx
+      return new AssignNode(obj, value).compile o
+    top     = del o, 'top'
+    otop    = merge o, top: yes
+    valVar  = o.scope.freeVariable 'ref'
+    assigns = ["#{valVar} = #{ value.compile o }"]
+    splat   = false
+    for obj, i in objects
       # A regular array pattern-match.
       idx = i
-      if @variable.isObject()
+      if isObject
         if obj instanceof AssignNode
           # A regular object pattern-match.
           [obj, idx] = [obj.value, obj.variable.base]
         else
           # A shorthand `{a, b, c} = val` pattern-match.
           idx = obj
-      if not (obj instanceof ValueNode or obj instanceof SplatNode)
+      unless obj instanceof ValueNode or obj instanceof SplatNode
         throw new Error 'pattern matching must use only identifiers on the left-hand side.'
-      isString = idx.value and idx.value.match IS_STRING
-      accessClass = if isString or @variable.isArray() then IndexNode else AccessorNode
-      if obj instanceof SplatNode and not splat
+      accessClass = if isObject and IDENTIFIER.test(idx.value) then AccessorNode else IndexNode
+      if not splat and obj instanceof SplatNode
         val = literal obj.compileValue o, valVar,
-          (oindex = indexOf(@variable.base.objects, obj)),
-          (olength = @variable.base.objects.length) - oindex - 1
+          (oindex  = indexOf objects, obj),
+          (olength = objects.length) - oindex - 1
         splat = true
       else
         idx = literal(if splat then "#{valVar}.length - #{olength - idx}" else idx) if typeof idx isnt 'object'
-        val = new ValueNode(literal(valVar), [new accessClass(idx)])
-      assigns.push(new AssignNode(obj, val).compile(o))
-    code = assigns.join("\n")
-    code
+        val = new ValueNode literal(valVar), [new accessClass idx]
+      assigns.push new AssignNode(obj, val).compile otop
+    assigns.push valVar unless top
+    code = assigns.join ', '
+    if top or @parenthetical then code else "(#{code})"

   # Compile the assignment from an array splice literal, using JavaScript's
   # `Array#splice` method.
@@ -963,8 +967,7 @@
     return "(#{utility 'bind'}(#{func}, #{@context}))" if @bound
     if top then "(#{func})" else func

-  topSensitive: ->
-    true
+  topSensitive: YES

   # Short-circuit traverseChildren method to prevent it from crossing scope boundaries
   # unless crossScope is true
@@ -1083,8 +1086,7 @@
     @returns = true
     this

-  topSensitive: ->
-    true
+  topSensitive: YES

   # The main difference from a JavaScript *while* is that the CoffeeScript
   # *while* can be used as a part of a larger expression -- while loops may


textMatch -> text (capser-chai changes)

textMatch -> text (capser-chai changes)

--- test/all.coffee
+++ test/all.coffee
@@ -105,7 +105,7 @@
           casper.waitForSelector 'h1', ->
             /mocha-casperjs/.should.matchTitle
           casper.then ->
-            'h1'.should.have.textMatch 'Hello World!'
+            'h1'.should.have.text 'Hello World!'
       , done


Test invalid user data

Test invalid user data

--- test/bot.coffee
+++ test/bot.coffee
@@ -289,6 +289,14 @@
     should.equal user.email_address, @stubs.user.profile.email
     should.equal user.slack.misc, undefined

+  it 'Should ignore user data which have no id', ->
+    invalid_user =
+      name: 'username'
+
+    @slackbot.userChange(invalid_user)
+    users = @slackbot.robot.brain.data.users
+    should.equal Object.keys(users).length, 0
+
   it 'Should load users data from web api', ->
     @slackbot.loadUsers(null, @stubs.responseUsersList)


resolve bug

resolve bug

--- test/lab.coffee
+++ test/lab.coffee
@@ -1,11 +1,11 @@
 Promise = require '../src/yaku'
-utils = require '../src/utils'

-list = [
-    -> utils.sleep 10, 1
-    -> throw 10
-    -> utils.sleep 10, 3
-]
+p = Promise.resolve 10

-utils.async 2, list
-.catch (err) -> err
+p.then(->)
+
+p.then (v) ->
+    console.log v
+    pp = new Promise ->
+    pp.id = 'pp'
+    pp


ssh: change text slightly

ssh: change text slightly

--- src/smc-webapp/account_ssh_keys.cjsx
+++ src/smc-webapp/account_ssh_keys.cjsx
@@ -27,7 +27,8 @@

             To SSH into a project, use the following username@host:

[projectIdWithoutDashes]@ssh.cocalc.com

-            A project's Id can be found in it's settings panel.
+            The project id without dashes can be found in the part of project settings about SSH keys.
+            To SSH between projects, use
[projectIdWithoutDashes]@ssh

     render: ->


Support Error serialization

Support Error serialization

--- lib/Format.coffee
+++ lib/Format.coffee
@@ -26,6 +26,8 @@
     return if key then key + '=' + obj else obj
   if obj instanceof Buffer
     return if key then key + '=' + obj.toString('base64') else obj.toString('base64')
+  if obj instanceof Error
+    return obj.message or obj
   msg = ''
   keys = Object.keys(obj)
   length = keys.length


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine.coffee
+++ test/engine.coffee
@@ -187,7 +187,7 @@

   engine.setState nextState

-  # Test state chage
+  # Test state change event
   engine.bind "stateEntered", ->
     ok true
   engine.bind "stateExited", ->


more lenient engine test

more lenient engine test

--- test/engine.coffee
+++ test/engine.coffee
@@ -86,7 +86,7 @@
   setTimeout ->
     engine.pause()
     age = engine.age()
-    ok(64 <= age <= 68, "Engine ran #{age} steps in #{milliseconds}ms")
+    ok(62 <= age <= 68, "Engine ran #{age} steps in #{milliseconds}ms")

     start()
   , milliseconds


fix 'Uncaught ReferenceError: done is not defined'

--- test/errors.coffee
+++ test/errors.coffee
@@ -131,7 +131,7 @@
             " but found that it increased by #{increase_in_max_listeners}" +
             " (from #{starting_max_listeners} to #{stream._maxListeners})"
           done()
-    describe 'does not crash if there are no maxListeners', ->
+    it 'does not crash if there are no maxListeners', (done) ->
       stream = new Understream([]).stream()
       delete stream._maxListeners
       new_stream = new Understream(stream).each(-> ).run (err) ->


:fire: Remove top-level self-API deprecations

--- lib/linter.coffee
+++ lib/linter.coffee
@@ -4,7 +4,6 @@
 EditorLinter = require './editor-linter'
 Helpers = require './helpers'
 Commands = require './commands'
-{deprecate} = require 'grim'

 class Linter
   # State is an object by default; never null or undefined
@@ -62,26 +61,6 @@
   onDidUpdateMessages: (callback) ->
     @messages.onDidUpdateMessages(callback)

-  onDidChangeMessages: (callback) ->
-    deprecate("Linter::onDidChangeMessages is deprecated, use Linter::onDidUpdateMessages instead")
-    @onDidUpdateMessages(callback)
-
-  onDidChangeProjectMessages: (callback) ->
-    deprecate("Linter::onDidChangeProjectMessages is deprecated, use Linter::onDidChangeMessages instead")
-    @onDidChangeMessages(callback)
-
-  getProjectMessages: ->
-    deprecate("Linter::getProjectMessages is deprecated, use Linter::getMessages instead")
-    @getMessages()
-
-  setProjectMessages: (linter, messages) ->
-    deprecate("Linter::setProjectMessages is deprecated, use Linter::setMessages instead")
-    @setMessages(linter, messages)
-
-  deleteProjectMessages: (linter) ->
-    deprecate("Linter::deleteProjectMessages is deprecated, use Linter::deleteMessages instead")
-    @deleteMessages(linter)
-
   getActiveEditorLinter: ->
     @editors.ofActiveTextEditor()


:art: :fire: duplicate code

:art: :fire: duplicate code

--- lib/scroll.coffee
+++ lib/scroll.coffee
@@ -5,6 +5,11 @@
     true
   isRecordable: ->
     false
+
+  getPixelCursor: (which) ->
+    # which is `top` or `left`
+    point = @editor.getCursorScreenPosition()
+    @editorElement.pixelPositionForScreenPosition(point)[which]

   constructor: (@vimState, @options={}) ->
     {@editorElement} = @vimState
@@ -56,10 +61,6 @@
 class ScrollCursor extends Scroll
   keepCursor: false
   @extend()
-  constructor: ->
-    super
-    point = @editor.getCursorScreenPosition()
-    @pixelCursorTop = @editorElement.pixelPositionForScreenPosition(point).top

   execute: ->
     @moveToFirstCharacterOfLine() unless @keepCursor
@@ -77,7 +78,7 @@
     not (@rows.last is @rows.final)

   getScrollTop: ->
-    @pixelCursorTop - @getOffSetPixelHeight()
+    @getPixelCursor('top') - @getOffSetPixelHeight()

 class ScrollCursorToBottom extends ScrollCursor
   @extend()
@@ -85,7 +86,7 @@
     not (@rows.first is 0)

   getScrollTop: ->
-    @pixelCursorTop - (@editor.getHeight() - @getOffSetPixelHeight(1))
+    @getPixelCursor('top') - (@editor.getHeight() - @getOffSetPixelHeight(1))

 class ScrollCursorToMiddle extends ScrollCursor
   @extend()
@@ -93,7 +94,7 @@
     true

   getScrollTop: ->
-    @pixelCursorTop - (@editor.getHeight() / 2)
+    @getPixelCursor('top') - (@editor.getHeight() / 2)

 class ScrollCursorToTopLeave extends ScrollCursorToTop
   @extend()
@@ -111,24 +112,19 @@
 # -------------------------
 class ScrollHorizontal extends Scroll
   @extend()
-  constructor: ->
-    super
-    cursorPos = @editor.getCursorScreenPosition()
-    @pixelCursorTop = @editorElement.pixelPositionForScreenPosition(cursorPos).left
-
   putCursorOnScreen: ->
     @editor.scrollToCursorPosition({center: false})

 class ScrollCursorToLeft extends ScrollHorizontal
   @extend()
   execute: ->
-    @editor.setScrollLeft(@pixelCursorTop)
+    @editor.setScrollLeft(@getPixelCursor('left'))
     @putCursorOnScreen()

 class ScrollCursorToRight extends ScrollHorizontal
   @extend()
   execute: ->
-    @editor.setScrollRight(@pixelCursorTop)
+    @editor.setScrollRight(@getPixelCursor('left'))
     @putCursorOnScreen()

 module.exports = {


Line 188 - added or '' Removed trailing spaces

--- lib/server.coffee
+++ lib/server.coffee
@@ -54,7 +54,7 @@
         ref = firebase.child "users/#{user}/developer/listener"
         pkg.modified = Date.now()
         ref.set pkg
-
+

   # Middleware
   (req, res, next) ->
@@ -119,7 +119,7 @@
       ===
       #{msg}
       === END ERROR ===
-
+
       """

@@ -170,7 +170,7 @@
           pkg = read_package id, version
           return [] unless pkg
           js = []
-
+
           if pkg.dependencies
             js = js.concat(build(k, v)) for k, v of pkg.dependencies

@@ -185,7 +185,7 @@

           paths = ['main', 'header', 'footer', 'app']
           add path.join(root, "#{p}.coffee") for p in paths
-          add path.join(root, pkg.main?.js)
+          add path.join(root, pkg.main?.js or '')

           if pkg.type == 'app' and pkg.pages
             for type of pkg.pages


removed same serv check

removed same serv check

--- lib/server.coffee
+++ lib/server.coffee
@@ -109,8 +109,6 @@
 			view += '\tvar msg=' + JSON.stringify(JSON.stringify(body)) + ';\n'
 			if state.redirect_uri
 				redirect_infos = Url.parse state.redirect_uri
-				if redirect_infos.hostname == config.url.hostname
-					return next new check.Error 'OAuth.redirect url must NOT be "' + config.url.host + '"'
 				view += '\tdocument.location.href = "' + state.redirect_uri + '#oauthio=" + encodeURIComponent(msg);\n'
 			else
 				view += '\tvar opener = window.opener || window.parent.window.opener;\n'