Update translations.coffee

Update translations.coffee

--- addon/locales/zh-tw/translations.coffee
+++ addon/locales/zh-tw/translations.coffee
@@ -18,7 +18,7 @@
   "back" : "回到上一頁"
   "end" : "結束"
   "done": "完成"
-  "other": "Other"
+  "other": "其他"
   "error.message" : "出錯了，請見諒！"
   "images" : "圖片"
   "delete" : "刪除"
@@ -47,15 +47,15 @@
   "thank_you": "多謝"
   "go_to_top": "回到頁面頂端"
   "input_length_error_message" : "InputWithCounter doesn\'t work without a maxlength attribute'"
-  "loading_timeout_error": "Loading view timeout reached."
+  "loading_timeout_error": "Loading view timeout reached.時限而過"
   "items_text": "項目"
-  "version": "Version"
-  "crossroads_foundation": "Crossroads Foundation"
+  "version": "版本"
+  "crossroads_foundation": "十字路會"

   "menu":
-    "terms": "Terms of Use"
-    "policy": "Privacy Policy"
-    "version_short": "ver."
+    "terms": "使用條款"
+    "policy": "個人私隱政策"
+    "version_short": "版本"

   "day":
     "today": "今天"
@@ -118,7 +118,7 @@
     "unread" : "({{unreadCount}}) 條未讀信息"
     "owner" : "你"
     "today_with_time": "今天, {{time}}"
-    "chat_note": "Chat about this item with our reviewers"
+    "chat_note": "與審查員討論此項物資"

   "notifications":
     "view": "查看"
@@ -142,8 +142,8 @@
       "back": "回到上一頁"
       "cancel_booking": "取消預約"
       "confirm_ggv_cancel": "當GoGoVan確認預約取消，您就能取消預約"
-      "driver_mobile": "Driver's Mobile"
-      "ggv_office": "GoGoVan Office"
+      "driver_mobile": "司機電話號碼"
+      "ggv_office": "GoGoVan辦公室"

   "delivery_details":
     "name": "聯絡人姓名"
@@ -185,13 +185,13 @@
     "missing": "{{name}} 將此物的狀況改為'失蹤'"

   "offer_log":
-    "donor_called": "{{name}} called"
-    "call_accepted": "{{name}} answered call"
+    "donor_called": "{{name}} 曾來電"
+    "call_accepted": "{{name}} 接聽來電"

   "edit_images":
-    "upload": "Choose Image"
-    "camera": "Take Photo"
-    "cancel": "Cancel"
+    "upload": "選擇圖片"
+    "camera": "拍照"
+    "cancel": "取消"
     "add_photo" : "加入圖片"
     "delete_confirm" : "確定要刪除圖片嗎？"
     "cant_delete_last_image" : "最少需要一張圖片"
@@ -203,8 +203,8 @@
     "image_uploading": "圖片上載中"
     "remove_image": "移除圖片"
     "cancel_item": "取消項目"
-    "cannot_provide_photo": "Cannot provide photo"
-    "last_image_with_item": "This is the only image associated with this item"
+    "cannot_provide_photo": "沒有照片提供"
+    "last_image_with_item": "此項物資只有一張圖片"
     "cancelling_item_will_cancel_offer" : "刪除最後一項物品即會刪除此捐獻及已預約的運輸。"

   "plan_delivery":
@@ -304,7 +304,7 @@
   "drop-off":
     "select_schedule": "選擇時間"
     "expect_delivery": "物資大約何時到達？"
-    "find_crossroads": "Find Crossroads on Google Maps"
+    "find_crossroads": "在Google地圖上尋找十字路會位置"
     "open_new_tab": "將在新視窗打開"
     "arrive_on_time": "煩請於我們的辦公時間內到達，以確保我們能順利接收您的物資。"
     "book_schedule": "預約時間"
@@ -314,10 +314,10 @@
         "show_more": "更多"

   "offer":
-    "disable": "關閉"
-    "closed_by": "Closed by {{lastName}} {{firstName}}"
-    "cancelled_by": "Cancelled by {{lastName}} {{firstName}}"
-    "received_by": "Received by {{lastName}} {{firstName}}"
+    "disable": "暫停"
+    "closed_by": "{{lastName}} {{firstName}} 封存"
+    "cancelled_by": "{{lastName}} {{firstName}} 取消"
+    "received_by": "{{lastName}} {{firstName}} 接收"

   # Components
   "upload-images":
@@ -398,7 +398,7 @@
     "last_updated": "本聲明最後更新日期為2014年8月。"

   "terms":
-    "title": "Terms and Conditions"
+    "title": "條款及細則"
     "info": "GoodCity.hk is part of the Crossroads Foundation network. By using the GoodCity.hk website http://www.goodcity.hk (\"GoodCity Website\") you are agreeing to be bound by these terms of use between you and the Crossroads Foundation and its affiliates (\"Crossroads\", \"us\", \"we\" and \"our\")."

     "agreement":


destroying body (wip)

destroying body (wip)

--- source/javascripts/all.coffee
+++ source/javascripts/all.coffee
@@ -41,23 +41,23 @@
         [
           new shapeConfig(0.5, 1, 0.5, 0.8)
           new shapeConfig(1, 1, 0.5, 0.8)
+          new shapeConfig(1.5, 1, 0.5, 0.8)
           new shapeConfig(2, 1, 0.5, 0.8)
           new shapeConfig(3, 1, 0.5, 0.8)
-          new shapeConfig(4, 1, 0.5, 0.8)
         ]
         [
           new shapeConfig(0.5, 1, 0.5, 0.8)
           new shapeConfig(1, 1, 0.5, 0.8)
+          new shapeConfig(1.5, 1, 0.5, 0.8)
           new shapeConfig(2, 1, 0.5, 0.8)
           new shapeConfig(3, 1, 0.5, 0.8)
-          new shapeConfig(4, 1, 0.5, 0.8)
         ]
         [
           new shapeConfig(0.5, 1, 0.5, 0.8)
           new shapeConfig(1, 1, 0.5, 0.8)
+          new shapeConfig(1.5, 1, 0.5, 0.8)
           new shapeConfig(2, 1, 0.5, 0.8)
           new shapeConfig(3, 1, 0.5, 0.8)
-          new shapeConfig(4, 1, 0.5, 0.8)
         ]
       ]
     }
@@ -245,8 +245,11 @@
           color = iris.config.color[data.color]
           if data.state == iris.const.STATE_FALLING
             @s.fillStyle = "rgba(#{color[0]}, #{color[1]}, #{color[2]}, 0.7)"
-          else
+          else if data.state == iris.const.STATE_ACTIVE
             @s.fillStyle = "rgba(#{color[0]}, #{color[1]}, #{color[2]}, 1)"
+          else if data.state == iris.const.STATE_MATCH
+            @s.fillStyle = "rgba(#{color[0] + 64}, #{color[1] + 64}, #{color[2]}, 1)"
+
           if b.GetFixtureList().GetShape() instanceof b2CircleShape
             pos = b.GetWorldCenter()
             @s.beginPath()
@@ -257,14 +260,24 @@
       b = b.GetNext()

   handleCollision: (bm, bo) ->
-    data = bm.GetUserData()
-    if data
-      if data.type == "shape" and data.state == 0
-        data.state = 1
-        filter = bm.GetFixtureList().GetFilterData()
-        filter.categoryBits = iris.const.CATEGORY_SHAPE_ACTIVE
-        filter.maskBits = iris.const.CATEGORY_WALL + iris.const.CATEGORY_CEIL + iris.const.CATEGORY_FLOOR + iris.const.CATEGORY_SHOOT + iris.const.CATEGORY_SHAPE_FALLING + iris.const.CATEGORY_SHAPE_ACTIVE
-        bm.GetFixtureList().SetFilterData(filter)
+    md = bm.GetUserData()
+    od = bo.GetUserData()
+    if md
+      if md.type == "shape"
+        if md.state == 0
+          md.state = 1
+          filter = bm.GetFixtureList().GetFilterData()
+          filter.categoryBits = iris.const.CATEGORY_SHAPE_ACTIVE
+          filter.maskBits = iris.const.CATEGORY_WALL + iris.const.CATEGORY_CEIL + iris.const.CATEGORY_FLOOR + iris.const.CATEGORY_SHOOT + iris.const.CATEGORY_SHAPE_FALLING + iris.const.CATEGORY_SHAPE_ACTIVE
+          bm.GetFixtureList().SetFilterData(filter)
+        if md.state == 1
+          if od and od.color == md.color
+            md.state = 2
+        if od.type == "floor" or (od.type == "shape" and od.state == 3)
+          # collide with floor
+          if md.state == 2
+            iris.field.world.DestroyBody(bm)
+

   fillTextLine: (context, text, x, y) ->
     list = text.split("\n")


github: Moving to minimatch

github: Moving to minimatch

--- src/lib/resolvers/github.coffee
+++ src/lib/resolvers/github.coffee
@@ -17,11 +17,13 @@
   constructor: (expression) ->
     match = @parseExpression expression

+    logger.debug "GitHub: #{match[1]}/#{match[2]}, branch #{match[4]}"
+
     @user = match[1]
-    @repoRe = new RegExp match[2] #minimatch.makeRe match[2]
-    @branchRe = if match[4] then new RegExp match[4] else /master/ # minimatch.makeRe
+    @repoRe = minimatch.makeRe match[2]

-    logger.debug "GitHub: #{@user}/#{match[2]}, branch #{@branchRe.source}"
+    match[4] ?= 'master'
+    @branchRe = minimatch.makeRe match[4]

     @repos = []


Refreshing hooks properly

Refreshing hooks properly

--- tests/www/utils/hooks.coffee
+++ tests/www/utils/hooks.coffee
@@ -1,10 +1,12 @@
 should = do (require 'chai').should
 quit = require './quit'

-failures = 0
-passed = no
+failures = null
+passed = null

 exports.before = (browser, browser_conf, base_url, done)->
+  failures = 0
+  passed = no

   browser.init browser_conf, (err)->
     console.log err if err?


Fixing logout

Fixing logout

--- lib/generators/pbw/templates/router.coffee
+++ lib/generators/pbw/templates/router.coffee
@@ -22,7 +22,7 @@
     $.ajax
       url: '/pbw/users/sign_out.json'
       type: 'DELETE'
-      success: ->
+      complete: ->
         window.location.hash = "/"

   signup: ->


Documentation: Fix duplicated entries

Documentation: Fix duplicated entries

Fixes #32

--- src/Documentation.coffee
+++ src/Documentation.coffee
@@ -108,7 +108,7 @@
       for subPropName, subProp of messageProp.properties
         p "  - `#{subPropName}`: #{subProp.description}"

-    if items?.type is 'object'
+    else if items?.type is 'object'
       line += ", each containing"
       p line

@@ -122,7 +122,7 @@
         else
           p "  - `#{itemPropName}`: #{itemProp.description}"

-    if items?.type is 'string' and items?._enumDescriptions
+    else if items?.type is 'string' and items?._enumDescriptions
       line += " Options include:"
       p line


#1000 Undo changes from #1032 since ThreeJS was downgraded again

--- app/assets/javascripts/oxalis/geometries/materials/particle_material_factory.coffee
+++ app/assets/javascripts/oxalis/geometries/materials/particle_material_factory.coffee
@@ -25,6 +25,9 @@
       showRadius :
         type : "i"
         value : 1
+      devicePixelRatio :
+        type : "f"
+        value : window.devicePixelRatio || 1

     @attributes = _.extend @attributes,
       sizeNm :
@@ -71,6 +74,7 @@
       uniform float particleSize;
       uniform float scale;
       uniform int   showRadius;
+      uniform float devicePixelRatio;
       varying vec3 vColor;
       attribute float sizeNm;
       attribute float nodeScaleFactor;
@@ -83,7 +87,7 @@
             gl_PointSize = max(
                 sizeNm / zoomFactor / baseVoxel,
                 particleSize
-              ) * scale * nodeScaleFactor;
+              ) * devicePixelRatio * scale * nodeScaleFactor;
           else
             gl_PointSize = particleSize * nodeScaleFactor;
           gl_Position = projectionMatrix * mvPosition;


Properly display 'false' value in the table view + fix for primitive values. Related to #790

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -2839,8 +2839,8 @@

         # Sort the keys per level
         order_keys: (keys) =>
+            copy_keys = []
             if keys.object?
-                copy_keys = []
                 for key, value of keys.object
                     if jQuery.isPlainObject(value)
                         @order_keys value
@@ -2857,9 +2857,9 @@
                             return 1
                         else # We cannot have two times the same key
                             return -1
-                keys.sorted_keys = _.map copy_keys, (d) -> return d.key
-                if keys.primitive_value_count > 0
-                    keys.sorted_keys.unshift @primitive_key
+            keys.sorted_keys = _.map copy_keys, (d) -> return d.key
+            if keys.primitive_value_count > 0
+                keys.sorted_keys.unshift @primitive_key

         # Build the table
         # We order by the most frequent keys then by alphabetic order
@@ -2953,7 +2953,6 @@
                             value = value[key]
                         else
                             value = undefined
-
                     new_document.cells.push @json_to_table_get_td_value value, col
                 new_document.record = @container.saved_data.start_record + i
                 document_list.push new_document
@@ -3000,6 +2999,7 @@
                     data['classname'] = 'jta_string'
             else if value_type is 'boolean'
                 data['classname'] = 'jta_bool'
+                data.value = if value is true then 'true' else 'false'

             return data


Settlers cards reworked

Settlers cards reworked

--- static/script/games/settlers/settlers.coffee
+++ static/script/games/settlers/settlers.coffee
@@ -41,27 +41,45 @@

 '
+window.settlers.templates.game.cards_can_be_used_cell = doT.template '
+   0}}id="card_{{= it[0].id}}"{{?}}>{{? it.length > 0}}
 1}}icon-documents{{??}}icon-document{{?}} card card-unused" />{{= it.length}}x{{?}}
+'
+window.settlers.templates.game.cards_free_cell = doT.template '
+  {{? it.length > 0}}
 1}}icon-documents{{??}}icon-document{{?}} card card-free" />{{= it.length}}x{{?}}
+'
+window.settlers.templates.game.cards_used_cell = doT.template '
+  {{? it.length > 0}}
 1}}icon-documents{{??}}icon-document{{?}} card card-used" />{{= it.length}}x{{?}}
+'
 window.settlers.templates.game.cards = doT.template '
-

-    {{~ it.cards :card:index}}
-

-
-

-            {{? card.used}}
-

-            {{??}}
-

-            {{?}}
-

-
{{= window.hlib._g(window.settlers.game.card_type_to_name[card.type])}}

-              {{? card.used}}
-
{{= window.hlib._g("Used in round")}} #{{= card.used}}.

-              {{?}}
-

-

-
-

-    {{~}}
+

+

+      {{= window.hlib._g("Knight")}}
+      {{= window.hlib._g("Monopoly")}}
+      {{= window.hlib._g("Paths")}}
+      {{= window.hlib._g("Invention")}}
+      {{= window.hlib._g("Point")}}
+

+

+      {{= window.settlers.templates.game.cards_can_be_used_cell(it.can_be_used.Knight)}}
+      {{= window.settlers.templates.game.cards_can_be_used_cell(it.can_be_used.Monopoly)}}
+      {{= window.settlers.templates.game.cards_can_be_used_cell(it.can_be_used.Paths)}}
+      {{= window.settlers.templates.game.cards_can_be_used_cell(it.can_be_used.Invention)}}
+      {{= window.settlers.templates.game.cards_can_be_used_cell(it.can_be_used.Point)}}
+

+

+      {{= window.settlers.templates.game.cards_free_cell(it.free.Knight)}}
+      {{= window.settlers.templates.game.cards_free_cell(it.free.Monopoly)}}
+      {{= window.settlers.templates.game.cards_free_cell(it.free.Paths)}}
+      {{= window.settlers.templates.game.cards_free_cell(it.free.Invention)}}
+      {{= window.settlers.templates.game.cards_free_cell(it.free.Point)}}
+

+

+      {{= window.settlers.templates.game.cards_used_cell(it.used.Knight)}}
+      {{= window.settlers.templates.game.cards_used_cell(it.used.Monopoly)}}
+      {{= window.settlers.templates.game.cards_used_cell(it.used.Paths)}}
+      {{= window.settlers.templates.game.cards_used_cell(it.used.Invention)}}
+      {{= window.settlers.templates.game.cards_used_cell(it.used.Point)}}
+

 '
 window.settlers.templates.game.events = doT.template '
@@ -984,12 +1002,56 @@
   if G.state == 1 and G.my_player.resources.rock >= 1 and G.my_player.resources.grain >= 1 and G.my_player.resources.sheep >= 1
     $('#new_card_form').show()

-  $('#cards_list').html window.settlers.templates.game.cards G.my_player.cards
+  # precrunch cards a bit
+  cards_to_decorate =
+    Knight: null
+    Monopoly: null
+    Paths: null
+    Invention: null
+    Point: null
+
+  cards =
+    can_be_used:
+      Knight:    []
+      Monopoly:  []
+      Paths:     []
+      Invention: []
+      Point:     []
+    free:
+      Knight:    []
+      Monopoly:  []
+      Paths:     []
+      Invention: []
+      Point:     []
+    used:
+      Knight:    []
+      Monopoly:  []
+      Paths:     []
+      Invention: []
+      Point:     []
+
+  __crunch_card = (card) ->
+    type_name = window.settlers.game.card_type_to_name[card.type]
+    console.log type_name
+    if card.can_be_used
+      if cards_to_decorate[type_name] == null
+        cards_to_decorate[type_name] = card
+
+      cards.can_be_used[type_name].push card
+
+    else if card.used
+      cards.used[type_name].push card
+
+    else
+      cards.free[type_name].push card
+
+  __crunch_card c for c in G.my_player.cards.cards
+
+  console.log cards
+  console.log cards_to_decorate
+  $('#cards_list').html window.settlers.templates.game.cards cards

   decorate_card = (c) ->
-    if not c.can_be_used
-      return
-
     $('#card_' + c.id).click () ->
       new window.hlib.Ajax
         url:			'/game/card_click'
@@ -1002,7 +1064,16 @@
             window.settlers.show_board()
       return false

-  decorate_card c for c in G.my_player.cards.cards
+  if cards_to_decorate.Knight
+    decorate_card cards_to_decorate.Knight
+  if cards_to_decorate.Monopoly
+    decorate_card cards_to_decorate.Monopoly
+  if cards_to_decorate.Paths
+    decorate_card cards_to_decorate.Paths
+  if cards_to_decorate.Invention
+    decorate_card cards_to_decorate.Invetion
+  if cards_to_decorate.Point
+    decorate_card cards_to_decorate.Point

   if G.my_player.cards.unused_cards > 0
     window.settlers.show_menu_alert 'show_cards', G.my_player.cards.unused_cards, 'badge-info', (window.hlib.format_string (window.hlib._g '%(count)s unused cards'), {count: G.my_player.cards.unused_cards})


检查tag和status统一交到common中

检查tag和status统一交到common中

--- biz/issue.coffee
+++ biz/issue.coffee
@@ -6,6 +6,7 @@
 _schema = require('../schema/issue').schema
 _AssetIssueRelation = require './asset_issue_relation'
 _async = require 'async'
+_common = require '../common'

 #定义一个Project类
 class Issue extends _BaseEntity
@@ -30,9 +31,9 @@

     data.creator = this.member.member_id
     #只允许指定的tag
-    #data.tag = '需求' if data.tag not in ['bug', '需求', '支持', '功能', 'project']
+    data.tag = _common.checkTag data.tag if data.tag
     #只允许四种状态
-    data.status = 'new' if data.status not in ['new', 'doing', 'pause', 'done']
+    data.status = _common.checkStatus data.status if data.status
     self = this
     super data, (err, issue_id)->
       #如果是更新，则没有提交新的assets，则不更新assets。这里会有一个问题，如果客户端要删除所有的assets的关联时，会出问题，这个问题以后再处理。
@@ -52,9 +53,9 @@
       status: status
     }

+
     #修改状态
-    @save data, (err)->
-      res.end()
+    @save data, (err)-> res.end()

   find: (data, cb)->
     cond = {}


#0@bug#doing 修复未分配issue无法按日期查询的bug，并重构合并代码

--- biz/issue.coffee
+++ biz/issue.coffee
@@ -159,6 +159,20 @@

     #http://127.0.0.1:14318/api/project/1/discussion

+  #查询指定时间内的issue
+  findIssueInRange: (start_time, end_time, condition, cb)->
+    sql = "SELECT A.*, B.title AS project_name FROM issue A LEFT JOIN project B ON A.project_id = B.id
+              WHERE 1 = 1 #{condition} AND (
+                (A.finish_time BETWEEN #{start_time} AND #{end_time})
+                  OR
+                (A.timestamp BETWEEN #{start_time} AND #{end_time})
+              ) AND A.status <> 'trash' AND A.tag <> 'project'"
+
+    console.log sql
+    @entity().knex.raw(sql).exec (err, result)->
+      return cb err if err
+      cb err, result[0]
+
   #获取已关联的issue
   findAssignedIssue: (start_time, end_time, data, cb)->
     #http://localhost:8000/api/report/issue?start_time=1399790723523&end_time=1400136323523
@@ -170,6 +184,14 @@
       (-> return index < data.assigned.length)
       ((done)->
         member = data.assigned[index].member
+        cond = "AND A.owner = #{member.id}"
+        self.findIssueInRange start_time, end_time, cond, (err, result)->
+          return done err if err
+          data.assigned[index].issue = result
+          index++
+          done(err)
+
+        ###
         sql = "SELECT A.*, B.title AS project_name FROM issue A LEFT JOIN project B ON A.project_id = B.id
           WHERE A.owner = #{member.id} AND (
             (A.finish_time BETWEEN #{start_time} AND #{end_time})
@@ -182,9 +204,16 @@
           data.assigned[index].issue = result[0]
           index++
           done(err)
+        ###
       )
       cb
     )
+
+  #查找所有的未关联任务
+  findUnassignedIssue: (start_time, end_time, cb)->
+    cond = ' AND A.owner IS null'
+    @findIssueInRange start_time, end_time, cond, cb
+

   #获取报表
   report: (req, res, next)->
@@ -217,8 +246,8 @@
     #查询未关联到人的，即没有owner的任务
     queue.push(
       (done)->
-        self.find owner: null, (err, data)->
-          result.unassigned = data.items
+        self.findUnassignedIssue start_time, end_time, (err, data)->
+          result.unassigned = data
           done null
     )


Added members view to registered users more link at activity.

--- client/landing/app/Applications/Activity.kdapplication/views/newmemberbucket.coffee
+++ client/landing/app/Applications/Activity.kdapplication/views/newmemberbucket.coffee
@@ -22,32 +22,37 @@
     lastFetchedDate = null

     @group.on "moreLinkClicked", =>
-      # TBDL an accordion view
-      return
-      log "expand the view to show more users"
+      # @group.loader.show()
+
       selector    =
         type      : { $in : ['CNewMemberBucketActivity'] }
         createdAt :
-          $lt     : lastFetchedDate or @getData().createdAt[0]
-          $gt     : @getData().createdAt[1]
+          $gt     : lastFetchedDate or @getData().createdAtTimestamps[0]
+          $lt     : @getData().createdAtTimestamps[1]

       options     =
-        limit     : 20
-
-      @group.loader.show()
+        limit     : 10
+
       KD.remote.api.CActivity.some selector, options, (err, activities)=>
-        if err then warn err
-        else
-          activities = ActivityAppController.clearQuotes activities
-          lastFetchedDate = activities.last.createdAt
-          KD.remote.reviveFromSnapshots activities, (err, teasers)=>
-            if err then warn err
-            else
-              @getData().anchors = @getData().anchors.concat teasers.map (item)-> item.anchor
-              @group.setData @getData().anchors
-              @group.visibleCount += 20
-              @group.render()
-              @group.loader.hide()
+        return warn err if err
+        activities = ActivityAppController.clearQuotes activities
+        # lastFetchedDate = activities.last.createdAt
+        KD.remote.reviveFromSnapshots activities, (err, teasers)=>
+          if err then warn err
+          else
+            @getData().anchors = @getData().anchors.concat teasers.map (item)-> item.anchor
+            @group.setData @getData().anchors
+            @group.visibleCount += 10
+            # @group.render()
+            # @group.loader.hide()
+
+            @newMembersList = new KDListViewController
+              view        : new NewMemberList
+              wrapper     : no
+              scrollView  : no
+            , items: @getData().anchors
+
+            @group.addSubView @newMembersList.getView()

   pistachio:->

@@ -57,13 +62,45 @@
     {{> @group}}
     """

+
+class NewMemberList extends KDListView
+  constructor: (options = {}, data) ->
+    options.tagName   = "ul"
+    options.itemClass = NewMemberListItem
+
+    super options, data
+
+
+class NewMemberListItem extends KDListItemView
+  constructor: (options = {}, data) ->
+    options.tagName   = "li"
+
+    super options, data
+
+  fetchUserDetails: ->
+    KD.remote.cacheable "JAccount", @getData().id, (err, res) =>
+      @addSubView new MembersListItemView {}, res
+
+  viewAppended: ->
+    @setTemplate @pistachio()
+    @template.update()
+    @fetchUserDetails()
+
+  pistachio: -> ""
+
+  userTemplate: (data) ->
+    # """
+    #   #{userName}
+    # """
+
+
 class NewMemberLinkGroup extends LinkGroup

   constructor:->

     super

-    # @loader = new KDLoaderView
+    @loader = new KDLoaderView

   createMoreLink:->


Fix tests

Fix tests

--- lib/Bumped.semver.coffee
+++ lib/Bumped.semver.coffee
@@ -109,8 +109,8 @@
   _releasesBasedOnSemver: (opts, cb) =>
     cb null, semver.inc(@bumped._version, opts.version, opts.prefix)

-  _releasesBasedOnVersion: (version, cb) =>
-    version = semver.clean version
+  _releasesBasedOnVersion: (opts, cb) =>
+    version = semver.clean opts.version
     version = semver.valid version
     return cb MSG.NOT_VALID_VERSION version unless version?
     return cb MSG.NOT_GREATER_VERSION(version, @bumped._version) unless semver.gt version, @bumped._version


Generic minted env support

Generic minted env support

--- grammars/latex.cson
+++ grammars/latex.cson
@@ -160,7 +160,7 @@
       # { include: '#metaEnvTable' }
       { include: '#envList' }
       { include: '#envGnuplot' }
-      # { include: '#metaEnvMinted' }
+      { include: '#metaEnvMinted' }
       # { include: '#metaEnvListing' }
       { include: '#envLuaCode' }
       { include: '#envVerbatim' }
@@ -1167,6 +1167,55 @@
         name: 'punctuation.definition.arguments.end.latex'
     patterns: [{ include: 'text.tex.latex.tikz' }] # The l-latex package has complicated stuff here, so this might break
   }
+  metaEnvMinted: {
+    comment: 'This groups together all the minted languages supported by this grammar.'
+    patterns: [
+      { include: '#mintGeneric'}
+    ]
+  }
+
+  mintGeneric: {
+    comment: 'A catch all for any other languages.'
+    name: 'meta.environment.embedded.$11.latex'
+    contentName: 'markup.raw.verbatim.latex'
+    begin: '((\\\\)begin)(\\{)((minted)\\*?)(\\})(?:(\\[)(.*?)(\\]))?(\\{)([a-zA-Z]*)(\\})'
+    beginCaptures:
+      1:
+        name: 'support.function.environment.begin.latex'
+      2:
+        name: 'punctuation.definition.function.begin.latex'
+      3:
+        name: 'punctuation.definition.arguments.begin.latex'
+      4:
+        name: 'variable.parameter.environment.$5.latex'
+      # 5: Used for meta scope name (basically just non starred version)
+      6:
+        name: 'punctuation.definition.arguments.end.latex'
+      7:
+        name: 'punctuation.definition.arguments.optional.begin.latex'
+      8:
+        name: 'variable.parameter.optional.function.environment.latex'
+      9:
+        name: 'punctuation.definition.arguments.optional.end.latex'
+      10:
+        name: 'punctuation.definition.arguments.begin.latex'
+      11:
+        name: 'variable.parameter.language.$11.latex'
+      12:
+        name: 'punctuation.definition.arguments.end.latex'
+    end: '((\\\\)end)(\\{)(\\4)(\\})'
+    endCaptures:
+      1:
+        name: 'support.function.environment.end.latex'
+      2:
+        name: 'punctuation.definition.function.latex'
+      3:
+        name: 'punctuation.definition.arguments.begin.latex'
+      4:
+        name: 'variable.parameter.environment.latex'
+      5:
+        name: 'punctuation.definition.arguments.end.latex'
+  }

   # Experimental


Better spell check support

Better spell check support

--- grammars/latex.cson
+++ grammars/latex.cson
@@ -236,6 +236,7 @@
     }
     hypersetup: {
         comment: 'Recongnises hypersetup environment. Can be extended to be a general key-option scoper, but this (potentially) allows custom colours for known keys.'
+        name: 'meta.preamble.latex'
         begin: '(\\\\)hypersetup(?=[^a-zA-Z@])'
         beginCaptures:
             0:
@@ -270,6 +271,7 @@
     }
     usepackage: {
         comment: 'Package finder. Similar to documentclass, but allows custom colours. A macro name is only valid with letters (exceptions apply--eg. makeatletter). This checks to see if the name is purely "usepackage" and the following character is not a legal part of the macro name.'
+        name: 'meta.preamble.latex'
         begin: '(\\\\)usepackage(?=[^a-zA-Z@])'
         beginCaptures:
             0:
@@ -299,6 +301,7 @@
     }
     sectioning: {
         comment: 'Covers all sectioning commands such as section, subsection, subparagraph, etc.'
+        name: 'support.function.section.latex'
         begin: '(\\\\)((sub){0,2}section|(sub){0,1}paragraph|part|chapter)(\\*)?(?=[^a-zA-Z@])'
         beginCaptures:
             0:
@@ -368,6 +371,7 @@
     }
     documentclass: {
         comment: 'Documentclass finder.'
+        name: 'meta.preamble.latex'
         begin: '(\\\\)documentclass(?=[^a-zA-Z@])'
         beginCaptures:
             0:
@@ -655,11 +659,11 @@
                 name: 'variable.parameter.argument.latex'
             5:
                 name: 'punctuation.definition.argument.end.latex'
-        # patterns: [
-        #     {
-        #         include: '#figureMacros'
-        #     }
-        # ]
+        patterns: [
+            {
+                include: '$base'
+            }
+        ]
     }
     mathEnvironmentMeta: {
         comment: 'Matches math environments'


add @error highlighting in comment

add @error highlighting in comment

--- grammars/stata.cson
+++ grammars/stata.cson
@@ -926,6 +926,12 @@
   'docblockr-comment':
     'patterns': [
       {
+        'match': '(^\\s*\\*\\s*|\\s+)(@(error|ERROR))'
+        'captures':
+          '2':
+            'name': 'invalid.illegal.name.stata'
+      }
+      {
         'match': '(^\\s*\\*\\s*|\\s+)(@[A-za-z0-9]+)'
         'captures':
           '2':


Update router.

Update router.

--- app/router.cjsx
+++ app/router.cjsx
@@ -42,6 +42,7 @@
 `import Acknowledgements from './pages/about/acknowledgements';`
 `import Contact from './pages/about/contact';`
 `import Faq from './pages/about/faq';`
+`import GetInvolved from './pages/get-involved/index';`
 `import CallForProjects from './pages/get-involved/call-for-projects';`
 `import Education from './pages/get-involved/education';`
 `import Volunteering from './pages/get-involved/volunteering';`
@@ -75,7 +76,7 @@
     </Route>

-    <Route path="get-involved" component={require './pages/get-involved'} ignoreScrollBehavior>
+    <Route path="get-involved" component={GetInvolved} ignoreScrollBehavior>
       <IndexRoute component={Volunteering} />
       <Route path="call-for-projects" component={CallForProjects} />
       <Route path="education" component={Education} />


Add redirect for stellar sleuths (#5824)

* Add redirect for stellar sleuths

* Update the routes for Stellar Sleuths to Zwicky's Stellar Sleuths

--- app/router.cjsx
+++ app/router.cjsx
@@ -149,6 +149,9 @@
     <Route path="/projects/nora-dot-eisner/planet-hunters-tess" component={() => <RELOAD newUrl='https://fe-project.zooniverse.org/projects/nora-dot-eisner/planet-hunters-tess' />} />
     <Route path="/projects/nora-dot-eisner/planet-hunters-tess/classify" component={() => <RELOAD newUrl='https://fe-project.zooniverse.org/projects/nora-dot-eisner/planet-hunters-tess/classify' />} />

+    <Route path="/projects/adamamiller/zwickys-stellar-sleuths" component={() => <RELOAD newUrl='https://fe-project.zooniverse.org/projects/adamamiller/zwickys-stellar-sleuths' />} />
+    <Route path="/projects/adamamiller/zwickys-stellar-sleuths/classify" component={() => <RELOAD newUrl='https://fe-project.zooniverse.org/projects/adamamiller/zwickys-stellar-sleuths/classify' />} />
+
     <Route path="projects/:owner/:name" component={require('./pages/project').default}>
       <IndexRoute component={ProjectHomePage} />
       <Route path="home" component={ONE_UP_REDIRECT} />


Add computeBoundingBoxY

Add computeBoundingBoxY

--- src/view.coffee
+++ src/view.coffee
@@ -781,27 +781,14 @@

           @boundingBoxFlag = true

-        # We will center the children vertically along this
-        # `axis`, which is the middle of our
-        # bounding box.
-        axis = top + @dimensions[line].height / 2
-
-        # Go to each child and lay them out
-        # so that their center lies
-        # along our center, the axis.
+        # Go to each child and lay them out so that their distanceToBase
+        # lines up.
+        above = @distanceToBase[line].above
         for lineChild, i in @lineChildren[line]
           childView = @view.getViewNodeFor lineChild.child
           childLine = line - lineChild.startLine
-
-          # Exception: if an indent is ending on this line,
-          # it must stick to the top of the line. Thus instead of
-          # putting its center on ours, we put its top on ours.
-          if (lineChild.child.type is 'indent') or (@multilineChildrenData[line] is MULTILINE_END and i is 0)
-            childView.computeBoundingBoxY top, childLine
-
-          # Otherwise, match centers.
-          else
-            childView.computeBoundingBoxY axis - childView.dimensions[childLine].height / 2, childLine
+          childAbove = childView.distanceToBase[childLine].above
+          childView.computeBoundingBoxY top + above - childAbove, childLine

         # Return the bounds we just computed.
         return @bounds[line]
@@ -1205,30 +1192,6 @@
         else
           super

-      # ## computeBoundingBoxY
-      # Again exception: sockets containing block
-      # should mimic blocks exactly.
-      computeBoundingBoxY: (top, line) ->
-        # Use cache if possible.
-        if @computedVersion is @model.version and
-            top is @bounds[line]?.y
-          return @bounds[line]
-
-        # A Socket should copy its content
-        # block, if there is a content block
-        if @model.start.next.type is 'blockStart'
-          @bounds[line] =
-            @view.getViewNodeFor(@model.start.next.container).computeBoundingBoxY(top, line).clone()
-
-          @boundingBoxFlag = @view.getViewNodeFor(@model.start.next.container).boundingBoxFlag
-
-        # Otherwise, decrement to force super to recompute,
-        # and call super.
-        else
-          super
-
-        return @bounds[line]
-
       # ## computeOwnPath
       # Again, exception: sockets containing block
       # should mimic blocks exactly.


Can set autofocus on element if needed

Can set autofocus on element if needed

--- lib/written/app/assets/javascripts/written/core/content.coffee
+++ lib/written/app/assets/javascripts/written/core/content.coffee
@@ -30,7 +30,8 @@

     @render(document)

-    document.cursor.focus(document.toString().length)
+    if options.focus == true
+      document.cursor.focus(document.toString().length)

     @history = new Written.History(document)
     @observer = new Written.Observer(@element(), @changed)


:lipstick: Whitespace

:lipstick: Whitespace

--- spec/autocomplete-spec.coffee
+++ spec/autocomplete-spec.coffee
@@ -185,7 +185,6 @@
         expect(items[1]).not.toHaveClass("selected")
         expect(items[2]).not.toHaveClass("selected")
         expect(items[3]).not.toHaveClass("selected")
-

         autocompleteView = atom.views.getView(autocompleteManager)
         # Select previous item
@@ -338,7 +337,6 @@
         mainModule = a.mainModule
         autocompleteManager = mainModule.autocompleteManagers[0]

-
     it "sets the width of the view to be wide enough to contain the longest completion without scrolling", ->
       editor.moveToBottom()
       editor.insertNewline()
@@ -367,7 +365,6 @@

       runs ->
         editorView = atom.views.getView(editor)
-

     it "includes completions for the scope's completion preferences", ->
       runs ->
@@ -402,7 +399,6 @@

       runs ->

-
     it "does not show suggestions after a delay", ->
       triggerAutocompletion editor
       advanceClock completionDelay


fixed the specs to reflect recent changes

--- spec/block-cursor-spec.coffee
+++ spec/block-cursor-spec.coffee
@@ -1,19 +1,6 @@
+fs = require 'fs'
+path = require 'path'
 blockCursor = require '../lib/block-cursor'
-
-testColor = (index) ->
-  configKey = if index > 0 then 'secondaryColor' else 'primaryColor'
-  style = blockCursor.getColorStyleElement()
-
-  initialColor = atom.config.get "block-cursor.#{configKey}"
-  initialRule = style.sheet.cssRules[index].cssText
-
-  newColor = initialColor
-  newColor.red = (newColor.red + 1) % 256
-  atom.config.set "block-cursor.#{configKey}", newColor
-  newRule = style.sheet.cssRules[index].cssText
-
-  expect(newRule).not.toEqual initialRule
-  expect(newRule).toMatch /rgba?\(([0-9]{1,3}(,?\s?)?){3,4}\)/

 describe 'Block Cursor', ->
   beforeEach ->
@@ -23,33 +10,40 @@
     workspaceView = atom.views.getView atom.workspace
     expect(workspaceView.className).toMatch /\s*block-cursor\s*/

-  describe 'the style element added by this package', ->
-    styleElement = blockCursor.getColorStyleElement()
+  describe 'config', ->
+    colorsFile = path.join __dirname, '..', 'styles', 'includes', 'colors.less'

-    it 'is in the atom-styles element in the head element', ->
-      expect(styleElement.parentNode).toEqual document.querySelector 'head atom-styles'
-      expect(styleElement.parentNode.parentNode).toEqual document.head
-
-
-    it 'has two rules', ->
-      expect(styleElement.sheet.cssRules.length).toEqual(2)
-
-  describe 'config', ->
     describe 'the primary color option', ->
-      it 'changes the first rule of the packages stylesheet', ->
-        testColor 0
+      it 'changes only the first line of styles/colors.less', ->
+        initialLines = fs.readFileSync(colorsFile, 'utf8').split '\n'
+        newColor = do ->
+          initialColor = atom.config.get 'block-cursor.primaryColor'
+          initialColor.red = (initialColor.red + 1) % 256
+          initialColor
+        atom.config.set 'block-cursor.primaryColor', newColor
+        newLines = fs.readFileSync(colorsFile, 'utf8').split '\n'
+        expect(newLines[0]).not.toEqual(initialLines[0])
+        expect(newLines[1]).toEqual(initialLines[1])

     describe 'the secondary color option', ->
-      it 'changes the second rule of the packages stylesheet', ->
-        testColor 1
+      it 'changes only the second line of styles/colors.less', ->
+        initialLines = fs.readFileSync(colorsFile, 'utf8').split '\n'
+        newColor = do ->
+          initialColor = atom.config.get 'block-cursor.secondaryColor'
+          initialColor.red = (initialColor.red + 1) % 256
+          initialColor
+        atom.config.set 'block-cursor.secondaryColor', newColor
+        newLines = fs.readFileSync(colorsFile, 'utf8').split '\n'
+        expect(newLines[0]).toEqual(initialLines[0])
+        expect(newLines[1]).not.toEqual(initialLines[1])

     describe 'the enablePulse option', ->
       it 'adds the block-cursor-pulse class to the workspaceView if enabled', ->
         workspaceView = atom.views.getView atom.workspace
-        atom.config.set 'block-cursor.pulseEnabled', true
+        atom.config.set 'block-cursor.enablePulse', true
         expect(workspaceView.className).toMatch /\s*block-cursor-pulse\s*/

       it 'removes the block-cursor-pulse class from the workspaceView if disabled', ->
         workspaceView = atom.views.getView atom.workspace
-        atom.config.set 'block-cursor.pulseEnabled', false
+        atom.config.set 'block-cursor.enablePulse', false
         expect(workspaceView.className).not.toMatch /block-cursor-pulse/


replace eventlistener for uploading end event

--- www/js/file.coffee
+++ www/js/file.coffee
@@ -108,10 +108,15 @@
 						_fulfill = ->
 							transfer.end()
 							fulfill()
-						_reject = ->
+						_reject = (err) ->
 							transfer.end()
-							reject()
-						xhr.upload.addEventListener 'loadend', _fulfill
+							reject new Error err
+						xhr.addEventListener 'readystatechange', (e) ->
+							if xhr.readyState = 4
+								if xhr.status == 201
+									_fulfill()
+								else
+									_reject xhr.statusText
 						xhr.upload.addEventListener 'error', _reject
 						xhr.upload.addEventListener 'progress', onprogress || transfer.progress
 						xhr.send data


fix: #5 Yaku will check the final shape of the tree.

--- src/yaku.coffee
+++ src/yaku.coffee
@@ -514,13 +514,16 @@
 		iter = (node) ->
 			i = 0
 			len = node._pCount
-			console.log node
+			return if node._onRejected
+
 			while i < len
-				iter node[i]
-
-		# iter p
-
-		Yaku.onUnhandledRejection p._value, p
+				return if not iter node[i++]
+
+			return true
+
+		if iter p
+			Yaku.onUnhandledRejection p._value, p
+
 		return

 	callHanler = (handler, value) ->


application manager to work with kodingappscontroller wip

--- core/KD.coffee
+++ core/KD.coffee
@@ -70,6 +70,7 @@
   appsUri         : KD.config.appsUri
   utils           : __utils
   appClasses      : {}
+  appScripts      : {}

   whoami:-> KD.getSingleton('mainController').userAccount

@@ -160,6 +161,7 @@
     options.route        or= ""           # a String
     options.openWith     or= "lastActive" # a String "lastActive" or "prompt"
     options.behavior     or= ""           # a String "application", "hideTabs", or ""
+    options.thirdParty    ?= no           # a Boolean

     Object.defineProperty KD.appClasses, options.name,
       configurable  : yes
@@ -170,12 +172,21 @@
         options
       }

-  unregisterAppClass:(name)-> delete KD.appClasses[name]
-
-  getAppClass:(name)-> KD.appClasses[name]?.fn or null
-  getAppOptions:(name)-> KD.appClasses[name]?.options or null
-
-  getAllKDInstances:()-> KD.instances
+  unregisterAppClass :(name)-> delete KD.appClasses[name]
+
+  getAppClass        :(name)-> KD.appClasses[name]?.fn or null
+
+  getAppOptions      :(name)-> KD.appClasses[name]?.options or null
+
+  getAppScript       :(name)-> @appScripts[name] or null
+
+  registerAppScript  :(name, script)-> @appScripts[name] = script
+
+  unregisterAppScript:(name)-> delete @appScripts[name]
+
+  resetAppScripts    :-> @appScripts = {}
+
+  getAllKDInstances  :-> KD.instances

   getKDViewInstanceFromDomElement:(domElement)->
     @instances[$(domElement).data("data-id")]


Delete Atom Beautify shortcut

Delete Atom Beautify shortcut

Uninstalled package; no longer needed.

--- .config/keymap.cson
+++ .config/keymap.cson
@@ -62,7 +62,6 @@
 	'alt-down':       'editor:move-line-down'
 	'cmd-alt-up':     'editor:duplicate-lines'
 	'ctrl-w':         'whitespace:convert-spaces-to-tabs'
-	'cmd-b':          'atom-beautify:beautify-editor'

 # Character transformation


reverting api helper error handling change

--- tutor/src/helpers/api.coffee
+++ tutor/src/helpers/api.coffee
@@ -164,7 +164,7 @@
         requestConfig.handleError?(response, args...)
         return if handled

-        Actions.FAILED(status, statusMessage, data, args...)
+        Actions.FAILED(status, statusMessage, args...)

       axios(requestConfig)
         .then(resolved, rejected)


Tests: Added runCommandOnTerminal test.

Tests: Added runCommandOnTerminal test.

--- tests/src/ide/ide.coffee
+++ tests/src/ide/ide.coffee
@@ -215,7 +215,20 @@
         .end()

-  openTerminal: (browser) ->
+  runCommandOnTerminal: (browser) ->
+
+    helpers.beginTest(browser)
+
+    time = Date.now()
+
+    browser
+      .waitForElementVisible  '.terminal-pane .console span.outlined', 20000
+      .execute                "KD.singletons.appManager.frontApp.ideViews.last.tabView.activePane.view.webtermView.terminal.server.input('echo #{time}')"
+      .execute                "KD.singletons.appManager.frontApp.ideViews.last.tabView.activePane.view.webtermView.terminal.keyDown({type: 'keydown', keyCode: 13, stopPropagation: function() {}, preventDefault: function() {}});"
+      .pause                  5000
+      .assert.containsText    '.terminal-pane .webterm', time
+      .end()
+

     helpers.beginTest(browser)


Fix attempting to load texture many times

--- engine/texture.coffee
+++ engine/texture.coffee
@@ -25,7 +25,7 @@
             @formats # See above
             @wrap='R' # Clamp, Repeat or Mirrored
             @filter=true # enable bilinear filtering
-            @use_mipmap=true
+            @use_mipmap=true # TODO: currently not exported!
         } = tex_data
         @gl_tex = null
         @loaded = false
@@ -67,7 +67,7 @@
         # Trying formats from best to worst
         if astc? and extensions.compressed_texture_astc
             # NOTE: Assuming a single element in the list
-            if @loaded
+            if @promise
                 return @promise
             @promise = new Promise (resolve, reject) =>
                 fetch(base+astc[0].file_name).then((data)->data.arrayBuffer()).then (buffer) =>
@@ -78,6 +78,7 @@
                         @offset = 16
                         @gl_internal_format = astc[0].format_enum
                         @buffers = [buffer]
+                        @use_mipmap = false
                         @restore() #TODO: use @upload and @configure instead when possible
                         resolve @
                 .catch reject
@@ -133,7 +134,7 @@
         else if rgb565?
             # TODO: Test this part
             # NOTE: Assuming a single element in the list
-            if @loaded
+            if @promise?
                 return @promise
             @promise = new Promise (resolve, reject) =>
                 fetch(base+rgb565[0].file_name).then((data)->data.arrayBuffer()).then (buffer) =>


Destruct simple MDE instance

Destruct simple MDE instance

--- app/assets/javascripts/common.js.coffee
+++ app/assets/javascripts/common.js.coffee
@@ -1,11 +1,12 @@
 initYandexShare = (element) -> setTimeout (() -> Ya.share2(element)), 0

 $ ->
+  window.simpleMDE = null
   simplemdeId = document.querySelectorAll('.edit_event')[0]?.id
   simplemdeId ||= document.getElementById('new_event')?.id

   if simplemdeId?
-    new SimpleMDE
+    window.simpleMDE = new SimpleMDE
       element: document.getElementById("event_description")
       indentWithTabs: false
       promptURLs: true


Sankey: nicer tooltips

Sankey: nicer tooltips

--- app/assets/javascripts/d3/sankey.coffee
+++ app/assets/javascripts/d3/sankey.coffee
@@ -486,9 +486,9 @@
         .attr("data-tooltip", (d) =>
           h = ""
           for l in d.right_links
-            h += "-> #{l.right.label()}: #{@value_formatter l.value()}
"
+            h += " #{l.right.label()}: #{@value_formatter l.value()}
"
           for l in d.left_links
-            h += "<- #{l.left.label()}: #{@value_formatter l.value()}
"
+            h += " #{l.left.label()}: #{@value_formatter l.value()}
"
           h
           )
         .attr("data-tooltip-title", (d) =>


Fix hover behaviour on Sankey diagrams

Fix hover behaviour on Sankey diagrams

* Tooltips are now positioned outside the rectangular node, and will not
  move with the mouse. Tracking the mouse seemed to interfere with the
  tooltip and frequently caused its disappearance.

* Set the "viewport" and "adjust.method" options to ensure that tooltips
  remain inside the chart; prevents tooltips from the left-most nodes
  from being rendered off-screen when using the "Zoom" button.

Fixes #1840.

--- app/assets/javascripts/d3/sankey.coffee
+++ app/assets/javascripts/d3/sankey.coffee
@@ -434,21 +434,23 @@
       nodes

     setup_tooltips: ->
-        $("#{@container_selector()} g.node").qtip
+        $("#{@container_selector()} g.node rect").qtip
           content:
-            title: -> $(this).attr('data-tooltip-title')
-            text: -> $(this).attr('data-tooltip')
+            title: -> $(this).parent('g').attr('data-tooltip-title')
+            text: -> $(this).parent('g').attr('data-tooltip')
           position:
-            target: 'mouse'
             my: 'right center'
-            at: 'top center'
+            at: 'left center'
+            viewport: $("#{@container_selector()}")
+            adjust:
+              method: 'flipinvert'

         $("#{@container_selector()} g.link path").qtip
           content: -> $(this).attr('data-tooltip')
           position:
             target: 'mouse'
-            my: 'bottom right'
-            at: 'top left'
+            my: 'right center'
+            at: 'left center'
           style:
             classes: "qtip-tipsy"


Improve logging out errors when performing task's actions.

--- lib/base_task.coffee
+++ lib/base_task.coffee
@@ -25,7 +25,8 @@
     return

   initialize: ->
-    action.initialize() for action in @actions
+    for action in @actions
+      action.initialize()
     return

   _buildAction: (config) ->
@@ -38,9 +39,12 @@
     scope = data: data
     for action in @actions
       if promise
-        promise.then (lastResult) ->
+        promise.then ((lastResult) ->
           scope.lastResult = lastResult
           Q.when action.run scope
+        ), ((err) =>
+          @log 'warn', 'task.base', "Action #{action} performed not well.", err
+        )
       else
         promise = Q.when action.run scope
     return promise


If uniform is not used we just clear next siblings when selection changes

--- app/assets/javascripts/modals.js.coffee
+++ app/assets/javascripts/modals.js.coffee
@@ -62,7 +62,7 @@
   $("#modal-wrap").delegate "select", "change", () ->
     select = $(this)
     organization_id = select.val()
-    select.parents('.selector:first').nextAll().remove()
+    select.nextAll().remove()

     $.get $("#modal-wrap .modal").attr('data-url'), (organizations) ->
       children = _(organizations).filter((org) -> org.parent_id == organization_id)


Elektron: Tray: update machine listing for new kd

--- elektron/src/trayicon.coffee
+++ elektron/src/trayicon.coffee
@@ -14,12 +14,15 @@

 # Helpers

-parseTeam  = (teams) ->
-
-  [ team ] = teams
-  team     = team.toLowerCase()
-  prefix   = if team is 'koding' then '' else "#{team}."
-  teamUrl  = "https://#{prefix}koding.com"
+Status      =
+  Connected : 3
+  Online    : 2
+  Offline   : 1
+
+parseTeam  = (machine) ->
+
+  { team } = machine
+  teamUrl  = "https://#{team}.koding.com"

   return { team, teamUrl }

@@ -212,34 +215,39 @@

     @setMenu 'Fetching machines...'

-    @kd 'list --json', (err, result) =>
+    @kd 'machine list --json', (err, result) =>

       return  if @setFailed err

       menu = []

-      result = result.filter (machine) -> machine.machineStatus isnt 1
+      # show only connected machines for now
+      result = result.filter (machine) ->
+        machine.status.state is Status.Connected

       result.forEach (machine) =>
-        # { team, teamUrl } = parseTeam machine.teams
-        label = "(#{machine.vmName})"
-        label = "#{machine.machineLabel} #{label}"  if machine.machineLabel
+
+        { team, teamUrl } = parseTeam machine
+
+        alias   = "(#{machine.alias})"
+        label   = "#{machine.label} #{alias}"
+
         item    = {
           label   : label
           submenu : [
-          #   label : "Open #{team}"
-          #   click : handleOpen teamUrl
-          # ,
-          #   type  : 'separator'
-          # ,
+            label : "Open #{team}"
+            click : handleOpen teamUrl
+          ,
+            type  : 'separator'
+          ,
             label : "Open #{machine.ip}"
             click : handleOpen "http://#{machine.ip}"
-          # ,
-          #   label : 'Open IDE'
-          #   click : handleOpen "#{teamUrl}/IDE/#{machine.machineLabel}/my-workspace"
+          ,
+            label : 'Open IDE'
+            click : handleOpen "#{teamUrl}/IDE/#{machine.label}"
           ,
             label : 'Open Terminal'
-            click : handleOpen "kd ssh #{machine.vmName}", 'terminal'
+            click : handleOpen "kd ssh #{machine.alias}", 'terminal'
           ,
             type  : 'separator'
           ]


Fix javascripts.

Fix javascripts.

--- app/assets/javascripts/tinami.js.coffee
+++ app/assets/javascripts/tinami.js.coffee
@@ -51,12 +51,9 @@
     $('.hfeed .content .thumbnail img').
       each(TINAMI.setImagePosition).
       on('load', TINAMI.setImagePosition))
-  remote_actions = $('a[data-remote]')
   for eventName, callback of TINAMI.defaultHandler
-    remote_actions.on('ajax:' + eventName, callback)
-  user_actions = $('.support a, .collection-add a, .bookmark-add a')
+    $d.on('ajax:' + eventName, 'a[data-remote]', callback)
   for eventName, callback of TINAMI.actionHandler
-    user_actions.on('ajax:' + eventName, callback)
-  comments_actions = $('.comments-load a')
+    $d.on('ajax:' + eventName, '.support a, .collection-add a, .bookmark-add a', callback)
   for eventName, callback of TINAMI.commentsHandler
-    comments_actions.on('ajax:' + eventName, callback)
+    $d.on('ajax:' + eventName, '.comments-load a', callback)


Remove ;

Remove ;

--- app/coffee/modules/common/loader.coffee
+++ app/coffee/modules/common/loader.coffee
@@ -102,7 +102,7 @@
                     timeout = 0

                 setTimeout ( ->
-                    $rootscope.$broadcast("loader:end");
+                    $rootscope.$broadcast("loader:end")
                 ), timeout

             start: () ->
@@ -118,13 +118,13 @@
                         ), 100

                     startLoadTime = new Date().getTime()
-                    $rootscope.$broadcast("loader:start");
+                    $rootscope.$broadcast("loader:start")

             onStart: (fn) ->
-                $rootscope.$on("loader:start", fn);
+                $rootscope.$on("loader:start", fn)

             onEnd: (fn) ->
-                $rootscope.$on("loader:end", fn);
+                $rootscope.$on("loader:end", fn)

             logRequest: () ->
                 log.request.count++


Comment added.

Comment added.

--- app/assets/javascripts/heros.coffee
+++ app/assets/javascripts/heros.coffee
@@ -1,3 +1,5 @@
+# We need javascript, form is too slow on mobile
+
 activate_buttons = ->
   $( '.update_hero' ).click ->


enable katex in trix

enable katex in trix

--- app/assets/javascripts/katex.coffee
+++ app/assets/javascripts/katex.coffee
@@ -22,6 +22,7 @@
         display: true
       }
     ]
+    ignoredClasses: ['trix-content']
     throwOnError: false

   return


fixed bug in Model.Binary.get

fixed bug in Model.Binary.get

--- app/assets/javascripts/model.coffee
+++ app/assets/javascripts/model.coffee
@@ -24,48 +24,47 @@

 	rotateAndTranslate : (data, moveVector, axis, callback) ->

-		@lazyInitialize (err) ->
-
-			return callback(err) if err
-
-			output = new Float32Array(data.length)
-			axis = V3.normalize axis
-
-
-			unless axis[0] == 0 and axis[1] == 1 and axis[2] == 0
-
-				mat = M4x4.makeRotate V3.angle([0,1,0], axis), [axis[2], 0, -axis[0]]
-				mat = M4x4.translateSelf moveVector, mat
+		output = new Float32Array(data.length)
+		axis = V3.normalize axis
+
+
+		unless axis[0] == 0 and axis[1] == 1 and axis[2] == 0

-				_.defer -> callback null, M4x4.transformPointsAffine(mat, data, output)
+			mat = M4x4.makeRotate V3.angle([0,1,0], axis), [axis[2], 0, -axis[0]]
+			mat = M4x4.translateSelf moveVector, mat
+
+			_.defer -> callback null, M4x4.transformPointsAffine(mat, data, output)
+
+		else
+
+			[px, py, pz] = moveVector

-			else
-
-				[px, py, pz] = moveVector
-
-				for i in [0...data.length] by 3
-					output[i]     = px + data[i]
-					output[i + 1] = py + data[i + 1]
-					output[i + 2] = pz + data[i + 2]
-
-				_.defer -> callback null, output
+			for i in [0...data.length] by 3
+				output[i]     = px + data[i]
+				output[i + 1] = py + data[i + 1]
+				output[i + 2] = pz + data[i + 2]
+
+			_.defer -> callback null, output

 	get : (position, direction, callback) ->

-		loadedData = []
-
-		finalCallback = (err, vertices, colors) ->
-			if err
-				callback err
-			else
-				colorsFloat = new Float32Array(colors.length)
-				colorsFloat[i] = colors[i] / 255 for i in [0...colors.length]
-				callback null, vertices, colorsFloat
-
-
-		@rotateAndTranslate @vertexTemplate, position, direction, @synchronizingCallback(loadedData, finalCallback)
-
-		@load position, direction, @synchronizingCallback(loadedData, finalCallback)
+		@lazyInitialize (err) =>
+			return callback err if err
+
+			loadedData = []
+
+			finalCallback = (err, vertices, colors) ->
+				if err
+					callback err
+				else
+					colorsFloat = new Float32Array(colors.length)
+					colorsFloat[i] = colors[i] / 255 for i in [0...colors.length]
+					callback null, vertices, colorsFloat
+
+
+			@rotateAndTranslate @vertexTemplate, position, direction, @synchronizingCallback(loadedData, finalCallback)
+
+			@load position, direction, @synchronizingCallback(loadedData, finalCallback)


that doesn't need parsing apparently

that doesn't need parsing apparently

--- app/assets/javascripts/users.coffee
+++ app/assets/javascripts/users.coffee
@@ -19,7 +19,7 @@
   )

   $('a[data-remote].soft-delete').on('ajax:success', (ev, data, status, xhr) ->
-    QPixel.createNotification('success', '
Complete. ' + JSON.parse(data)['message'] + ' Return to user index.
', $(this))
+    QPixel.createNotification('success', '
Complete. ' + data['message'] + ' Return to user index.
', $(this))
     $('div.delete-actions').remove()
   ).on('ajax:error', (rv, xhr, status, error) ->
     QPixel.createNotification('danger', '
Failed: ' + JSON.parse(xhr.responseText)['message'] + '
', $(this))


Wait a tick before firing objectFitPolyfill

--- concerns/fits-assets.coffee
+++ concerns/fits-assets.coffee
@@ -57,7 +57,7 @@
 			return unless window.objectFitPolyfill
 			@$refs[assetType].dataset.objectFit = @objectFit
 			@$refs[assetType].dataset.objectPosition = @objectPosition
-			window.objectFitPolyfill @$refs[assetType].$el
-
+			setTimeout (=> window.objectFitPolyfill @$refs[assetType].$el), 0
+
 		# Support plain numbers for px units
 		autoUnit: (val) -> if String(val).match /^\d+$/ then "#{val}px" else val


master: Removing conditional from reddit jokes so that the title is always included

--- src/scripts/reddit-jokes.coffee
+++ src/scripts/reddit-jokes.coffee
@@ -23,10 +23,8 @@
           children = data.data.children
           joke = msg.random(children).data

-          if joke.selftext.match /^\.\.\./
-            joketext = joke.title.replace(/\*\.\.\.$/,'') + ' ' + joke.selftext.replace(/^\.\.\.\s*/, '')
-          else
-            joketext = joke.selftext
+
+          joketext = joke.title.replace(/\*\.\.\.$/,'') + ' ' + joke.selftext.replace(/^\.\.\.\s*/, '')

           msg.send joketext.trim()


openConfig button in EVND panel now opens specific config page of EVND package - it's now possible to do this

--- lib/ever-notedown.coffee
+++ lib/ever-notedown.coffee
@@ -841,7 +841,7 @@
       searchAllPanes: true
     if atom.config.get('ever-notedown.openPreviewInSplitPane')
       options.split = "left"
-    atom.workspace.open 'atom://config/packages', options
+    atom.workspace.open 'atom://config/packages/ever-notedown/', options

   openHelpDoc: ->
     if window.evnd.init then @loadModule()


Always use empty array instead of null

Always use empty array instead of null

--- lib/git-diff-view.coffee
+++ lib/git-diff-view.coffee
@@ -5,7 +5,7 @@
   constructor: (@editor) ->
     @subscriptions = new CompositeDisposable()
     @decorations = {}
-    @markers = null
+    @markers = []

     @subscriptions.add(@editor.onDidStopChanging(@updateDiffs))
     @subscriptions.add(@editor.onDidChangePath(@updateDiffs))
@@ -108,12 +108,10 @@
     return

   removeDecorations: ->
-    return unless @markers?
     marker.destroy() for marker in @markers
-    @markers = null
+    @markers = []

   markRange: (startRow, endRow, klass) ->
     marker = @editor.markBufferRange([[startRow, 0], [endRow, Infinity]], invalidate: 'never')
     @editor.decorateMarker(marker, type: 'gutter', class: klass)
-    @markers ?= []
     @markers.push(marker)


Grab Q*** linter error

Grab Q*** linter error

Needed when using flake8-quotes plugin

--- lib/linter-flake8.coffee
+++ lib/linter-flake8.coffee
@@ -13,7 +13,7 @@

   # A regex pattern used to extract information from the executable's output.
   regex:
-    '(.*?):(?<line>\\d+):(?<col>\\d+): (?<message>((?<error>E11|E9)|(?<warning>W|E|F4|F84|N*|C)|F)\\d+ .*?)\r?\n'
+    '(.*?):(?<line>\\d+):(?<col>\\d+): (?<message>((?<error>E11|E9)|(?<warning>W|E|F4|F84|N*|C|Q)|F)\\d+ .*?)\r?\n'

   constructor: (editor)->
     super(editor)


Return array if @cmd do not initialized.

--- lib/linter-pylama.coffee
+++ lib/linter-pylama.coffee
@@ -229,7 +229,7 @@

   lint: (textEditor) =>
     if not @cmd
-      return
+      return []
     if @lintOnFly_
       return @lintOnFly textEditor
     else


Remove temporary fix 382eb28

Remove temporary fix 382eb28

--- lib/linter-pylama.coffee
+++ lib/linter-pylama.coffee
@@ -130,7 +130,6 @@
       @cmd = ''
       return
     cmd = [@pylamaPath]
-    cmd.push '-F'

     ignoreEW = atom.config.get 'linter-pylama.ignoreErrorsAndWarnings'
     if ignoreEW then cmd.push ['-i', ignoreEW]


Error handling

Error handling

--- lib/memory_cursor.coffee
+++ lib/memory_cursor.coffee
@@ -40,6 +40,8 @@
     exists = @hasCursorQuery('$exists')

     @buildFindQuery (err, find_query) =>
+      return callback(err) if err
+
       keys = _.keys(find_query)
       json = []
       queue = new Queue(1)


Color adjectives in email notifications so they stand out more

--- lib/notifications.coffee
+++ lib/notifications.coffee
@@ -473,15 +473,21 @@
             verb = 'updated'
           else
             verb = 'created'
-          adjectives = ''
+          ## Colors for adjectives based on .contents styles
+          adjectivesText = []
+          adjectivesHTML = []
           unless msg.published
-            adjectives += 'unpublished '
+            adjectivesText.push 'unpublished '
+            adjectivesHTML.push 'unpublished '
           if msg.deleted
-            adjectives += 'deleted '
+            adjectivesText.push 'deleted '
+            adjectivesHTML.push 'deleted '
           if msg.private
-            adjectives += 'private '
+            adjectivesText.push 'private '
+            adjectivesHTML.push 'private '
           if msg.minimized
-            adjectives += 'minimized '
+            adjectivesText.push 'minimized '
+            adjectivesHTML.push 'minimized '
           changed = notification.changed
           unless old?
             ## Ignore some initial values during creation of message.
@@ -514,11 +520,11 @@
           updated = momentInUserTimezone msg.updated, user
           dates = "on #{updated.format 'ddd, MMM D, YYYY [at] H:mm z'}"
           if msg.root?
-            html += "
#{authorsHTML} #{verb} #{adjectives}message #{linkToMessage msg, user, true, true} in the thread #{linkToMessage rootmsg, true, true} #{dates}:"
-            text += "#{authorsText} #{verb} #{adjectives}message #{linkToMessage msg, user, false, true} in the thread #{linkToMessage rootmsg, user, false, true} #{dates}:"
+            html += "
#{authorsHTML} #{verb} #{adjectivesHTML.join ''}message #{linkToMessage msg, user, true, true} in the thread #{linkToMessage rootmsg, true, true} #{dates}:"
+            text += "#{authorsText} #{verb} #{adjectivesText.join ''}message #{linkToMessage msg, user, false, true} in the thread #{linkToMessage rootmsg, user, false, true} #{dates}:"
           else
-            html += "
#{authorsHTML} #{verb} #{adjectives}root message in the thread #{linkToMessage msg, user, true, true} #{dates}:"
-            text += "#{authorsText} #{verb} #{adjectives}root message in the thread #{linkToMessage msg, user, false, true} #{dates}:"
+            html += "
#{authorsHTML} #{verb} #{adjectivesHTML.join ''}root message in the thread #{linkToMessage msg, user, true, true} #{dates}:"
+            text += "#{authorsText} #{verb} #{adjectivesText.join ''}root message in the thread #{linkToMessage msg, user, false, true} #{dates}:"
           html += '\n\n'
           text += '\n\n'
           ## xxx also could use diff on body


Dispose subscriptions in ResolverView.

Dispose subscriptions in ResolverView.

--- lib/resolver-view.coffee
+++ lib/resolver-view.coffee
@@ -21,12 +21,14 @@
         @button class: 'btn btn-primary', click: 'resolve', 'Stage'

   initialize: (@editor) ->
-    @disposables = new CompositeDisposable()
+    @subs = new CompositeDisposable()

     @refresh()
     @editor.onDidSave => @refresh()

-    @disposables.add atom.commands.add @element, 'merge-conflicts:quit', => @dismiss()
+    @subs.add atom.commands.add @element, 'merge-conflicts:quit', => @dismiss()
+
+  detached: -> @subs.dispose()

   getModel: -> null


added changed file rsync

added changed file rsync

--- lib/server-script.coffee
+++ lib/server-script.coffee
@@ -4,7 +4,7 @@
 fs         = require 'fs-plus'
 path       = require 'path'
 filewalker = require 'filewalker'
-parser     = require 'gitignore-parser'
+gitIgParse = require 'gitignore-parser'
 cson       = require 'season'
 SubAtom    = require 'sub-atom'
 {execSync} = require 'child_process'
@@ -12,17 +12,12 @@
 module.exports =
   activate: ->
     @subs = new SubAtom
-    try
-      @gitignore = parser.compile fs.readFileSync ".gitignore", "utf8"
-    catch e
-      @gitignore = null
     @subs.add atom.commands.add 'atom-workspace', 'server-script:save': => @save()
     @rootDirPath = atom.project.getDirectories()[0].getPath()
     @serverScriptFolder = path.join @rootDirPath, '.server-script'

   initSetupFolder: ->
     fs.copySync 'init-setup-folder', @serverScriptFolder
-    ignorePath = path.join @serverScriptFolder, '.gitignore'
     fs.writeFileSync ignorePath, 'secrets.cson\n.run-server-script.sh\n'
     atom.notifications.addInfo \
         "A new .server-script folder was created in the root folder. " +
@@ -49,13 +44,19 @@
        not (secret = @loadCsonInitFile 'secrets.cson') then return
     pwdStr = (if secret.password then ':' + password else '')
     @server = secret.login.user + pwdStr + '@' + @setup.server.location
+    # console.log @setup, secret, @server
+    # console.log @doSSH 'ls -al'

-    runScript = fs.readFileSync 'init-setup-folder/.run-server-script.sh', 'utf8'
-    runPath = path.join @serverScriptFolder, '.run-server-script.sh'
-    fs.writeFileSync runPath, runScript
-    console.log @setup, secret, @server
+    if @setup.options.syncChangedFiles
+      remotePath = @server + ':' + path.normalize @setup.server.projectRoot
+      cmd = "rsync -a --filter=':- .gitignore' #{@rootDirPath}/ #{remotePath}/\n"
+      console.log cmd
+      console.log execSync cmd, timeout:5e3, encoding:'utf8', maxBuffer:10e6

-    console.log @doSSH 'ls -al'
+    # runScript = fs.readFileSync 'init-setup-folder/.run-server-script.sh', 'utf8'
+    # runPath = path.join @serverScriptFolder, '.run-server-script.sh'
+    # fs.writeFileSync runPath, runScript
+
   #
   #
   #


:bug: Fix text editor going past the limit of the pane

Fixes #59

--- lib/table-element.coffee
+++ lib/table-element.coffee
@@ -833,10 +833,19 @@
     activeCellRect = @cellScreenRect(position)
     bounds = @getBoundingClientRect()

-    @editorElement.style.top = @toUnit(activeCellRect.top + bounds.top)
-    @editorElement.style.left = @toUnit(activeCellRect.left + bounds.left)
-    @editorElement.style.minWidth = @toUnit(activeCellRect.width)
-    @editorElement.style.minHeight = @toUnit(activeCellRect.height)
+    leftPos = Math.max(activeCellRect.left + bounds.left, bounds.left)
+    topPos = Math.max(activeCellRect.top + bounds.top, bounds.top)
+    availableWidth = (bounds.left + @clientWidth) - leftPos
+    availableHeight = (bounds.top + @clientHeight) - topPos
+    preferredWidth = Math.min(activeCellRect.width, availableWidth)
+    preferredHeight = Math.min(activeCellRect.height, availableHeight)
+
+    @editorElement.style.top = @toUnit(topPos)
+    @editorElement.style.left = @toUnit(leftPos)
+    @editorElement.style.minWidth = @toUnit(preferredWidth)
+    @editorElement.style.maxWidth = @toUnit(availableWidth)
+    @editorElement.style.minHeight = @toUnit(preferredHeight)
+    @editorElement.style.maxHeight = @toUnit(availableHeight)
     @editorElement.style.display = 'block'

     @editorElement.dataset.column = @tableEditor.getScreenColumn(position.column).name ? columnName(position.column)


bugfix: window.postMessage should set context to the sandboxed version of 'window', else window != evt.source for website code running in the sandbox

--- lib/zombie/window.coffee
+++ lib/zombie/window.coffee
@@ -155,7 +155,12 @@
     event.data = data
     # Window A (source) calls B.postMessage, to determine A we need the
     # caller's window.
-    event.source = inContext
+
+    # DDOPSON-2012-11-09 - inContext.getGlobal() is used here so that for website code executing
+    # inside the sandbox context, event.source == window.  Even though the inContext object is mapped
+    # to the sandboxed version of the object returned by getGlobal, they are not the same object
+    # ie, inContext.foo == inContext.getGlobal().foo, but inContext != inContext.getGlobal()
+    event.source = inContext.getGlobal()
     origin = event.source.location
     event.origin = URL.format(protocol: origin.protocol, host: origin.host)
     window._dispatchEvent(window, event, true)


Should add `end` to the dependent keys

Should add `end` to the dependent keys

--- src/widgets/components/bs-pagination.coffee
+++ src/widgets/components/bs-pagination.coffee
@@ -18,6 +18,7 @@
   start: 1
   current: 1
   size: 9
+  total: 0
   url: ''

   sizingClassName: (->
@@ -38,7 +39,7 @@
         url: @get('url')
         content: i
         current: @get('current')
-  ).property('start', 'size', 'current', 'url')
+  ).property('start', 'end', 'current', 'url')

   atFirstPage: (->
     @get('current') == 1


View Logs: Parse logs to keep date and time less visible

--- packages/rocketchat-logger/client/views/viewLogs.coffee
+++ packages/rocketchat-logger/client/views/viewLogs.coffee
@@ -9,7 +9,9 @@
 		return stdout.find({}, {sort: {ts: 1}})

 	ansispan: (string) ->
-		return ansispan(string.replace(/\s/g, ' ').replace(/(\\n|\n)/g, '
'))
+		string = ansispan(string.replace(/\s/g, ' ').replace(/(\\n|\n)/g, '
'))
+		string = string.replace(/(.\d{8}-\d\d:\d\d:\d\d\.\d\d\d\(?.{0,2}\)?)/, '$1')
+		return string

 	formatTS: (date) ->
 		return moment(date).format('YMMDD-HH:mm:ss.SSS(ZZ)')


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/active_bounds.coffee
+++ src/active_bounds.coffee
@@ -14,5 +14,5 @@
   log I.activeBounds

   self.bind 'update', ->
-    self.destroy() unless I.activeBounds.x <= I.x <= I.activeBounds.x
+    self.destroy() unless I.activeBounds.x <= I.x <= I.activeBounds.x + I.activeBounds.width
     self.destroy() unless I.activeBounds.top <= I.y <= I.activeBounds.bottom


Working on the JSON algorithm

Working on the JSON algorithm


activity: use whenRemovingFinished to hook in for item removal

--- client/activity/lib/views/messagepane.coffee
+++ client/activity/lib/views/messagepane.coffee
@@ -293,7 +293,7 @@
     [item] = listItems.filter (item) -> item.getData().getId() is message.getId()

     if item?
-      item.once 'HideAnimationFinished', =>
+      item.whenRemovingFinished =>
         @listController.removeItem item
         @listController.showNoItemWidget()  if @listController.getListItems().length is 0


client/redux: handle bongo realtime actions in middleware

--- client/app/lib/redux/middleware/bongo.coffee
+++ client/app/lib/redux/middleware/bongo.coffee
@@ -1,4 +1,5 @@
 _ = require 'lodash'
+{ LOAD, REMOVE } = require 'app/redux/modules/bongo'

 module.exports = bongoMiddleware = (remote) -> (store) -> (next) -> (action) ->

@@ -8,8 +9,22 @@
   rest = _.omit action, ['bongo', 'type', 'types']

   next
-    types: types or generateTypes type
-    promise: bongo(remote, { getState: store.getState })
+    types: types or generateTypes(type)
+    promise: ->
+
+      bongo(remote, { getState: store.getState }).then (results) ->
+
+        unless Array.isArray results
+          results = [results]
+
+        results.forEach (result) ->
+          result.on 'update', ->
+            store.dispatch { type: LOAD.SUCCESS, result }
+
+          result.on 'deleteInstance', ->
+            store.dispatch { type: REMOVE.SUCCESS, result }
+
+        return results

 generateTypes = (type) -> [


Changed ReportsCtrl init to only get new stats on first init.

--- client/app/reports/reports.controller.coffee
+++ client/app/reports/reports.controller.coffee
@@ -4,7 +4,8 @@
 .controller 'ReportsCtrl', ($scope, Statistics) ->
   console.log 'reportsctrl'
   $scope.eloStatsData = Statistics.eloStats
-  Statistics.getEloStats($scope)
+  if not $scope.eloStatsData[0]
+    Statistics.getEloStats($scope)
   $scope.$watch(
     ->
       return Statistics.eloStats[0]


Add snippets for disabling ESLint rule

Add snippets for disabling ESLint rule

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -316,6 +316,24 @@
     'prefix': 'exportdefault'
     'body': 'export default ${1:mname};'

+  # ESLint
+
+  'eslint disable':
+    'prefix': 'eslintdisable'
+    'body': '/* eslint-disable */'
+
+  'eslint disable rule':
+    'prefix': 'eslintdisablerule'
+    'body': '/* eslint-disable $1 */$2'
+
+  'eslint disable next':
+    'prefix': 'eslintdisablenext'
+    'body': '/* eslint-disable-next-line */'
+
+  'eslint disable next rule':
+    'prefix': 'eslintdisablenextrule'
+    'body': '/* eslint-disable-next-line $1 */$2'
+
   # Mocha

   'it':


Style

Style

--- src/trix/text_controller.coffee
+++ src/trix/text_controller.coffee
@@ -56,17 +56,16 @@
       delete @currentAttributes[attributeName]
     else
       @currentAttributes[attributeName] = true
-    @notifyDelegateOfCurrentAttributeChange()
+    @notifyDelegateOfCurrentAttributesChange()

   updateCurrentAttributes: ->
     if selectedRange = @textView.getSelectedRange()
       position = selectedRange[0] - 1
       @currentAttributes = @text.getAttributesAtPosition(position)
-      @notifyDelegateOfCurrentAttributeChange()
+      @notifyDelegateOfCurrentAttributesChange()

-  notifyDelegateOfCurrentAttributeChange: ->
+  notifyDelegateOfCurrentAttributesChange: ->
     @delegate?.textControllerDidChangeCurrentAttributes?(@currentAttributes)
-

   # Selection observer delegate


actually check i am the user with the roles (#4152)

only show the lab links to users that possess the roles, not just if those roles exist you muppet cam.

--- app/pages/project/project-page.cjsx
+++ app/pages/project/project-page.cjsx
@@ -85,8 +85,9 @@
       @props.project.display_name

   userHasLabAccess: ->
-    userRoles = @props.projectRoles.some ({roles}) =>
-      roles.includes('owner') || roles.includes('collaborator')
+    userRoles = @props.projectRoles.some (role) =>
+      if role.links.owner.id == @props.user.id
+        role.roles.includes('owner') || role.roles.includes('collaborator')

   render: ->
     rearrangedLinks = @props.project.urls.sort (a, b) => a.path? & !b.path? ? 1 : 0


Remove unneeded empty hash fallback

Remove unneeded empty hash fallback

--- lib/directory.coffee
+++ lib/directory.coffee
@@ -96,7 +96,7 @@
       return

     newEntries = []
-    removedEntries = _.clone(@entries) ? {}
+    removedEntries = _.clone(@entries)
     index = 0
     for entry in @directory.getEntries() when not @isPathIgnored(entry.path)
       name = entry.getBaseName()


Skip Directories

Skip Directories

Because `**/*-js` matches the `highlight.js` directory.

--- lib/generator.coffee
+++ lib/generator.coffee
@@ -44,6 +44,8 @@
   # ### Processing Pipeline
   vfs.src glob, base: root
   .pipe map (file, cb) ->
+    if file.stat.isFile() then cb(null, file) else cb()
+  .pipe map (file, cb) ->
     # Save start time
     file.timingStart = process.hrtime()
     cb(null, file)


修正DropDown中filterItem事件名不符的BUG

修正DropDown中filterItem事件名不符的BUG

--- src/widget/edit/dropdown.coffee
+++ src/widget/edit/dropdown.coffee
@@ -494,12 +494,12 @@

 		if @_opened and @_filterable
 			inputDom = @_doms.input
-			$fly(inputDom).on("input.filter", () => @_onInput(inputDom.value))
+			$fly(inputDom).on("input.filterItem", () => @_onInput(inputDom.value))
 		return

 	close: (selectedValue) ->
 		if @_filterable
-			$fly(@_doms.input).off("input.filter")
+			$fly(@_doms.input).off("input.filterItem")
 		return super(selectedValue)

 	_onInput: (value) ->


Update app to listen to repo change events and reload the workspace

--- scripts/gh-book/app.coffee
+++ scripts/gh-book/app.coffee
@@ -129,7 +129,7 @@
         controller.main = App.main

         # Custom routes to configure the Github User and Repo from the browser
-        new class GithubRouter extends Backbone.Router
+        router = new class GithubRouter extends Backbone.Router

           setDefaultRepo = () ->
             if not session.get('repoName')
@@ -163,9 +163,9 @@
               repoName: repoName
               branch:   branch

-            remoteUpdater.stop()
-            onFail(allContent.reload(), 'There was a problem re-loading the repo')
-            @goWorkspace()
+            # The app listens to session onChange events and will call .goWorkspace
+            # It listens to 'change' because the auth view may also change the session
+

           # Delay the route handling until the initial content is loaded
           # TODO: Move this into the controller
@@ -173,6 +173,13 @@
             @_loadFirst().done () => controller.goWorkspace()
           goEdit: (id)    ->
             @_loadFirst().done () => controller.goEdit(id)
+
+
+        session.on 'change', () =>
+          if not _.isEmpty _.pick(session.changed, ['repoUser', 'repoName', 'branch'])
+            remoteUpdater.stop()
+            onFail(allContent.reload(), 'There was a problem re-loading the repo')
+            router.goWorkspace()

         Backbone.history.start


look up the root node when calling addChild

--- scripts/mixins/tree.coffee
+++ scripts/mixins/tree.coffee
@@ -19,8 +19,6 @@
       throw 'BUG: Missing root' if not options.root
       #throw 'BUG: Missing title or model' if not options.title

-      @_tree_root = options.root
-
       @_tree_children = options.children or new Backbone.Collection()

       @_tree_children.on 'add', (child, collection, options) =>
@@ -33,12 +31,10 @@
         child._tree_parent.removeChild(child, options) if child._tree_parent and child._tree_parent != @

         child._tree_parent = @
-        child._tree_root = @_tree_root
         @trigger 'tree:add', child, collection, options

       @_tree_children.on 'remove', (child, collection, options) =>
         delete child._tree_parent
-        delete child._tree_root
         @trigger 'tree:remove', child, collection, options

       @_tree_children.on 'change', (child, options) =>
@@ -115,11 +111,9 @@
       # Before adding the model make sure it's a Tree Node (does it have `._tree_children`.
       # If not, wrap it in a node
       if ! (model._tree_children)
-        model = @_tree_root.newNode {model:model}
+        model = (@getRoot() or @).newNode {model:model}

       children.add(model, {at:at})
-      # When moving from one book to another make sure the root is set
-      model._tree_root = @_tree_root

   return treeMixin


Pass a function

Pass a function

--- scripts/poo-tracker.coffee
+++ scripts/poo-tracker.coffee
@@ -30,6 +30,4 @@

   robot.adapter.on 'connected', ->
     console.log("Connected event for poop tracker")
-    setInterval ->
-      checkRedisForShit()
-    , 1000
+    setInterval checkRedisForShit, 1000


Use the open-balena-api endpoints for device type & version info

Resolves: #664
Depends-on: https://github.com/balena-io/resin-api/issues/1829
HQ: https://github.com/balena-io/balena/issues/1744
Change-type: minor
Signed-off-by: Thodoris Greasidis <5791bdc4541e79bd43d586e3c3eff1e39c16665f@balena.io>

--- lib/models/os.coffee
+++ lib/models/os.coffee
@@ -63,13 +63,23 @@
 			if not isValid
 				throw new errors.BalenaInvalidDeviceType('No such device type')

-	getDownloadSize = imgMakerHelper.buildApiRequester
-		buildUrl: ({ deviceType, version }) -> "/size_estimate?deviceType=#{deviceType}&version=#{version}"
-		postProcess: ({ body }) -> body.size
-
-	getOsVersions = imgMakerHelper.buildApiRequester
-		buildUrl: ({ deviceType }) -> "/image/#{deviceType}/versions"
-		postProcess: ({ body: { versions, latest } }) ->
+	getDownloadSize = (deviceType, version) ->
+		request.send
+			method: 'GET'
+			url: "/device-types/v1/#{deviceType}/images/#{version}/download-size"
+			baseUrl: apiUrl
+			sendToken: false
+		.get('body')
+		.get('size')
+
+	getOsVersions = (deviceType) ->
+		request.send
+			method: 'GET'
+			url: "/device-types/v1/#{deviceType}/images"
+			baseUrl: apiUrl
+			sendToken: false
+		.get('body')
+		.then ({ versions, latest }) ->

 			versions.sort(bSemver.rcompare)
 			potentialRecommendedVersions = reject versions, (version) ->


clean up outdent docs and messages

clean up outdent docs and messages

--- lib/operators.coffee
+++ lib/operators.coffee
@@ -252,7 +252,7 @@
     @complete = true

 #
-# It indents everything selected by the following motion.
+# It outdents everything selected by the following motion.
 #
 class Outdent
   motion: null
@@ -263,7 +263,7 @@

   isComplete: -> @complete

-  # Public: Indents the text selected by the given motion.
+  # Public: Outdents the text selected by the given motion.
   #
   # count - The number of times to execute.
   #
@@ -280,12 +280,12 @@

   # Public: Marks this as complete and saves the motion.
   #
-  # motion - The motion used to select what to delete.
-  #
-  # Returns nothing.
-  compose: (motion) ->
-    if not motion.select
-      throw new OperatorError("Indent must compose with a motion")
+  # motion - The motion used to select what to outdent.
+  #
+  # Returns nothing.
+  compose: (motion) ->
+    if not motion.select
+      throw new OperatorError("Outdent must compose with a motion")

     @motion = motion
     @complete = true


Fix issue when toggling a breakpoint and editor is in an invalid state

--- lib/php-debug.coffee
+++ lib/php-debug.coffee
@@ -196,13 +196,14 @@

   toggle: ->
     editor = atom.workspace.getActivePaneItem()
+    return if !editor || !editor.getSelectedBufferRange
     range = editor.getSelectedBufferRange()
     marker = editor.markBufferRange(range)

   toggleDebugging: ->
     if @currentCodePointDecoration
       @currentCodePointDecoration.destroy()
-
+
     pane = atom.workspace.paneForItem(@unifiedWindow)
     if !pane
       @showWindows()
@@ -246,6 +247,7 @@

   toggleBreakpoint: ->
     editor = atom.workspace.getActivePaneItem()
+    return if !editor || !editor.getSelectedBufferRange
     range = editor.getSelectedBufferRange()
     path = editor.getPath()
     breakpoint = new Breakpoint({filepath:path, line:range.getRows()[0]+1})


fix: remove auth token from overseer urls

--- src/app/api/models/task.coffee
+++ src/app/api/models/task.coffee
@@ -35,7 +35,7 @@
     "#{DoubtfireConstants.API_URL}/projects/#{project.project_id}/task_def_id/#{task.definition.id}/submission"

   Task.generateTestSubmissionUrl = (unitId, task_def) ->
-    "#{DoubtfireConstants.API_URL}/units/#{unitId}/task_definitions/#{task_def.id}/test_overseer_assessment?auth_token=#{currentUser.authenticationToken}"
+    "#{DoubtfireConstants.API_URL}/units/#{unitId}/task_definitions/#{task_def.id}/test_overseer_assessment"

   Task.getTaskPDFUrl = (unit, task_def) ->
     "#{DoubtfireConstants.API_URL}/units/#{unit.id}/task_definitions/#{task_def.id}/task_pdf.json"
@@ -50,16 +50,13 @@
     "#{DoubtfireConstants.API_URL}/submission/assess.json?unit_id=#{unit.id}"

   Task.getTaskAssessmentResourcesUrl = (unit, task_def) ->
-    "#{DoubtfireConstants.API_URL}/units/#{unit.id}/task_definitions/#{task_def.id}/task_assessment_resources.json?auth_token=#{currentUser.authenticationToken}"
+    "#{DoubtfireConstants.API_URL}/units/#{unit.id}/task_definitions/#{task_def.id}/task_assessment_resources.json"

   Task.generateLatestAssessmentUrl = (task) ->
-    "#{DoubtfireConstants.API_URL}/projects/#{task.project().project_id}/task_def_id/#{task.definition.id}/submissions/latest?auth_token=#{currentUser.authenticationToken}"
-
-  Task.getLatestTimestampsUrl = (task) ->
-    "#{DoubtfireConstants.API_URL}/projects/#{task.project().project_id}/task_def_id/#{task.definition.id}/submissions/timestamps?auth_token=#{currentUser.authenticationToken}"
+    "#{DoubtfireConstants.API_URL}/projects/#{task.project().project_id}/task_def_id/#{task.definition.id}/submissions/latest"

   Task.getSubmissionByTimestampUrl = (task, timestamp) ->
-    "#{DoubtfireConstants.API_URL}/projects/#{task.project().project_id}/task_def_id/#{task.definition.id}/submissions/timestamps/#{timestamp}?auth_token=#{currentUser.authenticationToken}"
+    "#{DoubtfireConstants.API_URL}/projects/#{task.project().project_id}/task_def_id/#{task.definition.id}/submissions/timestamps/#{timestamp}"

   Task.generateMarkingSubmissionUrl = ->


--amend

--amend

initial commit

--- src/circle-build-status.coffee
+++ src/circle-build-status.coffee
@@ -17,7 +17,8 @@
 #
 # Author:
 #   sarkis
-#
+#   maletor
+
 moment = require("moment")
 require("moment-duration-format")


milestones stays open with multipart

milestones stays open with multipart

--- src/components/task/index.cjsx
+++ src/components/task/index.cjsx
@@ -188,6 +188,7 @@
       true

   onPartEnter: (stepKey, scrollInfo) ->
+    return if @context.router.getCurrentParams().milestones?
     {previousPosition} = scrollInfo
     return unless previousPosition is 'below'

@@ -202,6 +203,7 @@
     @context.router.transitionTo('viewTaskStep', params)

   onPartLeave: (stepKey, scrollInfo) ->
+    return if @context.router.getCurrentParams().milestones?
     {currentPosition} = scrollInfo
     return unless currentPosition is 'below'


add tests for new line text with format option

--- test/new-line-text-test.coffee
+++ test/new-line-text-test.coffee
@@ -2,85 +2,168 @@
 bHtml = require './'

 describe 'NewLineText (|)', ->
-  it 'works', ->
-    source = '''
-      |text
-    '''
-    assert bHtml(source) is '\ntext'
-    assert bHtml(source, format: 'demo') is '''
-      text
-    '''
+  context '1', ->
+    it 'works', ->
+      source = '''
+        |text
+      '''
+      format = (nodes, options) ->
+        assert nodes.length is 1
+        assert nodes[0].type is 'new line text'
+        assert nodes[0].value is 'text'
+        assert.deepEqual options, { format }
+      bHtml source, { format }
+      assert bHtml(source) is '\ntext'
+      assert bHtml(source, format: 'demo') is '''
+        text
+      '''

-    source = '''
-      |line1
-      |line2
-    '''
-    assert bHtml(source) is '\nline1\nline2'
-    assert bHtml(source, format: 'demo') is '''
-      line1
-      line2
-    '''
-
-    source = '''
-
+      source = '''
         |line1
         |line2
-    '''
-    assert bHtml(source) is '
\nline1\nline2
'
-    assert bHtml(source, format: 'demo') is '''
-

+      '''
+      format = (nodes, options) ->
+        assert nodes.length is 2
+        assert nodes[0].type is 'new line text'
+        assert nodes[0].value is 'line1'
+        assert nodes[1].type is 'new line text'
+        assert nodes[1].value is 'line2'
+        assert.deepEqual options, { format }
+      bHtml source, { format }
+      assert bHtml(source) is '\nline1\nline2'
+      assert bHtml(source, format: 'demo') is '''
         line1
         line2
-

-    '''
+      '''

-    source = '''
-
+    it 'works', ->
+      source = '''
+

+        assert nodes.length is 1
+        assert nodes[0].type is 'element'
+        assert nodes[0].name is 'p'
+        assert nodes[0].attributes.length is 0
+        assert nodes[0].children.length is 1
+        assert nodes[0].children[0].type is 'new line text'
+        assert nodes[0].children[0].value is 'text'
+        assert.deepEqual options, { format }
+      bHtml source, { format }
+      assert bHtml(source) is '
\ntext
'
+      assert bHtml(source, format: 'demo') is '''
+

+          text
+

+      '''

-    # NOTE: escape attribute
-    source = '''
-      |@text
-    '''
-    assert bHtml(source) is '\n@text'
-    assert bHtml(source, format: 'demo') is '''
-      @text
-    '''
+  context '4', ->
+    it 'works', ->
+      source = '''
+

+        assert nodes.length is 1
+        assert nodes[0].type is 'element'
+        assert nodes[0].name is 'p'
+        assert nodes[0].attributes.length is 0
+        assert nodes[0].children.length is 2
+        assert nodes[0].children[0].type is 'new line text'
+        assert nodes[0].children[0].value is 'line1'
+        assert nodes[0].children[1].type is 'new line text'
+        assert nodes[0].children[1].value is 'line2'
+        assert.deepEqual options, { format }
+      bHtml source, { format }
+      assert bHtml(source) is '
\nline1\nline2
'
+      assert bHtml(source, format: 'demo') is '''
+

+          line1
+          line2
+

+      '''

-    # NOTE: escape element
-    source = '''
-      |<text
-    '''
-    assert bHtml(source) is '\n<text'
-    assert bHtml(source, format: 'demo') is '''
-      <text
-    '''
+  context '5', ->
+    it 'works', ->
+      source = '''
+

+        assert nodes.length is 1
+        assert nodes[0].type is 'element'
+        assert nodes[0].name is 'p'
+        assert nodes[0].attributes.length is 0
+        assert nodes[0].children.length is 3
+        assert nodes[0].children[0].type is 'new line text'
+        assert nodes[0].children[0].value is 'line1'
+        assert nodes[0].children[1].type is 'new line text'
+        assert nodes[0].children[1].value is ''
+        assert nodes[0].children[2].type is 'new line text'
+        assert nodes[0].children[2].value is 'line3'
+        assert.deepEqual options, { format }
+      bHtml source, { format }
+      assert bHtml(source) is '
\nline1\n\nline3
'
+      # NOTE: prevent to remove trailing spaces
+      assert bHtml(source, format: 'demo') is '
\n  line1\n  \n  line3\n
'

-    # NOTE: escape text
-    source = '''
-      |>text
-    '''
-    assert bHtml(source) is '\n>text'
-    assert bHtml(source, format: 'demo') is '''
-      >text
-    '''
+  context '6', ->
+    it 'works', ->
+      # NOTE: escape attribute
+      source = '''
+        |@text
+      '''
+      format = (nodes, options) ->
+        assert nodes.length is 1
+        assert nodes[0].type is 'new line text'
+        assert nodes[0].value is '@text'
+        assert.deepEqual options, { format }
+      bHtml source, { format }
+      assert bHtml(source) is '\n@text'
+      assert bHtml(source, format: 'demo') is '''
+        @text
+      '''
+
+  context '7', ->
+    it 'works', ->
+      # NOTE: escape element
+      source = '''
+        |<text
+      '''
+      format = (nodes, options) ->
+        assert nodes.length is 1
+        assert nodes[0].type is 'new line text'
+        assert nodes[0].value is '<text'
+        assert.deepEqual options, { format }
+      bHtml source, { format }
+      assert bHtml(source) is '\n<text'
+      assert bHtml(source, format: 'demo') is '''
+        <text
+      '''
+
+  context '8', ->
+    it 'works', ->
+      # NOTE: escape text
+      source = '''
+        |>text
+      '''
+      format = (nodes, options) ->
+        assert nodes.length is 1
+        assert nodes[0].type is 'new line text'
+        assert nodes[0].value is '>text'
+        assert.deepEqual options, { format }
+      bHtml source, { format }
+      assert bHtml(source) is '\n>text'
+      assert bHtml(source, format: 'demo') is '''
+        >text
+      '''

   context '(errors)', ->
     it 'works', ->


test(view): less strict direction detection

--- test/spec/services/view.coffee
+++ test/spec/services/view.coffee
@@ -25,6 +25,8 @@
         url: '/home/baz/fifth'
         data:
           transition: 'slide'
+      .state 'faroff',
+        url: '/home/second/foo/bar/baz'
       .state 'alien',
         url: '/crazy/route'
     return
@@ -33,7 +35,7 @@
   state = null
   scope = null

-  home = second = third = fourth = fifth = alien = null
+  home = second = third = fourth = fifth = alien = faroff = null

   beforeEach inject ($rootScope, bpView, $state) ->
@@ -46,6 +48,8 @@
     fourth = state.get 'fourth'
     fifth = state.get 'fifth'
     alien = state.get 'alien'
+    faroff = state.get 'faroff'
+

   describe 'listen', ->
     it 'should listen to events', ->
@@ -108,9 +112,11 @@
     it 'should detect "normal"', ->
       expect(viewService.getDirection home, second).toBe 'normal'
       expect(viewService.getDirection second, third).toBe 'normal'
+      expect(viewService.getDirection home, faroff).toBe 'normal'

     it 'should detect "reverse"', ->
       expect(viewService.getDirection second, home).toBe 'reverse'
+      expect(viewService.getDirection faroff, home).toBe 'reverse'

     it 'should detect no direction', ->
       expect(viewService.getDirection fourth, fifth).toBe null


Bongo: change last parameter name

Bongo: change last parameter name

--- node_modules_koding/bongo/lib/bongo.coffee
+++ node_modules_koding/bongo/lib/bongo.coffee
@@ -160,27 +160,27 @@
   getStaticRoutingKey =(konstructor, event)->
     "constructor.#{konstructor.name}.event.#{event}"

-  handleEvent:(type, ctx, event, rest...)->
+  handleEvent:(type, ctx, event, params)->
     switch type
       when 'instance'
         if event is 'notification'
-          @publishEventToExchange event, rest[0]
+          @publishEventToExchange event, params[0]
         if event is 'control'
-          @publishToControlExchange event, rest[0]
+          @publishToControlExchange event, params[0]
         else
-          payload     = JSON.stringify rest[0] or null
+          payload     = JSON.stringify params[0] or null
           routingKey  = getInstanceRoutingKey ctx, event
           @enqueueUpdateInstancesEvent routingKey, payload
       when 'static'
         if event is 'broadcast'
-          [secretChannelId, message] = rest
+          [secretChannelId, message] = params
           @fetchBrokerExchange (brokerExchange)->
             messageStr = JSON.stringify message
             brokerExchange.publish secretChannelId, messageStr  if messageStr?
         else
           broadcastable = ctx.getBroadcastable()
           if !broadcastable? or broadcastable # TODO: this is insecure by default :(
-            [data] = rest
+            [data] = params
             data = JSON.stringify data
             @enqueueUpdateInstancesEvent getStaticRoutingKey(ctx, event), data


Use refs instead of id

Use refs instead of id

--- resources/assets/coffee/react/profile-page/extra.coffee
+++ resources/assets/coffee/react/profile-page/extra.coffee
@@ -37,7 +37,7 @@
     osu.pageChange()
     @_modeScan()

-    $('#profile-extra-list').sortable
+    $(@refs.pages).sortable
       cursor: 'move'
       handle: '.profile-extra__dragdrop-toggle'
       revert: 150
@@ -109,7 +109,7 @@

   updateOrder: (element) =>
     oldOrder = @state.profileOrder
-    newOrder = $('#profile-extra-list').sortable('toArray')
+    newOrder = $(@refs.pages).sortable('toArray')

     id = element.attr 'id'

@@ -162,7 +162,7 @@

                 el ProfilePage.ExtraTab, key: m, mode: m, currentMode: @state.mode

-      div className: 'osu-layout__row', id: 'profile-extra-list',
+      div className: 'osu-layout__row', ref: 'pages',
         @props.profileOrder.map (m) =>
           topClassName = 'js-profile-page-extra--scrollspy'
           page =


Use setstate's callback, and add comments

--- resources/assets/coffee/react/profile-page/extra.coffee
+++ resources/assets/coffee/react/profile-page/extra.coffee
@@ -56,6 +56,8 @@
       @setState mode: _.last(elements).getAttribute('id')
       return

+    # FIXME: I don't remember why this one scans from bottom while
+    # the one in forum.refreshCounter does it from top.
     for page in pages by -1
       pageTop = page.getBoundingClientRect().top
       continue unless pageTop <= anchorHeight
@@ -67,11 +69,18 @@

   _modeSwitch: (_e, mode) =>
+    # Don't bother scanning the current position.
+    # The result will be wrong when target page is too short anyway.
     @_scrolling = true
+
     $.scrollTo "##{mode}", 500,
       onAfter: =>
-        @setState mode: mode
-        setTimeout (=> @_scrolling = false), 100
+        # Manually set the mode to avoid confusion (wrong highlight).
+        # Scrolling will obviously break it but that's unfortunate result
+        # from having the scrollspy marker at middle of page.
+        @setState mode: mode, =>
+          @_scrolling = false
+      # count for the tabs height
       offset: @refs.tabs.getBoundingClientRect().height * -1


Comment odd behavior in main_loop

Comment odd behavior in main_loop

--- engine/main_loop.coffee
+++ engine/main_loop.coffee
@@ -76,9 +76,13 @@
         @last_frame_durations[@_fdi] = frame_duration
         @_fdi = (@_fdi+1) % @last_frame_durations.length

-        @_frame_callbacks.shift()?()

-        if not @_frame_callbacks.length
+        if @_frame_callbacks.length != 0
+            @_frame_callbacks.shift()()
+        else
+            # TODO: doesn't run logic or physics if there's stuff loading;
+            # instead, it should block this stuff only in the initial load;
+            # ideally, set explicitely
             for scene in @context.loaded_scenes
                 if scene.loader.remaining_tasks[0] != 0 or not scene.enabled
                     continue


Issue #15: add the time function to the Atom plugin

--- tools/atom-plugin/language-micro/grammars/micro.cson
+++ tools/atom-plugin/language-micro/grammars/micro.cson
@@ -47,7 +47,7 @@
 	}
 	{
 		'comment': 'Built-in functions (it should be described before functions)'
-		'match': '\\b(nil|false|true|num|type|arity|floor|ceil|trunc|round|sin|cos|tn|arcsin|arccos|arctn|angle|pow|sqrt|exp|ln|lg|abs|random|head|tail|if|str|strb|strl|env|in|out|outln|err|errln|exit)\\b'
+		'match': '\\b(nil|false|true|num|type|arity|floor|ceil|trunc|round|sin|cos|tn|arcsin|arccos|arctn|angle|pow|sqrt|exp|ln|lg|abs|random|head|tail|if|str|strb|strl|env|time|in|out|outln|err|errln|exit)\\b'
 		'name': 'variable.function.micro'
 	}
 	{


remove mock data

remove mock data

--- assets/javascripts/parser.js.coffee
+++ assets/javascripts/parser.js.coffee
@@ -374,20 +374,6 @@
         scope.$on '$destroy', ->
           body = form = null
           document.off 'mouseup.anystyle'
-
-        # Mock Input
-        # scope.source =
-          """
-          Lamm, C., Zucconi, A., & Silani, G. (2013). Carl Rogers Meets the Neurosciences: Insights from Social Neuroscience for Client-Centered Therapy. In J. H. D. Cornelius-White, R. Motschnig-Pitrik & M. Lux (Hrsg.), Interdisciplinary Handbook of the Person-Centered Approach (63–78). New York: Springer.
-          Fisher, L. (2013). How Can I Trust You? Encounters with Carl Rogers and Game Theory. In J. H. D. Cornelius-White, R. Motschnig-Pitrik & M. Lux (Hrsg.), Interdisciplinary Handbook of the Person-Centered Approach (299–317). New York: Springer.
-          Rachlin, H., Sommerbeck, L., & Frankel, M. (2010). Rogers’ concept of the actualizing tendency in relation to Darwinian theory. (M. Cooper, J. C. Watson & W. B. Stiles, Eds.) Person-Centered & Experiential Psychotherapies, 9(1), 69-80.
-          Mason, M. J. (2009). Rogers Redux: Relevance and Outcomes of Motivational Interviewing Across Behavioral Problems. Journal of Counseling and Development, 87(3), 357–362.
-          """
-
-        # Parse Mock Input
-        # setTimeout(->
-        #   scope.parse()
-        # 200)

   ]).


計画コマンドの雛形作成

計画コマンドの雛形作成

--- scripts/plan.coffee
+++ scripts/plan.coffee
@@ -3,3 +3,16 @@
 #
 # Commands:
 #   hubot plan day #{yyyy/MM/DD} time #{hh:mm} min #{least_player_number}  max #{max_player_nunmber} - Plan boardgame party
+
+storage = require('../src/storage')
+
+module.exports = (robot) ->
+  robot.respond /plan day (\d{4}\/\d{2}\/\d{2}) time (\d{2}:\d{2}) min (\d+) max (\d+)$/, (msg) ->
+    day = msg.match[1]
+    time = msg.match[2]
+    min = msg.match[3]
+    max = msg.match[4]
+
+    msg.send "#{day}の#{time}からボードゲーム会を開催します。\n
+最小開催人数は#{min}人、最大人数は#{max}人までとなります。\n
+参加可否をよろしくお願い致します。"


fix(HomeCtrl): refresh scrollspy after each upload/download

--- app/scripts/controllers/HomeCtrl.coffee
+++ app/scripts/controllers/HomeCtrl.coffee
@@ -25,11 +25,13 @@
 		$scope.upload = ( item, callback = -> ) ->
 			audio.upload item, $scope.localPath, ->
 				$scope.$apply()
+				$( "body" ).scrollspy "refresh"
 				callback()
 			, throttledApply
 		$scope.download = ( item, callback = -> ) ->
 			audio.download item, $scope.localPath, ->
 				$scope.$apply()
+				$( "body" ).scrollspy "refresh"
 				callback()
 			, throttledApply