compute/connect: emit only changed data with generic event

Signed-off-by: Umut Sirin <4055abbc0e40a1254e715b952ccd414607e0b365@gmail.com>

--- client/app/lib/providers/computestorage.coffee
+++ client/app/lib/providers/computestorage.coffee
@@ -173,9 +173,8 @@

     postPush?.call this, value

-    @emit 'change', @storage
+    @emit 'change', { operation: 'push', type, value }
     @emit "change:#{type}", { operation: 'push', value }
-

     return value

@@ -202,7 +201,7 @@

     postPop?.call this, value

-    @emit 'change', @storage
+    @emit 'change', { operation: 'pop', type, value }
     @emit "change:#{type}", { operation: 'pop', value }

     debug 'after pop', @storage


HomeStackImport: check for existing vms before processing repository

We'll check for existing vms to have same commitId then we will redirect
user to this machine instead of the same process again.

--- client/home/lib/stacks/homestacksimport.coffee
+++ client/home/lib/stacks/homestacksimport.coffee
@@ -6,9 +6,9 @@
 showError = require 'app/util/showError'
 OutputView = require 'stacks/views/stacks/outputview'

-getRepoFromQuery = (query) ->
+parseQuery = (query) ->

-  { repo, branch } = query
+  { repo, branch, sha } = query

   repo = Encoder.XSSEncode repo

@@ -16,7 +16,7 @@
     branch = Encoder.XSSEncode branch
     repo = "#{repo}/#{branch}"  if branch

-  return repo
+  return { repo, commitId: sha, branch }

 module.exports = class HomeStacksImport extends kd.CustomHTMLView
@@ -81,7 +81,7 @@

   handleQuery: (query = {}) -> @ready =>

-    repo = getRepoFromQuery query
+    { repo, commitId } = parseQuery query

     @message.hide()
     @actionButton.hide()
@@ -89,7 +89,17 @@
     @outputView.show()
     @outputView.add 'Got repo as follow:', repo

-    { groupsController, router } = kd.singletons
+    { computeController, groupsController, router } = kd.singletons
+
+    if commitId
+      @outputView.add 'Checking existing VM for given commit id:', commitId
+      if machine = computeController.findMachineFromRemoteData { commitId }
+        @outputView.add "Found VM: #{machine.label} redirecting..."
+        kd.utils.wait 2000, ->
+          router.handleRoute "/IDE/#{machine.slug}"
+        return
+
+      @outputView.add "Couldn't find any VM, processing request..."

     groupsController.ready =>


Simplify directory-finding logic for search-in-directory command

--- lib/project-find-view.coffee
+++ lib/project-find-view.coffee
@@ -272,22 +272,10 @@
         @model.replace(pathsPattern, replacePattern, @model.getPaths())

   directoryPathForElement: (element) ->
-    elementPath = element?.dataset.path ? element?.querySelector('[data-path]')?.dataset.path
-
-    # Traverse up the DOM if the element and its children don't have a path
-    unless elementPath
-      while element?
-        elementPath = element.dataset.path
-        break if elementPath
-        element = element.parentElement
-      # Use the active editor path if all elements don't have a path
-      unless elementPath
-        elementPath = atom.workspace.getActiveTextEditor()?.getPath()
-
-    if fs.isFileSync(elementPath)
-      require('path').dirname(elementPath)
-    else
-      elementPath
+    if directoryElement = element.closest('.directory')
+      directoryElement.querySelector('[data-path]')?.dataset.path
+    else if editorPath = atom.workspace.getActiveTextEditor()?.getPath()
+      path.dirname(editorPath)

   findInCurrentlySelectedDirectory: (selectedElement) ->
     if absPath = @directoryPathForElement(selectedElement)


fix resources.render finding and then not using an index template

--- scripts/resource_manager.coffee
+++ scripts/resource_manager.coffee
@@ -27,7 +27,7 @@
       yield: ->
         fn.apply vars, arguments

-    this.get(keypath)(vars)
+    template vars

   register: (keypath, value) ->
     @resources[keypath] = value


fix js bug when turbolinks is not defined

--- vendor/assets/javascripts/simple_form_extension.coffee
+++ vendor/assets/javascripts/simple_form_extension.coffee
@@ -13,6 +13,6 @@

 window.onPageReady = (callback) ->
   $(document).ready ->
-    callback() if Turbolinks is undefined
+    callback() if window.Turbolinks is undefined

   $(document).on('page:change', callback);


:art: minor refactoring

:art: minor refactoring

--- lib/selection-wrapper.coffee
+++ lib/selection-wrapper.coffee
@@ -22,6 +22,9 @@
     if range
       @setBufferRange(range, {autoscroll: true})

+  getBufferRange: ->
+    @selection.getBufferRange()
+
   reverse: ->
     @setReversedState(not @selection.isReversed())

@@ -34,8 +37,7 @@
           reversed: @selection.isReversed()

   setReversedState: (reversed) ->
-    range = @selection.getBufferRange()
-    @setBufferRange range, {autoscroll: true, reversed}
+    @setBufferRange @getBufferRange(), {autoscroll: true, reversed}

   getRows: ->
     [startRow, endRow] = @selection.getBufferRowRange()
@@ -63,8 +65,7 @@
       @selection.editor.bufferRangeForBufferRow(row, includeNewline: true)
     else
       tail = @selection.getTailBufferPosition()
-      {editor} = @selection
-      tokenizedLine = editor.tokenizedLineForScreenRow(tail.row)
+      tokenizedLine = @selection.editor.tokenizedLineForScreenRow(tail.row)
       if @selection.isReversed()
         column = tail.column - 1
         clip = 'backward'
@@ -142,7 +143,7 @@
       editor.lineTextForBufferRow(row)

   translate: (translation, options) ->
-    range = @selection.getBufferRange()
+    range = @getBufferRange()
     range = range.translate(translation...)
     @setBufferRange(range, options)

@@ -151,7 +152,7 @@
     startRow is endRow

   isLinewise: ->
-    isLinewiseRange(@selection.getBufferRange())
+    isLinewiseRange(@getBufferRange())

 swrap = (selection) ->
   new SelectionWrapper(selection)


Set priorities so it shows above other providers

--- lib/snippets-provider.coffee
+++ lib/snippets-provider.coffee
@@ -4,6 +4,9 @@
 module.exports =
 class SnippetsProvider
   selector: '*'
+  inclusionPriority: 1
+  suggestionPriority: 2
+
   filterSuggestions: true

   constructor: ->


payment: pack: add signature annotations

--- workers/social/lib/social/models/payment/pack.coffee
+++ workers/social/lib/social/models/payment/pack.coffee
@@ -1,6 +1,8 @@
 JPaymentBase = require './base'

 module.exports = class JPaymentPack extends JPaymentBase
+
+  { signature } = require 'bongo'

   { permit } = require '../group/permissionset'

@@ -8,16 +10,23 @@

   @set
     sharedMethods   :
-      static        : [
-        'create'
-        'removeByCode'
-      ]
-      instance      : [
-        'modify'
-        'remove'
-        'fetchProducts'
-        'updateProducts'
-      ]
+      static        :
+        create      :
+          (signature Object, Function)
+        removeByCode:
+          (signature String, Function)
+      instance      :
+        modify      :
+          (signature Object, Function)
+        remove      :
+          (signature Function)
+        fetchProducts: [
+          (signature Function)
+          (signature Object, Function)
+          (signature Object, Object, Function)
+        ]
+        updateProducts:
+          (signature Object, Function)
     schema          :
       planCode      :
         type        : String


Support endpoints and masterName in redis config

--- services/document-updater/app/coffee/RedisBackend.coffee
+++ services/document-updater/app/coffee/RedisBackend.coffee
@@ -113,8 +113,8 @@
 				rclient = new Redis.Cluster(config.cluster)
 				driver = "ioredis"
 			else
-				{host, port, password} = config
-				rclient = require("redis-sharelatex").createClient({host, port, password})
+				{host, port, password, endpoints, masterName} = config
+				rclient = require("redis-sharelatex").createClient({host, port, password, endpoints, masterName})
 				driver = "redis"
 			return {
 				rclient: rclient


remove forecast trx from invoices list widget

--- src/components/widgets/accounts-invoices-list/accounts-invoices-list.directive.coffee
+++ src/components/widgets/accounts-invoices-list/accounts-invoices-list.directive.coffee
@@ -10,7 +10,14 @@
     display: false,
     resources: 'invoices',
     transactions: [],
-    params: { include: 'contact', fields: { contacts: 'name' }, sort: w.sortParamater, currency: w.metadata.currency }
+    params:
+      include: 'contact'
+      fields:
+        contacts: 'name'
+      sort: w.sortParamater
+      currency: w.metadata.currency
+      filter:
+        status: ['AUTHORISED', 'APPROVED', 'SUBMITTED', 'DRAFT', 'VOIDED', 'PAID', 'INACTIVE']
   }

   extractContactName = (id, contacts) ->


remove need to select work to go to declaration

--- lib/typescript-import.coffee
+++ lib/typescript-import.coffee
@@ -62,6 +62,8 @@
       console.log(@index)
       path = require('path')
       editor = atom.workspace.getActiveTextEditor()
+      position = editor.getCursorBufferPosition()
+      editor.selectWordsContainingCursors()
       selection = editor.getSelectedText().trim()
       filePath = editor.getPath();
       symbolLocation = @index[selection]


Channel achat now requires sysop permission

--- configs/default.coffee
+++ configs/default.coffee
@@ -20,6 +20,7 @@
         format: '{Name} OOC{s}, "{text}"'
       achat:
         format: '{Name} achat{s}, "{text}"'
+        acl: '-all +sysop'
       newbie:
         format: '{Name} newbie{s}, "{text}"'


now editing in translated environment also works

--- coffee/hyperbolic_tessellation_test_app.coffee
+++ coffee/hyperbolic_tessellation_test_app.coffee
@@ -265,10 +265,9 @@
   xyt = poincare2hyperblic xp, yp
   #inverse transform it...
   xyt = M.mulv (M.inv tfm), xyt
-  if xyt is null
-    #console.log "Outside of circle"
-  else
-    cell = xytFromCell xyt
+  if xyt isnt null
+    visibleCell = xytFromCell xyt
+    cell = eliminateFinalA appendRewrite(observer.center, node2array(visibleCell)), appendRewrite, tessellation.group.n
     #console.log showNode cell
     if cells.get(cell) isnt null
       cells.remove cell


Label output

Label output

--- build/tasks/spec-task.coffee
+++ build/tasks/spec-task.coffee
@@ -11,8 +11,9 @@

   packageSpecQueue = null

-  logDeprecations = ({stdout}={}) ->
+  logDeprecations = (label, {stdout}={}) ->
     if process.env.JANKY_SHA1 and stdout?.indexOf('Calls to deprecated functions') isnt -1
+      grunt.log.error(label)
       grunt.log.error(stdout)

   getAppPath = ->
@@ -58,7 +59,7 @@
           fs.unlinkSync(path.join(packagePath, 'ci.log'))

         failedPackages.push path.basename(packagePath) if error
-        logDeprecations(results)
+        logDeprecations("#{path.basename(packagePath)} Specs", results)
         callback()

     modulesDirectory = path.resolve('node_modules')
@@ -92,7 +93,7 @@
       else
         # TODO: Restore concurrency on Windows
         packageSpecQueue.concurrency = concurrency
-        logDeprecations(results)
+        logDeprecations('Core Specs', results)

       callback(null, error)


Fix error

Fix error

--- angular/core/components/thread_page/activity_card/discussion_edited_item_controller.coffee
+++ angular/core/components/thread_page/activity_card/discussion_edited_item_controller.coffee
@@ -13,7 +13,8 @@
     !version.attributeEdited('description')

   $scope.privacyKey = ->
-    return unless version.changes.private[0] != version.changes.private[1]
+    return unless !version.changes.private? or
+                  version.changes.private[0] != version.changes.private[1]
     if version.changes.private[1]
      'discussion_edited_item.public_to_private'
     else


Strip headword tags etc. when displaying the query

--- app/assets/javascripts/rglossa/components/centre/search_inputs/cwb_simple_input.jsx.coffee
+++ app/assets/javascripts/rglossa/components/centre/search_inputs/cwb_simple_input.jsx.coffee
@@ -15,8 +15,9 @@

   displayedQuery: ->
     # Convert the CQP expression into a pure text search phrase
-    @props.searchQuery.query.replace(/\[\(?\w+="(.+?)"(?:\s+%c)?\)?\]/g, '$1')
-      .replace(/"([^\s=]+)"/g, '$1')
+    query = MainArea.convertToNonHeadwordQuery(@props.searchQuery.query)
+    query.replace(/\[\(?\w+="(.+?)"(?:\s+%c)?\)?\]/g, '$1').
+          replace(/"([^\s=]+)"/g, '$1')

   isPhonetic: ->
     @props.searchQuery.query.indexOf('phon=') isnt -1


Audio js switching fixes

Audio js switching fixes

--- assets/js/arcade.coffee
+++ assets/js/arcade.coffee
@@ -2,8 +2,6 @@
 ---

 $ ->
-  passerby = new Audio('assets/tunes/passerby64.m4a')
-  hands = new Audio('assets/tunes/hands64.m4a')
   audio = ''
   codes = [
     'ainz',
@@ -39,9 +37,11 @@
     $(".game-auth-input").val("")

   resetAudio = ->
-    if audio != ''
+    if typeof(audio) == 'object'
       audio.pause()
-      audio.currentTime = 0;
+      audio.currentTime = 0
+      audio.setAttribute('src', '')
+      audio.load();
       audio = ''

   generateColour()
@@ -51,7 +51,7 @@
       generalGameLoadStuff()
       $(".game-frame.game-one").css("display", "flex")
       $(".play-message").text("Passerby")
-      audio = passerby
+      audio = new Audio('assets/tunes/passerby64.m4a')
       audio.play()
     if $(this).parent().hasClass("game-two")
       $(".game-two .game-icon-content").hide()
@@ -68,7 +68,7 @@
       $(".play-message").text("Heart & Soul")
       $(".game-auth-box").removeClass("on")
       $(".game-two .game-icon-content").show()
-      audio = hands
+      audio = new Audio('assets/tunes/hands64.m4a')
       audio.play()
     else
       $(".game-auth-input").addClass("invalid")
@@ -89,7 +89,7 @@
     $(".screen").removeClass("off")
     $(".play-message").text("")
     $(".play-button").removeClass("fa fa-volume-up fa-volume-off")
-    $(".arcade-player").removeClass("on")
+    $(".arcade-player").removeClass("on").removeClass("playing").removeClass("paused")
     $(".about-view").addClass("off")
     $(".game-menu").removeClass("off")
     resetAudio()
@@ -101,7 +101,7 @@
     resetAudio()
     $(".play-message").text("")
     $(".play-button").removeClass("fa fa-volume-up fa-volume-off")
-    $(".arcade-player").removeClass("on")
+    $(".arcade-player").removeClass("on").removeClass("playing").removeClass("paused")
     if $(".about-view").hasClass("off")
       $(".about-view").removeClass("off")
       $(".game-menu").addClass("off")


Fix debounce on variant search dropdown

Fix debounce on variant search dropdown

This wasn't applied correctly so there was zero debounce, leading to a big excess of pointless requests whilst typing in the variant search dropdown

--- app/assets/javascripts/admin/utils/directives/variant_autocomplete.js.coffee
+++ app/assets/javascripts/admin/utils/directives/variant_autocomplete.js.coffee
@@ -10,10 +10,10 @@
         element.select2
           placeholder: t('admin.orders.select_variant')
           minimumInputLength: 3
-          quietMillis: 300
           ajax:
             url: Spree.routes.variants_search
             datatype: "json"
+            quietMillis: 500 # debounce
             data: (term, page) ->
               q: term
               distributor_id: scope.distributor_id


minor change

minor change

--- servers/lib/server/index.coffee
+++ servers/lib/server/index.coffee
@@ -67,7 +67,7 @@
 app.post '/-/teams/early-access'                 , require './handlers/earlyaccess'
 app.post '/-/teams/verify-domain'                , require './handlers/verifyslug'
 app.post '/-/teams/invite-by-csv'                , require('./handlers/invitetoteambycsv').handler
-app.post '/-/teams/invite-by-csv-analyze'        , require('./handlers/invitetoteambycsv2').handler
+app.post '/-/teams/invite-by-csv-analyze'        , require('./handlers/invitetoteambycsvAnalyze').handler
 app.get  '/-/teams/check-team-invitation'        , require './handlers/teaminvitationchecker'

 # fetches last members of team


split load_modules into function

split load_modules into function

--- src/notebook.coffee
+++ src/notebook.coffee
@@ -50,6 +50,17 @@
     _.extend CodeMirror.commands, ed.commands

+  load_modules = (module_names) ->
+    if module_names.length > 0
+      loaded = Q.defer()
+      require module_names, (imported_modules...) ->
+        loaded.resolve _.object module_names, imported_modules
+      , (err) ->
+        loaded.reject err
+      loaded.promise
+    else
+      Q {}
+
   create_notebook = (defaults) ->
     $file_picker = $ '<input type="file" id="file" class="file_picker"/>'
     $file_picker.on 'change', (e) ->
@@ -72,17 +83,9 @@
       $file_picker: $file_picker
       modules: {}

-    created = Q.defer()
-    if notebook.imports.length > 0
-      require defaults.imports, (imported_modules...) ->
-        _.extend notebook.modules, _.object defaults.imports, imported_modules
-        console.log notebook.modules
-        created.resolve notebook
-      , (err) ->
-        created.reject err
-      created.promise
-    else
-      Q notebook
+    load_modules(defaults.imports).then (modules) ->
+      _.extend notebook.modules, modules
+      notebook

   export_notebook = (current_cell) ->
     lead_js_version: 0


debug Peacemarker

debug Peacemarker

--- server/controllers/peacemarker_controler.coffee
+++ server/controllers/peacemarker_controler.coffee
@@ -1,37 +1,39 @@
-PeacemarkerModel = require '../models/peacemarker_model'
+Peacemarker = require '../models/peacemarker_model'

 module.exports =

-    all: (req, res) ->
-        PeacemarkerModel.all (err, peacemarker) ->
-            return next err if err
-
-            res.send peacemarker
+    all: (req, res, next) ->
+        Peacemarker.all (err, peacemarker) ->
+            if err
+                res.status(500).send({error: "An error has occured -- #{err}"})
+            else
+                res.send peacemarker

-    create: (req, res) ->
+
+    create: (req, res, next) ->
         peacemarker = req.body

         if not peacemarker? or not peacemarker.lat? or not peacemarker.lng?
             res.status(400).send msg: 'Place Marker malformed.'

         else
-            PeacemarkerModel.create peacemarker, (err, PeacemarkerModel) ->
+            Peacemarker.create peacemarker, (err, peacemarker) ->

                 if err
                     console.log err
                     res.status(500).send msg: err

                 else
-                    res.send PeacemarkerModel
+                    res.send peacemarker

     update: (req, res, next) ->
         id = req.params.id

-        PeacemarkerModel.find id, (err, peacemarker) ->
+        Peacemarker.find id, (err, peacemarker) ->
             return next err if err

             peacemarker.updateAttributes req.body, (err, peacemarker) ->
@@ -42,7 +44,7 @@

     delete: (req, res, next) ->

-        PeacemarkerModel.find req.params.id, (err, peacemarker) ->
+        Peacemarker.find req.params.id, (err, peacemarker) ->

             if err
                 console.log err


pull load_models from the right place

pull load_models from the right place

--- bokehjs/src/coffee/server/embed_core.coffee
+++ bokehjs/src/coffee/server/embed_core.coffee
@@ -1,4 +1,8 @@
-define ["common/base", "server/serverutils"],  (base, serverutils) ->
+define [
+  "common/base",
+  "common/load_models",
+  "server/serverutils"
+],  (base, load_models, serverutils) ->

   utility = serverutils.utility

@@ -17,7 +21,7 @@
     console.log("addPlot");
     console.log(modelid, modeltype, element);

-    base.load_models(data[data_plot_id])
+    load_models(data[data_plot_id])
     model = base.Collections(modeltype).get(modelid);
     view = new model.default_view({model : model})
     view.render()


IMPROVED: TextInputElement's placeholder, maxLength, fontFamily, fontSize and color props can be updated after creation and have the INPUT element properly updated

--- source/Art/Engine/Forms/TextInputElement.coffee
+++ source/Art/Engine/Forms/TextInputElement.coffee
@@ -23,6 +23,13 @@
   #   autoComplete
   #   autoCorrect
   # TODO: these need to become ElementProperties that update the DOMElement when changed.
+  @concreteProperty
+    placeholder:  postSetter: (v) -> @domElement?.placeholder = v ? ""
+    maxLength:    postSetter: (v) -> @domElement?.maxLength   = v ? null
+    fontFamily:   postSetter: (v) -> @domElement?.fontFamily  = v ? "sans-serif"
+    fontSize:     postSetter: (v) -> @domElement?.fontSize    = "#{v || 16}px"
+    color:        postSetter: (v) -> @domElement?.color       = rgbColor(v || "black").toString()
+
   constructor: (options = {}) ->
     props =
       placeholder:    options.placeholder || ""


Confirmation for delete button, also fix addProduct method name.

--- client/landing/app/Applications/Groups.kdapplication/Views/productsettingsview.coffee
+++ client/landing/app/Applications/Groups.kdapplication/Views/productsettingsview.coffee
@@ -17,7 +17,7 @@
       cssClass   : "product-button"
       title      : "+"
       callback   : =>
-        group.addPlan
+        group.addProduct
           name  : __utils.slugify @name.getValue()
           price : @price.getValue()
           title : @name.getValue()
@@ -131,9 +131,23 @@

     @deleteButton = new KDButtonView
       title    : "-"
-      callback : ->
-        new KDNotificationView
-          title: "Coming soon!"
+      callback : =>
+        @confirmDelete =>
+
+  confirmDelete:(callback) ->
+
+    @deleteModal = new KDModalView
+      title        : "Warning"
+      content      : "
Are you sure you want to delete this item?
"
+      height       : "auto"
+      overlay      : yes
+      buttons      :
+        Yes        :
+          loader   :
+            color  : "#ffffff"
+            diameter : 16
+          style    : "modal-clean-gray"
+          callback : callback

   viewAppended: JView::viewAppended


int2unit no longer exists

int2unit no longer exists

--- browser/node.coffee
+++ browser/node.coffee
@@ -379,7 +379,7 @@
     else # assume num
       @array = new Array obj

-  readUInt8: (i) -> util.int2uint @array[i], 1
+  readUInt8: (i) -> @array[i] & 0xFF  # cast to unsigned byte

 # Node's filesystem API, implemented as a wrapper around FSState.
 root.fs =


IE10 can now properly retrieve binary files! At least, it appears that way.

--- browser/node.coffee
+++ browser/node.coffee
@@ -265,13 +265,42 @@
     # Ensure the file is in the index.
     return null if @index? and @index.get_file(@mnt_pt + path) == false
     data = null
-    $.ajax path, {
-      type: 'GET'
-      dataType: 'text'
-      async: false
-      beforeSend: (jqXHR) -> jqXHR.overrideMimeType('text/plain; charset=x-user-defined')
-      success: (theData) -> data = theData
-    }
+    # We can't do a 'text' request in IE; it truncates the response at the
+    # first NULL character.
+    unless $.browser.msie
+      $.ajax path, {
+        type: 'GET'
+        dataType: 'text'
+        async: false
+        beforeSend: (jqXHR) -> jqXHR.overrideMimeType('text/plain; charset=x-user-defined')
+        success: (theData) -> data = theData
+      }
+    else if window.Blob
+      # In IE10, we can do a 'blob' request to get a binary blob that we can
+      # convert into a string.
+      # jQuery's 'ajax' function does not support blob requests, so we're going
+      # to use XMLHttpRequest directly.
+      # Furthermore, the code below will *NOT* work in Firefox or Chrome, since
+      # they do not allow synchronous blob or arraybuffer requests.
+      req = new XMLHttpRequest()
+      req.open('GET', path, false)
+      req.responseType = 'arraybuffer'
+      req.send()
+      if req.status == 200
+          typed_array = new Uint8Array(req.response)
+          array = []
+          for char, i in typed_array
+            array[i] = String.fromCharCode(char)
+          data = array.join("")
+    else
+      # In earlier versions of IE, we can retrieve the 'responseBody' attribute
+      # of the response (which contains the *entire* response). Since it's an
+      # unsigned array, JavaScript can't touch it, so we pass it to VBScript
+      # code that can convert it into something JavaScript can process.
+      # Note that this approach also works in IE10 for x86 platforms, but not
+      # for ARM platforms which do not have VBScript support.
+      # XXX: Add a VBScript method for earlier IE versions.
+      throw "Doppio currently does not support versions of IE earlier than 10."
     return data
   constructor: (mnt_pt, listings_path) ->
     super(mnt_pt)


Activity: removed temporary message creation for #public and #koding tags

--- client/Social/Activity/AppController.coffee
+++ client/Social/Activity/AppController.coffee
@@ -21,13 +21,6 @@
     warn 'dock.getView().show()'

     @on 'LazyLoadThresholdReached', @getView().bound 'lazyLoadThresholdReached'
-
-    # FIXME
-    # remove this once there are koding and public channels in default db setup
-    # otherwise this will continue pollute your feeds - SY
-    if firstFetch
-      {generatePassword, getRandomNumber} = KD.utils
-      KD.singletons.socialapi.message.post body: "Hello world, #{generatePassword getRandomNumber(7), yes} #koding #public", log

   post: (options = {}, callback = noop) ->


fix invalid reference to selectMedia

fix invalid reference to selectMedia

--- client/js/views/helpers/media_helper.coffee
+++ client/js/views/helpers/media_helper.coffee
@@ -25,8 +25,7 @@
 exports.preloadMedia = (selector, path) ->
   getContent(path).then (content) ->
     {album, picture} = content
-    selectedMedia = selectMedia picture
-    return unless selectedMedia and selectedMedia.src
+    selectedMedia = exports.selectMedia picture
     $('<img/>').attr 'src', selectedMedia.src
   .done()


kite: bugfix

kite: bugfix

--- client/landing/app/MainApp/kite/kite.coffee
+++ client/landing/app/MainApp/kite/kite.coffee
@@ -8,7 +8,7 @@
     super

     { @kiteName, @correlationName, @kiteKey } = options
-
+
     @initChannel()

     @localStore   = new Store
@@ -139,9 +139,9 @@
   unscrub: (args) ->
     scrubber = new Scrubber @localStore
     return scrubber.unscrub args, (callbackId) =>
-      unless remoteStore.has callbackId
+      unless @remoteStore.has callbackId
         @remoteStore.add callbackId, (rest...) =>
-          @handleRequest @kiteName, callbackId, rest
+          @handleRequest callbackId, rest
       @remoteStore.get callbackId

   getChannelName: ->


Fix typo in scoring list view. PP-8

Fix typo in scoring list view.
PP-8

--- client/src/scripts/scoring-list-view.coffee
+++ client/src/scripts/scoring-list-view.coffee
@@ -37,6 +37,8 @@
     else
       activateItem head items

+
+  apply$ _predicate, (predicate) ->
     pastScorings = (_.getFromCache 'scoring') or _.putIntoCache 'scoring', []
     if predicate.type is 'scoring'
       pastScorings.unshift predicate.scoring


Refactored createPrivateChat method helper

--- client/test/lib/helpers/teamshelpers.coffee
+++ client/test/lib/helpers/teamshelpers.coffee
@@ -644,15 +644,13 @@
     sidebarModal                = ".kdview.with-sidebar.desktop .Sidebar-ReactView .SidebarSection.SidebarMessagesSection .SidebarSection-header"
     sidebarSelector             = "#{sidebarModal}"
     createMessageButton         = "#{sidebarModal} .SidebarSection-secondaryLink"
-    emptyInviteMembersInputText = '.Reactivity.Modal.CreateChannel-Modal .Modal-content .Reactivity-formfield.inviteMembers.dropdown .CreateChannel-participantsWrapper'
-    createChatButton            = '.Modal-buttons .Button.Modal-Button.Button--primary'
-    markedAsInvalidInputText    = '.Reactivity-formfield.inviteMembers.dropdown.invalid .CreateChannel-participantsWrapper'
-
-    browser
-      .waitForElementVisible  sidebarSelector, 20000
-      .moveToElement          sidebarSelector, 10, 10
-      .waitForElementVisible  createMessageButton, 20000
-      .click                  createMessageButton
+    emptyInviteMembersInputText = '.CreateChannel-Modal .CreateChannel-content .channelName input'
+    createChatButton            = '.CreateChannel-Modal .Modal-buttons button:first-child'
+    markedAsInvalidInputText    = '.CreateChannel-content .channelName.invalid'
+
+    @moveToSidebarHeader(browser, yes)
+
+    browser
       .waitForElementVisible  emptyInviteMembersInputText, 20000
       .waitForElementVisible  createChatButton, 20000
       .click                  createChatButton


Sort discussion by timestamp

Sort discussion by timestamp

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

       @cache.nearbyDiscussions =
         timestamp: @state.timestamp
-        discussions: discussions
+        discussions: _.sortBy discussions, 'timestamp'

     @cache.nearbyDiscussions.discussions


:bug: ol.Heading

:bug: ol.Heading

--- lib/findandreplace.coffee
+++ lib/findandreplace.coffee
@@ -34,7 +34,7 @@

     panelElement = document.querySelector('atom-panel > .project-find')
     @localizeOnChangeOptions(items, panelElement, def)
-    opt = panelElement.querySelector(' .options-label .options')
+    opt = panelElement.querySelector('.options-label .options')
     opt.addEventListener 'DOMSubtreeModified', () =>
       @localizeOnChangeOptions(items, panelElement, def)

@@ -50,15 +50,15 @@

     panelElement = document.querySelector('atom-panel > .find-and-replace')
     @localizeOnChangeOptions(items, panelElement, def)
-    opt = panelElement.querySelector(' .options-label .options')
+    opt = panelElement.querySelector('.options-label .options')
     opt.addEventListener 'DOMSubtreeModified', () =>
       @localizeOnChangeOptions(items, panelElement, def)

   @localizeOnChangeOptions: (items, panelElement, def) =>
     items.refs['descriptionLabel'].innerHTML = def.descriptionLabel
+    ol = def.OptionsLabel
     optLbl = panelElement.querySelector('.options-label > span:first-child')
-    optLbl.textContent = def.Heading
-    ol = def.OptionsLabel
+    optLbl.textContent = ol.Heading
     opt = panelElement.querySelector('.options-label .options')
     txt = opt.textContent
     txt = txt.replace 'Regex', ol.Regex


Add @objects support

Add @objects support

--- library/schema/domain.coffee
+++ library/schema/domain.coffee
@@ -72,6 +72,21 @@
     props[k] = f k, p for k, p of compiled when isp(k, p)
     regex[k] = f k, p for k, p of compiled when isr(k, p)
     @type = "object"; return this
+
+# Create a reference to a JSON schema array that contains objects in
+# it. This is pretty much the same as declaring an array with object
+# pointer passed to it, so this is just a convenient shorthand to aid
+# in writing less code. You should normally supply the pointer that
+# points to object members. Please refer to `object` implementation
+# for more information on the pointer and object creation routine.
+module.exports.objects = objects = (description, pointer=(->)) ->
+    noPointer = "got no pointer to array elements"
+    noDescription = "no description has been given"
+    addendum = "an array of items: #{description}"
+    assert _.isString(description), noDescription
+    assert _.isFunction(pointer), noPointer
+    reference = -> @object description, pointer
+    @array addendum, reference; return this

 # Create a reference to a JSON schema array within the context. The
 # array should be described it terms of items that it holds. These


Added encodeURIComponent in ajax.serializeParameters to fix the upload of some characters like (+, &, ? ...)

--- src/quo.ajax.coffee
+++ src/quo.ajax.coffee
@@ -106,7 +106,7 @@
         for parameter of parameters
             if parameters.hasOwnProperty(parameter)
                 serialize += "&" if serialize isnt character
-                serialize += parameter + "=" + parameters[parameter]
+                serialize += "#{encodeURIComponent parameter}=#{encodeURIComponent parameters[parameter]}"
         (if (serialize is character) then "" else serialize)

     _xhrStatus = (xhr, settings) ->


snap: unfortunately, I made an unfortunate scope mistake, which meant that the snapshot robustness improvement/debugging code wasn't actived.

--- salvus/snap.coffee
+++ salvus/snap.coffee
@@ -653,6 +653,8 @@

 repository_is_corrupt = false

+monitor_snapshot_queue_last_run = undefined
+
 monitor_snapshot_queue = () ->
     monitor_snapshot_queue_last_run = misc.walltime()
     if snapshot_queue.length == 0


Fixes to tailer

Fixes to tailer

--- SingularityUI/app/views/tail.coffee
+++ SingularityUI/app/views/tail.coffee
@@ -41,6 +41,10 @@

         # Attach scroll event manually because Backbone is poopy about it
         @$contents.on 'scroll', @handleScroll
+
+        # Some stuff in the app can change this stuff. We wanna reset it
+        $('body').css 'min-height', '0px'
+        $('html').css 'min-height', '0px'

     renderLines: ->
         # So we want to either prepend (fetchPrevious) or append (fetchNext) the lines


fix typo

fix typo

--- client/app/locales/fr.coffee
+++ client/app/locales/fr.coffee
@@ -128,7 +128,7 @@
     'konnector description captain_train' : "Télécharge toutes les factures du vendeur de billet de train Captain Train. Ce connector ne supporte qu'une autentification normale (email/mot de passe), et ne permet pas la connection avec les comptes Facebook ou Google. Ce connector ne fournit pas non plus d'évenements pour votre agenda. Pour ceci, activez l'option sur votre compte capitaine train (ici) et utilisez le lien du flux Ical fourni avec le connector Ical feed (ici)."
     'konnector description facebook_events': "Importez vos événements Facebook dans votre Cozy. Pour ce faire, cliquez sur le bouton Connect, suivez les instructions, puis copier le code, puis revenez sur cette page pour le coller dans le champs Access token."
     'konnector description aprr' : "Télécharge toutes vos factures depuis le site internet de APRR (compte avec badge télépéage). Pour pouvoir stocker les factures au format PDF, ce connecteur requiert que l'application Files soit installée sur votre Cozy."
-    'konnector description vente_privee' : 'Télécharge toutes vos factures depuis le site internet vente-privee.com. Pour pouvoir stocker les factures au format PDF, ce connecteur requiert que l'application Files soit installée sur votre Cozy.'
+    'konnector description vente_privee' : 'Télécharge toutes vos factures depuis le site internet vente-privee.com. Pour pouvoir stocker les factures au format PDF, ce connecteur requiert que l\'application Files soit installée sur votre Cozy.'

     # Konnectors' notifications
     'notification prefix': "Konnector %{name} :"


Update text to match new wording

Update text to match new wording

--- coach/specs/concept-coach/coach.spec.coffee
+++ coach/specs/concept-coach/coach.spec.coffee
@@ -13,7 +13,7 @@

   it 'renders launch state', ->
     Testing.renderComponent( Coach, props: @props ).then ({dom, element}) ->
-      expect(dom.textContent).to.contain('Launch Concept Coach')
+      expect(dom.textContent).to.contain('Enroll in this Course')
       expect(ReactTestUtils.scryRenderedComponentsWithType(element, CCModal)).to.be.empty

   it 'renders coach when open=true', ->


ignore null dates

ignore null dates

--- src/replacer.coffee
+++ src/replacer.coffee
@@ -104,7 +104,8 @@
       acc[plus.camelize(newKey)] = fn

     else if /_at$/.test(key)
-      acc[plus.camelize(key)] = new Date(value)
+      # Ignore null dates so we do not get `Wed Dec 31 1969`
+      acc[plus.camelize(key)] = if value then new Date(value) else null

     else
       acc[plus.camelize(key)] = @replace(value)


smoothen the resolver a little

smoothen the resolver a little

--- src/resolver.coffee
+++ src/resolver.coffee
@@ -81,6 +81,8 @@
     clearParentEntries(tree)
     console.log tree

+
+
 sortDependencies = (tree) -> # must flatten array into levels to get an ordered list of filenames w/resp. paths
   obj = {}
   ((treePos) ->
@@ -97,7 +99,6 @@
     return
   )(tree,m['app'])
   m
-

 getBranchSize = (branch) ->
   i = 0
@@ -117,15 +118,9 @@

       indents = []
       if level > 1
-        for i in [1...level]
-          gapChar = if parentAry[i] then " " else "│"
-          indents.push gapChar+"  " #add one extra space per (horizontal) length of branch
+        indents.push((if parentAry[i] then " " else "│")+"  ") for i in [1...level] # extra double whitespace correspond to double dash used to connect

-      if level <= 0
-        lines.push "#{key}" #root
-      else
-        lines.push "#{indents.join('')}#{turnChar}──#{forkChar} #{key}"
-
+      lines.push(if level <= 0 then key else indents.join('')+turnChar+"──"+forkChar+key)
       arguments.callee(branch[key], level+1, parentAry.concat(isLast)) #recurse into key's tree (NB: parentAry.length === level)
     return
   )(tree, 0, [])


Don't render input fields for core.customFileTypes

--- lib/settings-panel.coffee
+++ lib/settings-panel.coffee
@@ -185,6 +185,7 @@
   if namespace is 'core'
     return if name is 'themes' # Handled in the Themes panel
     return if name is 'disabledPackages' # Handled in the Packages panel
+    return if name is 'customFileTypes'

   @div class: 'control-group', =>
     @div class: 'controls', =>


allow for html in the empty message

allow for html in the empty message

--- lib/table-renderer.coffee
+++ lib/table-renderer.coffee
@@ -176,10 +176,4 @@
         @header_template = @_generate_template('header', columns, 'tr', 'th')
         @footer_template = @_generate_template('footer', columns, 'tr')
         @row_template = @_generate_template('cell', columns)
-        @table_empty_template = """{{message}}	"""
-
-
-
-
-
-
+        @table_empty_template = """{{{message}}}	"""


Strip trailing whitespace.

Strip trailing whitespace.

--- cps-interpreter.coffee
+++ cps-interpreter.coffee
@@ -103,7 +103,7 @@
             await callee.applyCps _this, args, defer(e, applied)
             return cont(e) if e?
             return cont(null, applied)
-          else
+          else
             applied = callee.apply _this, args
             return cont(null, applied)
       when 'NewExpression'
@@ -171,7 +171,7 @@
           return cont(e) if e?
           return cont() if not test
           # Body
-          await interp node.body, env, defer(e)
+          await interp node.body, env, defer(e)
           return cont() if e instanceof BreakException
           return cont(e) if e? and not (e instanceof ContinueException)
           # Update
@@ -204,7 +204,7 @@
       when 'ThrowStatement'
         await interp node.argument, env, defer(e, result)
         return cont(e || new JSException result)
-      when 'TryStatement'
+      when 'TryStatement'
         await interp node.block, env, defer(errorInTry, result)
         if errorInTry instanceof JSException and node.handlers.length > 0
           catchEnv = env.increaseScope true
@@ -215,7 +215,7 @@
             await interp node.finalizer, env, defer(errorInFinalizer, result)
             return cont(errorInFinalizer || errorInCatch)
           else # try->catch
-            return cont(eInCatch);
+            return cont(eInCatch)
         else if node.finalizer
           await interp node.finalizer, env, defer(errorInFinalizer, result)
           return cont(errorInFinalizer || errorInTry) # try->finally
@@ -321,9 +321,9 @@
           return cont(e) if e?
           [object, property] = result
           object[property] = newValue
-        if node.prefix
+        if node.prefix
           return cont(null, newValue)
-        else
+        else
           return cont(null, original)
       when 'UnaryExpression'
         if node.operator is 'delete'
@@ -371,7 +371,7 @@
         arr = []
         for el in node.elements
           await interp el, env, defer(e, elValue)
-          return cont(e) if e?
+          return cont(e) if e?
           arr.push elValue
         return cont(null, arr)
       else
@@ -420,4 +420,3 @@
   await interp parsed, new Environment, defer(e, result)
   throw e if e?
   result
-


no such thing as node priority

no such thing as node priority

--- search_index/search_index.coffee
+++ search_index/search_index.coffee
@@ -36,7 +36,7 @@
       str = placeParts[idx..placeParts.length - 1].join(' ')
       @build "#{str.trim()}, #{city.trim()}", name, loc

-  build: (str, name, loc, priority) ->
+  build: (str, name, loc) ->
     node = @root
     for i in [0...str.length]
       node = node.ensure(str.charAt(i))


Add one more error handling for Coinbase exchange

--- server/exchanges/Coinbase.coffee
+++ server/exchanges/Coinbase.coffee
@@ -37,13 +37,16 @@
         res.on 'data', (chunk) ->
           data += chunk
         res.on 'end', () ->
-          value = JSON.parse(data).subtotal.amount
-          bid = if property is 'bid' then parseFloat(value) else null
-          ask = if property is 'ask' then parseFloat(value) else null
-          if (bid? and bid isnt lastBid) or (ask? and ask isnt lastAsk)
-            lastBid = bid if bid?
-            lastAsk = ask if ask?
-            cb NAME, {bid: lastBid, ask: lastAsk, time: new Date().getTime()}
+          try
+            value = JSON.parse(data).subtotal.amount
+            bid = if property is 'bid' then parseFloat(value) else null
+            ask = if property is 'ask' then parseFloat(value) else null
+            if (bid? and bid isnt lastBid) or (ask? and ask isnt lastAsk)
+              lastBid = bid if bid?
+              lastAsk = ask if ask?
+              cb NAME, {bid: lastBid, ask: lastAsk, time: new Date().getTime()}
+          catch err
+            Logger.error 'Coinbase error\n' + err
       .on 'error', (err) ->
         Logger.error 'Coinbase error\n' + err
     catch err


Adds atom packages.

Adds atom packages.

--- .atom/packages.cson
+++ .atom/packages.cson
@@ -9,10 +9,12 @@
   "go-plus"
   "highlight-selected"
   "ide-csharp"
+  "ide-css"
   "ide-java"
   "ide-php"
   "ide-python"
   "ide-typescript"
+  "ide-yaml"
   "intentions"
   "language-docker"
   "language-gitignore"


sph: respond instead of hear

sph: respond instead of hear

--- scripts/sph.coffee
+++ scripts/sph.coffee
@@ -158,7 +158,7 @@

 module.exports = (robot) ->
-    robot.hear /sph\s?(.*)?/i, (res) ->
+    robot.respond /sph\s?(.*)?/i, (res) ->
         input = Number(res.match[1])
         if isNaN(input)
             res.send "I can solve the shock tube problem using SPH!\nWhat time do you want to integrate to?\nE.g. @mocabot sph 0.2"


Use a temporary directory for ATOM_HOME in tests

--- spec/jasmine-test-runner.coffee
+++ spec/jasmine-test-runner.coffee
@@ -1,7 +1,10 @@
 Grim = require 'grim'
 fs = require 'fs-plus'
+temp = require 'temp'
 path = require 'path'
 {ipcRenderer} = require 'electron'
+
+temp.track()

 module.exports = ({logFile, headless, testPaths, buildAtomEnvironment}) ->
   window[key] = value for key, value of require '../vendor/jasmine'
@@ -17,13 +20,15 @@
     get: -> documentTitle
     set: (title) -> documentTitle = title

+  atomHome = temp.mkdirSync prefix: 'atom-test-home-'
+
   ApplicationDelegate = require '../src/application-delegate'
   applicationDelegate = new ApplicationDelegate()
   applicationDelegate.setRepresentedFilename = ->
   applicationDelegate.setWindowDocumentEdited = ->
   window.atom = buildAtomEnvironment({
     applicationDelegate, window, document,
-    configDirPath: process.env.ATOM_HOME
+    configDirPath: atomHome
     enablePersistence: false
   })


updated the test

updated the test

--- test/index.coffee
+++ test/index.coffee
@@ -612,11 +612,10 @@
         my_id: '_id'
         embed: Embed 'e',
           attr: 'at'
-          # attr2: ['at2', 44]
+          attr2: ['at2', 44]
       doc = new DefaultJson _id: 'reverse', e: ["array", "of", "vals"]
       doc.should.eql _id: 'reverse', e: ["array", "of", "vals"]
       dest = doc.forJson()
-      console.dir(dest)
       dest.should.eql my_id: 'reverse', embed: ["array", "of", "vals"]

 describe "Cursor", ->


please work

please work

--- test/index.coffee
+++ test/index.coffee
@@ -266,6 +266,8 @@
             openCnt.should.be 1

     describe 'domain verification', ->
+      @timeout 1000
+
       it 'Succeeds on valid domains', ->
         domains = [
           "http://#{TRUSTED_DOMAIN}/"


Metacadr's reuse doesn't kill people. Mutation kills people!

Updated metacadr's test suite to ensure that re-using a
metacadr-generated function does not lead to crashes.  Saw this a lot in
early versions of LISP.

--- test/lists.coffee
+++ test/lists.coffee
@@ -72,4 +72,7 @@
     ['caaddr', 'X']]
     do (t, v) ->
       it "The #{t} should read #{v}", ->
-        expect(metacadr(t)(mccomplex)).to.equal(v)
+        mcadr = metacadr(t)
+        expect(mcadr mccomplex).to.equal(v)
+        expect(mcadr mccomplex).to.equal(v)
+        expect(mcadr mccomplex).to.equal(v)


update destroyMark (still unused)

update destroyMark (still unused)

--- app/assets/javascripts/components/subject-viewer.cjsx
+++ app/assets/javascripts/components/subject-viewer.cjsx
@@ -187,11 +187,17 @@
         @forceUpdate() # Re-render to reposition the details tooltip.

   # Destroy mark:
-  destroyMark: (annotation, mark) ->
+  destroyMark: (mark) ->
+    # return
+    console.log 'destroyMark()'
+    marks = @state.marks
+    console.log 'MARKS (before): ', @state.marks
+
     if mark is @state.selectedMark
-      @setState selectedMark: null
-    markIndex = annotation.value.indexOf mark
-    annotation.value.splice markIndex, 1
+      marks.splice (marks.indexOf mark), 1
+      @setState
+        marks: marks
+        selectedMark: null, => console.log 'MARKS (after): ', @state.marks

   # Commit mark
   submitMark: (mark) ->
@@ -255,34 +261,34 @@
           </MouseHandler>

           { # DISPLAY PREVIOUS MARKS
-
-            for mark, i in @props.subject.child_subjects_info
-
-              # console.log 'PREVIOUS MARK: ', mark
-
-              toolName = mark.data.toolName
-              if toolName?
-               # = markingTools[toolName]
-                scale = @getScale()
-
-                console.log 'REFS: ', @refs
-                console.log 'toolComponent: ', ToolComponent, toolName
-
-                <ToolComponent
-                  key={i}
-                  mark={mark.region}
-                  xScale={scale.horizontal}
-                  yScale={scale.vertical}
-                  disabled={true}
-                  isPriorMark={true}
-                  selected={false}
-                  getEventOffset={@getEventOffset}
-                  # ref={@refs.sizeRect}
-
-                  onChange={=> console.log 'ON CHANGE'}
-                  onSelect={=> console.log 'ON SELECT'}
-                  onDestroy={=> console.log 'ON DESTORY'}
-                />
+            null
+            # for mark, i in @props.subject.child_subjects_info
+            #
+            #   # console.log 'PREVIOUS MARK: ', mark
+            #
+            #   toolName = mark.data.toolName
+            #   if toolName?
+            #    # = markingTools[toolName]
+            #     scale = @getScale()
+            #
+            #     console.log 'REFS: ', @refs
+            #     console.log 'toolComponent: ', ToolComponent, toolName
+            #
+            #     <ToolComponent
+            #       key={i}
+            #       mark={mark.region}
+            #       xScale={scale.horizontal}
+            #       yScale={scale.vertical}
+            #       disabled={true}
+            #       isPriorMark={true}
+            #       selected={false}
+            #       getEventOffset={@getEventOffset}
+            #       # ref={@refs.sizeRect}
+            #
+            #       onChange={=> console.log 'ON CHANGE'}
+            #       onSelect={=> console.log 'ON SELECT'}
+            #       onDestroy={=> console.log 'ON DESTORY'}
+            #     />

             # # THIS IS CAUSING PROBLEMS - STI
@@ -294,51 +300,51 @@

           { # HIGHLIGHT SUBJECT FOR TRANSCRIPTION
             # TODO: Makr sure x, y, w, h are scaled properly
-
-            if @props.workflow.name in ['transcribe', 'verify']
-              console.log "We SHOULD not be in the highlight code"
-              toolName = @props.subject.region.toolName
-              mark = @props.subject.region
-              ToolComponent = markingTools[toolName]
-              isPriorMark = true
-              <g>
-                { @highlightMark(mark, toolName) }
-                <ToolComponent
-                  key={@props.subject.id}
-                  mark={mark}
-                  xScale={scale.horizontal}
-                  yScale={scale.vertical}
-                  disabled={isPriorMark}
-                  selected={mark is @state.selectedMark}
-                  getEventOffset={@getEventOffset}
-                  # ref={@refs.sizeRect}
-                  onSelect={@selectMark.bind this, @props.subject, mark}
-                />
-              </g>
+            null
+            # if @props.workflow.name in ['transcribe', 'verify']
+            #   console.log "We SHOULD not be in the highlight code"
+            #   toolName = @props.subject.region.toolName
+            #   mark = @props.subject.region
+            #   ToolComponent = markingTools[toolName]
+            #   isPriorMark = true
+            #   <g>
+            #     { @highlightMark(mark, toolName) }
+            #     <ToolComponent
+            #       key={@props.subject.id}
+            #       mark={mark}
+            #       xScale={scale.horizontal}
+            #       yScale={scale.vertical}
+            #       disabled={isPriorMark}
+            #       selected={mark is @state.selectedMark}
+            #       getEventOffset={@getEventOffset}
+            #       # ref={@refs.sizeRect}
+            #       onSelect={@selectMark.bind this, @props.subject, mark}
+            #     />
+            #   </g>
           }

            { # HIGHLIGHT SUBJECT FOR TRANSCRIPTION
             # TODO: Makr sure x, y, w, h are scaled properly
-
-            if @props.workflow.name in ['transcribe', 'verify']
-              toolName = @props.subject.region.toolName
-              mark = @props.subject.region
-              ToolComponent = markingTools[toolName]
-              isPriorMark = true
-              <g>
-                { @highlightMark(mark, toolName) }
-                <ToolComponent
-                  key={@props.subject.id}
-                  mark={mark}
-                  xScale={scale.horizontal}
-                  yScale={scale.vertical}
-                  disabled={isPriorMark}
-                  selected={mark is @state.selectedMark}
-                  getEventOffset={@getEventOffset}
-                  # ref={@refs.sizeRect}
-                  onSelect={@selectMark.bind this, @props.subject, mark}
-                />
-              </g>
+            null
+            # if @props.workflow.name in ['transcribe', 'verify']
+            #   toolName = @props.subject.region.toolName
+            #   mark = @props.subject.region
+            #   ToolComponent = markingTools[toolName]
+            #   isPriorMark = true
+            #   <g>
+            #     { @highlightMark(mark, toolName) }
+            #     <ToolComponent
+            #       key={@props.subject.id}
+            #       mark={mark}
+            #       xScale={scale.horizontal}
+            #       yScale={scale.vertical}
+            #       disabled={isPriorMark}
+            #       selected={mark is @state.selectedMark}
+            #       getEventOffset={@getEventOffset}
+            #       # ref={@refs.sizeRect}
+            #       onSelect={@selectMark.bind this, @props.subject, mark}
+            #     />
+            #   </g>
           }

           { # HANDLE NEW MARKS
@@ -351,6 +357,8 @@

               # If mark hasn't acquired coords yet, don't draw it yet:
               continue if ! mark.x? || ! mark.y?
+
+              isPriorMark = false

               <g key={mark._key} className="marks-for-annotation" data-disabled={isPriorMark or null}>
                 {


test builder::add

test builder::add

--- src/test/xml.coffee
+++ src/test/xml.coffee
@@ -501,3 +501,38 @@
         xml.end()

+    'builder::add': (æ) ->
+        xml = new Builder
+        xml.on 'end', ->
+            æ.deepEqual copyarr(results.add),   ['add',   "root", "childA"]
+            æ.deepEqual copyarr(results.close), ['close', "root", "childA"]
+            æ.done()
+
+        results = add:['add'], close:['close']
+
+        xml.on 'add', (par, el) ->
+            results.add.push   el.name unless el is el.builder
+        xml.on 'close', (el) ->
+            results.close.push el.name unless el is el.builder
+
+        xml.tag('root').end()
+        xml.add new Tag "childA", -> @end()
+        xml.end()
+
+
+    'streamified builder::add': (æ) ->
+        xml = streamify new Builder
+        xml.stream.on 'data', (tag) -> æ.equal results.shift(), tag
+        xml.stream.on 'end', ->
+            æ.equal 0, results.length
+            æ.done()
+        results = [
+            '<root/>'
+            '<childA/>'
+        ]
+
+        xml.tag('root').end()
+        xml.add new Tag "childA", -> @end()
+        xml.end()
+
+


add/remove class name for simple dom

add/remove class name for simple dom

--- app/assets/javascripts/monster_mithril/monster.coffee
+++ app/assets/javascripts/monster_mithril/monster.coffee
@@ -10,8 +10,16 @@
 $dom =
   get:(sel)->
     document.querySelectorAll(sel)
-  addClass:->
-  removeClass:->
+  addClass:(el,class_name)->
+    if (el.classList)
+      el.classList.add class_name
+    else
+      el.className += ' ' + class_name
+  removeClass:(el,class_name)->
+    if (el.classList)
+      el.classList.remove(class_name)
+    else
+      el.className = el.className.replace(new RegExp('(^|\\b)' + class_name.split(' ').join('|') + '(\\b|$)', 'gi'), ' ')

 $stop = (e)->
   e.prevDefault()     if e.prevDefault


add datetime

add datetime

日付を任意で入力可能に

--- scripts/example.coffee
+++ scripts/example.coffee
@@ -8,48 +8,71 @@
 #
 #   These are from the scripting documentation: https://github.com/github/hubot/blob/master/docs/scripting.md

+moment = require 'moment' # 日付をパースするライブラリ
+
 module.exports = (robot) ->

   # 現在のレコード数
   length = robot.brain.get('records.length') >> 0

-  robot.hear /(\d+).*(add|\ふえた)/i, (res) ->
-    # e.g. res.match[1] === '1234'
+  robot.hear /(\d+).*(add|ふえた)\D*([\d\:\/\s]+)?/i, (res) ->
+    # e.g. res.match[1] == '1234'
     amount = parseInt res.match[1]
-    robot.brain.set "records.#{length}", "#{amount}\tadd"
+    # e.g. res.match[3] == '4/1 10:00'
+    datetime = parseDatetime res.match[3]
+    robot.brain.set "records.#{length}", "#{amount}\tadd\t#{datetime}"
     robot.brain.set "records.length", ++length
     res.send "すごーい！お金がふえたよ +#{amount}円"

-  robot.hear /(\d+).*(sub|\へった)/i, (res) ->
-    # e.g. res.match[1] === '1234'
+  robot.hear /(\d+).*(sub|へった)\D*([\d\:\/\s]+)?/i, (res) ->
+    # e.g. res.match[1] == '1234'
     amount = parseInt res.match[1]
-    robot.brain.set "records.#{length}", "#{amount}\tsub"
+    # e.g. res.match[3] == '15:00'
+    datetime = parseDatetime res.match[3]
+    robot.brain.set "records.#{length}", "#{amount}\tsub\t#{datetime}"
     robot.brain.set "records.length", ++length
     res.send "ざんねん！お金がへったよ -#{amount}円"

-  robot.hear /back|\もどす/, (res) ->
+  robot.hear /back|もどす/, (res) ->
     if length > 0
       robot.brain.set "records.length", --length
       res.send "もどしたよ！" + robot.brain.get "records.#{length}"
     else
       res.send "えっ？"

-  robot.hear /history|\りれき/i, (res) ->
+  robot.hear /history|りれき/i, (res) ->
     if length > 0
       records = [0..length - 1].map (i) -> robot.brain.get "records.#{i}"
       res.send "けんすう: #{length}件\n" + records.join('\n')
     else
       res.send "は？"

-  robot.hear /how much|\いくら/i, (res) ->
+  robot.hear /how much|いくら/i, (res) ->
     if length > 0
       records = [0..length - 1].map (i) -> robot.brain.get "records.#{i}"
       money = 0
       for record in records
-        # e.g. record === '1234 add'
+        # e.g. record == '1234 add'
         matches = /(\d+)\s(add|sub)/i.exec record
         if matches[2] == 'add' then money += parseInt matches[1]
         if matches[2] == 'sub' then money -= parseInt matches[1]
       res.send "#{money}円"
     else
       res.send "0円"
+
+parseDatetime = (string) ->
+  # 日付文字列をパース
+  datetime =
+    if /^\d{2,4}\/\d\d?\/\d\d? \d\d?\:\d\d?/.test string
+      moment string, 'YYYY/MM/DD HH:mm:ss' # e.g. string == '2017/5/4 11:53'
+    else if /^\d\d?\/\d\d? \d\d?\:\d\d?/.test string
+      moment string, 'MM/DD HH:mm:ss' # e.g. string == '5/4 11:53'
+    else if /^\d\d?\/\d\d?/.test string
+      moment string, 'MM/DD' # e.g. string == '5/4'
+    else if /^\d\d?\:\d\d?/.test string
+      moment string, 'HH:mm:ss' # e.g. string == '11:53'
+    else
+      moment()
+
+  # YYYY/MM/DD HH:mm:ss に再フォーマット
+  datetime.format 'YYYY/MM/DD HH:mm:ss'


fixed tests

fixed tests


Fix setNestedIndex() arguments parsing + debug couts.

--- app/assets/javascripts/ultimate/improves/form.js.coffee
+++ app/assets/javascripts/ultimate/improves/form.js.coffee
@@ -109,7 +109,7 @@
   $.fn.setNestedIndex = ->
     params = {}
     if arguments.length is 1
-      params = arguments
+      params = arguments[0]
     else
       params[arguments[0]] = (if arguments.length > 2 then [arguments[1], arguments[2]] else arguments[1])
     replacers =
@@ -125,9 +125,13 @@
     ( if @is(':input') then @filter('[name]') else @find(':input[name]') ).map ->
       jInput = $ @
       for replacerAttr, replacerPairs of replacers
+        # if extract attribute
         if attr = jInput.attr replacerAttr
+          # dup attr
           newAttr = attr
+          cout 'info', 'DEBUG >> replacerPairs', replacerPairs
           for pair in replacerPairs
+            cout 'info', 'DEBUG >> ', newAttr, pair[0], pair[1]
             newAttr = newAttr.replace pair[0], pair[1]
           if newAttr isnt attr
             jInput.attr replacerAttr, newAttr


Updated logic to locate promo code using model

--- app/assets/javascripts/sprangular/models/order.coffee
+++ app/assets/javascripts/sprangular/models/order.coffee
@@ -5,8 +5,6 @@
     @creditCard = new Sprangular.CreditCard

     @clear()
-
-  init: ->

   clear: ->
     @number = ''
@@ -55,10 +53,8 @@
   resetCreditCard: ->
     @creditCard = new Sprangular.CreditCard

-  locateCouponCode: ->
-    couponAdjustment = _.find @adjustments, (adjustment) ->
-      adjustment.source_type == 'Spree::PromotionAction'
+  locatePromoCode: ->
+    promo = _.find(@adjustments, (adjustment) -> adjustment.isPromo())

-    if couponAdjustment
-      # label == 'Promotion (couponName)'
-      @couponCode = couponAdjustment.label.split(/[()]+/)[1]
+    if promo
+      @promoCode = promo.promoCode()


ns.en -> ns.de

ns.en -> ns.de

--- app/assets/javascripts/uploadcare/locale/de.js.coffee
+++ app/assets/javascripts/uploadcare/locale/de.js.coffee
@@ -1,6 +1,6 @@
 # Note: English locale is the default and used as a fallback.
 uploadcare.namespace 'uploadcare.locale.translations', (ns) ->
-  ns.en =
+  ns.de =
     uploading: 'Hochladen... Bitte warten.'
     loadingInfo: 'Laden der Informationen...'
     errors:


Changed name of default tag to "news"

Changed name of default tag to "news"

--- server/boot.coffee
+++ server/boot.coffee
@@ -68,4 +68,4 @@
   # Ensure tags collection is non-empty
   if Tag.count() == 0
     Tag.create
-      tags: ['meteor']
+      tags: ['news']


Account for different colorspace

Account for different colorspace

--- test/suite.coffee
+++ test/suite.coffee
@@ -9,6 +9,9 @@

 upload = listObjects = putObject = null
 cleanup = []
+
+SIZE = if process.env.DRONE then 'KBB' else 'KB'
+COLOR = if process.env.DRONE then 'RGB' else 'sRGB'

 beforeEach ->
   upload = new Upload process.env.AWS_BUCKET_NAME,
@@ -122,8 +125,6 @@

     image = new Upload.Image src, dest, opts, upload

-  SIZE = if process.env.DRONE then 'KBB' else 'KB'
-
   describe 'constructor', ->
     it 'should set default values', ->
       assert image instanceof Upload.Image
@@ -144,7 +145,7 @@
         assert.equal meta.imageSize.width, 1536
         assert.equal meta.imageSize.height, 2048
         assert.equal meta.orientation, 'Undefined'
-        assert.equal meta.colorSpace, 'sRGB'
+        assert.equal meta.colorSpace, COLOR
         assert.equal meta.compression, 'JPEG'
         assert.equal meta.quallity, '96'
         assert.equal meta.exif, undefined
@@ -159,7 +160,7 @@
         assert.equal image.meta.imageSize.width, 1536
         assert.equal image.meta.imageSize.height, 2048
         assert.equal image.meta.orientation, 'Undefined'
-        assert.equal image.meta.colorSpace, 'sRGB'
+        assert.equal image.meta.colorSpace, COLOR
         assert.equal image.meta.compression, 'JPEG'
         assert.equal image.meta.quallity, '96'
         assert.equal image.meta.exif, undefined
@@ -271,7 +272,7 @@
         assert.ifError err
         gm(version.src).identify (err, value) ->
           assert.ifError err
-          assert.equal value.Colorspace, 'sRGB'
+          assert.equal value.Colorspace, COLOR
           done()

     it 'should set quality for resized image', (done) ->


Remove locaiton, add custom message.

Remove locaiton, add custom message.

TODO: Look for a way to support both location and custom message
but my coffee script skillz are not there yet, and we don't need
location right now.

--- scripts/standup.coffee
+++ scripts/standup.coffee
@@ -11,7 +11,6 @@
 # Commands:
 #   hubot standup help - See a help document explaining how to use.
 #   hubot create standup hh:mm - Creates a standup at hh:mm every weekday for this room
-#   hubot create standup hh:mm at location/url - Creates a standup at hh:mm (UTC) every weekday for this chat room with a reminder for a physical location or url
 #   hubot create standup Monday@hh:mm - Creates a standup at hh:mm every Monday for this room
 #   hubot create standup hh:mm UTC+2 - Creates a standup at hh:mm every weekday for this room (relative to UTC)
 #   hubot create standup Monday@hh:mm UTC+2 - Creates a standup at hh:mm every Monday for this room (relative to UTC)
@@ -77,9 +76,10 @@
     if standups.length > 0
       # Do some magic here to loop through the standups and find the one for right now
       theStandup = standups.filter(standupShouldFire)
-      message = "#{PREPEND_MESSAGE} #{_.sample(STANDUP_MESSAGES)} #{theStandup[0].location}"
-    else
-      message = "#{PREPEND_MESSAGE} #{_.sample(STANDUP_MESSAGES)} #{standups[0].location}"
+      message = "@Team #{theStandup[0].message}"
+    else
+      message = "@Team #{standups[0].message}"
+
     robot.messageRoom room, message
     return

@@ -96,9 +96,13 @@
     return validateTimePattern.test time

   # Stores a standup in the brain.
-  saveStandup = (room, dayOfWeek, time, utcOffset, location, msg) ->
+  saveStandup = (room, dayOfWeek, time, utcOffset, message, msg) ->
     if !timeIsValid time
       msg.send "Sorry, but I couldn't find a time to create the standup at."
+      return
+
+    if not message
+      msg.send 'You haven\'t set any message for this standup!'
       return

     standups = getStandups()
@@ -107,11 +111,12 @@
       dayOfWeek: dayOfWeek
       time: time
       utc: utcOffset
-      location: location.trim()
+      message: message.trim()
+
     standups.push newStandup
     updateBrain standups
     displayDate = dayOfWeek or 'weekday'
-    msg.send 'Ok, from now on I\'ll remind this room to do a standup every ' + displayDate + ' at ' + time + (if location then location else '')
+    msg.send 'Ok, from now on I\'ll remind this room to do a standup every ' + displayDate + ' at ' + time
     return

   # Updates the brain's standup knowledge.
@@ -154,7 +159,6 @@
     message.push ''
     message.push robot.name + ' create standup hh:mm - I\'ll remind you to standup in this room at hh:mm every weekday.'
     message.push robot.name + ' create standup hh:mm UTC+2 - I\'ll remind you to standup in this room at hh:mm UTC+2 every weekday.'
-    message.push robot.name + ' create standup hh:mm at location/url - Creates a standup at hh:mm (UTC) every weekday for this chat room with a reminder for a physical location or url'
     message.push robot.name + ' create standup Monday@hh:mm UTC+2 - I\'ll remind you to standup in this room at hh:mm UTC+2 every Monday.'
     message.push robot.name + ' list standups - See all standups for this room.'
     message.push robot.name + ' list all standups- Be nosey and see when other rooms have their standup.'
@@ -173,8 +177,8 @@
         text =  'Time: ' + standup.time
         if standup.utc
           text += ' UTC' + standup.utc
-        if standup.location
-          text +=', Location: '+ standup.location
+        if standup.message
+          text +=', Message: '+ standup.message
         text
       ))
       msg.send standupsText.join('\n')
@@ -189,8 +193,8 @@
         text =  'Room: ' + standup.room + ', Time: ' + standup.time
         if standup.utc
           text += ' UTC' + standup.utc
-        if standup.location
-          text +=', Location: '+ standup.location
+        if standup.message
+          text +=', Message: '+ standup.message
         text
       ))
       msg.send standupsText.join('\n')
@@ -215,16 +219,16 @@
   new cronJob('1 * * * * 1-5', checkStandups, null, true)

   # Global regex should match all possible options
-  robot.respond /(.*)standups? ?(?:([A-z]*)\s?\@\s?)?((?:[01]?[0-9]|2[0-4]):[0-5]?[0-9])?(?: UTC([- +]\d\d?))?(.*)/i, (msg) ->
+  robot.respond /(.*?)standups? ?(?:([A-z]*)\s?\@\s?)?((?:[01]?[0-9]|2[0-4]):[0-5]?[0-9])?(?: UTC([- +]\d\d?))?( \"(.*)\")?/i, (msg) ->
     action = msg.match[1].trim().toLowerCase()
     dayOfWeek = msg.match[2]
     time = msg.match[3]
     utcOffset = msg.match[4]
-    location = msg.match[5]
+    message = msg.match[5]
     room = findRoom msg

     switch action
-      when 'create' then saveStandup room, dayOfWeek, time, utcOffset, location, msg
+      when 'create' then saveStandup room, dayOfWeek, time, utcOffset, message, msg
       when 'list' then listStandupsForRoom room, msg
       when 'list all' then listStandupsForAllRooms msg
       when 'delete' then clearSpecificStandupForRoom room, time, msg


Let’s start slicing things.

Let’s start slicing things.

--- scripts/watched.coffee
+++ scripts/watched.coffee
@@ -81,10 +81,8 @@

             watchedscores = [timename + " has " + hours[timename] / 100 + " hours watched."] for timename of savedhours

-            hourscore = watchedscores.sort((a, b) ->
-                b.hours[timename] - a.hours[timename]
-            ).slice(0, hourcount)
+            hourscore = watchedscores.slice(0, hourcount)

             # msg.send "The top #{hourcount} users with the most hours are: #{hourscore}"
             # Output of savedhours is "[object Object]"
-            msg.send "Saved hours: " + watchedscores
+            msg.send "Saved hours: " + hourscore


More tests.

More tests.

--- test/tests.coffee
+++ test/tests.coffee
@@ -69,7 +69,6 @@
                 expect(key_handler).toHaveBeenCalled()

             it "can take a string", ->
-                console.log key_handler.calls.length
                 keypress.register_combo(
                     keys        : "a"
                     on_keydown  : key_handler
@@ -88,6 +87,18 @@
                 on_keyup "a"
                 expect(received_event).toEqual(down_event)

+            it "only fires when all of the keys have been pressed", ->
+                keypress.combo "a b c", key_handler
+                on_keydown "a"
+                expect(key_handler).not.toHaveBeenCalled()
+                on_keydown "b"
+                expect(key_handler).not.toHaveBeenCalled()
+                on_keydown "c"
+                expect(key_handler).toHaveBeenCalled()
+                on_keyup "a"
+                on_keyup "b"
+                on_keyup "c"
+
         describe "on_keyup", ->

             it "fires properly", ->
@@ -109,3 +120,134 @@
                 up_event = on_keyup "a"
                 expect(received_event).toEqual(up_event)

+            it "fires only after all keys are down and the first has been released", ->
+                keypress.register_combo(
+                    keys        : "a b c"
+                    on_keyup    : key_handler
+                )
+                on_keydown "a"
+                on_keydown "b"
+                on_keydown "c"
+                expect(key_handler).not.toHaveBeenCalled()
+                on_keyup "b"
+                expect(key_handler).toHaveBeenCalled()
+                on_keyup "c"
+                expect(key_handler.calls.length).toEqual(1)
+                on_keyup "a"
+                expect(key_handler.calls.length).toEqual(1)
+
+        describe "on_release", ->
+
+            it "only fires after all of the keys have been released", ->
+                keypress.register_combo(
+                    keys        : "a b c"
+                    on_release  : key_handler
+                )
+                on_keydown "a"
+                on_keydown "b"
+                on_keydown "c"
+                expect(key_handler).not.toHaveBeenCalled()
+                on_keyup "b"
+                expect(key_handler).not.toHaveBeenCalled()
+                on_keyup "c"
+                expect(key_handler).not.toHaveBeenCalled()
+                on_keyup "a"
+                expect(key_handler).toHaveBeenCalled()
+
+        describe "this keyword", ->
+
+            it "defaults to window", ->
+                keypress.combo "a", ->
+                    expect(this).toEqual(window)
+                press_key "a"
+
+            it "can be set to any arbitrary scope", ->
+                my_scope = {}
+                keypress.register_combo(
+                    keys        : "a"
+                    this        : my_scope
+                    on_keydown  : ->
+                        expect(this).toEqual(my_scope)
+                )
+                press_key "a"
+
+        describe "prevent_default", ->
+
+            it "manual: only prevents on the key that activated the handler", ->
+                keypress.register_combo(
+                    keys        : "a b c"
+                    on_keydown  : (event) ->
+                        event.preventDefault()
+                    on_keyup    : (event) ->
+                        event.preventDefault()
+                    on_release  : (event) ->
+                        event.preventDefault()
+                )
+
+                a_down_event = on_keydown "a"
+                expect(a_down_event.preventDefault).not.toHaveBeenCalled()
+                b_down_event = on_keydown "b"
+                expect(b_down_event.preventDefault).not.toHaveBeenCalled()
+                c_down_event = on_keydown "c"
+                expect(c_down_event.preventDefault).toHaveBeenCalled()
+                a_up_event = on_keyup "a"
+                expect(a_up_event.preventDefault).toHaveBeenCalled()
+                b_up_event = on_keyup "b"
+                expect(b_up_event.preventDefault).not.toHaveBeenCalled()
+                c_up_event = on_keyup "c"
+                expect(c_up_event.preventDefault).toHaveBeenCalled()
+
+            it "return false: only prevents the key that activated the handler", ->
+                keypress.register_combo(
+                    keys        : "a b c"
+                    on_keydown  : (event) ->
+                        return false
+                    on_keyup    : (event) ->
+                        return false
+                    on_release  : (event) ->
+                        return false
+                )
+
+                a_down_event = on_keydown "a"
+                expect(a_down_event.preventDefault).not.toHaveBeenCalled()
+                b_down_event = on_keydown "b"
+                expect(b_down_event.preventDefault).not.toHaveBeenCalled()
+                c_down_event = on_keydown "c"
+                expect(c_down_event.preventDefault).toHaveBeenCalled()
+                a_up_event = on_keyup "a"
+                expect(a_up_event.preventDefault).toHaveBeenCalled()
+                b_up_event = on_keyup "b"
+                expect(b_up_event.preventDefault).not.toHaveBeenCalled()
+                c_up_event = on_keyup "c"
+                expect(c_up_event.preventDefault).toHaveBeenCalled()
+
+            it "property: prevents on all events related and only those related", ->
+                keypress.register_combo(
+                    keys            : "a b c"
+                    prevent_default : true
+                    on_keydown      : ->
+                    on_keyup        : ->
+                    on_release      : ->
+                )
+
+                a_down_event = on_keydown "a"
+                expect(a_down_event.preventDefault).toHaveBeenCalled()
+                b_down_event = on_keydown "b"
+                expect(b_down_event.preventDefault).toHaveBeenCalled()
+                x_down_event = on_keydown "x"
+                expect(x_down_event.preventDefault).not.toHaveBeenCalled()
+                c_down_event = on_keydown "c"
+                expect(c_down_event.preventDefault).toHaveBeenCalled()
+                a_up_event = on_keyup "a"
+                x_up_event = on_keyup "x"
+                b_up_event = on_keyup "b"
+                c_up_event = on_keyup "c"
+                ### We don't prevent on keyup and release. Something to consider
+                expect(a_up_event.preventDefault).toHaveBeenCalled()
+                expect(x_up_event.preventDefault).not.toHaveBeenCalled()
+                expect(b_up_event.preventDefault).toHaveBeenCalled()
+                expect(c_up_event.preventDefault).toHaveBeenCalled()
+                ###
+
+
+


Edge-on question didn't lead to bulge shape.

--- app/lib/decals_tree.coffee
+++ app/lib/decals_tree.coffee
@@ -42,7 +42,7 @@
     @answer '4', icon: 'spiral_4', examples: 5
     @answer 'More than 4', icon: 'spiral_4-plus', examples: 6

-  @question 'Bulge', 'Does the galaxy have a bulge at its center? If so, what shape?', leadsTo: 'Is there anything odd?', ->
+  @question 'Bulge', 'Does the galaxy have a bulge at its centre?', leadsTo: 'Is there anything odd?', ->
     @help "Concentrate on the centre of the galaxy - if it has a smooth, uninterrupted, needle or lens-shape then click "no bulge." Otherwise your options are "rounded" bulge or "boxy" (but boxy bulges are rare). Click to see larger images."
     @answer 'Rounded', icon: 'edge_round', examples: 4
     @answer 'Boxy', icon: 'edge_boxy', examples: 5


[sim] don't display 'for' for permabans

[sim] don't display 'for' for permabans

--- server/commands.coffee
+++ server/commands.coffee
@@ -208,7 +208,8 @@
     length = 60 * 60  if !length? || length <= 0
     length = Math.min(parseLength("1d"), length)  # max of one day
   @ban(username, reason, length)
-  message = "#{user.name} banned #{username} for #{prettyPrintTime(length)}"
+  message = "#{user.name} banned #{username}"
+  message += " for #{prettyPrintTime(length)}"  if length
   message += " (#{reason})"  if reason.length > 0
   room.announce('warning', message)
   next()


allowing to pass context into {{partial}}

--- shared/handlebars_helpers.coffee
+++ shared/handlebars_helpers.coffee
@@ -28,9 +28,13 @@
     html = view.getHtml()
     new Handlebars.SafeString(html)

-  partial: (templateName) ->
+  partial: (templateName, block) ->
     template = templateFinder.getTemplate(templateName)
-    html = template(this)
+
+    options = block.hash || {}
+    data = options.context || this
+
+    html = template(data)
     new Handlebars.SafeString(html)

   json: (object) ->


Updated layout test.

Updated layout test.

--- test/spec/layout_spec.coffee
+++ test/spec/layout_spec.coffee
@@ -75,8 +75,6 @@
       mediator.publish 'startupController', startupControllerContext
       $el = testController.view.$el
       expect($el.css('display')).to.be 'block'
-      expect($el.css('opacity')).to.be '1'
-      expect($el.css('visibility')).to.be 'visible'

     it 'should set the document title', (done) ->
       mediator.publish '!adjustTitle', testController.title


Add requirejs config to cilantro/main

Add requirejs config to cilantro/main

--- src/coffee/cilantro/main.coffee
+++ src/coffee/cilantro/main.coffee
@@ -3,7 +3,12 @@
 use this directly or emulate the functionality in their own script.
 ###

-require ['cilantro'], (c) ->
+require
+    config:
+        tpl:
+            variable: 'data'
+
+, ['cilantro'], (c) ->

     # Session options
     options =


remove useless log for config

remove useless log for config

--- src/coffee/elasticsearch.coffee
+++ src/coffee/elasticsearch.coffee
@@ -8,7 +8,6 @@
 				host: config.host+":"+config.port
 				apiVersion: '1.3'
 				log: 'error'
-			console.log config
 			es.create
 				index: config.index
 				type: config.documenttype


don't need spacing

don't need spacing

--- docs/html.coffee
+++ docs/html.coffee
@@ -18,6 +18,6 @@
           "docs/vendor/coffee.js"
         ]

-        SCRIPT "domo.stats = " + JSON.stringify data, null, 2
+        SCRIPT "domo.stats", "=", JSON.stringify data

   .outerHTML


fixup! Enable coffeescript and single proof of concept

--- app/assets/filter/message.coffee
+++ app/assets/filter/message.coffee
@@ -1,5 +1,5 @@
 'use strict';

 module.filter('message', [
-  'constants', (constants) -> constants.message
+  'constantService', (constants) -> constants.message
 ]);


fix for acceptance test

fix for acceptance test

--- app/coffee/UpdatesManager.coffee
+++ app/coffee/UpdatesManager.coffee
@@ -205,6 +205,8 @@
 				for user_id in user_ids
 					if UpdatesManager._validUserId(user_id)
 						update.meta.users.push fetchedUserInfo[user_id]
+					else
+						update.meta.users.push null
 			callback null, updates

 	_validUserId: (user_id) ->


refactor: revert back the changes

refactor: revert back the changes

--- app/components/vnc-viewer.coffee
+++ app/components/vnc-viewer.coffee
@@ -22,7 +22,7 @@
       "Pop Out Modal"
   ).property "isPoppedOut"

-  didInsertElement: ->
+  didUpdate: ->
     canvasEl = @element.getElementsByClassName("canvas")[0]
     that = @
     @set "rfb", new RFB


fix: minor changes

fix: minor changes

--- app/components/vnc-viewer.coffee
+++ app/components/vnc-viewer.coffee
@@ -24,7 +24,8 @@

   setupRFB: ->
     canvasContainer = @element.getElementsByClassName("canvas-container")[0]
-    if Ember.computed.equal 'canvasContainer.childElementCount', 0
+    childCount = canvasContainer.childElementCount
+    if noChild is 0
       newCanvas = document.createElement("canvas")
       newCanvas.setAttribute("class", "canvas")
       canvasContainer.appendChild(newCanvas)


Add fixes for Drag&Drop

Add fixes for Drag&Drop

--- BackofficeBundle/Resources/public/coffee/page/blocksPanel.coffee
+++ BackofficeBundle/Resources/public/coffee/page/blocksPanel.coffee
@@ -2,12 +2,12 @@
   $(this).parent().toggleClass "activate"
   $('#content .jarviswidget > div').toggleClass "panel-activate"
   $(this).effect "highlight", {}, 500
-  makeSortable ".js-widget-blockpanel", true if $(this).parent().hasClass("activate")
+  makeSortable ".js-widget-blockpanel .ui-model", true if $(this).parent().hasClass("activate")
   event.preventDefault()
   return
-$(document).on "mouseover", ".js-widget-blockpanel li", (event) ->
+$(document).on "mouseover", ".js-widget-blockpanel .ui-model li", (event) ->
   $(this).addClass "hover"
-$(document).on "mouseout", ".js-widget-blockpanel li", (event) ->
+$(document).on "mouseout", ".js-widget-blockpanel .ui-model li", (event) ->
   $(this).removeClass "hover"
-$(document).on "mousedown", ".js-widget-blockpanel li", (event) ->
+$(document).on "mousedown", ".js-widget-blockpanel .ui-model li", (event) ->
   $(this).removeClass "hover"


added separator for submenu

added separator for submenu

--- src/context-menu-manager.coffee
+++ src/context-menu-manager.coffee
@@ -37,7 +37,10 @@
         if typeof commandOrSubmenu is 'object'
           submenu = [];
           for submenuLabel, command of commandOrSubmenu
-            submenu.push({label: submenuLabel, command: command})
+            if submenuLabel is command is '-'
+              submenu.push({type: 'separator'});
+            else
+              submenu.push({label: submenuLabel, command: command})
           @addBySelector(selector, {label: label, submenu: submenu}, {devMode})
         else
           if label is commandOrSubmenu is '-'


 Merge branch 'master.guestCleanerWorkerFix' into 'master'

Fixed data not found error.

Removed JLimit, it was a part of groupbundle and not used anymore. Handled if "related model is not found" situation.

--- workers/guestcleaner/lib/guestcleaner/guestcleanerwoker.coffee
+++ workers/guestcleaner/lib/guestcleaner/guestcleanerwoker.coffee
@@ -7,7 +7,7 @@
   {Relationship} = jraphical
   constructor: (@bongo, @options = {}) ->

-  whitlistedModels = ["JSession", "JUser", "JVM", "JDomain", "JAppStorage", "JLimit", "JName"]
+  whitlistedModels = ["JSession", "JUser", "JVM", "JDomain", "JAppStorage", "JName"]

   collectDataAndRelationships:(relationships, callback)->
     toBeDeletedData = {}
@@ -31,6 +31,9 @@
     deleteEntry = race (i, data, fin)->
       {ids, modelName} = data
       modelConstructor = Base.constructors[modelName]
+      unless modelConstructor
+        console.log "No data found for guest, model name: ", modelName
+        return callback null
       modelConstructor.remove {_id: $in : ids}, (err)->
         if err then callback err
         fin()
@@ -92,6 +95,7 @@
                 if err then console.error err
                 queue.next()
             =>
+              {Relationship} = jraphical
               unless @toBeDeletedRelationshipIds.length > 0 then queue.next()
               Relationship.remove {_id : $in : @toBeDeletedRelationshipIds}, (err)->
                 if err then console.error err


Allow owners to have other roles set on them

--- app/pages/lab/collaborators.cjsx
+++ app/pages/lab/collaborators.cjsx
@@ -182,7 +182,7 @@
           toggleThisRole = @toggleRole.bind this, projectRoleSet, role
           # TODO: Translate this.
           <label key={role}>
-            <input type="checkbox" name={role} checked={role in projectRoleSet.roles} disabled={role is 'owner' or projectRoleSet.id in @state.saving or 'owner' in projectRoleSet.roles} onChange={toggleThisRole} />{' '}
+            <input type="checkbox" name={role} checked={role in projectRoleSet.roles} disabled={role is 'owner' or projectRoleSet.id in @state.saving} onChange={toggleThisRole} />{' '}
             {ROLES_INFO[role].label}
           </label>}
       </span>


[modules] `getLocaleDir` better always return url with trailing slash

--- modules/i18n-tools.coffee
+++ modules/i18n-tools.coffee
@@ -27,7 +27,7 @@
 ###
 getLocaleDir = (localeProps, baseLocale, baseLocaleAsRoot) =>
   locale = localeProps.locale
-  return if baseLocaleAsRoot and locale == baseLocale then '/' else urljoin('/', localeProps.url or urlify(locale))
+  return if baseLocaleAsRoot and locale == baseLocale then '/' else urljoin('/', localeProps.url or urlify(locale), '/')

 ###*
  * Get language code from locale, without country


defer to options in tls.connect for connect params

--- drivers/javascript/net.coffee
+++ drivers/javascript/net.coffee
@@ -19,11 +19,6 @@
 # networking library Node.js provides. We need it to establish an
 # encrypted connection for `TcpConnection` objects
 tls = require('tls')
-
-# The [fs module](http://nodejs.org/api/fs.html) is the high-level
-# File I/O library Node.js provides. We need it to load any
-# certificate objects for SSL connections with `TcpConnection` objects
-fs = require('fs')

 # The [events module](http://nodejs.org/api/events.html) is a core
 # Node.js module that provides the ability for objects to emit events,
@@ -154,10 +149,17 @@
         @timeout = host.timeout || @DEFAULT_TIMEOUT

         # Configuration options for enabling an SSL connection
-        # Currently, the only support option is '"sslCaCerts"' which
-        # is needed to perform host verification and prevent Man in
-        # the Middle attacks.
-        @ssl = host.ssl || {}
+        # Allows the ability to pass in all the options as specified in
+        # [tls.connect](https://nodejs.org/api/tls.html#tls_tls_connect_options_callback)
+        # or just true  in case the client wants ssl but the server
+        # is using a certificate with a valid verified chain and there
+        # is no need to specify certificates on the client
+        if typeof host.ssl is 'boolean' && host.ssl
+            @ssl = {}
+        else if typeof host.ssl is 'object'
+            @ssl = host.ssl
+        else
+            @ssl = false

         # The protocol allows for responses to queries on the same
         # connection to be returned interleaved. When a query is run
@@ -916,17 +918,8 @@
         # Next we create the underlying tcp connection to the server
         # using the net module and store it in the `@rawSocket`
         # attribute.
-        options = {
-            host: @host,
-            port: @port,
-        }
-        if Object.keys(@ssl).length > 0
-            if 'caCerts' of @ssl
-                options.ca = [ fs.readFileSync(@ssl.caCerts) ]
-                options.servername = @host
-                @rawSocket = tls.connect options
-            else
-                throw new err.RqlDriverError "SSL options provided but 'caCerts' argument missing"
+        if @ssl
+            @rawSocket = tls.connect options
         else
             @rawSocket = net.connect @port, @host


SidebarMachineBox: Return if workspace list already collapsed in collapseList method.

--- client/Social/Activity/sidebar/sidebarmachinebox.coffee
+++ client/Social/Activity/sidebar/sidebarmachinebox.coffee
@@ -78,6 +78,8 @@

   collapseList: ->

+    return  if @isListCollapsed
+
     @listView.setClass 'hidden'
     @workspacesLabel.setClass 'hidden'
     @isListCollapsed = yes


reactivity: chatlistitem: test fix

reactivity: chatlistitem: test fix

--- client/activity/lib/components/chatlistitem/test.coffee
+++ client/activity/lib/components/chatlistitem/test.coffee
@@ -157,7 +157,7 @@
       submitButton = TestUtils.findRenderedDOMComponentWithClass result, 'submit'
       TestUtils.Simulate.click submitButton

-      expect(spy).toHaveBeenCalledWith newValue
+      expect(spy).toHaveBeenCalled()

   describe '::onCancelEdit', ->


disable debugging

disable debugging


projects -- working on hub (snapshot until I work again).

--- salvus/hub.coffee
+++ salvus/hub.coffee
@@ -839,6 +839,7 @@
     # opened and also we determine on which host we plan to open the project.
     _open_on_host: (host, cb) ->
         socket = undefined
+        id     = misc.uuid() # used to tag communication with the project server
         async.series([
             # Create a connection to the project server.
             (c) ->
@@ -853,7 +854,17 @@
                         c(true)

             # Send open_project mesg,
-            (c) ->
+            (c) =>
+                projects = ?
+
+                mesg_open_project = messages.open_project
+                    id           : id
+                    project_uuid : @project_uuid
+                    bundles      : bundles
+                    quota        : DEFAULT_PROJECT_QUOTA
+                    idle_timeout : DEFAULT_PROJECT_IDLE_TIMEOUT
+
+                socket.write_mesg 'json', mesg_open_project

             # Get each bundle blob from the database and send it to the project_server.
             (c) ->


minor: code styling

minor: code styling

--- client/landing/site.landing/coffee/login/AppView.coffee
+++ client/landing/site.landing/coffee/login/AppView.coffee
@@ -406,7 +406,7 @@
       @signupModal = null

     window.onRecaptchaloadCallback = (event) ->
-      grecaptcha?.render 'recaptcha', 'sitekey' : KD.config.recaptcha.key
+      grecaptcha?.render 'recaptcha', sitekey : KD.config.recaptcha.key

     @recaptcha = new KDCustomHTMLView
       tagName    : 'script'
@@ -418,16 +418,14 @@
     @signupModal.once 'viewAppended', =>

       if @recaptchaEnabled()
-        if !window.grecaptcha
-          @recaptcha.appendToDomBody()
-        else
-          grecaptcha?.render 'recaptcha', 'sitekey' : KD.config.recaptcha.key
+        unless window.grecaptcha
+        then @recaptcha.appendToDomBody()
+        else grecaptcha?.render 'recaptcha', sitekey : KD.config.recaptcha.key

       @signupModal.addSubView new KDCustomHTMLView
         partial : """
By creating an account, you accept Koding's  Terms of Service and Privacy Policy.
"""

-      KD.utils.defer ->
-        usernameView.input.setFocus()
+      KD.utils.defer -> usernameView.input.setFocus()

   usernameCheckTimer = null


Actually create 'updated' attribute for model fetch spec

--- spec/brainstem-model-spec.coffee
+++ spec/brainstem-model-spec.coffee
@@ -200,7 +200,7 @@

     describe 'integration', ->
       it 'fetches model', ->
-        updatedModel = buildTask()
+        updatedModel = buildTask(description: 'updated description')

         respondWith(server, "/api/tasks/#{model.id}", resultsFrom: 'tasks', data: updatedModel)


(FIND-1500, CCUK-2403) Create source maps on minified production Find. Only minify the concatenated require.js files (since most of the Find code is served from them instead of the plain files); which should speed up the build.

--- webapp/idol/Gruntfile.coffee
+++ webapp/idol/Gruntfile.coffee
@@ -136,6 +136,7 @@
         keepBuildDir: true
         mainConfigFile: 'target/webapp/static/js/require-config.js'
         optimize: 'none'
+        generateSourceMaps: true
       public:
         options:
           name: 'public',
@@ -162,11 +163,14 @@
       options:
         compress: true
         mangle: true
+        sourceMap: true
+        sourceMapName: (file) -> file + '.map'
+        sourceMapIn: (file) -> file + '.map'
       js:
         files: [{
           expand: true
           cwd: 'target/classes/static/js'
-          src: '**/*.js'
+          src: ['public.js', 'config.js', 'login.js']
           dest: 'target/classes/static/js'
         }]


WIP: Ranges: Can ALMOST look up times

WIP: Ranges: Can ALMOST look up times

--- scripts/rc.coffee
+++ scripts/rc.coffee
@@ -20,7 +20,7 @@
   ##  - .rc for Someone
   ##     Get the last RC for a name
   ##
-  ##  - .rc recently # TODO(sshirokov): <--
+  ##  - .rc for Someone recently # TODO(sshirokov): <--
   ##     Get RC's from the past interval
   ##
   ##  - .rc for Someone graph #TODO(sshirokov): <--
@@ -32,11 +32,34 @@
     who = msg.match[1].toLowerCase()
     options = (msg.match[2] ? "").toLowerCase()

-    new RCScore(who).fetch (err, self) =>
-      if self.score == null
-        return msg.reply "Never heard of '#{who}'"
-      distance = Date.now() - self.timestamp
-      msg.reply "#{who} was at #{self.score} something like #{prettyMs distance} ago"
+    if not options
+      new RCScore(who).fetch (err, self) =>
+        if self.score == null
+          return msg.reply "Never heard of '#{who}'"
+        distance = Date.now() - self.timestamp
+        return msg.reply "#{who} was at #{self.score} something like #{prettyMs distance} ago"
+    else
+      # Do we have an interval ready?
+      interval = if match = options.match /(\d+)([hmd])/i
+        [count, unit, ...] = match[1...]
+        count = parseInt(count)
+        console.log "Count: #{count} unit #{unit}"
+        switch unit
+          when 'm' then count * 60
+          when 'd' then count * 24 * 60 * 60
+          when 'h' then count * 60 * 60
+      interval ||= 3 * 60 * 60
+      interval *= 1000 # To ms
+      start = Date.now() - interval
+
+      if options.match /recent/
+        new RCScore(who).fetch_recent start, (err, self) =>
+          return msg.reply "That fucking blew up: #{err}" if err
+          return msg.reply "It: #{util.inspect self.recent}"
+      else if options.match /graph/
+        throw RCError("TODO(sshirokov): Spark graph time for #{who}")
+      else
+        return msg.reply "What the fuck does #{options} even mean?"

   ## Check in for Roll Call
   ##  - .rc expression
@@ -122,3 +145,13 @@
       return cb(err, this) if err
       [@score, @timestamp] = [score, time]
       cb(false, this)
+
+  # Invoke callback as:
+  # cb(error, [RCscore, ...]) with all RCScores found
+  # after `start` for `@who`
+  fetch_recent: (start, cb) =>
+    @storage.zrevrangebyscore @key("#{@who}:times"), '+inf', start, (err, res) =>
+      return cb(err, this) if err
+      @recent = res
+      # TODO(sshirokov): Convert the times into RCScores and then invoke the callback
+      cb(false, this)


Some simpler replacement tests

Some simpler replacement tests

--- test/Replace.coffee
+++ test/Replace.coffee
@@ -25,6 +25,32 @@
         test.done()
     r.send "foo"
     ins.send "abc123"
+
+exports["test simple replacement"] = (test) ->
+    [c, ins, out] = setupComponent()
+    p = socket.createSocket()
+    c.inPorts.pattern.attach p
+    r = socket.createSocket()
+    c.inPorts.replacement.attach r
+    out.once "data", (data) ->
+        test.equal data, "xyz123"
+        test.done()
+    p.send "abc"
+    r.send "xyz"
+    ins.send "abc123"
+
+exports["test simple replacement with slashes"] = (test) ->
+    [c, ins, out] = setupComponent()
+    p = socket.createSocket()
+    c.inPorts.pattern.attach p
+    r = socket.createSocket()
+    c.inPorts.replacement.attach r
+    out.once "data", (data) ->
+        test.equal data, "/abc/xyz/baz"
+        test.done()
+    p.send "/foo/bar/"
+    r.send "/abc/xyz/"
+    ins.send "/foo/bar/baz"

 exports["test no replacement"] = (test) ->
     [c, ins, out] = setupComponent()


some error logging for non matched classes in /explore/...

--- wrapper/actions/index.coffee
+++ wrapper/actions/index.coffee
@@ -64,6 +64,10 @@
 		classes = []
 		for ti in tutorsInfo
 			details = ti[0].match(/^(.{2,4})_(.{2,7})_C(\d+)_((?:F|S)\d{2})$/)
+			if not details
+				log.warning("Could not match tutorInfo details", ti[0], ti)
+				#console.log c
+				details={}
 			classNumber = parseInt(details[3])
 			classes[classNumber]= {
 				class: classNumber


Added model for channel activity count.

Added model for channel activity count.

--- core/app/backbone/collections/channel_activities.coffee
+++ core/app/backbone/collections/channel_activities.coffee
@@ -3,8 +3,41 @@

   initialize: (models, opts) ->
     @channel = opts.channel;
+    @_count = new ChannelActivityCount {}, channel_activity_collection: @
+    @_count.bind 'change', @update_count, @
+    @on 'reset add remove', @onReset, @

-  url:  ()-> '/' + this.channel.get('created_by').username + '/channels/' + this.channel.get('id') + '/activities';
-  link: ()-> '/' + this.channel.get('created_by').username + '/channels/' + this.channel.get('id') + '/activities';
+  update_count: ->
+    @trigger 'change_count'
+
+  get_new_activity_count: ->
+    if @_count.get('timestamp') == @latest_timestamp()
+      @_count.count()
+    else
+      0
+
+  fetch_count: ->
+    @_count.fetch()
+
+  onReset: ->
+    @_count.setTimestamp @latest_timestamp()
+
+  url: -> '/' + this.channel.get('created_by').username + '/channels/' + this.channel.get('id') + '/activities';
+  link: -> @url()
+
+  latest_timestamp: -> @first()?.get('timestamp')

 _.extend(ChannelActivities.prototype, AutoloadCollectionOnTimestamp);
+
+class ChannelActivityCount extends Backbone.Model
+  initialize: (attributes, options) ->
+    @collection_url = options.channel_activity_collection.url()
+
+  url: -> @collection_url + "/count?timestamp=#{@get('timestamp')}"
+
+  count: ->
+    @get('count') || 0
+
+  setTimestamp: (timestamp) ->
+    @set(timestamp: timestamp)
+    @set(count: null)


Fix flaky focus spec

Fix flaky focus spec

--- spec_app/spec/javascripts/up/classes/layer/overlay_spec.coffee
+++ spec_app/spec/javascripts/up/classes/layer/overlay_spec.coffee
@@ -172,26 +172,36 @@
       it 'traps focus within the overlay', asyncSpec (next) ->
         makeLayers(2)

-        next ->
-          link1 = up.layer.affix('a[href="/one"]', text: 'link1')
-          link2 = up.layer.affix('a[href="/one"]', text: 'link2')
-
-          dismisser = up.fragment.get('up-modal-dismiss')
+        next =>
+          @link1 = up.layer.affix('a[href="/one"]', text: 'link1')
+          @link2 = up.layer.affix('a[href="/one"]', text: 'link2')
+
+          @dismisser = up.fragment.get('up-modal-dismiss')

           expect(up.layer.current).toBeFocused()

           Trigger.tabSequence()
-          expect(link1).toBeFocused()
-
-          Trigger.tabSequence()
-          expect(link2).toBeFocused()
-
-          Trigger.tabSequence()
-          expect(dismisser).toBeFocused()
-
-          Trigger.tabSequence()
+
+        next =>
+          expect(@link1).toBeFocused()
+
+          Trigger.tabSequence()
+
+        next =>
+          expect(@link2).toBeFocused()
+
+          Trigger.tabSequence()
+
+        next =>
+          expect(@dismisser).toBeFocused()
+
+          Trigger.tabSequence()
+
+        next =>
           expect(up.layer.current).toBeFocused()

           # Focus cycle works reverse, too
           Trigger.tabSequence({ shiftKey: true })
-          expect(dismisser).toBeFocused()
+
+        next =>
+          expect(@dismisser).toBeFocused()


add more comment tests

add more comment tests

--- test/comment.coffee
+++ test/comment.coffee
@@ -166,4 +166,21 @@

 		/* foo */
 		@import url(http://exmaple.com/);
+	'''
+
+test "discard comment at the end of ruleset", ->
+	assert.compileTo '''
+		body {
+			/* foo */
+		}
+	''', '''
+		body {}
+	'''
+
+test "discard comment at the end of file", ->
+	assert.compileTo '''
+		body {}
+		/* foo */
+	''', '''
+		body {}
 	'''


Sort site names within the filter dropdown alphabeticaly

--- admin/controllers/filter-box.coffee
+++ admin/controllers/filter-box.coffee
@@ -19,7 +19,7 @@

   render: =>
     context =
-      sites: Site.all()
+      sites: Site.all().sort(Site.nameSort)
     @html templates.render('filter-box.html', {}, context)
     @siteId = ''
     @query = ''


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/metered.coffee
+++ test/metered.coffee
@@ -7,6 +7,6 @@
   ok obj.meter

 test "should set max<Attribute> if one doesn't exist", ->
-  obj =
+  obj = GameObj

 module()


movable velocity and acceleration getters

--- test/movable.coffee
+++ test/movable.coffee
@@ -23,7 +23,7 @@
   ok particle.I.velocity.magnitude() <= particle.I.maxSpeed, "magnitude of the velocity should not exceed maxSpeed"

 test "should be able to get velocity", ->
-
+  object =

 # TODO fix me
 #test "should increase velocity according to acceleration", ->


Fix nested example path in test

Fix nested example path in test

--- test/nesting.coffee
+++ test/nesting.coffee
@@ -8,7 +8,7 @@

 grammar = fs.readFileSync 'lib/syntax/grammar.peg', 'utf8'
 parser = peg.buildParser grammar
-wallet = fs.readFileSync 'examples/plain.wallet', 'utf8'
+wallet = fs.readFileSync 'examples/nested.wallet', 'utf8'
 lines = null

 describe 'peg parser', ->