LikeView tooltip re-written to support guest likes correctly

--- client/landing/app/CommonViews/LikeView.coffee
+++ client/landing/app/CommonViews/LikeView.coffee
@@ -54,44 +54,60 @@
       sort  : timestamp : -1
     , (err, likes) =>

-      peopleWhoLiked = []
-      guestsWhoLiked = 0
+      users  = []
+      likers = []
+      guests = 0

       if likes

+        strong  = (x)-> "#{x}"
+
         for item in likes
-          if item.type is 'unregistered'
-            guestsWhoLiked++
-          else
-            name = KD.utils.getFullnameFromAccount item
-            peopleWhoLiked.push "#{name}"
+          name = KD.utils.getFullnameFromAccount item
+          likers.push "#{strong name}"

-        sep  = ', '
-        and_ = if peopleWhoLiked.length>0 then 'and ' else ''
+          if item.type is 'unregistered' then guests++
+          else users.push "#{strong name}"

-        guestLikes =
-          switch guestsWhoLiked
-            when 0 then ""
-            when 1 then "#{and_}a guest"
-            when 2 then "#{and_}2 guests"
-            when 3 then "3 guests"
+        if data.meta.likes > 3
+          sep = ', '
+          andMore = "and #{data.meta.likes - 3} more."
+        else
+          sep = ' and '
+          andMore = ""
+
+        # For following cases tooltip will be:
+        #
+        # 1 guest  : a guest
+        # 2 guests : 2 guests
+        # 3 guests : 3 guests
+        #
+        # 1 user   : user[0]
+        # 2 users  : user[0] and username[1]
+        # 3 users  : user[0], user[1] and user[2]
+        #
+        # 1 guest 1 users : a guest and user[0]
+        # 1 guest 2 users : a guest, user[0], and user[1] {and N-3 more}
+        # 2 guests 1 user : user[0] and 2 guests {and N-3 more}
+        #
+        # N users/guest   : user[0], user[1], user[2] and N-3 more
+        # N guests        : 3 guests and N-3 more

         tooltip =
           switch data.meta.likes
-            when 0 then "#{guestLikes}"
-            when 1 then "#{peopleWhoLiked[0]} #{guestLikes}"
-            when 2 then "#{peopleWhoLiked[0]} and #{peopleWhoLiked[1]} #{guestLikes}"
-            when 3 then "#{peopleWhoLiked[0]}#{sep}#{peopleWhoLiked[1]} and #{peopleWhoLiked[2]} #{guestLikes}"
+            when 0 then ""
+            when 1 then "#{likers[0]}"
+            when 2
+              if guests is 2 then "#{strong '2 guests'}"
+              else "#{likers[0]} and #{likers[1]}"
             else
-              # there is no guarantee that likes will not satisfy upper cases
-              # and it will be an integer which is more than 3
-              # this is also hadling the undefined case
-              if data.meta.likes > 3
-                "#{peopleWhoLiked[0]}#{sep}#{peopleWhoLiked[1]}#{sep}#{peopleWhoLiked[2]} and #{data.meta.likes - 3} more."
-              else
-                ""
+              switch guests
+                when 3 then "#{strong '3 guests'} #{andMore}"
+                when 2 then "#{users[0]}#{sep}#{strong '2 guests'} #{andMore}"
+                else "#{likers[0]}, #{likers[1]}#{sep}#{likers[2]} #{andMore}"
+
         @likeCount.getTooltip().update { title: tooltip }
-        @_lastUpdatedCount = likes.length
+        @_lastUpdatedCount = data.meta.likes

   click:(event)->
     event.preventDefault()
@@ -106,6 +122,7 @@
         unless err
           @_currentState = not @_currentState
           @likeLink.updatePartial if @_currentState is yes then "Unlike" else "Like"
+          @_lastUpdatedCount = -1

   pistachio:->
     """{{> @likeLink}}{{> @likeCount}}"""


Got rid of unneeded comments in main.coffee

--- client/src/main.coffee
+++ client/src/main.coffee
@@ -1,20 +1,4 @@
 $(document).ready(->
   Game.initialise()
   ScreenSystem.loadAllScreens(->Network.initialise())
-)
-
-
-###
-  $('#unallocated ul li').click(->
-    $(this).toggleClass('glow')
-    $('.mat').toggleClass('glow')
-  )
-###
-###
-$('#inside-goal li').ready(->
-  $('#inside-goal li').click(->
-    alert "clicked on li with index #{this.index()}"
-    ui.moveToGoal(this.index())
-  )
-)
-###+)


Fix role points directive.

Fix role points directive.

--- app/coffee/directives/common.coffee
+++ app/coffee/directives/common.coffee
@@ -272,7 +272,7 @@
 GmRolePointsEditionDirective = ->
     compile: (element, attrs) ->
         template = """
-        <fieldset class="us-role-points" ng-repeat="role in roles">
+        <fieldset class="us-role-points" ng-repeat="role in constants.computableRolesList">
             {{ role.name }} (points)
             <select class="points" name="points" ng-model="form.points[role.id]" data-required="true"
                 data-error-message="Required"


Small fixes for team members

Small fixes for team members

--- app/coffee/modules/team/main.coffee
+++ app/coffee/modules/team/main.coffee
@@ -37,12 +37,14 @@
         "$tgResources",
         "$routeParams",
         "$q",
+        "$location",
+        "$tgNavUrls",
         "$appTitle",
-        "$tgAuth"
+        "$tgAuth",
         "tgLoader"
     ]

-    constructor: (@scope, @rootscope, @repo, @rs, @params, @q, @appTitle, @auth, tgLoader) ->
+    constructor: (@scope, @rootscope, @repo, @rs, @params, @q, @location, @navUrls, @appTitle, @auth, tgLoader) ->
         @scope.sectionName = "Team"

         promise = @.loadInitialData()
@@ -80,8 +82,8 @@
                     return membership

             for membership in @scope.memberships
-                if not membership.user.photo?
-                    membership.user.photo = "/images/unnamed.png"
+                if not membership.photo?
+                    membership.photo = "/images/unnamed.png"

             return data


Improved inconsistencies on resource service private methods.

--- app/coffee/services/resource.coffee
+++ app/coffee/services/resource.coffee
@@ -34,74 +34,72 @@

         return data

-    _queryMany: (name, params, options, urlParams) ->
+    _queryMany: (name, params, options) ->
         defaultHttpParams = {
             method: "GET",
             headers:  @_headers(),
             url: @gmUrls.api(name, urlParams)
         }

+        httpParams = _.extend({}, defaultHttpParams, options)
         if not _.isEmpty(params)
-            defaultHttpParams.params = params
-
-        httpParams = _.extend({}, defaultHttpParams, options)
-        defered = @q.defer()
-
+            httpParams.params = params
+
+        defered = @q.defer()
         promise = @http(httpParams)
+
         promise.success (data, status) =>
             models = _.map data, (attrs) => @model.make_model(name, attrs)
             defered.resolve(models)

         promise.error (data, status) ->
-            defered.reject(data, status)
-
-        return defered.promise
-
-    _queryRaw: (name, id, params, options, cls) ->
+            defered.reject(data)
+
+        return defered.promise
+
+    _queryRaw: (name, id, params, options) ->
         defaultHttpParams = {method: "GET", headers:  @_headers()}
+        httpParams =  _.extend({}, defaultHttpParams, options)

         if id
-            defaultHttpParams.url = "#{@gmUrls.api(name)}/#{id}"
+            httpParams.url = "#{@gmUrls.api(name)}/#{id}"
         else
-            defaultHttpParams.url = "#{@gmUrls.api(name)}"
+            httpParams.url = "#{@gmUrls.api(name)}"

         if not _.isEmpty(params)
-            defaultHttpParams.params = params
-
-        httpParams =  _.extend({}, defaultHttpParams, options)
-
-        defered = @q.defer()
-
+            httpParams.params = params
+
+        defered = @q.defer()
         promise = @http(httpParams)
+
         promise.success (data, status) ->
-            defered.resolve(data, cls)
-
-        promise.error (data, status) ->
-            defered.reject()
+            defered.resolve(data)
+
+        promise.error (data, status) ->
+            defered.reject(data)

         return defered.promise

     _queryOne: (name, id, params, options, cls) ->
         defaultHttpParams = {method: "GET", headers:  @_headers()}
+        httpParams =  _.extend({}, defaultHttpParams, options)
+
+        if not _.isEmpty(params)
+            httpParams.params = params

         if id
-            defaultHttpParams.url = "#{@gmUrls.api(name)}/#{id}"
+            httpParams.url = "#{@gmUrls.api(name)}/#{id}"
         else
-            defaultHttpParams.url = "#{@gmUrls.api(name)}"
-
-        if not _.isEmpty(params)
-            defaultHttpParams.params = params
-
-        httpParams =  _.extend({}, defaultHttpParams, options)
-
-        defered = @q.defer()
-
+            httpParams.url = "#{@gmUrls.api(name)}"
+
+        defered = @q.defer()
         promise = @http(httpParams)
+
         promise.success (data, status) =>
             defered.resolve(@model.make_model(name, data, cls))

         promise.error (data, status) ->
-            defered.reject()
+            defered.reject(data)

         return defered.promise

@@ -111,13 +109,14 @@
             headers: @_headers(false),
             url: @gmUrls.api(name, urlParams)
         }
+
+        httpParams =  _.extend({}, defaultHttpParams, options)
         if not _.isEmpty(params)
-            defaultHttpParams.params = params
-
-        httpParams =  _.extend({}, defaultHttpParams, options)
-        defered = @q.defer()
-
+            httpParams.params = params
+
+        defered = @q.defer()
         promise = @http(httpParams)
+
         promise.success (data, status, headersFn) =>
             currentHeaders = headersFn()

@@ -331,8 +330,9 @@
         # Second step: make user story models
         _makeUserStoryModels = (objects) =>
             for milestone in objects
-                milestone.user_stories = _.map milestone.user_stories, (obj) => @model.make_model("userstories", obj)
-
+                objects = _.map milestone.user_stories, (obj) =>
+                    @model.make_model("userstories", obj)
+                milestone.user_stories = objects
             return objects

         # Third step: make milestone models
@@ -361,7 +361,9 @@

         # Second step: make user story models
         _makeUserStoryModels = (milestone) =>
-            milestone.user_stories = _.map milestone.user_stories, (obj) => @model.make_model("userstories", obj)
+            objects = _.map milestone.user_stories, (obj) =>
+                @model.make_model("userstories", obj)
+            milestone.user_stories = objects

             return milestone

@@ -489,7 +491,10 @@

         obj = { milestone: milestoneId }

-        promise = @http({method: "PATCH", url: "#{@gmUrls.api("userstories")}/#{usId}", data: obj, headers:@_headers()})
+        promise = @http({
+            method: "PATCH",
+            url: "#{@gmUrls.api("userstories")}/#{usId}",
+            data: obj, headers:@_headers()})

         promise.success (data, status) =>
             defered.resolve(@model.make_model("userstories", data))
@@ -500,7 +505,6 @@
         return defered.promise

     createMilestone: (projectId, form) ->
-        #return @model.create("milestones", data)
         obj = _.extend({}, form, {project: projectId})
         defered = @q.defer()


Simplify tabs component

Simplify tabs component

Previously we had logic to see whether the container had grown or
shrunk, and responded by either taking elements out of the dropdown or
moving them in.

Instead, this commit combines the separate code paths. Every time the
width is changed all elements are removed from the DOM and one-by-one
re-inserted into the correct parent.

This shouldn't be significantly slower then the previous behaviour.
We are looking at very few DOM nodes and are careful to not force layout
at any point in the loop.

--- backend/app/assets/javascripts/spree/backend/components/tabs.coffee
+++ backend/app/assets/javascripts/spree/backend/components/tabs.coffee
@@ -5,73 +5,39 @@
     @tabs = @$tabs.toArray()
     @$tabList.append("

")
     @$dropdown = @$tabList.find(".tabs-dropdown")
-    @setWidths()
-    @initEvents()

-  initEvents: ->
-    $(window).on "resize", @overflowTabs
-    @overflowTabs()
-
-  setWidths: ->
     @tabWidths = @tabs.map (tab) ->
       tab.offsetWidth
     @totalTabsWidth = @tabWidths.reduce (previousValue, currentValue) ->
       previousValue + currentValue
+    @dropdownWidth = @$dropdown[0].offsetWidth
+
+    $(window).on "resize", @overflowTabs
+    @overflowTabs()

   overflowTabs: =>
     containerWidth = @$tabList[0].offsetWidth
-    @lastKnownWidth = containerWidth unless @lastKnownWidth
-    widthDifference = @totalTabsWidth - containerWidth
-    widthDifferenceWithDropdown = widthDifference + @dropdownWidth()
     dropdownActive = @$dropdown.find("li").length

-    if containerWidth <= @lastKnownWidth
-      # The window is being sized down or we've just loaded the page
-      if (dropdownActive and widthDifferenceWithDropdown > 0) or (not dropdownActive and widthDifference > 0)
-        @hideTabsToFit(widthDifferenceWithDropdown)
-    if containerWidth > @lastKnownWidth
-      # The window is getting larger
-      @showTabsToFit(widthDifference)
+    for tab in @tabs
+      $(tab).remove()

-    @lastKnownWidth = containerWidth
+    remainingWidth = containerWidth

-  dropdownWidth: ->
-    @$dropdown[0].offsetWidth
+    if @totalTabsWidth < containerWidth
+      # everything fits
+      @$tabList.removeClass("tabs-overflowed")
+    else
+      @$tabList.addClass("tabs-overflowed")
+      remainingWidth -= @dropdownWidth

-  hideTabsToFit: (widthDifference) ->
-    @$tabList.addClass("tabs-overflowed")
-    tabs = @tabs.slice().reverse()
+    for tab, i in @tabs.slice()
+      remainingWidth -= @tabWidths[i]

-    for tab in tabs
-      # Bail if things are now fitting
-      return if widthDifference <= 0
-      # Skip items already in the dropdown or active
-      continue if $(tab).hasClass("in-dropdown") or $(tab).hasClass("active")
-
-      tabWidth = tab.offsetWidth
-      @totalTabsWidth -= tabWidth
-      widthDifference -= tabWidth
-      $(tab).appendTo(@$dropdown.find("ul")).addClass("in-dropdown")
-
-  showTabsToFit: (widthDifference) ->
-    for tab, i in @tabs.slice()
-      # Skip items that aren't already in the dropdown
-      continue unless $(tab).hasClass("in-dropdown")
-
-      # Get our tab's width from the array
-      # We can't measure it here because it's hidden in the dropdown
-      tabWidth = @tabWidths[i]
-
-      # Bail if there's no room for this tab
-      break if widthDifference + tabWidth > 0
-
-      @totalTabsWidth += tabWidth
-      widthDifference += tabWidth
-      $(tab).insertBefore(@$dropdown).removeClass("in-dropdown")
-
-    # Reset styles if our dropdown is now empty
-    if @$dropdown.find("li").length is 0
-      @$tabList.removeClass("tabs-overflowed")
+      if remainingWidth >= 0
+        $(tab).insertBefore(@$dropdown).removeClass("in-dropdown")
+      else
+        $(tab).appendTo(@$dropdown.find("ul")).addClass("in-dropdown")

 window.onload = ->
   new Tabs(el) for el in $(".tabs")


Cleanup: minor indentation issue

Cleanup: minor indentation issue

--- client/Social/Activity/views/inputwidget.coffee
+++ client/Social/Activity/views/inputwidget.coffee
@@ -21,7 +21,7 @@
       size      :
         width   : 35
         height  : 35
-      , KD.whoami()
+    , KD.whoami()

   submit: (callback) ->
     return  unless value = @input.getValue().trim()


Activity: Edit Mode is added to input widget

--- client/Social/Activity/views/inputwidget.coffee
+++ client/Social/Activity/views/inputwidget.coffee
@@ -5,6 +5,8 @@
   constructor: (options = {}, data) ->
     options.cssClass = KD.utils.curry "input-wrapper", options.cssClass
     super options, data
+
+    options.editMode ?= no

     @input    = new ActivityInputView
     @input.on "Escape", @bound "reset"
@@ -32,11 +34,21 @@
       iconOnly : yes
       callback : @bound "submit"

+    if options.editMode
+      @cancel = new KDButtonView
+        cssClass : "gray"
+        title    : "Cancel"
+        callback : @bound "cancel"
+
     @avatar = new AvatarView
       size      :
         width   : 35
         height  : 35
     , KD.whoami()
+
+  cancel: ->
+    @destroy()
+    @emit "Cancel"

   submit: (callback) ->
     return  unless value = @input.getValue().trim()
@@ -86,6 +98,7 @@
           @reset yes
           @embedBox.resetEmbedAndHide()
           @emit "Submit"
+          @destroy() if @getOptions().editMode
           @notification.show()
           callback? err, activity
     ]
@@ -121,7 +134,6 @@
     content = activity.body.replace /\n/g, "<br>"
     @input.setContent content, activity
     @embedBox.loadEmbed activity.link.link_url  if activity.link
-    # @submit.setTitle "Update"

   reset: (lock = yes) ->
     @input.setContent ""
@@ -147,4 +159,5 @@
     @addSubView @notification
     @addSubView @embedBox
     @input.addSubView @submit
+    @input.addSubView @cancel if @getOptions().editMode
     @hide()  unless KD.isLoggedIn()


Show lazyloader on activity

Show lazyloader on activity

--- client/Social/Activity/views/messagepane.coffee
+++ client/Social/Activity/views/messagepane.coffee
@@ -146,12 +146,14 @@

   lazyLoad: ->
+    @listController.showLazyLoader()

     {appManager} = KD.singletons
     last         = @listController.getItemsOrdered().last
     from         = last.getData().meta.createdAt.toISOString()

     @fetch {from}, (err, items = []) =>
+      @listController.hideLazyLoader()

       return KD.showError err  if err


client: sidebar menu analytics addition

client: sidebar menu analytics addition

--- client/app/lib/activity/sidebar/teamname.coffee
+++ client/app/lib/activity/sidebar/teamname.coffee
@@ -1,5 +1,6 @@
 kd    = require 'kd'
 JView = require '../../jview'
+kookies = require 'kookies'
 showError = require 'app/util/showError'
 whoami = require 'app/util/whoami'
 AvatarStaticView = require 'app/commonviews/avatarviews/avatarstaticview'
@@ -101,6 +102,8 @@
       'Change Team' : { callback }
     }

+    menuItems.Analytics = { callback }  if kookies.get 'countly-enabled'
+
     intercomSupport (isSupported) =>
       menuItems['Support'] = if isSupported then { callback }
       else { callback: @bound 'mailToSupport' }
@@ -128,6 +131,11 @@
     kd.singletons.router.handleRoute '/Home/stacks'

+  handleAnalytics: ->
+
+    kd.singletons.router.handleRoute '/Analytics'
+
+
   handleLogout: ->

     kd.singletons.router.handleRoute '/Logout'


resolves #15

resolves #15

--- coffee/atPagination.coffee
+++ coffee/atPagination.coffee
@@ -75,10 +75,10 @@
       #   update(false) if newValue != oldValue

       $scope.$watch "itemsPerPage", () ->
-        $scope.update()
+        update()

       $scope.$watch "list", () ->
-        $scope.update()
+        update()

   }
 ]


Added stroke to arrows by creating a thicker black line behind.

For the arrowhead just added a black stroke.

--- coffeescripts/xwing.coffee
+++ coffeescripts/xwing.coffee
@@ -976,7 +976,8 @@
                                 arrowEndy = 180

                         outTable += $.trim """
-                          <path d='M#{arrowStartx},#{arrowStarty} #{arrowMove} L#{arrowEndx},#{arrowEndy} Z' fill='#{color}' #{transform}/>
+                          <path d='M#{arrowStartx},#{arrowStarty} #{arrowMove} L#{arrowEndx},#{arrowEndy} Z' fill='#{color}' #{transform} stroke-width='5' stroke='black' />
+                          <path stroke-width='25' fill='none' stroke='black' d='M#{startx},#{starty} #{turnType}#{smoothx},#{smoothy} #{extraPoint}#{endx},#{endy}' />
                           <path stroke-width='15' fill='none' stroke='#{color}' d='M#{startx},#{starty} #{turnType}#{smoothx},#{smoothy} #{extraPoint}#{endx},#{endy}' />
                         """
                     outTable += "</svg>"


Fix test

Fix test

--- client/apps/edit/components/layout/index.coffee
+++ client/apps/edit/components/layout/index.coffee
@@ -37,7 +37,7 @@
     window.onbeforeunload = =>
       if $.active > 0
         "Your article is not finished saving."
-      else if @changedAPublishedArticle is true and not @finished
+      else if @changedSection is true and not @finished
         "You have unsaved changes, do you wish to continue?"
       else
         null
@@ -150,7 +150,7 @@

   onKeyup: =>
     if @article.get('published')
-      @changedAPublishedArticle = true
+      @changedSection = true
       $('#edit-save').addClass 'attention'
     else
       @article.save @serialize()


Add more null check for discussion posts

--- resources/assets/coffee/react/beatmap-discussions/discussion.coffee
+++ resources/assets/coffee/react/beatmap-discussions/discussion.coffee
@@ -313,7 +313,7 @@

   resolvedSystemPostId: =>
     if !@_resolvedSystemPostId?
-      systemPost = _.findLast(@props.discussion.posts, (post) -> post.system && post.message.type == 'resolved')
+      systemPost = _.findLast(@props.discussion.posts, (post) -> post? && post.system && post.message.type == 'resolved')
       @_resolvedSystemPostId = systemPost?.id ? -1

     return @_resolvedSystemPostId


- Add close button, catch closeTopicsModal signal to destroy modal

--- client/landing/app/CommonViews/LinkViews.coffee
+++ client/landing/app/CommonViews/LinkViews.coffee
@@ -185,6 +185,11 @@
     options.height   = "auto"
     options.overlay  = yes
     options.cssClass = "modal-topic-wrapper"
+    options.buttons  =
+      Close :
+        style : "modal-clean-gray"
+        callback : =>
+          @destroy()

     super

@@ -198,6 +203,9 @@
         cssClass      : "modal-topic-list"
     ,
       items           : participants
+
+    controller.getListView().on "closeTopicsModal", =>
+      @destroy()

     @addSubView controller.getView(), ".kdmodal-content"


NSE: Editor: support custom return types, add content parser

--- client/new-stack-editor/lib/views/editor.coffee
+++ client/new-stack-editor/lib/views/editor.coffee
@@ -4,6 +4,8 @@

 Events = require '../events'
 BaseView = require './baseview'
+
+yamlutils = require 'app/util/stacks/yamlutils'

 module.exports = class Editor extends BaseView
@@ -32,9 +34,24 @@
     @getPreview = preview  if preview

-  getContent: ->
-
-    @aceView.ace.getContents()
+  getContent: (as = 'is') ->
+
+    content = @aceView.ace.getContents()
+    return content  if as is 'is'
+
+    switch as
+      when 'json'
+        { content } = yamlutils.yamlToJson content
+      when 'yaml'
+        { content } = yamlutils.jsonToYaml content
+
+    return content
+
+
+  parseContent: ->
+
+    { err, content, contentObject } = yamlutils.yamlToJson @getContent()
+    return [ err, contentObject ]

   setContent: (content, type = 'text') -> @ready =>
@@ -68,6 +85,9 @@

   setReadOnly: (state) -> @ready =>
+
+    if intialState = @getOption 'readonly'
+      state = intialState

     @aceView.ace.setReadOnly state, no
     state = if state then 'none' else 'block'


'/build/static/' added as statis folder

'/build/static/' added as statis folder

--- impl/node/index.coffee
+++ impl/node/index.coffee
@@ -46,7 +46,7 @@
 				data += chunk;

 			serverReq.on 'end', ->
-				if ///^\/static\////.test(serverReq.url)
+				if ///^(?:\/static\/|\/build\/static\/)///.test(serverReq.url)
 					staticServer.serve serverReq, serverRes
 					return


disable timeouts while waiting for webdriver to load the initial page

--- spec/suite.spec.coffee
+++ spec/suite.spec.coffee
@@ -11,6 +11,7 @@
 {expect} = chai

 before (done) ->
+  @timeout 0 # this may take a while in CI
   url = "file://#{path.join __dirname, 'test.html'}"
   driver.get(url).then -> done()


Refactor db.Driver::query to clean up async logic. v0.10.15

--- lib/system/db/Driver.coffee
+++ lib/system/db/Driver.coffee
@@ -188,31 +188,35 @@

   #
-  # Execute a list of sql statements
-  #
-  # @return	array
+  # Execute a list of sql statements in order
+  #
+  # @param  [Array<String>] sql array of sql statements
+  # #param  [Function] next async callback
+  # @return none
   #
   queryList: ($sql, $next) ->

-    $results = []
-    $index = 0
-    $query = @query
-
-    $iterate = =>
-
-      return $next(null, $results) if $sql.length is 0
-      #
-      # execute sql
-      #
-      $query $sql[$index], ($err, $result) =>
-        return $next($err) if $err
-
-        $results.push $result
-        $index += 1
-        if $index is $sql.length then $next null, $results
-        else $iterate()
-
-    $iterate()
+    async.mapSeries $sql, @query, $next
+
+#    $results = []
+#    $index = 0
+#    $query = @query
+#
+#    $iterate = =>
+#
+#      return $next(null, $results) if $sql.length is 0
+#      #
+#      # execute sql
+#      #
+#      $query $sql[$index], ($err, $result) =>
+#        return $next($err) if $err
+#
+#        $results.push $result
+#        $index += 1
+#        if $index is $sql.length then $next null, $results
+#        else $iterate()
+#
+#    $iterate()

@@ -827,22 +831,12 @@
   #
   displayError : ($error = '', $swap = '', $native = false) ->

-    console.log 'ERROR '+$error
-    return
-
     @i18n.load('db')
-
-    $heading = @i18n.line('db_error_heading')
-
-    if $native is true
-      $message = $error
-
-    else
-      $message = if 'string' is typeof($error) then [@i18n.line($error).replace('%s', $swap)] else $error
-
+    $message = if $native is true then $error
+    else if 'string' is typeof($error) then @i18n.line($error).replace('%s', $swap) else $error

     console.log $message
-    #show_error $message
+    $message

   #


Don't show SSD in the storage size

Don't show SSD in the storage size

--- app/scripts/filters/instance.coffee
+++ app/scripts/filters/instance.coffee
@@ -9,8 +9,7 @@
       driveSize = input.storage.size + ' GB'
       deviceCount = input.storage.devices
       deviceCount = if deviceCount > 1 then ' x ' + deviceCount else ''
-      ssd = if input.storage.ssd then ' (SSD)' else ''
-      return driveSize + deviceCount + ssd
+      return driveSize + deviceCount
   )
   .filter('instanceTotalStorageSize', () ->
     return (input) ->


functions that only pass self can be bound so that they’re easier to use from native JS. the function will throw out the self object that’s passed to it.

--- coffeescript/love.js/graphics/canvas_2d.coffee
+++ coffeescript/love.js/graphics/canvas_2d.coffee
@@ -23,22 +23,22 @@
     self.context.fillRect(0, 0, self.width, self.height)
     self.context.restore()

-  getDimensions: (self) ->
-    [@getWidth(self), @getHeight(self)]
-
-  getHeight: (self) ->
-    self.height
-
-  getImageData: (self) ->
-    image_data = self.context.getImageData(0, 0, self.width, self.height)
+  getDimensions: =>
+    [@getWidth(), @getHeight()]
+
+  getHeight: =>
+    @height
+
+  getImageData: =>
+    image_data = @context.getImageData(0, 0, @width, @height)
     new ImageData(image_data)

   getPixel: (self, x, y) ->
     data = self.context.getImageData(x, y, 1, 1).data
     [data[0], data[1], data[2], data[3]]

-  getWidth: (self) ->
-    self.width
+  getWidth: =>
+    @width

   # TODO: wrapping also applies to textures and quads
   getWrap: (self) ->
@@ -76,8 +76,8 @@

   draw: (drawable, quad) ->
     switch true
-      when quad not instanceof Love.Graphics.Quad then drawDrawable.apply(this, arguments)
-      when quad instanceof Love.Graphics.Quad then drawWithQuad.apply(this, arguments)
+      when quad not instanceof Love.Graphics.Quad then @drawDrawable.apply(this, arguments)
+      when quad instanceof Love.Graphics.Quad then @drawWithQuad.apply(this, arguments)

   line: (points...) ->
     if points.length == 1
@@ -246,7 +246,7 @@
     @element.setAttribute('height', @height)

   # INTERNAL
-  drawDrawable = (drawable, x = 0, y = 0, r = 0, sx = 1, sy = sx, ox = 0, oy = 0, kx = 0, ky = 0) ->
+  drawDrawable: (drawable, x = 0, y = 0, r = 0, sx = 1, sy = sx, ox = 0, oy = 0, kx = 0, ky = 0) ->
     halfWidth = drawable.element.width / 2
     halfHeight = drawable.element.height / 2

@@ -259,7 +259,7 @@
     @context.drawImage(drawable.element, 0, 0)
     @context.restore()

-  drawWithQuad = (drawable, quad, x = 0, y = 0, r = 0, sx = 1, sy = sx, ox = 0, oy = 0, kx = 0, ky = 0) ->
+  drawWithQuad: (drawable, quad, x = 0, y = 0, r = 0, sx = 1, sy = sx, ox = 0, oy = 0, kx = 0, ky = 0) ->
     halfWidth = drawable.element.width / 2
     halfHeight = drawable.element.height / 2


Implement RETR command

Implement RETR command

--- coffee/BabyFtpd.coffee
+++ coffee/BabyFtpd.coffee
@@ -2,9 +2,9 @@
 #
 # # Baby FTP Deamon
 #
-net       = require "net"
-fs        = require "fs"
-exec      = require("child_process").exec
+net   = require "net"
+fs    = require "fs"
+exec  = require("child_process").exec

 module.exports = class BabyFtpd
   # Static variables
@@ -163,9 +163,6 @@
     "STAT": ()->
       @reply 202

-    # 仮実装しているもの
-    #"RETR": ()->
-
     # 各コマンドの処理
     "USER": (username)->
       @userName = username
@@ -191,22 +188,29 @@
     "NLST": (reqPath = "")->
       @fileSystem.getNlst @sessionDir, reqPath, (err, files)=>
         if err?
+          if @passive
+            @dtpServer.close()
           @reply 550, "#{reqPath}: No such file or directory"
         else
-          @dtpServer.dataQueue = files.join("\r\n") + "\r\n"
-          @reply 150
+          @dtpServer.dtpSocket.sender files.join("\r\n") + "\r\n"

     "LIST": (reqPath = "")->
       @fileSystem.getList @sessionDir, reqPath, (err, stdout, stderr)=>
         if err?
+          if @passive
+            @dtpServer.close()
           @reply 550, "#{reqPath}: No such file or directory"
         else
-          @dtpServer.dataQueue = stdout.replace(/^total [0-9]+$/im, "").trim()
-          @reply 150
-
-    "RETR": ()->
-      @dtpServer.dataQueue = "<html></html>"
-      @reply 150
+          @dtpServer.dtpSocket.sender stdout.replace(/^total [0-9]+$/im, "").trim()
+
+    "RETR": (reqPath)->
+      @fileSystem.getFile @sessionDir, reqPath, (err, data)=>
+        if err?
+          if @passive
+            @dtpServer.close()
+          @reply 550, "#{reqPath}: No such file or directory"
+        else
+          @dtpServer.dtpSocket.sender data

     "SYST": ()->
       @reply 215, BabyFtpd.sysName
@@ -216,50 +220,9 @@
       @end()

     "PASV": ()->
-      socket = @
-      socket.passive = true
-      socket.dtpServer = net.createServer()
-      dtpHost = socket.server.address().address
-      socket.dtpServer.dataQueue = null
-
-      socket.dtpServer.on 'listening', ()->
-        dtpAddress = @address()
-        console.log "Data Transfer Proccess Server listening on #{dtpAddress.address}:#{dtpAddress.port}"
-        host  = dtpAddress.address.split(".").join(",")
-        port1 = parseInt(dtpAddress.port / 256, 10)
-        port2 = dtpAddress.port % 256
-        socket.reply 227, "Entering Extended Passive Mode (#{host},#{port1},#{port2})"
-
-      socket.dtpServer.on "connection", (dtpSocket)->
-        console.log "DTP Connect from #{dtpSocket.remoteAddress}"
-        dtpSocket.setTimeout(0)
-        dtpSocket.setNoDelay()
-        dtpSocket.dataEncoding = "binary"
-
-        dtpSocket.on "end", ()->
-            socket.dtpServer.close()
-            socket.reply 226
-        dtpSocket.on "close", ()->
-          console.log "DTP Socket closed"
-        dtpSocket.on "connect", ()->
-          console.log "DTP Socket connect"
-        dtpSocket.on 'data', (recData)->
-          console.log recData.toString().trim()
-
-        dtpSocket.sender = ()->
-          console.log socket.dtpServer.dataQueue
-          if socket.dtpServer.dataQueue isnt null
-            console.log "Send"
-            clearInterval(dtpSocket.intervalId)
-            dtpSocket.end(socket.dtpServer.dataQueue)
-
-        dtpSocket.intervalId = setInterval(dtpSocket.sender, 100)
-
-      socket.dtpServer.on "close", ()->
-        console.log "Data Transfer Proccess Server closed"
-        socket.passive = false
-
-      socket.dtpServer.listen(0, dtpHost)
+      @passive = true
+      @dtpServer = dtpServer.call @, @
+      @dtpServer.listen 0, @server.address().address

     "NOOP": ()->
       @reply 200
@@ -271,6 +234,44 @@
         CWD     QUIT    PASV    NOOP    HELP
         Direct comments to root
         """
+
+  dtpServer = (socket)->
+    dtp = net.createServer()
+
+    dtp.on 'listening', ()->
+      dtpAddress = @address()
+      console.log "Data Transfer Proccess Server listening on #{dtpAddress.address}:#{dtpAddress.port}"
+      host  = dtpAddress.address.split(".").join(",")
+      port1 = parseInt(dtpAddress.port / 256, 10)
+      port2 = dtpAddress.port % 256
+      socket.reply 227, "Entering Extended Passive Mode (#{host},#{port1},#{port2})"
+
+    dtp.on "close", ()->
+      console.log "Data Transfer Proccess Server closed"
+      socket.passive = false
+
+    dtp.on "connection", (dtpSocket)->
+      @dtpSocket = dtpSocket
+      console.log "DTP Connect from #{dtpSocket.remoteAddress}"
+      dtpSocket.setTimeout(0)
+      dtpSocket.setNoDelay()
+      dtpSocket.dataEncoding = "binary"
+
+      dtpSocket.on "end", ()->
+          socket.dtpServer.close()
+          socket.reply 226
+      dtpSocket.on "close", ()->
+        console.log "DTP Socket closed"
+      dtpSocket.on "connect", ()->
+        console.log "DTP Socket connect"
+      dtpSocket.on 'data', (recData)->
+        console.log recData.toString().trim()
+
+      dtpSocket.sender = (dataQueue)->
+        socket.reply 150
+        console.log "DTP Send"
+        dtpSocket.end(dataQueue)
+    dtp

 class BabyFtpd.FileSystem
@@ -297,13 +298,26 @@
             tmpDir.pop()
           tmpDir.push aPath
       retPath = tmpDir.join("/")
-    fs.readdirSync @baseDir+retPath
+    fs.statSync @baseDir+retPath
     retPath

   getNlst: (nowDir, reqPath, callback)->
-    retPath = @getNewPath nowDir, reqPath
-    fs.readdir @baseDir+retPath, callback
+    try
+      retPath = @getNewPath nowDir, reqPath
+      fs.readdir @baseDir+retPath, callback
+    catch err
+      callback(err)

   getList: (nowDir, reqPath, callback)->
-    retPath = @getNewPath nowDir, reqPath
-    exec "export LANG=en_US.UTF-8; ls -l #{@baseDir}#{retPath}", callback
+    try
+      retPath = @getNewPath nowDir, reqPath
+      exec "export LANG=en_US.UTF-8; ls -l #{@baseDir}#{retPath}", callback
+    catch err
+      callback(err)
+
+  getFile: (nowDir, reqPath, callback)->
+    try
+      retPath = @getNewPath nowDir, reqPath
+      fs.readFile @baseDir+retPath, callback
+    catch err
+      callback(err)


Fixes invlid database count when count is 0 and limit is > 0

--- coffee/database.coffee
+++ coffee/database.coffee
@@ -42,7 +42,7 @@
         next(++i, count)

   cursor.count (err, count) ->
-    count = cursor.limitValue || count
+    count = cursor.limitValue || count if count isnt 0
     return done err, 0, count if err or count is 0
     next 0, count


util: fixed css appending

util: fixed css appending

--- coffee/src/util.coffee
+++ coffee/src/util.coffee
@@ -72,8 +72,9 @@

 # add acorn css
 acorn.util.appendCss = (srcs) ->
-  srcs ?= ['/static/css/acorn-player.css', '/static/css/acorn.css']
-  _.each _.toArray(srcs), (src) ->
+  srcs ?= '/static/css/acorn-player.css'
+  srcs = [srcs] unless _.isArray(srcs)
+  _.each srcs, (src) ->
     css = $('<link>')
     css.attr 'rel', 'stylesheet'
     css.attr 'href', src


Show Signup validation errors on blank submit

--- app/signup/signup-controller.coffee
+++ app/signup/signup-controller.coffee
@@ -14,9 +14,9 @@
     $scope.$watch 'confirmPassword', @verifyPasswordMatch, true

   signup: (email, password, confirmPassword) =>
-    @errorMessage = SignupController.ERROR_NO_EMAIL unless email
-    @errorMessage = SignupController.ERROR_NO_PASSWORD unless password
-    @errorMessage = SignupController.ERROR_PASSWORDS_MATCH unless confirmPassword == password
+    @signupForm?.email.$setTouched()
+    @signupForm?.password.$setTouched()
+    @signupForm?.confirmPassword.$setTouched()

     @AuthenticatorService.register(email, password)
     .then (result) =>
@@ -25,14 +25,10 @@
       @errorMessage = SignupController.ERROR_REGISTERING

   verifyPasswordMatch: =>
-    if @scope.confirmPassword?.length and @scope.password isnt @scope.confirmPassword
+    if @scope.confirmPassword?.length && @scope.password != @scope.confirmPassword
       @signupForm?.confirmPassword.$error.match = true
     else
       @signupForm?.confirmPassword.$error.match = false
-
-  confirmPasswordError: =>
-    return true if @signupForm?.confirmPassword.$error.match
-    return true if @signupForm?.confirmPassword.$error.required && @signupForm?.confirmPassword.$touched

   emailRequiredError: =>
     return true if @signupForm?.email.$error.required && @signupForm?.email.$touched
@@ -40,4 +36,8 @@
   passwordRequiredError: =>
     return true if @signupForm?.password.$error.required && @signupForm?.password.$touched

+  confirmPasswordError: =>
+    return true if @signupForm?.confirmPassword.$error.match
+    return true if @signupForm?.confirmPassword.$error.required && @signupForm?.confirmPassword.$touched
+
 angular.module('email-password').controller 'SignupController', SignupController


Translate darwin's menu

Translate darwin's menu

--- def/th/menu_darwin.cson
+++ def/th/menu_darwin.cson
@@ -3,323 +3,323 @@
     value: "Atom"
     submenu:
       "About Atom":
-        value: "About Atom"
+        value: "เกี่ยวกับ Atom"
       "View License":
-        value: "View License"
+        value: "ดูการอนุญาต License"
       "Check for Update":
-        value: "Check for Update"
+        value: "ตรวจสอบการอัพเดต"
       "Preferences…":
-        value: "Preferences..."
+        value: "การปรับแต่ง..."
       "Config…":
-        value: "Config..."
+        value: "แก้ไข..."
       "Init Script…":
-        value: "Init Script..."
+        value: "สคริปต์เริ่มต้น..."
       "Keymap…":
-        value: "Keymap..."
+        value: "การจับคู่คีย์..."
       "Snippets…":
-        value: "Snippets..."
+        value: "ชุดโค้ด..."
       "Stylesheet…":
-        value: "Stylesheet..."
+        value: "ชุดรูปแบบ..."
       "Install Shell Commands":
-        value: "Install Shell Commands"
+        value: "ติดตั้งคำสั่งเชลล์"
       "Hide Atom":
-        value: "Hide Atom"
+        value: "ซ่อน Atom"
       "Hide Others":
-        value: "Hide Others"
+        value: "ซ่อนอื่นๆ"
       "Show All":
-        value: "Show All"
+        value: "แสดงทั้งหมด"
       Quit:
-        value: "Quit"
+        value: "ออก"
   File:
-    value: "File"
+    value: "ไฟล์"
     submenu:
       "New Window":
-        value: "New Window"
+        value: "หน้าต่างใหม่"
       "New File":
-        value: "New File"
+        value: "ไฟล์ใหม่"
       "Open…":
-        value: "Open..."
+        value: "เปิด..."
       "Add Project Folder…":
-        value: "Add Project Folder..."
+        value: "เพิ่มโฟลเดอร์โปรเจค..."
       "Reopen Project":
-        value: "Reopen Project"
+        value: "เปิดโปรเจคอีกครั้ง"
         submenu:
           "Clear Project History":
-            value: "Clear Project History"
+            value: "ล้างประวัติโปรเจค"
       "Reopen Last Item":
-        value: "Reopen Last Item"
+        value: "เปิดไอเทมล่าสุดอีกครั้ง"
       Save:
-        value: "Save"
+        value: "บันทึก"
       "Save As…":
-        value: "Save As..."
+        value: "บันทึกเป็น..."
       "Save All":
-        value: "Save All"
+        value: "บันทึกทั้งหมด"
       "Close Tab":
-        value: "Close Tab"
+        value: "ปิดแท็บ"
       "Close Pane":
-        value: "Close Pane"
+        value: "ปิดกระดาน"
       "Close Window":
-        value: "Close Window"
+        value: "ปิดหน้าต่าง"
       "Close All Tabs":
-        value: "Close All Tabs"
+        value: "ปิดแท็บทั้งหมด"
   Edit:
-    value: "Edit"
+    value: "แก้ไข"
     submenu:
       Undo:
-        value: "Undo"
+        value: "เลิกทำ"
       Redo:
-        value: "Redo"
+        value: "ทำซ้ำ"
       Cut:
-        value: "Cut"
+        value: "ตัด"
       Copy:
-        value: "Copy"
+        value: "คัดลอก"
       "Copy Path":
-        value: "Copy Path"
+        value: "คัดลอกเส้นทาง"
       Paste:
-        value: "Paste"
+        value: "วาง"
       "Select All":
-        value: "Select All"
+        value: "เลือกทั้งหมด"
       "Toggle Comments":
-        value: "Toggle Comments"
+        value: "สลับความเห็น"
       Lines:
-        value: "Lines"
+        value: "การจัดแถว"
         submenu:
           Indent:
-            value: "Indent"
+            value: "ย่อหน้า"
           Outdent:
-            value: "Outdent"
+            value: "เยื่องหน้า"
           "Auto Indent":
-            value: "Auto Indent"
+            value: "ย่อหน้าอัตโนมัติ"
           "Move Line Up":
-            value: "Move Line Up"
+            value: "เลื่อนบรรทัดขึ้น"
           "Move Line Down":
-            value: "Move Line Down"
+            value: "เลื่อนบรรทัดลง"
           "Duplicate Lines":
-            value: "Duplicate Lines"
+            value: "ทำซ้ำทั้งบรรทัด"
           "Delete Line":
-            value: "Delete Line"
+            value: "ลบบรรทัด"
           "Join Lines":
-            value: "Join Lines"
+            value: "รวบบรรทัด"
       Columns:
-        value: "Columns"
+        value: "คอลัมน์"
         submenu:
           "Move Selection Left":
-            value: "Move Selection Left"
+            value: "ย้ายส่วนที่เลือกไปทางซ้าย"
           "Move Selection Right":
-            value: "Move Selection Right"
+            value: "ย้ายส่วนที่เลือกไปทางขวา"
       Text:
-        value: "Text"
+        value: "ข้อความ"
         submenu:
           "Upper Case":
-            value: "Upper Case"
+            value: "ตัวใหญ่"
           "Lower Case":
-            value: "Lower Case"
+            value: "ตัวเล็ก"
           "Delete to End of Word":
-            value: "Delete to End of Word"
+            value: "ลบไปจนจบคำ"
           "Delete to Previous Word Boundary":
-            value: "Delete to Previous Word Boundary"
+            value: "ลบไปจนกระทั่งถึงคำก่อนหน้า"
           "Delete to Next Word Boundary":
-            value: "Delete to Next Word Boundary"
+            value: "ลบไปจนกระทั่งถึงคำถัดไป"
           "Delete Line":
-            value: "Delete Line"
+            value: "ลบบรรทัด"
           Transpose:
-            value: "Transpose"
+            value: "ผวนคำ"
       Folding:
-        value: "Folding"
+        value: "ปล่อยคำ"
         submenu:
           Fold:
-            value: "Fold"
+            value: "ปล่อย"
           Unfold:
-            value: "Unfold"
+            value: "ไม่ปล่อย"
           "Unfold All":
-            value: "Unfold All"
+            value: "ไม่ปล่อยทั้งหมด"
           "Fold All":
-            value: "Fold All"
+            value: "ปล่อยทั้งหมด"
           "Fold Level 1":
-            value: "Fold Level 1"
+            value: "ปล่อย 1 ระดับ"
           "Fold Level 2":
-            value: "Fold Level 2"
+            value: "ปล่อย 2 ระดับ"
           "Fold Level 3":
-            value: "Fold Level 3"
+            value: "ปล่อย 3 ระดับ"
           "Fold Level 4":
-            value: "Fold Level 4"
+            value: "ปล่อย 4 ระดับ"
           "Fold Level 5":
-            value: "Fold Level 5"
+            value: "ปล่อย 5 ระดับ"
           "Fold Level 6":
-            value: "Fold Level 6"
+            value: "ปล่อย 6 ระดับ"
           "Fold Level 7":
-            value: "Fold Level 7"
+            value: "ปล่อย 7 ระดับ"
           "Fold Level 8":
-            value: "Fold Level 8"
+            value: "ปล่อย 8 ระดับ"
           "Fold Level 9":
-            value: "Fold Level 9"
+            value: "ปล่อย 9 ระดับ"
       "Reflow Selection":
-        value: "Reflow Selection"
+        value: "ปรับลำดับส่วนที่เลือกใหม่"
       Bookmark:
-        value: "Bookmark"
+        value: "ที่คั่นหน้า"
         submenu:
           "View All":
-            value: "View All"
+            value: "แสดงทั้งหมด"
           "Toggle Bookmark":
-            value: "Toggle Bookmark"
+            value: "สลับที่คั่นหน้า"
           "Jump to Next Bookmark":
-            value: "Jump to Next Bookmark"
+            value: "กระโดดไปยังที่คั่นหน้าถัดไป"
           "Jump to Previous Bookmark":
-            value: "Jump to Previous Bookmark"
+            value: "กระโดดไปยังที่คั่นหน้าก่อนหน้า"
       "Select Encoding":
-        value: "Select Encoding"
+        value: "เลือกการเข้ารหัส"
       "Go to Line":
-        value: "Go to Line"
+        value: "ไปยังบรรทัด"
       "Select Grammar":
-        value: "Select Grammar"
+        value: "เลือกไวยากรณ์"
   View:
-    value: "View"
+    value: "วิว"
     submenu:
       "Toggle Full Screen":
-        value: "Toggle Full Screen"
+        value: "สลับเต็มจอ"
       Panes:
-        value: "Panes"
+        value: "กระดาน"
         submenu:
           "Split Up":
-            value: "Split Up"
+            value: "แบ่งขึ้นด้านบน"
           "Split Down":
-            value: "Split Down"
+            value: "แบ่งลงด้านล่าง"
           "Split Left":
-            value: "Split Left"
+            value: "แบ่งไปทางซ้าย"
           "Split Right":
-            value: "Split Right"
+            value: "แบ่งไปทางขวา"
           "Focus Next Pane":
-            value: "Focus Next Pane"
+            value: "ย้ายไปกระดานถัดไป"
           "Focus Previous Pane":
-            value: "Focus Previous Pane"
+            value: "ย้ายไปกระดานก่อนหน้า"
           "Focus Pane Above":
-            value: "Focus Pane Above"
+            value: "ย้ายไปกระดานด้านบน"
           "Focus Pane Below":
-            value: "Focus Pane Below"
+            value: "ย้ายไปกระดานด้านล่าง"
           "Focus Pane On Left":
-            value: "Focus Pane On Left"
+            value: "ย้ายไปกระดานด้านซ้าย"
           "Focus Pane On Right":
-            value: "Focus Pane On Right"
+            value: "ย้ายไปกระดานด้านขวา"
           "Close Pane":
-            value: "Close Pane"
+            value: "ปิดกระดาน"
       Developer:
-        value: "Developer"
+        value: "นักพัฒนา"
         submenu:
           "Open In Dev Mode…":
-            value: "Open In Dev Mode..."
+            value: "เปิดโหมดนักพัฒนา..."
           "Reload Window":
-            value: "Reload Window"
+            value: "โหลดหน้้าจอใหม่"
           "Run Package Specs":
             value: "Run Package Specs"
           "Run Benchmarks":
             value: "Run Benchmarks"
           "Toggle Developer Tools":
-            value: "Toggle Developer Tools"
+            value: "สลับเครื่องมือนักพัฒนา"
       "Increase Font Size":
-        value: "Increase Font Size"
+        value: "เพิ่มขนาดตัวอักษร"
       "Decrease Font Size":
-        value: "Decrease Font Size"
+        value: "ลดขนาดตัวอักษร"
       "Reset Font Size":
-        value: "Reset Font Size"
+        value: "ปรับขนาดตัวอักษร"
       "Toggle Soft Wrap":
-        value: "Toggle Soft Wrap"
+        value: "สลับการตัดคำ"
       "Toggle Command Palette":
-        value: "Toggle Command Palette"
+        value: "สลับถาดคำสั่ง"
       "Toggle Tree View":
-        value: "Toggle Tree View"
+        value: "สลับวิวต้นไม้"
   Selection:
-    value: "Selection"
+    value: "การเลือก"
     submenu:
       "Add Selection Above":
-        value: "Add Selection Above"
+        value: "เพิ่มการเลือกด้านบน"
       "Add Selection Below":
-        value: "Add Selection Below"
+        value: "เพิ่มการเลือกด้านล่าง"
       "Single Selection":
-        value: "Single Selection"
+        value: "เลือกครั้งเดียว"
       "Split into Lines":
-        value: "Split into Lines"
+        value: "แยกเป็นบรรทัด"
       "Select to Top":
-        value: "Select to Top"
+        value: "เลือกไปจนถึงด้านบนสุด"
       "Select to Bottom":
-        value: "Select to Bottom"
+        value: "เลือกไปจนถึงด้านล่างสุด"
       "Select Line":
-        value: "Select Line"
+        value: "เลือกบรรทัด"
       "Select Word":
-        value: "Select Word"
+        value: "เลือกคำ"
       "Select to Beginning of Word":
-        value: "Select to Beginning of Word"
+        value: "เลือกไปจนถึงจุดเริ่มต้นคำ"
       "Select to Beginning of Line":
-        value: "Select to Beginning of Line"
+        value: "เลือกไปจนถึงต้นบรรทัด"
       "Select to First Character of Line":
-        value: "Select to First Character of Line"
+        value: "เลือกไปจนถึงตัวอักษรแรกของบรรทัด"
       "Select to End of Word":
-        value: "Select to End of Word"
+        value: "เลือกไปจนสุดคำ"
       "Select to End of Line":
-        value: "Select to End of Line"
+        value: "เลือกไปจนสุดบรรทัด"
       "Select Inside Brackets":
-        value: "Select Inside Brackets"
+        value: "เลือกภายในวงเล็บ"
   Find:
-    value: "Find"
+    value: "ค้นหา"
     submenu:
       "Find in Buffer":
-        value: "Find in Buffer"
+        value: "ค้นหาในบัฟเฟอร์"
       "Replace in Buffer":
-        value: "Replace in Buffer"
+        value: "แทนในบัฟเฟอร์"
       "Select Next":
-        value: "Select Next"
+        value: "เลือกถัดไป"
       "Select All":
-        value: "Select All"
+        value: "เลือกทั้งหมด"
       "Toggle Find in Buffer":
-        value: "Toggle Find in Buffer"
+        value: "สลับค้นหาในบัฟเฟอร์"
       "Find in Project":
-        value: "Find in Project"
+        value: "ค้นในโปรเจค"
       "Toggle Find in Project":
-        value: "Toggle Find in Project"
+        value: "สลับค้นในโปรเจค"
       "Find All":
-        value: "Find All"
+        value: "เลือกทั้งหมด"
       "Find Next":
-        value: "Find Next"
+        value: "เลือกถัดไป"
       "Find Previous":
-        value: "Find Previous"
+        value: "เลือกก่อนหน้า"
       "Replace Next":
-        value: "Replace Next"
+        value: "แทนที่ถัดไป"
       "Replace All":
-        value: "Replace All"
+        value: "แทนที่ทั้งหมด"
       "Clear History":
-        value: "Clear History"
+        value: "ล้างประวัติ"
       "Find Buffer":
-        value: "Find Buffer"
+        value: "ค้นบัฟเฟอร์"
       "Find File":
-        value: "Find File"
+        value: "ค้นไฟล์"
       "Find Modified File":
-        value: "Find Modified File"
+        value: "ค้นไฟล์ที่ถูกแก้ไข"
   Packages:
-    value: "Packages"
+    value: "แพคเก็จ"
   Window:
-    value: "Window"
+    value: "หน้าต่าง"
     submenu:
       Minimize:
-        value: "Minimize"
+        value: "ย่อขนาด"
       Zoom:
-        value: "Zoom"
+        value: "ขยาย"
       "Bring All to Front":
-        value: "Bring All to Front"
+        value: "นำมาด้านหน้าทั้งหมด"
   Help:
-    value: "Help"
+    value: "ช่วยเหลือ"
     submenu:
       "Terms of Use":
-        value: "Terms of Use"
+        value: "ข้อกำหนดการใช้งาน"
       Documentation:
-        value: "Documentation"
+        value: "เอกสารประกอบ"
       Roadmap:
-        value: "Roadmap"
+        value: "แนวทาง"
       "Frequently Asked Questions":
-        value: "Frequently Asked Questions"
+        value: "คำถามที่พบบ่อย"
       "Community Discussions":
-        value: "Community Discussions"
+        value: "ข้อคิดเห็นของชุมชน"
       "Report Issue":
-        value: "Report Issue"
+        value: "รายงานปัญหา"
       "Search Issues":
-        value: "Search Issues"
+        value: "ค้นปัญหา"
       "Welcome Guide":
-        value: "Welcome Guide"
+        value: "แนวทางต้นรับ"


enable incremental compiles for all users

--- services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
+++ services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
@@ -144,9 +144,9 @@
 				rootDoc_id: options.rootDocOverride_id or null
 				draft: $scope.draft
 				check: checkType
-				# use incremental compile for beta users but revert to a full
+				# use incremental compile for all users but revert to a full
 				# compile if there is a server error
-				incrementalCompilesEnabled: window.user?.betaProgram and not $scope.pdf.error
+				incrementalCompilesEnabled: not $scope.pdf.error
 				_csrf: window.csrfToken
 			}, {params: params}


limiting the embed requests so they don't fire simultaneously

--- client/landing/app/Applications/Activity.kdapplication/widgets/statuswidget.coffee
+++ client/landing/app/Applications/Activity.kdapplication/widgets/statuswidget.coffee
@@ -17,6 +17,7 @@
           maxLength : 2000

     @previousURL = []
+    @requestEmbedLock = off

     @largeInput = new KDInputView
       cssClass      : "status-update-input"
@@ -31,14 +32,22 @@
           maxLength : 3000
         messages    :
           required  : "Please type a message..."
+
       paste:=>
         @requestEmbed()
-      blur:=>
-        @requestEmbed()
+
+      # # this will cause problems when clicking on a embedLinks url
+      # # right after entering the url -> double request
+
+      # blur:=>
+      #   @requestEmbed()
+
       keyup:=>
+        # this needs to be refactored, this will only capture URLS when the user
+        # adds a space after them
+
         if ($(event.which)[0] is 32) # when space key is hit, URL is usually complete
           @requestEmbed()
-

     @cancelBtn = new KDButtonView
       title       : "Cancel"
@@ -52,12 +61,11 @@
       title       : "Submit"
       type        : "submit"

-    embedOptions = $.extend {}, options, {
+    embedOptions = $.extend {}, options,
       delegate  : @
       hasConfig : yes
       click:->
         no
-    }

     @embedBox = new EmbedBox embedOptions, data

@@ -92,31 +100,22 @@
     @tagAutoComplete = @tagController.getView()

   requestEmbed:=>
-    setTimeout =>
-      firstUrl = @largeInput.getValue().match(/([a-zA-Z]+\:\/\/)?(\w+:\w+@)?([a-zA-Z\d.-]+\.[A-Za-z]{2,4})(:\d+)?(\/\S*)?/g)
-      if firstUrl?
-        @embedBox.embedLinks.setLinks firstUrl
-        # if firstUrl.length is 1
-        #   unless @previousURL is firstUrl
-
-        #     unless firstUrl is null
-        #       @embedBox.embedUrl firstUrl?[0], {
-        #         maxWidth: 525
-        #       }
-        #       @previousURL = firstUrl
-
-        # else
-        #   unless @previousURL is firstUrl
-        #     @embedBox.embedUrl firstUrl?[0], {
-        #       maxWidth: 525
-        #     }
-        #     @previousURL = firstUrl
-        unless @previousURL is firstUrl
-          @embedBox.embedUrl firstUrl?[0], {
-            maxWidth: 525
-          }
-          @previousURL = firstUrl
-    ,500
+    unless @requestEmbedLock is on
+      @requestEmbedLock = on
+      setTimeout =>
+        firstUrl = @largeInput.getValue().match(/([a-zA-Z]+\:\/\/)?(\w+:\w+@)?([a-zA-Z\d.-]+\.[A-Za-z]{2,4})(:\d+)?(\/\S*)?/g)
+        if firstUrl?
+          @embedBox.embedLinks.setLinks firstUrl
+
+          unless @previousURL is firstUrl
+            @embedBox.embedUrl firstUrl?[0], {
+              maxWidth: 525
+            }, =>
+              @requestEmbedLock = off
+              @previousURL = firstUrl
+        else
+          @requestEmbedLock = off
+      ,500

   switchToSmallView:->

@@ -154,6 +153,8 @@

       bodyUrls = @largeInput.getValue().match(/([a-zA-Z]+\:\/\/)?(\w+:\w+@)?([a-zA-Z\d.-]+\.[A-Za-z]{2,4})(:\d+)?(\/\S*)?/g)
       if bodyUrls?
+
+        # put the item with link_url as its url to array[0] for auto-active
         selected = bodyUrls.splice(bodyUrls.indexOf(link.link_url),1)
         bodyUrls.unshift selected[0]
         @embedBox.embedLinks.setLinks bodyUrls


Workspace: Refactored drawing pane. Separated spaghetti logic to methods. Fixed size of canvas.

--- client/CollaborativeWorkspace/panes/collaborativedrawingpane.coffee
+++ client/CollaborativeWorkspace/panes/collaborativedrawingpane.coffee
@@ -1,6 +1,8 @@
 class CollaborativeDrawingPane extends CollaborativePane

   constructor: (options = {}, data) ->
+
+    options.cssClass = KD.utils.curry "ws-drawing-pane", options.cssClass

     super options, data

@@ -8,21 +10,52 @@
     @linesRef    = @workspaceRef.child "lines"
     @stateRef    = @workspaceRef.child "state"
     @usersRef    = @workspaceRef.child "users"
+    @drawedQueue = []
+    @userColors  = {}

+    @container.on "viewAppended", =>
+      @createCanvas()
+      @setUserColor()
+
+      @bindMouseDownOnCanvas()
+      @bindMouseUpOnCanvas()
+      @bindMouseMoveCanvas()
+
+      @workspaceRef.onDisconnect().remove()  if @amIHost
+      @redrawCanvas()  if @isJoinedASession
+
+      @bindRemoteEvents()
+      @container.addSubView @canvas
+
+  createCanvas: ->
     @canvas      = new KDCustomHTMLView
       tagName    : "canvas"
-      domId      : "paintCanvas"
       bind       : "mousemove mousedown mouseup"
       attributes :
-        width    : 640
-        height   : 480
-        style    : "border: 2px solid #333;"
+        width    : @getWidth()
+        height   : @getHeight()

     @context     = @canvas.getElement().getContext "2d"
-    @drawedQueue = []
-    @userColors  = {}
-    @setUserColor()

+  redrawCanvas: ->
+    @context.closePath()
+    @linesRef.once "value", (snapshot) =>
+      value = snapshot.val()
+      return unless value
+      @context.beginPath()
+      for own key, points of value
+        pointsArr = points.split "|"
+        [username, color]     = pointsArr.splice 0, 2
+        @userColors[username] = color
+
+        @context.closePath()
+        @context.beginPath()
+        for point, index in pointsArr
+          [x, y] = point.split ","
+          @context.moveTo x, y  if index is 0
+          @addPoint x, y, username
+
+  bindMouseDownOnCanvas: ->
     @canvas.on "mousedown", (e) =>
       KD.utils.stopDOMEvent e
       x             = e.offsetX
@@ -37,15 +70,7 @@
       @pointRef.set { x, y, nickname: KD.nick() }
       @stateRef.set yes

-    @canvas.on "mouseup", (e) =>
-      @context.closePath()
-      @startDrawing = no
-      @stateRef.set   no
-
-      @drawedQueue.unshift KD.nick(), @userColors[KD.nick()]
-      @linesRef.push @drawedQueue.join "|"
-      @drawedQueue.length = 0
-
+  bindMouseMoveCanvas: ->
     @canvas.on "mousemove", (e) =>
       if @startDrawing
         x = e.offsetX
@@ -56,28 +81,17 @@

         @pointRef.set { x, y, nickname: KD.nick() }

-    @container.addSubView @canvas
+  bindMouseUpOnCanvas: ->
+    @canvas.on "mouseup", (e) =>
+      @context.closePath()
+      @startDrawing = no
+      @stateRef.set   no

-    @workspaceRef.onDisconnect().remove()  if @amIHost
+      @drawedQueue.unshift KD.nick(), @userColors[KD.nick()]
+      @linesRef.push @drawedQueue.join "|"
+      @drawedQueue.length = 0

-    if @isJoinedASession
-      @context.closePath()
-      @linesRef.once "value", (snapshot) =>
-        value = snapshot.val()
-        return unless value
-        @context.beginPath()
-        for own key, points of value
-          pointsArr = points.split "|"
-          [username, color]     = pointsArr.splice 0, 2
-          @userColors[username] = color
-
-          @context.closePath()
-          @context.beginPath()
-          for point, index in pointsArr
-            [x, y] = point.split ","
-            @context.moveTo x, y  if index is 0
-            @addPoint x, y, username
-
+  bindRemoteEvents: ->
     @isContextMoved = yes

     @pointRef.on "value", (snapshot) =>
@@ -100,7 +114,6 @@
       for own key, userData of value
         @userColors[userData.nickname] = userData.color

-
   addPoint: (x, y, nickname = KD.nick()) ->
     ctx             = @context
     ctx.strokeStyle = @userColors[nickname]


pull out the common elements object for readbility

--- test-integration/helpers/task.coffee
+++ test-integration/helpers/task.coffee
@@ -3,24 +3,24 @@

 {TestHelper} = require './test-element'

+COMMON_ELEMENTS =
+  continueButton:
+    locator:
+      css: '.continue'
+  enabledContinueButton:
+    locator:
+      css: '.continue:not([disabled])'
+  stepCrumbs:
+    locator:
+      css: '.task-breadcrumbs-step'
+    isSingle: false
+
 # all convenience functions for helping with task tests will be seen here.
-
 class TaskHelper extends TestHelper
   constructor: (test, testElementLocator) ->
-    commonElements =
-      continueButton:
-        locator:
-          css: '.continue'
-      enabledContinueButton:
-        locator:
-          css: '.continue:not([disabled])'
-      stepCrumbs:
-        locator:
-          css: '.task-breadcrumbs-step'
-        isSingle: false

     testElementLocator ?= '.task-reading, .task-homework'
-    super(test, testElementLocator, commonElements)
+    super(test, testElementLocator, COMMON_ELEMENTS)

   continue: =>
     continueButton = @getContinueButton()


Add period to categories label

Add period to categories label

--- desktop/apps/artsy_primer/personalize/client/views/next_step.coffee
+++ desktop/apps/artsy_primer/personalize/client/views/next_step.coffee
@@ -12,7 +12,7 @@
   copy: [
     {
       header: 'Select Categories'
-      label: 'Select as many as you like'
+      label: 'Select as many as you like.'
     }
     {
       header: 'Select appealing works'


old request find improvement

old request find improvement

--- noddy/service/v2/find.coffee
+++ noddy/service/v2/find.coffee
@@ -68,10 +68,11 @@
       afnd.v2 = API.service.oab.find opts
       try
         afnd.data.ill = afnd.v2.ill
-        afnd.data.meta.article = afnd.v2.metadata
+        afnd.data.meta.article = _.clone afnd.v2.metadata
         afnd.data.meta.cache = afnd.v2.cached
         afnd.data.meta.refresh = afnd.v2.refresh
-        afnd.data.availability.push({type: 'article', url: afnd.v2.url}) if afnd.v2.url
+        afnd.data.meta.article.url = afnd.data.meta.article.url[0] if _.isArray afnd.data.meta.article.url
+        afnd.data.availability.push({type: 'article', url: afnd.data.meta.article.url}) if afnd.data.meta.article.url
       try
         finder = _finder(afnd.v2.metadata)
         if finder isnt '' and request = oab_request.find finder + ' AND type:article'


Updating sysv_service resource snippet

Updating sysv_service resource snippet

--- snippets/sysv_service.cson
+++ snippets/sysv_service.cson
@@ -1,4 +1,4 @@
 '.source.inspec':
   'Describe sysv_service':
     'prefix': 'sysv_service'
-    'body': "# http://inspec.io/docs/reference/resources/sysv_service/\ndescribe sysv_service('${1:name}') do\n  it { should${2:_not} ${3:exist} }${4}\nend"
+    'body': "# http://inspec.io/docs/reference/resources/sysv_service/\ndescribe sysv_service('${1:service_name}') do\n  it { should${2:_not} ${3:be_running} }${4}\nend"


Fix function scope on create method-

Fix function scope on create method-

--- snippets/vue_template.cson
+++ snippets/vue_template.cson
@@ -40,7 +40,7 @@
     prefix: 'vLifecycle'
     leftLabelHTML: 'Ⓢ'
     rightLabelHTML: 'Vue Lifecycle: beforeCreate|created|beforeMount|mounted|beforeUpdate...'
-    body: '${1:beforeCreate|created|beforeMount|mounted|beforeUpdate|updated|activated|deactivated|beforeDestroy|destroyed|errorCaptured}: (){\n\t$1\n},\n'
+    body: '${1:beforeCreate|created|beforeMount|mounted|beforeUpdate|updated|activated|deactivated|beforeDestroy|destroyed|errorCaptured}: (){\n\t$2\n},\n'
   'Vue: Options Assets':
     prefix: 'vAssets'
     leftLabelHTML: 'Ⓢ'


Use the api to do the filtering, avoiding fetching unnecessary data.

--- src/application.coffee
+++ src/application.coffee
@@ -179,6 +179,7 @@
 					'commit'
 				]
 				filter:
+					commit: $ne: null
 					device:
 						uuid: uuid
 			customOptions:
@@ -186,7 +187,6 @@
 		)
 		.then (remoteApps) ->
 			console.log('Remote apps')
-			remoteApps = _.filter(remoteApps, 'commit')
 			remoteApps = _.map remoteApps, (app) ->
 				env =
 					RESIN_DEVICE_UUID: uuid


Fixed visiting /dashboard when not logged in causes a loop #47

--- both/router.coffee
+++ both/router.coffee
@@ -22,11 +22,14 @@
         Meteor.subscribe 'attachments'
       ]
     onBeforeAction: ->
-      url = Session.get 'redirectToAfterSignIn'
-      if url
-        Session.set 'redirectToAfterSignIn', null
-        Router.go url
-      @next()
+      if not Meteor.user()
+        @redirect '/sign-up'
+      else
+        url = Session.get 'redirectToAfterSignIn'
+        if url
+          Session.set 'redirectToAfterSignIn', null
+          Router.go url
+        @next()
     data: ->
       Posts: Posts.find({},{sort: {createdAt: -1}}).fetch()
   @route "profile",


alignment

alignment

--- js/jackInTheBox.coffee
+++ js/jackInTheBox.coffee
@@ -38,7 +38,7 @@
     @show = =>
       @$window.scroll =>
         @$boxes.each (index, box) =>
-          $box    = $(box)
+          $box = $(box)
           if (@visible($box))
             $box.css(visibility: 'visible').addClass @settings.animateClass


Ignore deleted projects when determining the number of uncategorized projects.

--- services/web/public/coffee/main/project-list/project-list.coffee
+++ services/web/public/coffee/main/project-list/project-list.coffee
@@ -20,7 +20,7 @@
 		, 10

 		$scope.$watch((
-			() -> $scope.projects.filter((project) -> !project.tags? or project.tags.length == 0).length
+			() -> $scope.projects.filter((project) -> (!project.tags? or project.tags.length == 0) and !project.archived).length
 		), (newVal) -> $scope.nUntagged = newVal)

 		storedUIOpts = JSON.parse(localStorage("project_list"))


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/camera_fade.coffee
+++ src/camera_fade.coffee
@@ -17,7 +17,7 @@
     duration: 30

   fadeOutDefaults =
-    alpha: 0
+    alpha: 1
     color: 'transparent'
     duration: 30


Fix throttling I just broke

Fix throttling I just broke

--- source/coffee/index.coffee
+++ source/coffee/index.coffee
@@ -48,12 +48,12 @@
         # Stub if missing
         node.actions ?= []
         node.status ?= "unknown"
+        node.children ?= {}
         keyed[nid] = node # Add/update node

       # Step through IDs
       for id of keyed
         node = keyed[id]
-        node.children ?= {}
         pid = node.facts?.parent_id
         if pid? # Has parent ID?
           pnode = keyed?[pid]
@@ -97,9 +97,9 @@
         if update?
           @getData "/roush/updates", (data) =>
             @updateTransaction data
-            setTimeout doit, 1 unless throttle else 1000
+            setTimeout doit, if throttle then 1000 else 1
         else
-          setTimeout doit, 1 unless throttle else 1000
+          setTimeout doit, if throttle then 1000 else 1

       $.ajax
         url: url


Added debug log function for testing sugar (commented out)

--- source/coffee/sugar.coffee
+++ source/coffee/sugar.coffee
@@ -5,6 +5,7 @@

 exports.translate_sugar = (tokens) ->
   out_tokens = coffee_style_functions noparen_function_calls multiline_statements clean tokens
+  #console.log (t.value or t.type for t in out_tokens).join ' '
   return out_tokens

 clean = (tokens) ->


URL visible shortening now using an icon instead of /..

--- core/utils.coffee
+++ core/utils.coffee
@@ -179,7 +179,7 @@
       if not /[A-Za-z]+:\/\//.test url
         # url has no protocol
         url = '//'+url
-      "#{visibleUrl}/…"
+      "#{visibleUrl}"

       # new KDView
       #   partial :   "#{visibleUrl}"


ContextMenu: Add slugified title as class name to context menu items.

--- src/components/contextmenu/contextmenuitem.coffee
+++ src/components/contextmenu/contextmenuitem.coffee
@@ -6,7 +6,7 @@
   constructor:(options = {}, data = {})->

     options.type       = "contextitem"
-    options.cssClass or= "default"
+    options.cssClass or= "default #{KD.utils.slugify data.title}"

     super options, data


ApiGenerator: add securityDefinitions for Bearer token

--- scripts/api-generator.coffee
+++ scripts/api-generator.coffee
@@ -18,6 +18,13 @@
     license:
       name: 'Apache 2.0'
       url: 'http://www.apache.org/licenses/LICENSE-2.0.html'
+
+  securityDefinitions:
+    Bearer:
+      type: 'apiKey'
+      name: 'Authorization'
+      in: 'header'
+
   tags: [
     {
       name: 'system'
@@ -200,6 +207,7 @@
         tags: [ name ]
         consumes: [ 'application/json' ]
         parameters: parameters ? []
+        security: [ { Bearer: [] } ]
         description: docs[name]['static'][method]?.description ? ''
         responses:
           '200':
@@ -238,6 +246,7 @@
     paths["/remote.api/#{name}.#{method}/{id}"] =
       post:
         tags: [ name ]
+        security: [ { Bearer: [] } ]
         consumes: [ 'application/json' ]
         description: docs[name]['instance'][method]?.description ? ''
         parameters: parameters


Add tests for copy/paste between lists

Add tests for copy/paste between lists

--- spec/list-edit-spec.coffee
+++ spec/list-edit-spec.coffee
@@ -36,34 +36,75 @@
       atom.commands.dispatch editorView, 'list-edit:cut'
       expect(editor.getText()).toBe '[Blinky, Inky, Clyde]'

-    describe 'list-paste', ->
-      it 'Cursor before first element of multi-element list pastes clipboard element with correct whitespace and separator', ->
-        atom.clipboard.write 'NewGhost', ListEdit.mkListEditMeta('[', ',')
-        editor.setText '[Blinky, Pinky, Inky, Clyde]'
-        editor.setCursorBufferPosition [0, 1]
-        atom.commands.dispatch editorView, 'list-edit:paste'
-        expect(editor.getText()).toBe '[NewGhost, Blinky, Pinky, Inky, Clyde]'
+  describe 'list-paste', ->
+    it 'Cursor before first element of multi-element list pastes clipboard element with correct whitespace and separator', ->
+      atom.clipboard.write 'NewGhost', ListEdit.mkListEditMeta('[', ',')
+      editor.setText '[Blinky, Pinky, Inky, Clyde]'
+      editor.setCursorBufferPosition [0, 1]
+      atom.commands.dispatch editorView, 'list-edit:paste'
+      expect(editor.getText()).toBe '[NewGhost, Blinky, Pinky, Inky, Clyde]'

-      it 'Cursor before between elements of multi-element list pastes clipboard element with correct whitespace and separator', ->
-        atom.clipboard.write 'NewGhost', ListEdit.mkListEditMeta('[', ',')
-        editor.setText '[Blinky, Pinky, Inky, Clyde]'
-        editor.setCursorBufferPosition [0, 7]
-        atom.commands.dispatch editorView, 'list-edit:paste'
-        expect(editor.getText()).toBe '[Blinky, NewGhost, Pinky, Inky, Clyde]'
+    it 'Cursor before between elements of multi-element list pastes clipboard element with correct whitespace and separator', ->
+      atom.clipboard.write 'NewGhost', ListEdit.mkListEditMeta('[', ',')
+      editor.setText '[Blinky, Pinky, Inky, Clyde]'
+      editor.setCursorBufferPosition [0, 7]
+      atom.commands.dispatch editorView, 'list-edit:paste'
+      expect(editor.getText()).toBe '[Blinky, NewGhost, Pinky, Inky, Clyde]'

-      it 'Cursor after last element of multi-element list pastes clipboard element with correct whitespace and separator', ->
-        atom.clipboard.write 'NewGhost', ListEdit.mkListEditMeta('[', ',')
-        editor.setText '[Blinky, Pinky, Inky, Clyde]'
-        editor.setCursorBufferPosition [0, 27]
-        atom.commands.dispatch editorView, 'list-edit:paste'
-        expect(editor.getText()).toBe '[Blinky, Pinky, Inky, Clyde, NewGhost]'
+    it 'Cursor after last element of multi-element list pastes clipboard element with correct whitespace and separator', ->
+      atom.clipboard.write 'NewGhost', ListEdit.mkListEditMeta('[', ',')
+      editor.setText '[Blinky, Pinky, Inky, Clyde]'
+      editor.setCursorBufferPosition [0, 27]
+      atom.commands.dispatch editorView, 'list-edit:paste'
+      expect(editor.getText()).toBe '[Blinky, Pinky, Inky, Clyde, NewGhost]'

-      it 'Cursor inside element of multi-element list replaces element with clipboard element', ->
-        atom.clipboard.write 'NewGhost', ListEdit.mkListEditMeta('[', ',')
-        editor.setText '[Blinky, Pinky, Inky, Clyde]'
-        editor.setCursorBufferPosition [0, 10]
-        atom.commands.dispatch editorView, 'list-edit:paste'
-        expect(editor.getText()).toBe '[Blinky, NewGhost, Inky, Clyde]'
+    it 'Cursor inside element of multi-element list replaces element with clipboard element', ->
+      atom.clipboard.write 'NewGhost', ListEdit.mkListEditMeta('[', ',')
+      editor.setText '[Blinky, Pinky, Inky, Clyde]'
+      editor.setCursorBufferPosition [0, 10]
+      atom.commands.dispatch editorView, 'list-edit:paste'
+      expect(editor.getText()).toBe '[Blinky, NewGhost, Inky, Clyde]'
+
+  describe 'list-copy/paste between lists', ->
+    listCopyFrom = (txt, row, col) ->
+      editor.setText txt
+      editor.setCursorBufferPosition [row, col]
+      atom.commands.dispatch editorView, 'list-edit:copy'
+
+  # 0123456789012345678
+    verticalListTxt = '''
+    vertical = [ Larry
+               , Moe
+               , Curly
+               ]
+    '''
+
+    it 'uses separator whitespace from the target, if available', ->
+      listCopyFrom verticalListTxt, 1, 14 # copy Moe
+      #               01234567890123456789012345678901234567890
+      editor.setText '[   Blinky ,  Pinky ,  Inky ,  Clyde   ]'
+      editor.setCursorBufferPosition [0, 11]
+      atom.commands.dispatch editorView, 'list-edit:paste'
+      expect(editor.getText()).toBe '[   Blinky ,  Moe ,  Pinky ,  Inky ,  Clyde   ]'
+
+    it 'uses separator whitespace from the source, if not available from target', ->
+      listCopyFrom   '[   Blinky ,  Pinky ,  Inky ,  Clyde   ]', 0, 32 # copy Clyde
+      #               01234567890123456789012345678901234567890
+      editor.setText '[  Blinky  ]'
+      editor.setCursorBufferPosition [0, 11]
+      atom.commands.dispatch editorView, 'list-edit:paste'
+      expect(editor.getText()).toBe '[  Blinky ,  Clyde  ]'
+
+    # Not ideal, as we throw away the bracket whitespace already present, but it seems more logical
+    # than splitting the existing whitespace into initial and final, and also, on cutting all elements,
+    # we remove initial and final whitespace as well.
+    it 'uses bracket whitespace from the source, if not available from target', ->
+      listCopyFrom   '[   Blinky ,  Pinky ,  Inky ,  Clyde   ]', 0, 32 # copy Clyde
+      #               01234567890123456789012345678901234567890
+      editor.setText '[  ]'
+      editor.setCursorBufferPosition [0, 2]
+      atom.commands.dispatch editorView, 'list-edit:paste'
+      expect(editor.getText()).toBe '[   Clyde   ]'

 describe 'ListEdit on a file with a JavaScript grammar', ->
   [editor, editorView] = []


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/core_object.coffee
+++ src/core_object.coffee
@@ -146,6 +146,8 @@
         if Module.isString?()
           moduleName = Module
           Module = Module.constantize()
+        else
+          # Attempt to discover module name

         unless I.included_modules.include moduleName
           self.extend Module(I, self)


Moderation: child/deleted topic retrieval is only allowed to super-admins

--- workers/social/lib/social/models/tag.coffee
+++ workers/social/lib/social/models/tag.coffee
@@ -81,6 +81,8 @@
           (signature Object, Function)
         removeTagFromStatus:
           (signature Object, Function)
+        fetchSynonym:
+          (signature Function)
         delete:
           (signature Function)
         fetchRandomFollowers:
@@ -288,6 +290,7 @@

   @create$ = permit 'create tags',
     success: (client, data, callback)->
+      {connection:{delegate}} = client
       data.category = "user-tag"
       data.group = client.context.group
       create data, delegate, callback
@@ -314,13 +317,20 @@
           create {title, group}, delegate, (err, tag) =>
             return callback err if err
             addSynonym tag
-        else if tag.status is 'synonym' #for preventing circular synonym definition
-          tag.fetchSynonym (err, synonym) =>
-            if String(synonym?.getId()) is String(@getId())
-              return callback new KodingError "Circular Synonym Definition!"
+        else if tag.status is "synonym" or tag.status is "deleted"
+          return callback new KodingError "##{tag.title} already set as #{tag.status}!"
+        else
+          @checkChildSynonyms (err) =>
+            return callback err if err
             addSynonym tag
-        else
-          addSynonym tag
+
+  checkChildSynonyms : (callback) =>
+    Relationship.one "targetId": @getId(), "as": "synonymOf", (err, childSynonym) =>
+      return callback err if err
+      if childSynonym
+        return callback new KodingError "##{@title} have child tags! You must first delete them"
+      callback null
+

   @findSuggestions = (client, seed, options, callback)->
     {limit, blacklist, skip, category} = options
@@ -331,7 +341,7 @@
         _id     :
           $nin  : blacklist
         category: "user-tag"
-
+        status  : $ne : 'deleted'
       },{
         skip
         limit
@@ -384,6 +394,7 @@
   @_some = (client, selector, options, callback)->
     selector.group    = makeGroupSelector client.context.group
     selector.category = "user-tag"
+    setSelectorStatus client, selector
     @some selector, options, callback

   @some$ = permit 'read tags', success: @_some
@@ -398,18 +409,21 @@
       selector ?= {}
       selector.group    = makeGroupSelector client.context.group
       selector.category = "user-tag"
+      setSelectorStatus client, selector
       @count selector, callback

   @cursor$ = permit 'read tags',
     success:(client, selector, options, callback)->
       selector.group    = makeGroupSelector client.context.group
       selector.category = "user-tag"
+      setSelectorStatus client, selector
       @cursor selector, options, callback

   @each$ = permit 'read tags',
     success:(client, selector, fields, options, callback)->
       selector.group    = makeGroupSelector client.context.group
       selector.category = "user-tag"
+      setSelectorStatus client, selector
       @each selector, fields, options, callback

   @byRelevance$ = permit 'read tags',
@@ -501,3 +515,7 @@
     Followable = require '../traits/followable'
     Followable::follow.call this, client, options, callback

+  setSelectorStatus = (client, selector) ->
+    {connection:{delegate}} = client
+    # selector.status = $nin: ['deleted','synonym'] unless delegate.checkFlag 'super-admin'
+


Simulator: Recovery moves send %

Simulator: Recovery moves send %

--- data/bw/moves.coffee
+++ data/bw/moves.coffee
@@ -179,7 +179,8 @@
         @fail(battle)
         return false
       amount = Math.round(hpStat / 2)
-      battle.message "#{target.name} recovered #{amount} HP!"
+      percent = Math.floor(amount / target.stat('hp'))
+      battle.message "#{target.name} recovered #{percent} HP!"
       target.damage(-amount)

 makeBasePowerBoostMove = (name, rawBasePower, maxBasePower, what) ->
@@ -199,7 +200,8 @@
         util.roundHalfDown(target.stat('hp') * 2 / 3)
       else
         util.roundHalfDown(target.stat('hp') / 4)
-      battle.message "#{target.name} recovered #{amount} HP!"
+      percent = Math.floor(amount / target.stat('hp'))
+      battle.message "#{target.name} recovered #{percent} HP!"
       target.damage(-amount)

 makeTrickMove = (name) ->


remote: handle circular reference in wrapArgs

--- atom/renderer/api/lib/remote.coffee
+++ atom/renderer/api/lib/remote.coffee
@@ -5,18 +5,29 @@

 callbacksRegistry = new CallbacksRegistry

+# Check for circular reference.
+isCircular = (field, visited) ->
+  if typeof field is 'object'
+    if field in visited
+      return true
+    visited.push field
+  return false
+
 # Convert the arguments object into an array of meta data.
-wrapArgs = (args) ->
+wrapArgs = (args, visited=[]) ->
   valueToMeta = (value) ->
     if Array.isArray value
-      type: 'array', value: wrapArgs(value)
+      type: 'array', value: wrapArgs(value, visited)
     else if Buffer.isBuffer value
       type: 'buffer', value: Array::slice.call(value, 0)
     else if value? and typeof value is 'object' and v8Util.getHiddenValue value, 'atomId'
       type: 'remote-object', id: v8Util.getHiddenValue value, 'atomId'
     else if value? and typeof value is 'object'
       ret = type: 'object', name: value.constructor.name, members: []
-      ret.members.push(name: prop, value: valueToMeta(field)) for prop, field of value
+      for prop, field of value
+        ret.members.push
+          name: prop
+          value: valueToMeta(if isCircular(field, visited) then null else field)
       ret
     else if typeof value is 'function' and v8Util.getHiddenValue value, 'returnValue'
       type: 'function-with-return-value', value: valueToMeta(value())


use more generic terminology

use more generic terminology

--- html-app/app/scripts/directives/pagination-nav.coffee
+++ html-app/app/scripts/directives/pagination-nav.coffee
@@ -19,7 +19,7 @@
         {{pageNum}}

-

+

   """
   replace: true


Remove some deprecated code

Remove some deprecated code

--- src/eg/services.coffee
+++ src/eg/services.coffee
@@ -223,8 +223,6 @@
 				}

 				auth.setup_timeout response.authtime
-				#$().publish 'login_event', [attempted_username]
-				#attempted_username = ''
 				return response
 			login_required: false


engine data module

engine data module

--- src/engine_data.coffee
+++ src/engine_data.coffee
@@ -8,7 +8,8 @@
 @param {Object} self Reference to the engine
 ###
 Engine.Data = (I, self) ->
-  Object.reverse
+  Object.reverseMerge I,
+    data: {}

   get: ->
   set: ->


Reset map if there's no relics selected

Reset map if there's no relics selected

--- app/assets/javascripts/angular/controllers/walking_guide_ctrl.coffee
+++ app/assets/javascripts/angular/controllers/walking_guide_ctrl.coffee
@@ -4,13 +4,15 @@
     $scope.relics = []
     directionsService  = new google.maps.DirectionsService()
     directionsRenderer = new google.maps.DirectionsRenderer()
+    center =
+      latitude: 52.4118436
+      longitude: 19.0984013
+    zoom = 6

     $scope.map =
       instance: null
-      center:
-        latitude: 52.4118436
-        longitude: 19.0984013
-      zoom: 6
+      center: center
+      zoom: zoom
       events:
         tilesloaded: (map) ->
           $scope.$apply ->
@@ -42,11 +44,18 @@
       $scope.relics.splice(index, 1)
       $scope.drawRoute()

+    $scope.resetMap = ->
+      $scope.map.instance.setCenter(new google.maps.LatLng(center.latitude, center.longitude))
+      $scope.map.instance.setZoom(zoom)
+
     $scope.clearRoute = ->
       directionsRenderer.setMap(null)

     $scope.drawRoute = ->
       $scope.clearRoute()
+
+      if !$scope.relics.length
+        return $scope.resetMap()

       request =
         origin: $scope.relics.first().latlng


Stringify args in job modal to improve readability

--- app/assets/javascripts/sidekiq/monitor/abstract_jobs_table.js.coffee
+++ app/assets/javascripts/sidekiq/monitor/abstract_jobs_table.js.coffee
@@ -129,7 +129,7 @@

               Args
-              #{args}
+              #{JSON.stringify(args, null, 2)}

               Started


Updated variant-selection to load values when variant defined

--- app/assets/javascripts/sprangular/directives/variantSelection.coffee
+++ app/assets/javascripts/sprangular/directives/variantSelection.coffee
@@ -22,3 +22,10 @@
     $scope.selectValue = (value) ->
       $scope.values[value.option_type_id] = value
       $scope.variant = $scope.product.variantForValues(_.values($scope.values))
+
+  link: (scope, element, attrs) ->
+    scope.values = {}
+
+    if scope.variant
+      for value in scope.variant.option_values
+        scope.values[value.option_type_id] = value


removes confusing hover over tooltips from the new files restrict access modal radio buttons Fixes: CNVS-16560

Test Plan:
- Ensure that " Better File Browsing" feature flag is enabled.
- Go to Course/x/files
- Select a file.
- Click "Restrict Access" button on the toolbar.
- Verify that no confusing hover over tooltip appears on the radio buttons.
- Ensure that screen reader reads the radio buttons correctly.

Change-Id: I3396d2a16a2d7e75e54ac495644f36b9a34a8097
Reviewed-on: https://gerrit.instructure.com/43551
Reviewed-by: Ryan Shaw <ea3cd978650417470535f3a4725b6b5042a6ab59@instructure.com>
Product-Review: Ryan Shaw <ea3cd978650417470535f3a4725b6b5042a6ab59@instructure.com>
QA-Review: Ryan Shaw <ea3cd978650417470535f3a4725b6b5042a6ab59@instructure.com>
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>

--- app/coffeescripts/react_files/components/RestrictedDialogForm.coffee
+++ app/coffeescripts/react_files/components/RestrictedDialogForm.coffee
@@ -89,8 +89,7 @@
       form {
         ref: 'dialogForm',
         onSubmit: @handleSubmit,
-        className: 'form-horizontal form-dialog permissions-dialog-form',
-        title: I18n.t("title.limit_student_access", "Permissions")
+        className: 'form-horizontal form-dialog permissions-dialog-form',
       },
         div className: "radio",
           label {},
@@ -102,7 +101,7 @@
               onChange: (event) =>
                 @setState { selectedOption: 'published'}
             },
-            I18n.t("options.publish.description", "Publish")
+          I18n.t("options.publish.description", "Publish")

         div className: "radio",
           label {},


Use function rather than inline.

Use function rather than inline.

--- lib/auto-indent.coffee
+++ lib/auto-indent.coffee
@@ -107,8 +107,7 @@
     highestRow = Math.max selectedRange.start.row, selectedRange.end.row
     if highestRow isnt @highestSelectedRow
       @highestSelectedRow = highestRow
-      scope = @editor.scopeDescriptorForBufferPosition([highestRow,0]).getScopesArray()
-      if 'meta.tag.jsx' in scope
+      if @jsxInScope(highestRow)
         endPointOfJsx = new Point highestRow,0
         startPointOfJsx =  autoCompleteJSX.getStartOfJSX @editor, endPointOfJsx
         @editor.transact 300, =>


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/flickerable.coffee
+++ src/flickerable.coffee
@@ -36,10 +36,12 @@
   player.include(Flickerable)

   player.flicker()
-  # => This causes the sprite to flicker between full opacity and 50% opacity every 3 frames for 30 frames
+  # => This causes the sprite to flicker between full opacity
+  # => and 50% opacity every 3 frames for 30 frames

   player.flicker(90, 5, 0.3)
-  # => This causes the sprite to flicker between full opacity and 30% opacity every 5 frames for 90 frames
+  # => This causes the sprite to flicker between full opacity
+  # => and 30% opacity every 5 frames for 90 frames
   </pre></code>

   @name flicker


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

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

     # called with no arguments, flicker will toggle the player's alpha
     # value between 0.9 (value provided above) and 0.5 (flickerable default)
-    # every 0.1 second, lasting 2 seconds total
+    # every 0.1 second, for a total of 2 seconds
     player.flicker()

 @name Flickerable


need util for inspect

need util for inspect

--- services/data/helpers.coffee
+++ services/data/helpers.coffee
@@ -1,3 +1,4 @@
+util = require 'util'
 _ = require 'underscore'

 # Avoid wasting time on static resources


Remove generate kite key script execution in default configuration

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

--- config/main.default.coffee
+++ config/main.default.coffee
@@ -69,25 +69,6 @@
   KONFIG.workers = require('./workers')(KONFIG, options, credentials)
   KONFIG.client.runtimeOptions = require('./generateRuntimeConfig')(KONFIG, credentials, options)

-  generateSh = "#{options.projectRoot}/config/generate.sh"
-
-  # BUG(rjeczalik): The Configuration gets executed twice, once with uninitialized
-  # options, which makes the following code execute generate.sh with
-  # options.projectRoot equal to "/opt/koding/config". The todo here is to
-  # fix it so it gets executed only once one remove the workaround.
-  if fs.existsSync generateSh
-    { execFile } = require 'child_process'
-
-    execFile generateSh, ["#{KONFIG.kontrol.url}"], (err, stdout, stderr) ->
-      process.stderr.write stdout
-      process.stderr.write stderr
-
-      if err
-        console.log """
-          failed to run #{options.projectRoot}/config/generate.sh (error: #{err})
-          please execute it manually and most likely install missing dependencies
-        """
-        process.exit 1

   options.disabledWorkers = [
     "algoliaconnector"


Remove flags, they lead to render errors and don't impact perfs noticeably.

--- jax-core/lib/assets/javascripts/jax/texture.js.coffee
+++ jax-core/lib/assets/javascripts/jax/texture.js.coffee
@@ -25,23 +25,6 @@
 class Jax.Texture
   @include Jax.Mixins.Attributes
   @include Jax.Mixins.EventEmitter
-
-  # Texture params flags, for tracking params needing refreshed
-  FLAGS =
-    texture_validity:      0x0001
-    min_filter:            0x0002
-    mag_filter:            0x0004
-    generate_mipmap:       0x0008
-    mipmap_hint:           0x0010
-    format:                0x0020
-    target:                0x0040
-    data_type:             0x0080
-    wrap_s:                0x0100
-    wrap_t:                0x0200
-    flip_y:                0x0400
-    premultiply_alpha:     0x0800
-    colorspace_conversion: 0x1000
-    data:                  0x2000

   constructor: (options) ->
     @initializeAttributes()
@@ -80,8 +63,7 @@
   invalid, and also makes a note as to which parameters must be refreshed.
   ###
   texParamChanged: (self, evtName) =>
-    param = evtName.substring(evtName.indexOf('change:')+7, evtName.length)
-    @invalidate param
+    @invalidate()

   ###
   Finds or creates and then returns the texture handle for the given context.
@@ -105,8 +87,7 @@
   updated.
   ###
   invalidate: (param) ->
-    flags = @get('changedTexParams') || 0
-    @set 'changedTexParams', flags | FLAGS.texture_validity | FLAGS[param]
+    @set 'valid', false

   ###
   Returns true if this texture is valid for rendering, false otherwise.
@@ -114,13 +95,13 @@
   will cause it to be invalid. Textures are invalid by default. They become
   valid automatically when they are bound to a graphics driver.
   ###
-  isValid: -> @get 'changedTexParams' & FLAGS.texture_validity
+  isValid: -> @get 'valid'

   ###
   Forces this texture to become valid, even if the latest changes have not
   been reflected within the graphics driver.
   ###
-  forceValid: -> @set 'flags', 0
+  forceValid: -> @set 'valid', true

   ###
   Subclasses must implement this method. It is called during validation,
@@ -152,8 +133,7 @@
     return false unless @isReady()
     attrs = @attributes
     handle = @getHandle context
-    flags = attrs.changedTexParams
-    return handle unless flags & FLAGS.texture_validity
+    return handle if @get('valid')
     # texture is not valid, make it so!
     target = attrs.target
     gl = context.renderer
@@ -174,7 +154,7 @@
       @generateMipmap gl, target, attrs.mipmap_hint

     # done!
-    @set 'changedTexParams', 0
+    @forceValid()
     return handle

   generateMipmap: (renderer, target, hint) ->


states

states

--- client/game.coffee
+++ client/game.coffee
@@ -19,11 +19,11 @@
 SPRITE_Z_INDEX = 2 # The Z-Index to add new sprites at

 noughts.state =
-  PLAY: me.state.PLAY
-  INITIAL_PROMO: me.state.USER + 0 # Initial game description state
-  NEW_GAME_MENU: me.state.USER + 1 # Game state for showing new game menu
-  FACEBOOK_INVITE: me.state.USER + 2 # Game state for showing new game menu
-  LOADING: me.state.USER + 3 # Game loading state
+  PLAY: "PLAY"
+  INITIAL_PROMO: "INITIAL_PROMO" # Initial game description state
+  NEW_GAME_MENU: "NEW_GAME_MENU" # Game state for showing new game menu
+  FACEBOOK_INVITE: "FACEBOOK_INVITE" # Game state for showing new game menu
+  LOADING: "LOADING" # Game loading state

 # Changes the current game state to 'newState'. The optional "urlBehavior"
 # parameter shoud be a noughts.state.UrlBehavior, and the browser URL will be
@@ -37,21 +37,22 @@
     me.state.current().onExitState()
   urlBehavior ||= noughts.state.UrlBehavior.PUSH_URL
   Session.set("state", newState)
-  me.state.change.apply(null, arguments)
+  newScreen = noughts.state.stateMap[newState]
+  newScreen.enterState.apply(newScreen, _.rest(arguments))

 # What a new state should do to the browser URL when entered.
 noughts.state.UrlBehavior =
   # Change the current URL and add the old one to the browser history stack.
   # The default behavior.
-  PUSH_URL: 1
+  PUSH_URL: "PUSH_URL"

   # Keep the existing URL without modifying browser history. Used when e.g.
   # determining the initial state from the URL on page load.
-  PRESERVE_URL: 2
+  PRESERVE_URL: "PRESERVE_URL"

   # Change the current URL and do not add the previous URL to the history stack.
   # Used to e.g. implement a redirect.
-  REPLACE_URL: 3
+  REPLACE_URL: "REPLACE_URL"

 # Stores the initial length of the browser history. Used to figure out if
 # invoking noughts.state.back() will take us off-site.
@@ -124,7 +125,8 @@

 # Loads the game with the specified ID
 playGame = (gameId) ->
-  noughts.state.changeState(noughts.state.PLAY, noughts.state.PUSH_URL, gameId)
+  noughts.state.changeState(noughts.state.PLAY,
+      noughts.state.UrlBehavior.PUSH_URL, gameId)

 # Pops up an alert to the user saying that an error has occurred. Should be used
 # for un-recoverable errors.
@@ -242,13 +244,12 @@
 # Displays a short informative message to the user.
 displayNotice = (msg) -> $(".nNotification").text(msg)

-noughts.Screen = me.ScreenObject.extend
-  onResetEvent: (urlBehavior) ->
-    debugger
+noughts.Screen = Object.extend
+  enterState: ->
     @onEnterState.apply(this, arguments)

-noughts.LoadingScreen = me.ScreenObject.extend
-  onResetEvent: ->
+noughts.LoadingScreen = noughts.Screen.extend
+  onEnterState: ->
     $(".nMain").children().hide()
     $(".nScreenLoading").show()

@@ -297,8 +298,8 @@
     $(".nMain").children().hide()
     $(".nScreenNewGame").show()

-noughts.FacebookInviteMenu = me.ScreenObject.extend
-  onResetEvent: (urlBehavior) ->
+noughts.FacebookInviteMenu = noughts.Screen.extend
+  onEnterState: (urlBehavior) ->
     # TODO(dthurn): Log user into facebook here if they aren't yet.
     noughts.state.updateUrl("/facebookInvite", urlBehavior)
     $(".nMain").children().hide()
@@ -418,13 +419,19 @@
 # Functions to run as soon as possible on startup. Defines the state map
 # and adds a melonjs callback.
 Meteor.startup ->
-  me.state.set(noughts.state.PLAY, new noughts.PlayScreen())
-  me.state.set(noughts.state.NEW_GAME_MENU, new noughts.NewGameMenu())
-  me.state.set(noughts.state.INITIAL_PROMO, new noughts.InitialPromo())
-  me.state.set(noughts.state.FACEBOOK_INVITE, new noughts.FacebookInviteMenu())
-  me.state.set(noughts.state.LOADING, new noughts.LoadingScreen())
+  noughts.state.stateMap = {
+    PLAY: new noughts.PlayScreen()
+    NEW_GAME_MENU: new noughts.NewGameMenu()
+    INITIAL_PROMO: new noughts.InitialPromo()
+    FACEBOOK_INVITE: new noughts.FacebookInviteMenu()
+    LOADING: new noughts.LoadingScreen()
+  }
+
   noughts.state.changeState(noughts.state.LOADING,
       noughts.state.UrlBehavior.PRESERVE_URL)
+
+  me.state.set(me.state.PLAY, new me.ScreenObject())
+  me.state.change(me.state.PLAY)

   window.onReady(-> initialize())
   $.when(noughts.melonDeferred, noughts.facebookDeferred).done ->


Handle missing year and \includegraphics optional

--- client/import.iced
+++ client/import.iced
@@ -246,12 +246,15 @@
       console.log match[0] unless title
       title = title[1][1...-1].replace /[{}]/g, ''
       year = /\byear\s*=\s*("(?:{[^{}]*}|[^"])*"|{(?:[^{}]|{[^{}]*})*}|\d+)/i.exec match[0]
-      year = year[1].replace /["{}]/g, ''
+      if year?
+        year = year[1].replace /["{}]/g, ''
+      else
+        console.warn match[1], 'has no year'
       if author.length <= 1
         abbrev = /([^{}]|{[^{}]*}){1,3}/.exec(lastName author[0])[0].replace /[{}]/g, ''
       else
         abbrev = (lastName(auth)[0] for auth in author).join ''
-      abbrev += year[-2..]
+      abbrev += year[-2..] if year?
       console.log match[1], '=', abbrev, '=', author.join(' & '), year, '=', match[2][1...-1]
       bibs[match[1]] =
         author: author
@@ -294,7 +297,7 @@
     graphics = []
     text.replace /\\includegraphics\s*(\[[^\[\]]*\]\s*)?{((?:[^{}]|{[^{}]*})*)}/g,
       (match, optional, filename) ->
-        if optional.match /page\s*=/
+        if optional?.match /page\s*=/
           console.warn 'Import does not support \\includegraphics[page=...]'
         graphics.push
           filename: filename


hided line numbers in ace

hided line numbers in ace

--- client/main.coffee
+++ client/main.coffee
@@ -98,6 +98,13 @@
   initEditor: (model) ->
     @editor = new Editor model: model

+    # Hide the line numbering, doesn't work perfectly
+    lineNumberWidth = parseInt($(".ace_scroller").css('left'))
+    alert(lineNumberWidth)
+    $(".ace_scroller").css('left', '0px')
+    $(".ace_gutter").hide()
+    $(".ace_scroller").css('width',  parseInt($(".ace_scroller").css('width')) + lineNumberWidth)
+
     @editor.bind "init", =>
       @preview.id = model.get "id"
       @preview.reload()


Zoom into video feeds that are in 16:9 so that they fill 4:3 box

--- client/tawk.coffee
+++ client/tawk.coffee
@@ -71,7 +71,7 @@
     connections = sb[server + '/connections']
     active_connections = sb['tawk/active_connections']

-    available_width = sb['tawk/width'] - 30 # Margin between groups and edge
+    available_width = sb['tawk/width'] - 100 # Margin between groups and edge
     available_height = sb['tawk/height'] - 100 # Some room for scratch space and margin with topbar

     # Calculate dimensions based on if everybody is in one group
@@ -82,7 +82,7 @@
     estimated_width = available_width / size_if_all_in_one_group.width
     estimated_height = available_height / size_if_all_in_one_group.height

-    # 35 x 48 is the minimum
+    # 36 x 48 is the minimum
     person_height = Math.max(estimated_height, 36)
     person_width = Math.max(estimated_width, 48)

@@ -324,9 +324,12 @@
           VIDEO
             autoPlay: 'true'
             style:
-              position: 'relative'
+              position: 'absolute'
+              top: 0
+              left: 0
+              width: '100%'
               height: '100%'
-              width: '100%'
+              objectFit: 'cover'
               zIndex: -1
               opacity: .9999 # http://stackoverflow.com/questions/5736503
               borderRadius: 1 # TODO: why is this necessary? https://github.com/invisible-college/tawk.space/issues/40


prevents #1349 regression

prevents #1349 regression

--- jsapp/xlform_model_view/view.row.coffee
+++ jsapp/xlform_model_view/view.row.coffee
@@ -164,7 +164,7 @@
       ,
       edit_callback: (value) ->
         value = value.replace(new RegExp(String.fromCharCode(160), 'g'), '')
-        value = value.replace /\t/g, ''
+        value = value.replace /\t/g, ' '
         view.model.set 'value', value

         if value == ''


:memo: Add a todo in linter-plus

:memo: Add a todo in linter-plus

--- lib/linter-plus.coffee
+++ lib/linter-plus.coffee
@@ -25,6 +25,7 @@
       @views.setShowPanel(showPanel)
     @subscriptions.add atom.config.observe 'linter.underlineIssues', (underlineIssues) =>
       @views.setUnderlineIssues(underlineIssues)
+    # Todo: When observing that showErrorTabLine updateTabs() is called thrice, resulting in redundant updates
     @subscriptions.add atom.config.observe 'linter.showErrorTabLine', =>
       @views.updateTabs()
     @subscriptions.add atom.config.observe 'linter.showErrorTabFile', =>