Fix windows specs

Fix windows specs

--- spec/status-bar-spec.coffee
+++ spec/status-bar-spec.coffee
@@ -1,6 +1,7 @@
 {_, $, $$, fs, RootView} = require 'atom'
 StatusBar = require '../lib/status-bar-view'
 path = require 'path'
+os = require 'os'

 describe "StatusBar", ->
   [editor, statusBar, buffer] = []
@@ -54,7 +55,7 @@

   describe "when the buffer content has changed from the content on disk", ->
     it "disables the buffer modified indicator on save", ->
-      filePath = "/tmp/atom-whitespace.txt"
+      filePath = path.join(os.tmpdir(), "atom-whitespace.txt")
       fs.writeSync(filePath, "")
       rootView.openSync(filePath)
       editor = rootView.getActiveView()
@@ -109,7 +110,7 @@

   describe "git branch label", ->
     beforeEach ->
-      fs.remove('/tmp/.git') if fs.isDirectorySync('/tmp/.git')
+      fs.remove(path.join(os.tmpdir(), '.git')) if fs.isDirectorySync(path.join(os.tmpdir(), '.git'))
       rootView.attachToDom()

     it "displays the current branch for files in repositories", ->
@@ -119,12 +120,12 @@
       expect(statusBar.branchLabel.text()).toBe 'master'

     it "doesn't display the current branch for a file not in a repository", ->
-      project.setPath('/tmp')
-      rootView.openSync('/tmp/temp.txt')
+      project.setPath(os.tmpdir())
+      rootView.openSync(path.join(os.tmpdir(), 'temp.txt'))
       expect(statusBar.branchArea).toBeHidden()

     it "doesn't display the current branch for a file outside the current project", ->
-      rootView.openSync('/tmp/atom-specs/not-in-project.txt')
+      rootView.openSync(path.join(os.tmpdir(), 'atom-specs', 'not-in-project.txt'))
       expect(statusBar.branchArea).toBeHidden()

   describe "git status label", ->


LanguageMode switches to a better-matching grammar when it is added

--- spec/status-bar-spec.coffee
+++ spec/status-bar-spec.coffee
@@ -183,7 +183,6 @@
     beforeEach ->
       atom.activatePackage('text.tmbundle', sync: true)
       atom.activatePackage('javascript.tmbundle', sync: true)
-      syntax.trigger 'grammars-loaded'

     it "displays the name of the current grammar", ->
       expect(statusBar.find('.grammar-name').text()).toBe 'JavaScript'


Use commas in download count

Use commas in download count

Before:

https://cloud.githubusercontent.com/assets/2/4600787/1d24d408-50dd-11e4-9174-ca840b88a08b.png

After:

https://cloud.githubusercontent.com/assets/2/4600790/56ec76be-50dd-11e4-8d96-8c7d4850c3be.png

--- lib/available-package-view.coffee
+++ lib/available-package-view.coffee
@@ -11,7 +11,8 @@
           @span outlet: 'status', class: 'package-status icon'
           @h4 class: 'package-name native-key-bindings', tabindex: -1, _.undasherize(_.uncamelcase(name))
           if downloads >= 0
-            @p class: 'downloads native-key-bindings', tabindex: -1, _.pluralize(downloads, 'download')
+            count = if downloads is 1 then '1 download' else "#{downloads.toLocaleString()} downloads"
+            @p class: 'downloads native-key-bindings', tabindex: -1, count
           @p class: 'description native-key-bindings', tabindex: -1, description ? ''
           @div class: 'btn-toolbar', =>
             @button outlet: 'installButton', class: 'btn btn-primary', 'Install'


Changes round strategy in sales/purchases form to prevent excessive round. Fixes #481.

--- app/assets/javascripts/backend/trade.js.coffee
+++ app/assets/javascripts/backend/trade.js.coffee
@@ -64,14 +64,21 @@

   E.trade =

+    round: (value, digits) ->
+      magnitude = Math.pow(10, digits)
+      console.log value, magnitude, value * magnitude, Math.round(value * magnitude), (Math.round(value * magnitude) / magnitude), (Math.round(value * magnitude) / magnitude).toFixed(digits)
+      return (Math.round(value * magnitude) / magnitude).toFixed(digits)
+
     # Compute other amounts from unit pretax amount
     updateUnitPretaxAmount: (item) ->
       values = E.trade.itemValues(item)
       updates = {}
       # Compute pretax_amount
-      updates.pretax_amount = (values.unit_pretax_amount * values.quantity * (100.0 - values.reduction_percentage) / 100.0).toFixed(2)
+      updates.pretax_amount = values.unit_pretax_amount * values.quantity * (100.0 - values.reduction_percentage) / 100.0
       # Compute amount
-      updates.amount = (updates.pretax_amount * values.tax).toFixed(2)
+      updates.amount = E.trade.round(updates.pretax_amount * values.tax, 2)
+      # Round pretax amount
+      updates.pretax_amount = E.trade.round(updates.pretax_amount, 2)
       E.trade.itemValues(item, updates)

     # Compute other amounts from pretax amount
@@ -79,9 +86,9 @@
       values = E.trade.itemValues(item)
       updates = {}
       # Compute unit_pretax_amount
-      updates.unit_pretax_amount = (values.pretax_amount / (values.quantity * (100.0 - values.reduction_percentage) / 100.0)).toFixed(4)
+      updates.unit_pretax_amount = E.trade.round(values.pretax_amount / (values.quantity * (100.0 - values.reduction_percentage) / 100.0), 4)
       # Compute amount
-      updates.amount = (values.pretax_amount * values.tax).toFixed(2)
+      updates.amount = E.trade.round(values.pretax_amount * values.tax, 2)
       E.trade.itemValues(item, updates)

     # Compute other amounts from amount
@@ -89,9 +96,11 @@
       values = E.trade.itemValues(item)
       updates = {}
       # Compute pretax_amount
-      updates.pretax_amount = (values.amount / values.tax).toFixed(2)
+      updates.pretax_amount = values.amount / values.tax
       # Compute unit_pretax_amount
-      updates.unit_pretax_amount = (updates.pretax_amount / (values.quantity * (100.0 - values.reduction_percentage) / 100.0)).toFixed(4)
+      updates.unit_pretax_amount = E.trade.round(updates.pretax_amount / (values.quantity * (100.0 - values.reduction_percentage) / 100.0), 4)
+      # Round pretax amount
+      updates.pretax_amount = E.trade.round(updates.pretax_amount, 2)
       E.trade.itemValues(item, updates)

     # Compute other amounts from amount
@@ -99,9 +108,11 @@
       values = E.trade.itemValues(item)
       updates = {}
       # Compute pretax_amount
-      updates.pretax_amount = (-1 * values.unit_pretax_amount * values.credited_quantity * (100.0 - values.reduction_percentage) / 100.0).toFixed(2)
+      updates.pretax_amount = -1 * values.unit_pretax_amount * values.credited_quantity * (100.0 - values.reduction_percentage) / 100.0
       # Compute unit_pretax_amount
-      updates.amount = (updates.pretax_amount * values.tax).toFixed(2)
+      updates.amount = E.trade.round(updates.pretax_amount * values.tax, 2)
+      # Round pretax amount
+      updates.pretax_amount = E.trade.round(updates.pretax_amount, 2)
       E.trade.itemValues(item, updates)

     find: (item, name) ->


Assignment feature is done

Assignment feature is done

--- app/assets/javascripts/channels/cluster.coffee
+++ app/assets/javascripts/channels/cluster.coffee
@@ -13,19 +13,25 @@
       update_attribute(data.uuid, data.state)
     if data.type == 3
       add_new_assignment data.user
+    if data.type == 4
+      remove_assignment(data.clusterId, data.userId)

-  add_new_cluster: (cluster) ->
+  add_new_cluster = (cluster) ->
     $("#cluster-panel").append cluster
     $("#new-cluster").modal('hide')

-  update_cluster_state: (uuid, state) ->
+  update_cluster_state = (uuid, state) ->
     $(uuid).html("<strong>#{state}</strong>")
     if state.downcase.include? 'ready'
       $(uuid).attr("class", "text-success")
     if state.downcase.include? 'fail'
       $(uuid).attr("class", "text-danger")

-  add_new_assignment: (user) ->
+  add_new_assignment = (user) ->
     $("#assignment_email").val("")
     $("#assignment-list").append user

+  remove_assignment = (clusterId, userId) ->
+    assignment = "#{clusterId}-assigned-to-#{userId}"
+    $("#"+assignment).remove()
+


Extract MessageInfoBoxes from Message template

--- client/message.coffee
+++ client/message.coffee
@@ -206,6 +206,34 @@
     setTitle titleOrUntitled message
     undefined
   , [message.title]
+
+  ## Set sticky column height to remaining height of screen:
+  ## 100vh when stuck, and less when header is (partly) visible.
+  stickyRef = useRef()
+  stickyHeight = useCallback _.debounce(->
+    rect = stickyRef.current.getBoundingClientRect()
+    stickyRef.current.style.height = "calc(100vh - #{rect.y}px)" if rect.height
+    undefined
+  , 100), []
+  useEffect stickyHeight, []  # initialize
+  useEventListener 'resize', stickyHeight
+  useEventListener 'scroll', stickyHeight
+
+

+

+      <MaybeRootHeader message={message}/>
+      <Submessage message={message}/>
+      <MessageInfoBoxes message={message}/>
+      <Credits/>
+

+

+      <TableOfContentsID messageID={message._id}/>
+

+

+Message.displayName = 'Message'
+
+MessageInfoBoxes = React.memo ({message}) ->
   orphans = useTracker ->
     messageOrphans message._id
   , [message._id]
@@ -249,75 +277,54 @@
     )
   , [message._id, message.group]

-  ## Set sticky column height to remaining height of screen:
-  ## 100vh when stuck, and less when header is (partly) visible.
-  stickyRef = useRef()
-  stickyHeight = useCallback _.debounce(->
-    rect = stickyRef.current.getBoundingClientRect()
-    stickyRef.current.style.height = "calc(100vh - #{rect.y}px)" if rect.height
-    undefined
-  , 100), []
-  useEffect stickyHeight, []  # initialize
-  useEventListener 'resize', stickyHeight
-  useEventListener 'scroll', stickyHeight
-
-

-

-      <MaybeRootHeader message={message}/>
-      <Submessage message={message}/>
-

-        {if authors.length
-          <>
-

-              Coauthors of visible messages in this thread:
-

-
{authors}

-          </>
+  <>
+

+      {if authors.length
+        <>
+

+            Coauthors of visible messages in this thread:
+

+
{authors}

+        </>
+      }
+      {if authors.length and mentions.length
+

+      }
+      {if mentions.length
+        <>
+

+            Users @mentioned in visible messages in this thread:
+

+
{mentions}

+        </>
+      }
+

+

+

+        {if emailless()
+          Users who can read this message:
+        else
+          Users who can read this message, and whether they are subscribed to notifications:
         }
-        {if authors.length and mentions.length
-

-        }
-        {if mentions.length
-          <>
-

-              Users @mentioned in visible messages in this thread:
-

-
{mentions}

-          </>
-        }
-

-

+

+
{subscribers}

+

+    {if orphans.length
+

-          {if emailless()
-            Users who can read this message:
-          else
-            Users who can read this message, and whether they are subscribed to notifications:
+          <TextTooltip placement="right" title="Orphan subthreads are caused by someone deleting a message that has (undeleted) children, which become orphans.  You can move these orphans to a valid parent, or delete them, or ask the author or a superuser to undelete the original parent.">
+            {pluralize orphans.length, 'orphaned subthread'}:
+          </TextTooltip>
+

+

+          {for orphan in orphans
+            <Submessage key={orphan._id} message={orphan}/>
           }

-
{subscribers}

-      {if orphans.length
-

-

-            <TextTooltip placement="right" title="Orphan subthreads are caused by someone deleting a message that has (undeleted) children, which become orphans.  You can move these orphans to a valid parent, or delete them, or ask the author or a superuser to undelete the original parent.">
-              {pluralize orphans.length, 'orphaned subthread'}:
-            </TextTooltip>
-

-

-            {for orphan in orphans
-              <Submessage key={orphan._id} message={orphan}/>
-            }
-

-

-      }
-      <Credits/>
-

-

-      <TableOfContentsID messageID={message._id}/>
-

-

-Message.displayName = 'Message'
+    }
+  </>
+MessageInfoBoxes.displayName = 'MessageInfoBoxes'

 editingMessage = (message, user = Meteor.user()) ->
   user? and user.username in (message.editing ? [])


cleanup setter view using helpers

cleanup setter view using helpers

--- views/setter.coffee
+++ views/setter.coffee
@@ -1,4 +1,5 @@
 View = require '../view'
+{ setterNicknames, meanStarRating } = require '../app/helpers'

 Futures = require 'futures'

@@ -45,50 +46,10 @@
     boulders: ->
         for boulder in @req.setter_boulders

-            if boulder.grade is '0'
-                boulder.color = "yellow"
-            else if boulder.grade is '1'
-                boulder.color = "green"
-            else if boulder.grade is '2'
-                boulder.color = "orange"
-            else if boulder.grade is '3'
-                boulder.color = "blue"
-            else if boulder.grade is '4'
-                boulder.color = "red"
-            else if boulder.grade is '5'
-                boulder.color = "white"
-            else
-                console.log "ERROR GRADE"
-
-            setters = ""
-            for setter in boulder.setters
-                for fs in @req.setters
-                    if "#{setter}" == "#{fs.id}"
-                        setters += fs.nickname + " "
-
-            boulder.prettySetters = setters
-
-            month = boulder.date.getMonth() + 1
-            if month < 10
-                month = '0' + month
-            day = boulder.date.getDate()
-            if day < 10
-                day = '0' + day
-            boulder.prettyDate = day + "." + month + "." + boulder.date.getFullYear()
-
-            num_stars = 0
-            sum_stars = 0
-            for star in boulder.stars
-                num_stars += 1
-                sum_stars += star
-
-            if num_stars is 0
-                boulder.mean_stars = ""
-            else
-                star_str = ""
-                for star in [1..Math.round(sum_stars / num_stars)]
-                    star_str += "*"
-                boulder.mean_stars = star_str
+            boulder.color = boulder.colorName()
+            boulder.prettySetters = setterNicknames boulder.setters, @req.setters
+            boulder.prettyDate    = boulder.formattedDate()
+            boulder.mean_stars    = meanStarRating boulder

             boulder


UI for setting list to be closed by default & for disabling comments

--- @client/list.coffee
+++ @client/list.coffee
@@ -198,7 +198,7 @@
       customizations[list_key] ?= {}
       list_config = customizations[list_key]

-      fields = ['list_title', 'list_description', 'list_permit_new_items', 'list_category', 'slider_pole_labels', 'list_opinions_title']
+      fields = ['list_title', 'list_description', 'list_permit_new_items', 'list_category', 'slider_pole_labels', 'list_opinions_title', 'discussion_enabled', 'list_is_archived']

       for f in fields
         val = edit_list[f]
@@ -457,6 +457,9 @@
         padding: "36px 36px"
         width: HOMEPAGE_WIDTH() + 36 * 2

+    edit_list.discussion_enabled ?= customization('discussion_enabled', list_key)
+    edit_list.list_is_archived ?= customization('list_is_archived', list_key)
+
     DIV
       style: wrapper_style

@@ -761,6 +764,50 @@
                             position: 'absolute'
                             right: 0
                           option.support
+
+            if !@props.combines_these_lists
+
+              DIV
+                style:
+                  marginTop: 36
+
+                DIV null,
+                  LABEL
+                    style: {}
+
+                    INPUT
+                      type: 'checkbox'
+                      defaultChecked: !edit_list.discussion_enabled
+                      name: 'discussion_enabled'
+                      onChange: (e) =>
+                        edit_list.discussion_enabled = !edit_list.discussion_enabled
+                        save edit_list
+
+                    SPAN
+                      style:
+                        paddingLeft: 4
+                      translator 'engage.list-config-discussion-enabled', 'Disable commenting. Spectrums only.'
+
+                DIV null,
+
+                  LABEL
+                    style: {}
+
+                    INPUT
+                      type: 'checkbox'
+                      defaultChecked: edit_list.list_is_archived
+                      name: 'list_is_archived'
+                      onChange: (e) =>
+                        edit_list.list_is_archived = !edit_list.list_is_archived
+                        save edit_list
+
+                    SPAN
+                      style:
+                        paddingLeft: 4
+                      translator 'engage.list-config-archived', 'List is closed by default on page load. Useful for archiving past issues.'
+
+
+

       if @props.allow_editing
         EditList


A new callback method so it's possible to do some housecleaning if required

--- lib/observejs/app/assets/javascripts/observejs/creator.js.coffee
+++ lib/observejs/app/assets/javascripts/observejs/creator.js.coffee
@@ -39,6 +39,7 @@
     el.instance.on.events?.forEach (event) ->
       event[1].removeEventListener(event[0], event[2])

+    el.instance.remove?()
     delete el.instance

 @ObserveJS.Creator = new Creator()


Use raw remote RELEASES url

Use raw remote RELEASES url

--- tasks/pack.coffee
+++ tasks/pack.coffee
@@ -337,7 +337,7 @@
         process.env.SIGN_WIN_CERTIFICATE_PASSWORD
       ]

-      remoteReleasesUrl = manifest.updater.urls.win32
+      remoteReleasesUrl = manifest.updater.urls.win32 + '/raw'
       releasesUrl = manifest.updater.urls.win32 + '/RELEASES'

       request {url: releasesUrl}, (err, res, body) ->


ArgumentsWrapper updated with new _describe_ method

--- src/ArgumentsWrapper.coffee
+++ src/ArgumentsWrapper.coffee
@@ -19,4 +19,11 @@
           -> (new Callback(fn)).run()
         ]

+      _describe_: ->
+        args = args[0..1]
+
+        [
+          u(arg for arg in args when u(arg).isAFunction()).first(),
+          u(arg for arg in u(args).cropFrom(this[0]) when u(arg).isAString()).first() || ' '
+        ].reverse()
     }


Fix coffee-script require statement

Fix coffee-script require statement

--- app/templates/mvc-coffee/config/express.coffee
+++ app/templates/mvc-coffee/config/express.coffee
@@ -26,7 +26,7 @@

   controllersPath = path.join __dirname, '../app/controllers'
   fs.readdirSync(controllersPath).forEach (file) ->
-    if file.match(/\.coffee$/) >= 0
+    if file.indexOf('.coffee') >= 0
       require(controllersPath + '/' + file)(app)

   # catch 404 and forward to error handler


Fix GLM coeff vis.

Fix GLM coeff vis.

--- src/ext/components/model-output.coffee
+++ src/ext/components/model-output.coffee
@@ -34,11 +34,11 @@

   switch _model.algo
     when 'glm'
-      if table = _.inspect 'normalized_coefficient_magnitudes', _model
+      if table = _.inspect 'output - Coefficient Magnitudes', _model
         renderPlot 'Normalized Coefficient Magnitudes', _.plot (g) ->
           g(
             g.rect(
-              g.position 'scaled', 'variable'
+              g.position 'coefficients', 'names'
             )
             g.from table
             g.limit 25


Refactored Tests for on-page Map Modal View

--- apps/show/components/map_modal/test/map.coffee
+++ apps/show/components/map_modal/test/map.coffee
@@ -6,30 +6,32 @@
 { fabricate } = require 'antigravity'
 PartnerShow = require '../../../../../models/partner_show.coffee'
 Partner = require '../../../../../models/partner.coffee'
+ModalView = benv.requireWithJadeify resolve(__dirname, '../../../../../components/modal/view'), ['modalTemplate']
+PageModalView= benv.requireWithJadeify resolve(__dirname, '../map'), ['template']
+PageModalView::modalTemplate = ModalView::modalTemplate

-describe 'MapModalView', ->
+describe 'PageModalView', ->

   beforeEach (done) ->
     benv.setup =>
       benv.expose $: benv.require 'jquery'
       Backbone.$ = $
       sinon.stub Backbone, 'sync'
-      MapModalView = benv.requireWithJadeify resolve(__dirname, '../map.coffee'), ['template']
-      MapModalView.__set__ 'GMaps', class @GMaps
+      PageModalView.__set__ 'GMaps', class @GMaps
         addStyle: ->
         setStyle: ->
         addMarker: sinon.stub()
       @show = new PartnerShow fabricate 'show'
       @show.get('location').coordinates = {lat: 30, lng: 30 }
       @partner = new Partner fabricate 'partner'
-      @view = new MapModalView show: @show, partner: @partner
-      done()
+      @view = new PageModalView show: @show, partner: @partner
+      done()

-  afterEach ->
+  afterEach ->
     benv.teardown()
     Backbone.sync.restore()

-  describe '#initialize', ->
+  describe '#initialize', ->
     it 'displays the shows information correctly', ->
       @view.$('.map-modal-partner-name').html().should.containEql 'Gagosian Gallery'
       @view.$('.map-modal-partner-location-address').html().should.containEql '529 W 20th St.'


Optionally get bookings from company

Optionally get bookings from company

--- src/admin/javascripts/services/booking.js.coffee
+++ src/admin/javascripts/services/booking.js.coffee
@@ -15,6 +15,15 @@
     existing = BookingCollections.find(prms)
     if existing
       deferred.resolve(existing)
+    else if prms.company
+      prms.company.$get('bookings').then (collection) ->
+        collection.$get('bookings').then (bookings) ->
+          models = (new BBModel.Admin.Booking(b) for b in bookings)
+          deferred.resolve(models)
+        , (err) ->
+          deferred.reject(err)
+      , (err) ->
+        deferred.reject(err)
     else
       url = ""
       url = prms.url if prms.url


MainView: initial sidebar related changes

--- client/Main/maincontroller/mainviews/mainview.coffee
+++ client/Main/maincontroller/mainviews/mainview.coffee
@@ -4,29 +4,36 @@
   constructor: (options = {}, data)->

     options.domId    = 'kdmaincontainer'
+    options.cssClass = if KD.isLoggedInOnLoad then 'with-sidebar' else ''

     super options, data

     @notifications = []

-  viewAppended:->
+
+  viewAppended: ->
+
+    {mainController} = KD.singletons

     @bindPulsingRemove()
     @bindTransitionEnd()
     @createHeader()
-    @createMainPanels()
+
+    if KD.isLoggedInOnLoad
+    then @createSidebar()
+    else mainController.once 'accountChanged.to.loggedIn', @bound 'createSidebar'
+
+    @createPanelWrapper()
     @createMainTabView()

-    KD.singletons.mainController.ready =>
-      @createAccountArea()
+    mainController.ready =>
+      if KD.isLoggedInOnLoad
+      then @createAccountArea()
+      else mainController.once 'accountChanged.to.loggedIn', @bound 'createAccountArea'
+
       @setStickyNotification()
       @emit 'ready'

-  createMainPanels:->
-
-    @addSubView @panelWrapper = new KDView
-      tagName  : "section"
-      domId    : "main-panel-wrapper"

   createHeader:->

@@ -39,10 +46,9 @@

     @header.clear()

-    @header.addSubView @headerContainer = new KDCustomHTMLView
-      cssClass  : "inner-container"
-
-    @logo = new KDCustomHTMLView
+    @header.addSubView new TopNavigation
+
+    @header.addSubView @logo = new KDCustomHTMLView
       tagName   : "a"
       domId     : "koding-logo"
       cssClass  : if entryPoint?.type is 'group' then 'group' else ''
@@ -54,70 +60,40 @@
         then router.handleRoute '/Activity', {entryPoint}
         else router.handleRoute '/', {entryPoint}

-    @headerContainer.addSubView @logo
-
-    groupLogo = ""
-    if KD.currentGroup?.customize?.logo
-      groupLogo = KD.utils.proxifyUrl KD.currentGroup.logo,
-        crop         : yes
-        width        : 55
-        height       : 55
-
-      @logo.setCss 'background-image', "url(#{groupLogo})"
-      @logo.setClass 'custom'
-
     @logo.setClass KD.config.environment

-    @headerContainer.addSubView @logotype = new CustomLinkView
+    @header.addSubView @logotype = new CustomLinkView
       cssClass : 'logotype'
       title    : 'Koding'
       href     : '/Home'

-    @addDock()
-    @addLoggedOutNav()
-
-
-  addDock:-> @headerContainer.addSubView KD.singleton('dock').getView()
-
-  showDock:-> @header.unsetClass 'no-dock'
-
-  hideDock:-> @header.setClass 'no-dock'
-
-  addLoggedOutNav:->
-
-    @headerContainer.addSubView loggedOutNav = new KDCustomHTMLView
-      tagName : 'nav'
-      partial : """
-        EDUCATION
-        BUSINESS
-        ABOUT
-        PRICING
-        SIGN IN
-        """
-
-    appManager    = KD.singleton "appManager"
-    navDomElement = loggedOutNav.domElement.context
-
-    appManager.on 'AppIsBeingShown', (instance, view, options) ->
-      appName = options.name.toLowerCase()
-
-      for child in navDomElement.children
-        child.classList.remove 'active'
-
-      target = navDomElement.getElementsByClassName(appName)[0]
-      target?.classList.add 'active'
+
+  createSidebar: ->
+
+    @setClass 'with-sidebar'
+
+    @addSubView @sidebar = new KDCustomHTMLView
+      tagName  : 'aside'
+      domId    : 'main-sidebar'
+
+    @sidebar.addSubView KD.singletons.dock.getView()
+
+
+  createPanelWrapper:->
+
+    @addSubView @panelWrapper = new KDView
+      tagName  : 'section'
+      domId    : 'main-panel-wrapper'
+

   createAccountArea:->

-    @accountArea = new KDCustomHTMLView
+    @sidebar.addSubView @accountArea = new KDCustomHTMLView
       cssClass : 'account-area'
-
-    @headerContainer.addSubView @accountArea

     if KD.isLoggedIn()
     then @createLoggedInAccountArea()
     else
-
       mc = KD.getSingleton "mainController"
       mc.once "accountChanged.to.loggedIn", @bound 'createLoggedInAccountArea'


resurrection.topics: use local variable reference of `TopcsListItemView` data

--- client/Social/Topics/Views/TopicsListItemView.coffee
+++ client/Social/Topics/Views/TopicsListItemView.coffee
@@ -8,8 +8,7 @@
       tagName     : 'a'
       pistachio   : '{{#(title)}}'
       click       : (event) =>
-        {slug} = @getData()
-        KD.singletons.router.handleRoute "/Activity?tagged=#{slug}"
+        KD.singletons.router.handleRoute "/Activity?tagged=#{data.slug}"
         KD.utils.stopDOMEvent event
     , data


Make ACE fast and strong. Don't show it until fetch completed, show notification instead

--- client/app/Applications/Ace.kdapplication/ace.coffee
+++ client/app/Applications/Ace.kdapplication/ace.coffee
@@ -21,14 +21,18 @@

   viewAppended:->

+    @hide()
     @appStorage.fetchStorage (storage)=>
       require ['ace/ace'], (ace)=>
-        @editor = ace.edit "editor#{@getId()}"
-        @prepareEditor()
-        @utils.wait => @emit "ace.ready"
         @fetchContents (err, contents)=>
+          notification?.destroy()
+          @editor = ace.edit "editor#{@getId()}"
+          @prepareEditor()
+          @utils.wait => @emit "ace.ready"
           @setContents contents if contents
+          @editor.setReadOnly no
           @editor.gotoLine 0
+          @show()

   prepareEditor:->

@@ -94,6 +98,7 @@

     file = @getData()
     unless /localfile:/.test file.path
+      @notify "Loading...", null, null, 10000
       file.fetchContents callback
     else
       callback null, file.contents or ""
@@ -243,7 +248,7 @@
   ###

   notification = null
-  notify:(msg, style, details)->
+  notify:(msg, style, details, duration)->

     notification.destroy() if notification

@@ -254,7 +259,7 @@
       type      : "mini"
       cssClass  : "editor #{style}"
       container : @parent
-      duration  : if details then 5000 else 2500
+      duration  : duration or if details then 5000 else 2500
       details   : details
       click     : ->
         if notification.getOptions().details


Throw error on incorrect number of arguments.

--- jquery.typeText.coffee
+++ jquery.typeText.coffee
@@ -22,7 +22,7 @@
       addString = arguments[0]
       options = arguments[1]
     else
-      throw "improper number of args"
+      throw new Error "incorrect number of args for typeText"

     # default options if necessary
     defaultOptions =
@@ -31,7 +31,7 @@
       then: ->

     # extended options
-    options = $.extend(defaultOptions, options)
+    options = $.extend defaultOptions, options

     # for each element in the selection
     this.each ->
@@ -56,12 +56,10 @@
       # for every tick. Clears the timer and triggers
       # any callbacks when finished
       onTick = =>
-
         if index < wholeString.length
           # add a character to the string
           printedString += wholeString[index++]
           setString()
-
         else
           # finished printing the string,
           # clean up and print the next line
@@ -72,8 +70,8 @@
             # print the next line
             setTimeout =>
               currentEl.removeClass "printing"
-              currentEl = $(this).children().eq(lineIndex)
-              printLine(stringArray[lineIndex++])
+              currentEl = $(this).children().eq lineIndex
+              printLine stringArray[lineIndex++]
             , options.lineWait

           else


update tests

update tests

--- entry/__tests__/EmbedBlock-test.cjsx
+++ entry/__tests__/EmbedBlock-test.cjsx
@@ -8,19 +8,19 @@

             expect(
                 parseVideoURL('https://www.youtube.com/watch?v=dQw4w9WgXcQ')
-            ).toEqual('http://www.youtube.com/embed/dQw4w9WgXcQ?modestbranding=1')
+            ).toEqual('//www.youtube.com/embed/dQw4w9WgXcQ?modestbranding=1')

             expect(
                 parseVideoURL('https://youtu.be/dQw4w9WgXcQ')
-            ).toEqual('http://www.youtube.com/embed/dQw4w9WgXcQ?modestbranding=1')
+            ).toEqual('//www.youtube.com/embed/dQw4w9WgXcQ?modestbranding=1')

         it 'handles Vimeo links', ->
             { parseVideoURL } = require '../EmbedBlock.js'

             expect(
                 parseVideoURL('https://vimeo.com/168590856')
-            ).toEqual('http://player.vimeo.com/video/168590856')
+            ).toEqual('//player.vimeo.com/video/168590856')

             expect(
                 parseVideoURL('http://player.vimeo.com/video/168590856')
-            ).toEqual('http://player.vimeo.com/video/168590856')+            ).toEqual('//player.vimeo.com/video/168590856')


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/color.coffee
+++ test/color.coffee
@@ -108,3 +108,10 @@
   alpha1 = Color(51, 51, 51, 1)
   alpha2 = Color('rgba(51, 51, 51, 1)')
   alpha3 = Color('#333333ff')
+  alpha4 = Color('#333f')
+  alpha5 = Color([51,51,51,1])
+
+  ok(alpha1.equals(alpha2))
+  ok(alpha1.equals(alpha3))
+  ok(alpha1.equals(alpha4))
+  ok(alpha1.equals(alpha5))


modify login

modify login

--- proxy.coffee
+++ proxy.coffee
@@ -243,18 +243,18 @@
                 console.log err if err
                 @sendError res, "Wrong password", 400
             else
-                req.logIn user, {}, answer
+                passwordKeys.initializeKeys req.body.password, (err) =>
+                    if err
+                        @sendError res, "Keys aren't initialized"
+                    else
+                        req.logIn user, {}, answer

         authenticator(req, res)

     loginAction: (req, res) =>
-        passwordKeys.initializeKeys req.body.password, (err) =>
-            if err
-                success: false
-            else
-                req.body.username = "owner"
-                @authenticate(req, res)
+        req.body.username = "owner"
+        @authenticate(req, res)

     # Clear authentication credentials from session for current user.
     logoutAction: (req, res) =>


chained calls to socket.on()

chained calls to socket.on()

--- proxy.coffee
+++ proxy.coffee
@@ -100,38 +100,39 @@
     {onevent} = socket # intercept all browser-to-proxy events and log them:
     socket.onevent = (packet) -> log 'from browser: ' + JSON.stringify(packet.data)[..1000]; onevent.apply socket, [packet]

-    socket.on 'Execute', ({text, trace}) -> cmd 'Execute', "<Text>#{b64 text}</Text><Trace>#{+!!trace}</Trace>"
-    socket.on 'Edit', ({win, pos, text}) -> cmd 'Edit', "<Text>#{b64 text}</Text><Pos>#{pos}</Pos><Win>#{win}</Win>"
-    socket.on 'CloseWindow',    ({win}) -> cmd 'CloseWindow',         "<win>#{win}</win>"
-    socket.on 'RunCurrentLine', ({win}) -> cmd 'DebugRunLine',        "<win>#{win}</win>"
-    socket.on 'StepInto',       ({win}) -> cmd 'DebugStepInto',       "<win>#{win}</win>"
-    socket.on 'TraceBackward',  ({win}) -> cmd 'DebugBackward',       "<win>#{win}</win>"
-    socket.on 'TraceForward',   ({win}) -> cmd 'DebugForward',        "<win>#{win}</win>"
-    socket.on 'ContinueTrace',  ({win}) -> cmd 'DebugContinueTrace',  "<win>#{win}</win>"
-    socket.on 'Continue',       ({win}) -> cmd 'DebugContinue',       "<win>#{win}</win>"
-    socket.on 'RestartThreads', ({win}) -> cmd 'DebugRestartThreads', "<win>#{win}</win>"
-    socket.on 'Cutback',        ({win}) -> cmd 'DebugCutback',        "<win>#{win}</win>"
-    socket.on 'WeakInterrupt', -> cmd 'WeakInterrupt'
-    socket.on 'Autocomplete', ({line, pos, token}) -> cmd 'GetAutoComplete', "<line>#{b64 line}</line><pos>#{pos}</pos><token>#{token}</token>"
-    socket.on 'SaveChanges', ({win, text, attributes: {stop, monitor, trace}}) ->
-      v = []; for i in [0...text.split('\n').length] by 1 then v.push "<LineAttributeValue><row>#{i}</row><value>#{+(i in (stop or []))}</value></LineAttributeValue>"
-      cmd 'SaveChanges', """
-        <win>#{win}</win>
-        <Text>#{b64 text}</Text>
-        <attributes><LineAttribute><attribute>Stop</attribute><values>#{v.join '\n'}</values></LineAttribute></attributes>
-      """
+    socket
+      .on 'Execute', ({text, trace}) -> cmd 'Execute', "<Text>#{b64 text}</Text><Trace>#{+!!trace}</Trace>"
+      .on 'Edit', ({win, pos, text}) -> cmd 'Edit', "<Text>#{b64 text}</Text><Pos>#{pos}</Pos><Win>#{win}</Win>"
+      .on 'CloseWindow',    ({win}) -> cmd 'CloseWindow',         "<win>#{win}</win>"
+      .on 'RunCurrentLine', ({win}) -> cmd 'DebugRunLine',        "<win>#{win}</win>"
+      .on 'StepInto',       ({win}) -> cmd 'DebugStepInto',       "<win>#{win}</win>"
+      .on 'TraceBackward',  ({win}) -> cmd 'DebugBackward',       "<win>#{win}</win>"
+      .on 'TraceForward',   ({win}) -> cmd 'DebugForward',        "<win>#{win}</win>"
+      .on 'ContinueTrace',  ({win}) -> cmd 'DebugContinueTrace',  "<win>#{win}</win>"
+      .on 'Continue',       ({win}) -> cmd 'DebugContinue',       "<win>#{win}</win>"
+      .on 'RestartThreads', ({win}) -> cmd 'DebugRestartThreads', "<win>#{win}</win>"
+      .on 'Cutback',        ({win}) -> cmd 'DebugCutback',        "<win>#{win}</win>"
+      .on 'WeakInterrupt', -> cmd 'WeakInterrupt'
+      .on 'Autocomplete', ({line, pos, token}) -> cmd 'GetAutoComplete', "<line>#{b64 line}</line><pos>#{pos}</pos><token>#{token}</token>"
+      .on 'SaveChanges', ({win, text, attributes: {stop, monitor, trace}}) ->
+        v = []; for i in [0...text.split('\n').length] by 1 then v.push "<LineAttributeValue><row>#{i}</row><value>#{+(i in (stop or []))}</value></LineAttributeValue>"
+        cmd 'SaveChanges', """
+          <win>#{win}</win>
+          <Text>#{b64 text}</Text>
+          <attributes><LineAttribute><attribute>Stop</attribute><values>#{v.join '\n'}</values></LineAttribute></attributes>
+        """

-    # proxy management events that don't reach the interpreter start with a '*'
-    socket.on '*connect', ({host, port}) -> connectToInterpreter host, port
-    socket.on '*spawn', ({port}) ->
-      {spawn} = require 'child_process'
-      child = spawn 'dyalog', ['+s'], env: extend process.env, RIDE_LISTEN: '0.0.0.0:' + port
-      toBrowser '*spawned', pid: child.pid
-      child.on 'error', (err) -> toBrowser '*spawnedError', err: '' + err; return
-      child.on 'exit', (code, signal) -> toBrowser '*spawnedExited', {code, signal}; return
-      return
+      # proxy management events that don't reach the interpreter start with a '*'
+      .on '*connect', ({host, port}) -> connectToInterpreter host, port
+      .on '*spawn', ({port}) ->
+        {spawn} = require 'child_process'
+        child = spawn 'dyalog', ['+s'], env: extend process.env, RIDE_LISTEN: '0.0.0.0:' + port
+        toBrowser '*spawned', pid: child.pid
+        child.on 'error', (err) -> toBrowser '*spawnedError', err: '' + err; return
+        child.on 'exit', (code, signal) -> toBrowser '*spawnedExited', {code, signal}; return
+        return

-    # "disconnect" is a built-in socket.io event
-    socket.on 'disconnect', -> log 'browser disconnected'; rm sockets, socket; return
+      # "disconnect" is a built-in socket.io event
+      .on 'disconnect', -> log 'browser disconnected'; rm sockets, socket; return

     return


* [bug] forget to removeAllListeners on destroy.

--- src/Object.coffee
+++ src/Object.coffee
@@ -64,6 +64,7 @@
   destroy: ->
     @changeObjectState OBJECT_STATES.destroying
     @final()
+    @removeAllListeners()
     @changeObjectState OBJECT_STATES.destroyed
   free: ->
     @destroy()


:memo: Mention calling after setUpEnvironment

--- src/window.coffee
+++ src/window.coffee
@@ -46,6 +46,8 @@
 #
 # This can be used by packages to have a minimum level of keybindings and
 # menus available when not using the standard editor window.
+#
+# This should only be called after setUpEnvironment() has been called.
 window.setUpDefaultEvents = ->
   windowEventHandler = new WindowEventHandler
   keymap.loadBundledKeymaps()


fix: return the old node from replaceChild

--- dom.coffee
+++ dom.coffee
@@ -231,6 +231,7 @@
 		oldNode._private.parentNode = null
 		oldNode._private.childIndex = -1
 		@childNodes.splice(i, 1, newNode)
+		oldNode
 	toString: (pretty=false,deep=true,indentLevel=0) ->
 		# console.log("Node::toString[#{@nodeName}] from #{@nodeValue}")
 		if pretty


Rename listTree() to listTreeSync()

Rename listTree() to listTreeSync()

--- lib/package-generator-view.coffee
+++ lib/package-generator-view.coffee
@@ -60,7 +60,7 @@
     templatePath = fsUtils.resolveOnLoadPath(path.join("package-generator", "template"))
     packageName = path.basename(@getPackagePath())

-    for templateChildPath in fsUtils.listTree(templatePath)
+    for templateChildPath in fsUtils.listTreeSync(templatePath)
       relativePath = templateChildPath.replace(templatePath, "")
       relativePath = relativePath.replace(/^\//, '')
       relativePath = relativePath.replace(/\.template$/, '')


Remove redundant mixing in of Backbone.Events

SessionManager is already a collection class

--- src/coffee/cilantro/session.coffee
+++ src/coffee/cilantro/session.coffee
@@ -291,8 +291,5 @@
             @close()
             @reset()

-        # Give the session manager events
-        _.extend(SessionManager::, Backbone.Events)
-

     _.extend { SessionManager, Session }, events


Disable ALM crap

Disable ALM crap

--- component/main/AlmApi.coffee
+++ component/main/AlmApi.coffee
@@ -1,5 +1,5 @@

-AlmApi = require 'churro/apis/AlmApi'
+# AlmApi = require 'churro/apis/AlmApi'

@@ -18,7 +18,7 @@
           tId: 123
           eId: 123

-AlmApi.query('UserStory').then (response) ->
-  debugger
-.catch (err) ->
-  debugger+# AlmApi.query('UserStory').then (response) ->
+#   debugger
+# .catch (err) ->
+#   debugger


Clear tooltips on tag remove, too

Clear tooltips on tag remove, too

Fixes #3065

--- app/assets/volume/slot.coffee
+++ app/assets/volume/slot.coffee
@@ -517,9 +517,9 @@
             tag.fillData(data)
             if (if editing then tag.keyword else tag.weight)
               tag.update()
-              tooltips.clear() # hack for broken tooltips
             else
               $scope.tags.remove(tag)
+            tooltips.clear() # hack for broken tooltips
             return
           , (res) ->
             messages.addError


Populate only keyword tags in in edit mode

--- app/assets/volume/slot.coffee
+++ app/assets/volume/slot.coffee
@@ -540,7 +540,7 @@

       fillData: (t) ->
         @weight = t.weight
-        for f in ['coverage','vote','keyword']
+        for f in (if editing then ['keyword'] else ['coverage','vote','keyword'])
           this[f] = []
           if t[f]
             for s in t[f]


Change pool size to 20

Change pool size to 20

--- server/code/index.coffee
+++ server/code/index.coffee
@@ -50,7 +50,7 @@
 # Set up database connection
 mongoose.connect process.env.CU_DB,
   server:
-    poolSize: 200
+    poolSize: 20
     auto_reconnect: true
     socketOptions:
       keepAlive: 1


lowering fps to compensate for limitations of using free-tier hosting

--- server/listServer.coffee
+++ server/listServer.coffee
@@ -41,7 +41,7 @@
             min = arrayOfChoices.length * 4 ; max = arrayOfChoices.length * 7
             spinLength =  Math.random() * (max - min) + min
             prev = null
-            timeToSleep = 1000/30
+            timeToSleep = 1000/20

             selectNext = (array, index, timeOut, iterationsLeft, firstCall) ->


extracting point

extracting point

--- test/point.coffee
+++ test/point.coffee
@@ -1,5 +1,11 @@
 ( ->
   module "Point"
+
+  TOLERANCE = 0.00001
+
+  equalEnough = (expected, actual, tolerance, message) ->
+    message ||= "" + expected + " within " + tolerance + " of " + actual
+    ok(expected + tolerance >= actual && expected - tolerance <= actual, message)

   test "#add", ->
     p1 = Point(5, 6)


Selecting WiFi

Selecting WiFi

--- lib/views/select-wifi-view.coffee
+++ lib/views/select-wifi-view.coffee
@@ -20,7 +20,8 @@

     @addClass 'overlay from-top'
     @prop 'id', 'spark-ide-select-wifi-view'
-    @listPortsPromise = null
+
+    @port = null

   destroy: ->
     @remove()
@@ -46,7 +47,7 @@

   confirmed: (item) ->
     # TODO: Test this
-    atom.workspaceView.trigger 'spark-ide:', [item.value]
+    atom.workspaceView.trigger 'spark-ide:enter-wifi-credentials', [@port, item.value, item.security]
     @cancel()

   getFilterKey: ->
@@ -84,4 +85,20 @@

       cp.exec '/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -s', (error, stdout, stderr) =>
         # TODO: Parse networks list
+        #        SSID BSSID             RSSI CHANNEL HT CC SECURITY (auth/unicast/group)
+        #               Plus MF60 BEB22B 2c:26:c5:be:b2:2b -74  11      N  PL WPA(PSK/AES,TKIP/TKIP) WPA2(PSK/AES,TKIP/TKIP)
+        #                        Outline 64:7c:34:0c:ee:f9 -87  11      Y  PL WPA(PSK/AES,TKIP/TKIP) WPA2(PSK/AES,TKIP/TKIP)
+        #                     UPC1818424 c4:27:95:85:3a:2f -51  11      Y  -- WPA(PSK/AES,TKIP/TKIP) WPA2(PSK/AES,TKIP/TKIP)
+        #                    UPC Wi-Free c6:27:95:85:3a:21 -50  11      Y  -- WPA2(802.1x/AES,TKIP/TKIP)
+        #                         future 00:18:39:d4:46:76 -82  11      N  -- WPA(PSK/AES,TKIP/TKIP) WPA2(PSK/AES,TKIP/TKIP)
+        # HP-Print-E2-Officejet Pro 8600 2c:59:e5:f2:97:e2 -56  11      N  -- NONE
+        #                     Konceptika d8:50:e6:5c:36:b8 -79  6       Y  -- WPA2(PSK/AES/AES)
+        #                    UPC Wi-Free 8e:04:ff:f3:fe:20 -85  4       Y  -- WPA2(802.1x/AES,TKIP/TKIP)
+        #                           suda 00:25:bc:8a:19:63 -43  5       Y  PL WPA(PSK/TKIP/TKIP) WPA2(PSK/AES,TKIP/TKIP)
+        #                    UPC Wi-Free 8e:04:ff:f3:90:d8 -81  1       Y  -- WPA2(802.1x/AES,TKIP/TKIP)
+        #                     UPC0802810 8c:04:ff:f3:90:d6 -84  1       Y  -- WPA(PSK/AES,TKIP/TKIP) WPA2(PSK/AES,TKIP/TKIP)
+        #            Pentagram P 6331-42 00:04:ed:a3:7a:fc -82  1       N  -- WPA(PSK/TKIP/TKIP)
+        #                   BRAK DOSTEPU e0:91:f5:75:21:f2 -60  1       Y  -- WPA2(PSK/AES/AES)
+        #                     UPC3397002 64:7c:34:40:3f:2a -86  36,+1   Y  PL WPA(PSK/AES,TKIP/TKIP) WPA2(PSK/AES,TKIP/TKIP)
+        #                           suda 00:25:bc:8a:19:64 -49  40,-1   Y  PL WPA(PSK/TKIP/TKIP) WPA2(PSK/AES,TKIP/TKIP)
         console.log stdout


Fixed the switch bug of the constructor

Fixed the switch bug of the constructor

--- src/Vector.coffee
+++ src/Vector.coffee
@@ -8,7 +8,7 @@
         @x = vec.x
         @y = vec.y
         @z = vec.z
-      when 2
+      when 3
         @x = x
         @y = y
         @z = z


Clarification of how to become Beta Tester

I've added in a bit of text to make it clearer that ticking the box on the settings page is how you get the emails which enable you to become a beta tester

--- app/pages/settings/email.cjsx
+++ app/pages/settings/email.cjsx
@@ -92,7 +92,7 @@
         <AutoSave resource={@props.user}>
           <label>
             <input type="checkbox" name="beta_email_communication" checked={@props.user.beta_email_communication} onChange={handleInputChange.bind @props.user} />{' '}
-            Get beta project email updates
+            Get beta project email updates and become a beta tester
           </label>
         </AutoSave>
       </p>


Misc fixes to some identifiers.

Misc fixes to some identifiers.

--- src/batman.coffee
+++ src/batman.coffee
@@ -3758,8 +3758,8 @@
     model = record?.constructor || @model
     model.get('storageKey') || helpers.pluralize(helpers.underscore($functionName(model)))

-  getRecordFromData: (attributes, klass = @model) ->
-    record = new klass()
+  getRecordFromData: (attributes, constructor = @model) ->
+    record = new constructor()
     record.fromJSON(attributes)
     record


removing unnecessary logs

removing unnecessary logs

--- server/stream/messages.coffee
+++ server/stream/messages.coffee
@@ -32,15 +32,11 @@

 	options = {}

-	console.log 'Message.find(',JSON.stringify(filter, null, '  '),JSON.stringify(options, null, '  '),')'
-
 	ChatMessage.find(filter, options).observe
 		added: (record) ->
-			console.log 'added ->'.red,record
 			msgStream.emit record.rid, record

 		changed: (record) ->
-			console.log 'changed ->'.red,record
 			msgStream.emit record.rid, record

 	ChatMessage.find({ _deleted: true }, { fields: { rid: 1, _id: 1 } }).observeChanges
@@ -50,4 +46,4 @@
 			msgStream.emit record.rid, { _id: _id, _deleted: true }
 			ChatMessage.remove
 				_id: message.id
-				'u._id': Meteor.userId()+				'u._id': Meteor.userId()


comment fix

comment fix

--- client/helpers.coffee
+++ client/helpers.coffee
@@ -21,7 +21,7 @@
   relativePath = pathFor(path, view)
   Meteor.absoluteUrl(relativePath.substr(1))

-# get query parameter
+# get parameter
 param = (name) ->
   FlowRouter.getParam(name);


Fix name of helper in error message

Fix name of helper in error message

--- client/helpers.coffee
+++ client/helpers.coffee
@@ -1,7 +1,7 @@
 isActive = (type, inverse = false) ->
   name = 'is'
   name = name + 'Not' if inverse
-  name = name + _.capitalize type
+  name = name + 'Active' + _.capitalize type

   (view) ->
     unless view instanceof Spacebars.kw


Add hashbang option to pathFor

Add hashbang option to pathFor

--- client/helpers.coffee
+++ client/helpers.coffee
@@ -14,7 +14,8 @@
     path = view.hash.route
     delete view.hash.route
   query = if view.hash.query then FlowRouter._qs.parse(view.hash.query) else {}
-  FlowRouter.path(path, view.hash, query)
+  hashBang = if view.hash.hash then view.hash.hash else ''
+  FlowRouter.path(path, view.hash, query) + hashBang

 # return absolute url
 urlFor = (path, view) ->
@@ -34,7 +35,7 @@
   FlowRouter.getRouteName()

 # deprecated
-isSubReady = (sub) ->
+isSubReady = (sub) ->
   return FlowRouter.subsReady(sub) if sub
   return FlowRouter.subsReady()


dodge mysterious error on BK/FD

dodge mysterious error on BK/FD

--- client/session.coffee
+++ client/session.coffee
@@ -49,8 +49,9 @@
     else if i >= @hist.length then $.alert 'There is no previous line', 'Dyalog APL Error'
     else
       l = @cm.getCursor().line; if !@histIndex then @hist[0] = @cm.getLine l
-      @cm.replaceRange @hist[i], {line: l, ch: 0}, {line: l, ch: @cm.getLine(l).length}, 'D'
-      @cm.setCursor line: l, ch: @hist[i].replace(/[^ ].*$/, '').length; @histIndex = i
+      if @hist[i]?
+        @cm.replaceRange @hist[i], {line: l, ch: 0}, {line: l, ch: @cm.getLine(l).length}, 'D'
+        @cm.setCursor line: l, ch: @hist[i].replace(/[^ ].*$/, '').length; @histIndex = i
     return

   add: (s) ->


don't allow multiline undo in session

don't allow multiline undo in session

--- client/session.coffee
+++ client/session.coffee
@@ -17,11 +17,16 @@
     @cm.on 'beforeChange', (_, c) =>
       if c.origin != 'D'
         l0 = c.from.line; l1 = c.to.line; m = l1 - l0 + 1; n = c.text.length
-        if n < m then c.update c.from, c.to, c.text.concat(for [0...m - n] then ''); n = m # pad shrinking changes with empty lines
-        if m < n then h = @dirty; @dirty = {}; for x, y of h then @dirty[x + (n - m) * (x > l1)] = y
+        if n < m
+          if c.update
+            c.update c.from, c.to, c.text.concat(for [0...m - n] then ''); n = m # pad shrinking changes with empty lines
+          else
+            c.cancel(); return # the change is probably the result of Undo
+        else if m < n
+          h = @dirty; @dirty = {}; for x, y of h then @dirty[x + (n - m) * (x > l1)] = y
         l = l0
         while l <= l1    then @dirty[l] ?= @cm.getLine l; l++
-        while l < l0 + n then @dirty[l] = 0;             l++
+        while l < l0 + n then @dirty[l] = 0;              l++
       return
     @cm.on 'change', (_, c) =>
       if c.origin != 'D'


[master] fix

[master] fix

--- scraper/compact.coffee
+++ scraper/compact.coffee
@@ -2,37 +2,38 @@
 fs = require 'fs-extra'
 moment = require 'moment'
 debug = require('debug') 'compact'
+_ = require 'lodash'
 Q = require 'q'

 exports.f = f = (date, redis) ->
   Q.ninvoke redis, 'keys', '*'
   .then (res) ->
-    accumulator = {}
-    Q.all _.map(res, (item) ->
-      if item == 'progress'
-        Q()
-      else
-        [_, symbol, _] = item.split(':')
-        symbol = parseInt symbol
-        accumulator[symbol] ?= {}
-        Q.ninvoke redis, 'get', item
-        .then (res) ->
-          payload = JSON.parse res
-          readCount = payload.readCount
-          commentCount = payload.commentCount
-          createDate = payload.createDate
-          accumulator[symbol][createDate] ?=
-            readCount: 0
-            commentCount: 0
-          accumulator[symbol][createDate].readCount += readCount
-          accumulator[symbol][createDate].commentCount += commentCount
-          Q()
-    )
-    .then ->
+    _.reduce res, (accumulator, item) ->
+      accumulator.then (accumulator) ->
+        if item == 'progress'
+          Q accumulator
+        else
+          [_, symbol, _] = item.split(':')
+          symbol = parseInt symbol
+          accumulator[symbol] ?= {}
+          Q.ninvoke redis, 'get', item
+          .then (res) ->
+            payload = JSON.parse res
+            readCount = payload.readCount
+            commentCount = payload.commentCount
+            createDate = payload.createDate
+            accumulator[symbol][createDate] ?=
+              readCount: 0
+              commentCount: 0
+            accumulator[symbol][createDate].readCount += readCount
+            accumulator[symbol][createDate].commentCount += commentCount
+            Q accumulator
+    , Q {}
+    .then (accumulator) ->
       Q.ninvoke redis, 'save'
-    .then ->
-      fs.writeFileSync "#{date.format 'YYYY-MM-DD'}.json", JSON.stringify(accumulator)
-      fs.copySync 'dump.rdb', "#{date.format 'YYYY-MM-DD'}.rdb"
-      debug 'compact finish'
-      Q.ninvoke redis, 'flushall'
+      .then ->
+        fs.writeFileSync "#{date.format 'YYYY-MM-DD'}.json", JSON.stringify(accumulator)
+        fs.copySync 'dump.rdb', "#{date.format 'YYYY-MM-DD'}.rdb"
+        debug 'compact finish'
+        Q.ninvoke redis, 'flushall'


Show space characters in editor

Show space characters in editor

--- .atom/config.cson
+++ .atom/config.cson
@@ -44,6 +44,7 @@
     fontSize: 15
     invisibles: {}
     preferredLineLength: 120
+    showInvisibles: true
   "exception-reporting":
     userId: "757f6ce6-cf27-cab7-5d9f-ec17d5a39efd"
   "git-history":


Update atom configuration

Update atom configuration

--- .atom/config.cson
+++ .atom/config.cson
@@ -1,5 +1,6 @@
 "*":
   core:
+    projectHome: "~/workspace"
     telemetryConsent: "no"
     themes: [
       "one-light-ui"
@@ -7,7 +8,7 @@
     ]
   editor:
     fontFamily: "mononoki"
-    fontSize: 18
+    fontSize: 16
     invisibles: {}
     showIndentGuide: true
     showInvisibles: true


Fix force touch

Fix force touch

--- framer/GestureInputRecognizer.coffee
+++ framer/GestureInputRecognizer.coffee
@@ -392,6 +392,11 @@
 			if event.fingers == 2 and @session.lastEvent.fingers == 2
 				event.delta = Utils.pointSubtract(event.touchCenter, @session.lastEvent.touchCenter)

+		# Force touch
+		if @session?.lastEvent
+			if @session.force
+				event.force = @session.force
+
 		# Convert point style event properties to dom style:
 		# event.delta -> event.deltaX, event.deltaY
 		for pointKey in ["point", "start", "previous", "offset", "delta", "velocity", "touchCenter", "touchOffset"]


Added a run() test. Many bugs have been discovered.

--- test/suite.coffee
+++ test/suite.coffee
@@ -87,4 +87,29 @@
     it 'should append the runner to the stack', ->
       suite.add_before_each runner
       suite._before_eachs.should.eql([runner])
+
+# We're going to try a different approach here to structuring the tests.
+# Please bear with us.
+describe 'A suite', ->
+  {Suite} = require '../lib/suite'
+  {Runner} = require '../lib/runner'
+  {Test} = require '../lib/test'
+  suite = null
+
+  before_each ->
+    suite = new Suite()
+
+  describe 'with two tests', ->
+    test_count_a = test_count_b = null

+    before_each ->
+      test_count_a = 0
+      test_count_b = 0
+
+      suite.add_test new Test -> test_count_a += 1
+      suite.add_test new Test -> test_count_b += 1
+
+    it 'should call each test once when run', ->
+      suite.run()
+      test_count_a.should.equal(1)
+      test_count_b.should.equal(1)


check in some certain progress on the localization tools

--- library/exposure/localized.coffee
+++ library/exposure/localized.coffee
@@ -24,6 +24,7 @@
 ###

 _ = require "lodash"
+yaml = require "js-yaml"
 assert = require "assert"
 asciify = require "asciify"
 connect = require "connect"
@@ -55,6 +56,33 @@
     # Once inherited from, the inheritee is not abstract anymore.
     @abstract yes

+    # Walk over all of the declared translation message locations
+    # and try loading messages off the every declared location. All
+    # the messages will be merged into one object, indexed by the
+    # languages names as keys. Colliding keys are overriden. Every
+    # YAML document is treated as a translation for one language.
+    # Please, never execute this method directly, since it is very
+    # heavy on reasources and should be shadowed with the caching.
+    collectTranslationMessages: ->
+        assert resolve = r = require("path").join
+        assert sreader = require("fs").readFileSync
+        identify = @constructor.identify().underline
+        e = "UTF-8" # all messages files must be UTF-8
+        collector = {} # will hold the translation map
+        assert locations = @constructor.messages() or []
+        locations = _.cloneDeep locations # no mutations
+        deduce = (o) -> o.f = r o.directory, o.location
+        o.blob = sreader deduce(o), e for o in locations
+        processing = (f) -> _.each locations, f; collector
+        processing (o) -> yaml.safeLoadAll o.blob, (doc) ->
+            assert loc = o.location, "got invalid location"
+            message = "Get translation for %s at %s".cyan
+            logger.debug message, identify, loc.underline
+            noLanguage = "got incorrect translation file"
+            assert (language = doc.language), noLanguage
+            previous = collector[language] or new Object
+            collector[language] = _.merge previous, doc
+
     # Specify the translation file for the current service. This
     # file is meant to contain and provide internationalization
     # messages. These are the strings to use for displaying with
@@ -63,6 +91,7 @@
     @messages: (location, options={}) ->
         assert not _.isEmpty cwd = try process.cwd()
         assert previous = @messageLocations or Array()
+        return previous if arguments.length is 0 # get
         implicit = "locale" # default dir for messages
         noLocation = "the location has to be a string"
         noOptions = "no suitable options are supplied"


minor fix

minor fix

--- library/membrane/bilateral.coffee
+++ library/membrane/bilateral.coffee
@@ -92,7 +92,7 @@
             assert _.isArray s = [@__origin, socket: @socket]
             return overwrap.call(s...)(c...) if @__isolated
             name = _.findKey p, (x) -> return x is overwrap
-            assert socket = container.socket or container
+            assert socket = container?.socket or container
             assert socket._events?, "no container/socket"
             return @createLinkage socket, name, directives
         remoted = _.isObject(implement.remote or null)


add unification proc

add unification proc

--- library/membrane/preflight.coffee
+++ library/membrane/preflight.coffee
@@ -169,6 +169,7 @@
         assert _.isArray(previous), noPrevious
         assert _.isString(direction), indirect
         @jscripts = previous.concat direction
+        assert @jscripts = _.unique @jscripts

     # This is a preflight directive that can be used to link any
     # arbitrary CSS style file source. Is important do understand
@@ -184,6 +185,7 @@
         assert _.isArray(previous), noPrevious
         assert _.isString(direction), indirect
         @stsheets = previous.concat direction
+        assert @stsheets = _.unique @stsheets

 # This abstract base class service is an extension of the Screenplay
 # family that does some further environment initialization and set


added acceptance test for large doc ~1mb

--- services/docstore/test/acceptance/coffee/ArchiveDocsTests.coffee
+++ services/docstore/test/acceptance/coffee/ArchiveDocsTests.coffee
@@ -102,6 +102,44 @@
 								callback()
 				async.series jobs, done

+
+
+		describe "archiving massive document", (done)->
+			beforeEach (done)->
+				@timeout 1000 * 30
+				quarterMegInBytes = 250000
+				lines = require("crypto").randomBytes(quarterMegInBytes).toString("hex")
+				console.log @project_id, @docs[1]._id, "helllllo"
+				@docs[1].lines = [lines,lines,lines,lines]
+				DocstoreClient.updateDoc @project_id, @docs[1]._id, @docs[1].lines, =>
+					DocstoreClient.archiveAllDoc @project_id, (error, @res) =>
+						done()
+
+
+			it "should archive all the docs", (done) ->
+				@res.statusCode.should.equal 204
+				done()
+
+			it "should set inS3 and unset lines in each doc", (done) ->
+				jobs = for archiveDoc in @docs
+					do (archiveDoc) =>
+						(callback) =>
+							db.docs.findOne _id: archiveDoc._id, (error, doc) =>
+								should.not.exist doc.lines
+								doc.inS3.should.equal true
+								callback()
+				async.series jobs, done
+
+			it "should be able get the same docs back", (done) ->
+
+				jobs = for archiveDoc in @docs
+					do (archiveDoc) =>
+						(callback) =>
+							DocstoreClient.getS3Doc @project_id, archiveDoc._id, (error, res, doc) =>
+								doc.toString().should.equal archiveDoc.lines.toString()
+								callback()
+				async.series jobs, done
+
 		describe "Unarchiving all docs", ->

 			it "should unarchive all the docs", (done) ->


add missing scope.$apply in pdf viewer event handlers

shows up now there are no apply's coming from the unsaved file check

--- services/web/public/coffee/ide/pdfng/directives/pdfViewer.coffee
+++ services/web/public/coffee/ide/pdfng/directives/pdfViewer.coffee
@@ -307,7 +307,7 @@
 						element.innerWidth()
 					]
 					scope.$emit 'flash-controls'
-					#scope.$apply()
+					scope.$apply()

 				scope.$on 'layout:main:resize', () ->
 					# console.log 'GOT LAYOUT-MAIN-RESIZE EVENT'
@@ -324,7 +324,7 @@
 						element.innerHeight(),
 						element.innerWidth()
 					]
-					#scope.$apply()
+					scope.$apply()

 				scope.$on 'pdf:error', (event, error) ->
 					return if error == 'cancelled'


remove debugging from pdf viewer

remove debugging from pdf viewer

--- services/web/public/coffee/ide/pdfng/directives/pdfViewer.coffee
+++ services/web/public/coffee/ide/pdfng/directives/pdfViewer.coffee
@@ -344,7 +344,7 @@
 					#console.log 'orig position', JSON.stringify(origposition)
 					if pageNum - 1 < origposition.page && delta != 0
 						currentScrollTop =  element.scrollTop()
-						console.log 'adjusting scroll from', currentScrollTop, 'by', delta
+						#console.log 'adjusting scroll from', currentScrollTop, 'by', delta
 						scope.adjustingScroll = true
 						element.scrollTop(currentScrollTop + delta)


Fix kiwi includes

Fix kiwi includes

--- bokehjs/src/coffee/common/layout_box.coffee
+++ bokehjs/src/coffee/common/layout_box.coffee
@@ -1,15 +1,10 @@
 _ = require "underscore"
 Collection = require "./collection"
-{Var, Expr, Constraint, EQ, LE, GE} = "kiwi"
+kiwi = require "kiwi"
 HasProperties = require "./has_properties"
 Range1d = require "../range/range1d"

-Var = kiwi.Variable
-Expr = kiwi.Expression
-Constraint = kiwi.Constraint
-EQ = kiwi.Operator.Eq
-LE = kiwi.Operator.Le
-GE = kiwi.Operator.Ge
+{Var, Expr, Constraint, EQ, LE, GE} = kiwi

 class LayoutBox extends HasProperties
   type: 'LayoutBox'


fixes #257

fixes #257

--- resource/translators/unicode_translator.coffee
+++ resource/translators/unicode_translator.coffee
@@ -7,17 +7,25 @@

 LaTeX.cleanHTML = (text) ->
   html = ''
-  for chunk, i in text.split(/(<\/?(?:i|italic|b|sub|sup|pre|span)(?:[^a-z][^>]*)?>)/i)
-    if i % 2 == 0 # text
-      html += LaTeX.he.escape(chunk)
-    else
-      switch
-        when chunk.match(/^<pre/i)
-          html += '<![CDATA['
-        when chunk.match(/^<\/pre/i)
-          html += ']]>'
-        else
-          html += chunk
+  cdata = false
+  for chunk, i in text.split(/(<\/?(?:i|italic|b|sub|sup|pre|span)(?:[^>a-z][^>]*)?>)/i)
+    switch
+      when i % 2 == 0 # text
+        html += LaTeX.he.escape(chunk)
+
+      when chunk.match(/^<pre/i)
+        html += '<![CDATA['
+        cdata = true
+
+      when chunk.match(/^<\/pre/i)
+        html += ']]>'
+        cdata = false
+
+      else
+        html += chunk
+
+  html += ']]>' if cdata
+
   return html

 LaTeX.html2latex = (html) ->


Delete data.coffee

Delete data.coffee

更新為js檔案

--- src/devtools/panel/app/data.coffee
+++ src/devtools/panel/app/data.coffee
@@ -1,281 +0,0 @@
-# 基础数据模型
-# 不再直接使用游戏数据，以提升程序的稳定性
-((exports)->
-  exports.tohken = {} if !exports.tohken
-  exports.tohken.data = {
-    # 玩家信息
-    player: {
-      "filling": false
-      # 玩家名
-      "name":  null
-      # 等级
-      "level": null
-      # 经验
-      "exp": null
-      # 最大刀剑持有量
-      "max_sword": null
-      # 当前刀剑持有量
-      "now_sword": null
-      # 最大刀装持有量
-      "equip_max": null
-      # 当前刀装持有量
-      "equip_now": null
-      # 秘书刀
-      "secretary": null
-      # 秘书刀的SID
-      "secretary_serial_id": null
-      # 最大队伍数
-      "max_party": null
-      # 最大锻刀数
-      "forge_slot": null
-      # 最大手入数
-      "repair_slot": null
-      # 服务器
-      "world": null
-      # 创建日期
-      "created_at": null
-      # 成就
-      "complete": {
-        # 全刀剑
-        "sword": null
-        # 短刀
-        "dagger": null
-        # 脇差
-        "short": null
-        # 打刀
-        "normal": null
-        # 太刀
-        "long": null
-        # 大太刀
-        "large": null
-        # 槍
-        "spear": null
-        # 薙刀
-        "partisan": null
-        # 刀装
-        "equip": null
-        # 马
-        "horse": null
-        # 回想
-        "scene": null
-        # 景趣
-        "furniture": null
-      }
-      # 战绩
-      "summary": {
-        # 出阵次数
-        "battle": null
-        # 出阵获胜次数
-        "battle_win": null
-        # 演练次数
-        "practice": null
-        # 演练获胜次数
-        "practice_win": null
-        # 远征次数
-        "conquest": null
-        # 远征成功次数
-        "conquest_success": null
-      }
-      # 上次更新时间
-      "last_update": null
-    }
-    # 资源信息
-    resource: {
-      "filling": false
-      # 依頼札
-      "bill":         null
-      # 手伝い札
-      "bonemeal":     null
-      # 木炭
-      "charcoal":     null
-      # 玉鋼
-      "steel":        null
-      # 冷却材
-      "coolant":      null
-      # 砥石
-      "file":         null
-      # 最大资源储量
-      "max_resource": null
-      # 小判
-      "money": null
-      # 模拟数据叠加
-      "vcharcoal":    null
-      "vsteel":       null
-      "vcoolant":     null
-      "vfile":        null
-      # 上次更新时间
-      "last_update": null
-    }
-    # 刀剑
-    sword: {
-      "filling": false
-      # 基准模型
-      "template": {
-        # 序列号
-        "serial_id": null
-        # 图鉴ID
-        "sword_id": null
-        # 稀有度
-        "rarity": null
-        # 等级
-        "level": null
-        # 累计经验
-        "exp": null
-        # 下一级经验
-        "next_exp": null
-        # 进阶次数
-        "evol_num": null
-        # HP
-        "hp": null
-        # HP 上限
-        "hp_max": null
-        # 打撃
-        "atk": null
-        # 統率
-        "def": null
-        # 機動
-        "mobile": null
-        # 衝力
-        "back": null
-        # 偵察
-        "scout": null
-        # 隠蔽
-        "hide": null
-        # 生存提升上限
-        "hp_up": null
-        # 打撃提升上限
-        "atk_up": null
-        # 統率提升上限
-        "def_up": null
-        # 機動提升上限
-        "mobile_up": null
-        # 衝力提升上限
-        "back_up": null
-        # 偵察提升上限
-        "scout_up": null
-        # 隠蔽提升上限
-        "hide_up": null
-        # 必殺
-        "loyalties": null
-        # 疲劳
-        "fatigue": null
-        # 模拟疲劳叠加
-        "vfatigue": null
-        # 刀装位
-        "equip_serial_id1": null
-        "equip_serial_id2": null
-        "equip_serial_id3": null
-        # 马
-        "horse_serial_id": null
-        # 氪金道具 1 普通 2 极
-        "item_id": null
-        # 写保护
-        "protect": null
-        # 状态
-        # 0：正常，1：手入，3：远征 暂未发现其他情况…
-        "status": null
-        # 上次战斗时间
-        "recovered_at": null
-        # 获得时间
-        "created_at": null
-      }
-      # 数据填充位置
-      "data": {}
-      # 上次更新时间
-      "last_update": null
-    }
-    # 刀装
-    equip: {
-      "filling": false
-      "template": {
-        # 序列号
-        "serial_id": null
-        # 装备ID，10001及以上为马匹
-        "equip_id": null
-        # 持有兵数
-        "soldier": null
-      }
-      # 数据填充位置
-      "data": {}
-      # 上次更新时间
-      "last_update": null
-    }
-    # 队伍
-    party: {
-      "filling": false
-      "template": {
-        # 队伍编号
-        "party_no": null
-        # 状态
-        # 0：未开放，1：放置，2：远征
-        "status": "2"
-        # 队伍名
-        "party_name": null
-        # 远征结束时间
-        "finished_at": null
-        # 编组信息
-        "slot": {
-          "1": {
-            "serial_id": null
-          }
-          "2": {
-            "serial_id": null
-          }
-          "3": {
-            "serial_id": null
-          }
-          "4": {
-            "serial_id": null
-          }
-          "6": {
-            "serial_id": null
-          }
-          "5": {
-            "serial_id": null
-          }
-        }
-      }
-      # 数据填充位置
-      "data": {}
-      # 上次更新时间
-      "last_update": null
-    }
-    # 锻造
-    forge: {
-      "filling": false
-      # 锻造位
-      "open": null
-      # 数据填充位置
-      "data": {}
-      # 上次更新时间
-      "last_update": null
-    }
-    # 手入
-    repair: {
-      "filling": false
-      # 手入位
-      "open": null
-      # 数据填充位置
-      "data": {}
-      # 上次更新时间
-      "last_update": null
-    }
-    # 远征
-    conquest: {
-      "filling": false
-      "template": {
-        "party": null
-        "return": null
-      }
-      # 数据填充位置
-      "data": {}
-      # 上次更新时间
-      "last_update": null
-    }
-    logs: {
-      "forge": {}
-      "battle": {}
-      "error": {}
-    }
-  }
-)(window)


fix(macAutocomplete): Fixed $animate API changes due to AngularJS v1.3

--- src/directives/autocomplete.coffee
+++ src/directives/autocomplete.coffee
@@ -175,8 +175,9 @@
       @description
       Adding menu to DOM
       @param {Function} callback Callback after enter animation completes
-      ###
-      appendMenu = (callback) ->
+      @returns {Promise} Animation promise
+      ###
+      appendMenu = ->
         unless isMenuAppended
           element.bind "blur", blurHandler

@@ -187,18 +188,19 @@
         isMenuAppended = true

         if inside
-          $animate.enter menuEl, undefined, element, callback
+          return $animate.enter(menuEl, undefined, element)
         else
-          $animate.enter menuEl, angular.element(document.body), undefined, callback
+          return $animate.enter(menuEl, angular.element(document.body))

       ###
       @function
       @name reset
       @description
       Resetting autocomplete
+      @returns {Promise} Leave animation promise
       ###
       reset = ->
-        $animate.leave menuEl, ->
+        $animate.leave(menuEl).then ->
           $menuScope.index        = 0
           $menuScope.items.length = 0

@@ -209,8 +211,6 @@
           isMenuAppended = false

           element.unbind "blur", blurHandler
-
-        return

       ###
       @function
@@ -254,7 +254,7 @@
             else
               {label: item, value: item}

-          appendMenu positionMenu
+          appendMenu().then positionMenu

         else
          reset()


Clear errors when re-running a scenario.

--- src/Witness/_witness/Scripts/witness-ui/ScenarioViewModel.coffee
+++ src/Witness/_witness/Scripts/witness-ui/ScenarioViewModel.coffee
@@ -9,6 +9,9 @@
 		{ @path, @url } = @scenario.parentSpecification.parentFile
 		@parts = (new PartViewModel part for part in [].concat @scenario.given, @scenario.when, @scenario.then)
 		@errors = ko.observableArray []
+
+		@scenario.on.running.addHandler =>
+			@errors.removeAll()

 		@scenario.on.failed.addHandler (errors) =>
 			if jQuery.isArray errors


Added tests for each type

Added tests for each type

--- test/all/injection.coffee
+++ test/all/injection.coffee
@@ -9,13 +9,7 @@
 {expect} = chai

 describe 'udefine.inject', ->
-
-  objEmpty = {}
-  obj1 = {}
-  obj2 = {}
-  obj3 = {}
-  obj4 = {}
-
+
   it 'udefine.inject return value is a function', ->
     expect(udefine.inject()).to.be.a('function')

@@ -23,36 +17,83 @@
     expect(-> udefine.inject()()).to.not.throw(Error)

   it 'udefine.inject called without name does not modify the object', ->
-    udefine.inject(objEmpty)()
+    obj = {}

-    expect(objEmpty).to.be.empty
+    udefine.inject(obj)()
+
+    expect(obj).to.be.empty

   it 'udefine.inject adding an undefined value', ->
-    udefine.inject(obj1, '1')()
+    obj = {}

-    expect(obj1).to.have.property('1')
-    expect(obj1).to.have.ownProperty('1')
-    expect(obj1['1']).to.be.undefined
+    udefine.inject(obj, '1')()
+
+    expect(obj).to.have.ownProperty('1')
+    expect(obj['1']).to.be.undefined

   it 'udefine.inject adding a null value', ->
-    udefine.inject(obj2, '2')(null)
+    obj = {}

-    expect(obj2).to.have.property('2')
-    expect(obj2).to.have.ownProperty('2')
-    expect(obj2['2']).to.be.null
+    udefine.inject(obj, '2')(null)
+
+    expect(obj).to.have.property('2')
+    expect(obj).to.have.ownProperty('2')
+    expect(obj['2']).to.be.null

   it 'udefine.inject adding an string value', ->
-    udefine.inject(obj3, '3')('test')
+    obj = {}

-    expect(obj3).to.have.property('3')
-    expect(obj3).to.have.ownProperty('3')
-    expect(obj3['3']).to.be.a('string')
-    expect(obj3['3']).to.equal('test')
+    udefine.inject(obj, '3')('test')
+
+    expect(obj).to.have.property('3')
+    expect(obj).to.have.ownProperty('3')
+    expect(obj['3']).to.be.a('string')
+    expect(obj['3']).to.equal('test')

   it 'udefine.inject adding a number value', ->
-    udefine.inject(obj4, '4')(4)
+    obj = {}

-    expect(obj4).to.have.property('4')
-    expect(obj4).to.have.ownProperty('4')
-    expect(obj4['4']).to.be.a('number')
-    expect(obj4['4']).to.equal(4)
+    udefine.inject(obj, '4')(4)
+
+    expect(obj).to.have.property('4')
+    expect(obj).to.have.ownProperty('4')
+    expect(obj['4']).to.be.a('number')
+    expect(obj['4']).to.equal(4)
+
+  it 'udefine.inject adding a boolean value', ->
+    obj = {}
+
+    udefine.inject(obj, '5')(true)
+
+    expect(obj).to.have.property('5')
+    expect(obj).to.have.ownProperty('5')
+    expect(obj['5']).to.be.true
+
+  it 'udefine.inject adding an object', ->
+    obj = {}
+
+    udefine.inject(obj, '6')({})
+
+    expect(obj).to.have.property('6')
+    expect(obj).to.have.ownProperty('6')
+    expect(obj['6']).to.be.a('object')
+    expect(obj['6']).to.be.empty
+
+  it 'udefine.inject adding an array', ->
+    obj = {}
+
+    udefine.inject(obj, '7')([])
+
+    expect(obj).to.have.property('7')
+    expect(obj).to.have.ownProperty('7')
+    expect(obj['7']).to.be.a('array')
+    expect(obj['7']).to.be.empty
+
+  it 'udefine.inject adding a function', ->
+    obj = {}
+
+    udefine.inject(obj, '8')(->)
+
+    expect(obj).to.have.property('8')
+    expect(obj).to.have.ownProperty('8')
+    expect(obj['8']).to.be.a('function')


shuffle結果は空白区切りで出力する

shuffle結果は空白区切りで出力する

slackの投稿は行が多くなると自動的に改行が削減される仕様みたいなので変更

--- scripts/shuffle.coffee
+++ scripts/shuffle.coffee
@@ -24,4 +24,4 @@
       return

     arr = attr.split(" ")
-    msg.send shuffle(arr).join("\n")
+    msg.send shuffle(arr).join(" ")


Add persistence

Add persistence

--- scripts/standup.coffee
+++ scripts/standup.coffee
@@ -20,9 +20,10 @@
 # Author:
 #   <github username of the original script author>
 module.exports = (robot) ->
-  standup = null
+  robot.brain.set 'standup', null

   robot.hear /\/standup$/i, (msg) ->
+    standup = robot.brain.get('standup')
     if !standup
       msg.send "No standup recorded yet."
     else
@@ -30,9 +31,11 @@

   robot.hear /\/standup (.*)$/i, (msg) ->
     status = msg.message.user.name + ": " + msg.match[1] + "\n"
+    standup = robot.brain.get('standup')
     if !standup
       standup = status
     else
       standup += status

+    robot.brain.set 'standup', standup
     msg.send "Standup recorded."


Testing auto deploy.

Testing auto deploy.

--- scripts/testing.coffee
+++ scripts/testing.coffee
@@ -5,4 +5,4 @@

 module.exports = (robot) ->
     robot.respond /testing$/i, (msg) ->
-        msg.send "I’m alive!"
+        msg.send "I’m alive and well!"


updated tflobby command documentation

updated tflobby command documentation

--- scripts/tflobby.coffee
+++ scripts/tflobby.coffee
@@ -8,18 +8,19 @@
 #
 # Commands:
 #   hubot rcon [say|message|msg] <message> on <server> - sends <message> via rcon to <server>. limited to `rcon` roles.
-#   hubot rcon [list|the list|roster|players] on <server> - sends the player roster from the previous lobby to <server>. limited to `rcon` roles.
+#   hubot rcon send [list|the list|roster|players] on <server> - sends the player roster from the previous lobby to <server>. limited to `rcon` roles.
 #   hubot rcon [change map|changelevel|map] on <server> to <mapname> - changes the map on <server> to <mapname>. limited to `rcon` roles.
-#   hubot [sg|new] <mapname> - creates a new lobby with the map set to <mapname>. limited to `officer` roles.
+#   hubot [[sg|new]|[[sg|new] <mapname>]|[sg|new] random <gamemode> map] - creates a new lobby with the map set to <mapname>. limited to `officer` roles.
 #   hubot [cg|kill] - cancels a running lobby. limited to `officer` roles.
 #   hubot format <nPlayers> - changes the number of players per side to nPlayers.
-#   hubot add <me|user> - adds <user> to the lobby. addition of other users is limited to `officer` roles.
-#   hubot rem <me|user> - removes <user> from the lobby. removal of other users is limited to `officer` roles.
-#   hubot map <mapname> - changes the lobby map to <mapname>. limited to `officer` roles.
+#   hubot [add|add <me|user>] - adds <user> to the lobby. addition of other users is limited to `officer` roles.
+#   hubot [rem|rem <me|user>] - removes <user> from the lobby. removal of other users is limited to `officer` roles.
+#   hubot [random <gamemode> map|map <mapname>] - changes lobby map to random of game mode or changes the lobby map to <mapname>. limited to `officer` roles.
 #   hubot server <servername> - changes the lobby server to <servername>. limited to `officer` roles.
 #   hubot [status|games] - reports the lobby status
 #   hubot [previous|last game|lastgame|previous game] - reports the previous lobby status
 #   hubot top <maps|players> - reports the daily counter data accumulated for either maps or players
+#   hubot change <property> to <value> - changes a tflobby bot setting
 #
 # Author:
 #   skibz


Output error and reply from Twitter response.

--- scripts/tweeter.coffee
+++ scripts/tweeter.coffee
@@ -78,6 +78,8 @@
     twit.post "statuses/update",
       status: update
     , (err, reply) ->
+      console.log "===== Twitter responses:"
+      console.log err, reply
       if err
         data = JSON.parse(err.data).errors[0]
         msg.reply "I can't do that. #{data.message} (error #{data.code})"


Use the right freaking method for getting the tweet length maybe.

What is programming.

#17

--- scripts/tweeter.coffee
+++ scripts/tweeter.coffee
@@ -24,6 +24,7 @@
 #
 # Dependencies:
 #   "twit": "1.1.8"
+#   "twitter-text": "1.7.x"
 #
 # Configuration:
 #   HUBOT_TWITTER_CONSUMER_KEY
@@ -70,7 +71,7 @@
       msg.reply "You can't very well tweet an empty status, can ya?"
       return

-    tweetLength = twitterText.tweetLength(update)
+    tweetLength = twitterText.getTweetLength(update)
     if tweetLength > 140
       msg.reply "Your tweet is #{tweetLength - 140} characters too long. Twitter users can't read that many characters!"
       return


[grunt] replace `nunjucks:build` with `nunjucks` in Grunt config, since we can't rely on target name anymore — it's used by locales

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -129,7 +129,7 @@
   grunt.registerTask 'build', [
     'clean'
     'copy:build'
-    'nunjucks:build'
+    'nunjucks'
     'sprite:build'
     'webfont:build'
     'sass:build'


[test] check IDs

[test] check IDs

--- test/index.coffee
+++ test/index.coffee
@@ -199,6 +199,7 @@
         await fakeJob queuename, data, state, ideally defer {queue, job}

         await instance.idsAndCountByState queuename, state, ideally defer {ids, count}
+        expect(ids[queuename]).to.contain(job.jobId)
         expect(count).to.equal 1

         qCleaner(queue).asCallback done
@@ -299,12 +300,15 @@
       ideally = errify done
       data    = {name: "testjob"}
       await fakeJob queuename, data, null, ideally defer {queue, job}
-
-      formattedJob =
-        id: job.jobId
-        queue: queuename
-      await instance.remove [formattedJob], ideally defer()
-      await instance.unknownKeysForIds [formattedJob.id], ideally defer keys
+      await fakeJob queuename, data, null, ideally defer queueAndJob2
+
+      formattedJobs = [
+        {id: job.jobId,              queue: queuename}
+        {id: queueAndJob2.job.jobId, queue: queuename}
+      ]
+      ids = (job.id for job in formattedJobs)
+      await instance.remove formattedJobs, ideally defer()
+      await instance.unknownKeysForIds ids, ideally defer keys
       expect(keys).to.be.empty

       done()


Correct failing tests

Correct failing tests

--- test/index.coffee
+++ test/index.coffee
@@ -791,7 +791,7 @@
                   done())

     it "should allow a read request to proceed when a prior write request dies without releasing write_req", (done) ->
-      expectedOrder = "1331"
+      expectedOrder = "12133"
       order = ''
       lock2.timeOut = 100
       lock1.lockExpiration = 1000
@@ -802,15 +802,16 @@
         # set the write_req flag without creating a lock, to simulate a dead write request
         setWriteReq lock2, (err, doc) ->
           assert not err
-          lock3.obtainReadLock().on 'locked', (ld) ->
-            assert ld?
-            order += '3'
-            lock3.releaseLock().on 'released', (ld) ->
+          order += '2'
+          lock1.releaseLock().on 'released', (ld) ->
+            assert ld?
+            order += '1'
+            lock3.obtainReadLock().on 'locked', (ld) ->
               assert ld?
               order += '3'
-              lock1.releaseLock().on 'released', (ld) ->
+              lock3.releaseLock().on 'released', (ld) ->
                 assert ld?
-                order += '1'
+                order += '3'
                 assert.equal order, expectedOrder
                 done()

@@ -836,7 +837,7 @@
                 done())

     it "should allow a read request to proceed when a prior write request dies waiting for a write lock without releasing write_req", (done) ->
-      expectedOrder = "133"
+      expectedOrder = "12133"
       order = ''
       lock2.timeOut = 100
       lock1.lockExpiration = 1000
@@ -846,14 +847,18 @@
         # set the write_req flag without creating a lock, to simulate a dead write request
         setWriteReq lock2, (err, doc) ->
           assert not err
-          lock3.obtainReadLock().on 'locked', (ld) ->
-            assert ld?
-            order += '3'
-            lock3.releaseLock().on 'released', (ld) ->
+          order += '2'
+          lock1.releaseLock().on 'released', (ld) ->
+            assert ld?
+            order += '1'
+            lock3.obtainReadLock().on 'locked', (ld) ->
               assert ld?
               order += '3'
-              assert.equal order, expectedOrder
-              done()
+              lock3.releaseLock().on 'released', (ld) ->
+                assert ld?
+                order += '3'
+                assert.equal order, expectedOrder
+                done()

     it "of a new read lock shouldn't be affected by when the previous write lock was due to expire", (done) ->
       lock1.lockExpiration = 0  # Never expires


add test for logout if calling logout deletes req.user

--- test/index.coffee
+++ test/index.coffee
@@ -197,13 +197,14 @@
     beforeEach ->
       @artsyPassport.__set__ 'request', del: @del = sinon.stub().returns(send: @send = sinon.stub().returns(end: (cb) -> cb()))
       @logout = @artsyPassport.__get__ 'logout'
-      @req = { query: {}, get: (-> 'access-foo-token'), logout: sinon.stub(), user: {get: -> 'secret'} }
+      @req = { query: {}, get: (-> 'access-foo-token'), logout: (=> @req.user = null), user: {get: -> 'secret'} }
       @res = { send: sinon.stub() }
+      @logoutSpy = sinon.spy(@req, 'logout');
       @next = sinon.stub()

     it 'logs out, deletes the auth token, and redirects home', ->
       @logout @req, @res, @next
-      @req.logout.called.should.be.true
+      @logoutSpy.called.should.be.true
       @del.args[0][0].should.containEql '/api/v1/access_token'
       @send.args[0][0].should.eql access_token: 'secret'
       @next.called.should.be.true
@@ -211,6 +212,7 @@
     it 'still works if there is no access token', ->
       @req.user = undefined
       @logout @req, @res, @next
-      @req.logout.called.should.be.true
+      @logoutSpy.called.should.be.true
+      (@req.user?).should.not.be.ok
       @del.called.should.not.be.ok
       @next.called.should.be.true


[test] RedisModel

[test] RedisModel

--- test/index.coffee
+++ test/index.coffee
@@ -95,3 +95,20 @@
       await queue.count().asCallback ideally defer count
       expect(count).to.equal 1
       queue.empty().then(queue.close()).asCallback done
+
+
+describe "RedisModel", ->
+  client   = null
+  instance = null
+  beforeEach ->
+    client   = redis.createClient()
+    instance = new RedisModel client
+
+  afterEach ->
+    client   = null
+    instance = null
+
+  describe "##constructor", ->
+    it "should set redis on the model", ->
+      expect(instance.redis).to.equal client
+


ci(win): N1.exe => N1Setup.exe (it's an installer)

--- build/tasks/publish-nylas-build-task.coffee
+++ build/tasks/publish-nylas-build-task.coffee
@@ -76,7 +76,6 @@
           Key: destName
           ACL: "public-read"
           Bucket: "edgehill"
-          ContentDisposition:"attachment; filename=\"N1#{ext}\""

       uploader.on "error", (err) ->
         reject(err)
@@ -148,7 +147,7 @@
           uploadPromises.push uploadZipToS3(appName(), "#{fullVersion}/#{process.platform}/#{process.arch}/N1.zip")
         if process.platform is 'win32'
           uploadPromises.push uploadToS3("installer/"+winReleasesName(), "#{fullVersion}/#{process.platform}/#{process.arch}/RELEASES")
-          uploadPromises.push uploadToS3("installer/"+winSetupName(), "#{fullVersion}/#{process.platform}/#{process.arch}/N1.exe")
+          uploadPromises.push uploadToS3("installer/"+winSetupName(), "#{fullVersion}/#{process.platform}/#{process.arch}/N1Setup.exe")
           uploadPromises.push uploadToS3("installer/"+winNupkgName(), "#{fullVersion}/#{process.platform}/#{process.arch}/#{winNupkgName()}")
         if process.platform is 'linux'
           buildDir = grunt.config.get('atom.buildDir')


Remove Form plugin (DOM)

Remove Form plugin (DOM)

--- src/marbles/plugins/dom/form.js.coffee
+++ src/marbles/plugins/dom/form.js.coffee
@@ -1,152 +0,0 @@
-#= require marbles/dom
-#= require_self
-
-Marbles.DOM.Form = class HTMLForm
-  constructor: (@el) ->
-    @fields = {}
-
-    @validator = new HTMLFormValidator(@)
-
-    @initialize?()
-
-    Marbles.DOM.on(@el, 'submit', @handleSubmit)
-
-  handleSubmit: (e) =>
-    e.preventDefault()
-
-    @submitWithValidation()
-
-  submitWithValidation: (field_names = Object.keys(@fields), callbacks = {}) =>
-    callbacks.success ?= @submit
-    @validator.validate(field_names, callbacks)
-
-  submit: =>
-    @el.submit()
-
-Marbles.DOM.Form.Validator = class HTMLFormValidator
-  constructor: (@form) ->
-
-  validate: (field_names, callbacks = {}) =>
-    @num_pending = 0
-    @num_failed = 0
-
-    @failureFn = callbacks.failure
-    @successFn = callbacks.success
-
-    for name, field of @form.fields
-      if field_names.indexOf(name) is -1
-        field.clearInvalid()
-        continue
-
-      @num_pending += 1
-
-      @validateField(field)
-
-  validateField: (field) =>
-    # wait until finished looping through fields
-    # so @num_pending is accurate
-    setTimeout =>
-      field.validate(success: @fieldValid, failure: @fieldInvalid)
-    , 0
-
-  finally: =>
-    if @num_failed > 0
-      @failureFn?()
-    else
-      @successFn?()
-
-    @failureFn = null
-    @successFn = null
-    @num_pending = null
-    @num_failed = null
-
-  fieldValid: =>
-    @num_pending -= 1
-
-    if @num_pending <= 0
-      @finally()
-
-  fieldInvalid: =>
-    @num_pending -= 1
-    @num_failed += 1
-
-    if @num_pending <= 0
-      @finally()
-
-Marbles.DOM.Form.Field = class HTMLFormField
-  @CSS_INVALID_CLASS: 'invalid'
-  @CSS_VALID_CLASS: 'valid'
-  @CSS_HIDDEN_CLASS: 'hidden'
-
-  container_el: null
-  error_msg_el: null
-
-  constructor: (@el) ->
-    @initialize?()
-
-  getValue: =>
-    @el.value
-
-  clearInvalid: =>
-    if @container_el
-      Marbles.DOM.removeClass(@container_el, @constructor.CSS_INVALID_CLASS) if @container_el
-      Marbles.DOM.addClass(@error_msg_el, @constructor.CSS_HIDDEN_CLASS) if @error_msg_el
-
-  markValid: =>
-    if @container_el
-      Marbles.DOM.removeClass(@container_el, @constructor.CSS_INVALID_CLASS)
-      Marbles.DOM.addClass(@container_el, @constructor.CSS_VALID_CLASS)
-    Marbles.DOM.addClass(@error_msg_el, @constructor.CSS_HIDDEN_CLASS) if @error_msg_el
-
-  markInvalid: (msg) =>
-    if @container_el
-      Marbles.DOM.removeClass(@container_el, @constructor.CSS_VALID_CLASS)
-      Marbles.DOM.addClass(@container_el, @constructor.CSS_INVALID_CLASS)
-
-    if msg && @error_msg_el
-      Marbles.DOM.setInnerText(@error_msg_el, msg)
-      Marbles.DOM.removeClass(@error_msg_el, @constructor.CSS_HIDDEN_CLASS)
-
-  validate: (callbacks = {}) =>
-    @performValidation?(
-      success: =>
-        @markValid()
-        callbacks.success?()
-
-      failure: (msg) =>
-        @markInvalid(msg)
-        callbacks.failure?()
-    )
-
-Marbles.DOM.Form.CombinedFields = class HTMLFormCombinedFields
-  constructor: (@fields) ->
-    @field_names = Object.keys(@fields)
-
-    @initialize?()
-
-  getValue: (name) =>
-    @fields[name].getValue() if name
-
-  clearInvalid: (field_names = @field_names) =>
-    for name in field_names
-      @fields[name].clearInvalid()
-
-  markValid: (field_names = @field_names) =>
-    for name in field_names
-      @fields[name].markValid()
-
-  markInvalid: (errs) =>
-    for name, msg in errs
-      @fields[name].markInvalid(msg)
-
-  validate: (callbacks = {}) =>
-    @performValidation?(
-      success: (field_names) =>
-        @markValid(field_names)
-        callbacks.success?()
-
-      failure: (errs) =>
-        @markInvalid(errs)
-        callbacks.failure?()
-    )
-


Add 5sec. timeout for locationInfo helper

--- client/Main/utils.coffee
+++ client/Main/utils.coffee
@@ -871,6 +871,8 @@

       return  if (queue.push callback) > 1

+      KD.utils.wait 5000, fail
+
       $.getJSON '//freegeoip.net/json/?callback=?', (data, status)->

         if status is "success"


+ limits range FakeIterator(not fined)

+ limits range FakeIterator(not fined)

--- test/fake-iterator.coffee
+++ test/fake-iterator.coffee
@@ -4,15 +4,55 @@

 module.exports =  class FakeIterator
   inherits FakeIterator, EncodingIterator
+  getIndexGreaterThan = (arr, name)->
+    i = -1
+    while ++i < arr.length
+      return i if name <= arr[i]
+    return -1
+  getIndexLessThan = (arr, name)->
+    i = arr.length
+    while --i >= 0
+      return i if name >= arr[i]
+    return -1
   constructor: ->
-    @i=0
     super
     @keys = Object.keys @db.data
+    @keys.sort()
+    @start      = 0
+    @notReverse = @options.reverse isnt true
+    @end        = @keys.length
+    if @options.lt
+      index = getIndexLessThan(@keys, @options.lt)
+      index = @keys.length if index < 0
+      index--
+      if @notReverse
+        @end = index
+      else
+        @start = index
+    else if @options.lte
+      index = getIndexLessThan(@keys, @options.lte)
+      index = @keys.length if index < 0
+      if @notReverse
+        @end = index
+      else
+        @start = index
+    if @options.gt
+      index = getIndexGreaterThan(@keys, @options.gt) + 1
+      if @notReverse
+        @start = index
+      else
+        @end = index
+    else if @options.gte
+      index = getIndexGreaterThan(@keys, @options.gte)
+      if @notReverse
+        @start = index
+      else
+        @end = index
   _nextSync: sinon.spy ->
-    result  = @i < @keys.length
+    result  = @start < @end
     if result
-      key   = @keys[@i]
+      key   = @keys[@start]
       result= [key, @db.data[key]]
-      ++@i
+      ++@start
     result


IDE: remove unused route

IDE: remove unused route

--- client/ide/routes.coffee
+++ client/ide/routes.coffee
@@ -163,10 +163,4 @@

       { machineLabel, workspaceSlug } = data.params

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


try harder to detect the place to do scrolling

this has been a persistent problem with chrome
on small screens due to the way it does rounding.

--- client/lib/active.coffee
+++ client/lib/active.coffee
@@ -7,7 +7,7 @@
   if scrolled.length > 0
     scrolled
   else
-    $("body, html").scrollLeft(4).filter(-> $(this).scrollLeft() > 0).scrollTop(0)
+    $("body, html").scrollLeft(12).filter(-> $(this).scrollLeft() > 0).scrollTop(0)

 scrollTo = (el) ->
   active.scrollContainer ?= findScrollContainer()


Bio changes, closes #33

Bio changes, closes #33

--- app/translations/en_us.coffee
+++ app/translations/en_us.coffee
@@ -1026,9 +1026,9 @@
           name: 'David Miller'
           image: 'images/about/team/david.jpg'
           description: '''
-            As a visual communicator, David is passionate about tellings stories through clear, clean, and effective design.
-            Before joining the Zooniverse team as Visual Designer, David worked for The Raindance Film Festival, the News 21 Initiative's Apart From War, Syracuse Magazine, and as a freelance designer for his small business, Miller Visual.
-            David is a graduate of the S.I. Newhouse School of Public Communications at Syracuse University, where he studied Visual & Interactive Communications.
+            David is passionate about tellings stories through clear, clean, and effective design.
+            Before joining the Zooniverse team as Visual Designer, David worked for the Raindance Film Festival, the News 21 Initiative's Apart From War, and as a freelance designer for his small business, Miller Visual.
+            David is a graduate of the S.I. Newhouse School of Public Communications at Syracuse University.
           '''

         parrish:
@@ -1043,8 +1043,8 @@
           name: 'Arfon Smith'
           image: 'images/about/team/arfon.jpg'
           description: '''
-            As an undergraduate, Arfon studied Chemistry at the University of Sheffield before completing his Ph.D. in Astrochemistry at The University of Nottingham in 2006.
-            He worked as a senior developer at the Wellcome Trust Sanger Institute (Human Genome Project) in Cambridge before joining the Galaxy Zoo team in Oxford.
-            Over the past 3 years he has been responsible for leading the development of a platform for citizen science called Zooniverse.
-            In August of 2011 he took up the position of Director of Citizen Science at the Adler Planetarium where he continues to lead the software and infrastructure development for the Zooniverse.
-          '''
+            Arfon is responsible for leading the development of the Zooniverse, a platform for online citizen science.
+            In August of 2011 he took up the position of Director of Citizen Science at the Adler Planetarium
+            where he continues to lead the software and infrastructure development for the Zooniverse.
+            He studied Chemistry at the University of Sheffield and completed his PhD in Astrochemistry at The University of Nottingham in 2006.
+          '''


Wrapped check calls in validateArgument calls.

--- server/jobqueue.coffee
+++ server/jobqueue.coffee
@@ -30,7 +30,7 @@
         isAdmin: 1

 Meteor.publish 'jobs-by-publication', (publicationId) ->
-  check publicationId, DocumentId
+  validateArgument publicationId, DocumentId, 'publicationId'

   @related (person, publication) ->
     return unless publication?.hasReadAccess person


Inverse erf.

Inverse erf.

--- ruler.coffee
+++ ruler.coffee
@@ -1,18 +1,24 @@
 # Measurements from N trials
 fig1 = figure
-    xlabel: "Trial"
-    ylabel: "Measurements"
+    xlabel: "probabilty"
+    ylabel: "inverse erf"
     height: 200
     series:
         color: "green"
         shadowSize: 0
         lines: {lineWidth: 1, show:true}
-        points: {show: true}
+        points: {show: false}

-L = 7.8 #; True length of pencil
-N = 50 #; Number of trials
-dither = rand([N]) - 0.5 #; $\in[-0.5,0.5]$
-measurements = round(L + dither) #;
-# Estimate
-estimate = measurements.sum() / N
-plot [1..N], measurements, fig: fig1
+p = linspace 0.0001, 0.9999, 1000
+
+c0 = 1.758
+c1 = -2.257
+c2 = 0.1661
+
+erfinv = (p) ->
+    l = p<=0.5
+    g = p>0.5
+    t = sqrt(-log(p*l+(1-p)*g))
+    (c0 + c1*t + c2*t*t)*(l-g)
+
+plot p, erfinv(p), fig: fig1


Fix method name detection for special function names.

--- src/coffee.coffee
+++ src/coffee.coffee
@@ -529,11 +529,16 @@
         # We will not add a socket around @variable when it
         # is only some text
         when 'Call'
-
           if node.variable?
-            if node.variable?.base?.value in BLOCK_FUNCTIONS
+            methodname = null
+            if node.variable.properties?.length > 0
+              methodname = node.variable.
+                  properties[node.variable.properties.length - 1].name?.value
+            else if node.variable.base?.value
+              methodname = node.variable.base.value
+            if methodname in BLOCK_FUNCTIONS
               @addBlock node, depth, 0, 'command', wrappingParen, MOSTLY_BLOCK
-            else if node.variable in VALUE_FUNCTIONS
+            else if methodname in VALUE_FUNCTIONS
               @addBlock node, depth, 0, 'value', wrappingParen, MOSTLY_VALUE
             else
               @addBlock node, depth, 0, 'command', wrappingParen, ANY_DROP


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color2.coffee
+++ src/color2.coffee
@@ -110,7 +110,7 @@
     a: parsedColor[3]

   Color:: =
-    equals: (other) ->
+    equal: (other) ->
       other.r == @r &&
       other.g == @g &&
       other.b == @b &&


spliced some more badboys

spliced some more badboys

--- server/src/Game.coffee
+++ server/src/Game.coffee
@@ -260,6 +260,8 @@
       @players.splice(index, 1)
       @playerSocketIds.splice(index,1)
       @state.playerScores.splice(index, 1)
+      @submittedSolutions.splice(index, 1)
+      @rightAnswers.splice(index,1)
       #now we update possiblePlayers, impossiblePlayers and readyForNextRound arrays in state
       for i in [0...@state.possiblePlayers.length]
         if @state.possiblePlayers[i] == index


Nodejs: metrics: handle non-existent req.route case in servers/datadog.coffee

--- servers/datadog.coffee
+++ servers/datadog.coffee
@@ -17,8 +17,9 @@
   @generateName : (req) ->

     path = switch
-      when req.route.path is '*'  then req.path
-      else                             req.route.path
+      when req?.route?.path is '*'  then req.path
+      when req?.route?              then req.route.path
+      else                               req.path

     @sanitizeMetricName "#{@prefix}.#{path}"

@@ -48,7 +49,7 @@
     timer = new @timer

     onHeaders res, =>
-      metricName  = @generateName(req)
+      metricName  = @generateName req
       tags        = @populateTags req, res
       elapsedTime = timer.getElapsedTimeInMilliSecs()
       @sendMetrics @metricsOnHeaders({ elapsedTime }), metricName, tags


corrected same track title search bug

corrected same track title search bug

--- coffee/_search.coffee
+++ coffee/_search.coffee
@@ -82,7 +82,11 @@
         doSearch(searchVal)

     $('#ContentWrapper').on 'click', '.track-container', ->
-      if videojs('video_player').paused() != true && videojs('video_player').currentTime() == 0 && !$(@).find('.artist').text() || $(@).find('.artist').text() != __currentTrack.artist && !$(@).find('.title').text() || $(@).find('.title').text() != __currentTrack.title
+      if videojs('video_player').paused() != true && videojs('video_player').currentTime() == 0
+        PlayTrack($(@).find('.artist').text(), $(@).find('.title').text(), $(@).find('.cover').attr('data-cover_url_medium'), $(@).find('.cover').attr('data-cover_url_large'))
+        $(@).siblings('.playing').removeClass('playing')
+        $(@).addClass('playing')
+      if $(@).find('.artist').text() != __currentTrack.artist || $(@).find('.title').text() != __currentTrack.title
         PlayTrack($(@).find('.artist').text(), $(@).find('.title').text(), $(@).find('.cover').attr('data-cover_url_medium'), $(@).find('.cover').attr('data-cover_url_large'))
         $(@).siblings('.playing').removeClass('playing')
         $(@).addClass('playing')