Add internal wait when finding by text

Add internal wait when finding by text

fixes #229

--- src/widgets/Widget.coffee
+++ src/widgets/Widget.coffee
@@ -182,14 +182,19 @@
     @root + (if selector then " #{selector}" else '')

   _findByText: (opts) ->
-    @find().then (el) ->
-      el.findElement(
-        # WebDriver lets you go out of the child scope
-        # if you pass an absolute xpath selector
-        # this is a bug in WebDriver and is terrible
-        # by passing a `.` this is no longer an issue.
-        Driver.By.xpath('.//*[normalize-space(text())=normalize-space("' + opts.text + '")]')
-      )
+    # WebDriver lets you go out of the child scope
+    # if you pass an absolute xpath selector
+    # this is a bug in WebDriver and is terrible
+    # by passing a `.` this is no longer an issue.
+    _selector = Driver.By.xpath('.//*[normalize-space(text())=normalize-space("' + opts.text + '")]')
+
+    @find().then (el) =>
+      @driver.wait(
+        _.bind(el.findElement, el, _selector),
+        global.timeout,
+        "Unable to find node containing text #{opts.text}"
+      ).then =>
+        el.findElement(_selector)

   _ensureElement: (selector) ->
     @driver.wait(


Check if unready exist

Check if unready exist

--- controllers/site.coffee
+++ controllers/site.coffee
@@ -30,6 +30,7 @@
         limit: 50
       Message.mapReduce opts, callback
   , (err, result) ->
+    unreads = result.messages?[0] or []
     res.render "app",
       user:
         _id: req.user._id
@@ -40,4 +41,4 @@
         group: req.user.group
       users: result.users
       posts: result.posts
-      unreads: result.messages[0]
+      unreads: unreads


Less noise on a non-match

Less noise on a non-match

**Example of Noisy Inspect Behavior**
![](http://cl.ly/image/320e2O1o1A2s/Image%202015-02-19%20at%2010.27.15%20PM.png)

--- src/scripts/pagerduty.coffee
+++ src/scripts/pagerduty.coffee
@@ -677,7 +677,7 @@
             foundIncidents.push(incident)

         if foundIncidents.length == 0
-          msg.reply "Couldn't find incidents #{incidentNumbers.join(', ')} in #{inspect incidents}"
+          msg.reply "Couldn't find incident(s) #{incidentNumbers.join(', ')}. Use `#{robot.name} pager incidents` for listing."
         else
           # loljson
           data = {


Fix testing coffee script.

Fix testing coffee script.

--- test.coffee
+++ test.coffee
@@ -1,3 +1,3 @@
-core = require('./nointro')
+nointro = require('./nointro')
 fs = require('fs')
 should = require('should')


test for multiple possibles

test for multiple possibles

--- test.coffee
+++ test.coffee
@@ -4,18 +4,13 @@
 create_liar = require './create_liar'

 # TODO
-# test for multiple possibles
 # Check for function called = string
 # check that arguments is an array
 # Check for value on callback arguments
 # Give context to error messages
-# Some nice debug output
-
-# Improve syntax:
-#liar.fake 'someFunction', ->
-# returns:
-#   value:
-#     someProperty: 5
+
+# Maybe some nice debug output
+

 describe 'create_mock', ->

@@ -160,6 +155,22 @@
       connection.query().size.should.equal 72
       done()

+it 'should match to right lie if multiple per function', ->
+  liar = create_liar [{
+    function_name: 'add'
+    arguments: [2, 3]
+    returns:
+      value: 5
+  },{
+    function_name: 'add'
+    arguments: [5, 4]
+    returns:
+      value: 9
+  }]
+  liar.add(2,3).should.equal 5
+  liar.add(5,4).should.equal 9
+
+
 describe 'Lie validation', ->

   it 'should validate that return has a value property', ->


"Themes" submenu accessible through keyboard

more or less...

--- client/jquery-dyalog.coffee
+++ client/jquery-dyalog.coffee
@@ -55,12 +55,20 @@
         return

       leftRight = (d) -> -> # d: delta, either +1 or -1;   note that this is a higher-order function
-        $t = $m.children(); i = $(@).parentsUntil('.menu').last().index() # Which top-level menu are we under?
-        n = $t.length; mFocus $t.eq((i + d + n) % n).find('a').eq 1; false
+        if $(@).is '.m-opener'
+          mFocus $(@).next('.m-box').find('a').first()
+        else
+          $t = $m.children(); i = $(@).parentsUntil('.menu').last().index() # Which top-level menu are we under?
+          n = $t.length; mFocus $t.eq((i + d + n) % n).find('a').eq 1
+        false

       upDown = (d) -> -> # d: delta, either +1 or -1;   note that this is a higher-order function
-        if $(@).is '.m-top' then mFocus $(@).parent().find('a').eq 1
-        else $s = $(@).parent().children 'a'; i = $s.index @; n = $s.length; mFocus $s.eq (i + d + n) % n
+        if $(@).is '.m-top'
+          mFocus $(@).parent().find(':not(hr)').eq 1
+        else
+          $s = $(@).closest('.m-box').children ':not(hr)'
+          i = $s.index @; n = $s.length; $f = $s.eq (i + d + n) % n
+          mFocus if $f.is 'a' then $f else $f.find('a').first()
         false

       $m = $(@).empty().addClass('menu').append arg.map render


separated colour groups def from styles def

--- client/prefs-colours.coffee
+++ client/prefs-colours.coffee
@@ -7,40 +7,62 @@
   # t: token type, a short name used as a key for storing customisations in localStorage
   # c: css selector
   # s: name to display in the UI
-  # d: default style
-  {t:' ',  c:'.cm-s-default,.CodeMirror-gutter-wrapper',s:'normal',d:{fg:'#000000',bg:'#ffffff'}}
-  {t:'0',  c:'.cm-apl-num',            s:'number',           d:{fg:'#888888'}}
-  {t:"'",  c:'.cm-apl-str',            s:'string',           d:{fg:'#008888'}}
-  {t:'⍬',  c:'.cm-apl-zilde',          s:'zilde',            d:{fg:'#000088'}}
-  {t:'a',  c:'.cm-apl-name',           s:'name',             d:{fg:'#888888'}}
-  {t:'A',  c:'.cm-apl-global-name',    s:'global name',      d:{fg:'#000000'}}
-  {t:'⎕',  c:'.cm-apl-quad-name',      s:'quad name',        d:{fg:'#880088'}}
-  {t:'+',  c:'.cm-apl-fn',             s:'function',         d:{fg:'#000088'}}
-  {t:'/',  c:'.cm-apl-op1',            s:'monadic operator', d:{fg:'#0000ff'}}
-  {t:'.',  c:'.cm-apl-op2',            s:'dyadic operator',  d:{fg:'#0000ff'}}
-  {t:'#',  c:'.cm-apl-ns',             s:'namespace',        d:{fg:'#888888'}}
-  {t:'←',  c:'.cm-apl-assignment',     s:'assignment',       d:{fg:'#0000ff'}}
-  {t:'⋄',  c:'.cm-apl-diamond',        s:'diamond',          d:{fg:'#0000ff'}}
-  {t:'(',  c:'.cm-apl-paren',          s:'parenthesis',      d:{fg:'#0000ff'}}
-  {t:'[',  c:'.cm-apl-bracket',        s:'bracket',          d:{fg:'#0000ff'}}
-  {t:';',  c:'.cm-apl-semicolon',      s:'semicolon',        d:{fg:'#0000ff'}}
-  {t:'{',  c:'.cm-apl-dfn',            s:'dfn',              d:{fg:'#0000ff'}}
-  {t:'{1', c:'.cm-apl-dfn1',           s:'dfn level 1',      d:{fg:'#0000ff'}}
-  {t:'{2', c:'.cm-apl-dfn2',           s:'dfn level 2',      d:{fg:'#0000ff'}}
-  {t:'{3', c:'.cm-apl-dfn3',           s:'dfn level 3',      d:{fg:'#0000ff'}}
-  {t:'∇',  c:'.cm-apl-tradfn',         s:'tradfn',           d:{fg:'#888888'}}
-  {t:':',  c:'.cm-apl-kw',             s:'keyword',          d:{fg:'#880000'}}
-  {t:'l:', c:'.cm-apl-label',          s:'label',            d:{fg:'#000000'}}
-  {t:'i',  c:'.cm-apl-idiom',          s:'idiom',            d:{fg:'#0000ff'}}
-  {t:'⍝',  c:'.cm-apl-comment',        s:'comment',          d:{fg:'#008888'}}
-  {t:'!',  c:'.cm-apl-err',            s:'error',            d:{fg:'#ff0000'}}
-  {t:'L',  c:'.CodeMirror-linenumber', s:'line number',      d:{fg:'#000088'}}
+  {t:' ',  c:'.cm-s-default,.CodeMirror-gutter-wrapper', s:'normal'}
+  {t:'0',  c:'.cm-apl-num',            s:'number'          }
+  {t:"'",  c:'.cm-apl-str',            s:'string'          }
+  {t:'⍬',  c:'.cm-apl-zilde',          s:'zilde'           }
+  {t:'a',  c:'.cm-apl-name',           s:'name'            }
+  {t:'A',  c:'.cm-apl-global-name',    s:'global name'     }
+  {t:'⎕',  c:'.cm-apl-quad-name',      s:'quad name'       }
+  {t:'+',  c:'.cm-apl-fn',             s:'function'        }
+  {t:'/',  c:'.cm-apl-op1',            s:'monadic operator'}
+  {t:'.',  c:'.cm-apl-op2',            s:'dyadic operator' }
+  {t:'#',  c:'.cm-apl-ns',             s:'namespace'       }
+  {t:'←',  c:'.cm-apl-assignment',     s:'assignment'      }
+  {t:'⋄',  c:'.cm-apl-diamond',        s:'diamond'         }
+  {t:'(',  c:'.cm-apl-paren',          s:'parenthesis'     }
+  {t:'[',  c:'.cm-apl-bracket',        s:'bracket'         }
+  {t:';',  c:'.cm-apl-semicolon',      s:'semicolon'       }
+  {t:'{',  c:'.cm-apl-dfn',            s:'dfn'             }
+  {t:'{1', c:'.cm-apl-dfn1',           s:'dfn level 1'     }
+  {t:'{2', c:'.cm-apl-dfn2',           s:'dfn level 2'     }
+  {t:'{3', c:'.cm-apl-dfn3',           s:'dfn level 3'     }
+  {t:'∇',  c:'.cm-apl-tradfn',         s:'tradfn'          }
+  {t:':',  c:'.cm-apl-kw',             s:'keyword'         }
+  {t:'l:', c:'.cm-apl-label',          s:'label'           }
+  {t:'i',  c:'.cm-apl-idiom',          s:'idiom'           }
+  {t:'⍝',  c:'.cm-apl-comment',        s:'comment'         }
+  {t:'!',  c:'.cm-apl-err',            s:'error'           }
+  {t:'L',  c:'.CodeMirror-linenumber', s:'line number'     }
 ]
 H = dict G.map (g, i) -> [g.t, i]

+scheme =
+  '0':fg:'#888888'
+  "'":fg:'#008888'
+  '⍬':fg:'#000088'
+  'a':fg:'#888888'
+  '⎕':fg:'#880088'
+  '+':fg:'#000088'
+  '/':fg:'#0000ff'
+  '.':fg:'#0000ff'
+  '#':fg:'#888888'
+  '←':fg:'#0000ff'
+  '⋄':fg:'#0000ff'
+  '(':fg:'#0000ff'
+  '[':fg:'#0000ff'
+  ';':fg:'#0000ff'
+  '{':fg:'#0000ff'
+  '∇':fg:'#888888'
+  ':':fg:'#880000'
+  'i':fg:'#0000ff'
+  '⍝':fg:'#008888'
+  '!':fg:'#ff0000'
+  'L':fg:'#000088'
+
 renderCSS = (v, rp = '') -> # v: style objects keyed by token type, rp: css rule prefix
   join G.map (g) ->
-    h = $.extend {}, g.d, v[g.t] # h: effective style
+    h = $.extend {}, scheme[g.t], v[g.t] # h: effective style
     g.c.split(',').map((x) -> rp + ' ' + x).join(',') + '{' +
       (h.fg         && "color:#{h.fg};"             || '') +
       (h.bg         && "background-color:#{h.bg};"  || '') +
@@ -57,7 +79,7 @@
 sel = -1 # index of the selected group

 @init = ($e) ->
-  u = []; (for g in G when 0 < u.indexOf g.d.fg then u.push g.d.fg); u.sort() # u: unique colours from the defaults
+  u = []; (for _, {fg} of scheme when fg && 0
-  v = prefs.hi(); model = G.map (g) -> $.extend {}, g.d, v[g.t]
+  v = prefs.hi(); model = G.map (g) -> $.extend {}, scheme[g.t], v[g.t]
   updateSampleStyle(); selectGroup 0, 1; cm.setSize $cm.width(), $cm.height()
   cm.setValue '''
     dfn←{ ⍝ sample
@@ -116,7 +138,7 @@
   v = {}
   for g, i in G
     m = model[i]
-    for p in props when m[p] != (g.d[p] || '') then (v[g.t] ?= {})[p] = m[p]
+    for p in props when m[p] != (scheme[g.t]?[p] || '') then (v[g.t] ?= {})[p] = m[p]
   v

 @save = -> prefs.hi getModelAsObject(); return


Add finder related files.

Add finder related files.

--- client/ide/includes.coffee
+++ client/ide/includes.coffee
@@ -19,6 +19,48 @@
   "workspace/panel.coffee"
   "workspace/workspace.coffee"

+  # finder
+  "finder/AppController.coffee",
+
+  "finder/commonviews/DNDUploader.coffee",
+
+  "finder/filetree/modals/openwith/openwithmodalitem.coffee",
+  "finder/filetree/modals/openwith/openwithmodal.coffee",
+  "finder/filetree/modals/vmdangermodalview.coffee",
+
+  "finder/filetree/controllers/findercontroller.coffee",
+  "finder/filetree/controllers/findertreecontroller.coffee",
+  "finder/filetree/controllers/findercontextmenucontroller.coffee",
+
+  "finder/filetree/itemviews/finderitem.coffee",
+  "finder/filetree/itemviews/fileitem.coffee",
+  "finder/filetree/itemviews/folderitem.coffee",
+  "finder/filetree/itemviews/mountitem.coffee",
+  "finder/filetree/itemviews/brokenlinkitem.coffee",
+  "finder/filetree/itemviews/sectionitem.coffee",
+  "finder/filetree/itemviews/vmitem.coffee",
+
+  "finder/filetree/itemsubviews/finderitemdeleteview.coffee",
+  "finder/filetree/itemsubviews/finderitemdeletedialog.coffee",
+  "finder/filetree/itemsubviews/finderitemrenameview.coffee",
+  "finder/filetree/itemsubviews/setpermissionsview.coffee",
+  "finder/filetree/itemsubviews/vmtogglebuttonview.coffee",
+  "finder/filetree/itemsubviews/mounttogglebuttonview.coffee",
+  "finder/filetree/itemsubviews/copyurlview.coffee",
+
+  "finder/fs/fshelper.coffee",
+  "finder/fs/fswatcher.coffee",
+  "finder/fs/fsitem.coffee",
+  "finder/fs/fsfile.coffee",
+  "finder/fs/fsfolder.coffee",
+  "finder/fs/fsmount.coffee",
+  "finder/fs/fsbrokenlink.coffee",
+  "finder/fs/fsvm.coffee",
+  "finder/fs/appswatcher.coffee",
+
+  "finder/styl/resurrection.finder.styl"
+
+
   # ide
   "views/tabview/idefilestabview.coffee"
   "views/tabview/ideview.coffee"


fix dynamic field bug for bdc title

fix dynamic field bug for bdc title

--- src/sprint/directives/sprint-widget/controller.coffee
+++ src/sprint/directives/sprint-widget/controller.coffee
@@ -10,7 +10,9 @@
   Project
   Sprint
 ) ->
-  dynamicFields.ready $scope.sprint, $scope.project
+  dynamicFieldsPromise = dynamicFields.ready $scope.sprint, $scope.project
+
+  dynamicFieldsPromise
   .then (builtDict) ->
     $scope.bdcTitle = dynamicFields.render $scope.project?.settings?.bdcTitle, builtDict

@@ -24,10 +26,9 @@
         availableFields: -> dynamicFields.getAvailableFields()
     .then (title) ->
       Project.saveTitle($scope.project, title)
-      .then (title) ->
-        dynamicFields.render title
-      .then (title) ->
-        $scope.bdcTitle = title
+      dynamicFieldsPromise
+      .then (builtDict) ->
+        $scope.bdcTitle = dynamicFields.render title, builtDict

   DialogController = ($scope, $mdDialog, title, availableFields) ->
     $scope.title = title


update calls

update calls

--- js/src/auth/SearchKeySerializer.coffee
+++ js/src/auth/SearchKeySerializer.coffee
@@ -18,9 +18,9 @@
   log = Logger.get('SearchKeySerializer')

   {
-    chunkUint
+    chunkUint8
     cleanUint8Buffer
-    joinUint
+    joinUint8
     stringToUint16
     uint16ToString
     uint16ToUint8
@@ -79,7 +79,7 @@
         .map((chunk) -> stringToUint16(chunk))
         .map((chunk) -> uint16ToUint8(chunk))
         .tap((chunks) -> validateEncryptedChunks(chunks))
-        .thru((chunks) -> joinUint(chunks))
+        .thru((chunks) -> joinUint8(chunks))
         .value()

       return encryptedUint
@@ -89,7 +89,7 @@
       rsaCryptoService = @createRsaCryptoService()

       decryptedUint = _.chain(uint8)
-        .thru((uint8) -> chunkUint(uint8, ENCRYPTED_PADDED_BLOCK_LENGTH))
+        .thru((uint8) -> chunkUint8(uint8, ENCRYPTED_PADDED_BLOCK_LENGTH))
         .tap((chunks) -> validateEncryptedChunks(chunks))
         .map((chunk) -> uint8ToUint16(chunk))
         .map((chunk) -> uint16ToString(chunk))


Travel panel, various small tweaks

Travel panel, various small tweaks

--- src/javascript/main.coffee
+++ src/javascript/main.coffee
@@ -1,3 +1,11 @@
+
+
+# Ideas
+# * Damage represented by ship icon (relative EHP)
+#   * Silhoette icon, standard EHP on a PVP fit
+# * Stat per minute (any stat divided by active minutes)
+
+
 React = require 'react'
 $ = require 'jquery'
 CharacterStats = require './character_stats'
@@ -7,6 +15,18 @@
 dom = React.DOM

 source_json = './dscan.json'
+
+samples = [
+  'dscan.json'
+  'explorer.json'
+  'industry.json'
+  'lowsec.json'
+  'market.json'
+  'miner.json'
+  'missions.json'
+  'random.json'
+]
+

 # sources of sampled colors
 #   * subcap weapons: http://i.imgur.com/c08RJ.jpg
@@ -43,6 +63,18 @@
     label: 'Hull'
     color: 'white'
     iconId: 3663
+  high:
+    label: 'High Sec'
+    color: '#11ee11'
+  low:
+    label: 'Low Sec'
+    color: '#dfdf00'
+  null:
+    label: 'Null Sec'
+    color: '#df0000'
+  wormhole:
+    label: 'Wormholes'
+    color: '#0000df'

 HP_BAR_ORDER = ['shield','armor','hull']

@@ -55,9 +87,10 @@
         name: 'Bellatroix'
         id: 1412571394
       year: 2014
+      source: './explorer.json'
     }
   componentDidMount: ->
-    $.get source_json, (data) =>
+    $.get @state.source, (data) =>
       stats = new CharacterStats(data)
       @setState {stats: stats}
   render: ->
@@ -76,12 +109,14 @@
     charInfoPanel = React.createElement(CharacterInfoPanel, {character: @state.character})
     weaponUsagePanel = React.createElement(WeaponUsagePanel, {stats: @state.stats})
     damageTakenPanel = React.createElement(DamageTakenPanel, {stats: @state.stats})
+    travelPanel = React.createElement(TravelPanel, {stats: @state.stats})
     selfRepPanel = React.createElement(SelfRepPanel, {stats: @state.stats})
     repsReceivedPanel = React.createElement(RepsReceivedPanel, {stats: @state.stats})
     repsGivenPanel = React.createElement(RepsGivenPanel, {stats: @state.stats})
     rawStatsList = React.createElement(RawStatsList, {stats: @state.stats})
     dom.div {className: 'container'},
       charInfoPanel
+      travelPanel
       weaponUsagePanel
       damageTakenPanel
       selfRepPanel
@@ -239,6 +274,34 @@
     dom.div {className: 'container'},
       dom.h3 null, 'Reps Received'
         React.createElement(PieDataPanel,{data: @chartData(), chartType: 'shipHp'})
+)
+
+TravelPanel = React.createClass(
+  displayName: 'TravelPanel'
+  chartData: ->
+    data = [
+      {
+        key: 'high'
+        value: @props.stats?.travelJumpsStargateHighSec
+      }
+      {
+        key: 'low'
+        value: @props.stats?.travelJumpsStargateLowSec
+      }
+      {
+        key: 'null'
+        value: @props.stats?.travelJumpsStargateNullSec
+      }
+      {
+        key: 'wormhole'
+        value: @props.stats?.travelJumpsWormhole
+      }
+    ]
+    return data
+  render: ->
+    dom.div {className: 'container'},
+      dom.h3 null, 'Stargate / Wormhole Jumps'
+        React.createElement(PieDataPanel,{data: @chartData(), chartType: 'pie'})
 )

 PieDataPanel = React.createClass(
@@ -286,8 +349,8 @@
       return dom.tr {key: d.key},
         iconCell
         dom.td null, STYLES[d.key]?.label
-        dom.td null, d.value
-        dom.td null, d3.round(100*d.percentOfTotal,2)+'%'
+        dom.td null, Intl.NumberFormat().format(d.value)
+        dom.td null, d3.round(100*d.percentOfTotal,0)+'%'
     dom.table {className: 'table'},
       dom.tbody null,
         rows
@@ -312,7 +375,7 @@

     pie = d3.layout.pie()
             .value (d) -> d.value
-            .padAngle Math.PI / 100
+            .padAngle Math.PI / 200

     data = pie(@props.data)


Re-added SSO using implicit grant

Re-added SSO using implicit grant

This method doesn't need a secret key but is overall less secure.  It it intended for this
use case, however - a completely client side application.

--- src/javascript/main.coffee
+++ src/javascript/main.coffee
@@ -251,30 +251,27 @@
       year: 2014
     }
   componentDidMount: ->
-    #search = window.location.search.substring(1)
-    #queryParts = qs.parse(search)
-    #if queryParts.code
-    #  @setState {ssoState: 'loading', ssoCode: queryParts.code}
-    #  opts =
-    #    url: "#{SSO_HOST}/oauth/token"
-    #    method: 'POST'
-    #    auth:
-    #      user: SSO_CLIENT_ID
-    #      pass: SSO_SECRET_KEY
-    #    form:
-    #      grant_type: 'authorization_code'
-    #      code: queryParts.code
-    #  request opts, (err, res, body) =>
-    #    console.log err
-    #    console.log body
-    @setState {ssoState: 'loaded'}
-    @loadData()
+    hash = window.location.hash.substring(1)
+    queryParts = qs.parse(hash)
+    if queryParts.access_token
+      @setState {ssoState: 'loading', ssoCode: queryParts.code}
+      #opts =
+      #  url: "#{SSO_HOST}/oauth/token"
+      #  method: 'POST'
+      #  auth:
+      #    user: SSO_CLIENT_ID
+      #    pass: SSO_SECRET_KEY
+      #  form:
+      #    grant_type: 'authorization_code'
+      #    code: queryParts.code
+      #request opts, (err, res, body) =>
+      #  console.log err
+      #  console.log body
+      @setState {ssoState: 'loaded'}
+      @loadData()
   loadData: ->
-    if window.location.hash
-      source = "#{window.location.hash.substring(1)}"
-    else
-      source = "null_pvp"
-      source = _.sample samples
+    source = "null_pvp"
+    source = _.sample samples
     host = window.location.host
     proto = window.location.protocol
     opts =
@@ -289,7 +286,7 @@
     if @state.ssoState == 'login'
       ssoLoginImage = 'https://images.contentful.com/idjq7aai9ylm/4PTzeiAshqiM8osU2giO0Y/5cc4cb60bac52422da2e45db87b6819c/EVE_SSO_Login_Buttons_Large_White.png?w=270&h=45'
       ssoParams =
-        response_type: 'code'
+        response_type: 'token'
         redirect_uri: SSO_CALLBACK_URL
         client_id: SSO_CLIENT_ID
         scope: ''


remove loading animation after theme is ready

--- src/javascripts/app.coffee
+++ src/javascripts/app.coffee
@@ -52,6 +52,7 @@
     @theme.loaded.done =>
       @elem = @theme.render()
       window.setTimeout @sendSizeChange, 0
+      $('.loading-animation').remove()
       rendered.resolve()

     rendered.promise()


Drop CoffeeScript operators

Drop CoffeeScript operators

--- src/lib/byte-buffer.coffee
+++ src/lib/byte-buffer.coffee
@@ -71,7 +71,7 @@

       # Although Uint8Array's constructor succeeds when given strings,
       # it does not correctly instantiate the buffer
-      return null if source.constructor is String
+      return null if source.constructor == String

       try
         return (new Uint8Array(source)).buffer
@@ -132,7 +132,7 @@

   # Sets read/write index
   set index: (index) ->
-    if index < 0 or index > @length
+    if index < 0 || index > @length
       throw new RangeError('Invalid index ' + index + ', should be between 0 and ' + @length)

     @_index = index
@@ -382,7 +382,7 @@

       else
         # Four byte sequence
-        if i is length - 1
+        if i == length - 1
           throw new Error('Unpaired surrogate ' + string[i] + ' (index ' + i + ')')

         # Retrieve surrogate
@@ -412,7 +412,7 @@
     bytes = @_raw
     length = bytes.length
     i = @_index
-    while bytes[i] isnt 0x00 && i < length
+    while bytes[i] != 0x00 && i < length
       ++i

     length = i - @_index
@@ -482,7 +482,7 @@

   # Short string representation of this buffer
   toString: ->
-    order = if @_order is self.BIG_ENDIAN then 'big-endian' else 'little-endian'
+    order = if @_order == self.BIG_ENDIAN then 'big-endian' else 'little-endian'
     return '[ByteBuffer; Order: ' + order + '; Length: ' + @length + '; Index: ' + @_index + '; Available: ' + @available + ']'

   # Hex representation of this buffer with given spacer


socialworker: URLs are added to socialworker

--- workers/social/lib/social/models/socialapi/requests.coffee
+++ workers/social/lib/social/models/socialapi/requests.coffee
@@ -400,6 +400,22 @@
 checkOwnership = (data, callback) ->
   url = "#{socialProxyUrl}/account/#{data.accountId}/owns"
   get url, data, callback
+
+createNotificationSetting = (data, callback) ->
+  url = "#{socialProxyUrl}/channel/#{data.channelId}/notificationsetting"
+  post url, data, callback
+
+getNotificationSetting = (data, callback) ->
+  url = "#{socialProxyUrl}/channel/#{data.channelId}/notificationsetting"
+  get url, data, callback
+
+updateNotificationSetting = (data, callback) ->
+  url = "#{socialProxyUrl}/notificationsetting/#{data.id}"
+  post url, data, callback
+
+deleteNotificationSetting = (data, callback) ->
+  url = "#{socialProxyUrl}/notificationsetting/#{data.id}"
+  deleteReq url, data, callback

 expireSubscription = (accountId, callback) ->
   url = "#{socialProxyUrl}/payments/customers/#{accountId}/expire"
@@ -536,6 +552,10 @@
   updateChannel
   createChannel
   checkOwnership
+  createNotificationSetting
+  getNotificationSetting
+  updateNotificationSetting
+  deleteNotificationSetting
   expireSubscription
   fetchBotChannel
   post


config: update schema

config: update schema

--- config/config.schema.coffee
+++ config/config.schema.coffee
@@ -305,7 +305,8 @@
         apiHost     : 1
         apiPort     : 1
         port        : 1
-        switchHost  : 1
+        kodingHost  : 1
+        socialHost  : 1
       api           :
         port        : 1
         url         : 1


Switched inline editting to methods

Switched inline editting to methods

--- client/collection_view.coffee
+++ client/collection_view.coffee
@@ -66,7 +66,8 @@
       field_name = $this.data('field')
       update_dict = {}
       update_dict[field_name] = updated_val
-      get_collection().update(id, $set: update_dict)
+      Meteor.call("admin_#{Session.get('collection_name')}_update",
+        id, $set: update_dict)

   'change .column_filter': (event...) ->
     field_selectors = {}


remove auth logic from react router

remove auth logic from react router

--- app/assets/javascripts/components/app-router.cjsx
+++ app/assets/javascripts/components/app-router.cjsx
@@ -1,4 +1,4 @@
-React = require("react")
+React = require 'react'
 App         = require './app'
 Router      = require 'react-router'
 {Handler, Root, RouteHandler, Route, DefaultRoute, Redirect, Navigation, Link, Transition} = Router
@@ -9,7 +9,7 @@
 Verify        = require './verify'
 GroupPage     = require './group-page'
 GroupBrowser  = require './group-browser'
-AboutPage    = require './yale-about-page'
+AboutPage     = require './yale-about-page'
 YaleLogin     = require './yale-login'

 Project       = require 'models/project.coffee'
@@ -21,49 +21,20 @@
       @runRoutes window.project

   saveRequestedRoute: (requestedRoute) ->
-    $.post "/save_requested_route",
-      requested_route: "/#" + requestedRoute
+    $.post '/save_requested_route',
+      requested_route: '/#' + requestedRoute
       (data) ->
         response = data
         # pass

-  # function to check if the current route is restricted, and if so, to redirect
-  # users to a login route
-  checkForAuthentication: (Handler, state) ->
-    self = this
-    # check if the requested route is protected
-    if state.pathname in ["/mark", "/transcribe"]
-
-      # check if the current user is logged in
-      request = $.getJSON "/current_user"
-
-      request.done (result) =>
-        if result?.data
-          user = result.data
-          if user.guest == true
-
-            self.saveRequestedRoute(state.path)
-
-            # note: React Router v13.x is now unsupported, and github docs are not comprehensive
-            # but one can array the properties of the route handler with:
-            # console.log(Object.getOwnPropertyNames(Handler))
-            # and from there consult /node-modules/react-router/umd/ReactRouter.js to find
-            # the object method of interest
-
-            # Handler.transitionTo(routeToGoTo, {the: params}, {the: query})
-            Handler.transitionTo('/login', {})
-        else
-          self.saveRequestedRoute(state.path)
-          Handler.transitionTo('/login', {})
-
   runRoutes: (project) ->
     self = this
     routes =
-      <Route name="root" path="/" handler={App} >
+      <Route name='root' path='/' handler={App} >

-        <Redirect from="_=_" to="/" />
+        <Redirect from='_=_' to='/' />

-        <Route name="home" path="/home" handler={HomePage}/>
+        <Route name='home' path='/home' handler={HomePage}/>

         { (w for w in project.workflows when w.name in ['mark','transcribe','verify']).map (workflow, key) =>
             handler = eval workflow.name.charAt(0).toUpperCase() + workflow.name.slice(1)
@@ -137,47 +108,46 @@
           name='yale-login'
         />

-        <DefaultRoute name="home-default" handler={HomePage} />
+        <DefaultRoute name='home-default' handler={HomePage} />
       </Route>

     Router.run routes, (Handler, state) ->
-      self.checkForAuthentication(Handler, state)
       React.render <Handler />, document.body

   controllerForPage: (page) ->
     React.createClass
-      displayName: "#{page.name}Page"
+      displayName: '#{page.name}Page'

       componentDidMount: ->
         pattern = new RegExp('#/[A-z]*#(.*)')
-        selectedID = "#{window.location.hash}".match(pattern)
+        selectedID = '#{window.location.hash}'.match(pattern)

         if selectedID
-          $('.selected-content').removeClass("selected-content")
+          $('.selected-content').removeClass('selected-content')

-          $("div#" + selectedID[1]).addClass("selected-content")
-          $("a#" + selectedID[1]).addClass("selected-content")
+          $('div#' + selectedID[1]).addClass('selected-content')
+          $('a#' + selectedID[1]).addClass('selected-content')

         elms = $(React.findDOMNode(this)).find('a.about-nav')
-        elms.on "click", (e) ->
+        elms.on 'click', (e) ->
           e.preventDefault()
-          $('.selected-content').removeClass("selected-content")
-          $(this).addClass("selected-content")
+          $('.selected-content').removeClass('selected-content')
+          $(this).addClass('selected-content')

           divId = $(this).attr('href')
-          $(divId).addClass("selected-content")
+          $(divId).addClass('selected-content')

-        el = $(React.findDOMNode(this)).find("#accordion")
+        el = $(React.findDOMNode(this)).find('#accordion')
         el.accordion
           collapsible: true
           active: false
-          heightStyle: "content"
+          heightStyle: 'content'

       navToggle:(e)->

       render: ->
-        formatted_name = page.name.replace("_", " ")
-

+        formatted_name = page.name.replace('_', ' ')
+

{formatted_name}

           {
@@ -186,7 +156,7 @@
                 <GroupBrowser project={project} title={page.group_browser} />

           }
-
Last Update {page.updated_at}

+
Last Update {page.updated_at}

 module.exports = AppRouter


[#8] Clean up unnecessary code to prep for mark updates


Fix d3 version 3 to version api update.

Fix d3 version 3 to version api update.

change d3.svg.symbol() to d3.symbol() due to version 4 API change

--- app/assets/javascripts/population_chart.js.coffee
+++ app/assets/javascripts/population_chart.js.coffee
@@ -46,7 +46,7 @@
         arrowOffset = if data.lower_is_better then margin.left else margin.left + xScale(data.DENOM)
         arrow.append('path')
           .attr('class', 'arrow')
-          .attr('d', d3.svg.symbol().size(240).type('triangle-up'))
+          .attr('d', d3.symbol().size(240).type(d3.symbolTriangle))
           .attr('transform', "translate(#{arrowOffset}, #{height + 12})")
       my.update(data)


prevent dupe requests + properly load up long log lines

--- SingularityUI/app/collections/LogLines.coffee
+++ SingularityUI/app/collections/LogLines.coffee
@@ -1,8 +1,6 @@
 Collection = require './collection'

 LogLine = require '../models/LogLine'
-
-orZero = (value) => if value < 0 then 0 else value

 class LogLines extends Collection

@@ -26,6 +24,8 @@
         # there's more to fetch
         moreToFetch: undefined
         moreToFetchAtBeginning: undefined
+        pendingFetchPrev: false
+        pendingFetchNext: false

     url: => "#{ config.apiRoot }/sandbox/#{ @taskId }/read"

@@ -49,8 +49,7 @@
             data: {@path, length: @baseRequestLength}

         promise.done (response) =>
-            offset = response.offset - @baseRequestLength
-            offset = orZero offset
+            offset = Math.max(0, response.offset - @baseRequestLength)
             @ajaxError.set present: false

             request = @fetch data:
@@ -71,18 +70,29 @@
         promise

     fetchPrevious: ->
+        return if @state.get('pendingFetchPrev')
+        @state.set('pendingFetchPrev', true)
+
         @fetch(
             data:
-                offset: orZero @getMinOffset() - @baseRequestLength
+                offset: Math.max(0, @getMinOffset() - @baseRequestLength)
+                length: Math.min(@getMinOffset(), @baseRequestLength)
         ).error (error) =>
           app.caughtError() if error.status is 404
+        .done =>
+          @state.set('pendingFetchPrev', false)
+
     fetchNext: =>
+        return if @state.get('pendingFetchNext')
+        @state.set('pendingFetchNext', true)
+
         @fetch(data:
             offset: @nextOffset or @getMaxOffset()
         ).error (error) =>
           # Don't throw an error if the task ends while we're tailing
           app.caughtError() if error.status is 404
-        @nextOffset = null
+        .done =>
+            @state.set('pendingFetchNext', false)

     fetchFromStart: =>
         @fetch data:
@@ -112,24 +122,25 @@
         super

     parse: (result, options) =>
-        @nextOffset = result.nextOffset
+        @nextOffset = result.nextOffset or result.offset + result.data.length

         # bail early if no new data
         if result.data.length is 0
             return []

-        @state.set('moreToFetch', (result.data.length is options.data.length) and ((offset >= @getMaxOffset()) or @getMinOffset() is 0))
-        @state.set('moreToFetchAtBeginning', offset > 0 and @getMinOffset() > 0)
-
         # split on newlines
         lines = _.initial(result.data.match /[^\n]*(\n|$)/g)

-        # If out batch lines up with the end and the last line doesn't end with a newline, append the first line
+        @state.set('moreToFetch', (result.data.length is options.data.length) and ((result.offset >= @getMaxOffset()) or @getMinOffset() is 0) and lines.length > 1)
+        @state.set('moreToFetchAtBeginning', result.offset > 0 and @getMinOffset() > 0)
+
+        # If our batch lines up with the end and the last line doesn't end with a newline, append the first line
         if result.offset > 0 and result.offset is @getMaxOffset()
             origLine = @at(-1)
             unless origLine.get('data').endsWith '\n'
                 origLine.set
                     data: origLine.get('data') + lines[0]
+                result.offset += lines[0].length
                 lines = _.rest(lines)

         # If our batch lines up with the beginning and doesn't end with a newline, prepend the last line


:lipstick:

:lipstick:

--- src/packages/command-logger/src/command-logger.coffee
+++ src/packages/command-logger/src/command-logger.coffee
@@ -103,7 +103,7 @@
        .attr('height', (d) -> ky * d.dy - 1)

       t.select('.command-logger-node-text div')
-        .attr('style', (d) -> "height:#{ky * d.dy - 1}px;width:#{kx * d.dx - 1}px")
+       .attr('style', (d) -> "height:#{ky * d.dy - 1}px;width:#{kx * d.dx - 1}px")

       node = d
       d3.event.stopPropagation()


restore excerpt update after saving

restore excerpt update after saving

--- app/assets/volume/slot.coffee
+++ app/assets/volume/slot.coffee
@@ -367,6 +367,7 @@
               @excerpts.remove(excerpt)
               if 'excerpt' of excerpt
                 @excerpts.push(excerpt)
+              @updateExcerpt()
             , (res) =>
               messages.addError
                 type: 'red'


Fixes condition for adding message to channel

Signed-off-by: Akash Manohar J <e4cf979cf717fd295f4e4edc427ee1aee78fa7dc@akash.im>

--- app/assets/javascripts/backbone/helpers/channels.js.coffee
+++ app/assets/javascripts/backbone/helpers/channels.js.coffee
@@ -117,11 +117,11 @@

   @addMessage: (activityAttributes, state, local)->
     belongsToCurrentUser = ( activityAttributes.user.id == Kandan.Data.Users.currentUser().id )
-    activityExists       = ( $("#activity-#{activityAttributes.id}").length == 0 )
+    activityExists       = ( $("#activity-#{activityAttributes.id}").length > 0 )
     local = local || false
+    console.log !local, !belongsToCurrentUser, !activityExists

-    console.log local, !belongsToCurrentUser, !activityExists
-    if local || (!belongsToCurrentUser || !activityExists)
+    if local || (!local && !belongsToCurrentUser && !activityExists)
       @channelActivitiesEl(activityAttributes.channel_id)
         .append(@newActivityView(activityAttributes).render().el)


Improve $.getCachedScript syntax

Improve $.getCachedScript syntax

--- app/assets/javascripts/evrobone/jquery-additions.js.coffee
+++ app/assets/javascripts/evrobone/jquery-additions.js.coffee
@@ -79,11 +79,13 @@
 #    @css 'display', 'none'
 #    @

-  $.getCachedScript = (url, callback) ->
+  $.getCachedScript = (url, success, error) ->
+    {url, success, error} = url if $.isPlainObject(url)
     $.ajax
       type: 'GET'
       url: url
-      success: callback
+      success: success
+      success: error
       dataType: 'script'
       cache: true
 # coffeelint: enable=cyclomatic_complexity


bump up pagination for feature flags to 20 instead of 10

It seems like eventually we'll want to update the front-end backbone code to
do proper pagination on this, but we can certainly handle 20 feature flags
before doing this.

fixes CNVS-13461

test plan:
  - as an admin
  - visit the account settings page
  - click on the feature flags tab
  - all 11 feature flags should show up including draft state at the bottom
  - try turning the feature flags on/off to make sure things still work fine

Change-Id: Ifc7c31a48159f77d63a846c472567ae88f913f76
Reviewed-on: https://gerrit.instructure.com/35967
Reviewed-by: Jason Madsen <bcb5fff571a20f07c35d17ba9c364855c776dce7@instructure.com>
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
QA-Review: Trevor deHaan <85df72112e8a9484fec7b3db2e31789c2c2d4e07@instructure.com>
Product-Review: Derek DeVries <ff87a0776152510efd53bb1f4ea089dbbc90e730@instructure.com>

--- app/coffeescripts/collections/FeatureFlagCollection.coffee
+++ app/coffeescripts/collections/FeatureFlagCollection.coffee
@@ -7,3 +7,7 @@
   class FeatureFlagCollection extends Backbone.Collection

     model: FeatureFlag
+
+    fetch: (options = {}) ->
+      options.data = _.extend per_page: 20, options.data || {}
+      super options


make cookies configurable via env vars

make cookies configurable via env vars

--- services/real-time/config/settings.defaults.coffee
+++ services/real-time/config/settings.defaults.coffee
@@ -36,8 +36,8 @@
 			url: "http://#{process.env['DOCUMENT_UPDATER_HOST'] or process.env['DOCUPDATER_HOST'] or "localhost"}:3003"

 	security:
-		sessionSecret: "secret-please-change"
+		sessionSecret: process.env['SESSION_SECRET'] or "secret-please-change"

-	cookieName: "sharelatex.sid"
+	cookieName: process.env['COOKIE_NAME'] or "sharelatex.sid"

 	max_doc_length: 2 * 1024 * 1024 # 2mb


Fix for unnecessary origin pixel anchoring.

--- packages/landsofillusions-assets/documents/mesh/object/solver/polyhedron/polyhedron-computeclusterplanes.coffee
+++ packages/landsofillusions-assets/documents/mesh/object/solver/polyhedron/polyhedron-computeclusterplanes.coffee
@@ -60,18 +60,20 @@

   edge.startLinePointRecomputation() for edge in edges

-  # See if we have a cluster overlapping the camera target.
-  origin = cameraAngle.unprojectPoint cameraAngle.target
-  originCluster = _.find clusters, (cluster) => cluster.findPixelAtAbsoluteCoordinate origin.x, origin.y
+  # If no clusters were fixed through manual points or attachments,
+  # see if we have a cluster overlapping the camera target.
+  if clustersLeftCount is clusters.length
+    origin = cameraAngle.unprojectPoint cameraAngle.target
+    originCluster = _.find clusters, (cluster) => cluster.findPixelAtAbsoluteCoordinate origin.x, origin.y

-  # Otherwise look if a cluster is at the (0, 0) pixel.
-  originCluster ?= _.find clusters, (cluster) => cluster.findPixelAtAbsoluteCoordinate 0, 0
+    # Otherwise look if a cluster is at the (0, 0) pixel.
+    originCluster ?= _.find clusters, (cluster) => cluster.findPixelAtAbsoluteCoordinate 0, 0

-  if originCluster and not originCluster.plane.point
-    # Use the origin cluster as the base to calculate other clusters from.
-    console.log "Setting cluster #{originCluster.id} to origin." if LOI.Assets.Mesh.Object.Solver.Polyhedron.debug
-    originCluster.setPlanePoint new THREE.Vector3
-    clustersLeftCount--
+    if originCluster and not originCluster.plane.point
+      # Use the origin cluster as the base to calculate other clusters from.
+      console.log "Setting cluster #{originCluster.id} to origin." if LOI.Assets.Mesh.Object.Solver.Polyhedron.debug
+      originCluster.setPlanePoint new THREE.Vector3
+      clustersLeftCount--

   sortedEdges = _.reverse _.sortBy edges, (edge) => edge.segments.length


Remove unused JS

Remove unused JS

--- source/assets/javascripts/directives/slides.coffee
+++ source/assets/javascripts/directives/slides.coffee
@@ -20,7 +20,6 @@
       @updateProgress()
       if slide == $scope.slides[$scope.currentSlide - 1]
         @updateProgress()
-        #$timeout -> slide.addClass('is-active')
         $scope.activeSlide = !$scope.activeSlide
       $scope.slides.indexOf(slide) + 1


Fix selector in keymap

Fix selector in keymap

--- keymaps/toggle-quotes.cson
+++ keymaps/toggle-quotes.cson
@@ -1,2 +1,2 @@
-'workspace':
+'.workspace':
   'ctrl-"': 'toggle-quotes:toggle'


fixing typo, caused by web editor

fixing typo, caused by web editor

--- app/locale/cs.coffee
+++ app/locale/cs.coffee
@@ -1,4 +1,4 @@
- module.exports = nativeDescription: "čeština", englishDescription: "Czech", translation:
+module.exports = nativeDescription: "čeština", englishDescription: "Czech", translation:
   common:
     loading: "Načítání..."
     saving: "Ukládání..."


Update es.coffee

Update es.coffee

--- app/locale/es.coffee
+++ app/locale/es.coffee
@@ -3,7 +3,7 @@
     loading: "Cargando..."
     saving: "Guardando..."
     sending: "Enviando..."
-     send: "Enviar..."
+    send: "Enviar..."
     cancel: "Cancelar"
     save: "Guardar"
 #    publish: "Publish"
@@ -21,7 +21,7 @@

 #  units:
     second: "Segundo"
-   seconds: "Segundos"
+    seconds: "Segundos"
 #    minute: "minute"
 #    minutes: "minutes"
 #    hour: "hour"


Update fr.coffee

Update fr.coffee

--- app/locale/fr.coffee
+++ app/locale/fr.coffee
@@ -617,7 +617,7 @@
     monitor_progress_2: "pour votre classe"
     monitor_progress_3: "Pour ajouter un étudiant, envoyer leur le lien contenant une invitation pour votre Clan"
     monitor_progress_4: "page."
-    monitor_progress_5: "After they join, you will see a summary of the student's progress on your Clan's page."
+    monitor_progress_5: "Après leur adhésion, vous verrez un résumé des progrès de l'élève sur la page de votre clan."
     private_clans_1: "Private Clans provide increased privacy and detailed progress information for each student."
     private_clans_2: "Pour créer un Clan privé, veuillez vous référer à la boîte à cocher 'Faire un clan privé' pendant la création du"
     private_clans_3: "."


Update it.coffee (#4102)

Update it.coffee (#4102)

* Update it.coffee

* Update it.coffee

* Update it.coffee

* Update it.coffee

* Update it.coffee

* Update it.coffee

* Update it.coffee

--- app/locale/it.coffee
+++ app/locale/it.coffee
@@ -16,15 +16,15 @@
     designed_with: "Sviluppato con gli insegnanti in mente"
     real_code: "Vero, codice scritto"
     from_the_first_level: "dal primo livello"
-#    getting_students: "Getting students to typed code as quickly as possible is critical to learning programming syntax and proper structure."
+    getting_students: "Portare gli studenti a programmare il più velocemente possibile è critico per imparare la sintassi e le strutture adatte."
     educator_resources: "Risorse Educatore"
     course_guides: "e guide ai corsi"
     teaching_computer_science: "Insegnare l'informatica non richiede aver conseguito una laurea costosa, perchè diamo gli strumenti di supporto agli insegnanti che hanno avuto ogni tipo di formazione."
     accessible_to: "Accessibile a"
     everyone: "tutti"
-#    democratizing: "Democratizing the process of learning coding is at the core of our philosophy. Everyone should be able to learn to code."
-#    forgot_learning: "I think they actually forgot that they were actually learning something."
-#    wanted_to_do: " Coding is something I've always wanted to do, and I never thought I would be able to learn it in school."
+    democratizing: "Democratizzare il processo di apprendimento della programmazione è il cuore della nostra filosofia. Tutti dovrebbero essere capaci di imparare a scrivere codice."
+    forgot_learning: "Penso che abbiano dimenticato che stiano imparando qualcosa."
+    wanted_to_do: " Programmare è qualcosa che ho sempre voluto fare, e non pensavo fosse possibile per me insegnarlo a scuola."
     why_games: "Perchè imparare attraverso il gioco è importante?"
 #    games_reward: "Games reward the productive struggle."
 #    encourage: "Gaming is a medium that encourages interaction, discovery, and trial-and-error. A good game challenges the player to master skills over time, which is the same critical process students go through as they learn."
@@ -51,11 +51,11 @@
     have_an_account: "Hai un account?"
     logged_in_as: "Sei connesso come"
     computer_science: "Corsi di informatica per tutte le età"
-#    show_me_lesson_time: "Show me lesson time estimates for:"
-#    curriculum: "Total curriculum hours:"
+    show_me_lesson_time: "Mostrami il tempo della lezione stimata per:"
+    curriculum: "Ore curriculum totali:"
     ffa: "Gratis per tutti gli studenti"
     lesson_time: "Tempo lezione:"
-#    coming_soon: "More coming soon!"
+    coming_soon: "Di più in arrivo!"
 #    courses_available_in: "Courses are available in JavaScript and Python. Web Development courses utilize HTML, CSS, jQuery, and Bootstrap."
 #    boast: "Boasts riddles that are complex enough to fascinate gamers and coders alike."
 #    winning: "A winning combination of RPG gameplay and programming homework that pulls off making kid-friendly education legitimately enjoyable."
@@ -267,24 +267,24 @@
 #    individual_type: "For players learning to code outside of a class. Parents should sign up for an account here."
 #    signup_as_individual: "Sign up as an Individual"
 #    enter_class_code: "Enter your Class Code"
-#    enter_birthdate: "Enter your birthdate:"
-#    parent_use_birthdate: "Parents, use your own birthdate."
+    enter_birthdate: "Inserisci la data di nascita:"
+    parent_use_birthdate: "Genitori, usate la vostra data di nascita."
 #    ask_teacher_1: "Ask your teacher for your Class Code."
 #    ask_teacher_2: "Not part of a class? Create an "
     ask_teacher_3: "Account Individuale"
     ask_teacher_4: " invece."
 #    about_to_join: "You're about to join:"
-#    enter_parent_email: "Enter your parent’s email address:"
+    enter_parent_email: "Inserisci l'indirizzo email di un genitore:"
 #    parent_email_error: "Something went wrong when trying to send the email. Check the email address and try again."
 #    parent_email_sent: "We’ve sent an email with further instructions on how to create an account. Ask your parent to check their inbox."
-#    account_created: "Account Created!"
+    account_created: "Account Creato!"
 #    confirm_student_blurb: "Write down your information so that you don't forget it. Your teacher can also help you reset your password at any time."
 #    confirm_individual_blurb: "Write down your login information in case you need it later. Verify your email so you can recover your account if you ever forget your password - check your inbox!"
-#    write_this_down: "Write this down:"
-#    start_playing: "Start Playing!"
-#    sso_connected: "Successfully connected with:"
-#    select_your_starting_hero: "Select Your Starting Hero:"
-#    you_can_always_change_your_hero_later: "You can always change your hero later."
+    write_this_down: "Prendi nota di questo:"
+    start_playing: "Comincia a Giocare!"
+    sso_connected: "Connesso correttamente con:"
+    select_your_starting_hero: "Seleziona il tuo Eroe per cominciare:"
+    you_can_always_change_your_hero_later: "Puoi sempre cambiare il tuo eroe successivamente."

   recover:
     recover_account_title: "Recupera account"
@@ -851,8 +851,8 @@
 #    previous: "Previous"
 #    location_title: "We're located in downtown SF:"

-#  teachers:
-#    licenses_needed: "Licenses needed"
+  teachers:
+    licenses_needed: "Licenze necessarie"

 #  special_offer:
 #    special_offer: "Special Offer"
@@ -1150,7 +1150,7 @@
     questions: "Domande?"
     enroll: "Iscrivi"
     enter: "Entra"
-#    enroll_paid: "Enroll Students in Paid Courses"
+    enroll_paid: "Studenti iscritti nei Corsi a pagamento"
     get_enrollments: "Ottieni più licenze"
     change_language: "Cambia la Lingua del Corso"
 #    keep_using: "Keep Using"
@@ -1210,28 +1210,28 @@
 #    subtitle: "Review course overviews and levels" # Flat style redesign
 #    changelog: "View latest changes to course levels."
 #    select_language: "Select language"
-#    select_level: "Select level"
-#    play_level: "Play Level"
+    select_level: "Seleziona livello"
+    play_level: "Gioca Livello"
     concepts_covered: "Concetti Approfonditi"
 #    view_guide_online: "Level Overviews and Solutions"
 #    grants_lifetime_access: "Grants access to all Courses."
 #    enrollment_credits_available: "Licenses Available:"
-#    language_select: "Select a language" # ClassroomSettingsModal
-#    language_cannot_change: "Language cannot be changed once students join a class."
+    language_select: "Seleziona una lingua" # ClassroomSettingsModal
+    language_cannot_change: "La lingua non può essere cambiata una volta che uno studente entra in una classe."
 #    avg_student_exp_label: "Average Student Programming Experience"
 #    avg_student_exp_desc: "This will help us understand how to pace courses better."
-#    avg_student_exp_select: "Select the best option"
+    avg_student_exp_select: "Seleziona la migliore opzione"
 #    avg_student_exp_none: "No Experience - little to no experience"
 #    avg_student_exp_beginner: "Beginner - some exposure or block-based"
 #    avg_student_exp_intermediate: "Intermediate - some experience with typed code"
 #    avg_student_exp_advanced: "Advanced - extensive experience with typed code"
 #    avg_student_exp_varied: "Varied Levels of Experience"
-#    student_age_range_label: "Student Age Range"
-#    student_age_range_younger: "Younger than 6"
-#    student_age_range_older: "Older than 18"
-#    student_age_range_to: "to"
+    student_age_range_label: "Intervallo Età Studente"
+    student_age_range_younger: "Meno di 6 anni"
+    student_age_range_older: "Più di 18 anni"
+    student_age_range_to: "a"
     create_class: "Crea Classe"
-#    class_name: "Class Name"
+    class_name: "Nome Classe"
 #    teacher_account_restricted: "Your account is a teacher account and cannot access student content."
 #    account_restricted: "A student account is required to access this page."
 #    update_account_login_title: "Log in to update your account"
@@ -1539,14 +1539,14 @@
     ambassador_title: "Ambasciatore"
     ambassador_title_description: "(Supporto)"
     ambassador_summary: "Guida gli utenti del forum e dai consigli a coloro che hanno delle domande. I nostri Ambasciatori rappresentano CodeCombat nel mondo."
-#    teacher_title: "Teacher"
+    teacher_title: "Insegnante"

   editor:
     main_title: "Editor di CodeCombat"
     article_title: "Modifica articolo"
-    thang_title: "Modifica thang"
-    level_title: "Modifica livello"
-#    course_title: "Course Editor"
+    thang_title: "Modifica Thang"
+    level_title: "Modifica Livello"
+    course_title: "Modifica Corso"
     achievement_title: "Editor imprese"
     poll_title: "Editor di sondaggi"
     back: "Indietro"
@@ -1650,7 +1650,7 @@
     join_desc_3: ", o vieni a trovarci nel nostro "
     join_desc_4: "e cominceremo da lì a collaborare!"
     join_url_email: "Scrivici"
-#    join_url_slack: "public Slack channel"
+    join_url_slack: "canale Slack pubblico"
     archmage_subscribe_desc: "Ricevi le email sulle nuove opportunità di sviluppo e sugli ultimi aggiornamenti."
     artisan_introduction_pref: "Dobbiamo costruire nuovi livelli! La gente chide sempre nuovi contenuti, e non possiamo costruirne così tanti e così tanto velocemente da soli. Per ora la tua workstation è al livello uno; il nostro editor di livelli è a malapena usabile persino dai suoi creatori, quindi fai attenzione. Se hai idee per campanie che richiedano cicli for per"
     artisan_introduction_suf: ", allora beh, questo corso potrebbe fare per te."
@@ -1694,7 +1694,7 @@
     ambassador_join_note_strong: "Nota"
     ambassador_join_note_desc: "Una delle nostre prirità maggiori è costruire la parte multiplayer in cui i giocatori che stanno avendo difficoltà a completare un livello possano chiedere aiuto ad altri maghi del software. Questo sarebbe un buon modo per un ambasciatore per fare il proprio lavoro. Ti faremo sapere!"
     ambassador_subscribe_desc: "Ricevere email di aggiornamento sul supporto e sullo sviluppo del multiplayer."
-#    teacher_subscribe_desc: "Get emails on updates and announcements for teachers."
+    teacher_subscribe_desc: "Ricevere email su aggiornamenti e annunci per insegnanti."
     changes_auto_save: "Le modifiche vengono salvate automaticamente quando si segnano le caselle."
     diligent_scribes: "I nostri diligenti scribi:"
     powerful_archmages: "I nostri potenti arcimaghi:"
@@ -1970,12 +1970,12 @@
     math_trigonometry: "Trigonometria"
     object_literals: "Valori Letterali"
     parameters: "Parametri"
-#    property_access: "Accessing Properties"
-#    property_assignment: "Assigning Properties"
+    property_access: "Accesso alle Proprietà"
+    property_assignment: "Assegnamento Proprietà"
     queues: "Strutture Dati - Code"
     reading_docs: "Leggendo la Documentazione"
     recursion: "Ricorsione"
-#    return_statements: "Return Statements"
+    return_statements: "Istruzioni di ritorno"
     stacks: "Strutture Dati - Stacks"
     strings: "Stringhe"
     strings_concatenation: "Concatenazione di Stringhe"


Translated share_progress_modal

Translated share_progress_modal

Kept all the old strings for future reference.

--- app/locale/nb.coffee
+++ app/locale/nb.coffee
@@ -94,15 +94,15 @@
     campaign_classic_algorithms: "Klassiske Algoritmer"
     campaign_classic_algorithms_description: "... hvor du lærer noen av de mest populære algoritmene innen programmering"

-#  share_progress_modal:
-#    blurb: "You’re making great progress!  Tell someone how much you've learned with CodeCombat."
-#    email_invalid: "Email address invalid."
-#    form_blurb: "Enter their email below and we’ll show them!"
-#    form_label: "Email Address"
-#    placeholder: "email address"
-#    title: "Excellent Work, Apprentice"
-#    tell_friend: "Tell your Friend"
-#    tell_parent: "Tell your Parent"
+  share_progress_modal:
+    blurb: "Du gjør fantastisk fremgang! Fortell noen hvor mye du har lært gjennom CodeCombat." #"You’re making great progress!  Tell someone how much you've learned with CodeCombat."
+    email_invalid:"E-postadressen er ugjyldig." #"Email address invalid."
+    form_blurb: "Skriv ned e-postadressen deres nedenfor, så skal vi vise dem!" #Enter their email below and we’ll show them!"
+    form_label: "E-postadresse" #"Email Address"
+    placeholder: "Epost-adresse" #"email address"
+    title: "Godt jobbet, lærling" #"Excellent Work, Apprentice"
+    tell_friend: "Fortell din venn" #"Tell your Friend"
+    tell_parent: "Fortell din forelder" #"Tell your Parent"

   login:
     sign_up: "Lag konto"


fix route and active student name when switching

--- src/components/learning-guide/teacher-student.cjsx
+++ src/components/learning-guide/teacher-student.cjsx
@@ -34,10 +34,11 @@
     {courseId} = @props
     LearningGuide.TeacherStudent.actions.load(courseId, {roleId})
     @setState({roleId})
+    @context.router.transitionTo('viewStudentTeacherGuide', {courseId, roleId})

   renderHeading: ->
     students = PerformanceStore.getAllStudents(@props.courseId)
-    selected = PerformanceStore.getStudent(@props.courseId, @props.roleId)
+    selected = PerformanceStore.getStudent(@props.courseId, @state.roleId)
     return null unless selected
     name = <Name {...selected} />
     <div className='guide-heading'>


Update ro.coffee

Update ro.coffee

--- app/locale/ro.coffee
+++ app/locale/ro.coffee
@@ -66,12 +66,12 @@
     no_ie: "CodeCombat nu merge pe Internet Explorer 9 sau mai vechi. Scuze!"
     no_mobile: "CodeCombat nu a fost proiectat pentru dispozitive mobile si s-ar putea sa nu meargă!"
     play: "Joacă"
-#    old_browser: "Uh oh, your browser is too old to run CodeCombat. Sorry!"
-#    old_browser_suffix: "You can try anyway, but it probably won't work."
-#    campaign: "Campaign"
-#    for_beginners: "For Beginners"
-#    multiplayer: "Multiplayer"
-#    for_developers: "For Developers"
+    old_browser: "Mda , browser-ul tău este prea vechi pentru CodeCombat. Scuze!"
+    old_browser_suffix: "Poți să încerci oricum ,dar probabil nu o să meargă."
+    campaign: "Campanie"
+    for_beginners: "Pentru Începători"
+    multiplayer: "Multiplayer"
+    for_developers: "Pentru dezvoltatori"

   play:
     choose_your_level: "Alege nivelul"
@@ -88,7 +88,7 @@
     campaign_player_created_description: "... în care ai ocazia să testezi creativitatea colegilor tai Artisan Wizards."
     level_difficulty: "Dificultate: "
     play_as: "Alege-ți echipa"
-#    spectate: "Spectate"
+    spectate: "Spectator"

   contact:
     contact_us: "Contact CodeCombat"
@@ -214,31 +214,31 @@
     hud_continue: "Continuă (apasă shift-space)"
     spell_saved: "Vrajă salvată"
     skip_tutorial: "Sari peste (esc)"
-#    editor_config: "Editor Config"
-#    editor_config_title: "Editor Configuration"
-#    editor_config_keybindings_label: "Key Bindings"
-#    editor_config_keybindings_default: "Default (Ace)"
-#    editor_config_keybindings_description: "Adds additional shortcuts known from the common editors."
-#    editor_config_invisibles_label: "Show Invisibles"
-#    editor_config_invisibles_description: "Displays invisibles such as spaces or tabs."
-#    editor_config_indentguides_label: "Show Indent Guides"
-#    editor_config_indentguides_description: "Displays vertical lines to see indentation better."
-#    editor_config_behaviors_label: "Smart Behaviors"
-#    editor_config_behaviors_description: "Autocompletes brackets, braces, and quotes."
-#    loading_ready: "Ready!"
-#    tip_insert_positions: "Shift+Click a point on the map to insert it into the spell editor."
-#    tip_toggle_play: "Toggle play/paused with Ctrl+P."
-#    tip_scrub_shortcut: "Ctrl+[ and Ctrl+] rewind and fast-forward."
-#    tip_guide_exists: "Click the guide at the top of the page for useful info."
-#    tip_open_source: "CodeCombat is 100% open source!"
-#    tip_beta_launch: "CodeCombat launched its beta in October, 2013."
-#    tip_js_beginning: "JavaScript is just the beginning."
-#    tip_autocast_setting: "Adjust autocast settings by clicking the gear on the cast button."
-#    tip_baby_coders: "In the future, even babies will be Archmages."
-#    tip_morale_improves: "Loading will continue until morale improves."
-#    tip_all_species: "We believe in equal opportunities to learn programming for all species."
-#    tip_reticulating: "Reticulating spines."
-#    tip_harry: "Yer a Wizard, "
+    editor_config: "Editor Config"
+    editor_config_title: "Configurare Editor"
+    editor_config_keybindings_label: "Mapare taste"
+    editor_config_keybindings_default: "Default (Ace)"
+    editor_config_keybindings_description: "Adaugă comenzi rapide suplimentare cunoscute din editoarele obisnuite." # not sure, where is this on the site?
+    editor_config_invisibles_label: "Arată etichetele invizibile"
+    editor_config_invisibles_description: "Arată spațiile și taburile invizibile."
+    editor_config_indentguides_label: "Arată ghidul de indentare"
+    editor_config_indentguides_description: "Arată linii verticale pentru a vedea mai bine indentarea."
+    editor_config_behaviors_label: "Comportamente inteligente"  # context?
+    editor_config_behaviors_description: "Completează automat parantezele, ghilimele etc."
+    loading_ready: "Gata!"
+    tip_insert_positions: "Shift+Click oriunde pe harta pentru a insera punctul în editorul de vrăji."
+    tip_toggle_play: "Pune sau scoate pauza cu Ctrl+P."
+    tip_scrub_shortcut: "Înapoi și derulare rapidă cu Ctrl+[ and Ctrl+]."
+    tip_guide_exists: "Apasă pe ghidul din partea de sus a pagini pentru informații utile."
+    tip_open_source: "CodeCombat este 100% open source!"
+    tip_beta_launch: "CodeCombat a fost lansat beta in Octombrie 2013."
+    tip_js_beginning: "JavaScript este doar începutul."
+    tip_autocast_setting: "Ajutează setările de autocast apăsând pe rotița de pe buton."
+    tip_baby_coders: "În vitor până și bebelușii vor fi Archmage."
+    tip_morale_improves: "Se va încărca până până când va crește moralul."
+    tip_all_species: "Noi credem în șanse egale de a învăța programare pentru toate speciile."
+#    tip_reticulating: "Reticulating spines."  ??????????context ???
+    tip_harry: "Ha un Wizard, "

   admin:
     av_title: "Admin vede"
@@ -249,7 +249,7 @@
     av_other_debug_base_url: "Base (pentru debugging base.jade)"
     u_title: "Listă utilizatori"
     lg_title: "Ultimele jocuri"
-#    clas: "CLAs"
+    clas: "CLAs"

   editor:
     main_title: "Editori CodeCombat"
@@ -530,8 +530,8 @@
     simulation_explanation: "Simulând jocuri poți afla poziția în clasament a jocului tău mai repede!"
     simulate_games: "Simulează Jocuri!"
     simulate_all: "RESETEAZĂ ȘI SIMULEAZĂ JOCURI"
-#    games_simulated_by: "Games simulated by you:"
-#    games_simulated_for: "Games simulated for you:"
+    games_simulated_by: "Jocuri simulate de tine:"
+    games_simulated_for: "Jocuri simulate pentru tine:"
     leaderboard: "Clasament"
     battle_as: "Luptă ca "
     summary_your: "Al tău "


Update ru

Update ru

--- app/locale/ru.coffee
+++ app/locale/ru.coffee
@@ -252,7 +252,7 @@
     new_component_title: "Создать новый компонент"
     new_component_field_system: "Система"
     new_article_title: "Создать новую статью"
-    new_thang_title: "Создать новый объект"
+    new_thang_title: "Создать новый тип объектов"
     new_level_title: "Создать новый уровень"
     article_search_title: "Искать статьи"
     thang_search_title: "Искать типы объектов"


Update sk.coffee

Update sk.coffee

Why is red default in choose_hero ?

--- app/locale/sk.coffee
+++ app/locale/sk.coffee
@@ -67,9 +67,9 @@
 #    poll: "Poll" # Tooltip on poll button from /play
     next: "Ďalší" # Go from choose hero to choose inventory before playing a level
     change_hero: "Zmeniť hrdinu" # Go back from choose inventory to choose hero
-    choose_inventory: "Vyzbrojiť sa s predmetmy"
+    choose_inventory: "Vyzbrojiť sa s predmetmi"
     buy_gems: "Zakúpiť drahokamy"
-    subscription_required: "Vyžaduje sa rredplatné"
+    subscription_required: "Vyžaduje sa predplatné"
     older_campaigns: "Staršie kampane"
     anonymous: "Anonymný hráč"
     level_difficulty: "Obtiažnosť."
@@ -457,16 +457,16 @@
 #    subscribe_prepaid: "Click Subscribe to use prepaid code"
 #    using_prepaid: "Using prepaid code for monthly subscription"

-#  choose_hero:
-#    choose_hero: "Choose Your Hero"
-#    programming_language: "Programming Language"
-#    programming_language_description: "Which programming language do you want to use?"
-#    default: "Default"
-#    experimental: "Experimental"
-#    python_blurb: "Simple yet powerful, great for beginners and experts."
-#    javascript_blurb: "The language of the web. (Not the same as Java.)"
-#    coffeescript_blurb: "Nicer JavaScript syntax."
-#    clojure_blurb: "A modern Lisp."
+choose_hero:
+   choose_hero: "Vyber svojho hrdinu" # "Choose Your Hero"
+   programming_language: "Programovací jazyk" #"Programming Language"
+   programming_language_description: "Aký programovací jazyk chceš použiť ? "#"Which programming language do you want to use?" default: "predvolený" #"Default"
+   default: "Predvolený"
+   experimental: "Experimentálny" #"Experimental"
+   python_blurb: "Jednoduchý ale výkonný, skvelý pre začiatočníkov aj expertov" #"Simple yet powerful, great for beginners and experts."
+   javascript_blurb: "Jazyk webových stránok. (Nie je to Java)" #"The language of the web. (Not the same as Java.)"
+   coffeescript_blurb: "Krajšia syntax pre JavaScript" #"Nicer JavaScript syntax."
+   clojure_blurb: "Moderný Lisp"#"A modern Lisp."
 #    lua_blurb: "Game scripting language."
 #    io_blurb: "Simple but obscure."
 #    status: "Status"


Uncommented a string from last merge...

Uncommented a string from last merge...

... so that it doesn't get lost

--- app/locale/uk.coffee
+++ app/locale/uk.coffee
@@ -584,7 +584,7 @@
     violent_1: "Вчителі запитують нас це переважно через нашу назву. Хоча CodeCombat містить жорстокі малюнки, нічого такого немає ні в візуалізаціях, ні в мові."
     violent_2: "Якщо для Вас нормально, що учні грають в Angry Birds, то CodeCombat так само нормальний."
     for_girls_title: "Дівчатам підходить?"
-#    for_girls_1: "У CodeCombat є три режими гри: будування, пазли і битви. Ми розробляли кожен так, щоб підходило і хлопцям, і дівчатам і думаємо, що будування і пазлиhink that the building and puzzle levels especially differentiate the game from violent triple A titles that repel female players."
+    for_girls_1: "У CodeCombat є три режими гри: будування, пазли і битви. Ми розробляли кожен так, щоб підходило і хлопцям, і дівчатам і думаємо, що будування і пазлиhink that the building and puzzle levels especially differentiate the game from violent triple A titles that repel female players."
     age_recommended_title: "Для якого віку це рекомендуєте?"
     age_recommended_1: "Мінімальний рекомендований вік — 9 років, однак перші етапи з допомогою дорослих можуть проходити й діти 7-ми років."
     what_cover_title: "Що ми отримуємо?"


render the usual error page now that errors will be reported

--- src/smc-webapp/r_misc.cjsx
+++ src/smc-webapp/r_misc.cjsx
@@ -2054,9 +2054,6 @@
             info  : info

     render: ->
-        # This is way worse than nothing, because it surpresses reporting the actual error to the
-        # backend!!!  I'm disabling it completely.
-        return @props.children
         if @state.info?
             <Alert
                 bsStyle = 'warning'


check for available decorators before disposing

--- lib/minimap-highlight-selected.coffee
+++ lib/minimap-highlight-selected.coffee
@@ -53,7 +53,7 @@
     @decorations.push decoration

   markersDestroyed: =>
-    @decorations.forEach (decoration) -> decoration.destroy()
+    @decorations?.forEach (decoration) -> decoration.destroy()
     @decorations = []

   deactivatePlugin: ->


add app_id saving in variable

add app_id saving in variable

--- assets/js/app.coffee
+++ assets/js/app.coffee
@@ -9,7 +9,7 @@
   active_thumb = 0
   timer = null
   full_width = 0
-  app_id = ''
+  APP_ID = '4442537'

   stopTimer = ->
     console.log 'get in Stop timer()'
@@ -108,7 +108,7 @@
         id = curr_photo.res_url.match(/\d+$/)[0]

         $('#vk_like').empty()
-        VK.Widgets.Like("vk_like", {type: "mini", height: 20, pageTitle: "Угадай страну: " + curr_photo.country, pageUrl: 'https://vk.com/app' + app_id}, id);
+        VK.Widgets.Like("vk_like", {type: "mini", height: 20, pageTitle: "Угадай страну: " + curr_photo.country, pageUrl: 'https://vk.com/app' + APP_ID, }, id);

         $('#photo_url').prop 'href', curr_photo.res_url
         #center the image
@@ -170,8 +170,9 @@
     get_new_photos()

   VK.init (data) ->
+    console.log document.location
     #app_id = data.api_id
-    console.log data
+    #console.log data
     VK.api 'storage.get', {key: 'score'}, (data) ->
       if data.response
         if data.response == ''


couple extra fixes

couple extra fixes

-Added "ship" tag to the Protectorate Starfighter title
-Deleted and added again the entire Gyroscopic Targeting, plus adding the "ship" tag for it. For some reason it wasn't showing in the Modification lists but the builder was aknoweleding its existence (i.e. adding it on the english version and then switching to spanish the translated Gyroscopic would be there, while if it was missing would just disappear). I didn't see any problem on the entry, hence the rewrite and the added tag. I hope it will work :(

--- coffeescripts/cards-es.coffee
+++ coffeescripts/cards-es.coffee
@@ -1552,7 +1552,8 @@
             text: '''YT-1300 and YT-2400 only.%LINEBREAK%Your upgrade bar gains the %ILLICIT% upgrade icon.%LINEBREAK%You may equip 1 additional Modification upgrade that costs 3 or fewer squad points.'''
         'Gyroscopic Targeting':
             name: "Estabilización giroscópica"
-            text: '''Solo Nave de persecución clase Lancero.%LINEBREAK%Al final de la fase de Combate, si en esta ronda ejecutaste una maniobra de velocidad 3, 4 o 5, puedes reorientar tu arco de fuego móvil.'''
+            ship: "Nave de persecución clase Lancero"
+            text: '''Sólo Nave de persecución clase Lancero.%LINEBREAK%Al final de la fase de Combate, si en esta ronda ejecutaste una maniobra de velocidad 3, 4 o 5, puedes reorientar tu arco de fuego móvil.'''

     title_translations =
         "Slave I":
@@ -1694,6 +1695,7 @@
             text: '''TIE/sf only.%LINEBREAK%When attacking with a primary weapon from your primary firing arc, you may roll 1 additional attack die.  If you do not, you may perform an additional attack from your auxiliary firing arc.'''
         'Concord Dawn Protector':
             name: "Protector de Concord Dawn"
+            ship: "Caza Estelar del Protectorado"
             text: '''Sólo Caza estelar del Protectorado.%LINEBREAK%Cuando te defiendas, si estás dentro del arco de fuego del atacante y a alcance 1 y el atacante está dentro de tu arco de fuego, añade 1 resultado %EVADE%.'''
         'Shadow Caster':
             ship: "Nave de persecución clase Lancero"


sorted coffeelint files

sorted coffeelint files

--- grunt/coffeelint.coffee
+++ grunt/coffeelint.coffee
@@ -1,7 +1,7 @@
 module.exports = (grunt) ->
   grunt.config 'coffeelint',
     all: [
-      '<%= watch.coffee.all %>'
+      '<%= watch.coffee.files %>'
     ]
     options:
       arrow_spacing:


Update concurrent task

Update concurrent task

--- grunt/concurrent.coffee
+++ grunt/concurrent.coffee
@@ -1,2 +1,5 @@
 module.exports =
-  dev: ['observe', 'connect']
+  dev:
+    tasks: ['observe', 'connect']
+    options:
+      logConcurrentOutput: true


admins: add armagan & cihangir

admins: add armagan & cihangir

--- workers/social/lib/social/models/account/index.coffee
+++ workers/social/lib/social/models/account/index.coffee
@@ -543,7 +543,7 @@
       @update ($set: 'counts.topics': count), ->

   dummyAdmins = [ "sinan", "devrim","gokmen", "chris", "testdude", "blum", "neelance", "halk",
-                  "fatihacet", "chrisblum", "sent-hil", "kiwigeraint"]
+                  "fatihacet", "chrisblum", "sent-hil", "kiwigeraint", "armagan", "cihangirsavas"]

   flagAccount: secure (client, flag, callback)->
     {delegate} = client.connection


Use .findMarkers instead of .markersForBufferPosition in snippets

--- src/packages/snippets/lib/snippet-expansion.coffee
+++ src/packages/snippets/lib/snippet-expansion.coffee
@@ -8,7 +8,6 @@
   settingTabStop: false

   constructor: (@snippet, @editSession) ->
-
     @editSession.selectToBeginningOfWord()
     startPosition = @editSession.getCursorBufferPosition()
     @editSession.transact =>
@@ -60,7 +59,7 @@
     markerSelected

   tabStopsForBufferPosition: (bufferPosition) ->
-    _.intersection(@tabStopMarkers, @editSession.markersForBufferPosition(bufferPosition))
+    _.intersection(@tabStopMarkers, @editSession.findMarkers(containsBufferPosition: bufferPosition))

   destroy: ->
     @unsubscribe()


disabled rabbitmq heartbeat in vagrant

disabled rabbitmq heartbeat in vagrant

--- config/main.vagrant.coffee
+++ config/main.vagrant.coffee
@@ -144,7 +144,10 @@
     login       : 'PROD-k5it50s4676pO9O'
     componentUser: "PROD-k5it50s4676pO9O"
     password    : 'djfjfhgh4455__5'
-    heartbeat   : 10
+    # heartbeat disabled in vagrant, because it'll interfere with node-inspector
+    # when the debugger is paused, the target is not able to send the heartbeat,
+    # so it'll disconnect from RabbitMQ if heartbeat is enabled.
+    heartbeat   : 0
     vhost       : '/'
   broker        :
     ip          : ""


Prevent incorrect keydown handling when modifier key(s) are pressed

--- src/trix/controllers/level_2_input_controller.coffee
+++ src/trix/controllers/level_2_input_controller.coffee
@@ -23,8 +23,9 @@
           event.preventDefault()
       else
         if handler = @keys[event.key]
-          @event = event
-          handler.call(this)
+          unless event.altKey or event.shiftKey
+            @event = event
+            handler.call(this)

     beforeinput: (event) ->
       if handler = @inputTypes[event.inputType]


Issue #12: add new string functions 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|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|in|out|outln|err|errln|exit)\\b'
+		'match': '\\b(nil|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|in|out|outln|err|errln|exit)\\b'
 		'name': 'variable.function.micro'
 	}
 	{


Atom plugin: correct the syntax highlighting

--- tools/atom-plugin/language-micro/grammars/micro.cson
+++ tools/atom-plugin/language-micro/grammars/micro.cson
@@ -1,70 +1,63 @@
-"name": "Micro",
-"scopeName": "source.micro",
-"fileTypes": ["micro"],
-"patterns": [
+'name': 'Micro'
+'scopeName': 'source.micro'
+'fileTypes': ['micro']
+'patterns': [
 	{
-		"match": "\\bfn\\b",
-		"name": "keyword.control.statement.micro",
-		"comment": "Keywords."
-	},
+		'comment': 'Singleline comments'
+		'match': '\\bnb(?!:)\\b.*'
+		'name': 'comment.line.micro'
+	}
 	{
-		"match": "\\(|\\)|;|'|[\\!\\#\\$\\%\\&\\*\\+\\,\\-\\/\\:\\<\\=\\>\\?\\@\\[\\\\\\]\\^\\{\\|\\}\\~]+",
-		"name": "keyword.operator.micro",
-		"comment": "Operators."
-	},
+		'comment': 'Multiline comments'
+		'begin': '\\bnb:'
+		'end': '\\bnb;'
+		'name': 'comment.block.micro'
+	}
 	{
-		"match": "\\b\\d+(\\.\\d+)?\\b",
-		"name": "constant.numeric.micro",
-		"comment": "Numbers."
-	},
+		'comment': 'Numbers'
+		'match': '\\d+(\\.\\d+)?(e-?\\d+)?'
+		'name': 'constant.numeric.micro'
+	}
 	{
-		"begin": "\"",
-		"end": "\"",
-		"name": "string.quoted.double.micro",
-		"comment": "Strings.",
-		"patterns": [
+		'comment': 'Symbols'
+		'match': "'(?:(\\\\['\\\\tn])|[^'\\n])'"
+		'captures':
+			'0':
+				'name': 'string.quoted.single.micro'
+			'1':
+				'name': 'constant.character.escape.micro'
+	}
+	{
+		'comment': 'Strings'
+		'begin': '\"'
+		'end': '\"'
+		'name': 'string.quoted.double.micro'
+		'patterns': [
 			{
-				"include": "#escaped_symbol"
+				'comment': 'Escaped symbol'
+				'match': '\\\\["\\\\tn]'
+				'name': 'constant.character.escape.micro'
 			}
 		]
-	},
+	}
 	{
-		"begin": "`",
-		"end": "`",
-		"name": "string.quoted.single.micro",
-		"comment": "Symbols.",
-		"patterns": [
-			{
-				"include": "#escaped_symbol"
-			}
-		]
-	},
+		'comment': 'Keywords (it should be described before functions)'
+		'match': '\\bfn\\b'
+		'name': 'keyword.control.statement.micro'
+	}
 	{
-		"match": "\\bnb(?!:)\\b.*$",
-		"name": "comment.line.micro",
-		"comment": "Singleline comments."
-	},
+		'comment': 'Built-in functions (it should be described before functions)'
+		'match': '\\b(nil|num|type|arity|floor|ceil|trunc|sin|cos|tn|arcsin|arccos|arctn|angle|pow|sqrt|exp|ln|lg|abs|random|head|tail|if|str|in|out|err|exit)\\b'
+		'name': 'variable.function.micro'
+	}
 	{
-		"begin": "\\bnb:",
-		"end": "\\bnb;",
-		"name": "comment.block.micro",
-		"comment": "Multiline comments."
-	},
+		'comment': 'Operators'
+		'match': '\\(|\\)|:|;|[!#$%&*+,-./<=>?@[\\\\]^`{|}~]+'
+		'name': 'keyword.operator.micro'
+	}
 	{
-		"match": "\\b(nil|floor|ceil|trunc|sin|cos|tn|arcsin|arccos|arctn|arctn2|sh|ch|th|sqrt|pow|exp|ln|lg|abs|rand|true|false|if|list|append|concat|get|set|len|read|print|to_str|to_num|while|eval|require|require_once|type|exit)\\b",
-		"name": "variable.function.micro",
-		"comment": "Built-in functions."
-	},
-	{
-		"match": "[A-Za-z_]+",
-		"name": "entity.name.function.micro",
-		"comment": "Functions."
-	},
-],
-"repository": {
-	"escaped_symbol": {
-		"match": "\\\\.",
-		"name": "constant.character.escape.micro",
-		"comment": "Escaped symbol."
+		'comment': 'Functions'
+		'match': '[A-Za-z_]+'
+		'name': 'entity.name.function.micro'
 	}
-}
+]


Team progression graph draft.

Team progression graph draft.

--- web/coffee/progression_graphs.coffee
+++ web/coffee/progression_graphs.coffee
@@ -7,6 +7,12 @@
   title: "Team Score Progression",
   legend: {
     position: "none"
+  },
+  vAxis: {
+    title: "Score"
+  },
+  hAxis: {
+    ticks: []
   },
   pointSize: 3
 }
@@ -29,10 +35,11 @@

   return _.extend continuousBucket, buckets

-maxValuesFromBuckets = (buckets, sampleKey) ->
+maxValuesFromBucketsExtended = (buckets, sampleKey) ->
   maxValues = []

   lastInsertedValue = 0
+
   _.each buckets, (samples) ->
     values = _.pluck(samples, sampleKey)

@@ -45,21 +52,33 @@

   return maxValues

-@drawTeamProgressionGraph = (selector) ->
+progressionDataToPoints = (data, bucketWindow, dataPoints) ->
+      buckets = timestampsToBuckets data, "time", bucketWindow
+      steps = maxValuesFromBucketsExtended buckets, "score"
+
+      if steps.length < dataPoints
+        return steps
+
+      return _.rest(steps, steps.length - dataPoints)
+
+@drawTeamProgressionGraph = (selector, points) ->
   div = divFromSelector selector
   apiCall "GET", "/api/stats/team/score_progression", {}
   .done (data) ->
-    graphData = [["Time", "Score"]]
-
-    lastSubmission = null
-    _.each data.data, (submission) ->
-      if lastSubmission
-        graphData.push [submission.time - 1, lastSubmission.score]
-      graphData.push [submission.time, submission.score]
-      lastSubmission = submission
+    if data.data.length > 0
+
+      graphData = [
+        ["Time", "Score", {role: "tooltip"}]
+      ]

-    packagedData = google.visualization.arrayToDataTable graphData
+      steps = progressionDataToPoints data.data, 600, 30
+
+      (graphData.push(["", score, score]) for score in steps)

-    chart = new google.visualization.LineChart(div)
-    chart.draw(packagedData, teamGraphOptions)
+      console.log graphData

+      packagedData = google.visualization.arrayToDataTable graphData
+
+      chart = new google.visualization.SteppedAreaChart(div)
+      chart.draw(packagedData, teamGraphOptions)
+


set cursor in appropriate field when scheduling

--- app/assets/javascripts/states/runbooks/views/run_command.js.coffee
+++ app/assets/javascripts/states/runbooks/views/run_command.js.coffee
@@ -75,12 +75,14 @@
       @ui.cron.div.hide()
       @ui.next_schedule.hide()
       @validate_schedule("natural", @ui.natural.text.val())
+      @ui.natural.text.putCursorAtEnd()

     "click cron.radio": ->
       @ui.cron.div.show()
       @ui.natural.div.hide()
       @ui.next_schedule.hide()
       @validate_schedule("cron", @ui.cron.text.val())
+      @ui.cron.text.putCursorAtEnd()

     "keyup cron.text": _.debounceR 250, (e) ->
       _.unique_val e.target, (val) => @validate_schedule("cron", val)


Intercom detect group change (#3610)

Intercom detect group change (#3610)

detect change to Session.currentGroup and update intercom

--- angular/core/services/intercom_service.coffee
+++ angular/core/services/intercom_service.coffee
@@ -73,10 +73,14 @@
       else
         $window.location = LmoUrlService.contactForm()

-  $rootScope.$on 'analyticsSetGroup', (event, group) ->
-    service.updateWithGroup(group)
+  if $window? and $window.Intercom?
+    $rootScope.$watch ->
+      Session.currentGroup? && mapGroup(Session.currentGroup)
+    , ->
+      Session.currentGroup? && service.updateWithGroup(Session.currentGroup)
+    , true

-  $rootScope.$on 'logout', (event, group) ->
-    service.shutdown()
+    $rootScope.$on 'logout', (event, group) ->
+      service.shutdown()

   service


update dashing-src from dashing official repo

--- vendor/assets/javascripts/dashing/dashing-src.coffee
+++ vendor/assets/javascripts/dashing/dashing-src.coffee
@@ -22,30 +22,25 @@
     num

 class window.Dashing extends Batman.App
+  @on 'reload', (data) ->
+    window.location.reload(true)
+
   @root ->
 Dashing.params = Batman.URI.paramsFromQuery(window.location.search.slice(1));

 class Dashing.Widget extends Batman.View
-  constructor: ->
+  constructor:  ->
     # Set the view path
     @constructor::source = Batman.Filters.underscore(@constructor.name)
-
     super

-    @observe 'node', (newValue, oldValue) ->
-      if !oldValue && !@_registeredAtDashing
-        @_registeredAtDashing = true
+    @mixin($(@node).data())
+    Dashing.widgets[@id] ||= []
+    Dashing.widgets[@id].push(@)
+    @mixin(Dashing.lastEvents[@id]) # in case the events from the server came before the widget was rendered

-        @mixin($(@node).data())
-        Dashing.widgets[@id] ||= []
-        Dashing.widgets[@id].push(@)
-
-        # in case the events from the server came
-        # before the widget was rendered
-        @mixin(Dashing.lastEvents[@id])
-
-        type = Batman.Filters.dashize(@constructor.name)
-        $(@node).addClass("widget widget-#{type} #{@id}")
+    type = Batman.Filters.dashize(@view)
+    $(@node).addClass("widget widget-#{type} #{@id}")

   @accessor 'updatedAtMessage', ->
     if updatedAt = @get('updatedAt')
@@ -104,18 +99,24 @@
     console.log("Connection closed")
     setTimeout (->
       window.location.reload()
-    ), 5 * 60 * 1000
+    ), 5*60*1000

-source.addEventListener 'message', (e) =>
+source.addEventListener 'message', (e) ->
   data = JSON.parse(e.data)
   if lastEvents[data.id]?.updatedAt != data.updatedAt
     if Dashing.debugMode
       console.log("Received data for #{data.id}", data)
-    lastEvents[data.id] = data
     if widgets[data.id]?.length > 0
+      lastEvents[data.id] = data
       for widget in widgets[data.id]
         widget.receiveData(data)

+source.addEventListener 'dashboards', (e) ->
+  data = JSON.parse(e.data)
+  if Dashing.debugMode
+    console.log("Received data for dashboards", data)
+  if data.dashboard is '*' or window.location.pathname is "/#{data.dashboard}"
+    Dashing.fire data.event, data

 $(document).ready ->
   Dashing.run()


Revert ":art: rename variable"

Revert ":art: rename variable"

This reverts commit 540d2f0e4dae19e931e13afbe37e173ee2b16f2d.

--- lib/bracket-matcher.coffee
+++ lib/bracket-matcher.coffee
@@ -56,7 +56,7 @@
     nextCharacter = @editor.getTextInBufferRange([cursorBufferPosition, cursorBufferPosition.traverse([0, 1])])

     previousCharacter = previousCharacters.slice(-1)
-    hasNonWhitespaceAfterCursor = /\S/.test(nextCharacter)
+    hasWordAfterCursor = /\S/.test(nextCharacter)
     hasWordBeforeCursor = /\w/.test(previousCharacter)
     hasQuoteBeforeCursor = previousCharacter is text[0]
     hasEscapeSequenceBeforeCursor = previousCharacters.match(/\\/g)?.length >= 1 # To guard against the "\\" sequence
@@ -71,7 +71,7 @@
       text += '{'
       pair = '}'
     else
-      autoCompleteOpeningBracket = @getScopedSetting('bracket-matcher.autocompleteBrackets') and @isOpeningBracket(text) and (not hasNonWhitespaceAfterCursor or hasClosingBracketAfterCursor) and not (@isQuote(text) and (hasWordBeforeCursor or hasQuoteBeforeCursor)) and not hasEscapeSequenceBeforeCursor
+      autoCompleteOpeningBracket = @getScopedSetting('bracket-matcher.autocompleteBrackets') and @isOpeningBracket(text) and (not hasWordAfterCursor or hasClosingBracketAfterCursor) and not (@isQuote(text) and (hasWordBeforeCursor or hasQuoteBeforeCursor)) and not hasEscapeSequenceBeforeCursor
       pair = @pairedCharacters[text]

     skipOverExistingClosingBracket = false


Workspace: Added getActivePanelMethod to workspace.

--- client/app/CommonViews/workspace/workspace.coffee
+++ client/app/CommonViews/workspace/workspace.coffee
@@ -36,6 +36,9 @@

   prev: ->

+  getActivePanel: ->
+    return @panels[@lastCreatedPanelIndex]
+
   _windowDidResize: ->
     return unless @activePanel
     pane.emit "PaneResized" for pane in @activePanel.panes


myteam/sendinvites: send user role to container

--- client/home/lib/myteam/hometeamsendinvites.coffee
+++ client/home/lib/myteam/hometeamsendinvites.coffee
@@ -6,5 +6,14 @@

 module.exports = class HomeTeamSendInvites extends ReactView

+  constructor: (options = {}, data) ->
+
+    super options, data
+
+    @state =
+      role : data
+
+
   renderReact: ->
-    <TeamSendInvite.Container />+
+    <TeamSendInvite.Container role={@state.role} />


Revert the re-initialize fix from client side, since we fixed this issue in oskite.

--- client/landing/app/MainApp/login/loginview.coffee
+++ client/landing/app/MainApp/login/loginview.coffee
@@ -197,36 +197,27 @@
         $.cookie 'clientId', replacementToken
         KD.getSingleton('mainController').accountChanged account

-        # Reset VM Data and re-initialize
-        vmController = KD.getSingleton 'vmController'
-        vmController.resetVMData()
-        vmController.run
-          kiteName : 'os'
-          method   : 'vm.reinitialize'
-        , (err)=>
-          warn err  if err
-
-          new KDNotificationView
-            cssClass  : "login"
-            title     : 'Good to go, Enjoy!'
-            # content   : 'Successfully registered!'
-            duration  : 2000
-            @showInstructionsBookIfFirstLogin()
-
-          # send information to mixpanel
-          KD.track 'UserLogin', 'UserRegistered',
-            vendor         : 'mixpanel'
-            extra          :
-              '$username'  : account.profile.nickname
-              '$loginDate' : Date.now()
-
-          KD.getSingleton('router').clear()
-
-          setTimeout =>
-            @hide()
-            @registerForm.reset()
-            @registerForm.button.hideLoader()
-          , 1000
+        new KDNotificationView
+          cssClass  : "login"
+          title     : 'Good to go, Enjoy!'
+          # content   : 'Successfully registered!'
+          duration  : 2000
+          @showInstructionsBookIfFirstLogin()
+
+        # send information to mixpanel
+        KD.track 'UserLogin', 'UserRegistered',
+          vendor         : 'mixpanel'
+          extra          :
+            '$username'  : account.profile.nickname
+            '$loginDate' : Date.now()
+
+        KD.getSingleton('router').clear()
+
+        setTimeout =>
+          @hide()
+          @registerForm.reset()
+          @registerForm.button.hideLoader()
+        , 1000

   doLogin:(credentials)->
     credentials.username = credentials.username.toLowerCase()


login? why's that method on record-finder?

--- client/lanes/components/record-finder/Dialog.cjsx
+++ client/lanes/components/record-finder/Dialog.cjsx
@@ -10,10 +10,6 @@

     contextTypes:
         viewport: Lanes.PropTypes.State.isRequired
-
-    login: ->
-        @model.save().then =>
-            Lanes.current_user.setLoginData(@model.user, @model.access) if @model.user

     warning: ->
         <BS.Alert bsStyle='warning'>


GOV-461, show confirm-email screen on forgot password even if the reset password operation failed

--- client/views/forgotPassword/forgotPassword.coffee
+++ client/views/forgotPassword/forgotPassword.coffee
@@ -36,9 +36,10 @@
       Meteor.call('entryForgotPassword',Session.get('email'), (err, data) ->
         if err
           Session.set('entryError', "Failed to reset password")
-        else
-          Session.set('_accountsEntryProcessing', false)
-          Router.go('/confirm-email');
+          console.log "Forgot password failed for #{Session.get('email')} with error #{JSON.toString(err)}"
+        Router.go('/confirm-email');
+        Session.set('_accountsEntryProcessing', false)
+

       )


check the remember me box by default

check the remember me box by default

--- app/assets/javascripts/active_admin.js.coffee
+++ app/assets/javascripts/active_admin.js.coffee
@@ -37,4 +37,7 @@
   if $('#bill_amount_decimal').val() == '0.0'
     $('#bill_amount_decimal')
       .val('')
+
+  # Make the damn remember me checkbox default checked
+  $('#admin_user_remember_me').prop('checked', true)
 )


Last Changes before Merge

Last Changes before Merge

- Change variable name to make it more clear

--- common/lib/xmodule/xmodule/js/src/peergrading/track_changes.coffee
+++ common/lib/xmodule/xmodule/js/src/peergrading/track_changes.coffee
@@ -51,12 +51,12 @@

   undo_change: (event) =>
     event.preventDefault()
-    keyToUndo = 0
+    keyOfLatestChange = 0
     @tracked_elements.each ->
       key = parseInt(@attr('data-cid'))
-      if key > keyToUndo
-        keyToUndo = key
-    ICEtracker.rejectChange('[data-cid="'+ keyToUndo + '"]')
+      if key > keyOfLatestChange
+        keyOfLatestChange = key
+    ICEtracker.rejectChange('[data-cid="'+ keyOfLatestChange + '"]')

   stop_tracking_on_submit: () =>
     @tracker.stopTracking()


Use correct object itterator "of" not "in"

--- test/document-spec.coffee
+++ test/document-spec.coffee
@@ -148,7 +148,7 @@
       assert.ifError(err)
       req.get(url(res.body.document._id)).expect(200).end (err, res) ->
         assert.ifError(err)
-        assert.deepEqual res.body[key], val for val, key in doc
+        assert.deepEqual res.body[key], val for key, val of doc
         done()

   it 'should return newly created document (with id)', (done) ->
@@ -163,7 +163,7 @@
       assert.equal res.body.document._id, doc._id
       req.get(url(doc._id)).expect(200).end (err, res) ->
         assert.ifError(err)
-        assert.deepEqual res.body[key], val for val, key in doc
+        assert.deepEqual res.body[key], val for key, val of doc
         done()

   it 'should handle rapid fire', (done) ->
@@ -205,7 +205,7 @@
       req.put(u).send(doc).expect(200).end (err, res) ->
         req.get(u).expect(200).end (err, res) ->
           ignore = ['tilbyder', 'endret', 'checksum']
-          assert.deepEqual val, doc[key] for val, key in res.body when key not in ignore
+          assert.deepEqual val, doc[key] for key, val of res.body when key not in ignore
           done()

   it 'should override tilbyder, endret and checksum fields', (done) ->
@@ -219,8 +219,8 @@
       req.put(u).send(doc).expect(200).end (err, res) ->
         req.get(u).expect(200).end (err, res) ->
           ignore = ['tilbyder', 'endret', 'checksum']
-          assert.notEqual val, doc[key] for val, key in res.body when key in ignore
-          assert.deepEqual val, doc[key] for val, key in res.body when key not in ignore
+          assert.notEqual val, doc[key] for key, val of res.body when key in ignore
+          assert.deepEqual val, doc[key] for key, val of res.body when key not in ignore
           done()

   it 'should prevent unauthorized edits', (done) ->


facebook

facebook

--- app/assets/javascripts/sammy_router.js.coffee
+++ app/assets/javascripts/sammy_router.js.coffee
@@ -50,6 +50,10 @@

   @get "#/", (context) ->
     context.redirect("#/auctions")
+
+  # workaround for facebook redirect
+  @get "#_=_", (context) ->
+    context.redirect("#/reload")

   @get "#/options", (context) ->
     render( new Sview('options', {}) )


set max width for assignment column to 400

set the default max width to be 200

Change-Id: I8cfe3b2e9a7bd678307d32a336c96802df910e38
Reviewed-on: https://gerrit.instructure.com/15417
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
Reviewed-by: Ryan Shaw <ea3cd978650417470535f3a4725b6b5042a6ab59@instructure.com>

--- app/coffeescripts/gradebook2/Gradebook.coffee
+++ app/coffeescripts/gradebook2/Gradebook.coffee
@@ -37,10 +37,12 @@
     columnWidths =
       assignment:
         min: 10
-        max: 200
+        default_max: 200
+        max: 400
       assignmentGroup:
         min: 35
-        max: 200
+        default_max: 200
+        max: 400
       total:
         min: 85
         max: 100
@@ -652,7 +654,7 @@
                   SubmissionCell
           minWidth: columnWidths.assignment.min,
           maxWidth: columnWidths.assignment.max,
-          width: testWidth(assignment.name, minWidth, columnWidths.assignment.max),
+          width: testWidth(assignment.name, minWidth, columnWidths.assignment.default_max),
           sortable: true
           toolTip: assignment.name
           type: 'assignment'
@@ -684,7 +686,7 @@
           object: group
           minWidth: columnWidths.assignmentGroup.min,
           maxWidth: columnWidths.assignmentGroup.max,
-          width: testWidth(group.name, columnWidths.assignmentGroup.min, columnWidths.assignmentGroup.max)
+          width: testWidth(group.name, columnWidths.assignmentGroup.min, columnWidths.assignmentGroup.default_max)
           cssClass: "meta-cell assignment-group-cell",
           sortable: true
           type: 'assignment_group'


Don't let shift-enter submit

Don't let shift-enter submit

normal behaviour of shift-enter is to not submit if enter submits, so
this was very confusing behaviour.

--- core/app/backbone/factlink/react_text_area.coffee
+++ core/app/backbone/factlink/react_text_area.coffee
@@ -38,7 +38,7 @@
       @focusInput()

   _handleSubmit: (e)->
-    if e.keyCode == 13 && (e.ctrlKey || e.metaKey || e.shiftKey)
+    if e.keyCode == 13 && (e.ctrlKey || e.metaKey)
       e.preventDefault()
       e.stopPropagation()
       @props.onSubmit()


promise: refactor child test

promise: refactor child test

--- test/promise.child.coffee
+++ test/promise.child.coffee
@@ -8,41 +8,39 @@

   scratch = test.scratch @

-  describe 'child', ->
+  it 'dont change status of parent context', (next) ->
+    touched = 0
+    m = mecano
+    .call (options, next) ->
+      m
+      .child()
+      .touch
+        destination: "#{scratch}/a_file"
+      .then (err, changed) ->
+        touched++
+        changed.should.be.true
+        next err
+    .then (err, changed) ->
+      changed.should.be.false
+      touched.should.eql 1
+      next()

-    it 'dont change status of parent context', (next) ->
-      touched = 0
-      m = mecano
-      .call (options, next) ->
-        m
-        .child()
-        .touch
-          destination: "#{scratch}/a_file"
-        .then (err, changed) ->
-          touched++
-          changed.should.be.true
-          next err
-      .then (err, changed) ->
-        changed.should.be.false
-        touched.should.eql 1
-        next()
-
-    # it 'accept conditions', (next) ->
-    #   touched = 0
-    #   m = mecano
-    #   .call (options, next) ->
-    #     m
-    #     .child()
-    #     .touch
-    #       destination: "#{scratch}/a_file"
-    #     .then (err, changed) ->
-    #       touched++
-    #       changed.should.be.True
-    #       next err
-    #   .then (err, changed) ->
-    #     changed.should.be.False
-    #     touched.should.eql 1
-    #     next()
+  # it 'accept conditions', (next) ->
+  #   touched = 0
+  #   m = mecano
+  #   .call (options, next) ->
+  #     m
+  #     .child()
+  #     .touch
+  #       destination: "#{scratch}/a_file"
+  #     .then (err, changed) ->
+  #       touched++
+  #       changed.should.be.True
+  #       next err
+  #   .then (err, changed) ->
+  #     changed.should.be.False
+  #     touched.should.eql 1
+  #     next()


xmpp/connection: fix name

xmpp/connection: fix name

--- src/xmpp/connection.coffee
+++ src/xmpp/connection.coffee
@@ -7,7 +7,7 @@
 xmpp = require("node-xmpp")
 {EventEmitter} = require('events')
 errors = require("../errors")
-ns = require('./ns')
+NS = require('./ns')

 IQ_TIMEOUT = 10000
 MAX_STANZA_SIZE = 65000  # 535 bytes spare room


improved Block detection regexp

improved Block detection regexp

--- src/babel_bridge/Extensions/IndentBlocks.coffee
+++ src/babel_bridge/Extensions/IndentBlocks.coffee
@@ -1,7 +1,7 @@
 {defineModule, log} = require 'art-foundation'

 defineModule module, -> class IndentBlocks
-  blockStartRegExp = /\n( +)/y
+  blockStartRegExp = /\n(?: *\n)*( +)(?=[^ \n])/y

   @ruleProps:
     parse: (parentNode) ->


Allow filters to apply to blank queries

Allow filters to apply to blank queries

--- web/site/search.coffee
+++ web/site/search.coffee
@@ -135,6 +135,9 @@
       else if query.length > 0
         $scope.query = query

+      if !$scope.query?
+        $scope.query = "*"
+
       clearAllContainers()

       # Set the currently selected filter options here


Add update notification

Add update notification

--- views/components/settings/parts/plugin-config.cjsx
+++ views/components/settings/parts/plugin-config.cjsx
@@ -97,7 +97,7 @@
     plugins[index].version = @state.latest[plugins[index].packageName]
     updating = @state.updating
     updating[index] = false
-    @checkUpdate(@solveUpdate)
+    @checkUpdate(@solveUpdate, false)
     @setState {updating}
   handleUpdate: (index, callback) ->
     if !@props.disabled
@@ -171,22 +171,34 @@
         npm.commands.install [name], (er, data) ->
           callback(index)
       @setState {installStatus}
-  solveUpdate: (updateData) ->
+  solveUpdate: (updateData, isfirst) ->
     latest = @state.latest
     for updateObject, index in updateData
       latest[updateObject[1]] = updateObject[4]
+    if isfirst && updateData.length > 0
+      title = {__ 'Plugin update'}
+      content =
+

+          {
+            for plugin, index in plugins
+              if semver.lt(plugin.version, latest[plugin.packageName])
+                {plugin.displayName}
+          }
+          have newer version. Please update your plugins.
+

+      toggleModal title, content
     @setState
       latest: latest
       checking: false
-  checkUpdate: (callback) ->
+  checkUpdate: (callback, isfirst) ->
     npm.load {prefix: "#{PLUGIN_PATH}", registry: mirror[@state.mirror].server}, (err) ->
       npm.config.set 'depth', 1
       npm.commands.outdated [], (er, data) ->
-        callback(data)
+        callback(data, isfirst)
     @setState
       checking: true
   componentDidMount: ->
-    @checkUpdate(@solveUpdate)
+    @checkUpdate(@solveUpdate, true)
   render: ->
     <form>
       <Divider text={__ 'Plugins'} />
@@ -200,7 +212,7 @@
       <Grid>
         <Col xs={12} style={padding: '10px 15px'}>
           <ButtonGroup bsSize='small' style={width: '75%'}>
-            <Button onClick={@checkUpdate.bind(@, @solveUpdate)}
+            <Button onClick={@checkUpdate.bind(@, @solveUpdate, false)}
                     disabled={@state.checking}
                     className="control-button"
                     style={width: '33%'}>