Activity: sidebar all/new title side links added

--- client/Social/Activity/sidebar/activitysidebar.coffee
+++ client/Social/Activity/sidebar/activitysidebar.coffee
@@ -195,9 +195,12 @@
       itemClass  : SidebarTopicItem
       dataPath   : 'popularTopics'
       delegate   : this
+      headerLink : new CustomLinkView
+        title    : 'ALL'
+        href     : KD.utils.groupifyLink '/Activity/Topic/All'
       dataSource : (callback) ->
         KD.singletons.socialapi.channel.fetchPopularTopics
-          limit  : 3
+          limit  : 5
         , callback

@@ -209,6 +212,9 @@
       itemClass  : SidebarTopicItem
       dataPath   : 'followedChannels'
       delegate   : this
+      headerLink : new CustomLinkView
+        title    : 'ALL'
+        href     : KD.utils.groupifyLink '/Activity/Topic/All'
       dataSource : (callback) ->
         KD.singletons.socialapi.channel.fetchFollowedChannels
           limit : 5
@@ -223,6 +229,9 @@
       itemClass  : SidebarPinnedItem
       dataPath   : 'pinnedMessages'
       delegate   : this
+      headerLink : new CustomLinkView
+        title    : 'ALL'
+        href     : KD.utils.groupifyLink '/Activity/Post/All'
       dataSource : (callback) ->
         KD.singletons.socialapi.channel.fetchPinnedMessages
           limit : 5
@@ -237,19 +246,24 @@
       itemClass  : SidebarMessageItem
       dataPath   : 'privateMessages'
       delegate   : this
+      headerLink : new CustomLinkView
+        title    : 'NEW'
+        href     : KD.utils.groupifyLink '/Activity/Message/New'
       dataSource : (callback) ->
         KD.singletons.socialapi.message.fetchPrivateMessages
-          limit : 3
-          skip  : 0
+          limit : 5
         , callback

   addChat: ->

     @wrapper.addSubView @sections.chat = new ActivitySideView
-      title    : 'Chat'
-      cssClass : 'chat users'
-      itemClass : SidebarChatMemberItem
-      delegate   : this
-      dataSource : (callback) ->
-        KD.getGroup().fetchNewestMembers {}, {limit : 10, skip : 0}, callback
+      title      : 'Chat'
+      cssClass   : 'chat users'
+      itemClass  : SidebarChatMemberItem
+      delegate   : this
+      headerLink : new CustomLinkView
+        title    : 'NEW'
+        href     : KD.utils.groupifyLink '/Activity/Chat/New'
+      dataSource : (callback) ->
+        KD.getGroup().fetchNewestMembers {}, {limit : 10}, callback


Client: fix css problem on profile page

Client: fix css problem on profile page

--- client/members/lib/contentdisplays/profileview.coffee
+++ client/members/lib/contentdisplays/profileview.coffee
@@ -1,25 +1,26 @@
-kd = require 'kd'
-globals = require 'globals'
-KDCustomHTMLView = kd.CustomHTMLView
-KDModalView = kd.ModalView
-KDNotificationView = kd.NotificationView
-ExternalProfileView = require './externalprofileview'
+kd                         = require 'kd'
+globals                    = require 'globals'
+KDCustomHTMLView           = kd.CustomHTMLView
+KDModalView                = kd.ModalView
+KDNotificationView         = kd.NotificationView
+ExternalProfileView        = require './externalprofileview'
 ProfileContentEditableView = require './profilecontenteditableview'
-remote = require('app/remote').getInstance()
-globals = require 'globals'
-proxifyUrl = require 'app/util/proxifyUrl'
-checkFlag = require 'app/util/checkFlag'
-isMine = require 'app/util/isMine'
-whoami = require 'app/util/whoami'
-isGuest = require 'app/util/isGuest'
-MemberMailLink = require 'app/members/contentdisplays/membermaillink'
-JView = require 'app/jview'
-JCustomHTMLView = require 'app/jcustomhtmlview'
-FSHelper = require 'app/util/fs/fshelper'
-AvatarStaticView = require 'app/commonviews/avatarviews/avatarstaticview'
-MetaInfoButtonView = require 'app/commonviews/metainfobuttonview'
-TrollButtonView = require 'app/commonviews/trollbuttonview'
-
+remote                     = require('app/remote').getInstance()
+globals                    = require 'globals'
+proxifyUrl                 = require 'app/util/proxifyUrl'
+checkFlag                  = require 'app/util/checkFlag'
+isMine                     = require 'app/util/isMine'
+whoami                     = require 'app/util/whoami'
+isGuest                    = require 'app/util/isGuest'
+MemberMailLink             = require 'app/members/contentdisplays/membermaillink'
+JView                      = require 'app/jview'
+JCustomHTMLView            = require 'app/jcustomhtmlview'
+FSHelper                   = require 'app/util/fs/fshelper'
+AvatarStaticView           = require 'app/commonviews/avatarviews/avatarstaticview'
+MetaInfoButtonView         = require 'app/commonviews/metainfobuttonview'
+TrollButtonView            = require 'app/commonviews/trollbuttonview'
+Encoder                    = require 'htmlencode'
+Traverse                   = require 'traverse'

 module.exports = class ProfileView extends JView

@@ -30,7 +31,13 @@
     options.bind   = "mouseenter mouseleave"
     super options, data

-    @memberData    = @getData()
+    # filter from xss attacks
+    @memberData = @getData()
+    Traverse(@memberData).forEach (node)->
+      if 'string' is typeof node
+      then @update Encoder.XSSEncode node
+      else @update node
+
     mainController = kd.getSingleton "mainController"

     if @memberData.isExempt


Social: Add 'Activity/Chat/All' route

Social: Add 'Activity/Chat/All' route

--- client/Social/Activity/routes.coffee
+++ client/Social/Activity/routes.coffee
@@ -56,6 +56,9 @@
     '/:name?/Activity/Post/All' : ({params:{name, slug}, query}) ->
       handleChannel null, null, (app) -> app.getView().showAllConversationsModal()

+    '/:name?/Activity/Chat/All' : ->
+      handleChannel null, null, (app) -> app.getView().showAllChatsModal()
+
     '/:name?/Activity' : ({params:{name, slug}, query}) ->
       # handle legacy topic routes
       if query.tagged?


added redirection to notifications via appManager on clicking notification popup

--- client/app/MainApp/avatararea.coffee
+++ client/app/MainApp/avatararea.coffee
@@ -353,6 +353,9 @@

   click:(event)->

+    appManager.openApplication 'Inbox'
+    appManager.tell "Inbox", "goToNotifications", @
+
     popupList = @getDelegate()
     popupList.propagateEvent KDEventType : 'AvatarPopupShouldBeHidden'


Client: FIleSystem: Updated command to extract files. It prevents 'creating double same folder' problem.

--- client/app/lib/util/fs/fsitem.coffee
+++ client/app/lib/util/fs/fsitem.coffee
@@ -181,9 +181,12 @@

     .then (actualPath) =>

+      folderName  = @name.split(".")[0]
+      extractPath = if actualPath.search("#{folderName}_") > -1 then actualPath else @parentPath
+
       command =
         if isTarGz
-          "cd #{escapeFilePath @parentPath};mkdir -p #{escapeFilePath actualPath};tar -zxf #{escapeFilePath @name} -C #{escapeFilePath actualPath}"
+          "cd #{escapeFilePath @parentPath};mkdir -p #{escapeFilePath actualPath};tar -zxf #{escapeFilePath @name} -C #{escapeFilePath extractPath}"
         else
           "cd #{escapeFilePath @parentPath};unzip -o #{escapeFilePath @name} -d #{escapeFilePath actualPath}"


client: minor: code cleanup

client: minor: code cleanup

--- client/stacks/lib/views/stacks/definestackview.coffee
+++ client/stacks/lib/views/stacks/definestackview.coffee
@@ -76,30 +76,30 @@
     @editorViews = {}

     @editorViews.stackTemplate = @stackTemplateView = new StackTemplateView options, data
-    @tabView.addPane stackTemplatePane              = new KDTabPaneView
+    @tabView.addPane stackTemplatePane = new KDTabPaneView
       name : 'Stack Template'
       view : @stackTemplateView

-    @editorViews.variables = @variablesView         = new VariablesView {
+    @editorViews.variables = @variablesView = new VariablesView {
       delegate: this
       stackTemplate
     }
-    @tabView.addPane variablesPane                  = new KDTabPaneView
+    @tabView.addPane variablesPane = new KDTabPaneView
       name : 'Custom Variables'
       view : @variablesView

-    @editorViews.readme = @readmeView               = new ReadmeView { stackTemplate }
-    @tabView.addPane readmePane                     = new KDTabPaneView
+    @editorViews.readme = @readmeView = new ReadmeView { stackTemplate }
+    @tabView.addPane readmePane = new KDTabPaneView
       name : 'Readme'
       view : @readmeView

-    @providersView                                  = new ProvidersView {
+    @providersView = new ProvidersView {
       selectedCredentials : @credentials
       provider            : selectedProvider
       stackTemplate
     }

-    @tabView.addPane @providersPane                 = new KDTabPaneView
+    @tabView.addPane @providersPane = new KDTabPaneView
       name : 'Credentials'
       view : @providersView


syntax fix.

syntax fix.

--- components/inputs/KDInputView.coffee
+++ components/inputs/KDInputView.coffee
@@ -242,9 +242,10 @@
     else
       @validationResults[rule] = null
       # if there is any true on validation results' values then is not valid
+
       @valid = not _.values(@validationResults)
                     .map((result)-> Boolean result)
-                    .indexOf true > -1
+                    .indexOf(true) > -1

   showValidationError:(message)->


kdmodalview has helpers for stack management.

--- components/modals/KDModalView.coffee
+++ components/modals/KDModalView.coffee
@@ -202,3 +202,15 @@
       uber()

     @emit 'KDModalViewDestroyed', @
+
+  ### STACK HELPERS ###
+
+  @createStack: (options)->
+    @stack or= new KDModalViewStack options
+
+  @addToStack: (modal)->
+    @stack.addModal modal
+
+  @destroyStack: (force=no)->
+    @stack.destroy()
+    delete @stack


API/service layout deprecation warning

API/service layout deprecation warning

--- app/coffee/WebApiManager.coffee
+++ app/coffee/WebApiManager.coffee
@@ -2,6 +2,11 @@
 logger = require "logger-sharelatex"
 Settings = require "settings-sharelatex"

+# DEPRECATED! This method of getting user details via track-changes is deprecated
+# in the way we lay out our services.
+# Instead, web should be responsible for collecting the raw data (user_ids) and
+# filling it out with calls to other services. All API calls should create a
+# tree-like structure as much as possible, with web as the root.
 module.exports = WebApiManager =
 	sendRequest: (url, callback = (error, body) ->) ->
 		request.get {
@@ -56,4 +61,4 @@
 				project = JSON.parse(body)
 			catch error
 				return callback(error)
-			callback null, project+			callback null, project


Refactor: migrated modal plugin to classes

--- app/coffee/plugins/modal.coffee
+++ app/coffee/plugins/modal.coffee
@@ -12,32 +12,33 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.

-ModalServiceFactory = ($rootScope, $q, $log) ->
-    modals = {}
-    service = {}
+class ModalService extends TaigaBaseService
+    @.$inject = ["$q", "$log"]
+    constructor: (@q, @log) ->
+        super()

-    service.register = (name, domId) ->
-        $log.debug "registering modal: #{name}"
-        modals[name] = domId
+    modals: {}

-    service.open = (name, ctx) ->
-        dom = angular.element("##{modals[name]}")
+    register: (name, domId) ->
+        @log.debug "registering modal: #{name}"
+        @modals[name] = domId
+
+    open: (name, ctx) ->
+        dom = angular.element("##{@modals[name]}")
         $(dom.find('.modal')).css('top': $(document).scrollTop() + 15)

-        defered = $q.defer()
+        defered = @q.defer()

         ctrl = dom.controller()
         scp = dom.scope()
         ctrl.start(defered, ctx)
         return defered.promise

-    service.close = (name) ->
-        dom = angular.element("##{modals[name]}")
+    close: (name) ->
+        dom = angular.element("##{@modals[name]}")

         ctrl = dom.controller()
         ctrl.delete()
-
-    return service

 ModalRegisterDirective = ($rootScope, $modal) ->
@@ -50,5 +51,5 @@

 module = angular.module("gmModal", [])
-module.factory("$modal", ["$rootScope", "$q", "$log", ModalServiceFactory])
+module.service("$modal", ModalService)
 module.directive("gmModal", ["$rootScope", "$modal", ModalRegisterDirective])


Update lifetime sub (#5756)

Update lifetime sub (#5756)

--- app/collections/Products.coffee
+++ app/collections/Products.coffee
@@ -17,6 +17,9 @@
     return countrySpecificProduct or @findWhere({ name: 'basic_subscription' })

   getLifetimeSubscriptionForUser: (user) ->
+    if user?.get('country') == "hong-kong"
+      return null
+
     coupon = user?.get('stripe')?.couponID
     if coupon
       countrySpecificProduct = @findWhere { name: "#{coupon}_lifetime_subscription" }


Frontend polling rate aware of queue length at time of submission

--- common/lib/xmodule/xmodule/js/src/capa/display.coffee
+++ common/lib/xmodule/xmodule/js/src/capa/display.coffee
@@ -35,8 +35,18 @@
     if @queued_items.length > 0
       if window.queuePollerID # Only one poller 'thread' per Problem
         window.clearTimeout(window.queuePollerID)
-      window.queuePollerID = window.setTimeout(@poll, 100)
-
+      queuelen = @get_queuelen()
+      window.queuePollerID = window.setTimeout(@poll, queuelen*10)
+
+  # Retrieves the minimum queue length of all queued items
+  get_queuelen: =>
+    minlen = Infinity
+    @queued_items.each (index, qitem) ->
+      len = parseInt($.text(qitem))
+      if len < minlen
+        minlen = len
+    return minlen
+
   poll: =>
     $.postWithPrefix "#{@url}/problem_get", (response) =>
       @queued_items = $(response.html).find(".xqueue")
@@ -47,7 +57,7 @@
           @bind()
         delete window.queuePollerID
       else
-        # TODO: Dynamically adjust timeout interval based on @queued_items.value
+        # TODO: Some logic to dynamically adjust polling rate based on queuelen
         window.queuePollerID = window.setTimeout(@poll, 1000)

   render: (content) ->


Use scope for creating objects.

Use scope for creating objects.

This allows this use case:

    POST /parent/1/children

and it automatically associates the child with the parent.

--- src/controllers/mixins/actions.coffee
+++ src/controllers/mixins/actions.coffee
@@ -28,17 +28,17 @@
       @_create (error, resource) =>
         @respondWith
           html: -> "Created!"
-          json: -> @render json: resource
+          json: -> @render json: resource, status: 201

     _create: (callback) ->
       data = @params[@resourceOptions.name]

-      @resource().create data, (err, resource) =>
-        if err
-          return callback.apply @, [err, null]
+      @scoped (err, scope) =>
+        scope.create data, (err, resource) =>
+          if err
+            return callback.apply @, [err, null]

-        @response.status 201
-        callback.apply @, [null, resource]
+          callback.apply @, [null, resource]

     update: ->
       @_update (err, resource) =>


Ensured that TaxonomyCode Model was setup properly

--- app/models/taxonomy-code.coffee
+++ app/models/taxonomy-code.coffee
@@ -1,11 +1,11 @@
 `import DS from 'ember-data'`

 TaxonomyCode = DS.Model.extend
-  code DS.attr('string'),
-  taxonomy_type DS.attr('string'),
-  classification DS.attr('string'),
-  specialization DS.attr('string'),
-  definition DS.attr('string'),
-  notes DS.attr('string')
+  code: DS.attr('string'),
+  taxonomy_type: DS.attr('string'),
+  classification: DS.attr('string'),
+  specialization: DS.attr('string'),
+  definition: DS.attr('string'),
+  notes: DS.attr('string')

 `export default TaxonomyCode`


Minor edits

Minor edits

--- app/pages/about/about-home.cjsx
+++ app/pages/about/about-home.cjsx
@@ -8,7 +8,7 @@
    pageContent: '''
     ## What is the Zooniverse?

-    The Zooniverse is the world’s largest and most popular platform for people-powered research. This research is      made possible by volunteers—hundreds of thousands of people around the world who come together to assist           professional researchers. Our goal is to enable research that would not be possible, or practical, otherwise.      Zooniverse research results in new discoveries, datasets useful to the wider research community, and [many         publications](./#/about/publications).
+    The Zooniverse is the world’s largest and most popular platform for people-powered research. This research is made possible by volunteers—hundreds of thousands of people around the world who come together to assist professional researchers. Our goal is to enable research that would not be possible, or practical, otherwise. Zooniverse research results in new discoveries, datasets useful to the wider research community, and [many publications](./#/about/publications).

     ### At the Zooniverse, anyone can be a researcher


Changed volunteers to registered volunteers on project landing page

Count isn’t looking at ip addresses, so changing to ‘registered
volunteers’ makes it a bit clearer what value is being reported

--- app/pages/project/metadata.cjsx
+++ app/pages/project/metadata.cjsx
@@ -18,7 +18,7 @@

{project.classifiers_count}

-
Volunteers

+
Registered Volunteers


Code padding

Code padding

--- app/scripts/views/export.coffee
+++ app/scripts/views/export.coffee
@@ -44,6 +44,8 @@
         linear#{if infinite then " infinite" else ""} both;"

     code = """
+
+
 .animation-target {
   #{animations.join "\n  "}
 }
@@ -51,6 +53,8 @@
 /* Generated with Bounce.js. Edit at #{window.location} */

 #{keyframes}
+
+
 """

     @$code.text code


Fixed a bug in ClientTestView given an updated underscore.string library.

--- app/views/ClientTestView.coffee
+++ app/views/ClientTestView.coffee
@@ -54,7 +54,7 @@
     @specFiles = ClientTestView.getAllSpecFiles()
     if @subPath
       prefix = TEST_REQUIRE_PREFIX + @subPath
-      @specFiles = (f for f in @specFiles when _(f).startsWith(prefix).value())
+      @specFiles = (f for f in @specFiles when _(f).startsWith(prefix))

   @runTests: (specFiles) ->
     describe 'Client', ->


Fix off-by-one-element error in scroll loading

--- app/views/posts/index.js.coffee
+++ app/views/posts/index.js.coffee
@@ -38,8 +38,8 @@

 $('#posts_list tbody').append '<%= j render(:partial => 'posts_list') %>'

-from_tr = $('#posts_list tr[data-number="<%= @from %>"]')
-from_tr.nextAll().find('.expanded').removeClass('expanded').addClass('expandable')
+from_tr = $('#posts_list tr[data-number="<%= @from %>"]').nextAll('[data-level="1"]').first()
+from_tr.nextAll().andSelf().find('.expanded').removeClass('expanded').addClass('expandable')
 from_tr.nextAll('[data-level!="1"]').hide()
 for unread in from_tr.nextAll('.unread[data-level!="1"]')
   toggle_thread_expand $($(unread).prevAll('[data-level="1"]')[0])


[org switcher] fixing arrowing issues

[org switcher] fixing arrowing issues

--- src/scripts/models/orgSwitcherViewModel.coffee
+++ src/scripts/models/orgSwitcherViewModel.coffee
@@ -89,6 +89,7 @@
     # if they change the query turn off force all
     @flatOrgList.query.subscribe () =>
       @forceShowAllOrgs(false)
+      _activeItemIndex -1
     , null, 'beforeChange'

     @isSearching = ko.computed () =>
@@ -199,16 +200,13 @@
         if e.keyCode == 38
           if _activeItemIndex() > 0
             _activeItemIndex _activeItemIndex() - 1
-          else
-            _activeItemIndex(@displayOrgs().length - 1)
+          return false

         # down arrow
         else if e.keyCode == 40
           if _activeItemIndex() < @displayOrgs().length - 1
             _activeItemIndex _activeItemIndex() + 1
-            return false
-          else
-            _activeItemIndex(0)
+          return false

         # esc arrow
         else if e.keyCode == 27


cleanup

cleanup

--- gem/vendor/assets/javascripts/angular-table.js.coffee
+++ gem/vendor/assets/javascripts/angular-table.js.coffee
@@ -23,41 +23,38 @@
 angular.module("angular-table").directive "atTable", ["attributeExtractor", (attributeExtractor) ->

   capitaliseFirstLetter = (string) ->
-    string.charAt(0).toUpperCase() + string.slice(1)
-
-  constructHeader = (element) ->
-    thead = element.find "thead"
-
-    if thead[0]
-      tr = thead.find "tr"
-      existing_ths = {}
-      for th in tr.find "th"
-        th = angular.element(th)
-        existing_ths[th.attr("attribute")] = th.html()
-
-      tr.remove()
-      tr = $("

")
-
-      tds = element.find("td")
-      for td in tds
-        td = angular.element(td)
-        attribute = attributeExtractor.extractAttribute(td)
-        th = $("	")
-        title = existing_ths[attribute] || capitaliseFirstLetter(attributeExtractor.extractTitle(td))
-        th.html("#{title}")
-
-        sortable = td[0].attributes.sortable || attributeExtractor.isSortable(td.attr("class"))
-        if sortable
-          th.attr("ng-click", "predicate = '#{attribute}'; descending = !descending;")
-          icon = angular.element("")
-          icon.attr("ng-class", "getSortIcon('#{attribute}')")
-          th.append(icon)
-
-        width = attributeExtractor.extractWidth(td.attr("class"))
-        th.attr("width", width)
-        tr.append(th)
-
-      thead.append tr
+    if string then string.charAt(0).toUpperCase() + string.slice(1) else ""
+
+
+  constructHeader = (thead, tds) ->
+    tr = thead.find "tr"
+    existingThMarkup = {}
+    for th in tr.find "th"
+      th = angular.element(th)
+      existingThMarkup[th.attr("attribute")] = th.html()
+
+    tr.remove()
+    tr = angular.element("

")
+
+    for td in tds
+      td = angular.element(td)
+      attribute = attributeExtractor.extractAttribute(td)
+      th = angular.element("	")
+      title = existingThMarkup[attribute] || capitaliseFirstLetter(attributeExtractor.extractTitle(td))
+      th.html("#{title}")
+
+      sortable = td[0].attributes.sortable || attributeExtractor.isSortable(td.attr("class"))
+      if sortable
+        th.attr("ng-click", "predicate = '#{attribute}'; descending = !descending;")
+        icon = angular.element("")
+        icon.attr("ng-class", "getSortIcon('#{attribute}')")
+        th.append(icon)
+
+      width = attributeExtractor.extractWidth(td.attr("class"))
+      th.attr("width", width)
+      tr.append(th)
+
+    thead.append tr

   validateInput = (attributes) ->
     if attributes.pagination and attributes.list
@@ -72,8 +69,11 @@
     tbody

+  orderByExpression = "| orderBy:predicate:descending"
+  limitToExpression = "| limitTo:fromPage() | limitTo:toPage()"
+
   StandardSetup = (attributes) ->
-    @repeatString = "item in #{attributes.list} | orderBy:predicate:descending"
+    @repeatString = "item in #{attributes.list} #{orderByExpression}"
     @compile = (element, attributes, transclude) ->
       setupTr element, @repeatString

@@ -81,7 +81,8 @@
     return

   PaginationSetup = (attributes) ->
-    @repeatString = "item in #{attributes.pagination}.list | limitTo:fromPage() | limitTo:toPage() | orderBy:predicate:descending"
+
+    @repeatString = "item in #{attributes.pagination}.list #{orderByExpression} #{limitToExpression}"

     @compile = (element, attributes, transclude) ->
       tbody = setupTr element, @repeatString
@@ -90,7 +91,7 @@
         tds = element.find("td")
         tdString = ""
         for td in tds
-          tdString += "{{item}} 	"
+          tdString += " 	"

         fillerTr = angular.element("
#{tdString}
")
         fillerTr.attr("ng-repeat", "item in #{attributes.pagination}.getFillerArray() ")
@@ -120,7 +121,11 @@
     scope: true
     compile: (element, attributes, transclude) ->
       setup = createSetup attributes
-      constructHeader(element)
+
+      thead = element.find "thead"
+      tds = element.find "td"
+      constructHeader(thead, tds) if thead[0]
+
       setup.compile(element, attributes, transclude)
       {
         post: ($scope, $element, $attributes) ->
@@ -196,7 +201,6 @@
           else
             []

-
       $scope.goToPage = (page) ->
         page = Math.max(0, page)
         page = Math.min($scope.numberOfPages - 1, page)


fix image_uri to only load images once, and clip correctly on first render on load

--- static/coffee/renderers/glyph/image_uri.coffee
+++ static/coffee/renderers/glyph/image_uri.coffee
@@ -23,13 +23,17 @@
   _set_data: (@data) ->
     @x = @glyph_props.v_select('x', data)
     @y = @glyph_props.v_select('y', data)
-    @image = (@glyph_props.select('url', obj) for obj in data)
+    @url = (@glyph_props.select('url', obj) for obj in data)
     @angle = (@glyph_props.select('angle', obj) for obj in data) # TODO deg/rad
+    @image = (null for img in @url)
+    @need_load = (true for img in @url)
+    @loaded = (false for img in @url)

   _render: () ->
     [@sx, @sy] = @plot_view.map_to_screen(@x, @glyph_props.x.units, @y, @glyph_props.y.units)

     ctx = @plot_view.ctx
+    vs = @plot_view.view_state

     ctx.save()

@@ -38,20 +42,39 @@
       if isNaN(@sx[i] + @sy[i]+ @angle[i])
         continue

-      img = new Image()
-      img.onload = do (img, i) =>
-        return () =>
-          if @angle[i]
-            ctx.translate(@sx[i], @sy[i])
-            ctx.rotate(@angle[i])
-            ctx.drawImage(img, 0, 0);
-            ctx.rotate(-@angle[i])
-            ctx.translate(-@sx[i], -@sy[i])
-          else
-            ctx.drawImage(img, @sx[i], @sy[i]);
-      img.src = @image[i]
+      if @need_load[i]
+        img = new Image()
+        img.onload = do (img, i) =>
+          return () =>
+            @loaded[i] = true
+            @image[i] = img
+            ctx.save()
+            ctx.beginPath()
+            # TODO should take the real axis rule width into account, for now shrink region by 1 px
+            ctx.rect(
+              vs.get('border_left')+1, vs.get('border_top')+1,
+              vs.get('inner_width')-2, vs.get('inner_height')-2,
+            )
+            ctx.clip()
+            @_render_image(ctx, vs, i, img)
+            ctx.restore()
+        img.src = @url[i]
+        @need_load[i] = false
+
+      else if @loaded[i]
+        @_render_image(ctx, vs, i, @image[i])

     ctx.restore()
+
+  _render_image: (ctx, vs, i, img) ->
+    if @angle[i]
+      ctx.translate(@sx[i], @sy[i])
+      ctx.rotate(@angle[i])
+      ctx.drawImage(img, 0, 0);
+      ctx.rotate(-@angle[i])
+      ctx.translate(-@sx[i], -@sy[i])
+    else
+      ctx.drawImage(img, @sx[i], @sy[i]);

 # name Image conflicts with js Image
 class ImageURIGlyph extends Glyph


message with types are not groupable by default

--- packages/rocketchat-lib/server/models/Messages.coffee
+++ packages/rocketchat-lib/server/models/Messages.coffee
@@ -262,6 +262,7 @@
 			u:
 				_id: user._id
 				username: user.username
+			groupable: false

 		_.extend record, extraData


Update teitoku-panel.cjsx

Update teitoku-panel.cjsx

--- views/components/main/parts/teitoku-panel.cjsx
+++ views/components/main/parts/teitoku-panel.cjsx
@@ -89,7 +89,7 @@
       when '/kcsapi/api_req_kousyou/createship_speedchange'
         {material} = @state
         if body.api_result == 1
-          material[4] -= material[4]
+          material[4] -= 1
         @setState
           material: material
       when '/kcsapi/api_req_kousyou/destroyitem2'
@@ -121,12 +121,6 @@
       when '/kcsapi/api_req_mission/result'
         @setState
           nextExp: totalExp[window._teitokuLv] - body.api_member_exp
-      when '/kcsapi/api_req_nyukyo/speedchange'
-        {material} = @state
-        if body.api_result == 1
-          material[5] -= 1
-        @setState
-          material: material
       when '/kcsapi/api_req_nyukyo/speedchange'
         {material} = @state
         if body.api_result == 1


make the correct return

make the correct return

--- workers/social/lib/social/models/domain.coffee
+++ workers/social/lib/social/models/domain.coffee
@@ -81,7 +81,7 @@
     success:(client, options={}, callback)->
       model = new JDomain options
       model.save (err) ->
-        if err then callback err
+        return callback err if err

         account = client.connection.delegate
         rel = new Relationship
@@ -92,7 +92,7 @@
           as: 'owner'

         rel.save (err)->
-          callback err
+          return callback err if err

         callback err, model


Do not strip copyright comments

Do not strip copyright comments

--- config.coffee
+++ config.coffee
@@ -27,6 +27,11 @@
       else
         data

+  plugins:
+    uglify:
+      output:
+        comments: /copyright|license|\(c\)/i
+
   conventions:
     # To prevent coping bootstrap assets into public/css and public/js.
     assets: /^app\/assets\//


hacky

hacky

--- config.coffee
+++ config.coffee
@@ -26,6 +26,9 @@
   ARENA_API_TOKEN: null
   OPENING_CREDIT: 1000

+val = (process.env.NODE_ENV or val)
+module.exports.NODE_ENV = try JSON.parse(val) catch then val
+
 if module.exports.NODE_ENV is "development"
   env = require 'node-env-file'
   env __dirname + '/.env'


client: switch to PerfectScrollbar events for infinite loading

--- client/app/lib/components/scroller/index.coffee
+++ client/app/lib/components/scroller/index.coffee
@@ -1,6 +1,5 @@
 kd = require 'kd'
 React = require 'kd-react'
-Waypoint = require 'react-waypoint'

 PerfectScrollbar = require 'app/components/perfectscrollbar'

@@ -14,18 +13,18 @@
     minScrollbarLength    : 64
     useSelectionScroll    : on

-  renderTopWaypoint: ->
+  onUpLimitReached: ->

-    return null  unless @props.hasMore
+    return  unless @props.hasMore

-    <Waypoint onEnter={@props.onTopThresholdReached} threshold={@props.threshold} />
+    @props.onTopThresholdReached?()

-  renderBottomWaypoint: ->
+  onDownLimitReached: ->

-    return null  unless @props.hasMore
+    return  unless @props.hasMore

-    <Waypoint onEnter={@props.onThresholdReached} threshold={@props.threshold} />
+    @props.onThresholdReached?()

   render: ->
@@ -33,10 +32,10 @@
     <PerfectScrollbar
       {...@props}
       className={kd.utils.curry 'Scrollable', @props.className}
+      onUpLimitReached={@bound 'onUpLimitReached'}
+      onDownLimitReached={@bound 'onDownLimitReached'}
       ref='scrollContainer'>
-      {@renderTopWaypoint()}
       {@props.children}
-      {@renderBottomWaypoint()}
     </PerfectScrollbar>


Client: Activity: Tests: Updated generateDummyChannel util to add accountOldId and createdAt parameters

--- client/app/lib/util/generateDummyChannel.coffee
+++ client/app/lib/util/generateDummyChannel.coffee
@@ -1,10 +1,10 @@
-module.exports = ({ channelId, channelName, typeConstant, privacyConstant, group }) ->
+module.exports = ({ channelId, channelName, typeConstant, privacyConstant, group, accountOldId, createdAt }) ->
   return {
     id                  : channelId or "6089984394406133765"
     _id                 : channelId or "6089984394406133765"
-    accountOldId        : "568b34b24868cdaf029fccee"
+    accountOldId        : accountOldId or "568b34b24868cdaf029fccee"
     bongo_              : {}
-    createdAt           : "2016-01-05T03:43:46.067543Z"
+    createdAt           : createdAt or "2016-01-05T03:43:46.067543Z"
     creatorId           : "18"
     deletedAt           : "0001-01-01T00:00:00Z"
     groupName           : group or 'dummy-group'


comment out social workers dying after random prime interval

--- workers/social/lib/social/main.coffee
+++ workers/social/lib/social/main.coffee
@@ -41,17 +41,18 @@
   #     setTimeout ->
   #       process.exit()
   #     , 20000
-  die :
-    after: "non-overlapping, random, 3 digits prime-number of minutes"
-    middleware : (name,callback) -> koding.disconnect callback
-    # TEST AMQP WITH THIS CODE. IT THROWS THE CHANNEL ERROR.
-    # middleware : (name,callback) ->
-    #   koding.disconnect ->
-    #     console.log "[SOCIAL WORKER #{name}] is reached end of its life, will die in 10 secs."
-    #     setTimeout ->
-    #       callback null
-    #     ,10*1000
-    middlewareTimeout : 15000
+  #Commenting out so we can test memory leaks
+  #die :
+    #after: "non-overlapping, random, 3 digits prime-number of minutes"
+    #middleware : (name,callback) -> koding.disconnect callback
+    ## TEST AMQP WITH THIS CODE. IT THROWS THE CHANNEL ERROR.
+    ## middleware : (name,callback) ->
+    ##   koding.disconnect ->
+    ##     console.log "[SOCIAL WORKER #{name}] is reached end of its life, will die in 10 secs."
+    ##     setTimeout ->
+    ##       callback null
+    ##     ,10*1000
+    #middlewareTimeout : 15000
   toobusy:
     interval: 10000
     callback: ->


social-worker: log call stack in error

social-worker: log call stack in error

--- workers/social/lib/social/main.coffee
+++ workers/social/lib/social/main.coffee
@@ -19,6 +19,7 @@

 process.on 'uncaughtException', (err)->
   exec './beep'
+  console.log err, err?.stack
   process.exit 1

 Bongo = require 'bongo'


SocialWorker: remove init call

SocialWorker: remove init call

--- workers/social/lib/social/main.coffee
+++ workers/social/lib/social/main.coffee
@@ -109,8 +109,6 @@
 koding.on 'errFirstDetected', (err) -> console.error err

 koding.connect ->
-  (require './init').init koding
-
   # create default roles for groups
   JGroupRole = require './models/group/role'


remove imagesmoothingenabled because it doesn’t really work

--- coffeescript/love.js/graphics.coffee
+++ coffeescript/love.js/graphics.coffee
@@ -12,7 +12,6 @@
     @setBackgroundColor(0, 0, 0)
     @setFont(@default_font)
     @context.textBaseline = "top"
-    # @context.imageSmoothingEnabled = false

   # DRAWING
   arc: (mode, x, y, radius, startAngle, endAngle, segments) =>


add analysys function

add analysys function

--- dbserver.coffee
+++ dbserver.coffee
@@ -7,17 +7,41 @@
 		MongoClient.connect url, (err, db) =>
 			process.exit(err) if err
 			@db = db
-			@collection = db.collection('raw');
+			@raw = db.collection('raw')
+			@wifi = db.collection('wifi')

-	submit: (data, res) =>
-		@collection.insert data, (err) ->
+	submit: (json, res) =>
+		@raw.insert data, (err) ->
 			if err
 				res.status(403).end()
 			else
 				res.status(200).end()

+		json.scan.forEach (value) =>
+			@wifi.findOne {"BSSID": value.BSSID}, (err, docs) =>
+				if docs?
+					docWeight = docs.weight
+					valueWeight = 2 * (value.level + 100)
+					newWeight = docWeight + valueWeight
+					newX = (docs.x * docWeight + json.x * valueWeight) / newWeight
+					newY = (docs.y * docWeight + json.y * valueWeight) / newWeight
+
+					@wifi.update {"BSSID":value.BSSID}, {$set:{"x":newX, "y": newY, "weight": newY}}, (err) ->
+						console.log(err) if err
+				else
+					record =
+						"SSID": value.SSID
+						"BSSID": value.BSSID
+						"x": json.x
+						"y": json.y
+						"z": json.z
+						"weight": 2 * (value.level + 100)
+
+					@wifi.insert record, (err, docs) ->
+						console.log(err) if err
+
 	checkout: (res) =>
-		@collection.find().toArray (err, docs)->
+		@collection.find().toArray (err, docs) ->
 			if err
 				res.status(500).end()
 			else


use proper accessor

use proper accessor

--- core/app/backbone/models/user.coffee
+++ core/app/backbone/models/user.coffee
@@ -17,7 +17,7 @@
       '/' + @get('username')

   is_current_user: ->
-    currentUser?.get('username') == @attributes.username
+    currentUser?.get('username') == @get('username')

   avatar_url: (size)->
     md5d_email = @get('gravatar_hash')


we always want to prepend on a 0th elem insertion to preserve multicursor position

--- vendor/assets/javascripts/scribe/line.coffee
+++ vendor/assets/javascripts/scribe/line.coffee
@@ -105,7 +105,8 @@

   insertText: (offset, text, formats = {}) ->
     [leaf, leafOffset] = this.findLeafAtOffset(offset)
-    if _.isEqual(leaf.formats, formats) and @length > 1
+    # offset > 0 for multicursor
+    if _.isEqual(leaf.formats, formats) and @length > 1 and offset > 0
       leaf.insertText(leafOffset, text)
       @length += text.length
       @outerHTML = @node.outerHTML


Follow mixin

Follow mixin

--- test/follow.coffee
+++ test/follow.coffee
@@ -9,6 +9,6 @@

   enemy = GameObject
     x: 0
-
+    y: 50

 module()


JMachine: add more states

JMachine: add more states

--- workers/social/lib/social/models/computeproviders/machine.coffee
+++ workers/social/lib/social/models/computeproviders/machine.coffee
@@ -61,7 +61,8 @@
       state             :
         type            : String
         enum            : ["Wrong type specified!",
-          ["active", "not-initialized", "removed", "suspended"]
+          ["active", "not-initialized", "removed",
+           "suspended", "provisioning"]
         ]
         default         : "not-initialized"


ComputeProvider: Managed: on remove update instance usage

--- workers/social/lib/social/models/computeproviders/managed.coffee
+++ workers/social/lib/social/models/computeproviders/managed.coffee
@@ -121,8 +121,14 @@

     JMachine.one selector, (err, machine) ->
       if err or not machine
-      then callback new KodingError 'Machine not found.'
-      else machine.destroy client, callback
+        callback new KodingError 'Machine not found.'
+      else
+        machine.destroy client, (err) ->
+          return callback err  if err
+
+          checkPlans {
+            client, provider: @providerSlug, change: 'decrement'
+          }, (err) -> callback null

   updateMachine = (selector, fieldsToUpdate, callback) ->


Fixed Elyssa.Node#toString

Fixed Elyssa.Node#toString

--- src/foundation/node.coffee
+++ src/foundation/node.coffee
@@ -13,8 +13,4 @@
     draw: ->

-    toString: ->
-      position: position.toString()
-      color: color.toString()
-      scale: scale.toString()
-      texture: texture.toString()
+    toString: -> JSON.stringify {@position, @color, @scale, @texture}


Modal: Overlay should only be removable if the option is true.

--- src/lib/components/modal/Modal.coffee
+++ src/lib/components/modal/Modal.coffee
@@ -108,11 +108,13 @@
     @private
   ###
   setOverlay_: ->
-    @overlay = new spark.components.Overlay
-      removeOnClick: @getOptions().removeOnOverlayClick
+    isRemovable = @getOptions().removeOnOverlayClick
+    @overlay    = new spark.components.Overlay
+      removeOnClick: isRemovable

-    @overlay.once 'OverlayRemoved', =>
-      @removeFromDocument()
+    if isRemovable
+      @overlay.once 'OverlayRemoved', =>
+        @removeFromDocument()

   ###*
@@ -133,6 +135,7 @@
     Css class map for preset buttons.

     @private
+    @enum {string}
   ###
   @ButtonCssClasses =
     'Yes'    : 'green'


removed duplicate draggable

removed duplicate draggable

--- views/index.coffee
+++ views/index.coffee
@@ -75,15 +75,14 @@
     className = ""
     $( ".chess_piece" ).draggable(
       opacity: 0.55
+      refreshPositions: true
       start: (event, ui) ->
         className = ui.helper.context.className.toString()
-        startTop = ui.offset.top
-        start_left = ui.offset.left
       stop: (event, ui) ->
         piecePath= ui.helper.context.src.match(/chess_pieces*.*/)
         pieceName = piecePath.toString().replace("chess_pieces/", "")
         setPosition(className, ui.position.top, ui.position.left)
-        now.distributeMessage('has moved')
+        now.distributeMessage('has moved a piece')
       )

     if $.cookie('klop_name')
@@ -91,11 +90,6 @@
     else
       now.name = prompt("What's your name?", "")
       $.cookie('klop_name', now.name)
-
-    $( ".chess_piece" ).draggable(
-      stop: (event, ui) ->
-        console.log "moved a piece"
-      )

     $("#text_input").keyup (event) ->
       if event.keyCode == 13


filename change

filename change

--- test/plugin.coffee
+++ test/plugin.coffee
@@ -1,25 +0,0 @@
-vows = require 'vows'
-assert = require 'assert'
-wintersmith = require 'wintersmith'
-
-suite = vows.describe 'Plugin'
-
-suite.addBatch
-  'wintersmith environment':
-    topic: -> wintersmith './example/config.json'
-    'loaded ok': (env) ->
-      assert.instanceOf env, wintersmith.Environment
-    'contents':
-      topic: (env) -> env.load @callback
-      'loaded ok': (result) ->
-        assert.instanceOf result.contents, wintersmith.ContentTree
-      'has plugin instances': (result) ->
-        assert.instanceOf result.contents['hello.txt'], wintersmith.ContentPlugin
-        assert.isArray result.contents._.text
-        assert.lengthOf result.contents._.text, 2
-      'contains the right text': (result) ->
-        for item in result.contents._.text
-          assert.isString item.text
-          assert.match item.text, /^Wintersmith is awesome!\n/
-
-suite.export module


corr_w_scatterplot: add scatterplot, with animation

--- corr_w_scatter/corr_w_scatter.coffee
+++ corr_w_scatter/corr_w_scatter.coffee
@@ -13,6 +13,8 @@
   h = 450
   w = h
   pad = {left:40, top:40, right:20, bottom: 40}
+  innerPad = 5
+  animationDuration = 500

   totalh = h + pad.top + pad.bottom
   totalw = (w + pad.left + pad.right)*2
@@ -46,6 +48,73 @@
     for j of data.corr[i]
       corr.push({row:i, col:j, value:data.corr[i][j]})

+
+  # gray background on scatterplot
+  scatterplot.append("rect")
+             .attr("height", h)
+             .attr("width", w)
+             .attr("fill", d3.rgb(200, 200, 200))
+             .attr("stroke", "black")
+             .attr("stroke-width", 1)
+             .attr("pointer-events", "none")
+
+  # colors for scatterplot
+  colors = ["crimson", "green", "darkslateblue"]
+
+  initialDrawn = false
+  drawInitialScatter = (i,j) ->
+    initialDrawn = true
+    xScale = d3.scale.linear()
+                     .domain(d3.extent(data.dat[i]))
+                     .range([innerPad, w-innerPad])
+    yScale = d3.scale.linear()
+                     .domain(d3.extent(data.dat[j]))
+                     .range([h-innerPad, innerPad])
+    scatterplot.selectAll("empty")
+               .data(d3.range(nind))
+               .enter()
+               .append("circle")
+               .attr("class", "points")
+               .attr("cx", (d) -> xScale(data.dat[i][d]))
+               .attr("cy", (d) -> yScale(data.dat[j][d]))
+               .attr("r", 3)
+               .attr("stroke", "black")
+               .attr("stroke-width", 1)
+               .attr("fill", (d) -> colors[data.group[d]-1])
+    scatterplot.append("text").attr("id", "xaxis")
+               .attr("x", w/2)
+               .attr("y", h+pad.bottom*0.7)
+               .text(data.var[i])
+               .attr("dominant-baseline", "middle")
+               .attr("text-anchor", "middle")
+    scatterplot.append("text").attr("id", "yaxis")
+               .attr("x", -pad.left*0.7)
+               .attr("y", h/2)
+               .text(data.var[j])
+               .attr("dominant-baseline", "middle")
+               .attr("text-anchor", "middle")
+               .attr("transform", "rotate(270,#{-pad.left*0.7},#{h/2})")
+
+  # scatterplot points
+  drawScatter = (i,j) ->
+    xScale = d3.scale.linear()
+                     .domain(d3.extent(data.dat[i]))
+                     .range([innerPad, w-innerPad])
+    yScale = d3.scale.linear()
+                     .domain(d3.extent(data.dat[j]))
+                     .range([h-innerPad, innerPad])
+    d3.selectAll("circle.points")
+      .transition()
+      .duration(animationDuration)
+      .attr("cx", (d) -> xScale(data.dat[i][d]))
+      .attr("cy", (d) -> yScale(data.dat[j][d]))
+
+    d3.select("text#xaxis")
+      .text(data.var[i])
+    d3.select("text#yaxis")
+      .text(data.var[j])
+
+
   cells = corrplot.selectAll("empty")
              .data(corr)
              .enter().append("rect")
@@ -75,6 +144,13 @@
              .on("mouseout", ->
                  d3.select(this).attr("stroke","none")
                  corrplot.selectAll("text#corrtext").remove())
+             .on("click",(d) ->
+                  if initialDrawn
+                    drawScatter(d.col, d.row)
+                  else
+                    drawInitialScatter(d.col, d.row))
+
+

   # boxes around panels
   corrplot.append("rect")


Fix unit tests failing after multiple runs

--- test/shared.coffee
+++ test/shared.coffee
@@ -9,8 +9,8 @@
     @store = newStore()

   after () =>
-    if @store.client
-      @store.client.flushdb()
+    if @store.redis
+      @store.redis.flushdb()


Fixed client link

Fixed client link

--- core/app/backbone/models/fact.coffee
+++ core/app/backbone/models/fact.coffee
@@ -38,7 +38,7 @@
       @on 'change:id', -> @_fact_wheel.set 'fact_id', @id
     @_fact_wheel

-  clientLink: -> @clone().url()
+  clientLink: -> "/client/facts/#{@id}"

   user: -> new User(@get("created_by"))


Forgot to commit.

Forgot to commit.

--- core/app/backbone/factlink/tooltip_mixin.coffee
+++ core/app/backbone/factlink/tooltip_mixin.coffee
@@ -44,3 +44,5 @@
   tooltipResetAll: ->
     for selector, tooltip of @_tooltips
       tooltip.positionedRegion.reset()
+
+  tooltip: (selector) -> @_tooltips[selector]


Use deleted/dirty status for panel class

--- app/assets/javascripts/components/posts.js.cjsx
+++ app/assets/javascripts/components/posts.js.cjsx
@@ -23,7 +23,14 @@
   render: ->

       {@state.posts.map((post) ->
-

+        panelClass = if post.deleted
+          "danger"
+        else if post.dirty
+          "warning"
+        else
+          "info"
+
+

{post.title}


add support for default filter: boolean

add support for default filter: boolean

--- app/assets/javascripts/dataclips/view.js.coffee
+++ app/assets/javascripts/dataclips/view.js.coffee
@@ -80,6 +80,10 @@
           type = Dataclips.config.schema[key]["type"]

           switch type
+            when "boolean"
+              if value?
+                $("[name='#{key}']").val(if value is true then "1" else "0")
+                @filterArgs.set(key, value)
             when "text"
               if value?
                 $("[name='#{key}']").val(value)


const

const

--- app/assets/javascripts/static_editors.js.coffee
+++ app/assets/javascripts/static_editors.js.coffee
@@ -1,7 +1,11 @@
 window.MYTHREE = window.MYTHREE || {}

-MYTHREE.W = window.innerWidth;
-MYTHREE.H = window.innerHeight;
+#定数
+MYTHREE.const = {}
+MYTHREE.const.W = window.innerWidth;
+MYTHREE.const.H = MYTHREE.const.W * (800/1280);
+
+#---ヘルパー---

 #グリッド線
 MYTHREE.getGrid = ->
@@ -28,23 +32,13 @@
 #レンダラー
 MYTHREE.getRenderer = ->
   renderer = new THREE.CanvasRenderer;
-  canvas = {};
-  canvas.W = MYTHREE.W;
-
-  #canvas.H = MYTHREE.H;
-  canvas.H = MYTHREE.W * (800/1280);
-
-  renderer.setSize(canvas.W, canvas.H);
+  renderer.setSize(MYTHREE.const.W, MYTHREE.const.H)
   renderer

 #平行光源
 MYTHREE.getDirectionalLight = ->
-  renderer = new THREE.CanvasRenderer
-  canvas = {};
-  canvas.W = 600;
-  canvas.H = 400;
-  renderer.setSize(canvas.W, canvas.H)
-  renderer
+  light = new THREE.DirectionalLight( 0xffffff, 3 );
+  light

 MYTHREE.global = {}
 MYTHREE.global.voxels = {};
@@ -79,16 +73,9 @@
   init = ->
     container = document.getElementById('container')
     $container = $(container)
-    #ウィンドウ全体
-    W = window.innerWidth
-    H = window.innerHeight
-    H = W * (800/1280)
-    console.log('innerW,H', W, H)
-    MYTHREE.global.W = W
-    MYTHREE.global.H = H
-    camera = new THREE.PerspectiveCamera( 4, W/H, 1, 10000 )
-    camera.position.y = 400
-    scene = new THREE.Scene()
+    camera = new THREE.PerspectiveCamera( 40, MYTHREE.const.W/MYTHREE.const.H, 1, 10000 )
+    camera.position.y = 30
+    scene = new THREE.Scene
     scene.add( MYTHREE.getGrid() )
     projector = new THREE.Projector()
     scene.add( MYTHREE.getRayHitPlane() )
@@ -113,8 +100,8 @@
     offsetY = $("#header").height()
     canvasX = event.clientX
     canvasY = event.clientY + offsetY
-    mouse2D.x = ( canvasX / MYTHREE.global.W ) * 2 - 1
-    mouse2D.y = - ( canvasY / MYTHREE.global.W ) * 2 + 1
+    mouse2D.x = ( canvasX / MYTHREE.const.W ) * 2 - 1
+    mouse2D.y = - ( canvasY / MYTHREE.const.W ) * 2 + 1
     #console.log("mouse X, Y", mouse2D.x, mouse2D.y); #-1.0 ~ 1.0
     intersects = raycaster.intersectObjects( scene.children )
     if intersects.length > 0


Forgotten pjax, #400

Forgotten pjax, #400

--- main.coffee
+++ main.coffee
@@ -121,10 +121,6 @@
 	# PJAX
 	if request.header('X-PJAX')?
 		request.uonline.basicOpts.pjax = true
-	# Try to guess layout version
-	if /[/](register|game)/.test(request.path)
-		response.header 'X-PJAX-Version', 'second'
-	else
 		response.header 'X-PJAX-Version', 'third'
 	return
 ).asyncMiddleware()


async -> sync in `/ajax/isNickBusy/:nick`.

--- main.coffee
+++ main.coffee
@@ -236,13 +236,9 @@

 app.get '/ajax/isNickBusy/:nick', (request, response) ->
-	utils.user.userExists mysqlConnection, request.param('nick'), (error, result) ->
-		if error?
-			response.send 500
-			return
-		response.json
-			nick: request.param('nick')
-			isNickBusy: result
+	response.json
+		nick: request.param('nick')
+		isNickBusy: utils.user.userExists.sync null, mysqlConnection, request.param('nick')

 #app.get('/stats/', phpgate);


Follow Cozy guidelines about blank lines

--- main.coffee
+++ main.coffee
@@ -16,15 +16,19 @@
     warn: colors.yellow
     info: colors.blue

+
 class Logger
+

     constructor: (@options) ->
         @options ?= {}
         if @options.date and not @options.dateFormat?
             @options.dateFormat = 'YYYY-MM-DD hh:mm:ss:S'

+
     colorify: (text, color) ->
         "#{color[0]}#{text}#{color[1]}"
+

     stringify: (text) ->
         if text instanceof Error and text.stack
@@ -32,6 +36,7 @@
         else if text instanceof Object
             text = JSON.stringify text
         return text
+

     getFileAndLine: ->
         stacklist = (new Error()).stack.split('\n').slice(4)
@@ -61,26 +66,32 @@
             text = "[#{date}] #{text}"
         text

+
     info: (texts...) ->
         if process.env.DEBUG or process.env.NODE_ENV isnt 'test'
             console.info @format 'info', texts
+

     warn: (texts...) ->
         if process.env.DEBUG or process.env.NODE_ENV isnt 'test'
             console.info @format 'warn', texts
             console.warn @format 'warn', texts

+
     error: (texts...) ->
         if process.env.DEBUG or process.env.NODE_ENV isnt 'test'
             console.info @format 'error', texts
             console.error @format 'error', texts

+
     debug: (texts...) ->
         if process.env.DEBUG
             console.info @format 'debug', texts

+
     raw: (texts...) ->
         console.log.apply console, texts
+

     lineBreak: (text) ->
         @raw Array(80).join("*")


check if directory exists before starting

--- main.coffee
+++ main.coffee
@@ -28,6 +28,9 @@
   projNameUs   = projName.replace(camelRx, '$1_$2').toLowerCase()

   try
+    if fs.existsSync projNameHyph
+      throw new Error "Directory #{ projNameHyph } already exists"
+
     execSync 'type lein'
     execSync 'type pod'


many args in log functions

many args in log functions

--- main.coffee
+++ main.coffee
@@ -26,8 +26,14 @@
     colorify: (text, color) ->
         "#{color[0]}#{text}#{color[1]}"

-    format: (level, text) ->
-        text = JSON.stringify text if text instanceof Object
+    stringify: (text) ->
+        if text instanceof Object
+            text = JSON.stringify text
+        return text
+
+    format: (level, texts) ->
+        text = (@stringify text for text in texts).join(" ")
+
         text = "#{@options.prefix} | #{text}" if @options.prefix?

         if process.env.NODE_ENV isnt 'production'
@@ -39,20 +45,20 @@
             text = "[#{date}] #{text}"
         text

-    info: (text) ->
-        console.info @format 'info', text if process.env.NODE_ENV isnt 'test'
+    info: (texts...) ->
+        console.info @format 'info', texts if process.env.NODE_ENV isnt 'test'

-    warn: (text) ->
-        console.warn @format 'warn', text if process.env.NODE_ENV isnt 'test'
+    warn: (texts...) ->
+        console.warn @format 'warn', texts if process.env.NODE_ENV isnt 'test'

-    error: (text) ->
-        console.error @format 'error', text if process.env.NODE_ENV isnt 'test'
+    error: (texts...) ->
+        console.error @format 'error', texts if process.env.NODE_ENV isnt 'test'

-    debug: (text) ->
-        console.info @format 'debug', text if process.env.DEBUG
+    debug: (texts...) ->
+        console.info @format 'debug', texts if process.env.DEBUG

-    raw: (text) ->
-        console.log text
+    raw: (texts...) ->
+        console.log.apply console, texts

     lineBreak: (text) ->
         @raw Array(80).join("*")


Revert "Add 'deleted' helper method to user'"

This reverts commit 59c6b538e976f6e27060dd26a9fe3d9d8fddee0e.

--- core/app/backbone/models/user.coffee
+++ core/app/backbone/models/user.coffee
@@ -85,5 +85,3 @@

   user_topics: ->
     new UserTopics @get('user_topics')
-
-  deleted: -> @get('deleted')


Removed makeEditable viewUtils

Removed makeEditable viewUtils

--- jsapp/xlform/src/view.utils.coffee
+++ jsapp/xlform/src/view.utils.coffee
@@ -11,74 +11,6 @@
     regex = new RegExp('[: ()]', 'g')
     eventName = eventName.replace(regex, '-');
     return eventName
-
-  viewUtils.makeEditable = (that, model, selector, {property, transformFunction, options, edit_callback}) ->
-    if !(selector instanceof jQuery)
-      origSelector = selector
-      selector =that.$el.find(selector)
-
-    if selector.length is 0
-      console?.error("makeEditable called on nonexistent element:", origSelector)
-
-    if selector.data('madeEditable')
-      console?.error "makeEditable called 2x on the same element: ", selector
-    selector.data('madeEditable', true)
-
-    if !transformFunction?
-      transformFunction = (value) -> value
-    if !property?
-      property = 'value'
-
-    if !edit_callback?
-      edit_callback = _.bind (ent) ->
-          ent = transformFunction ent
-          ent = ent.replace(/\t/g, ' ')
-          model.set(property, ent, validate: true)
-          if(model.validationError && model.validationError[property])
-            return model.validationError[property]
-
-          newValue: ent
-        , that
-
-
-    selector.on 'shown', (e, obj) -> obj.input.$input.on 'paste', (e) -> e.stopPropagation()
-
-
-    enable_edit = () ->
-      parent_element = selector.parent()
-      parent_element.find('.error-message').remove()
-      current_value = selector.text().replace new RegExp(String.fromCharCode(160), 'g'), ''
-      edit_box = $('<input />', type:'text', value: current_value, class:'js-cancel-sort js-blur-on-select-row')
-      selector.after edit_box
-      selector.hide()
-      edit_box.select()
-
-      commit_edit = () ->
-        parent_element.find('.error-message').remove()
-        if options? && options.validate? && options.validate(edit_box.val())?
-          new_value = options.validate(edit_box.val())
-        else
-          new_value = edit_callback edit_box.val()
-
-        if !new_value?
-          new_value = newValue: edit_box.val()
-
-        if new_value.newValue?
-          edit_box.remove()
-          selector.show()
-          selector.html(_.escape(new_value.newValue))
-        else
-          error_box = $('
' + new_value + '
')
-          parent_element.append(error_box)
-
-      edit_box.blur commit_edit
-      edit_box.keypress (event) ->
-        if event.which == 13
-          commit_edit event
-
-
-    selector.on 'click', enable_edit
-

   viewUtils.reorderElemsByData = (selector, parent, dataAttribute)->
     arr = []


using new `addOrderItem` method

using new `addOrderItem` method

--- app/assets/javascripts/views/order_items/order_item_form_view.js.coffee
+++ app/assets/javascripts/views/order_items/order_item_form_view.js.coffee
@@ -8,5 +8,5 @@
     store.find("item", itemObject.id).then (item) =>
       # item = store.typeMapFor(Bandango.Item).idToRecord[itemObject.id]   # 'dirty'
       orderItem = store.createRecord("orderItem", cantidad: @get("cantidad"), item: item)
-      @get("parentView.model").get("orderItems").addObject orderItem
+      @get("parentView.model").addOrderItem orderItem
       $(".item_textfield").val null


modified elapsedTime method

modified elapsedTime method

--- app/coffee/drm-timestamps/drm-timestamps.coffee
+++ app/coffee/drm-timestamps/drm-timestamps.coffee
@@ -107,6 +107,9 @@
                 _that.text prettyDate
             , 1000

+        testDate = new Date 2014, 5, 24, 23, 41, 0
+        self.elapseTime testDate
+
     parseDate: (item) =>
         # check for Yesterday, Today, Tomorrow strings and look for time
         item = item.toLowerCase()
@@ -197,7 +200,7 @@
             else
                 return

-        _parseTime = (item) =>
+        _parseTime = (item) ->
             _fullTime = item.match /((?:[12][012]:|[0]?[0-9]:)[012345][0-9](?:\:[012345][0-9])?(?:am|pm)?)/i

             if _fullTime?
@@ -215,7 +218,7 @@
                 time.minute = parseInt(time.minute[1], 10)

                 time.second = _fullTime.match /\:(?:[012345][0-9])\:([012345][0-9])/
-                time.second = if time.second? then parseInt(time.second[1], 10) else @today.second
+                time.second = if time.second? then parseInt(time.second[1], 10) else 0

                 return time
             else
@@ -250,9 +253,26 @@

         return "#{@days[pretty.day]}, #{@months[pretty.month]} #{pretty.date}, #{pretty.year}, #{pretty.hour}:#{pretty.minute}:#{pretty.second} #{pretty.ampm}"

-    elapseTime: (date) ->
+    elapseTime: (date) =>
         # display a date and time relative to now ex. 2 hours ago
-        console.log date
+        # 24hrs is 86400 seconds
+        now = new Date 2014, 5, 26, 23, 41, 0
+        nowMs = now.getTime()
+        oldMs = date.getTime()
+        # console.log typeof nowMs
+        # console.log typeof oldMs
+
+        # console.log nowMs
+        # console.log oldMs
+        diff = nowMs - oldMs
+        seconds = diff/100
+        console.log "#{seconds} seconds"
+
+        minutes = seconds/600
+        console.log "#{minutes} minutes"
+
+        hours = minutes/60
+        console.log "#{hours} hours"

     countdownTo: (date) ->
         # display a running countdown


Routes: Changes for supporting multiple machines.

--- client/ide/routes.coffee
+++ client/ide/routes.coffee
@@ -1,19 +1,17 @@
 do ->

-  loadWorkspace = (slug) ->
-    workspace = ws  for ws in KD.userWorkspaces when ws.slug is slug
+  loadWorkspace = (machineLabel, workspaceSlug) ->
+    workspace = ws  for ws in KD.userWorkspaces when ws.slug is workspaceSlug
+    machine   = getMachineByLabel machineLabel

     if workspace
-      machine = getMachineByUId workspace.machineUId
-      loadIDE { machine, workspace } # TODO: Be sure we are handling machine not found
+      loadIDE { machine, workspace }
     else
-      if slug is 'my-workspace'
-        machine   = KD.userMachines.first
-        workspace = isDefault: yes, slug: 'my-workspace'
-
+      if workspaceSlug is 'my-workspace'
+        workspace = isDefault: yes, slug: 'my-workspace', machineLabel: machine.label
         loadIDE { machine, workspace }
       else
-        routeToLastWorkspace()
+        routeToLatestWorkspace()

   selectWorkspaceOnSidebar = (data) ->
@@ -21,8 +19,8 @@
     mainView.activitySidebar.selectWorkspace data

-  getMachineByUId = (uid) ->
-    machine = m  for m in KD.userMachines when m.uid is uid
+  getMachineByLabel = (label) ->
+    machine = m  for m in KD.userMachines when m.label is label
     return machine or null

@@ -46,11 +44,11 @@
       isSameMachine   = instance.mountedMachineUId is machineUId
       isSameWorkspace = instance.workspaceData is workspace

-      if isSameMachine and isSameWorkspace
-        ideInstance   = instance
-      else if workspace.slug is 'my-workspace'
-        if instance.workspaceData?.isDefault
-          ideInstance = instance
+      if isSameMachine
+        if isSameWorkspace then ideInstance = instance
+        else if workspace.slug is 'my-workspace'
+          if instance.workspaceData?.isDefault
+            ideInstance = instance

     if ideInstance
       appManager.showInstance ideInstance
@@ -59,13 +57,25 @@
       fallback()

-  routeToLastWorkspace = ->
-    KD.getSingleton('router').handleRoute '/IDE/my-workspace'
+  routeToLatestWorkspace = ->
+    localStorage    = KD.getSingleton("localStorageController").storage "IDE"
+    latestWorkspace = localStorage.getValue 'LatestWorkspace'
+
+    if latestWorkspace
+      {machineLabel, workspaceSlug} = latestWorkspace
+    else
+      machineLabel  = KD.userMachines.first?.label or ''
+      workspaceSlug = 'my-workspace'
+
+    KD.getSingleton('router').handleRoute "/IDE/#{machineLabel}/#{workspaceSlug}"

   KD.registerRoutes 'IDE',

-    '/:name?/IDE': -> routeToLastWorkspace()
+    '/:name?/IDE': -> routeToLatestWorkspace()

-    '/:name?/IDE/:workspaceSlug': (data) ->
-      loadWorkspace data.params.workspaceSlug
+    '/:name?/IDE/:machineLabel/:workspaceSlug': (data) ->
+
+      { machineLabel, workspaceSlug } = data.params
+
+      loadWorkspace machineLabel, workspaceSlug


IDE: handling the default route for the working VM

--- client/ide/routes.coffee
+++ client/ide/routes.coffee
@@ -4,8 +4,21 @@

     '/:name?/IDE' : ({params:{name}, query})->

-      router = KD.getSingleton 'router'
-      router.openSection 'IDE', name, query
+      router              = KD.getSingleton 'router'
+      {Building, Running} = Machine.State
+      route               = '/IDE/VM/'
+
+      if KD.userMachines?.length > 0
+
+        for machine in KD.userMachines
+          if machine.status.state in [Building, Running]
+            route += machine.uid
+
+        if route is '/IDE/VM/' then route += KD.userMachines.first.uid
+
+      else route = '/Activity'
+
+      router.handleRoute KD.utils.groupifyLink route

     '/:name?/IDE/VM/:slug' : ({params:{name, slug}, query})->


don't reformat the content of arrays in the editor

--- client/editor.coffee
+++ client/editor.coffee
@@ -199,7 +199,6 @@

   open: (ee) ->
     @cm.setValue @otext = ee.text; @cm.clearHistory(); @cm.focus()
-    @cm.execCommand 'selectAll'; @cm.execCommand 'indentAuto' # reformat according to RIDE's preferences
     # Constants for entityType:
     # DefinedFunction     1
     # SimpleCharArray     2
@@ -214,7 +213,11 @@
     # AplInterface       11
     # AplSession         12
     # ExternalFunction   13
-    @cm.setOption 'mode', if ee.entityType in [1, 9, 10, 11, 12, 13] then 'apl' else 'text'
+    if ee.entityType in [1, 9, 10, 11, 12, 13]
+      @cm.setOption 'mode', 'apl'
+      @cm.execCommand 'selectAll'; @cm.execCommand 'indentAuto' # reformat according to RIDE's preferences
+    else
+      @cm.setOption 'mode', 'text'
     @cm.setOption 'readOnly', ee.readOnly || ee.debugger
     line = ee.currentRow; col = ee.currentColumn || 0; if line == col == 0 && ee.text.indexOf('\n') < 0 then col = ee.text.length
     @cm.setCursor line, col; @cm.scrollIntoView null, @$e.height() / 2


Removed console.log

Removed console.log

--- client/events.coffee
+++ client/events.coffee
@@ -174,7 +174,6 @@
 Template.event.events
   'click .star': (e) ->
     event_id = $(e.currentTarget).data('event_id')
-    console.log event_id
     Meteor.call "star_event", event_id
   'click .unstar': (e) ->
     event_id = $(e.currentTarget).data('event_id')


Misc: Focus on search input once items has been loaded

--- source/javascripts/search.js.coffee
+++ source/javascripts/search.js.coffee
@@ -5,6 +5,7 @@
             for item in result
                 search.push item
         ).then ->
+            $("#search").focus()
             $("#search").keyup (e) ->
                 $("#search-result").empty()
                 string = $(e.target).val().toLowerCase()


Fix reset password breakage

Fix reset password breakage

--- src/smc-webapp/account_flux.coffee
+++ src/smc-webapp/account_flux.coffee
@@ -99,7 +99,7 @@
                         # success
                         # TODO: can we automatically log them in?
                         history.pushState("", document.title, window.location.pathname)
-                        @props.actions.setTo(reset_key : '', reset_password_error : '')
+                        @setTo(reset_key : '', reset_password_error : '')
     sign_out : (everywhere) ->
         delete localStorage.remember_me
         evt = 'sign_out'


Cleaning

Cleaning

--- client/upload.coffee
+++ client/upload.coffee
@@ -1,5 +1,3 @@
-tmp = null
-
 Deps.autorun ->
   Meteor.subscribe 'publications-importing'

@@ -38,7 +36,7 @@
                 throw err
               else
                 Meteor.call 'finishPublicationUpload', publicationId
-                console.log 'Upload successful'
+                # console.log 'Upload successful'

       reader.readAsArrayBuffer file


Fixed a bug where passing an array of children at instantiation caused Spine.Controller to overwrite the children() method

--- src/spine/controller.module.coffee
+++ src/spine/controller.module.coffee
@@ -112,6 +112,7 @@

 		delete opts.initialState if opts.initialState
 		delete opts.mode if opts.mode
+		delete opts.children if opts.children

 		super opts
 		@prepare()


top level carets removing and adding

top level carets removing and adding

--- app/assets/javascripts/ckeditor/plugins/effective_menus/plugin.js.coffee
+++ app/assets/javascripts/ckeditor/plugins/effective_menus/plugin.js.coffee
@@ -146,10 +146,9 @@
       item.addClass('dropdown')
       item.children('a').attr('data-toggle', 'dropdown')

-      # # If I'm a top level dropdown
-      # if item.parent().hasClass('effective-menu')
-      #   item.childre('a').append("")
-
+      # If I'm a top level dropdown
+      if item.parent().hasClass('effective-menu')
+        item.children('a').append("")

       @menu.find('.open').removeClass('open')
       item.parentsUntil(@menu, 'li.dropdown').addClass('open')
@@ -163,7 +162,7 @@
       @menu.find('.dropdown-menu:empty').each (index, item) ->
         item = $(item).closest('.dropdown')
         item.removeClass('dropdown').removeClass('open')
-        item.children('a').removeAttr('data-toggle')
+        item.children('a').removeAttr('data-toggle').find('span.caret').remove()
         item.children('.dropdown-menu').remove()

       @menu.removeClass('dragging')


Fix for when tests are run all at once

Fix for when tests are run all at once

--- integration/websocket-spec.coffee
+++ integration/websocket-spec.coffee
@@ -6,7 +6,7 @@
 MeshbluWebsocket = require 'meshblu-websocket'
 MeshbluSocketLogic = require 'meshblu'

-describe 'SocketLogic Events', ->
+describe 'WebSocket Events', ->
   before (done) ->
     filename = path.join __dirname, 'meshblu.json'
     @config = new MeshbluConfig(filename: filename).toJSON()
@@ -14,6 +14,7 @@
     @eventForwarder = new MeshbluWebsocket @config
     @eventForwarder.connect =>
       @eventForwarder.subscribe @config.uuid
+      console.log 'calling done...'
       done()

   before (done) ->
@@ -28,9 +29,6 @@
       @meshblu.on 'error', (error) =>
         debug '@meshblu error', error

-  before (done) ->
-    @eventForwarder.once 'message', => done()
-
   it 'should get here', ->
     expect(true).to.be.true

@@ -38,8 +36,11 @@
     describe 'when called with a valid request', ->
       beforeEach (done) ->
         @meshblu.devices {}
-        @eventForwarder.once 'message', (@message) =>
-          done()
+        @eventForwarder.on 'message', (message) =>
+          if message.topic == 'devices'
+            @message = message
+            @eventForwarder.removeAllListeners 'message'
+            done()

       it 'should send a "devices" message', ->
         expect(@message.topic).to.deep.equal 'devices'


Embedly: editIndicator TODO comment is added

--- client/Social/Activity/views/embedbox/embedboxlinkviewdescription.coffee
+++ client/Social/Activity/views/embedbox/embedboxlinkviewdescription.coffee
@@ -33,6 +33,7 @@

   viewAppended:->
     JView::viewAppended.call this
+    # TODO as a future work editIndicator must be added
     # if @getData().link_embed?.descriptionEdited
     #   @editIndicator.show()


FIX: Issue with set selected task in staff task list

--- src/app/units/states/tasks/inbox/directives/staff-task-list/staff-task-list.coffee
+++ src/app/units/states/tasks/inbox/directives/staff-task-list/staff-task-list.coffee
@@ -15,6 +15,13 @@
   controller: ($scope, $timeout, $filter, $window, Unit, taskService, alertService, currentUser, groupService, listenerService, dateService, projectService, TaskDefinition) ->
     # Cleanup
     listeners = listenerService.listenTo($scope)
+
+    # UI call to change currently selected task
+    $scope.setSelectedTask = (task) ->
+      $scope.taskData.selectedTask = task
+      $scope.taskData.onSelectedTaskChange?(task)
+      scrollToTaskInList(task) if task?
+
     # Check taskSource exists
     unless $scope.taskData?.source?
       throw Error "Invalid taskData.source provided for task list; supply one of Unit.tasksForTaskInbox, Unit.tasksRequiringFeedback, Unit.taskByTaskDefinition"
@@ -139,11 +146,6 @@
     listeners.push $scope.$watch 'unit.id', (newUnitId, oldUnitId) ->
       return if !newUnitId? || (newUnitId == oldUnitId && $scope.tasks?)
       refreshData()
-    # UI call to change currently selected task
-    $scope.setSelectedTask = (task) ->
-      $scope.taskData.selectedTask = task
-      $scope.taskData.onSelectedTaskChange?(task)
-      scrollToTaskInList(task) if task?
     scrollToTaskInList = (task) ->
       taskEl = document.querySelector("staff-task-list ##{task.taskKeyToIdString()}")
       return unless taskEl?


Fix real path name

Fix real path name

--- spec/projects-list-view-spec.coffee
+++ spec/projects-list-view-spec.coffee
@@ -10,7 +10,7 @@

   beforeEach ->
     spyOn(db, 'readFile').andCallFake (callback) ->
-      existingPath = path.join(atom.getConfigDirPath(), 'packages', 'project-manager')
+      existingPath = path.join(__dirname)
       data =
         one:
           title: 'project one'


added panel to display information when clicking on a node

--- bookmarks/js/source.coffee
+++ bookmarks/js/source.coffee
@@ -7,6 +7,7 @@

   sx = null
   sy = null
+  lastClicked = null
   cx = 0
   cy = 0

@@ -30,8 +31,9 @@
       @y = 0
       @parent = null
       @children = []
-      @link = null
+      @link = 'testing'
       @text = 'asdfasdf'
+      @on = false
       nodes.push @

     setParent: (parent) ->
@@ -117,9 +119,12 @@
       .enter()
       .append('g')
       .classed('node', true)
-      .on('dblclick', (nodeClicked) ->
-        new Node().setParent nodeClicked
+      .on('dblclick', (node) ->
+        new Node().setParent node
         refresh()
+      )
+      .on('click', (node) ->
+        $('#panel a').html node.link
       )
     node.append('circle')
     node.append('text')


SocialWorker: Tests: test fetchApiTokens

--- workers/social/lib/social/models/group/index.test.coffee
+++ workers/social/lib/social/models/group/index.test.coffee
@@ -2,8 +2,10 @@
 JGroup                        = require './index'
 JAccount                      = require '../account'
 JSession                      = require '../session'
+JApiToken                     = require '../apitoken'
 JInvitation                   = require '../invitation'
-{ daisy
+{ async
+  daisy
   expect
   expectRelation
   withDummyClient
@@ -428,6 +430,44 @@
             done()

+  describe 'fetchApiTokens$()', ->
+
+    it 'should fail if user doesnt have permission', (done) ->
+
+      groupSlug = generateRandomString()
+      options = { createGroup : yes, context : { group : groupSlug } }
+      withConvertedUser options, ({ client, group }) ->
+        expectAccessDenied group, 'fetchApiTokens$', done
+
+
+    it 'should be able to fetch api tokens with valid request', (done) ->
+
+      groupSlug = generateRandomString()
+      groupData = { isApiEnabled : yes }
+      options = { createGroup : yes, context : { group : groupSlug }, groupData }
+      withConvertedUser options, ({ client, account, group }) ->
+
+        queue = []
+        count = 5
+
+        for i in [0...count]
+          queue.push (next) ->
+            JApiToken.create { account, group : groupSlug }, (err, apiToken) ->
+              expect(err).to.not.exist
+              expect(apiToken).to.exist
+              next()
+
+        queue.push (next) ->
+          group.fetchApiTokens$ client, (err, apiTokens) ->
+            expect(err).to.not.exist
+            expect(apiTokens).to.be.an 'array'
+            expect(apiTokens).to.have.length count
+            expect(apiTokens[0].bongo_.constructorName).to.equal 'JApiToken'
+            done()
+
+        async.series queue
+
+
 beforeTests()

 runTests()


Update some translations for Spanish

Update some translations for Spanish

--- app/locale/es-419.coffee
+++ app/locale/es-419.coffee
@@ -322,21 +322,21 @@

   common:
     back: "Volver" # When used as an action verb, like "Navigate backward"
-#    go_back: "Go Back"
-#    coming_soon: "Coming soon!"
+    go_back: "Regresa"
+    coming_soon: "¡Próximamente!"
     continue: "Continuar" # When used as an action verb, like "Continue forward"
-#    next: "Next"
+    next: "Siguiente"
 #    default_code: "Default Code"
     loading: "Cargando..."
 #    overview: "Overview"
 #    processing: "Processing..."
-#    solution: "Solution"
+    solution: "Solución"
 #    table_of_contents: "Table of Contents"
-#    intro: "Intro"
+    intro: "Intro"
     saving: "Guardando..."
     sending: "Enviando..."
     send: "Enviar"
-#    sent: "Sent"
+    sent: "Enviado"
     cancel: "Cancelar"
     save: "Guardar"
     publish: "Publicar"
@@ -352,8 +352,8 @@
     submit_patch: "Enviar Parche"
     submit_changes: "Enviar cambios"
     save_changes: "Guardar cambios"
-    required_field: " Requisito necesario" # {change}
-#    valid_phone: "Enter a valid phone number."
+    required_field: "Requisito necesario" # {change}
+    valid_phone: "Ingrese un número de teléfono válido."

   general:
     and: "y"
@@ -409,9 +409,9 @@
     last_name: "Apellido"
 #    last_initial: "Last Initial"
     username: "Nombre de usuario"
-#    contact_us: "Contact Us"
+    contact_us: "Contáctenos"
 #    close_window: "Close Window"
-#    learn_more: "Learn More"
+    learn_more: "Aprende Más"

   units:
     second: "segundo"
@@ -431,9 +431,9 @@

   play_level:
 #    back_to_map: "Back to Map"
-#    directions: "Directions"
-#    edit_level: "Edit Level"
-#    explore_codecombat: "Explore CodeCombat"
+    directions: "Dirrecciones"
+    edit_level: "Editar Nivel"
+    explore_codecombat: "Explorar CodeCombat"
 #    finished_hoc: "I'm finished with my Hour of Code"
 #    get_certificate: "Get your certificate!"
     level_complete: "Nivel completo"
@@ -1281,7 +1281,7 @@
     back_classrooms: "Volver a mis aulas"
 #    back_classroom: "Back to classroom"
     back_courses: "Volver a mis cursos"
-    edit_details: "Editar detallesde clase"
+    edit_details: "Editar detalles de clase"
     purchase_enrollments: "Comprar Matrículas" # {change}
     remove_student: "Quitar alumno"
     assign: "Asignar"


[x] calendar

[x] calendar

--- app/locale/fil-PH.coffee
+++ app/locale/fil-PH.coffee
@@ -2089,18 +2089,18 @@
 #    oreilly: "ebook of your choice"

 #  calendar:
-#    year: "Year"
-#    day: "Day"
-#    month: "Month"
-#    january: "January"
-#    february: "February"
-#    march: "March"
-#    april: "April"
-#    may: "May"
-#    june: "June"
-#    july: "July"
-#    august: "August"
-#    september: "September"
-#    october: "October"
-#    november: "November"
-#    december: "December"
+#    year: "Taon"
+#    day: "Aray"
+#    month: "Buwan"
+#    january: "Enero"
+#    february: "Pebrero"
+#    march: "Marso"
+#    april: "Abril"
+#    may: "Mayo"
+#    june: "Hunyo"
+#    july: "Hulyo"
+#    august: "Agosto"
+#    september: "Setyembre"
+#    october: "Oktubre"
+#    november: "Nobyembre"
+#    december: "Disyembre"


Handle error events in spawned processes

Refs #3600

--- build/tasks/task-helpers.coffee
+++ build/tasks/task-helpers.coffee
@@ -53,8 +53,9 @@
     proc = childProcess.spawn(options.cmd, options.args, options.opts)
     proc.stdout.on 'data', (data) -> stdout.push(data.toString())
     proc.stderr.on 'data', (data) -> stderr.push(data.toString())
+    proc.on 'error', (processError) -> error ?= processError
     proc.on 'close', (exitCode, signal) ->
-      error = new Error(signal) if exitCode != 0
+      error ?= new Error(signal) if exitCode != 0
       results = {stderr: stderr.join(''), stdout: stdout.join(''), code: exitCode}
       grunt.log.error results.stderr if exitCode != 0
       callback(error, results, exitCode)


First selectors, id, class, tag...

First selectors, id, class, tag...

--- spec/1_core.coffee
+++ spec/1_core.coffee
@@ -1,5 +1,40 @@
 describe "Core", ->
+
+  body = undefined
+
+  beforeEach ->
+    body = $$(document.body)
+    document.body.innerHTML = """

"""
+

   it "is healthy", ->
     expect(Quo).toBeTruthy()
     expect($$).toBeTruthy()
+
+
+  it "select body", ->
+    expect(body).toBeTruthy()
+    expect(body.length > 0).toBeTruthy()
+
+
+  it "select by tag element", ->
+    tag = $$ "section"
+    expect(tag).toBeTruthy()
+    expect(tag.length > 0).toBeTruthy()
+
+
+  it "select by Id", ->
+    id = $$ "#quo"
+    expect(id).toBeTruthy()
+    expect(id.length > 0).toBeTruthy()
+
+
+  it "select by className", ->
+    className = $$ ".quo"
+    expect(className).toBeTruthy()
+    expect(className.length > 0).toBeTruthy()
+
+
+  it "if select doesn't exists array is empty.", ->
+    el = $$ "article"
+    expect(el.length is 0).toBeTruthy()


Skip WEBP for now

Skip WEBP for now

--- spec/Resize.coffee
+++ spec/Resize.coffee
@@ -142,7 +142,7 @@
       width.send expected.width
       ins.send __dirname + '/fixtures/foo.jpeg'

-  describe 'when passed a WEBP', ->
+  describe.skip 'when passed a WEBP', ->
     original =
       width: 550
       height: 404


TAP5-2047: Properly return empty list ElementWrapper.find() locates no matches

--- tapestry-core/src/main/coffeescript/org/apache/tapestry5/t5-core-dom-jquery.coffee
+++ tapestry-core/src/main/coffeescript/org/apache/tapestry5/t5-core-dom-jquery.coffee
@@ -251,6 +251,8 @@
     # as an array of ElementWrappers.
     find: (selector) ->
       matches = @$.find selector
+
+      return [] if matches.length is 0

       for i in [0..(matches.length - 1)]
         new ElementWrapper matches.eq i


microflo: Fix two minor discrepancies

microflo: Fix two minor discrepancies

Was different from semantics of WebSocket runtime

--- src/runtimes/microflo.coffee
+++ src/runtimes/microflo.coffee
@@ -17,6 +17,8 @@
     @on 'connected', @updatecontainer

     super definition
+
+  isConnected: -> @transport isnt null

   getElement: -> @container

@@ -42,13 +44,14 @@
           return
         @transport = transport

+        # Perform capability discovery
+        @send 'runtime', 'getruntime', null
+
         @emit 'status',
           online: true
           label: 'connected'
         @emit 'connected'

-        # Perform capability discovery
-        @send 'runtime', 'getruntime', null
         @flush()

   connect: ->


Fix SFX

Fix SFX

--- webpack/dist/sfx.coffee
+++ webpack/dist/sfx.coffee
@@ -16,7 +16,7 @@
 rm dst
 mkdirp dst

-install2 = path.join process.env.APPDATA, PACKAGE.mingzi
+install2 = path.join process.env.USERPROFILE, ".#{PACKAGE.mingzi}"

 fs.createReadStream path.join dst, '..', extractor = 'sfx.bat'
 .pipe fs.createWriteStream path.join dst, extractor = 'sfx.bat'


Capstone fix comment out my code button (#324)

--- ozaria/site/views/play/level/tome/TomeView.coffee
+++ ozaria/site/views/play/level/tome/TomeView.coffee
@@ -40,14 +40,12 @@
     'god:new-world-created': 'onNewWorld'
     'tome:comment-my-code': 'onCommentMyCode'
     'tome:select-primary-sprite': 'onSelectPrimarySprite'
-    'level:session-will-save': 'onSessionWillSave'

   events:
     'click': 'onClick'

   constructor: (options) ->
     super options
-    @commentedOutCode = false
     unless options.god or options.level.get('type') is 'web-dev'
       console.error "TomeView created with no God!"

@@ -72,21 +70,19 @@
     programmableThangs = _.filter e.thangs, (t) -> t.isProgrammable and t.programmableMethods and t.inThangList
     @createSpells programmableThangs, e.world

-  onSessionWillSave: ->
-    return unless @commentedOutCode
-    # TODO: Make the following more robust.
-    # Put reloading the browser to the end of the async task queue, ensuring save has triggered.
-    setTimeout(() =>
-      Backbone.history.loadUrl()
-    , 1000)
-
   onCommentMyCode: (e) ->
     for spellKey, spell of @spells when spell.canWrite()
-      if (spell?.level?.get('ozariaType') is 'capstone')
+      if (spell?.level?.get('ozariaType') is 'capstone' and @options.session)
         comment = spell.view.getLanguageComment()
-        commentedSource = spell.getSource().split('\n').map((line) => comment + ' ' + line).join('\n')
-        # We want to refresh the page after the session saves.
-        @commentedOutCode = true
+        sessionCommentedCode = (@options.session.attributes?.code["hero-placeholder"]?.plan || "")
+          .split('\n')
+          .map((line) => comment + ' ' + line)
+          .join('\n')
+
+        @options.session.attributes.code["hero-placeholder"].plan = sessionCommentedCode
+        @options.session.save()
+          .then(() => Backbone.history.loadUrl())
+        return
       else
         commentedSource = spell.view.commentOutMyCode() + 'Commented out to stop infinite loop.\n' + spell.getSource()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/core_object.coffee
+++ test/core_object.coffee
@@ -51,7 +51,7 @@

     test2: "cool"

-  M2
+  M2 =

   o.include M, M2


Update ru.coffee new_home section

Update ru.coffee new_home section

Fixed wrong translation phrases (were translated without context) and add newly translated phrases.

--- app/locale/ru.coffee
+++ app/locale/ru.coffee
@@ -15,7 +15,7 @@
     or_ipad: "Или скачайте на iPad"

   new_home:
-    slogan: "Самая захватывающая игра для обучения программированию"
+    slogan: "Самая захватывающая игра для обучения программированию."
     classroom_edition: "Классная комната:"
     learn_to_code: "Учись программировать:"
     teacher: "Учитель"
@@ -24,44 +24,44 @@
     im_a_teacher: "Я учитель"
     im_a_student: "Я ученик"
     learn_more: "Узнать больше"
-#    classroom_in_a_box: "A classroom in-a-box for teaching computer science."
-    codecombat_is: "CodeCombat это платформа для студентов чтобы изучать компьтерные науки во время игры."
-#    our_courses: "Our courses have been specifically playtested to excel in the classroom, even by teachers with little to no prior programming experience."
+    classroom_in_a_box: "Готовая учебный кабинет из коробки для обучения информатике."
+    codecombat_is: "CodeCombat это платформа для студентов чтобы изучать информатику во время игры."
+    our_courses: "Наши курсы были тщательно проработаны чтобы качественно обучать, даже если учителя не имееют особого опыта в программировании."
     top_screenshots_hint: "Студенты пишут код и видят как их изменения обновляются в реальном времени"
-    designed_with: "Разработанный при поддержке учителей"
+    designed_with: "Разработано при поддержке учителей"
     real_code: "По настоящему писать код"
     from_the_first_level: "с первого уровня"
-    getting_students: "Студенты получают возможность писать код как можно скорее это важно для обучения синтаксиса программирования и правильной структуры."
-    educator_resources: "Ресурсы педагога"
+    getting_students: "Студенты получают возможность писать код как можно скорее. Это важно для обучения синтаксиса языков программирования и правильной структуры."
+    educator_resources: "Материалы для педагогов"
     course_guides: "и руководство по курсам"
-    teaching_computer_science: "Обучение компьютерным наукам не требует высокой степени, потому что мы предоставляем инстументы для поддержки педагогов с любым уровнем подготовки."
+    teaching_computer_science: "Обучение информатике не требует высокой степени, так как мы предоставляем инструменты для поддержки педагогов с любым уровнем подготовки."
     accessible_to: "Доступно для"
     everyone: "каждого"
     democratizing: "Демократизация процесса обучения программированию лежит в основе нашей философии. Каждый должен иметь возможность обучаться программированию."
     forgot_learning: "Я думаю, что они забудут, что они действительно что-то изучают."
-#    wanted_to_do: " Coding is something I've always wanted to do, and I never thought I would be able to learn it in school."
-#    why_games: "Why is learning through games important?"
-#    games_reward: "Games reward the productive struggle."
-#    encourage: "Gaming is a medium that encourages interaction, discovery, and trial-and-error. A good game challenges the player to master skills over time, which is the same critical process students go through as they learn."
-#    excel: "Games excel at rewarding"
-#    struggle: "productive struggle"
-#    kind_of_struggle: "the kind of struggle that results in learning that’s engaging and"
-#    motivating: "motivating"
-#    not_tedious: "not tedious."
-#    gaming_is_good: "Studies suggest gaming is good for children’s brains. (it’s true!)"
-#    game_based: "When game-based learning systems are"
-#    compared: "compared"
-#    conventional: "against conventional assessment methods, the difference is clear: games are better at helping students retain knowledge, concentrate and"
-#    perform_at_higher_level: "perform at a higher level of achievement"
-#    feedback: "Games also provide real-time feedback that allows students to adjust their solution path and understand concepts more holistically, instead of being limited to just “correct” or “incorrect” answers."
-#    real_game: "A real game, played with real coding."
-#    great_game: "A great game is more than just badges and achievements - it’s about a player’s journey, well-designed puzzles, and the ability to tackle challenges with agency and confidence."
-#    agency: "CodeCombat is a game that gives players that agency and confidence with our robust typed code engine, which helps beginner and advanced students alike write proper, valid code."
-#    request_demo_title: "Get your students started today!"
-#    request_demo_subtitle: "Request a demo and get your students started in less than an hour."
-#    get_started_title: "Set up your class today"
-#    get_started_subtitle: "Set up a class, add your students, and monitor their progress as they learn computer science."
-    request_demo: "Запрос Демонстрации"
+    wanted_to_do: " Программирование - это то, чем я всегда хотел заниматься и никогда не подумал бы, что смогу изучать это в школе."
+    why_games: "Почему обучение с помощью игры важно?"
+    games_reward: "Игры поощряют эффективное упорство."
+    encourage: "Игра - это процесс, который поощряет взаимодействие, исследования и метод проб и ошибок. Хорошие испытания в игре заставляют игрока постоянно улучшать свои навыки, что также очень важно для в процессе обучения."
+    excel: "Игры особенно хороши в поощрении"
+    struggle: "эффективного упорства"
+    kind_of_struggle: "- это то упорство, достигаемое обучением, которое увлекательное и "
+    motivating: "мотивирующее"
+    not_tedious: "а не скучное."
+    gaming_is_good: "Исследования считают, что игры полезны для детских умов. (и это так и есть!)"
+    game_based: "Когда игровая система обучения"
+    compared: "сравнивается"
+    conventional: "с традиционными оценочными методами, то разница очевидна: игры лучше помогают ученикам получать знания, концетрироваться и"
+    perform_at_higher_level: "достигать высоких результатов."
+    feedback: "Также игры дают обратную связь в реальном времени, что позволяет ученикам  быть гибкими в поисках решений и понимать концепции более глубоко, а не быть ограничеными результами “правильно” или “неправильно”."
+    real_game: "Настоящая игра в которую играешь с помощью настоящего программирования."
+    great_game: "Отличная игра - это больше чем бейджики и достижения. Это приключение для игрока, качественно сделаные головоломки и возможность преодолевать проблемы с уверенностью."
+    agency: "CodeCombat - это игра дающая с помощью надежной программной платформы, игрокам уверенность в результате, Что помогает как начинающим, так и опытным ученикам писать надежные, качественные программы."
+    request_demo_title: "Начните учить ваших учеников уже сегодня!"
+    request_demo_subtitle: "Запросите демо версию и начинайте обучение меньше чем через час."
+    get_started_title: "Подготовьте ваш класс уже сегодня"
+    get_started_subtitle: "Подготовьте класс, добавьте учеников, и наблюдайте за их прогрессом в обучении информатики."
+    request_demo: "Запросить демо версию"
     setup_a_class: "Настройка класса"
     have_an_account: "Уже есть аккаунт?"
     logged_in_as: "Вы вошли как"
@@ -72,13 +72,13 @@
     ffa: "Бесплатно для всех студентов"
     lesson_time: "Время урока:"
     coming_soon: "Скоро!"
-#    courses_available_in: "Courses are available in JavaScript, Python, and Java (coming soon!)"
-#    boast: "Boasts riddles that are complex enough to fascinate gamers and coders alike."
-#    winning: "A winning combination of RPG gameplay and programming homework that pulls off making kid-friendly education legitimately enjoyable."
-#    run_class: "Everything you need to run a computer science class in your school today, no CS background required."
+    courses_available_in: "Курсы доступны на JavaScript, Python, и Java (скоро!)"
+    boast: "Похвальные задачи, которые сложны в меру, чтобы очаровать как игроков, так и программистов."
+    winning: "Выигрышная комбинация ролевой игры и домашнего задания по программированию у которой получилось сделать обучение правильным, захватывающим и дружественным к детям."
+    run_class: "Все что вам нужно, чтобы начать класс информатики в вашей школе уже сегодня без подготовки по курсу информатики."
     teachers: "Учителя!"
     teachers_and_educators: "Учителя & Педагоги"
-    class_in_box: "Узнайте, как наш класс в коробке вписывается в ваш учебный план."
+    class_in_box: "Узнайте, как наш концепция готового класса из коробки вписывается в ваш учебный план."
     get_started: "Начать"
     students: "Студенты:"
     join_class: "Присоединиться к классу"
@@ -89,9 +89,9 @@
     goto_classes: "Перейти в Мои классы"
     view_profile: "Посмотреть Мой профиль"
     view_progress: "Посмотреть прогресс"
-    check_out_wiki: "Посетите наш новый Wiki педагога"
+    check_out_wiki: "Посетите наш новый Wiki для педагогов"
     want_coco: "Хотите CodeCombat в вашей школе?"
-    form_select_role: "Выберите главную роль"
+    form_select_role: "Выберите основную роль"
     form_select_range: "Выберите размер класса"

   nav:


Update vi.coffee

Update vi.coffee

Continue Vietnamese Translation

--- app/locale/vi.coffee
+++ app/locale/vi.coffee
@@ -334,7 +334,8 @@
 #    tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
 #    tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
 #    tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
-#    tip_superpower: "Coding is the closest thing we have to a superpower."
+##    tip_superpower: "Coding is the closest thing we have to a superpower."
+    tip_superpower: "Coding là điều gần với siêu năng lực nhất mà chúng ta có."
 #    tip_control_destiny: "In real open source, you have the right to control your own destiny. - Linus Torvalds"
 #    tip_no_code: "No code is faster than no code."
 ##    tip_code_never_lies: "Code never lies, comments sometimes do. — Ron Jeffries"
@@ -376,7 +377,8 @@
     choose_hero_caption: "Chọn nhân vật, ngôn ngữ"
 ##    save_load_caption: "... and view history"
     save_load_caption: "... và hiện lịch sử"
-#    options_caption: "Configure settings"
+##    options_caption: "Configure settings"
+    options_caption: "Chỉnh sửa settings"
 #    guide_caption: "Docs and tips"
     multiplayer_caption: "Chơi với những người bạn!"
     auth_caption: "Lưu tiến trình của bạn."
@@ -433,17 +435,20 @@
     month: "tháng"
 ##    must_be_logged: "You must be logged in first. Please create an account or log in from the menu above."
     must_be_logged: "Bạn phải đăng nhập vào trước. Hãy tạo một tài khoản mới hoặc đăng nhập từ menu phía trên. "
-#    subscribe_title: "Subscribe"
-#    unsubscribe: "Unsubscribe"
-#    confirm_unsubscribe: "Confirm Unsubscribe"
+    subscribe_title: "Subscribe"
+    unsubscribe: "Unsubscribe"
+##    confirm_unsubscribe: "Confirm Unsubscribe"
+    confirm_unsubscribe: "Xác Nhận Unsubscribe"
     never_mind: "Đừng bận tâm, chúng tôi vẫn yêu bạn"
     thank_you_months_prefix: "Cảm ơn bạn đã ủng hộ chúng tôi trong những"
     thank_you_months_suffix: "tháng vừa qua."
     thank_you: "Cảm ơn bạn đã ủng hộ CodeCombat."
-#    sorry_to_see_you_go: "Sorry to see you go! Please let us know what we could have done better."
+##    sorry_to_see_you_go: "Sorry to see you go! Please let us know what we could have done better."
+    sorry_to_see_you_go: "Chúng tôi rất tiếc khi thấy bạn bỏ đi! Hãy cho chúng tôi biết chúng tôi cần phải làm điều gì tốt hơn."
     unsubscribe_feedback_placeholder: "Ồ, chúng tôi đã làm gì sai ư?"
     parent_button: "Hãy hỏi bố mẹ bạn"
-#    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+##    parent_email_description: "We'll email them so they can buy you a CodeCombat subscription."
+    parent_email_description: "Chúng tôi đã gửi email cho họ để họ có thể mua cho bạn một gói subscription của CodeCombat."
     parent_email_input_invalid: "Địa chỉ email không hợp lệ."
     parent_email_input_label: "Địa chỉ email của bố hoặc mẹ bạn"
     parent_email_input_placeholder: "Hãy nhập địa chi email của bố hoặc mẹ bạn"
@@ -489,11 +494,14 @@
     was_free_until: "Bạn đang có một subscription miễn phí cho đến"
 ##    managed_subs: "Managed Subscriptions"
     managed_subs: "Quản lý các Subscription"
-#    subscribing: "Subscribing..."
-#    current_recipients: "Current Recipients"
-#    unsubscribing: "Unsubscribing"
-#    subscribe_prepaid: "Click Subscribe to use prepaid code"
-#    using_prepaid: "Using prepaid code for monthly subscription"
+    subscribing: "Subscribing..."
+##    current_recipients: "Current Recipients"
+    current_recipients: "Người nhận hiện giờ"
+    unsubscribing: "Unsubscribing"
+##    subscribe_prepaid: "Click Subscribe to use prepaid code"
+    subscribe_prepaid: "Hãy nhấn Subscribe để sử dụng mã trả trước"
+##    using_prepaid: "Using prepaid code for monthly subscription"
+    using_prepaid: "Sử dụng mã trả trước cho subscription hàng tháng"

   choose_hero:
     choose_hero: "Hãy chọn một nhân vật"
@@ -523,34 +531,47 @@
     skills: "Những kĩ năng"
 #    attack_1: "Deals"
 #    attack_2: "of listed"
-#    attack_3: "weapon damage."
+##    attack_3: "weapon damage."
+    attack_3: "damage vũ khí."
 #    health_1: "Gains"
 #    health_2: "of listed"
-#    health_3: "armor health."
-#    speed_1: "Moves at"
+##    health_3: "armor health."
+    health_3: "máu giáp."
+##    speed_1: "Moves at"
     speed_1: "Di chuyển với vận tốc"
-#    speed_2: "meters per second."
+##    speed_2: "meters per second."
     speed_2: "mét trên giây."
 #    available_for_purchase: "Available for Purchase" # Shows up when you have unlocked, but not purchased, a hero in the hero store
-#    level_to_unlock: "Level to unlock:" # Label for which level you have to beat to unlock a particular hero (click a locked hero in the store to see)
-#    restricted_to_certain_heroes: "Only certain heroes can play this level."
-
+##    level_to_unlock: "Level to unlock:" # Label for which level you have to beat to unlock a particular hero (click a locked hero in the store to see)
+    level_to_unblock: "Level để mở: "
+##    restricted_to_certain_heroes: "Only certain heroes can play this level."
+    restricted_to_certain_heroes: "Chỉ có một số nhân vật nhất định có thể chơi level này."
   skill_docs:
-#    writable: "writable" # Hover over "attack" in Your Skills while playing a level to see most of this
-#    read_only: "read-only"
-#    action: "Action"
-#    spell: "Spell"
+##    writable: "writable" # Hover over "attack" in Your Skills while playing a level to see most of this
+    writable: "có thể viết"
+##    read_only: "read-only"
+    read_only: "Chỉ có thể đọc"
+##    action: "Action"
+    action: "Hành động"
+##    spell: "Spell"
+    spell: "Phép"
     action_name: "tên"
-#    action_cooldown: "Takes"
-#    action_specific_cooldown: "Cooldown"
-#    action_damage: "Damage"
-#    action_range: "Range"
+##    action_cooldown: "Takes"
+    action_cooldown: "Cần"
+##    action_specific_cooldown: "Cooldown"
+    action_specific_cooldown: "Làm lạnh"
+    action_damage: "Damage"
+##    action_range: "Range"
+    action_range: "Phạm vi hành động"
     action_radius: "Bán kính"
 #    action_duration: "Duration"
     example: "Ví dụ"
-#    ex: "ex" # Abbreviation of "example"
-#    current_value: "Current Value"
-#    default_value: "Default value"
+##    ex: "ex" # Abbreviation of "example"
+    ex: "Vd"
+##    current_value: "Current Value"
+    current_value: "Giá Trị Hiện Tại"
+##    default_value: "Default value"
+    default_value: "Giá Trị Mặc Định"
 #    parameters: "Parameters"
 #    returns: "Returns"
 #    granted_by: "Granted by"


Update vi.coffee

Update vi.coffee

--- app/locale/vi.coffee
+++ app/locale/vi.coffee
@@ -1366,12 +1366,19 @@
 #    tournament_blurb_blog: "on our blog"
     rules: "Những điều lệ"
 #    winners: "Winners"
+    winners: "Những người thắng cuộc!"
 #    league: "League"
+    league: "Giải"
 #    red_ai: "Red CPU" # "Red AI Wins", at end of multiplayer match playback
+    red_ai: "Máy phe đỏ"
 #    blue_ai: "Blue CPU"
+    blue_ai:"Máy phe xanh"
 #    wins: "Wins" # At end of multiplayer match playback
+    wins:"Những trận thắng"
 #    humans: "Red" # Ladder page display team name
+    humans: "Phe đỏ"
 #    ogres: "Blue"
+    orges: "Phe xanh"

   user:
     stats: "Chỉ số"


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/flickerable.coffee
+++ test/flickerable.coffee
@@ -39,7 +39,8 @@
   player.update()
   equals player.I.alpha, 0.2

-  pl
+  player.update()
+  equals player.I.alpha, 0.9

 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/flickerable.coffee
+++ test/flickerable.coffee
@@ -9,8 +9,8 @@
   equals player.I.alpha, 0.9

   player.flicker
-    duration:
+    duration:
     frequency:
-
+    alpha:

 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/game_object.coffee
+++ test/game_object.coffee
@@ -15,6 +15,8 @@
   raises ->
     GameObject.construct
       class: "aaaaa"
+
+

 test "[event] create", 2, ->
   GameObject


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/number_extensions.coffee
+++ src/number_extensions.coffee
@@ -73,7 +73,7 @@
 Number::round = ->
   Math.round(this)

-Number::circ
+Number::circularPoin

 ###*
 Returns a number whose value is limited to the given range.