Return active users count atop of htmlHomeTop (to app clients)

--- wrapper/index.coffee
+++ wrapper/index.coffee
@@ -221,7 +221,8 @@
 			htmlHomeTop: ''
 			htmlHomeBottom: ''
 		}
-		retObj.htmlHomeTop = 'Got any question? Send us a message to our Facebook page. Your feedback is highly appreciated!'
+		retObj.htmlHomeTop = "#{etabits.stats.activeUsers} users online
"
+		retObj.htmlHomeTop += 'Got any question? Send us a message to our Facebook page. Your feedback is highly appreciated!'
 		if self.doc.actionsCounter > 10
 			retObj.htmlHomeBottom = 'fb.com/SVUHelper: App Facebook page'
 		if self.doc.passwordExpired


remove ngCookies package

remove ngCookies package

--- www/js/locale.coffee
+++ www/js/locale.coffee
@@ -1,4 +1,4 @@
-angular.module('locale', ['pascalprecht.translate', 'ngCookies'])
+angular.module('locale', ['pascalprecht.translate'])

 	.config ($translateProvider) ->
 		$translateProvider


Make sure that sfnt key exists in dfont so we don't throw random errors

--- lib/font/dfont.coffee
+++ lib/font/dfont.coffee
@@ -87,7 +87,7 @@
     data = @contents
     pos = data.pos

-    entry = @map.sfnt.named[name]
+    entry = @map.sfnt?.named[name]
     throw new Error "Font #{name} not found in DFont file." unless entry

     data.pos = entry.offset


Added back parallelism

Added back parallelism

--- lib/json_utils.coffee
+++ lib/json_utils.coffee
@@ -75,7 +75,7 @@
   @renderJSONDSL = (model, dsl, options, callback) ->
     (callback = options; options = {}) if arguments.length is 3

-    queue = new Queue(1)
+    queue = new Queue()
     result = {}
     for key, args of dsl
       do (key, args) ->


remove classWithModifiers in osu common

remove classWithModifiers in osu common

--- resources/assets/coffee/osu_common.coffee
+++ resources/assets/coffee/osu_common.coffee
@@ -12,15 +12,6 @@
   bottomPageDistance: ->
     body = document.documentElement ? document.body.parent ? document.body
     (body.scrollHeight - body.scrollTop) - body.clientHeight
-
-
-  classWithModifiers: (className, modifiers) ->
-    ret = className
-
-    if modifiers?
-      ret += " #{className}--#{modifier}" for modifier in modifiers when modifier?
-
-    ret

   currentUserIsFriendsWith: (user_id) ->


Prevent showing empty error message

Prevent showing empty error message

Laravel now passes error json as well with same key as custom ones.

--- resources/assets/coffee/osu_common.coffee
+++ resources/assets/coffee/osu_common.coffee
@@ -268,7 +268,7 @@

     message ?= xhr?.responseJSON?.error

-    if !message?
+    if !message? || message == ''
       errorKey = "errors.codes.http-#{xhr?.status}"
       message = osu.trans errorKey
       message = osu.trans 'errors.unknown' if message == errorKey


correctly handle storage bucket copy (on for submit)

--- imports/ui/storage/bucket.cjsx
+++ imports/ui/storage/bucket.cjsx
@@ -39,14 +39,14 @@

               content_copy

-            <form id="start-app-form" role="form" className="dropdown-menu dropdown-menu-right" style={padding:'1em'}>
+            <form id="start-app-form" onSubmit={@copy(bucket)} role="form" className="dropdown-menu dropdown-menu-right" style={padding:'1em'}>

                     Bucket name
                     <input ref='bucketName' required pattern="[a-zA-Z0-9_-]+" type="text"
                       className="form-control bucket-name"
                       placeholder="A unique name. Must match [a-zA-Z0-9_-]+" />

-                <button type="submit" onClick={@copy(bucket)} className="start-app btn btn-mini">Copy</button>
+                <button type="submit" className="start-app btn btn-mini">Copy</button>
             </form>


Better layout for protocol

Better layout for protocol

--- client/components/Submit.coffee
+++ client/components/Submit.coffee
@@ -132,6 +132,8 @@
                         Stderr:

{@props.result.error_output}

                     </Col>
+                </Grid>
+                <Grid fluid>
                     <Col xs={12} sm={12} md={6} lg={6}>
                         Output:

{@props.result.output}


Collab: copy change

Collab: copy change

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -1481,8 +1481,8 @@

   showKickedModal: ->
     options        =
-      title        : 'Session ended'
-      content      : "You have been removed from the session by @#{@collaborationHost}."
+      title        : 'Your session has been closed'
+      content      : "You have been removed from the session by @#{@collaborationHost}. - Please reload your browser -"
       blocking     : yes
       buttons      :
         ok         :
@@ -1495,8 +1495,8 @@
   showSessionEndedModal: ->

     options        =
-      title        : 'Session Ended'
-      content      : "This session ended by @#{@collaborationHost} You won't be able to access it anymore."
+      title        : 'Session ended'
+      content      : "This session ended by @#{@collaborationHost} You won't be able to access it anymore. - Please reload your browser -"
       blocking     : yes
       buttons      :
         quit       :
@@ -1512,8 +1512,8 @@
   handleParticipantLeaveAction: ->

     options   =
-      title   : 'Are you sure'
-      content : "If you leave this session you won't be able to return this session."
+      title   : 'Are you sure?'
+      content : "If you leave this session you won't be able to return back."

     @showModal options, =>
       @broadcastMessages.push origin: KD.nick(), type: 'ParticipantWantsToLeave'


Collaboration: Leave session if host ping is timed out

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -1639,5 +1639,10 @@
       KD.utils.repeat pongInterval, =>
         lastPing = @pingTime.getText()

-        if Date.now() - lastPing > diffInterval
-          log 'falling behind'
+        return  if Date.now() - lastPing < diffInterval
+
+        KD.remote.api.Collaboration.stop @rtmFileId, @workspaceData, (err) =>
+          if err
+          then console.warn err
+          else @showSessionEndedModal \
+            "@#{@collaborationHost} has left the session."


split out check for field validity into method

--- client/lanes/models/Base.coffee
+++ client/lanes/models/Base.coffee
@@ -222,16 +222,17 @@
     sync: (options...) ->
         Lanes.Models.Sync.state(options...)

-    # Check if an attribute named "name" can be set to "value"
-    # Returns an empty string if value, and an appropriate error message if not
+    # Check if an attribute named "name" is invalid
+    # Returns an empty string if valid, and an appropriate error message if not
     invalidMessageFor: (name) ->
-        return '' unless @unmaskedInvalidFields and _.includes(@requiredAttributes, name) and
+        if @shouldCheckFieldValidity(name) and @isBlank(name)
+            "Cannot be empty"
+        else
+            ''
+    shouldCheckFieldValidity: (name) ->
+        @unmaskedInvalidFields and
+            _.includes(@requiredAttributes, name) and
             _.includes(@unmaskedInvalidFields, name)
-
-        if @isBlank(name)
-            "Cannot be empty"
-        else
-            ''

     maskInvalidFields: ->
         delete @unmaskedInvalidFields
@@ -244,6 +245,7 @@
             @unmaskedInvalidFields ||= []
             if _.includes(@requiredAttributes, attr) and !_.includes(@unmaskedInvalidFields, attr)
                 @unmaskedInvalidFields.push(attr)
+
                 @trigger("invalid-field:#{attr}", this)

     _calculateInvalidAttributes: ->


Re-organization of cards.coffee--separated Kingdom Victory cards, made them all derive from c.Estate, implemented Island, (re-)implemented Fairgrounds.

--- cards.coffee
+++ cards.coffee
@@ -222,7 +222,7 @@
   startingSupply: (state) ->
     switch state.nPlayers
       when 1, 2 then 8
-      else then 12
+      else 12
 }

 makeCard 'Duchy', c.Estate, {cost: 5, vp: 3}
@@ -305,12 +305,94 @@
 makeCard 'Bazaar', action, {
   cost: 5, actions: 2, cards: 1, coins: 1
 }
-makeCard 'Harem', c.Silver, {
+
+
+# Kingdom Victory cards
+# ---------------------
+# These cards are all derived from Estate to insure their starting supply
+# amount is correct. This goes for multi-type Victory cards too--deriving Great Hall
+# from action instead of Estate results in 10 Great Halls in the supply instead of
+# 8 for a 2-player game or 12 for more players.
+
+makeCard 'Duke', c.Estate, {
+  cost: 5
+  getVP: (state) -> state.current.countInDeck('Duchy')
+}
+
+makeCard 'Fairgrounds', c.Estate, {
   cost: 6
-  isVictory: true
+  getVP: (state) ->
+    unique = []
+    deck = state.current.getDeck()
+    for card in deck
+      if card not in unique
+        unique.push(card)
+    2 * Math.floor(unique.length / 5)
+}
+
+makeCard 'Gardens', c.Estate, {
+  cost: 4
+  getVP: (state) -> Math.floor(state.current.getDeck().length / 10)
+}
+
+makeCard 'Great Hall', c.Estate, {
+  isAction: true
+  cost: 3
+  cards: +1
+  actions: +1
+}
+
+makeCard 'Harem', c.Estate, {
+  isTreasure: true
+  cost: 6
+  coins: 2
   vp: 2
 }

+makeCard 'Island', c.Estate, {
+  isAction: true
+  cost: 4
+  vp: 2
+
+  playEffect: (state) ->
+    if state.current.hand.length == 0 # handle a weird edge case
+      state.log("…setting aside the Island (no other cards in hand).")
+    else
+      card = state.current.ai.choose('island', state, state.current.hand)
+      state.log("…setting aside the Island and a #{card}.")
+      state.current.hand.remove(card)
+      state.current.mats.island.push(card)
+
+    # removing the Island from play is conditional so it won't break with
+    # Throne Room and King's Court
+    if this in state.current.inPlay
+      state.current.inPlay.remove(this)
+    state.current.mats.island.push(this)
+}
+
+makeCard 'Nobles', c.Estate, {
+  isAction: true
+  cost: 6
+  vp: 2
+
+  # Nobles is an example of a card that allows a choice from multiple
+  # simple effects. We implement this using the `choose('benefit')` AI method,
+  # which is passed a list of benefit objects, one of which it will choose
+  # to apply to the state.
+  playEffect:
+    (state) ->
+      benefit = state.current.ai.choose('benefit', state, [
+        {actions: 2},
+        {cards: 3}
+      ])
+      applyBenefit(state, benefit)
+}
+
+makeCard 'Vineyard', c.Estate, {
+  cost: 0
+  costPotion: 1
+  getVP: (state) -> Math.floor(state.current.numActionCardsInDeck() / 3)
+}
 # Duration cards
 # --------------
 # These cards have additional properties, such as `durationActions`, defining
@@ -791,16 +873,6 @@
           state.revealHand(opp)
 }

-makeCard "Duke", c.Estate, {
-  cost: 5
-  getVP: (state) ->
-    vp = 0
-    for card in state.current.getDeck()
-      if card is c.Duchy
-        vp += 1
-    vp
-}
-
 makeCard 'Explorer', action, {
   cost: 5

@@ -848,11 +920,6 @@
   playEffect: (state) ->
     state.attackOpponents (opp) ->
       state.gainCard(opp, c.Curse)
-}
-
-makeCard "Gardens", c.Estate, {
-  cost: 4
-  getVP: (state) -> Math.floor(state.current.getDeck().length / 10)
 }

 # Goons: *see Militia*
@@ -1173,24 +1240,6 @@
       else
         state.gainCard(opp, c.Copper)
         state.gainCard(opp, c.Curse)
-}
-
-makeCard 'Nobles', action, {
-  cost: 6
-  isVictory: true
-  vp: 2
-
-  # Nobles is an example of a card that allows a choice from multiple
-  # simple effects. We implement this using the `chooseBenefit` AI method,
-  # which is passed a list of benefit objects, one of which it will choose
-  # to apply to the state.
-  playEffect:
-    (state) ->
-      benefit = state.current.ai.chooseBenefit(state, [
-        {actions: 2},
-        {cards: 3}
-      ])
-      applyBenefit(state, benefit)
 }

 makeCard 'Pawn', action, {
@@ -1594,12 +1643,6 @@
     if treasuresDrawn == 1
       state.current.inPlay.push(foundTreasure)
       foundTreasure.onPlay(state)
-}
-
-makeCard 'Vineyard', c.Estate, {
-  cost: 0
-  costPotion: 1
-  getVP: (state) -> Math.floor(state.current.numActionCardsInDeck() / 3)
 }

 makeCard 'Walled Village', c.Village, {


fix some potential errors in lantern process

--- server/api/service/lantern/process.coffee
+++ server/api/service/lantern/process.coffee
@@ -211,16 +211,16 @@
       result.confidence = 1 if not result.confidence
       result.publisher = crossref.publisher
       result.provenance.push 'Added publisher name from Crossref'
-      if not result.issn and crossref.ISSN and crossref.ISSN.length > 0
+      if not result.issn and crossref.ISSN? and crossref.ISSN.length > 0
         result.issn = crossref.ISSN[0]
         result.provenance.push 'Added ISSN from Crossref'
-      if not result.journal_title and crossref['container-title'] and crossref['container-title'].length > 0
+      if not result.journal_title and crossref['container-title']? and crossref['container-title'].length > 0
         result.journal_title = crossref['container-title'][0]
         result.provenance.push 'Added journal title from Crossref'
       if not result.authors and crossref.author
         result.authors = crossref.author
         result.provenance.push 'Added author list from Crossref'
-      if not result.title and crossref.title and crossref.title.length > 0
+      if not result.title and crossref.title? and crossref.title.length > 0
         result.title = crossref.title[0]
         result.provenance.push 'Added article title from Crossref'
     else
@@ -233,7 +233,7 @@
       if not result.authors and core.authors
         result.authors = core.author
         result.provenance.push 'Added authors from CORE'
-      if core.repositories?.length > 0
+      if core.repositories? and core.repositories.length > 0
         for rep in core.repositories
           rc = {name:rep.name}
           rc.oai = rep.oai if rep.oai?
@@ -279,7 +279,7 @@
       try
         domain = base.dclink.split('://')[1].split('/')[0]
         repo = API.use.opendoar.search domain
-        if repo.total is 1 and repo.data[0].url and repo.data[0].url.indexOf(domain) isnt -1
+        if repo.total is 1 and repo.data[0].url? and typeof repo.data[0].url is 'string' and repo.data[0].url.indexOf(domain) isnt -1
           result.repositories.push({
             fulltexts:[base.dclink],
             url: repo.data[0].url,
@@ -301,7 +301,7 @@
   else
     result.provenance.push 'Not attempting Crossref / CORE / BASE lookups - do not have DOI for article.'

-  if result.grants.length > 0
+  if result.grants? and result.grants.length > 0
     grants = []
     for gr in result.grants
       if gr.grantId
@@ -343,8 +343,8 @@
     if doaj.status isnt 'error'
       result.pure_oa = true
       result.provenance.push 'Confirmed journal is listed in DOAJ'
-      result.publisher ?= doaj.data.bibjson.publisher
-      result.journal_title ?= doaj.data.bibjson.title
+      result.publisher ?= doaj.bibjson.publisher
+      result.journal_title ?= doaj.bibjson.title
     else
       result.provenance.push 'Could not find journal in DOAJ'

@@ -353,13 +353,13 @@
       try journal = romeo.journals[0].journal[0]
       try publisher = romeo.publishers[0].publisher[0]
       if not result.journal_title
-        if journal?.jtitle and journal.jtitle.length > 0
+        if journal?.jtitle? and journal.jtitle.length > 0
           result.journal_title = journal.jtitle[0]
           result.provenance.push 'Added journal title from Sherpa Romeo'
         else
           result.provenance.push 'Tried, but could not add journal title from Sherpa Romeo.'
       if not result.publisher
-        if publisher?.name and publisher.name.length > 0
+        if publisher?.name? and publisher.name.length > 0
           result.publisher = publisher.name[0]
           result.provenance.push 'Added publisher from Sherpa Romeo'
         else
@@ -391,7 +391,7 @@
   if not result.licence or result.licence not in ['cc-by','cc-zero']
     publisher_licence_check_ran = true
     url = API.http.resolve('https://doi.org/'+result.doi) if result.doi
-    if url and url.indexOf('europepmc') is -1 # if it resolves to eupmc then it would already have been checked above
+    if url? and typeof url is 'string' and url.indexOf('europepmc') is -1 # if it resolves to eupmc then it would already have been checked above
       lic = API.service.lantern.licence url
       if lic.licence and lic.licence isnt 'unknown'
         result.licence = lic.licence


#1386 - Skip logic operators can't be modified

--- jsapp/xlform_model_view/view.rowDetail.SkipLogic.coffee
+++ jsapp/xlform_model_view/view.rowDetail.SkipLogic.coffee
@@ -263,7 +263,7 @@
         @presenter.change_question e.val

     bind_operator_picker: () ->
-      @$operator_picker.on 'blur', () =>
+      @$operator_picker.on 'change', () =>
         @operator_picker_view.value = @$operator_picker.select2 'val'
         @presenter.change_operator @operator_picker_view.value


Removed the duplicate Cutpurse.

Removed the duplicate Cutpurse.

--- cards.coffee
+++ cards.coffee
@@ -1338,20 +1338,6 @@

     nVictory = (card for card in state.current.hand when card.isVictory).length
     state.drawCards(state.current, nVictory)
-}
-
-makeCard 'Cutpurse', action, {
-  cost: 4
-  coins: +2
-  isAttack: true
-
-  playEffect: (state) ->
-      state.attackOpponents (opp) ->
-        if c.Copper in opp.hand
-          opp.doDiscard(c.Copper)
-        else
-          state.log("#{opp.ai} has no Copper in hand.")
-          state.revealHand(opp)
 }

 makeCard 'Embassy', action, {


array and field access

array and field access

--- chuck.coffee
+++ chuck.coffee
@@ -11,6 +11,10 @@
     return calculateExpressionComplexity expression.inverse
   if expression?.parenthesized?
     return calculateExpressionComplexity expression.parenthesized
+  if expression?.receiver?
+    cc = calculateExpressionComplexity expression.receiver
+    cc += calculateExpressionComplexity expression.index if expression.index?
+    return cc
   if expression?.argv?
     cc = 0
     cc += calculateExpressionComplexity arg for arg in expression.argv


Remove logging

Remove logging

--- lib/simulator2.coffee
+++ lib/simulator2.coffee
@@ -123,7 +123,6 @@

 simulateShelling = (fleet, enemyFleet, enemyEscort, hougeki, subtype) ->
   return unless hougeki?
-  console.log(hougeki)
   isNight = (subtype == StageType.Night)
   list = []
   for at, i in hougeki.api_at_list


Add command for small and large table in demos

--- lib/table-edit.coffee
+++ lib/table-edit.coffee
@@ -31,41 +31,62 @@
     TableView.registerViewProvider(true)

     atom.commands.add 'atom-workspace',
-      'table-edit:demo-in-pane': => @openDemoInPane()
+      'table-edit:demo-large': => atom.workspace.open('table://large')
+      'table-edit:demo-small': => atom.workspace.open('table://small')

-    atom.workspace.addOpener (uriToOpen) ->
+    atom.workspace.addOpener (uriToOpen) =>
       url ||= require 'url'

       {protocol, host} = url.parse uriToOpen
       return unless protocol is 'table:'

-      table = new Table
+      switch host
+        when 'large' then @getLargeTable()
+        when 'small' then @getSmallTable()

-      table.addColumn 'key', width: 150, align: 'right'
-      table.addColumn 'value', width: 150, align: 'center'
+  getSmallTable: ->
+    table = new Table
+
+    table.addColumn 'key', width: 150, align: 'right'
+    table.addColumn 'value', width: 150, align: 'center'
+    table.addColumn 'locked', width: 150, align: 'left'
+
+    for i in [0...100]
+      table.addRow [
+        "row#{i}"
+        Math.random() * 100
+        if i % 2 is 0 then 'yes' else 'no'
+      ]
+
+    table.clearUndoStack()
+
+    return table
+
+  getLargeTable: ->
+    table = new Table
+
+    table.addColumn 'key', width: 150, align: 'right'
+    table.addColumn 'value', width: 150, align: 'center'
+    for i in [0..100]
+      table.addColumn 'column_' + i, width: 150, align: 'left'
+
+    for i in [0...1000]
+      data = [
+        "row#{i}"
+        Math.random() * 100
+      ]
       for i in [0..100]
-        table.addColumn 'column_' + i, width: 150, align: 'left'
+        if i % 2 is 0
+          data.push if i % 2 is 0 then 'yes' else 'no'
+        else
+          data.push Math.random() * 100

-      for i in [0...1000]
-        data = [
-          "row#{i}"
-          Math.random() * 100
-        ]
-        for i in [0..100]
-          if i % 2 is 0
-            data.push if i % 2 is 0 then 'yes' else 'no'
-          else
-            data.push Math.random() * 100
+      table.addRow data

-        table.addRow data
+    table.clearUndoStack()

-      table.clearUndoStack()
-
-      return table
+    return table

   deactivate: ->

   serialize: ->
-
-  openDemoInPane: ->
-    atom.workspace.open('table://demo')


Always return a boolean

Always return a boolean

--- lib/tag-finder.coffee
+++ lib/tag-finder.coffee
@@ -131,7 +131,7 @@
   # given fragment then the end point of that pair does not count as a matching
   # closing tag.
   tagDoesNotCloseInFragment: (tags, fragment) ->
-    return if tags.length is 0
+    return false if tags.length is 0

     stack = tags
     stackLength = stack.length


Fix permission bug when editing group

Fix permission bug when editing group

--- lib/www_routes.coffee
+++ lib/www_routes.coffee
@@ -273,13 +273,12 @@
     unless utils.is_authenticated(req.session)
       return www_methods.permission_denied(req, res)

-    schema.Group.findOne {slug: req.params.slug}, (err, doc) ->
+    schema.Group.findOne {slug: req.params.slug}, (err, group) ->
       return www_methods.handle_error(req, res) if err?
-      return not_found(req, res) unless doc?
+      return not_found(req, res) unless group?

       # Ensure that we are a member of this group.
-      user = _.find(req.session.users, (u) -> u.email == req.session.auth.email)
-      membership = _.find doc.members, (m) -> m.user.email == user.email
+      membership = _.find group.members, (m) -> m.user.toString() == req.session.auth.user_id
       unless membership?
         return www_methods.permission_denied(req, res)


Greatly reduce the number of observables in the ActionLog output. Doesn't do much for the render time, sadly, but observables go from 12000, to 200 or so.

--- js/inner.coffee
+++ js/inner.coffee
@@ -42,25 +42,25 @@

 class LogOutput extends Base
   constructor: (json) ->
-    super json
-
-    @output_style = @komp =>
-      out: @type() == "out"
-      err: @type() == "err"
+    super json, {}, ["type"], false
+
+  output_style: =>
+    out: @type == "out"
+    err: @type == "err"

 class ActionLog extends Base
   constructor: (json) ->
     json.out = (new LogOutput(j) for j in json.out) if json.out
-    super json, {timedout: null, exit_code: 0, out: null, minimize: true}
+    super json, {timedout: null, exit_code: 0, out: null, minimize: true}, ["end_time", "timedout", "exit_code", "run_time_millis"]

     @status = @komp =>
-      if @end_time() == null
+      if @end_time == null
         "running"
-      else if @timedout()
+      else if @timedout
         "timedout"

-      else if (@exit_code() == null || @exit_code() == 0)
+      else if (@exit_code == null || @exit_code == 0)
         "success"
       else
         "failed"
@@ -85,7 +85,7 @@
       minimize: @minimize()

     @duration = @komp () =>
-      Circle.time.as_duration(@run_time_millis())
+      Circle.time.as_duration(@run_time_millis)

   toggle_minimize: =>
     @minimize(!@minimize())


Add unable to find project message

Add unable to find project message

--- plugins/gitlab-issues/index.coffee
+++ plugins/gitlab-issues/index.coffee
@@ -19,6 +19,7 @@
   @receiveEvent: (config, event, callback) =>
     @getProject config, (err, project) =>
       return callback(err) if err
+      return callback("Unable to find project") unless project?

       # Build the ticket
       payload =


[LFTUI-109] avoiding an immediately invoked function in permission/stream lookupc

--- src/coffee/directives/user_streams.coffee
+++ src/coffee/directives/user_streams.coffee
@@ -14,7 +14,8 @@
       found = null

       for p in $scope.permissions
-        found = p if p.stream == stream.id
+        if p.stream == stream.id
+          found = p

       found


Only bind key on Mac OS X

Only bind key on Mac OS X

--- keymaps/links.cson
+++ keymaps/links.cson
@@ -1,2 +1,2 @@
-'.workspace':
+'.workspace .platform-darwin':
   'ctrl-O': 'link:open'


fix builder

fix builder

--- kit/builder.coffee
+++ kit/builder.coffee
@@ -139,7 +139,6 @@
                         self.copy_to_dist from, to
                 ).then ->
                     opts_webapp = _.cloneDeep opts_pc
-                    opts_webapp.pragmas.FlashCoreExclude = true
                     opts_webapp.modules = [
                         {
                             name: 'muplayer/player'
@@ -147,6 +146,7 @@
                     ]

                     opts_webapp =_.extend(opts_webapp, options.webapp)
+                    opts_webapp.pragmas.FlashCoreExclude = true

                     # Webapp
                     requirejs.optimize opts_webapp, (buildResponse) ->


config: kontrol-prod uses kontrol mongo/rabbit

--- config/main.kontrol-prod.coffee
+++ config/main.kontrol-prod.coffee
@@ -146,9 +146,9 @@
     webPort     : 3020
   kontrold        :
     mongo         :
-      host        : '127.0.0.1'
+      host        : 'kontrol.in.koding.com'
     rabbitmq      :
-      host        : 'localhost'
+      host        : 'kontrol.in.koding.com'
       port        : '5672'
       login       : 'guest'
       password    : 'HV5l856bME'


port change from openstax/tutor-js#937 to this shared mixin

--- src/components/resize-listener-mixin.cjsx
+++ src/components/resize-listener-mixin.cjsx
@@ -40,12 +40,13 @@
     {width, height}

   _getComponentSize: ->
+    return {height: 0, width: 0} unless @isMounted()
     componentNode = @getDOMNode()
-
     width: componentNode.offsetWidth
     height: componentNode.offsetHeight

   setInitialSize: ->
+    return unless @isMounted()
     windowEl = @_getWindowSize()
     componentEl = @_getComponentSize()


Encode download file name with encodeURI

--- lib/cwmp.coffee
+++ lib/cwmp.coffee
@@ -542,7 +542,7 @@
       acsRequest.url = if FS_SSL then 'https://' else 'http://'
       acsRequest.url += FS_HOSTNAME
       acsRequest.url += ":#{FS_PORT}" if FS_PORT != 80
-      acsRequest.url += "/#{encodeURIComponent(acsRequest.fileName)}"
+      acsRequest.url += "/#{encodeURI(acsRequest.fileName)}"

     if not acsRequest.fileSize?
       return localCache.getFiles((err, hash, files) ->


Fix deprecations

Fix deprecations

--- lib/main.coffee
+++ lib/main.coffee
@@ -1,5 +1,4 @@
 url = require 'url'
-{ TextEditor } = require 'atom'

 SvgPreviewView = null # Defer until used

@@ -86,10 +85,9 @@
     grammar = item?.getGrammar?()?.scopeName

     return (
-      item instanceof TextEditor and (
-        grammar is 'text.xml.svg' or
-        ( grammar in grammars and item.getText().match(/<svg/) )
-      )
+      ( item?.getBuffer? and item?.getText? and item?.getGrammar?)
+      grammar is 'text.xml.svg' or
+      ( grammar in grammars and item.getText().match(/<svg/) )
     )

   toggle: ->


fix global path dependency

fix global path dependency

--- lib/main.coffee
+++ lib/main.coffee
@@ -1,5 +1,6 @@
 {CompositeDisposable, TextEditor} = require 'atom'
 CursorHistory = require './history'
+path = require 'path'

 module.exports =
   history: null
@@ -89,11 +90,6 @@
   serialize: ->
     @history?.serialize()

-  # createMarker: (editor, point, properties) ->
-  #   marker = editor.markBufferPosition point, invalidate: 'never', persistent: false
-  #   marker.setProperties properties
-  #   marker
-
   handleCursorMoved: ({oldBufferPosition, newBufferPosition, cursor}) ->
     unless @cursorMoveTracking
       @debug " -- Tracking Skip in CursorMoved"
@@ -103,8 +99,6 @@
     editor = cursor.editor
     return if editor.hasMultipleCursors()

-    # if atom.config.get('cursor-history.debug')
-    #   console.log [oldBufferPosition.toString(), newBufferPosition.toString(), cursor.editor.getURI()]
     args = [editor, oldBufferPosition, editor.getURI()]
     if @direction
       if @direction is 'prev' and @history.isNewest()
@@ -118,7 +112,6 @@
   needRemember: (oldBufferPosition, newBufferPosition, cursor) ->
     # [FIXME] currently buffer without URI is simply ignored.
     # is there any way to support 'untitled' buffer?
-    # console.log [oldBufferPosition, newBufferPosition]
     return false unless cursor.editor.getURI()

     # Line number delata exceeds or not.


Move backwards-compatible deserializers.add call back into activate

--- lib/main.coffee
+++ lib/main.coffee
@@ -10,6 +10,11 @@

 module.exports =
   activate: ->
+    if parseFloat(atom.getVersion()) < 1.7
+      atom.deserializers.add
+        name: 'MarkdownPreviewView'
+        deserialize: module.exports.createMarkdownPreviewView.bind(module.exports)
+
     atom.commands.add 'atom-workspace',
       'markdown-preview:toggle': =>
         @toggle()
@@ -108,9 +113,3 @@
         console.warn('Copying Markdown as HTML failed', error)
       else
         atom.clipboard.write(html)
-
-if parseFloat(atom.getVersion()) < 1.7
-  atom.deserializers.add(
-    name: 'MarkdownPreviewView'
-    deserialize: module.exports.createMarkdownPreviewView.bind(module.exports)
-  )


use cursor.getBufferRow() instead of an awful chain

--- lib/main.coffee
+++ lib/main.coffee
@@ -191,7 +191,7 @@

         cursor = @editor.getLastCursor()

-        row = cursor.marker.bufferMarker.range.start.row
+        row = cursor.getBufferRow()
         console.log "row:", row

         indentLevel = cursor.getIndentLevel()


:minibus: Rename ignoredMatchedFiles to ignoreGlob

--- lib/main.coffee
+++ lib/main.coffee
@@ -15,8 +15,8 @@
       default: 300
       order: 1

-    ignoreMatchedFiles:
-      title: 'Do Not Lint Files that match this Glob'
+    ignoreGlob:
+      title: 'Ignore files matching this Glob'
       type: 'string'
       default: '{\,/}**{\,/}*.min.{js,css}'
       order: 2


pathing fix

pathing fix

--- lib/main.coffee
+++ lib/main.coffee
@@ -10,7 +10,7 @@
     fusker.config.silent = true
     server = fusker.http.createServer port, username, password
     io = fusker.socket.listen server
-    breakf = path.join __dirname, "./break"
+    breakf = path.join __dirname, "../break"
     io.sockets.on 'connection', (socket) ->
       socket.cwd ?= process.cwd()
       socket.emit 'cwd', socket.cwd


cleanup

cleanup

--- lib/main.coffee
+++ lib/main.coffee
@@ -1,14 +1,14 @@
 path = require 'path'
 {CompositeDisposable} = require 'atom'

-createLabelElement = (labelChar, className=null) ->
+createLabel = (labelChar, className) ->
   element = document.createElement("div")
   element.classList.add("choose-pane")
   element.classList.add(className) if className
   element.textContent = labelChar
   element

-removeLabelElemnts = ->
+removeLabels = ->
   for element in document.querySelectorAll("div.choose-pane")
     element.remove()

@@ -91,7 +91,8 @@
         when isFocused(target) then 'active'
         when target is lastFocusedTarget then 'last-focused'

-      getView(target).appendChild(createLabelElement(labelChar, className))
+      label = createLabel(labelChar, className)
+      getView(target).appendChild(label)
       targetByLabel[labelChar.toLowerCase()] = target

     # Special label used for focus-last-focused
@@ -100,12 +101,12 @@
     focusedElement = document.activeElement
     restoreFocus = -> focusedElement?.focus()

-    @input.readInput().then (char) =>
+    @input.readInput().then (char) ->
       if target = targetByLabel[char.toLowerCase()]
         focusTarget(target)
       else
         restoreFocus()
-      removeLabelElemnts()
-    .catch =>
+      removeLabels()
+    .catch ->
       restoreFocus()
-      removeLabelElemnts()
+      removeLabels()


pass linter

pass linter

--- lib/main.coffee
+++ lib/main.coffee
@@ -34,8 +34,8 @@
     editor = atom.workspace.getActiveTextEditor()

     unless editor?
-       atom.notifications.addWarning("**language-bridlensis**: No active editor", dismissable: false)
-       return
+      atom.notifications.addWarning("**language-bridlensis**: No active editor", dismissable: false)
+      return

     script = editor.getPath()
     scope  = editor.getGrammar().scopeName


StatusBar: Update after running kernel command

Fixes #321

--- lib/main.coffee
+++ lib/main.coffee
@@ -120,14 +120,16 @@
         else if command is 'restart-kernel'
             KernelManager.destroyRunningKernel kernel
             @clearResultBubbles()
-            KernelManager.startKernelFor grammar
+            KernelManager.startKernelFor grammar, (kernel) =>
+                @setStatusBarElement kernel.statusView.getElement()

         else if command is 'switch-kernel'
             kernel = KernelManager.getRunningKernelFor language
             KernelManager.destroyRunningKernel kernel
             @clearResultBubbles()
             KernelManager.setKernelMapping kernelSpec, grammar
-            KernelManager.startKernel kernelSpec, grammar
+            KernelManager.startKernel kernelSpec, grammar, (kernel) =>
+                @setStatusBarElement kernel.statusView.getElement()

     getCurrentKernel: ->
         grammar = @editor.getGrammar()


Add order to config schema

Add order to config schema

--- lib/main.coffee
+++ lib/main.coffee
@@ -6,6 +6,7 @@
       type: 'string'
       title: 'Editor Font'
       description: 'Use one of the fonts available in this package.'
+      order: 1
       default: '(Default)'
       enum: [
         '(Default)'
@@ -20,6 +21,7 @@
       type: 'string'
       title: 'Markdown Preview Font'
       description: 'Use one of the fonts available in this package.'
+      order: 2
       default: '(Default)'
       enum: [
         '(Default)'
@@ -42,6 +44,7 @@
       type: 'string'
       title: 'Workspace Font'
       description: 'Use one of the fonts available in this package.'
+      order: 3
       default: '(Default)'
       enum: [
         '(Default)'


pusing the fix that was supposed to be pushed yesterday

made the luajit folder always be found in the .atom folder, it's easier
that way.

--- lib/main.coffee
+++ lib/main.coffee
@@ -28,7 +28,7 @@
           stream: 'stderr'
           allowEmptyStderr: true

-        return helpers.exec(__dirname + "\\luajit\\luajit.exe", parameters, execOpts).then (output) ->
+        return helpers.exec(__dirname + "..\\..\\..\\\luajit\\luajit.exe", parameters, execOpts).then (output) ->
           return helpers.parse(output, regex, filePath: textEditor.getPath()).map (error) ->
             error.type = 'Error'
             return error


Fix bounds and amounts in scrollIntoView

--- content_scripts/scroller.coffee
+++ content_scripts/scroller.coffee
@@ -256,22 +256,22 @@
     rect = element. getClientRects()?[0]
     if rect?
       # Scroll y axis.
-      if rect.top < 0
-        amount = rect.top - 10
+      if rect.bottom < 0
+        amount = rect.bottom - Math.min(rect.height, window.innerHeight)
         element = findScrollableElement element, "y", amount, 1
         CoreScroller.scroll element, "y", amount, false
-      else if window.innerHeight < rect.bottom
-        amount = rect.bottom - window.innerHeight + 10
+      else if window.innerHeight < rect.top
+        amount = rect.top + Math.min(rect.height - window.innerHeight, 0)
         element = findScrollableElement element, "y", amount, 1
         CoreScroller.scroll element, "y", amount, false

       # Scroll x axis.
-      if rect.left < 0
-        amount = rect.left - 10
+      if rect.right < 0
+        amount = rect.right - Math.min(rect.width, window.innerWidth)
         element = findScrollableElement element, "x", amount, 1
         CoreScroller.scroll element, "x", amount, false
-      else if window.innerWidth < rect.right
-        amount = rect.right - window.innerWidth + 10
+      else if window.innerWidth < rect.left
+        amount = rect.left + Math.min(rect.width - window.innerWidth, 0)
         element = findScrollableElement element, "x", amount, 1
         CoreScroller.scroll element, "x", amount, false


Add Day Schedules to Fabricated Partner Location


Added support.function to the scopes list

--- lib/fold-functions.coffee
+++ lib/fold-functions.coffee
@@ -83,13 +83,15 @@
     # line of the file.
     if autofold and atom.config.get('fold-functions.skipAutofoldWhenNotFirstLine')
       onFirstLine = true
+      bufferRow = null
       for cursor in editor.getCursors()
         if cursor.getBufferRow() > 0
+          bufferRow = cursor.getBufferRow()
           onFirstLine = false
           break

       if not onFirstLine
-        @debugMessage('fold function: not on first line, skipping autofold')
+        @debugMessage('fold function: not on first line, skipping autofold', bufferRow)
         autofold = false

     # figure out if we should skip autofolding because there is only one
@@ -190,7 +192,8 @@
         'meta.function',
         'meta.method',
         'storage.type.arrow',
-        'entity.name.function.constructor'
+        'entity.name.function.constructor',
+        'support.function'
       )
       @debugMessage('fold functions: is foldable', (foldable and isFunction and not isCommented))
       if foldable and isFunction and not isCommented


Slightly better movement.

Slightly better movement.

--- src/tile/objects/movableTileObject.coffee
+++ src/tile/objects/movableTileObject.coffee
@@ -20,13 +20,19 @@
   constructor : (options) ->
     options = _.defaults options or {}, {
       velocity: new eburp.Point(0,0)
+      tickRateMS: 250
     }
+    @_elapsed = 0
     super(options)
     @

   tick: (elapsed) ->
     # Early out if no velocity
     return if @velocity.x == 0 and @velocity.y == 0
+    @_elapsed += elapsed
+    return if @_elapsed < @tickRateMS
+    @_elapsed -= @tickRateMS
+
     map = @scene.objectByType eburp.TileMap
     return @point.add @velocity if !map


Rename RubocopLinter to Rubocop

Rename RubocopLinter to Rubocop

--- lib/linter/rubocop.coffee
+++ lib/linter/rubocop.coffee
@@ -3,7 +3,7 @@
 fs = require 'fs'

 module.exports =
-class RubocopLinter
+class Rubocop
   constructor: (@filePath) ->

   run: (callback) ->


deliver client

deliver client

--- lib/music-reminder.coffee
+++ lib/music-reminder.coffee
@@ -1,10 +1,15 @@
 express = require "express"
-fs = require "fs"

 app = express()
+
+app.get "/", (req, res) ->
+  res.sendfile "client/music-reminder.html"
+
+app.get "/music-reminder.js", (req, res) ->
+  res.sendfile "client/music-reminder.js"
+
 app.get "/music", (req, res) ->
-  fs.readFile "data/music.json", (e, data) ->
-    res.send JSON.parse(data)
+  res.sendfile "data/music.json"

 start = () ->
   app.listen 3000


started adding code for auto selecting chart buttons on click

--- public/dreamcatcher/controllers/admin_controller.coffee
+++ public/dreamcatcher/controllers/admin_controller.coffee
@@ -100,8 +100,9 @@
     if ev.currentTarget.id is 'entries-chart_j' then google.setOnLoadCallback @charts.getLineChartData('last 6 months in entry types','entry')
     if ev.currentTarget.id is 'entries-chart_k' then google.setOnLoadCallback @charts.getLineChartData('last 6 months in comments','entry')
     if ev.currentTarget.id is 'entries-chart_l' then google.setOnLoadCallback @charts.getPieChartData('top 32 tags','entry')
-
-
+
+    $("##{ev.currentTarget.id}").parent().addClass('select')
+
   '#bedsheetsHeader click': (el,ev) ->
     Dreamcatcher.Models.Admin.loadBedsheets @getOptions(), @callback('displayBedsheets',@bedsheetsLoaded)


Add node_modules to ignored files by default

--- lib/project-config.coffee
+++ lib/project-config.coffee
@@ -154,7 +154,7 @@
                 server: "localhost"
                 root: ""
                 active: false
-                ignore: ['.existdb.json', '.git/**']
+                ignore: ['.existdb.json', '.git/**', 'node_modules/**', 'bower_components/**']
             }
         }


Export the cache header and not modified logic from response cache

--- lib/response-cache.coffee
+++ lib/response-cache.coffee
@@ -10,8 +10,8 @@

     if cacheEntry = cache[req.url]
       if not exports.shouldSendResponse req, cacheEntry.headerInfo.etag, cacheEntry.headerInfo.lastModified
-        res.statusCode = NOT_MODIFIED
-        res.end()
+        exports.setCacheHeaders res, cacheEntry.headerInfo.etag, cacheEntry.headerInfo.lastModified
+        exports.sendNotModified res
       else
         data = cacheEntry.data
         res.writeHead cacheEntry.head.statusCode, cacheEntry.head.headers
@@ -56,6 +56,14 @@
         }

     next()
+
+exports.setCacheHeaders = (res, etag, lastModified) ->
+  res.setHeader 'Etag', etag
+  res.setHeader 'Last-Modified', lastModified.toUTCString?() ? lastModified
+
+exports.sendNotModified = (res) ->
+  res.statusCode = exports.NOT_MODIFIED
+  res.end()

 exports.shouldSendResponse = (req, etag, lastModified) ->
   etagIn = (field) ->


Remove unneeded parameter

Remove unneeded parameter

--- lib/tabs-to-spaces.coffee
+++ lib/tabs-to-spaces.coffee
@@ -77,7 +77,5 @@
         callback(obj)

   # Private: Sets the number of spaces to replace a single tab character with.
-  #
-  # spcs - {String} to replace a single tab character with.
-  setSpaces: (spcs) ->
+  setSpaces: ->
     spaces = @multiplyText(' ', atom.config.get('editor.tabLength'))


Move non-react functions to the bottom and clean up a bit

--- resources/assets/coffee/react/mp-history/content.coffee
+++ resources/assets/coffee/react/mp-history/content.coffee
@@ -20,27 +20,7 @@
 el = React.createElement

 class MPHistory.Content extends React.Component
-  teamScores: (eventIndex) ->
-    return if !@props.events[eventIndex].game?
-
-    @scoresCache ?= {}
-
-    if !@scoresCache[eventIndex]?
-      scores =
-        blue: 0
-        red: 0
-
-      return scores if !@props.events[eventIndex].game.end_time?
-
-      for score in @props.events[eventIndex].game.scores
-        continue if !score.multiplayer.pass
-        scores[score.multiplayer.team] += score.score
-
-      @scoresCache[eventIndex] = scores
-
-    return @scoresCache[eventIndex]
-
-  render: ->
+  render: =>
     if _.isEmpty @props.events
       div className: 'osu-layout__row osu-layout__row--page-mp-history',
         osu.trans 'multiplayer.match.loading-events'
@@ -73,3 +53,26 @@
                   event: event
                   lookupUser: @props.lookupUser
                   key: event.id
+
+
+  teamScores: (eventIndex) =>
+    game = @props.events[eventIndex].game
+
+    return if !game?
+
+    @scoresCache ?= {}
+
+    if !@scoresCache[eventIndex]?
+      scores =
+        blue: 0
+        red: 0
+
+      return scores if !game.end_time?
+
+      for score in game.scores
+        continue if !score.multiplayer.pass
+        scores[score.multiplayer.team] += score.score
+
+      @scoresCache[eventIndex] = scores
+
+    @scoresCache[eventIndex]


Fix \texttt{--} in LaTeX mode. #261

Fix \texttt{--} in LaTeX mode. #261


Always use latexmk placeholders (%S %O)

Always use latexmk placeholders (%S %O)

--- lib/latexmk.coffee
+++ lib/latexmk.coffee
@@ -30,8 +30,8 @@
     ]

     enableShellEscape = atom.config.get("latex.enableShellEscape")
-    pdfOpts.push("-shell-escape %O %S") if enableShellEscape?
-    args.push("-pdflatex=\"pdflatex #{pdfOpts.join(" ")}\"")
+    pdfOpts.push("-shell-escape") if enableShellEscape?
+    args.push("-pdflatex=\"pdflatex #{pdfOpts.join(" ")} %O %S\"")

     outdir = atom.config.get("latex.outputDirectory")
     if outdir?.length


update on connection and movement

update on connection and movement

--- lib/modules.coffee
+++ lib/modules.coffee
@@ -4,9 +4,10 @@
   activate: ->
     @createUI()

-    # TODO: subscribe to client creation/destruction
     @activeItemSubscription = atom.workspace.onDidChangeActivePaneItem =>
       @update()
+    comm.onConnected => @update()
+    comm.onDisconnected => @update()
     @update()

   deactivate: ->
@@ -58,9 +59,17 @@
     @sub.classList.add 'fade'

   update: ->
-    pane = atom.workspace.getActivePaneItem()
-    unless pane.getGrammar?().scopeName == 'source.julia' && comm.isConnected()
+    @moveSubscription?.dispose()
+    ed = atom.workspace.getActivePaneItem()
+    unless ed.getGrammar().scopeName == 'source.julia' && comm.isConnected()
       @clear()
       return
-    comm.msg 'module', {}, ({main, sub}) =>
+    @moveSubscription = ed.onDidChangeCursorPosition => @update()
+    {row, column} = ed.getCursors()[0].getScreenPosition()
+    data =
+      path: ed.getPath()
+      code: ed.getText()
+      row: row+1, column: column+1
+
+    comm.msg 'module', data, ({main, sub}) =>
       @reset main, sub


Cater for more completion types, icon and replacement

--- lib/psc-ide.coffee
+++ lib/psc-ide.coffee
@@ -154,10 +154,33 @@
         @getCompletion(prefix,modulePrefix)
           .then (completions) =>
             resolve completions.map (c) =>
-              text: c.identifier
-              displayText: c.identifier + ": " + @abbrevType c.type
-              description: c.type
-              type: if /->/.test(c.type) then "function" else "value"
+              type =
+                if c.type is "module"
+                  "import"
+                else if /^[A-Z]/.test(c.identifier)
+                  "type"
+                else if /->/.test(c.type)
+                  "function"
+                else
+                  "value"
+              {
+                replacementPrefix: # Keep the module prefix for values but not modules
+                  if c.type is "module"
+                    (modulePrefix||"") + originalPrefix
+                  else
+                    prefix
+                text: c.identifier
+                displayText:
+                  if c.type is "module"
+                    c.identifier
+                  else if type == "type"
+                    c.identifier + " " + @abbrevType c.type
+                  else
+                    c.identifier + ": " + @abbrevType c.type
+                description: c.type
+                type: type
+              }
+
           .catch (err) =>
             console.warn "Suggestion error: " + err


integrated datastore/usage and storage/size endpoints

--- lib/storage.coffee
+++ lib/storage.coffee
@@ -1,3 +1,4 @@
+exec          = (require 'child_process').exec
 fs            = require 'fs-extra'
 path          = require 'path'

@@ -29,6 +30,7 @@

   listStorageBuckets basePath, publishStorageBuckets
   fs.watch basePath, (eventType, filename) ->
+    console.log 'i saw something', eventType, filename
     listStorageBuckets basePath, publishStorageBuckets

   agent.on '/storage/list', (params, data, callback) ->
@@ -49,7 +51,30 @@
       srcpath = path.join basePath, source
       lockFile = path.join basePath, ".#{name}.copy.lock"
       fs.writeFile lockFile, "Copying #{srcpath} to #{targetpath}...", ->
-        child_process.exec "cp -rp #{srcpath} #{targetpath}", ->
+        exec "cp -rp #{srcpath} #{targetpath}", ->
           fs.unlink lockFile, callback
     else
       fs.mkdirs targetpath, callback
+
+  agent.on '/datastore/usage', ({name}, data, callback) ->
+    exec "df -B1 #{basePath} | tail -1 | awk '{ print $2 }{ print $3}{ print $5}'", (err, stdout, stderr) ->
+      if err
+        console.error err
+        callback null, stderr
+      totalSize = stdout.split("\n")[0]
+      usedSize = stdout.split("\n")[1]
+      percentage = stdout.split("\n")[2]
+      callback null, { name: basePath, total: totalSize, used: usedSize, percentage: percentage }
+
+  agent.on '/storage/size', ({name}, data, callback) ->
+    targetpath = path.join basePath, name
+    lockFile = path.join basePath, ".#{name}.size.lock"
+    console.log "Retrieving size #{targetpath}"
+    fs.writeFile lockFile, "Retrieving size #{targetpath} ...", ->
+      exec "du -sb #{targetpath} | awk '{ print $1 }'", (err, stdout, stderr) ->
+        if err
+          console.error err
+          fs.unlink lockFile, callback(null, stderr)
+        bucketSize = stdout.replace(/^\s+|\s+$/g, '')
+        fs.unlink lockFile, ->
+          callback null, { name: name, size: bucketSize}


Fix the header and success path

Fix the header and success path

--- lib/twitter.coffee
+++ lib/twitter.coffee
@@ -13,14 +13,16 @@

   # A helper for the general case, posts `body`
   # as a tweet, and calls you back with it, as well
-  # as a pre-built URL
-  tweet: (body, cb=(err, tweet, url)) =>
+  # as a pre-built URL.
+  #
+  # Callback invoked as `cb(err, tweet, url)`
+  tweet: (body, cb=(->)) =>
     params = status: body
     client.post 'statuses/update', params, (error, tweet, response) ->
-      return cb(error)  if error
+      return cb(error) if error

       # LUCKY US
       myself = tweet.user.screen_name
       tid = tweet.id_str
       url = "https://twitter.com/#{myself}/status/#{tid}"
-      return cb(tweet, url)
+      return cb(undefined, tweet, url)


bump version for release

bump version for release

--- lib/version.coffee
+++ lib/version.coffee
@@ -1 +1 @@
-@neuron_catalog_version = '0.5.0'
+@neuron_catalog_version = '0.5.2'


Fix context menu area

Fix context menu area

--- menus/csso.cson
+++ menus/csso.cson
@@ -1,5 +1,5 @@
 'context-menu':
-  'atom-workspace': [{
+  'atom-text-editor': [{
     label: 'Minify CSS',
     command: 'csso:minify'
   }, {


The settings provider sets the csIndex component for the /{resourceType} pattern.

--- src/cs-settings-provider/cs-settings-provider.js.coffee
+++ src/cs-settings-provider/cs-settings-provider.js.coffee
@@ -10,13 +10,10 @@
     return [{
       name: @settings['router-path-prefix'] + 'index'
       url: '/{resourceType}'
-      component: 'csPageRouter'
+      component: 'csIndex'
       resolve:
         resourceType: ['$transition$', ($transition$) ->
           $transition$.params().resourceType
-        ]
-        pageType: ['$transition$', ($transition$) ->
-          'index'
         ]
     },{
       name: @settings['router-path-prefix'] + 'show'


fix: do not mark book as dirty when it is selected

--- scripts/models/content/book.coffee
+++ scripts/models/content/book.coffee
@@ -8,6 +8,14 @@
   'cs!models/content/toc-pointer-node'
   'cs!models/utils'
 ], (_, Backbone, allContent, Module, loadable, TocNode, TocPointerNode, Utils) ->
+
+  # Copy/Pasta from Saveable.coffee
+  INTERNAL_ATTRIBUTES = [
+    '_original'
+    '_selected'
+    '_isDirty'
+    '_hasRemoteChanges'
+  ]

   return class BookModel extends (TocNode.extend loadable)
     mediaType: 'application/vnd.org.cnx.collection'
@@ -69,7 +77,11 @@

       @tocNodes.on 'add remove tree:change', (model, collection, options) =>
         setNavModel(options)
-      @tocNodes.on 'change reset', (collection, options) =>
+      # Explicitly enumerate the change properties to listen to so `_selected` does not trigger the node to be marked dirty
+      @tocNodes.on 'change reset', (model, options) =>
+        # Do not mark dirty if only "_*" attributes are changed
+        return if _.isEmpty _.omit(model.changedAttributes?(), INTERNAL_ATTRIBUTES)
+
         setNavModel(options)

       # These store the added items since last successful save.


Smooth Scroll - v1.4.13 - 2013-11-02

Smooth Scroll - v1.4.13 - 2013-11-02

--- vendor/assets/javascripts/jquery.smoothScroll.js.coffee
+++ vendor/assets/javascripts/jquery.smoothScroll.js.coffee
@@ -1,148 +1,255 @@
-###
-jQuery SmoothScroll Plugin
-Required: jQuery(http://jquery.com/)
-License: MIT
-Update: 2012/12/06
-Version: 1.1
-Original Author: yuu.creatorish
-Customized: machida
-URL: http://creatorish.com
-PluginURL: http://creatorish.com/lab/5393
-###
-
-jQuery.fn.smoothScroll = (op) ->
-  clickHandler = (e) ->
-    e.preventDefault()
-    e.stopPropagation()
-    h = jQuery(this).attr("href")
-    t = jQuery(h)
-    t = d  if t.length is 0
-    scrolling = true
-    if c
-      s.attr
-        st: c.scrollTop()
-        sl: c.scrollLeft()
-
-    else
-      s.attr
-        st: w.scrollTop()
-        sl: w.scrollLeft()
-
-    option.start s.attr("sl"), s.attr("st")
-    o = t.offset()
-    if c
-      o = t.position()
-      o.top = c.get(0).scrollHeight - c.innerHeight()  if o.top > c.get(0).scrollHeight - c.innerHeight()
-    s.stop(true).animate
-      st: o.top
-      sl: o.left
-    ,
-      duration: option.duration
-      easing: option.easing
-      step: stepHandler
-      complete: completeHandler
-
-  completeHandler = ->
-    scrolling = false
-    scroll @sl, @st
-    option.complete @sl, @st
-  stepHandler = ->
-    scroll @sl, @st
-    option.step @sl, @st
-  scroll = (x, y) ->
-    if c
-      c.scrollLeft x
-      c.scrollTop y
-    else
-      window.scrollTo x, y
-  stopQueue = ->
-    if scrolling
-      option.canceled s.attr("sl"), s.attr("st")
-      scrolling = false
-      s.stop true
-  scrollTo = (x, y) ->
-    scrolling = true
-    if c
-      s.attr
-        st: c.scrollTop()
-        sl: c.scrollLeft()
-
-    else
-      s.attr
-        st: w.scrollTop()
-        sl: w.scrollLeft()
-
-    option.start s.attr("sl"), s.attr("st")
-    o =
-      top: y
-      left: x
-
-    if c
-      o.top = c.get(0).scrollHeight - c.innerHeight()  if o.top > c.get(0).scrollHeight - c.innerHeight()
-      o.left = c.get(0).scrollWidth - c.innerWidth()  if o.left > c.get(0).scrollWidth - c.innerWidth()
-    else
-      o.top = document.body.scrollHeight - window.innerHeight  if o.top > document.body.scrollHeight - window.innerHeight
-      o.left = document.body.scrollWidth - window.innerWidth  if o.left > document.body.scrollWidth - window.innerWidth
-    s.stop(true).animate
-      st: o.top
-      sl: o.left
-    ,
-      duration: option.duration
-      easing: option.easing
-      step: stepHandler
-      complete: completeHandler
-
-  scrollToElement = (selector) ->
-    o = jQuery(selector).offset()
-    o = jQuery(selector).position()  if c
-    scrollTo o.left, o.top
-  option =
-    easing: "swing"
-    duration: 500
-    cancel: true
-    target: null
-    start: (x, y) ->
-
-    step: (x, y) ->
-
-    canceled: (x, y) ->
-
-    complete: (x, y) ->
-
-  jQuery.extend option, op
-  scrolling = false
-  w = jQuery(window)
-  d = jQuery(document.body)
-  s = jQuery(
-    st: 0
-    sl: 0
-    v: 0
-  )
-  c = undefined
-  if option.target
-    c = jQuery(option.target)
-    c.wrapInner "

"  unless c.children().length is 1
-  if option.cancel
-    if "ontouchend" of document
-      if c
-        c.unbind "touchstart", stopQueue
-        c.bind "touchstart", stopQueue
-      else
-        d.unbind "touchstart", stopQueue
-        d.bind "touchstart", stopQueue
-    else
-      if c
-        c.bind "DOMMouseScroll mousewheel", stopQueue
-        c.unbind "mousedown", stopQueue
-        c.bind "mousedown", stopQueue
-      else
-        w.bind "DOMMouseScroll mousewheel", stopQueue
-        d.unbind "mousedown", stopQueue
-        d.bind "mousedown", stopQueue
-  jQuery.each this, ->
-    jQuery(this).unbind "click", clickHandler
-    jQuery(this).bind "click", clickHandler
-
-  scroll: scrollTo
-  scrollToElement: scrollToElement
-  stop: stopQueue+/*!
+ * Smooth Scroll - v1.4.13 - 2013-11-02
+ * https://github.com/kswedberg/jquery-smooth-scroll
+ * Copyright (c) 2013 Karl Swedberg
+ * Licensed MIT (https://github.com/kswedberg/jquery-smooth-scroll/blob/master/LICENSE-MIT)
+ */
+
+(function($) {
+var version = '1.4.13',
+    optionOverrides = {},
+    defaults = {
+      exclude: [],
+      excludeWithin:[],
+      offset: 0,
+
+      // one of 'top' or 'left'
+      direction: 'top',
+
+      // jQuery set of elements you wish to scroll (for $.smoothScroll).
+      //  if null (default), $('html, body').firstScrollable() is used.
+      scrollElement: null,
+
+      // only use if you want to override default behavior
+      scrollTarget: null,
+
+      // fn(opts) function to be called before scrolling occurs.
+      // `this` is the element(s) being scrolled
+      beforeScroll: function() {},
+
+      // fn(opts) function to be called after scrolling occurs.
+      // `this` is the triggering element
+      afterScroll: function() {},
+      easing: 'swing',
+      speed: 400,
+
+      // coefficient for "auto" speed
+      autoCoefficent: 2,
+
+      // $.fn.smoothScroll only: whether to prevent the default click action
+      preventDefault: true
+    },
+
+    getScrollable = function(opts) {
+      var scrollable = [],
+          scrolled = false,
+          dir = opts.dir && opts.dir == 'left' ? 'scrollLeft' : 'scrollTop';
+
+      this.each(function() {
+
+        if (this == document || this == window) { return; }
+        var el = $(this);
+        if ( el[dir]() > 0 ) {
+          scrollable.push(this);
+        } else {
+          // if scroll(Top|Left) === 0, nudge the element 1px and see if it moves
+          el[dir](1);
+          scrolled = el[dir]() > 0;
+          if ( scrolled ) {
+            scrollable.push(this);
+          }
+          // then put it back, of course
+          el[dir](0);
+        }
+      });
+
+      // If no scrollable elements, fall back to ,
+      // if it's in the jQuery collection
+      // (doing this because Safari sets scrollTop async,
+      // so can't set it to 1 and immediately get the value.)
+      if (!scrollable.length) {
+        this.each(function(index) {
+          if (this.nodeName === 'BODY') {
+            scrollable = [this];
+          }
+        });
+      }
+
+      // Use the first scrollable element if we're calling firstScrollable()
+      if ( opts.el === 'first' && scrollable.length > 1 ) {
+        scrollable = [ scrollable[0] ];
+      }
+
+      return scrollable;
+    },
+    isTouch = 'ontouchend' in document;
+
+$.fn.extend({
+  scrollable: function(dir) {
+    var scrl = getScrollable.call(this, {dir: dir});
+    return this.pushStack(scrl);
+  },
+  firstScrollable: function(dir) {
+    var scrl = getScrollable.call(this, {el: 'first', dir: dir});
+    return this.pushStack(scrl);
+  },
+
+  smoothScroll: function(options, extra) {
+    options = options || {};
+
+    if ( options === 'options' ) {
+      if ( !extra ) {
+        return this.first().data('ssOpts');
+      }
+      return this.each(function() {
+        var $this = $(this),
+            opts = $.extend($this.data('ssOpts') || {}, extra);
+
+        $(this).data('ssOpts', opts);
+      });
+    }
+
+    var opts = $.extend({}, $.fn.smoothScroll.defaults, options),
+        locationPath = $.smoothScroll.filterPath(location.pathname);
+
+    this
+    .unbind('click.smoothscroll')
+    .bind('click.smoothscroll', function(event) {
+      var link = this,
+          $link = $(this),
+          thisOpts = $.extend({}, opts, $link.data('ssOpts') || {}),
+          exclude = opts.exclude,
+          excludeWithin = thisOpts.excludeWithin,
+          elCounter = 0, ewlCounter = 0,
+          include = true,
+          clickOpts = {},
+          hostMatch = ((location.hostname === link.hostname) || !link.hostname),
+          pathMatch = thisOpts.scrollTarget || ( $.smoothScroll.filterPath(link.pathname) || locationPath ) === locationPath,
+          thisHash = escapeSelector(link.hash);
+
+      if ( !thisOpts.scrollTarget && (!hostMatch || !pathMatch || !thisHash) ) {
+        include = false;
+      } else {
+        while (include && elCounter < exclude.length) {
+          if ($link.is(escapeSelector(exclude[elCounter++]))) {
+            include = false;
+          }
+        }
+        while ( include && ewlCounter < excludeWithin.length ) {
+          if ($link.closest(excludeWithin[ewlCounter++]).length) {
+            include = false;
+          }
+        }
+      }
+
+      if ( include ) {
+
+        if ( thisOpts.preventDefault ) {
+          event.preventDefault();
+        }
+
+        $.extend( clickOpts, thisOpts, {
+          scrollTarget: thisOpts.scrollTarget || thisHash,
+          link: link
+        });
+        $.smoothScroll( clickOpts );
+      }
+    });
+
+    return this;
+  }
+});
+
+$.smoothScroll = function(options, px) {
+  if ( options === 'options' && typeof px === 'object' ) {
+    return $.extend(optionOverrides, px);
+  }
+  var opts, $scroller, scrollTargetOffset, speed,
+      scrollerOffset = 0,
+      offPos = 'offset',
+      scrollDir = 'scrollTop',
+      aniProps = {},
+      aniOpts = {},
+      scrollprops = [];
+
+  if (typeof options === 'number') {
+    opts = $.extend({link: null}, $.fn.smoothScroll.defaults, optionOverrides);
+    scrollTargetOffset = options;
+  } else {
+    opts = $.extend({link: null}, $.fn.smoothScroll.defaults, options || {}, optionOverrides);
+    if (opts.scrollElement) {
+      offPos = 'position';
+      if (opts.scrollElement.css('position') == 'static') {
+        opts.scrollElement.css('position', 'relative');
+      }
+    }
+  }
+
+  scrollDir = opts.direction == 'left' ? 'scrollLeft' : scrollDir;
+
+  if ( opts.scrollElement ) {
+    $scroller = opts.scrollElement;
+    if ( !(/^(?:HTML|BODY)$/).test($scroller[0].nodeName) ) {
+      scrollerOffset = $scroller[scrollDir]();
+    }
+  } else {
+    $scroller = $('html, body').firstScrollable(opts.direction);
+  }
+
+  // beforeScroll callback function must fire before calculating offset
+  opts.beforeScroll.call($scroller, opts);
+
+  scrollTargetOffset = (typeof options === 'number') ? options :
+                        px ||
+                        ( $(opts.scrollTarget)[offPos]() &&
+                        $(opts.scrollTarget)[offPos]()[opts.direction] ) ||
+                        0;
+
+  aniProps[scrollDir] = scrollTargetOffset + scrollerOffset + opts.offset;
+  speed = opts.speed;
+
+  // automatically calculate the speed of the scroll based on distance / coefficient
+  if (speed === 'auto') {
+
+    // if aniProps[scrollDir] == 0 then we'll use scrollTop() value instead
+    speed = aniProps[scrollDir] || $scroller.scrollTop();
+
+    // divide the speed by the coefficient
+    speed = speed / opts.autoCoefficent;
+  }
+
+  aniOpts = {
+    duration: speed,
+    easing: opts.easing,
+    complete: function() {
+      opts.afterScroll.call(opts.link, opts);
+    }
+  };
+
+  if (opts.step) {
+    aniOpts.step = opts.step;
+  }
+
+  if ($scroller.length) {
+    $scroller.stop().animate(aniProps, aniOpts);
+  } else {
+    opts.afterScroll.call(opts.link, opts);
+  }
+};
+
+$.smoothScroll.version = version;
+$.smoothScroll.filterPath = function(string) {
+  return string
+    .replace(/^\//,'')
+    .replace(/(?:index|default).[a-zA-Z]{3,4}$/,'')
+    .replace(/\/$/,'');
+};
+
+// default options
+$.fn.smoothScroll.defaults = defaults;
+
+function escapeSelector (str) {
+  return str.replace(/(:|\.)/g,'\\$1');
+}
+
+})(jQuery);


payment: remove uses of KodingError

payment: remove uses of KodingError

replace them with object-literals.  Stack traces are not important for user errors.

--- workers/social/lib/social/models/payment/payment.coffee
+++ workers/social/lib/social/models/payment/payment.coffee
@@ -5,7 +5,6 @@

   {secure, daisy}      = require 'bongo'

-  KodingError          = require '../../error'
   JUser                = require '../user'
   JGroup               = require '../group'
   JPaymentPlan         = require './index'
@@ -30,7 +29,7 @@

   @makePayment = secure (client, data, callback)->
     @initialize client, data, (err, group, account, plan)=>
-      if err then return callback new KodingError 'Unable to access payment backend, please try again later.'
+      if err then return callback { message: 'Unable to access payment backend, please try again later.' }

       data.quantity ?= 1  # Default quantity is 1
       data.multiple ?= no # Don't allow multiple subscriptions by default
@@ -49,7 +48,7 @@
           active       : yes
         }
         pay.save (err)->
-          if err then return callback new KodingError 'Unable to save transaction to database.'
+          if err then return callback { message: 'Unable to save transaction to database.' }
           callback null, pay

       if data.chargeTo is 'group'
@@ -81,7 +80,7 @@
   # Get plan
   @fetchPlan = (planCode, callback) ->
     JPaymentPlan.fetchPlanByCode planCode, (err, plan) ->
-      if err then return callback new KodingError 'Unable to access product information. Please try again later.'
+      if err then return callback { message: 'Unable to access product information. Please try again later.' }
       callback null, plan

   # Get price for a product
@@ -122,13 +121,13 @@
           if allocation >= expenses + amount
             callback()
           else
-            callback new KodingError "You don't have enough balance."
+            callback { message: "Insufficient balance." }

   # Return quota that group gives to its users.
   @getGroupAllocation = (group, callback)->
     group.fetchBundle (err, bundle)=>
       if err or not bundle or bundle.allocation is 0
-        return callback new KodingError "This group doesn't allow you to purchase."
+        return callback { message: "This group doesn't allow you to purchase." }
       else
         callback null, bundle.allocation

@@ -136,12 +135,12 @@
   @chargeGroup = (group, p, data, callback)->
     {multiple, plan, quantity} = data
     p.subscribeGroup group, {multiple, plan, quantity}, (err, subscription)->
-      if err then return callback new KodingError "Unable to buy item: #{err}"
+      if err then return callback { message: "Unable to buy item: #{err}" }
       callback null, subscription

   # Charge user account
   @chargeUser = (account, plan, data, callback)->
-    return callback new KodingError 'Unable charge, insufficient funds.'
+    return callback { message: 'Not implemented!' }
     # TBI

   # List group's payments
@@ -169,7 +168,7 @@
   # TODO: Make sure this calculation is enough.
   #       Not tested for expired/canceled subscriptions.
   @getExpenses = (pattern, callback) ->
-    error = (err)-> new KodingError "Unable to query user balance: #{err}"
+    error = (err)-> { message: "Unable to query user balance: #{err}" }

     JPaymentPayment.some pattern, {subscription: 1}, (err, items) ->
       return callback error err  if err


log error

log error

--- workers/social/lib/social/models/user/index.test.coffee
+++ workers/social/lib/social/models/user/index.test.coffee
@@ -41,23 +41,23 @@
     root   : __dirname
     mongo  : mongo
     models : '../../models'
-
+
   bongo.once 'dbClientReady', ->
-
+
     # creating a session before running tests
     JSession.createSession (err, { session, account }) ->
       clientId                  = session.clientId
       dummyClient.sessionToken  = session.token
       done()
-
-
+
+
 # this function will be called after all tests are executed
 afterTests = -> after ->
-
+

 # here we have actual tests
 runTests = -> describe 'workers.social.user.index', ->
-
+
   it 'should be able to make mongodb connection', (done) ->

     { serverConfig : { _serverState } } = bongo.getClient()
@@ -689,6 +689,7 @@

         ->
           JUser.convert client, userFormData, (err) ->
+            console.log err
             expect(err).to.not.exist
             queue.next()

@@ -716,6 +717,7 @@

         ->
           JUser.convert client, userFormData, (err) ->
+            console.log err
             expect(err).to.not.exist
             queue.next()

@@ -749,5 +751,5 @@

 afterTests()

-
-
+
+


Amorcing AJAX-lettering refacto.

Amorcing AJAX-lettering refacto.

--- app/assets/javascripts/backend/bank_statements.js.coffee
+++ app/assets/javascripts/backend/bank_statements.js.coffee
@@ -288,6 +288,7 @@
       @_reconciliateLines selected, letter

     _reconciliateLines: (lines, letter) ->
+      @_letterItems letter
       lines.find(".bank-statement-letter:not(input)").text letter
       lines.find("input.bank-statement-letter").val letter
       lines.removeClass "selected"
@@ -307,6 +308,7 @@

     _clearLinesWithReconciliationLetter: (letter) ->
       return unless letter
+      @_unletterItems letter
       lines = @_linesWithReconciliationLetter(letter)
       lines.find(".bank-statement-letter:not(input)").text ""
       lines.find("input.bank-statement-letter").val null
@@ -403,6 +405,12 @@
       $(".remaining-reconciliated-debit").text debit.toFixed(2)
       $(".remaining-reconciliated-credit").text credit.toFixed(2)

+    _letterItems: (letter) ->
+      _ajaxLettering(letter, true)
+
+    _unletterItems: (letter) ->
+      _ajaxLettering(letter, false)
+
     # Other methods

     _lines: ->


+ validation message taken from the server on model AJAX

--- app/assets/javascripts/mixins/model_ajax_mixin.js.coffee
+++ app/assets/javascripts/mixins/model_ajax_mixin.js.coffee
@@ -1,6 +1,6 @@
 Barbecue.ModelAjaxMixin = Ember.Mixin.create

- _ajax: (url, method, options) ->
+  _ajax: (url, method, options) ->
     id = @get 'id'
     type    = @get 'constructor'
     store   = @get 'store'
@@ -12,12 +12,15 @@
       console.info 'inserted'

     error = (response) =>
-      # TODO: handle becameInvalid!
-      # see http://emberjs.com/api/data/classes/DS.RootState.html
-      @get('errors').add('rejectReson','sdfdsfdsf')
-
+      # we have to manually add the errors
+      for attr,message of response.errors.errors
+        prop = attr.camelize()
+        @get('errors').add(prop,message)
+
+    # we have to change the model state so that
+    # later on, errors can be added
+    @adapterWillCommit()
     adapter.ajax(url, method, options).then success,error
-

   post: (url, data) ->
     @_ajax url, 'POST', { data: data }


Reset the timer that updates feeds every minute when the user changes the read/unread state of a single feed or marks multiple entries as read (all in a feed, all in a folder or all entries).

This is related to commit 64bb1e88b4882ef3c07caa777986ae5bf13c8e84.

--- app/assets/javascripts/ng-services/ng-EntrySvc.js.coffee
+++ app/assets/javascripts/ng-services/ng-EntrySvc.js.coffee
@@ -4,9 +4,9 @@

 angular.module('feedbunch').service 'entrySvc',
 ['$rootScope', '$http', '$window', 'openEntrySvc', 'timerFlagSvc', 'unreadCountSvc',
-'currentFolderSvc', 'currentFeedSvc', 'findSvc', 'readSvc',
+'currentFolderSvc', 'currentFeedSvc', 'findSvc', 'readSvc', 'feedsFoldersSvc',
 ($rootScope, $http, $window, openEntrySvc, timerFlagSvc, unreadCountSvc,
-currentFolderSvc, currentFeedSvc, findSvc, readSvc)->
+currentFolderSvc, currentFeedSvc, findSvc, readSvc, feedsFoldersSvc)->

   #--------------------------------------------
   # PRIVATE FUNCTION - Mark a single entry as read or unread.
@@ -31,7 +31,13 @@
     $http.put("/entries/update.json", entry: {id: entry.id, state: state})
     .success ->
       entry.changing_state = false
-      read_feed feed_id if feed_id?
+      # Reset here the timer that updates feeds every minute only if feed_id argument has not been passed
+      # (if it has been passed, now entries from the feed will be loaded from the server and the
+      # update timer will be reset as part of that process).
+      if feed_id?
+        read_feed feed_id
+      else
+        feedsFoldersSvc.reset_refresh_timer()
     .error (data, status)->
       if status == 401
         $window.location.href = '/login'
@@ -83,6 +89,8 @@

       $http.put("/entries/update.json", entry: {id: first_entry.id, state: 'read', whole_feed: whole_feed, whole_folder: whole_folder, all_entries: all_entries})
       .success ->
+        # after marking multiple entries as read, reset the timer that updates feeds every minute
+        feedsFoldersSvc.reset_refresh_timer()
         for entry in $rootScope.entries
           entry.changing_state = false
       .error (data, status)->


Fix slow searches by filtering on the front end instead of back end (#430)

--- app/views/editor/campaign/CampaignEditorView.ozar.coffee
+++ app/views/editor/campaign/CampaignEditorView.ozar.coffee
@@ -251,7 +251,7 @@
       nodeClasses:
         levels: LevelsNode
         level: LevelNode
-        nextLevel: NextLevelNode
+        nextLevel: NextLevelNode
         campaigns: CampaignsNode
         campaign: CampaignNode
         achievement: AchievementNode
@@ -351,33 +351,34 @@
 class LevelsNode extends TreemaObjectNode
   valueClass: 'treema-levels'
   @levels: {}
+  @mapped: []
   ordered: true

-  buildValueForDisplay: (valEl, data) ->
-    @buildValueForDisplaySimply valEl, ''+_.size(data)
-
-  childPropertiesAvailable: -> @childSource
-
-  childSource: (req, res) =>
+  constructor: (args...) ->
+    super(args...)
     s = new Backbone.Collection([], {model:Level})
     s.url = '/db/level'
     s.url += '?archived=false'
-    s.fetch({data: {term:req.term, project: Campaign.denormalizedLevelProperties.join(',')}})
+    s.fetch({data: {project: Campaign.denormalizedLevelProperties.join(',')}})
     s.once 'sync', (collection) =>
       for level in collection.models
         LevelsNode.levels[level.get('original')] = level
         @settings.supermodel.registerModel level
-      mapped = ({label: r.get('name'), value: r.get('original')} for r in collection.models)
-
-      # Sort the results. Prioritize names that start with the search term, then contain the search term.
-      lowerPriority = _.clone(mapped)
-      lowerTerm = req.term.toLowerCase()
-      startsWithTerm = _.filter(lowerPriority, (item) -> _.string.startsWith(item.label.toLowerCase(), lowerTerm))
-      _.pull(lowerPriority, startsWithTerm...)
-      hasTerm = _.filter(lowerPriority, (item) -> _.string.contains(item.label.toLowerCase(), lowerTerm))
-      _.pull(lowerPriority, hasTerm...)
-      sorted = _.flatten([startsWithTerm, hasTerm, lowerPriority])
-      res(sorted)
+      @mapped = ({label: r.get('name'), value: r.get('original')} for r in collection.models)
+
+  buildValueForDisplay: (valEl, data) ->
+    @buildValueForDisplaySimply valEl, ''+_.size(data)
+
+  childPropertiesAvailable: -> @childSource
+
+  childSource: (req, res) =>
+    # Sort the results. Prioritize names that start with the search term, then contain the search term.
+    lowerTerm = req.term.toLowerCase()
+    sorted = _.filter(@mapped, (item) -> _.string.contains(item.label.toLowerCase(), lowerTerm))
+    startsWithTerm = _.filter(sorted, (item) -> _.string.startsWith(item.label.toLowerCase(), lowerTerm))
+    _.pull(sorted, startsWithTerm...)
+    res(_.flatten([startsWithTerm, sorted]))
+

 class LevelNode extends TreemaObjectNode
   valueClass: 'treema-level'
@@ -419,20 +420,29 @@
 class CampaignsNode extends TreemaObjectNode
   valueClass: 'treema-campaigns'
   @campaigns: {}
-
-  buildValueForDisplay: (valEl, data) ->
-    @buildValueForDisplaySimply valEl, ''+_.size(data)
-
-  childPropertiesAvailable: -> @childSource
-
-  childSource: (req, res) =>
+  @mapped = []
+
+  constructor: (args...) ->
+    super(args...)
     s = new Backbone.Collection([], {model:Campaign})
     s.url = '/db/campaign'
-    s.fetch({data: {term:req.term, project: Campaign.denormalizedCampaignProperties}})
+    s.fetch({data: {project: Campaign.denormalizedCampaignProperties}})
     s.once 'sync', (collection) ->
       CampaignsNode.campaigns[campaign.id] = campaign for campaign in collection.models
-      mapped = ({label: r.get('name'), value: r.id} for r in collection.models)
-      res(mapped)
+      @mapped = ({label: r.get('name'), value: r.id} for r in collection.models)
+
+  buildValueForDisplay: (valEl, data) ->
+    @buildValueForDisplaySimply valEl, ''+_.size(data)
+
+  childPropertiesAvailable: -> @childSource
+
+  childSource: (req, res) =>
+    # Sort the results. Prioritize names that start with the search term, then contain the search term.
+    lowerTerm = req.term.toLowerCase()
+    sorted = _.filter(@mapped, (item) -> _.string.contains(item.label.toLowerCase(), lowerTerm))
+    startsWithTerm = _.filter(sorted, (item) -> _.string.startsWith(item.label.toLowerCase(), lowerTerm))
+    _.pull(sorted, startsWithTerm...)
+    res(_.flatten([startsWithTerm, sorted]))

 class CampaignNode extends TreemaObjectNode


Dispatch to the right browser/page after download.

--- application/chrome/content/wesabe/download/Player.coffee
+++ application/chrome/content/wesabe/download/Player.coffee
@@ -158,8 +158,18 @@

         file.write statement, data

-        @job.recordSuccessfulDownload statement, filename, @job.nextDownloadMetadata
+        metadata = @job.nextDownloadMetadata
         delete @job.nextDownloadMetadata
+
+        # restore the browser and page the download was triggered from, if any
+        if metadata.browser
+          @browser = metadata.browser
+          delete metadata.browser
+        if metadata.page
+          @page = metadata.page
+          delete metadata.page
+
+        @job.recordSuccessfulDownload statement, filename, metadata
         @onDownloadSuccessful @browser, @page

     event.add 'downloadFail', (evt) =>
@@ -260,6 +270,9 @@
       callback = metadata
       metadata = url
       url = null
+
+      metadata.browser = browser
+      metadata.page = page

       @job.nextDownloadMetadata = metadata
       callback()


Make sure metdata is at least an empty object.

--- application/chrome/content/wesabe/download/Player.coffee
+++ application/chrome/content/wesabe/download/Player.coffee
@@ -164,7 +164,7 @@

         file.write statement, data

-        metadata = @job.nextDownloadMetadata
+        metadata = @job.nextDownloadMetadata or {}
         delete @job.nextDownloadMetadata

         # restore the browser and page the download was triggered from, if any


Added some delay before listening for connection to avoid handling requests before DB connections are established.

--- acs.coffee
+++ acs.coffee
@@ -278,7 +278,6 @@

 listener = (httpRequest, httpResponse) ->
   if httpRequest.method != 'POST'
-    #console.log '>>> 405 Method Not Allowed'
     httpResponse.writeHead 405, {'Allow': 'POST'}
     httpResponse.end('405 Method Not Allowed')
     return
@@ -450,6 +449,8 @@
   httpServer = http.createServer(listener)
   httpsServer = https.createServer(options, listener)

-  httpServer.listen(config.ACS_PORT, config.ACS_INTERFACE)
-  httpsServer.listen(config.ACS_HTTPS_PORT, config.ACS_HTTPS_INTERFACE)
-  #console.log "Server listening on port #{config.ACS_PORT}"
+  # wait until DB connections are established
+  setTimeout(() ->
+    httpServer.listen(config.ACS_PORT, config.ACS_INTERFACE)
+    httpsServer.listen(config.ACS_HTTPS_PORT, config.ACS_HTTPS_INTERFACE)
+  , 1000)


minor: codestyling

minor: codestyling

--- client/app/lib/environment/machineslistitemheader.coffee
+++ client/app/lib/environment/machineslistitemheader.coffee
@@ -9,7 +9,9 @@
   constructor: (options = {}, data) ->

     options.cssClass = kd.utils.curry 'machines-item header', options.cssClass
+
     super options, data
+

   pistachio: ->
     """


Launching a server, killing lobby while configuring working

--- server/serversystem/manager.coffee
+++ server/serversystem/manager.coffee
@@ -267,7 +267,7 @@
           sess = serverObj.activeLobbies.splice serverObj.activeLobbies.indexOf(lobIdx), 1
           sess = sess[0]
           lob = lobbies.findOne {_id: sess.lobby}
-          return if lob.status isnt 3
+          return if !lob? || lob.status isnt 3
           lobbies.update {_id: sess.lobby}, {$set: {status: 4}}
           servers.update {_id: ourID}, {$set: {activeLobbies: serverObj.activeLobbies}}
           queueProc()


Tweak default node diameters.

Tweak default node diameters.

--- community/browser/app/scripts/services/GraphStyle.coffee
+++ community/browser/app/scripts/services/GraphStyle.coffee
@@ -27,7 +27,7 @@
     # Default style
     @defaultStyle =
       'node':
-        'diameter': '40px'
+        'diameter': '50px'
         'color': '#A5ABB6'
         'border-color': '#9AA1AC'
         'border-width': '2px'
@@ -46,8 +46,8 @@
     @defaultSizes = [
       { diameter: '10px' }
       { diameter: '20px' }
-      { diameter: '30px' }
       { diameter: '50px' }
+      { diameter: '65px' }
       { diameter: '80px' }
     ]


Corrections to allValid

Corrections to allValid

--- vendor/assets/javascripts/modal.js.coffee
+++ vendor/assets/javascripts/modal.js.coffee
@@ -4,8 +4,12 @@

 _validate = (item) ->
   section = $(item.el).find('.bbm-modal__section')
+
   $(item.el).find('p.error').remove()
-  unless validationsUnsupported or _.every(section.find('input'), (i) -> i.validity.valid)
+
+  allValid = _.every section.find('input'), (i) -> i.validity.valid
+
+  unless validationsUnsupported or allValid
     section.find('span.error').remove()
     section.find('input').each ->
       if !this.validity.valid
@@ -17,7 +21,7 @@
         $(['keyup', 'change', 'blur']).each (i, event) ->
           $(field).on event, ->
             $(this).siblings(".error").remove()
-
+
     return false

   true


SocialWorker: remove commented out code block

--- workers/social/lib/social/init.coffee
+++ workers/social/lib/social/init.coffee
@@ -46,20 +46,4 @@

 exports.init = (koding) ->
   console.warn 'Initialization code is temporarily disabled.'
-  # JGroup = require './models/group'
-  # JGroup.count (err, count) ->
-  #   return console.error err  if err
-  #   initKodingGroup()  if count is 0
-  # If we need to create initialize the DB, do it now:
-  # counter = (require 'koding-counter') {
-  #   db          : koding.getClient()
-  #   counterName : 'dbinit'
-  # }
-  # counter.initizalize -> counter.count (err, count) ->
-  #   throw err  if err
-  #   if count is 0
-  #     counter.next (err, seq) ->
-  #       throw err  if err
-  #       init()

-


only set wal for sqlite

only set wal for sqlite

--- services/clsi/app/coffee/db.coffee
+++ services/clsi/app/coffee/db.coffee
@@ -14,7 +14,8 @@
 	options
 )

-sequelize.query("PRAGMA journal_mode=WAL;")
+if Settings.mysql.clsi.dialect == "sqlite"
+	sequelize.query("PRAGMA journal_mode=WAL;")

 module.exports =
 	UrlCache: sequelize.define("UrlCache", {


Impelemnted querying in API

Impelemnted querying in API

--- api.coffee
+++ api.coffee
@@ -35,12 +35,6 @@
   )
 else
   server = http.createServer (request, response) ->
-    if request.method != 'GET'
-      console.log '>>> 405 Method Not Allowed'
-      response.writeHead 405, {'Allow': 'GET'}
-      response.end('405 Method Not Allowed')
-      return
-
     request.content = ''

     request.addListener 'data', (chunk) ->
@@ -49,8 +43,34 @@
     request.addListener 'end', () ->
       urlParts = url.parse(request.url, true)
       if DEVICES_REGEX.test(urlParts.pathname)
-        # TODO search for devices
+        if request.method != 'POST'
+          response.writeHead 405, {'Allow': 'POST'}
+          response.end('405 Method Not Allowed')
+          return
+        req = JSON.parse(request.content)
+        query = devicesCollection.find(req.query)
+
+        if req.sort?
+          query.sort(req.sort)
+
+        if req.skip?
+          query.skip(req.skip)
+
+        if req.limit?
+          query.limit(req.limit)
+        else
+          query.limit(100)
+
+        query.toArray((err, devices) ->
+          response.writeHead 200
+          response.end(JSON.stringify(devices))
+        )
+        return
       else if DEVICE_REGEX.test(urlParts.pathname)
+        if request.method != 'GET'
+          response.writeHead 405, {'Allow': 'GET'}
+          response.end('405 Method Not Allowed')
+          return
         id = DEVICE_REGEX.exec(urlParts.pathname)[1]
         devicesCollection.findOne({_id : id}, (err, device)->
           if err
@@ -73,5 +93,5 @@
       response.writeHead 404
       response.end('404 Not Found')

-  server.listen config.API_PORT
+  server.listen config.API_PORT, config.API_INTERFACE
   console.log "Server listening on port #{config.API_PORT}"


Improved AnimatableRegion spec descriptions

--- spec/animatable_region_spec.coffee
+++ spec/animatable_region_spec.coffee
@@ -19,7 +19,7 @@
     @region = new AnimatableRegion(el: 'body')
     @region.show(currentPage)

-  context 'with no transitions', ->
+  context 'When no transactions have taken place yet', ->
     it 'can go back returns false', ->
       expect(@region.canGoBack()).to.be.false

@@ -33,5 +33,5 @@
       it 'current page does not exist any longer', (done) ->
         delayedAssert( -> expect($('#page-1')).not.to.exist)

-      it 'next page does not exist any longer', (done) ->
+      it 'next page is available', (done) ->
         delayedAssert( -> expect($('#page-2')).to.exist)


Make linter happy

Make linter happy

--- spec/browser/Initialization.coffee
+++ spec/browser/Initialization.coffee
@@ -47,7 +47,7 @@
       req = indexedDB.open 'noflo-ui', 4
       req.onerror = (err) ->
         done err
-      req.onupgradeneeded = (e) =>
+      req.onupgradeneeded = (e) ->
         e.target.transaction.abort()
         done new Error 'We didn\'t get a ready database'
       req.onsuccess = (event) ->


Remove use of fs-plus shims

Remove use of fs-plus shims

--- spec/project-find-view-spec.coffee
+++ spec/project-find-view-spec.coffee
@@ -391,11 +391,11 @@
           expect(projectFindView.infoMessages).toBeVisible()
           expect(projectFindView.infoMessages.find('li').text()).toContain 'Replaced'

-          sampleJsContent = fs.read sampleJs
+          sampleJsContent = fs.readFileSync(sampleJs, 'utf8')
           expect(sampleJsContent.match(/items/g)).toBeFalsy()
           expect(sampleJsContent.match(/sunshine/g)).toHaveLength 6

-          sampleCoffeeContent = fs.read sampleCoffee
+          sampleCoffeeContent = fs.readFileSync(sampleCoffee, 'utf8')
           expect(sampleCoffeeContent.match(/items/g)).toBeFalsy()
           expect(sampleCoffeeContent.match(/sunshine/g)).toHaveLength 7

@@ -464,10 +464,10 @@

             expect(projectFindView.infoMessages.find('li').text()).toBe "Replaced 13 results in 2 files"

-            sampleJsContent = fs.read sampleJs
+            sampleJsContent = fs.readFileSync(sampleJs, 'utf8')
             expect(sampleJsContent.match(/items/g)).toBeFalsy()
             expect(sampleJsContent.match(/sunshine/g)).toHaveLength 6

-            sampleCoffeeContent = fs.read sampleCoffee
+            sampleCoffeeContent = fs.readFileSync(sampleCoffee, 'utf8')
             expect(sampleCoffeeContent.match(/items/g)).toBeFalsy()
             expect(sampleCoffeeContent.match(/sunshine/g)).toHaveLength 7


fix adding task by torrent link

fix adding task by torrent link

--- firefox/lib/lixian/tasks.coffee
+++ firefox/lib/lixian/tasks.coffee
@@ -164,6 +164,7 @@
 super_search = (client, urls, callback) ->
 	search_tasks client, urls, (result) ->
 		if result.ok
+			# TODO: add not_found to expand_bt_tasks results
 			expand_bt_tasks client, result.tasks, callback
 		else
 			callback result
@@ -196,15 +197,16 @@
 		blob = new Blob [arraybuffer]
 		logging 'uploading_torrent'
 		client.upload_torrent_file_by_blob blob, (result) ->
+			upload_result = result
 			if result.ok
 				if result.done
 					super_search client, [result.info_hash], callback
 				else
 					super_search client, [result.info_hash], (result) ->
 						if result.ok
-							if result.tasks.length == 0 and result.skipped == 1
+							if result.tasks.length == 0
 								logging 'adding_bt'
-								client.commit_bt_task result, (result) ->
+								client.commit_bt_task upload_result, (result) ->
 									if result.ok
 										super_search client, [result.info_hash], callback
 									else


remove logger package

remove logger package

--- app.coffee
+++ app.coffee
@@ -1,13 +1,11 @@
 express = require 'express'
 path    = require 'path'
-logger  = require 'morgan'
 app     = express()
 debug   = require('debug')('app:server')
 http    = require 'http'

 app.set 'views', path.join __dirname, 'views'
 app.set 'view engine', 'ejs'
-app.use logger 'dev'
 app.use express.static path.join __dirname, 'prod'

 app.use (req, res, next) ->


Array bug for image

Array bug for image

--- app.coffee
+++ app.coffee
@@ -111,6 +111,7 @@
     processMessage = (conversation) ->
         image = req.query["MediaUrl0"]
         if image?
+            if image.length > 1 then image = image[0]
             hasOthers = req.query["MediaUrl1"]?
             handleImage(image, hasOthers, conversation)
         else


added vpn host & port definition to boostraping tasks

--- app.coffee
+++ app.coffee
@@ -39,6 +39,7 @@
 			fs.writeFileSync('/etc/openvpn/ca.crt', body.ca)
 			fs.writeFileSync('/etc/openvpn/client.crt', body.cert)
 			fs.writeFileSync('/etc/openvpn/client.key', body.key)
+			fs.appendFileSync('/etc/openvpn/client.conf', "remote #{body.vpnhost} #{body.vpnport}")

 			callback(null)
 		)


change port number

change port number

--- app.coffee
+++ app.coffee
@@ -29,4 +29,4 @@
 app.get "*", (req, res) ->
   res.render 'index'

-app.listen(3000)
+app.listen(2222)


Fix bug, bump to 2.0.1

Fix bug, bump to 2.0.1

--- app.coffee
+++ app.coffee
@@ -1,4 +1,4 @@
-global.POI_VERSION = '2.0.0'
+global.POI_VERSION = '2.0.1'

 app = require 'app'
 BrowserWindow = require 'browser-window'


Use production environment as default.

Use production environment as default.

--- app.coffee
+++ app.coffee
@@ -1,6 +1,6 @@

 # Read the environment variable
-global.app_env = process.env.NODE_ENV || "development"
+global.app_env = process.env.NODE_ENV || "production"

 # Setup the environment configuration
 global.config = require("./config/config.json")[app_env]


always add archiving root in

always add archiving root in

--- app.coffee
+++ app.coffee
@@ -21,8 +21,7 @@
 app.post '/project/:project_id/doc/:doc_id', bodyParser.json(limit: "2mb"), HttpController.updateDoc
 app.del  '/project/:project_id/doc/:doc_id', HttpController.deleteDoc

-if Settings.filestore?.backend == "s3"
-	app.get  '/project/:project_id/archive', HttpController.archiveAllDocs
+app.get  '/project/:project_id/archive', HttpController.archiveAllDocs

 app.get '/status', (req, res)->
 	res.send('docstore is alive')


Dont use bosskey on OSX

Dont use bosskey on OSX

--- app.coffee
+++ app.coffee
@@ -64,7 +64,8 @@
     app.quit()

 app.on 'ready', ->
-  shortcut.register()
+  if process.platform != 'darwin'
+    shortcut.register()
   screen = require 'screen'
   screenSize = screen.getPrimaryDisplay().workAreaSize
   global.mainWindow = mainWindow = new BrowserWindow


Mount the classifier page once from the wrapper Update subjects and classifications when props change.

--- app/classifier/index.cjsx
+++ app/classifier/index.cjsx
@@ -77,11 +77,18 @@

   componentWillReceiveProps: (nextProps) ->
     if nextProps.subject isnt @props.subject
-      @loadSubject subject
-    if nextProps.classification isnt @props.classification
-      @prepareToClassify nextProps.classification
+      @loadSubject nextProps.subject
     if @props.subject isnt nextProps.subject or !@context.geordi?.keys["subjectID"]?
       @context.geordi?.remember subjectID: nextProps.subject?.id
+
+    if nextProps.classification isnt @props.classification
+      @props.classification.stopListening 'change', =>
+        {annotations} = @props.classification
+        @setState {annotations}
+      nextProps.classification.listen 'change', =>
+        {annotations} = nextProps.classification
+        @setState {annotations}
+      @prepareToClassify nextProps.classification

   componentWillMount: () ->
     interventionMonitor.setProjectSlug @props.project.slug
@@ -619,9 +626,7 @@
       @loadClassification nextProps.classification

   loadClassification: (classification) ->
-    @setState subject: null
-
-    # TODO: These underscored references are temporary stopgaps.
+# TODO: These underscored references are temporary stopgaps.

     Promise.resolve(classification._subjects ? classification.get 'subjects').then ([subject]) =>
       # We'll only handle one subject per classification right now.


Update uk.coffee (#5621)

Update uk.coffee (#5621)

Co-authored-by: Matias Forbord <7d4dc3970a879d64548b3a155f22f7990dc8fc0c@users.noreply.github.com>

--- app/locale/uk.coco.coffee
+++ app/locale/uk.coco.coffee
@@ -2497,7 +2497,7 @@
     unauthorized: "Вам потрібно увійти. Ви вимкнули куки?"
     forbidden: "Недостатньо прав."
     forbidden_desc: "О ні, тут немає нічого що б ми могли вам показати! Перевірте, що ви зайшли під правильною обліківкою, або відвідайте одне з наступних посилань щоб повернутись до програмування!"
-    user_not_found: "Користувач не знайден"
+    user_not_found: "Користувача не знайдено"
     not_found: "Не знайдено."
     not_found_desc: "Хм, тут нічого немає. Відвідайте одне з наступних посилань щоб повернутись до програмування!"
     not_allowed: "Недозволений метод."


added guard on @stats?, change of Archive.complete() to be @stats.complete == @stats.total instead of some ratio calculation on progress_strict

--- app/models/Archive.coffee
+++ app/models/Archive.coffee
@@ -67,7 +67,8 @@
     @formatNumber @stats.total

   complete: =>
-    @progress_strict() is 100
+    unless @stats? then return 0
+    (@stats.complete is @stats.total)

   # Private
   formatNumber: (n) ->


Update subject selection

Update subject selection

--- app/models/subject.coffee
+++ app/models/subject.coffee
@@ -99,12 +99,12 @@
   # For iPhone apps there will also be a delay until the updated App is approved.
   @randomSurveyId: ->
     n = Math.random()
-    if n <= (0.3)
+    if n <= (0.333)
       @::surveys.gama09.id
-    else if n < (0.6)
-      @::surveys.missing_manga.id
+    else if n < (0.667)
+      @::surveys.sdss_lost_set.id
     else
-      @::surveys.ferengi_2.id
+      @::surveys.illustris.id

   @next: ->
     if @current