syncstring: automatic full patch squashing..

--- salvus/syncstring.coffee
+++ salvus/syncstring.coffee
@@ -20,6 +20,18 @@
 #
 #################################################################

+###
+Theoretical remarks.
+
+The code here provides a solution to the general problem of an eventually
+consistent synchronized string.  There are many standard choices and
+tradeoffs and conventions in choosing such a solution.   For example,
+if two writes in different locations occur at the same time (equal timestamps),
+then it's basically random which diff is actually recorded.
+... (should say more)
+###
+
+
 # The StringsDB class below models storing a distributed collection of strings, indexed by a uuid,
 # in an eventually consistent database (Cassandra).
 # It queries periodically, with exponetial backoff, for updates about strings that
@@ -38,7 +50,9 @@
 # propogate after this long may be lost.  This way we still eventually see
 # patches that were written to the database, but that we missed
 # due to them not being propogates to all data centers.
+
 TIMESTAMP_OVERLAP      = 60000
+## TIMESTAMP_OVERLAP      = 15000

 # If there are more than DB_PATCH_SQUASH_THRESH patches for a given string,
 # we squash the old patch history into a single patch the first time
@@ -316,9 +330,10 @@
         @last_sync = ''
         @live = ''
         @timestamp = 0
+        @patch_counter = 0

     sync: (cb) =>
-        @strings_db.sync(cb)   # NOTE: actually sync's all strings to db that have changed (not just this one)
+        @strings_db.sync([@string_id], cb)

 class StringsDB
     constructor : (@db) ->
@@ -434,7 +449,7 @@
     poll_for_updates: (interval=INIT_POLL_INTERVAL) =>
         retry = (interval) =>
             next_interval = Math.max(INIT_POLL_INTERVAL, Math.min(MAX_POLL_INTERVAL, POLL_DECAY_RATIO*interval))
-            @dbg("poll_for_updates", "waiting #{next_interval/1000}s...")
+            #@dbg("poll_for_updates", "waiting #{next_interval/1000}s...")
             setTimeout((()=>@poll_for_updates(next_interval)), interval)

         if misc.len(@strings) == 0
@@ -454,10 +469,10 @@
                         # nothing new -- try again after further exponential decay
                         retry(interval)

-    sync: (cb) =>
-        @_call_with_lock(@_write_updates_to_db, cb)
-
-    _write_updates_to_db: (cb) =>
+    sync: (string_ids, cb) =>
+        @_call_with_lock(((c)=>@_write_updates_to_db(string_ids,c)), cb)
+
+    _write_updates_to_db: (string_ids, cb) =>
         if not @db?
             cb("database not initialized"); return
         dbg = (m) => @dbg("_write_updates_to_db", m)
@@ -486,8 +501,14 @@
                             string.last_sync = string.live
                             string.applied_patches[timestamp] = {patch:patch, timestamp:timestamp}
                             string.timestamp = timestamp
-                            cb()
-        async.map(misc.keys(@strings), f, (err) => cb(err))
+                            string.patch_counter += 1
+                            if string.patch_counter >= DB_PATCH_SQUASH_THRESH
+                                @squash_old_patches
+                                    string_id : string_id
+                                    cb        : cb
+                            else
+                                cb()
+        async.map(string_ids, f, (err) => cb(err))

     read_updates_from_db: (opts) =>
         opts = defaults opts,
@@ -529,13 +550,9 @@
         #   patch     = string representation of a patch (since we don't want to de-JSON if not needed)
         #   is_first  = boolean; if true, start with this patch; used only to avoid race conditions when trimming history.

-        # WARNING!
-        # We first implement a very inefficient stupid version of this business, and
-        # will later implement things to make faster.  If this database sync thing
-        # turns out to be sensible.
-        #  SIMPLIFIED: ignore is_first and never trim.
-        @dbg("_process_updates", "process #{updates.length} updates")
-        #@dbg("_process_updates", "#{misc.to_json(updates)}")
+        if updates.length > 0
+            @dbg("_process_updates", "process #{updates.length} updates")
+            #@dbg("_process_updates", "#{misc.to_json(updates)}")
         new_patches = {}
         for update in updates
             update.timestamp = update.timestamp - 0  # better to key map based on string of timestamp as number
@@ -551,7 +568,7 @@
                     new_patches[update.string_id].push(update)

         if misc.len(new_patches) == 0
-            @dbg("_process_updates", "no new patches, so nothing further to do")
+            #@dbg("_process_updates", "no new patches, so nothing further to do")
             cb(undefined, false)
             return

@@ -560,7 +577,7 @@
         @dbg("_process_updates", "#{misc.len(new_patches)} new patches")

         # There are new patches
-        write_updates = false
+        write_updates = []
         for string_id, patches of new_patches
             string = @strings[string_id]
             last_sync_before = string.last_sync
@@ -621,9 +638,9 @@
                 string.applied_patches[p.timestamp] = p
                 if squash and p.timestamp <= squash_time and (i == patches.length-1 or patches[i+1].timestamp > squash_time)
                     @_squash_patches
-                        to_delete : patches.slice(0, i)
+                        to_delete : (x.timestamp for x in patches.slice(0, i))
                         string_id : string_id
-                        last_sync : string.last_sync
+                        value     : string.last_sync
                         timestamp : p.timestamp
                 i += 1

@@ -637,14 +654,14 @@
                 string.live = diffsync.dmp.patch_apply(patch, string.live)[0]
                 string.emit('change')

-            # If live != last_sync, write changes back to database
+            # If live != last_sync, write our changes back to database
             if string.live != string.last_sync
-                write_updates = true
-
-        if write_updates
+                write_updates.push(string_id)
+
+        if write_updates.length > 0
             #@dbg("_process_updates","writing our own updates back")
             # safe to call skipping lock, since we have the lock
-            @_write_updates_to_db (err) =>
+            @_write_updates_to_db write_updates, (err) =>
                 cb(err, true)
         else
             #@dbg("_process_updates","no further updates from us (stable)")
@@ -653,16 +670,16 @@

     _squash_patches: (opts) =>
         opts = defaults opts,
+            string_id : required
+            value     : required
+            timestamp : required
             to_delete : required
-            string_id : required
-            last_sync : required
-            timestamp : required
             cb        : undefined
         @dbg("_squash_patches", "string_id=#{opts.string_id}")
         async.series([
             (cb) =>
                 # write big new patch
-                patch = diffsync.dmp.patch_make('', opts.last_sync)
+                patch = diffsync.dmp.patch_make('', opts.value)
                 @db.update
                     table : 'syncstrings'
                     set   :
@@ -674,16 +691,96 @@
                     cb    : cb
             (cb) =>
                 # delete now-redundant old patches (in parallel)
-                f = (patch, cb) =>
+                f = (timestamp, cb) =>
                     @db.delete
                         table : 'syncstrings'
                         where :
                             string_id : opts.string_id
-                            timestamp : patch.timestamp
+                            timestamp : timestamp
                         cb    : cb
-                async.map opts.to_delete, f, (err) => cb(err)
-        ], (err) => opts.cb?(err))
-
+                async.map(opts.to_delete, f, (err) => cb(err))
+        ], (err) =>
+            if not err
+                string = @strings[opts.string_id]
+                if string?
+                    string.patch_counter = 1
+            opts.cb?(err)
+        )
+
+    # Squash all patches in the database older than squash_time
+    # into a single patch.  If succcessful, also resets the
+    # patch_counter for the synchronized string if @strings[string_id]
+    # is defined.
+    squash_old_patches: (opts) =>
+        opts = defaults opts,
+            string_id : required
+            cb        : undefined
+        dbg = (m) => @dbg("squash_old_patches(string_id=#{opts.string_id})", m)
+        dbg()
+
+        patches     = undefined
+        to_delete   = undefined
+        timestamp   = undefined
+        value       = undefined
+        async.series([
+            (cb) =>
+                # Get all the patches in the database for this string that
+                # are at least 1.2*TIMESTAMP_OVERLAP old, so by *hypothesis*
+                # they have all already been seen by all active clients.
+                @db.select
+                    table     : 'syncstrings'
+                    where     :
+                        string_id : opts.string_id
+                        timestamp : {'<=' : new Date() - 1.2*TIMESTAMP_OVERLAP}
+                    columns   : ['timestamp', 'patch', 'is_first']
+                    objectify : true
+                    cb        : (err, r) =>
+                        if err
+                            cb(err)
+                        else
+                            # Sort the patches we just read from oldest to newest.
+                            patches = r
+                            patches.sort(timestamp_cmp)
+                            # If we successfully squash them, then these are the timestamps
+                            # of patches that we will delete:
+                            to_delete = (x.timestamp for x in patches.slice(0,patches.length-1))
+                            # If one patch is marked as is_first, delete everything before it.
+                            for i in [0...patches.length]
+                                j = patches.length - i - 1  # go from right to left!
+                                if patches[j].is_first
+                                    patches = patches.slice(j)
+                                    break
+                            # Compute value of concatenation of all patches we're using, which
+                            # together defines the state of the string at the newest patch time.
+                            # This operation could be expensive if there were a large number
+                            # of patches, but there shouldn't be since we squash regularly.
+                            value = ''
+                            for p in patches
+                                try
+                                    value = diffsync.dmp.patch_apply(@string_to_patch(p.patch), value)[0]
+                                catch e
+                                    # This should never happen -- it would only happen if a patch were
+                                    # somehow corrupted, which should be impossible.  But it's better
+                                    # to catch and move on then destroy the syncstring entirely.
+                                    winston.debug("syncstring error applying patch -- #{misc.to_json(p.patch)} -- err=#{e}")
+                            cb()
+            (cb) =>
+                if patches.length == 0
+                    cb(); return
+                # Now do the actual squash operation and delete old patches from the database.
+                @_squash_patches
+                    string_id : opts.string_id
+                    value     : value
+                    timestamp : patches[patches.length-1].timestamp
+                    to_delete : to_delete
+                    cb        : cb
+        ], (err) =>
+            opts.cb?(err)
+            string = @strings[opts.string_id]
+            if string?
+                @dbg("reset patch_counter")
+                string.patch_counter = 0
+        )

 #---------------------------------------------------------------------


increased max width for dashboard/invitations tab handle so higher counts are also displayed

--- client/landing/app/Applications/Groups.kdapplication/Views/groupsinvitationtabview.coffee
+++ client/landing/app/Applications/Groups.kdapplication/Views/groupsinvitationtabview.coffee
@@ -2,7 +2,7 @@

   constructor:(options={}, data)->
     options.cssClass             or= 'invitations-tabs'
-    options.maxHandleWidth       or= 170
+    options.maxHandleWidth       or= 300
     options.hideHandleCloseIcons  ?= yes

     super options, data


Fix synced store remove

Fix synced store remove


Cleanup foundation require in js

Cleanup foundation require in js

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -6,22 +6,22 @@
 #
 #= require jquery
 #= require jquery_ujs
-#=require foundation/foundation
-#=require foundation/foundation.alerts
-#=require foundation/foundation.clearing
-#=require foundation/foundation.cookie
-#=require foundation/foundation.dropdown
-#=require foundation/foundation.forms
-#=require foundation/foundation.joyride
-#=require foundation/foundation.magellan
-#=require foundation/foundation.orbit
-#=require foundation/foundation.reveal
-#=require foundation/foundation.section
-#=require foundation/foundation.tooltips
-#=require foundation/foundation.topbar
-#=require foundation/foundation.interchange
-#=require foundation/foundation.placeholder
-#=require foundation/foundation.abide
+#= require foundation/foundation
+#= require foundation/foundation.alerts
+#= require foundation/foundation.clearing
+#= require foundation/foundation.cookie
+#= require foundation/foundation.dropdown
+#= require foundation/foundation.forms
+#= require foundation/foundation.joyride
+#= require foundation/foundation.magellan
+#= require foundation/foundation.orbit
+#= require foundation/foundation.reveal
+#= require foundation/foundation.section
+#= require foundation/foundation.tooltips
+#= require foundation/foundation.topbar
+#= require foundation/foundation.interchange
+#= require foundation/foundation.placeholder
+#= require foundation/foundation.abide
 #= require_tree .

 $ ->


Restringimos claramente los enlaces que borrarán cosas por AJAX.

--- app/assets/javascripts/borrar_ajax.js.coffee
+++ app/assets/javascripts/borrar_ajax.js.coffee
@@ -1,4 +1,4 @@
-$("a.destroy")
+$("a.destroy", $('#listado'))
   .live('ajax:beforeSend', ->
     $('#flash').remove()
     $('<div id="flash" />').prependTo('#content')


MO Chart: show availability in tooltips

MO Chart: show availability in tooltips

--- app/assets/javascripts/d3/merit_order.coffee
+++ app/assets/javascripts/d3/merit_order.coffee
@@ -25,10 +25,11 @@
       k = @get 'key'
       @gquery_x = new ChartSerie
         gquery_key: "merit_order_#{k}_capacity_in_merit_order_table"
-      D3.merit_order.series.push @gquery_x
       @gquery_y = new ChartSerie
         gquery_key: "merit_order_#{k}_operating_costs_in_merit_order_table"
-      D3.merit_order.series.push @gquery_y
+      @gquery_availability = new ChartSerie
+        gquery_key: "merit_order_#{k}_availability_in_merit_order_table"
+      D3.merit_order.series.push @gquery_x, @gquery_y, @gquery_availability

     value_x: => @gquery_x.safe_future_value()

@@ -36,6 +37,8 @@
       if @get('key') == 'must_run' then 0 else @original_y_value()

     original_y_value: => @gquery_y.safe_future_value()
+
+    availability_value: -> Metric.ratio_as_percentage @gquery_availability.safe_future_value()

     label: => @get('label') || @get('key')

@@ -169,7 +172,10 @@
         .attr("data-tooltip-text", (d) ->
           html = "Installed capacity: #{Metric.autoscale_value(d.value_x(), 'MW', 2)}

-                  Operating costs: #{Metric.autoscale_value d.original_y_value(), 'euro', 2}"
+                  Operating costs: #{Metric.autoscale_value d.original_y_value(), 'euro', 2}
+

+                  Availability: #{d.availability_value()}
+                  "
           if d.get('key') == 'must_run'
             html += '*
* Must run plants do not participate in merit order'
           html


stacked bar chart: better target bar width

--- app/assets/javascripts/d3/stacked_bar.coffee
+++ app/assets/javascripts/d3/stacked_bar.coffee
@@ -112,13 +112,13 @@
         .attr('class', 'target_line')
         .style('fill', (d) -> d.get 'color')
         .attr('height', 2)
-        .attr('width', 136)
+        .attr('width', @x.rangeBand() * .6)
         .attr('x', (s) =>
           column = if s.get('target_line_position') == '1' # Brrrrr
             'start_year'
           else
             'end_year'
-          @x(App.settings.get column) - 5)
+          @x(App.settings.get column) - (@x.rangeBand() * .02))
         .attr('y', 0)

     refresh: =>


change the language prefix for marked + prism

Prism’s CSS needs this language prefix

--- app/assets/javascripts/vocal/vocal.js.coffee
+++ app/assets/javascripts/vocal/vocal.js.coffee
@@ -33,6 +33,7 @@
   smartLists: false
   # smartLists: true
   smartypants: false
+  langPrefix: 'language-'
   highlight: (code, lang, callback) ->
     Prism.highlight(code, Prism.languages[lang])


Change user password model

Change user password model

--- models/user.coffee
+++ models/user.coffee
@@ -13,14 +13,9 @@
         .catch (err) ->
             return next err

-isLongEnough = (value, next) ->
-    try
-        if val.length < 7
-            throw new Error('Please choose a longer password')
-        else
-            return next()
-    catch err
-        return next err
+notNull: {
+    msg: "Valeur nulle"
+}

 # def model
 User = db.orm.define 'User',
@@ -35,6 +30,7 @@
         validate:
             len: [3, 25]
             isUnique: checkUnique
+            notNull: true
     email:
         type: Sequelize.STRING 25
         unique: true
@@ -42,17 +38,14 @@
         validate:
             len: [3, 25]
             isUnique: checkUnique
-    password_hash:
+            notNull: true
+    password:
         type: Sequelize.STRING
         allowNull: false
-    password:
-        type: Sequelize.VIRTUAL
-        allowNull: false
-        set: (val) ->
-            @setDataValue 'password', val
-            # Remember to set the data value, otherwise it won't be validated
-            @setDataValue 'password_hash', passwordHash.generate(val);
-        validate: isLongEnough
+        validate:
+            notNull: true
+            len: [6,100]
+

 # def model assocs
 User.belongsTo Team,


Remove semi-colon

Remove semi-colon

--- build/tasks/publish-build-task.coffee
+++ build/tasks/publish-build-task.coffee
@@ -22,7 +22,7 @@
     return if process.env.JANKY_SHA1 and process.env.JANKY_BRANCH isnt 'master'
     tasks = ['upload-assets']
     tasks.unshift('prepare-docs') if process.platform is 'darwin'
-    grunt.task.run(tasks);
+    grunt.task.run(tasks)

   grunt.registerTask 'prepare-docs', ['build-docs'], ->
     fs.copySync(grunt.config.get('docsOutputDir'), path.join(grunt.config.get('atom.buildDir'), 'atom-docs'))


ManagedProvider::remove: implementation

ManagedProvider::remove: implementation

--- workers/social/lib/social/models/computeproviders/managed.coffee
+++ workers/social/lib/social/models/computeproviders/managed.coffee
@@ -62,3 +62,13 @@

       JWorkspace = require '../workspace'
       JWorkspace.createDefault client, {machine, rootPath}, callback
+
+
+  @remove = (client, options, callback)->
+
+    {machineId} = options
+    provider    = 'managed'
+
+    JMachine = require './machine'
+    JMachine.one {_id: machineId, provider}, (err, machine)->
+      machine.destroy client, callback


Revert "Visual/edit modes: better (?) guessing for caret mode."

This reverts commit c1681fea2f2629c6bee1e27c5dfc704d77553d96.

--- content_scripts/mode_visual_edit.coffee
+++ content_scripts/mode_visual_edit.coffee
@@ -619,32 +619,25 @@
     @selection.modify "extend", forward, character

   # When visual mode starts and there's no existing selection, we launch CaretMode and try to establish a
-  # selection.  As a heuristic, we score visible text nodes by their visible area and the number of
-  # non-whitespace characters they contain.  We pick the first non-whitespece character in the highest scoring
-  # node.
+  # selection.  As a heuristic, we pick the first non-whitespace character of the first visible text node
+  # which seems to be big enough to be interesting.
+  # TODO(smblott).  It might be better to do something similar to Clearly or Readability; that is, try to find
+  # the start of the page's main textual content.
   establishInitialSelectionAnchor: ->
     nodes = document.createTreeWalker document.body, NodeFilter.SHOW_TEXT
-
-    # Find and score candidate text nodes.
-    candidates =
-      for node in (n while n = nodes.nextNode())
-        continue unless node.nodeType == 3 and 0 < node.data.trim().length
+    while node = nodes.nextNode()
+      # Don't choose short text nodes; they're likely to be part of a banner.
+      if node.nodeType == 3 and 50 <= node.data.trim().length
         element = node.parentElement
-        rect = DomUtils.getVisibleClientRect element
-        continue unless rect and not DomUtils.isEditable element
-        area = (rect.bottom - rect.top) * (rect.right - rect.left)
-        chars = node.data.split(/\s+/).join().length
-        { node: node, score: chars * area }
-
-    if 0 < candidates.length
-      node = (candidates.sort (a,b) -> b.score - a.score)[0].node
-      # Start at the offset of the first non-whitespace character.
-      offset = node.data.length - node.data.replace(/^\s+/, "").length
-      range = document.createRange()
-      range.setStart node, offset
-      range.setEnd node, offset
-      @setSelectionRange range
-      return range
+        if DomUtils.getVisibleClientRect(element) and not DomUtils.isEditable element
+          # Start at the offset of the first non-whitespace character.
+          offset = node.data.length - node.data.replace(/^\s+/, "").length
+          range = document.createRange()
+          range.setStart node, offset
+          range.setEnd node, offset
+          @setSelectionRange range
+          return true
+    false

 class EditMode extends Movement
   constructor: (options = {}) ->


requriring twoDigits for price and flatrate_shipping_cost

--- app/assets/javascripts/admin/product_form_validation_rules.js.coffee
+++ app/assets/javascripts/admin/product_form_validation_rules.js.coffee
@@ -7,12 +7,13 @@
                 maxlength: 500
             'product[price]':
                 required: true
-                number: true
+                decimalTwo: true
             'product[quantity]':
                 digits: true
                 required: (element) ->
                     !($ '#product_unlimited_quantity').prop('checked')
+            'product[flatrate_shipping_cost]':
+                decimalTwo: true
+                required: (element) -> element.value isnt ''

-            # flatrate_shipping number true if !empty
-            # quantity not required if unlimited is on
     $.extend(product_form_options, form_options)


removed unneeded method call

removed unneeded method call

--- app/assets/javascripts/app/reservation_time_field_adjustor.js.coffee
+++ app/assets/javascripts/app/reservation_time_field_adjustor.js.coffee
@@ -67,7 +67,6 @@
     # end time to X minutes after the start time
     endTime = @reserveStart.getDateTime().addMinutes(duration)
     @reserveEnd.setDateTime(endTime)
-    @_setDurationFields()

   _setDurationFields:  ->
     durationMinutes = @_getDuration() / 60 / 1000


 Merge branch 'master.groupFeedFix' into 'master'

Master.Group Feed Fix

* Groups fail to load activity -> https://app.asana.com/0/5526870156482/11117096706661

--- client/Social/Activity/AppView.coffee
+++ client/Social/Activity/AppView.coffee
@@ -77,7 +77,7 @@

     appendAside @referalBox       if KD.isLoggedIn() and not isPrivateGroup()
     appendAside @groupDescription if isPrivateGroup()
-    appendAside @groupMembers     if isPrivateGroup() and hasListPermissions()
+    appendAside @groupMembers     if isPrivateGroup() and canListMembers()
     appendAside @groupListBox     if isKoding()
     appendAside @topicsBox
     appendAside @usersBox         if canListMembers()


Revert "Activity: Back button callback is bound to router.back()"

This reverts commit b147e7e24eaf117a53851fd78b4bedbec8e942a2.

--- client/Social/Activity/AppView.coffee
+++ client/Social/Activity/AppView.coffee
@@ -167,7 +167,7 @@
     @warning   = new KDCustomHTMLView
     @goBack    = new KDButtonView
       cssClass : 'goback-button'
-      callback : => KD.singletons.router.back()
+      callback : => KD.singletons.router.handleRoute '/Activity'

   pistachio:->
     """


Fixed: deprecated moment functions. Refs #234

--- client/app/models/scheduleitem.coffee
+++ client/app/models/scheduleitem.coffee
@@ -72,7 +72,7 @@
         if @endDateField
             @_toDateObject @get @endDateField
         else
-            @getDateObject().add('m', 30)
+            @getDateObject().add 30, 'm'

     # Format a moment to the string format of the model.
     _formatMoment: (m) ->
@@ -171,7 +171,7 @@
             else if diff is -23
                 diff = 1

-            mDateRecurrentS.add 'hour', diff
+            mDateRecurrentS.add diff, 'hour'

             return mDateRecurrentS

@@ -184,7 +184,7 @@

                 # Compute event.end as event.start + event.duration.
                 mDateRecurrentE = mDateRecurrentS.clone()
-                    .add 'seconds', mDateEventE.diff(mDateEventS, 'seconds')
+                    .add mDateEventE.diff(mDateEventS, 'seconds'), 'seconds'
                 fce = generator @, mDateRecurrentS, mDateRecurrentE
                 return fce


update docupdater endpoint to /project/id/docs

--- services/web/app/coffee/Features/DocumentUpdater/DocumentUpdaterHandler.coffee
+++ services/web/app/coffee/Features/DocumentUpdater/DocumentUpdaterHandler.coffee
@@ -125,7 +125,7 @@

 	getProjectDocs: (project_id, callback = (error, docs) ->) ->
 		timer = new metrics.Timer("get-project-docs")
-		url = "#{settings.apis.documentupdater.url}/project/#{project_id}"
+		url = "#{settings.apis.documentupdater.url}/project/#{project_id}/doc"
 		logger.log project_id:project_id, "getting project docs from document updater"
 		request.get url, (error, res, body)->
 			timer.done()


remove unnecessary curly brackets

remove unnecessary curly brackets

--- app/classifier/index.cjsx
+++ app/classifier/index.cjsx
@@ -423,7 +423,7 @@
     @setState backButtonWarning: false

   renderBackButtonWarning: ->
-
{"Going back will delete all annotations for the current task."}

+
Going back will delete all annotations for the current task.

 module.exports = React.createClass
   displayName: 'ClassifierWrapper'


clients's password field lacked a name.

clients's password field lacked a name.

--- app/client/schemas.coffee
+++ app/client/schemas.coffee
@@ -24,6 +24,7 @@
     type:   "email"
     desc:   "E–Mail"
    ,
+    name:   "password"
     type:   "password"
     desc:   "Password"
    ,


Fix copying of constructor, mount itself and the owner property

Copying the owner property to the object it points to would be, well random...

--- app/core/Component.coffee
+++ app/core/Component.coffee
@@ -14,10 +14,13 @@
   ###
   mount: () =>
     for method of @
+      if method[0] is '_' or      # don't copy private methods
+      method == 'owner' or        # the owner property pointing to the components owner
+      method == 'constructor' or  # the constructor
+      method == 'mount'           # this method
+        continue
       if @owner[method]?
         console.debug 'Method %s exists already and will not be mounted on %O', method, @owner
         continue
-      if method[0] is '_'
-        continue
       @owner[method] = do (method) => =>
         @[method].apply @, arguments


remove list length test

remove list length test

--- test/js/totodoo.coffee
+++ test/js/totodoo.coffee
@@ -31,10 +31,6 @@
 			expect(text).to.eventually.equal 'Public list 1'
 			@timeout 4000, done()

-		it 'is the lenght of the default list items = 2', (done) ->
-			lis = @driver.findElement(js: 'return document.getElementById("todo-list")')
-			expect(lis.getElementsByTagName("li").length).to.eventually.equal 2
-			@timeout 4000, done()

 	describe 'List actions', ->


remove debugging from tests

remove debugging from tests

--- test/unit/coffee/CompileManagerTests.coffee
+++ test/unit/coffee/CompileManagerTests.coffee
@@ -71,7 +71,6 @@
 				.should.equal true

 		it "should return the output files", ->
-			console.log 'output_files', @build_files
 			@callback.calledWith(null, @build_files).should.equal true

 	describe "clearProject", ->


Update copy around license needed (#153)

--- app/locale/en.ozar.coffee
+++ app/locale/en.ozar.coffee
@@ -1653,7 +1653,7 @@
     current_hero_female: "Current Hero:"
     web_dev_language_transition: "All classes program in HTML / JavaScript for this course.  Classes that have been using Python will start with extra JavaScript intro levels to ease the transition.  Classes that are already using JavaScript will skip the intro levels."
     course_membership_required_to_play: "You'll need to join a course to play this level."
-    license_required_to_play: "Ask your teacher to assign a license to you so you can continue to play CodeCombat!"
+    license_required_to_play: "Ask your teacher to assign you a license so you can continue to play Ozaria!" # {change}
     update_old_classroom: "New school year, new levels!"
     update_old_classroom_detail: "To make sure you're getting the most up-to-date levels, make sure you create a new class for this semester by clicking Create a New Class on your"
     teacher_dashboard: "teacher dashboard"


Add some randomness to the async workers

--- test/spate_test.coffee
+++ test/spate_test.coffee
@@ -60,7 +60,7 @@
           assert.equal error, 'some error'

         "should stop the pool from continuing": (error, res) ->
-          assert.deepEqual @result, [11]
+          assert.equal @result.length, 1

     "with asynchronous worker functions":
@@ -73,7 +73,12 @@

           pool = spate.pool @workItems, options, (item, done) =>
             @inProgress += 1; @maxInProgress = Math.max(@inProgress, @maxInProgress)
-            setTimeout (=> @result.push item + 10; @inProgress -= 1; done(result...)), 1
+            worker = =>
+              @result.push item + 10
+              @inProgress -= 1
+              done(result...)
+
+            setTimeout worker, Math.ceil(Math.random() * 5)

           pool.exec @callback
           undefined
@@ -110,6 +115,6 @@
           assert.equal error, 'some error'

         "should stop the pool from continuing": (error, res) ->
-          assert.deepEqual @result, [11]
+          assert.equal @result.length, 1

 .export(module)


try debugging travis

try debugging travis

--- test/sshfs-test.coffee
+++ test/sshfs-test.coffee
@@ -41,7 +41,8 @@
         return

       'we got no error': (err, arg2) ->
-        assert.isNull err, tips
+        assert.isNull err
+
   .addBatch
     'when reading mountedpoint':
       topic: ->
@@ -49,7 +50,7 @@
         return

       'we got no error': (err, result) ->
-        assert.isNull err, tips
+        assert.isNull err

         assert.isTrue u.include result, 'config.public.coffee'
         assert.isTrue u.include result, 'sshfs-test.coffee'
@@ -82,7 +83,7 @@
         return

       'we got no error': (err, arg2) ->
-        assert.isNull err, tips
+        assert.isNull err
   .addBatch
     'when umounting a server':
       topic: ->
@@ -95,5 +96,5 @@
         return

       'we got no error': (err, arg2) ->
-        assert.isNull err, tips
+        assert.isNull err
   .export(module)


add a generic handler for error events to KDObject#constructor

--- client/Framework/core/KDObject.coffee
+++ client/Framework/core/KDObject.coffee
@@ -13,7 +13,8 @@
     @registerKDObjectInstance()

     super
-
+
+    @on 'error', (err)-> error err
     @once 'ready', => @readyState = READY

   bound: Bongo.bound


Kontrol: remove fetchRegion, we have full query string now

--- client/Main/kite/kodingkontrol.coffee
+++ client/Main/kite/kodingkontrol.coffee
@@ -54,26 +54,6 @@
   hasKite: (options = {}) ->
     { name, correlationName, region } = options
     return (kite = @getCachedKite name, correlationName)?
-
-  fetchRegion: (correlationName, region, id) ->
-    region ?= @regions[correlationName]
-    if region? or id?
-      return Promise.resolve region
-
-    new Promise (resolve, reject) =>
-      KD.remote.api.JVM.fetchVmRegion correlationName, (err, region) =>
-
-        if err
-          warn err
-          return reject err
-
-        if not region
-          # It's fallbacking to 'sj' for now but
-          region = 'sj'
-
-        @regions[correlationName] = region
-
-        resolve region

   fetchVmInfo: (correlationName) ->
     new Promise (resolve, reject) ->


KodingKontrol::fetchKite: look at the kite from cache first.

We are keeping getKite result based on the provided query for getKite. It's
trying to getting them from localStorage if possible

--- client/Main/kite/kodingkontrol.coffee
+++ client/Main/kite/kodingkontrol.coffee
@@ -37,6 +37,13 @@
     # reauthenticate with the current session token
     @authenticate @getAuthOptions()

+
+  fetchKite: Promise.promisify (args, callback)->
+
+    if (cachedKite = KiteCache.get args.query)?
+      return callback null, @createKite cachedKite, args.query
+
+    KontrolJS::fetchKite.call this, args, callback

   fetchKites: Promise.promisify (args = {}, callback) ->


Chat: Reset pane padding

Chat: Reset pane padding

--- client/ide/views/chat/chatview.coffee
+++ client/ide/views/chat/chatview.coffee
@@ -44,6 +44,13 @@
     @visible = no

     @hide()
+
+
+  show: ->
+
+    super
+
+    @chatPane?.resetPadding()

   createLoader: ->


use each vs invoke

use each vs invoke

--- client/lanes/models/Collection.coffee
+++ client/lanes/models/Collection.coffee
@@ -13,6 +13,9 @@
     pick: (keys...) ->
         _.map(@models, _.partialRight(_.pick, keys...))

+    unSaved: ->
+        @filter (pymnt) -> pymnt.isNew()
+
     sum: ->
         args = _.toArray(arguments)
         args.unshift(this.models)
@@ -35,7 +38,7 @@
         )

     clone: ->
-        new @constructor( @invoke( 'clone' ), @options )
+        new @constructor( @each( 'clone' ), @options )

     serialize: (options = {depth: 1}) ->
         depth = options.depth


Use false instead of no for consistency reasons

--- src/chaplin/views/layout.coffee
+++ src/chaplin/views/layout.coffee
@@ -115,11 +115,11 @@
       $el = $(el)
       href = $el.attr 'href'
       protocol = el.protocol
-
+
       protocolIsExternal = if protocol
         protocol not in ['http:', 'https:', 'file:']
       else
-        no
+        false

       # Ignore external URLs.
       # Technically an empty string is a valid relative URL


sort no longer alternates onclicking new heading

--- src/components/performance.cjsx
+++ src/components/performance.cjsx
@@ -20,6 +20,9 @@
     isNameSort: true

   sortClick: (event) ->
+    isActiveSort =
+      _.contains(event.target.classList, 'is-ascending') or
+      _.contains(event.target.classList, 'is-descending')
     # this is a special case for the name header data which is one level above nested data
     if not _.contains(event.target.classList, 'student-name')
       @setState({isNameSort: false})
@@ -28,6 +31,8 @@
     headers = event.target.parentNode.querySelectorAll('th')
     for header in headers
       header.classList.remove('is-ascending', 'is-descending')
+    if not isActiveSort
+      @state.sortOrder = 'is-ascending'
     event.target.classList.add(@state.sortOrder)
     @sortData(event.target.cellIndex)


pulling out date format for tutor input

pulling out date format for tutor input

--- src/components/tutor-input.cjsx
+++ src/components/tutor-input.cjsx
@@ -6,6 +6,7 @@
 {TimeStore} = require '../flux/time'
 DatePicker = require 'react-datepicker'
 TutorErrors = require './tutor-errors'
+TutorDateFormat = "MM/DD/YYYY"

 TutorInput = React.createClass
   propTypes:
@@ -60,9 +61,8 @@
         ref="input"
         type='text'
         className={classes.join(' ')}
-        value={@props.value}
         defaultValue={@props.default}
-        onChange={@onChange}
+        onBlur={@onChange}
       />

{@props.label}

       {errors}
@@ -88,7 +88,7 @@
     if (not valid)
       value = @props.min or null

-    date = value.format("MM/DD/YYYY")
+    date = value.format(TutorDateFormat)
     @props.onChange(date)
     @setState({expandCalendar: false, valid: valid, value: date})

@@ -130,7 +130,7 @@
           minDate={min}
           maxDate={max}
           onFocus={@expandCalendar}
-          dateFormat="MM/DD/YYYY"
+          dateFormat={TutorDateFormat}
           onBlur={@onBlur}
           key={@props.id}
           ref="picker"
@@ -141,7 +141,7 @@
           weekStart={0}
         />
     else if @props.disabled and value
-      displayValue = value.toString("MM/DD/YYYY")
+      displayValue = value.toString(TutorDateFormat)

       <input type='text' disabled className={classes.join(' ')} value={displayValue}/>
@@ -197,4 +197,4 @@

-module.exports = {TutorInput, TutorDateInput, TutorTextArea}
+module.exports = {TutorInput, TutorDateInput, TutorDateFormat, TutorTextArea}


facebook url set

facebook url set

--- client/views/facebook/facebook.coffee
+++ client/views/facebook/facebook.coffee
@@ -32,6 +32,7 @@
         if ShareIt.settings.sites.facebook.appId?
             template.$('.fb-share').click (e) ->
                 e.preventDefault()
+                url = Router.current().data().url;
                 FB.ui {
                     method: 'share'
                     display: 'popup'


Clean up buildModel() output handling

Clean up buildModel() output handling

--- src/ext/modules/routines.coffee
+++ src/ext/modules/routines.coffee
@@ -1153,14 +1153,21 @@

     _fork requestParseFiles, sourceKeys, destinationKey, parserType, separator, columnCount, useSingleQuotes, columnNames, columnTypes, deleteOnDone, checkHeader, chunkSize

-  buildModel = (algo, opts) ->
-    if algo and opts and keys(opts).length > 1
-      renderable _.requestModelBuild, algo, opts, (result, go) ->
+  requestModelBuild = (algo, opts, go) ->
+    _.requestModelBuild algo, opts, (error, result) ->
+      if error
+        go error
+      else
         if result.validation_error_count > 0
           messages = (validation.message for validation in result.validation_messages)
           go new Flow.Error "Model build failure: #{messages.join '; '}"
         else
-          go null, H2O.JobOutput _, head result.jobs
+          go null, extendJob head result.jobs
+
+
+  buildModel = (algo, opts) ->
+    if algo and opts and keys(opts).length > 1
+      _fork requestModelBuild, algo, opts
     else
       assist buildModel, algo, opts


Fix unhandled event formatter

Fix unhandled event formatter

--- src/formatters/unhandled.coffee
+++ src/formatters/unhandled.coffee
@@ -1,2 +1,2 @@
 module.exports = (data) ->
-  "GitHub sent an unknown event:\n#{JSON.stringify(event.data, null, 2)}"
+  "GitHub sent an unknown event:\n#{JSON.stringify(data, null, 2)}"


bumping version to 2.16.4

bumping version to 2.16.4


Improve minimal tests for slider

Improve minimal tests for slider

--- components/__tests__/Slider.spec.cjsx
+++ components/__tests__/Slider.spec.cjsx
@@ -1,20 +1,21 @@
-React       = require('react/addons')
+TestUtils   = React.addons.TestUtils
 expect      = require('expect')
 utils       = require('./utils')
-
-TestUtils   = React.addons.TestUtils
 Slider      = require('../slider')

 describe 'Slider', ->
-  before ->
-    @component = TestUtils.renderIntoDocument(<Slider />)
-    @component.setState({ sliderStart:  0, sliderLength: 1000 })
+  describe '#events', ->
+    slider = null

-  describe '#events', ->
+    before ->
+      props = { min: -500, max: 500 }
+      state = { sliderStart:  0, sliderLength: 1000 }
+      slider = utils.renderComponent(Slider, props, state)
+
     it "sets pressed state when knob is clicked", ->
-      TestUtils.Simulate.mouseDown(@component.refs.knob)
-      expect(@component.state.pressed).toEqual(true)
+      TestUtils.Simulate.mouseDown(slider.refs.knob)
+      expect(slider.state.pressed).toEqual(true)

     it "sets a proper value when the slider is clicked", ->
-      TestUtils.Simulate.mouseDown(@component.refs.slider, { pageX: 800 })
-      expect(@component.state.value).toEqual(80)
+      TestUtils.Simulate.mouseDown(slider.refs.slider, { pageX: 200 })
+      expect(slider.state.value).toEqual(-300)


Add comments.

Add comments.

--- src/js/collection-editor.coffee
+++ src/js/collection-editor.coffee
@@ -13,6 +13,7 @@
 google_oauth_url = ->
   "https://accounts.google.com/o/oauth2/auth?#{$.param(google_oauth_parameters_for_fusion_tables)}"

+# construct a CITE URN with optional version
 cite_urn = (namespace, collection, row, version) ->
   urn = "urn:cite:#{namespace}:#{collection}.#{row}"
   if arguments.length == 4
@@ -103,12 +104,15 @@
           if callback?
             callback(data)

+# getter for form inputs because we need a special case for markdown
 get_value_for_form_input = (element) ->
   if $(element).attr('class') == 'pagedown_container'
     $(element).find('.wmd-input').val()
   else
     $(element).val()

+# construct the latest URN - because this is asynchronous, it
+# takes a callback function which gets passed the resulting URN
 construct_latest_urn = (callback) ->
   collection = $('#collection_select').val()
   fusion_tables_query "SELECT ROWID FROM #{collection}", (data) =>
@@ -121,6 +125,7 @@
 scroll_to_bottom = ->
   $('html, body').animate({scrollTop: $(document).height()-$(window).height()},600,'linear')

+# save collection form values to localStorage
 save_collection_form = ->
   collection = $('#collection_select').val()
   localStorage[collection] = true
@@ -136,11 +141,14 @@
 fusion_tables_escape = (value) ->
   "'#{value.replace(/'/g,"\\\'")}'"

+# submit the form to Fusion Tables
 submit_collection_form = ->
   disable_collection_form()
   collection = $('#collection_select').val()
   column_names = []
   row_values = []
+  # the main body of the submission is in an anonymous callback function that gets the URN,
+  # this is so we hopefully have the latest URN possible
   construct_latest_urn (urn) =>
     $('#URN').attr('value',urn)
     update_timestamp_inputs()
@@ -155,6 +163,7 @@
       $('#submit_success').delay(1800).fadeOut 1800, ->
         $(this).remove()

+# populate collection form values from hash parameters or localStorage if set
 load_collection_form = ->
   collection = $('#collection_select').val()
   for child in $('#collection_form').children()
@@ -168,6 +177,7 @@
         else
           $(child).val(localStorage["#{collection}:#{$(child).attr('id')}"])

+# remove form values from localStorage and reset the form
 clear_collection_form = ->
   collection = $('#collection_select').val()
   localStorage.removeItem(collection)
@@ -176,6 +186,7 @@
       localStorage.removeItem("#{collection}:#{$(child).attr('id')}")
   $('#collection_select').change()

+# top-level function for building the collection form
 build_collection_form = (collection) ->
   form = $('<form>').attr('id','collection_form')

@@ -217,17 +228,20 @@

   $('.container').append form

+  # update various inputs after we've actually put the form in the DOM
   set_author_name()
   construct_latest_urn (urn) ->
     $('#URN').attr('value',urn)
   update_timestamp_inputs()

+  # create the Markdown/Pagedown preview after we've put the form in the DOM
   converter = new Markdown.Converter()
   for suffix in $(".pagedown_suffix")
     console.log "Running Markdown editor for: #{$(suffix).val()}"
     editor = new Markdown.Editor(converter,"-#{$(suffix).val()}")
     editor.run()

+  # if we have Flash, put clippy helpers on all the inputs
   if swfobject.hasFlashPlayerVersion('9')
     for property in properties
       if $(property).attr('type') == 'markdown'
@@ -235,6 +249,7 @@
       else
         clippy $(property).attr('name')

+# set the author name using Google profile information
 set_author_name = ->
   if get_cookie 'author_name'
     $('#Author').attr('value',get_cookie 'author_name')
@@ -250,6 +265,7 @@
         set_cookie('author_name',data['name'],3600)
         $('#Author').attr('value',data['name'])

+# parse URL hash parameters into an associative array object
 parse_query_string = (query_string) ->
   query_string ?= location.hash.substring(1)
   params = {}
@@ -259,6 +275,8 @@
       params[decodeURIComponent(m[1])] = decodeURIComponent(m[2])
   return params

+# filter URL parameters out of the window URL using replaceState
+# returns the original parameters
 filter_url_params = (params, filtered_params) ->
   rewritten_params = []
   filtered_params ?= ['access_token','expires_in','token_type']
@@ -287,6 +305,7 @@
     return cookie_fragment.substring(key.length, cookie_fragment.length) if cookie_fragment.indexOf(key) == 0
   return null

+# write a Google OAuth access token into a cached cookie that should expire when the access token does
 set_access_token_cookie = (params) ->
   if params['access_token']?
     # validate the token per https://developers.google.com/accounts/docs/OAuth2UserAgent#validatetoken
@@ -300,6 +319,7 @@
         set_cookie('access_token',params['access_token'],params['expires_in'])
         $('#collection_select').change()

+# construct a clippy element to replace the given placeholder id
 clippy = (id) ->
   console.log "Clippy: #{id}"
   flashvars =
@@ -313,11 +333,13 @@
     style: "padding-left:5px;padding-top:5px;background-position:5px 5px;background-repeat:no-repeat;background-image:url('vendor/clippy/button_up.png')"
   swfobject.embedSWF("vendor/clippy/clippy.swf", "#{id}-clippy", "110", "14", "9", false, flashvars, flashparams, objectattrs)

+# use hash parameters to set the selected collection
 set_selected_collection_from_hash_parameters = ->
   if parse_query_string()['collection']?
     $("option[value=#{parse_query_string()['collection']}]").attr('selected','selected')
     $('#collection_select').change()

+# push the selected collection into history with the collection as a hash parameter
 push_selected_collection = ->
   selected = $('#collection_select option:selected')[0]
   new_hash = "#collection=#{$(selected).attr('value')}"
@@ -328,6 +350,7 @@
     window.location.href + new_hash
   history.pushState(null,$(selected).text(),new_url)

+# main collection editor entry point
 $(document).ready ->
   # merge config parameters
   cite_collection_editor_config = $.extend({}, default_cite_collection_editor_config, window.cite_collection_editor_config)


Avoid jquery fadeIn/fadeOut since it's not working correctly in FF now.

--- js-library/app/js/jail_iframe/util/annotated_site_receiver.coffee
+++ js-library/app/js/jail_iframe/util/annotated_site_receiver.coffee
@@ -1,4 +1,6 @@
 # Object which holds the methods that can be called from the factlink core iframe
+
+modalCloseTimeoutHandle = null

 FactlinkJailRoot.annotatedSiteReceiver =
   modalFrameReady: (featureToggles) ->
@@ -6,8 +8,11 @@
     window.FACTLINK_ON_CORE_LOAD?()

   openModalOverlay: ->
-    $("#factlink-modal-frame").fadeIn 'fast'
     console.log 'fadeIn', $("#factlink-modal-frame")[0]
+    if modalCloseTimeoutHandle
+      clearTimeout(modalCloseTimeoutHandle)
+      modalCloseTimeoutHandle = null
+    $("#factlink-modal-frame").addClass 'factlink-frame-visible'
     FactlinkJailRoot.trigger 'modalOpened'

   highlightNewFactlink: (displaystring, id) ->
@@ -15,7 +20,8 @@
     FactlinkJailRoot.trigger 'factlinkAdded'

   closeModal_noAction: ->
-    $("#factlink-modal-frame").fadeOut 'fast', -> FactlinkJailRoot.trigger 'modalClosed'
+    $("#factlink-modal-frame").removeClass 'factlink-frame-visible'
+    modalCloseTimeoutHandle = setTimeout(-> FactlinkJailRoot.trigger 'modalClosed', 300)

   # For compatibility, please remove the next time you see this
   closeModal_highlightNewFactlink: (displaystring, id) ->


model_postgres: fixes

model_postgres: fixes

--- src/local/model_postgres.coffee
+++ src/local/model_postgres.coffee
@@ -175,6 +175,7 @@
                 else if res?.rows?[0]?
                     cb null
                 else
+                    console.log "#{node} does not exist!"
                     cb new errors.NotFound("Node does not exist")

     nodeExists: (node, cb) ->
@@ -182,6 +183,7 @@
             if err
                 cb err
             else
+                console.log "#{node} does not exist"
                 cb null, res?.rows?[0]?

     createNode: (node, cb) ->
@@ -330,7 +332,8 @@
         unless user
             return cb(new Error("No user"))

-        @db.query "SELECT affiliation FROM affiliations WHERE node=$1 AND \"user\"=$2 ORDER BY updated DESC", [ node, user ], cb2
+        @db.query "SELECT affiliation FROM affiliations WHERE node=$1 AND \"user\"=$2 ORDER BY updated DESC"
+        , [ node, user ]
         , (err, res) ->
             cb err, (res?.rows?[0]?.affiliation or "none")


Home: [minor] 'sign in' copy fix

Home: [minor] 'sign in' copy fix

--- client/app/Applications/Home.kdapplication/Views/homeloginbar.coffee
+++ client/app/Applications/Home.kdapplication/Views/homeloginbar.coffee
@@ -20,7 +20,7 @@
     @register     = new CustomLinkView
       tagName     : "a"
       cssClass    : "register"
-      title       : "Have an invite code?"
+      title       : "Have an invite code? Register!"
       icon        : {}
       attributes  :
         href      : "/Register"
@@ -50,7 +50,7 @@

     @login        = new CustomLinkView
       tagName     : "a"
-      title       : "Already a user?"
+      title       : "Already a user? Sign In!"
       icon        : {}
       cssClass    : "login"
       attributes  :


Handle reporting of cyclic objects to Sentry

--- services/web/public/coffee/ide/editor/directives/aceEditor/undo/UndoManager.coffee
+++ services/web/public/coffee/ide/editor/directives/aceEditor/undo/UndoManager.coffee
@@ -233,8 +233,16 @@
 			start = aceDelta.start
 			if !start?
 				error = new Error("aceDelta had no start event.")
+				JSONstringifyWithCycles = (o) ->
+					seen = []
+					return JSON.stringify o, (k,v) ->
+						if (typeof v == 'object')
+							if ( !seen.indexOf(v) )
+								return '__cycle__'
+							seen.push(v);
+						return v
 				Raven?.captureException(error, {
-					aceDelta: JSON.stringify(aceDelta)
+					aceDelta: JSONstringifyWithCycles(aceDelta)
 				})
 				throw error
 			linesBefore = docLines.slice(0, start.row)


Fix bug in get script

Fix bug in get script

--- script/get.coffee
+++ script/get.coffee
@@ -9,7 +9,7 @@
 r = redis.createClient port, host, return_buffers: true
 r.select slice

-r.get process.argv[2], (err, buf) ->
+r.get argv._[0], (err, buf) ->
   throw err if err
   console.log msgpack.unpack(buf)
   r.end()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com


Set '.notesrc' file type to 'source.shell' in .atom/config.cson.

--- home/.atom/config.cson
+++ home/.atom/config.cson
@@ -68,6 +68,7 @@
     "^.inputrc": "text.generic-config"
     "^.mailcap": "source.shell"
     ".*muttrc": "source.shell"
+    "^.notesrc": "source.shell"
     "^.offlineimaprc$": "source.ini"
     "^.profile$": "source.shell"
     "^.psqlrc": "source.sql"


test: rest: improve

test: rest: improve


unnecessary comma in comment

unnecessary comma in comment

--- plugins/linebreak.coffee
+++ plugins/linebreak.coffee
@@ -12,7 +12,7 @@
 #   It requires that all widgets have an id consisting of the uuid and the name for the surrounding element
 #   (<span id=\"#{@options.uuid}-" + widget.widgetName + "\">)
 #
-#   The only option is 'after', which should be an array of widgetnames, after which a linebreak should be inserted
+#   The only option is 'after', which should be an array of widgetnames after which a linebreak should be inserted
 #
 ((jQuery) ->
     jQuery.widget "IKS.hallolinebreak",


Remove bullet spray attack of boss

Remove bullet spray attack of boss

(doesn't fit with the character)

--- src/lazerscripts/stage1/stage1boss.coffee
+++ src/lazerscripts/stage1/stage1boss.coffee
@@ -137,25 +137,13 @@

 class Stage1BossStage1 extends Stage1Boss
   spawn: ->
-    Crafty.e('LargeDrone, Horizon, BulletCircle').drone(
+    Crafty.e('LargeDrone, Horizon').drone(
       x: Crafty.viewport.width + 40
       y: Crafty.viewport.height * .35
       defaultSpeed: 100
       health: 30000
       #defaultSpeed: 50
       pointsOnHit: 10
-    ).bulletCircle(
-      burstAmount: 16
-      projectile: (x, y, angle) =>
-        projectile = Crafty.e('Sphere, Hostile, Projectile')
-          .blink()
-          .attr(
-            w: 14
-            h: 14
-            speed: 200
-            damage: 1
-          )
-        projectile.shoot(x, y, angle)
     )

   execute: ->
@@ -425,10 +413,6 @@
           @wait 200
         )
       )
-      @repeat(5, @sequence(
-        => @entity.shootRing()
-        @wait 300
-      ))

       @moveTo(y: .1, speed: 200, easing: 'easeInOutQuad')
       @while(
@@ -477,10 +461,6 @@
     @sequence(
       @cancelBullets('Mine')
       @cancelBullets('shadow')
-      @repeat(5, @sequence(
-        => @entity.shootRing()
-        @wait 300
-      ))
       @invincible yes
       @while(
         @moveTo(x: .6, y: .90, speed: 50)


Implement the extension callback notifying the component unregistration.


Use GitHub's styling when previewing Markdown

--- user/config.cson
+++ user/config.cson
@@ -60,6 +60,8 @@
   "language-applescript":
     autoCompile: false
   "lisp-paredit": {}
+  "markdown-preview":
+    useGitHubStyle: true
   minimap:
     autoToggle: false
   popupDismissDelay: false


chore(valign-spec): Updated to not use deprecated API.

--- spec/valign-spec.coffee
+++ spec/valign-spec.coffee
@@ -11,17 +11,16 @@
   editor = null

   beforeEach ->
+    atom.workspaceView = new WorkspaceView
+
     waitsForPromise ->
-      atom.packages.activatePackage 'language-coffee-script'
+      atom.workspace.open 'test.coffee'

     runs ->
-      atom.workspaceView = new WorkspaceView
-      atom.workspaceView.openSync()
-
       editorView = atom.workspaceView.getActiveView()
       editor = editorView.getEditor()

-      editor.setGrammar atom.syntax.selectGrammar "test.coffee"
+      atom.packages.activatePackage 'language-coffee-script'

   describe 'Formatting ":"', ->
     beforeEach ->


Cloud Foundry now returns status 204 when deleting roles.

--- routes/cfapi.coffee
+++ routes/cfapi.coffee
@@ -88,15 +88,20 @@
       url: "https://#{services["cloud_foundry_api-domain"].value}/v2/#{level}/#{levelGuid}/#{associationType}/#{associationGuid}"
       headers: {'Authorization': token}
     requestjs options, (error,response,body) ->
-      if((!error)&& (response.statusCode == 201))
+      if((!error)&&(response.statusCode == 201))
         resolve
           status : response.statusCode
           body : JSON.parse(body)
+      else if((!error)&& (response.statusCode == 204))
+        resolve
+          status : response.statusCode
       else if(!error)
+        console.log("statusCode="+response.statusCode)
         reject
           status : 500
           body: response
       else
+        console.log("statusCode="+response.statusCode + " error="+error);
         reject
           status : 500
           body : error


test delay

test delay

--- project/coffee/App.coffee
+++ project/coffee/App.coffee
@@ -89,7 +89,7 @@
         clearInterval @initTimeout
         @initTimeout = setTimeout =>
             location.reload();
-        , 600000
+        , 5000

         console.log 'initapp'
         @setFlags()


prevent random font-size changes when flipping from portrait to landscape

--- @client/shared.coffee
+++ @client/shared.coffee
@@ -769,6 +769,16 @@
 ## CSS reset

 window.styles += """
+
+/*
+https://stackoverflow.com/questions/2710764/preserve-html-font-size-when-iphone-orientation-changes-from-portrait-to-landsca
+*/
+html {
+  -ms-text-size-adjust: 100%;
+  -webkit-text-size-adjust: 100%;
+}
+
+
 /* RESET
  * Eric Meyer's Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/)
  * http://cssreset.com


Check expected scopes in applyEndPatternLast spec

--- spec/grammar-spec.coffee
+++ spec/grammar-spec.coffee
@@ -274,9 +274,9 @@
             { some }excentricSyntax }
           """

-          expect(lines[1][2].value).toBe "}excentricSyntax"
-          expect(lines[4][2].value).toBe "}"
-          expect(lines[4][3].value).toBe "excentricSyntax }"
+          expect(lines[1][2]).toEqual value: "}excentricSyntax", scopes: ['source.apply-end-pattern-last', 'end-pattern-last-env', 'scope', 'excentric']
+          expect(lines[4][2]).toEqual value: "}", scopes: ['source.apply-end-pattern-last', 'normal-env', 'scope']
+          expect(lines[4][3]).toEqual value: "excentricSyntax }", scopes: ['source.apply-end-pattern-last', 'normal-env']

       describe "when the end pattern contains a back reference", ->
         it "constructs the end rule based on its back-references to captures in the begin rule", ->


Create test for the regex parser helper.

--- spec/helpers-spec.coffee
+++ spec/helpers-spec.coffee
@@ -55,3 +55,16 @@
     atom.workspace.observeTextEditors (editor) ->
       Helpers.execFilePath('cat', [], editor.getPath()).then (output) ->
         expect(output).toEqual(['This is a test.\n'])
+
+  describe "The Regex Parsing Helper", ->
+    it "should return Linter results when successful.", ->
+      regex = 'type:(?<type>.+) message:(?<message>.+)'
+      input = ['type:type message:message']
+      output = [(
+        type: 'type'
+        text: 'message'
+        filePath: undefined
+        range: [[0, 0], [0, 0]]
+      )]
+      Helpers.parse(input, regex).then (results) ->
+        expect(results).toEqual(output)


Tweak datatable init config

Tweak datatable init config

--- docs/assets/js/ui-components.coffee
+++ docs/assets/js/ui-components.coffee
@@ -2,7 +2,10 @@
 ---

 if $('#datatable').length
-  $('#datatable').DataTable()
+  $('#datatable').DataTable
+    paging: false
+    searching: false
+    info: false

 class ResponsiveLayout
   constructor: (@$dom)->


:fire: test focus

:fire: test focus

--- spec/palette-spec.coffee
+++ spec/palette-spec.coffee
@@ -3,7 +3,7 @@
 Color = require '../lib/color'
 Palette = require '../lib/palette'

-fdescribe 'Palette', ->
+describe 'Palette', ->
   [colors, palette] = []

   beforeEach ->


translation method in editable-title widget fixed

--- src/components/widgets-common/editable-title/editable-title.directive.coffee
+++ src/components/widgets-common/editable-title/editable-title.directive.coffee
@@ -16,7 +16,7 @@
       if $scope.pdfMode
         return ''
       else
-        tooltipText = $translate.invoke('impac.widget.editable_title.tooltip_text');
+        tooltipText = $translate.instant('impac.widget.editable_title.tooltip_text');
         return w.name + if w.hasEditAbility then ' ' + tooltipText else ''

     ImpacDashboardsSvc.pdfModeEnabled().then(null, null, ->


Change message in goal script

Change message in goal script

--- scripts/goal.coffee
+++ scripts/goal.coffee
@@ -148,7 +148,8 @@
     goals = data.goals or []
     achieved_goals = data.achieved_goals or []
     msg.send "#{user}さんの今日の目標：\n" +
-      goalsToString goals, achieved_goals
+      goalsToString(goals, achieved_goals) + "\n\n" +
+      "終わったら `#{robot.name} 目標達成`と話しかけてください。"

   robot.respond /.*目標達成.*/i, (msg) ->
     user = msg.envelope.user.name


Force focus to paste target

Force focus to paste target

--- scripts/home.coffee
+++ scripts/home.coffee
@@ -20,6 +20,10 @@
     compile = (name) -> Handlebars.compile(document.getElementById(name + '-template').innerHTML)
     templates.preview = compile 'preview'

+    pasteTarget = document.getElementById('paste-target')
+    pasteTarget.addEventListener('paste', handlePaste, false)
+    forceFocus()
+
     loadHistory()
     items = getHistory()[..4].reverse()
     historyContainer = document.getElementById('history')
@@ -38,6 +42,14 @@
                     historyList.appendChild(createHistoryItem({ item: result[item], hash: item }))
         )
 , false)
+
+forceFocus = ->
+    if document.activeElement.tagName in ['TEXTAREA', 'INPUT']
+        setTimeout(forceFocus, 250)
+        return
+    pasteTarget = document.getElementById('paste-target')
+    pasteTarget.focus()
+    setTimeout(forceFocus, 250)

 createHistoryItem = (h) ->
     item = h.item
@@ -140,6 +152,9 @@
     file.isHashed = true
     uploadFile(file)

+handlePaste = (e) ->
+    return
+
 uploadFile = (file) ->
     oldHash = file.hash
     upload = ->


Don't autoplay history items

Don't autoplay history items

--- scripts/mine.coffee
+++ scripts/mine.coffee
@@ -103,7 +103,6 @@
                 source.type = file.type
                 preview.appendChild(source)
             preview.volume = 0
-            preview.play()
         else if item.blob_type == 'image'
             preview = document.createElement('img')
             preview.src = window.cdn + item.files[0].file


avoid potential race condition in test

avoid potential race condition in test


quick deploy command

quick deploy command


WHO THE FUCK NEEDS PERCENTAGES

WHO THE FUCK NEEDS PERCENTAGES

--- scripts/loud.coffee
+++ scripts/loud.coffee
@@ -18,7 +18,7 @@
 #   annabunches

 module.exports = (robot) ->
-  robot.hear /^([A-Z"][A-Z0-9 .,'"()\?!&\%$#@+-]+)$/, (res) ->
+  robot.hear /^([A-Z"][A-Z0-9 .,'"()\?!&$#@+-]+)$/, (res) ->
     # Pick a loud from the stored list and say it. Skip if there are no louds.
     old_loud = res.random(robot.brain.get('louds'))
     if old_loud?


new validation2 02/22/2016

new validation2 02/22/2016

--- app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee
+++ app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee
@@ -5,27 +5,27 @@
   $scope.confirmDelete = true
   $scope.startDate = formatDate daysFromToday -7
   $scope.endDate = formatDate daysFromToday 1
-  $scope.bulkActions = [ { name: t("bom_actions_delete"), callback: 'deleteLineItems' } ]
+  $scope.bulkActions = [ { name: t "bom_actions_delete", callback: 'deleteLineItems' } ]
   $scope.selectedUnitsProduct = {};
   $scope.selectedUnitsVariant = {};
   $scope.sharedResource = false
   $scope.columns = Columns.setColumns
-    order_no:     { name: t("bom_no"), visible: false }
-    full_name:    { name: t("name"),         visible: true }
-    email:        { name: t("email"),     visible: false }
-    phone:        { name: t("phone"),        visible: false }
-    order_date:   { name: t("bom_date"),   visible: true }
-    producer:     { name: t("producer"),     visible: true }
-    order_cycle:  { name: t("bom_cycle"),  visible: false }
-    hub:          { name: t("bom_hub"),          visible: false }
-    variant:      { name: t("bom_variant"),      visible: true }
-    quantity:     { name: t("bom_quantity"),     visible: true }
-    max:          { name: t("bom_max"),          visible: true }
-    final_weight_volume:   { name: t("bom_final_weigth_volume"), visible: false }
-    price:        { name: t("bom_price"),        visible: false }
+    order_no:     { name: t "bom_no", visible: false }
+    full_name:    { name: t "name",         visible: true }
+    email:        { name: t "email",     visible: false }
+    phone:        { name: t "phone",        visible: false }
+    order_date:   { name: t "bom_date",   visible: true }
+    producer:     { name: t "producer",     visible: true }
+    order_cycle:  { name: t "bom_cycle",  visible: false }
+    hub:          { name: t "bom_hub",          visible: false }
+    variant:      { name: t "bom_variant",      visible: true }
+    quantity:     { name: t "bom_quantity",     visible: true }
+    max:          { name: t "bom_max",          visible: true }
+    final_weight_volume:   { name: t "bom_final_weigth_volume", visible: false }
+    price:        { name: t "bom_price",        visible: false }

   $scope.confirmRefresh = ->
-    LineItems.allSaved() || confirm(t("unsaved_changes_warning"))
+    LineItems.allSaved() || confirm(t "unsaved_changes_warning")

   $scope.resetSelectFilters = ->
     $scope.distributorFilter = blankOption().id
@@ -73,12 +73,12 @@
         StatusMessage.display 'success', "All changes saved"
         $scope.bulk_order_form.$setPristine()
       ).catch ->
-        StatusMessage.display 'failure', {t("unsaved_changes_warning")}
+        StatusMessage.display 'failure', {t "unsaved_changes_warning"}
     else
-      StatusMessage.display 'failure', {t("unsaved_changes_warning")}
+      StatusMessage.display 'failure', {t "unsaved_changes_warning"}

   $scope.deleteLineItem = (lineItem) ->
-    if ($scope.confirmDelete && confirm(t("are_you_sure"))) || !$scope.confirmDelete
+    if ($scope.confirmDelete && confirm(t "are_you_sure")) || !$scope.confirmDelete
       LineItems.delete lineItem, =>
         $scope.lineItems.splice $scope.lineItems.indexOf(lineItem), 1


add slack new webhook url.

add slack new webhook url.

--- send_message.coffee
+++ send_message.coffee
@@ -408,17 +408,17 @@
           @form['channel'] = tg
           # @form['mrkdwn']  = "true"

-          token = @info['token'][tg]
-          if token == undefined
-            console.log "#{@prefix}: No token for #{tg} channel."
-            return
-
           if @info['team_url'] and @info['token']
             uri = 'https://' + @info['team_url'] + '/services/hooks/incoming-webhook?token=' + token
+            token = @info['token'][tg]
+            if token == undefined
+              console.log "#{@prefix}: No token for #{tg} channel."
+              return
+
           else if @info['webhook_url']
             uri = @info['webhook_url']
           else
-            console.log "#{@prefix} URI Not found."
+            console.log "#{@prefix}: URI Not found."
             return

           if option? or option != false


remove concurrency

remove concurrency


added shouldUpdateScroll to router

added shouldUpdateScroll to router

--- app/main.cjsx
+++ app/main.cjsx
@@ -12,7 +12,15 @@
 browserHistory.listen ->
   dispatchEvent new CustomEvent 'locationchange'

-ReactDOM.render <Router history={browserHistory} render={applyRouterMiddleware(useScroll())}>{routes}</Router>,
+# make sure project stats page does not scroll back to the top when the URL changes
+shouldUpdateScroll = (prevRouterProps, routerProps) ->
+  pathname = routerProps.location.pathname.split('/')
+  if ('stats' in pathname) and ('projects' in pathname)
+    false
+  else
+    true
+
+ReactDOM.render <Router history={browserHistory} render={applyRouterMiddleware(useScroll(shouldUpdateScroll))}>{routes}</Router>,
   document.getElementById('panoptes-main-container')

 # Are we connected to the latest back end?


Show a success flash message when saving an integration

--- app/assets/javascripts/admin/models/integration.js.coffee
+++ app/assets/javascripts/admin/models/integration.js.coffee
@@ -23,6 +23,7 @@
       parameters: parameters
       enabled: enabled).
       done((mappings, textStatus, jqXHR) =>
+        Augury.Flash.success 'The integration has been successfully updated.'
         Augury.parameters.fetch()

         _(mappings).each (mapping) ->


Better handling of empty weeks for ThisWeek component

Resolves #692, although I'm not super confident that it's completely correct or that it won't break in some circumstances.

This is yet more pain because of #573

--- app/assets/javascripts/components/overview/this_week.cjsx
+++ app/assets/javascripts/components/overview/this_week.cjsx
@@ -15,6 +15,12 @@
     return count unless week == '()'
     count += 1

+emptyWeeksUntil = (weekMeetings, week_index) ->
+  count = 0
+  for week in weekMeetings[0...week_index]
+    count += 1 if week == '()'
+  return count
+
 ThisWeek = React.createClass(
   propTypes:
     course:  React.PropTypes.object
@@ -23,21 +29,27 @@
   displayName: 'ThisWeek'

   render: ->
-    week = @props.weeks[@props.current]
-    if week?
+    if @props.weeks?
       course = @props.course
       week_index = @props.current + 1

       meetings = DateUtils.meetings(course)
       weekMeetings = DateUtils.weekMeetings(meetings, course, course.day_exceptions)
-      is_first_week = moment().diff(@props.course.timeline_start, 'days') < 0
+      empty_weeks_at_beginning = emptyWeeksAtBeginning(weekMeetings)
+      days_until_beginning = empty_weeks_at_beginning * 7
+      is_first_week = moment().diff(@props.course.timeline_start, 'days') <= days_until_beginning
       if is_first_week
-        week_meetings_index = emptyWeeksAtBeginning(weekMeetings)
+        week_meetings_index = empty_weeks_at_beginning
         thisWeekMeetings = weekMeetings[week_meetings_index]
         week_index = week_meetings_index + 1
+        week = @props.weeks[0]
+        title = "First Active Week"
       else
         thisWeekMeetings = weekMeetings[@props.current]
+        emptyWeeksSoFar = emptyWeeksUntil(weekMeetings, @props.current)
+        week = @props.weeks[@props.current - emptyWeeksSoFar]

+    if week?
       week_component = (
         <Week
           week={week}
@@ -53,8 +65,7 @@
           meetings={if weekMeetings then thisWeekMeetings else false}
         />
       )
-      if is_first_week
-        title = "First Active Week"
+
     else
       no_weeks = (
         <li className="row view-all">


segfault

segfault

--- server/index.coffee
+++ server/index.coffee
@@ -1,6 +1,3 @@
-SegfaultHandler = require 'segfault_handler'
-SegfaultHandler.registerHandler();
-
 WaferPie = require 'waferpie'
 wafer = new WaferPie
 wafer.configure 'config.yml'


Cache-Control /file URLs to ensure reload during Replace File (#163)

--- server/files.coffee
+++ server/files.coffee
@@ -50,6 +50,7 @@
     ## get()
     headers =
       'Content-Type': 'text/plain'
+      'Cache-Control': 'stale-while-revalidate'
     if req.headers['if-modified-since']
       since = Date.parse req.headers['if-modified-since']  ## NaN if invaild
       if since and req.gridFS.uploadDate and (req.headers['if-modified-since'] == req.gridFS.uploadDate.toUTCString() or since >= req.gridFS.uploadDate.getTime())


Add public touchStart

Add public touchStart

--- framer/LayerDraggable.coffee
+++ framer/LayerDraggable.coffee
@@ -94,6 +94,11 @@
 		# Override this to add your own behaviour to the update position
 		return point

+	touchStart: (event) =>
+		# We expose this publicly so you can start the dragging from an external event
+		# this is for example needed with the slider.
+		@_touchStart(event)
+
 	_touchStart: (event) =>

 		# @_propagateEvents = true if (@multipleDraggables && @lockDirection)


fix key binding

fix key binding

--- keymaps/shell.cson
+++ keymaps/shell.cson
@@ -8,4 +8,4 @@
 # For more detailed documentation see
 # https://atom.io/docs/latest/advanced/keymaps
 '.workspace':
-  'ctrl-alt-t': 'term:open-split-right'
+  'ctrl-alt-t': 'term2:open-split-right'


delete node collections

delete node collections

--- tree2.coffee
+++ tree2.coffee
@@ -318,6 +318,12 @@
       c.addAnimations(c.sortNodes( ((n1, n2) -> n2.value - n1.value), t))
   },
   {
+    name: 'delete'
+    action: (c, t) ->
+      console.log('delete')
+      c.delete_flag = true
+  },
+  {
     name: 'tidy',
     action: (c, t) ->
       console.log('tidy trees')
@@ -530,7 +536,9 @@
         @dropdown_menu.options[@dropdown_menu.selected].action(this, t)
       @dropdown_menu = null

-      if @reconstruct_as?
+      if @delete_flag
+        return null
+      else if @reconstruct_as?
         newcollection = new @reconstruct_as(this.shape)
         newcollection.copyNodesFrom(this)
         delete @reconstruct_as
@@ -1041,4 +1049,5 @@
 canvas.addEventListener 'mouseup', (e) ->
   t = Date.now()/1000
   pos = getCursorPosition(canvas, e)
-  collections = (collection.mouseup(pos, t) for collection in collections)
+  collections = (c.mouseup(pos, t) for c in collections)
+  collections = (c for c in collections when c isnt null)


improve an error message

improve an error message

--- trust.coffee
+++ trust.coffee
@@ -1,6 +1,7 @@
 fs = require 'fs'
 zlib = require 'zlib'
 async = require 'async'
+{exec} = require 'child_process'
 {spawn_with_output} = require './util'

@@ -30,14 +31,11 @@

 fingerprint_for_cert_path = (path, callback) ->
-  args = ['x509', '-noout', '-in', path, '-fingerprint']
-  spawn_with_output "/usr/bin/openssl", args, (e, out, err) ->
+  exec "/usr/bin/openssl x509 -noout -in '#{path}' -fingerprint", args, (e, out, err) ->
     return callback e if e
     m = out.match /SHA1 Fingerprint=([A-F0-9:]+)/
     if not m
-      console.log err
-      console.log out
-      return callback 'unexpected output from openssl'
+      return callback "unexpected output from openssl: #{JSON.stringify [err, out]}"
     sha1 = m[1].replace /:/g, ''
     callback null, sha1


Added convertStartToLayer and convertEndToLayer


Fix typo in multicursor module.

Fix typo in multicursor module.

--- src/modules/multi-cursor.coffee
+++ src/modules/multi-cursor.coffee
@@ -62,7 +62,7 @@
   removeCursor: (userId) ->
     cursor = @cursors[userId]
     this.emit(MultiCursor.events.CURSOR_REMOVED, cursor)
-    cursor.elem.parentNode.removeChild(cursor.elem) of cursor?
+    cursor.elem.parentNode.removeChild(cursor.elem) if cursor?
     delete @cursors[userId]

   setCursor: (userId, index, name, color, update = true) ->


Fix multicursor position calculation

Fix multicursor position calculation

Prevent splitting line node

--- src/modules/multi-cursor.coffee
+++ src/modules/multi-cursor.coffee
@@ -131,8 +131,12 @@
       else if offset == leaf.length
         this._moveCursor(cursor, leaf.node.parentNode, 'right')
       else
-        [leftNode, rightNode] = Utils.splitNode(leaf.node.parentNode, offset)
-        this._moveCursor(cursor, leftNode, 'right')
+        [leftNode, rightNode] = Utils.splitNode(leaf.node, offset)
+        guide = @container.ownerDocument.createElement('span')
+        DOM.setText(guide, DOM.ZERO_WIDTH_NOBREAK_SPACE)
+        leaf.node.parentNode.insertBefore(guide, rightNode)
+        this._moveCursor(cursor, guide)
+        DOM.removeNode(guide)
         DOM.normalize(leaf.node.parentNode)
     else
       guide = @container.ownerDocument.createElement('span')


fix some more error states

fix some more error states

--- _source/packages/newspring:core/client/templates/objects/signup/signin.coffee
+++ _source/packages/newspring:core/client/templates/objects/signup/signin.coffee
@@ -156,8 +156,8 @@

     email = event.target.value.toLowerCase()

-    if not email
-      template.hasAccount.set true
+    if not Apollos.validate.isEmail email
+      template.hasErrors.set true

     Apollos.user.getAccountType email, (error, accountType) ->
       if error
@@ -219,21 +219,25 @@
     terms = template.find("input[name=terms]").checked

     if not Apollos.validate.isEmail email
+      template.hasErrors.set true
       emailTemplate = template.email.get()
       emailTemplate.methods.setStatus true
       return

     if not Apollos.validate.isEmail email
+      template.hasErrors.set true
       emailTemplate = template.email.get()
       emailTemplate.methods.setStatus true
       return

     if not password
+      template.hasErrors.set true
       passwordTemplate = template.password.get()
       passwordTemplate.methods.setStatus "Password cannot be empty", true
       return

     if not terms
+      template.hasErrors.set true
       termsTemplate = template.terms.get()
       termsTemplate.methods.setStatus "You must accept the terms and conditions", true
       return
@@ -252,6 +256,7 @@
         else
           template._.createAccount email, password

+  # TODO fix link not working when using enter to submit form
   "click [data-forgot-password]": (event, template) ->

     if event.target.dataset?.forgotPassword


Update config.cson (auto update)

Update config.cson (auto update)

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -20,6 +20,7 @@
   minimap:
     plugins:
       "highlight-selected": true
+      "highlight-selectedDecorationsZIndex": 0
   "save-session":
     dataSaveFolder: "/Users/chiragraman/.dotfiles/atom.symlink/packages/save-session/projects"
   "tool-bar":


Change terminology for managing suspended holds from 'Resume' to 'Activate'

--- src/account/holds.coffee
+++ src/account/holds.coffee
@@ -26,8 +26,8 @@
 		<input type="submit" class="cancel" name="all" value="Cancel all" />
 		<input type="submit" class="suspend" name="some" value="Suspend selected holds" />
 		<input type="submit" class="suspend" name="all" value="Suspend all" />
-		<input type="submit" class="resume" name="some" value="Resume selected holds" />
-		<input type="submit" class="resume" name="all" value="Resume all" />
+		<input type="submit" class="resume" name="some" value="Activate selected holds" />
+		<input type="submit" class="resume" name="all" value="Activate all" />
 	</form>
 	'''
 	tpl_hold_item = _.template '''
@@ -208,7 +208,7 @@
 						break
 				refresh()
 			else
-				$(@).publish 'notice', if suspend then ['Nothing was done because no active holds were found to suspend.'] else ['Nothing was done because no suspended holds were found to resume.']
+				$(@).publish 'notice', if suspend then ['Nothing was done because no active holds were found to suspend.'] else ['Nothing was done because no suspended holds were found to activate.']
 			return false

 		@delegate '.resume[name=some]', 'click', update_some


activity: clean up `ActivityLikeView` class

--- client/Social/Activity/views/likeview.coffee
+++ client/Social/Activity/views/likeview.coffee
@@ -9,68 +9,20 @@

     super options, data

-    @_currentState = no

-    @likeCount    = new ActivityLikeCount
-      cssClass    : 'like-count'
+  viewAppended: ->
+
+    data = @getData()
+
+    @link = new ActivityLikeLink {}, data
+
+    @count = new ActivityLikeCount
       tooltip     :
         gravity   : options.tooltipPosition
         title     : ""
     , data

-    @likeLink = new ActivityLikeLink {}, data
-
-    {useTitle}     = @getOptions()
-    {isInteracted} = data.interactions.like
-
-    @_currentState = isInteracted
-
-    if isInteracted
-    then @decorateLike no
-    else @decorateUnlike no
-
-
-  toggleState: (err) ->
-
-    return @showError err  if err
-
-    @_currentState = not @_currentState
-
-    if @_currentState
-    then @decorateLike()
-    else @decorateUnlike()
-
-
-  decorateLike: (track = yes) ->
-
-    @setClass "liked"
-    @likeLink.updatePartial "Unlike"  if @getOption "useTitle"
-
-    return  unless track
-
-    KD.mixpanel "Activity like, success"
-
-    KD.getSingleton("badgeController").checkBadge
-      source : "JNewStatusUpdate" , property : "likes", relType : "like", targetSelf : 1
-
-
-  decorateUnlike: (track = yes) ->
-
-    @unsetClass "liked"
-    @likeLink.updatePartial "Like" if @getOption "useTitle"
-
-    return  unless track
-
-    KD.mixpanel "Activity unlike, success"
-
-
-  showError: (err) ->
-
-    KD.showError err,
-      AccessDenied : "You are not allowed to like activities"
-      KodingError  : "Something went wrong"
-

   pistachio: ->

-    """{{> @likeLink}}{{> @likeCount}}"""
+    "{{> @link}}{{> @count}}"


Populate current attributes of item in filter().

--- app/assets/javascripts/app/controllers/_application_controller_form.js.coffee
+++ app/assets/javascripts/app/controllers/_application_controller_form.js.coffee
@@ -2142,8 +2142,7 @@
     return if !App[attribute.relation]

     attribute.options = []
-
-    list = []
+    list              = []
     if attribute.filter

       App.Log.debug 'ControllerForm', '_getRelationOptionList:filter', attribute.filter
@@ -2154,7 +2153,7 @@

         all = App[ attribute.relation ].search( sortBy: attribute.sortBy )

-        list = attribute.filter( all, 'collection' )
+        list = attribute.filter( all, 'collection', @params )

       # data based filter
       else if attribute.filter[ attribute.name ]


Decode HTML entities in block quotes

Decode HTML entities in block quotes

--- client/app/lib/util/formatBlockquotes.coffee
+++ client/app/lib/util/formatBlockquotes.coffee
@@ -1,4 +1,5 @@
 hljs = require 'highlight.js'
+Encoder = require 'htmlencode'

 module.exports = (text = '') ->

@@ -11,6 +12,6 @@
         [lang] = match
         part = "\n#{part}"  unless hljs.getLanguage lang

-      parts[index] = "\n```#{part}\n```\n"
+      parts[index] = "\n```#{Encoder.htmlDecode part}\n```\n"

   parts.join ''


update test for removed published option

--- client/apps/articles_list/test/routes.coffee
+++ client/apps/articles_list/test/routes.coffee
@@ -26,5 +26,4 @@
     it 'sends arguments to the template', ->
       routes.articles_list @req, @res, @next
       @res.render.args[0][0].should.equal 'index'
-      @res.render.args[0][1].published.should.be.true()
       @res.render.args[0][1].current_channel.name.should.equal 'Editorial'


EditorPane: Refactor for creating the initial change object.

--- client/ide/workspace/panes/editorpane.coffee
+++ client/ide/workspace/panes/editorpane.coffee
@@ -130,7 +130,7 @@
     return @aceView.getData()

-  bindChangeListeners: ->
+  getInitialChangeObject: ->

     change        =
       origin      : KD.nick()
@@ -142,9 +142,16 @@
           machine :
             uid   : @file.machine.uid

+    return change
+
+
+  bindChangeListeners: ->
+
+    change = @getInitialChangeObject()
+
     @getAce()
-      .on 'ace.change.cursor', @lazyBound 'handleCursorChange', change
-      .on 'FileContentChanged', @lazyBound 'handleFileContentChange', change
+      .on 'ace.change.cursor',   @lazyBound 'handleCursorChange',      change
+      .on 'FileContentChanged',  @lazyBound 'handleFileContentChange', change
       .on 'FileContentRestored', @lazyBound 'handleFileContentChange', change


activate clabot secret

activate clabot secret

--- src/clabot-sample.coffee
+++ src/clabot-sample.coffee
@@ -15,6 +15,7 @@
   secrets:
     clabot:
       sandbox: process.env.HUB_SECRET
+      clabot: process.env.HUB_SECRET

 app.use require('connect-assets')()
 app.use(express.compress());


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/camera.coffee
+++ src/camera.coffee
@@ -35,7 +35,7 @@
       width: 2 * deadzone.x
       height: 2 * deadzone.y

-
+    if
     I.scroll = Point(
       I.scroll.x.clamp(centerRect.left, centerRect.right)
       I.scroll.y.clamp(centerRect.top, centerRect.bottom)


Reduce some log level

Reduce some log level

--- src/coffee/routes.coffee
+++ src/coffee/routes.coffee
@@ -47,7 +47,7 @@
     res.header 'Access-Control-Allow-Methods', 'GET, POST'

     if _ph
-      logger.info 'Phantom process already running, skipping...'
+      logger.debug 'Phantom process already running, skipping...'
       next()
     else
       phantomOpts =


Specs cleaned and fixed

Specs cleaned and fixed

--- lib/command.coffee
+++ lib/command.coffee
@@ -12,7 +12,6 @@
     {name: 'extension', alias: 'x', describe: 'Coffee files extension', default: 'coffee'}
     {name: 'output', alias: 'o', describe: 'The output directory', default: './doc'}
     {name: 'min-coverage', alias: 'm', describe: 'Require a minimum percentage to be documented or fail', default: '0'}
-    {name: 'test', alias: 't', describe: 'Do not create any output files. Use with min-coverage'}
     {name: 'output-dir'}
     {name: 'theme', describe: 'The theme to be used', default: 'default'}
     {name: 'name', alias: 'n', describe: 'The project name used'}
@@ -122,7 +121,6 @@
     unless options.test
       @theme.compile(environment)

-
     overall      = 0
     undocumented = 0

@@ -136,8 +134,9 @@
           head: [section, 'Path']

         table.push(entry) for entry in data.undocumented
-        console.log table.toString()
-        console.log ''
+        unless options.test
+          console.log table.toString()
+          console.log ''
     else
       table = new Table
         head: ['', 'Total', 'Undocumented']
@@ -152,15 +151,17 @@
         ['Methods', sections['Methods'].total, sections['Methods'].undocumented.length]
       )

-      console.log table.toString()
-      console.log ''
-      console.log "  Totally documented: #{(100 - undocumented_percent).toFixed(2)}%"
-      console.log ''
+      unless options.test
+        console.log table.toString()
+        console.log ''
+        console.log "  Totally documented: #{(100 - undocumented_percent).toFixed(2)}%"
+        console.log ''

     documentedRatio = 100 - (100*undocumented/overall).toFixed(2)
     if documentedRatio < options["min-coverage"]
-      console.error colors.red("  Expected " + options["min-coverage"] +
-                     "% to be documented, but only " + documentedRatio + "% were.")
+      unless options.test
+        console.error colors.red("  Expected " + options["min-coverage"] +
+                       "% to be documented, but only " + documentedRatio + "% were.")
       cb 1 if cb
     else
       cb() if cb


Separate vast error and empty responses

Separate vast error and empty responses

--- src/client.coffee
+++ src/client.coffee
@@ -28,8 +28,8 @@

         # TODO: handle request timeout

-        VASTParser.parse url, headers, timeout, logger, (response) =>
-            cb(response)
+        VASTParser.parse url, headers, timeout, logger, (err, response) =>
+            cb(err, response)

     # 'Fake' static constructor


Deprecation for `--output-dir` has been added (#140)

--- lib/command.coffee
+++ lib/command.coffee
@@ -9,6 +9,7 @@
     {name: 'help', alias: 'h', describe: 'Show this help'}
     {name: 'version', describe: 'Show version'}
     {name: 'output', alias: 'o', describe: 'The output directory', default: './doc'}
+    {name: 'output-dir'}
     {name: 'theme', describe: 'The theme to be used', default: 'default'}
     {name: 'name', alias: 'n', describe: 'The project name used'}
     {name: 'readme', alias: 'r', describe: 'The readme file used'}
@@ -69,6 +70,10 @@
     @extendOptimist(optimist, defaults, @theme::options)

     @options = @prepareOptions(optimist, defaults)
+
+    if @options['output-dir']
+      console.log "The usage of outdated `--output-dir` option detected. Please switch to `--output`."
+      process.exit()

     if @options.help
       console.log optimist.help()


Append space when input is focused and empty.

--- vendor/assets/javascripts/control.js.coffee
+++ vendor/assets/javascripts/control.js.coffee
@@ -89,6 +89,9 @@
     @input.on "click", (event) =>
       # When the user clicks into the editor, check if they have clicked on a partial token to be completed.
       @checkAutocomplete()
+
+      # Append a space so the caret blinks, if the input is empty.
+      @input.append(' ') if @input.text().length is 0

     @input.on "blur", (event) =>
       # When the user clicks out of the editor, wait a tick to get the active item- if they did not click on the


removing whitespace. last commit did not push.

--- lib/cupcake.coffee
+++ lib/cupcake.coffee
@@ -24,8 +24,8 @@
     datastore:
       label: 'Data Store'
       options: ['redis', 'nano', 'mysql', 'mongoose', 'mongoskin']
-
-  project:
+
+  project:
     name: 'foobar'
     framework: 'express'
     template: 'jade'
@@ -106,7 +106,7 @@

   run: ->
     unless process.argv.length == 3
-      console.log 'Please enter project name!'
+      console.log 'Please enter project name!'
       return

     console.log "Welcome to Cupcake - #{@VERSION}"


Add Category 8 to extending 2

Add Category 8 to extending 2

--- views/components/main/parts/task-panel.cjsx
+++ views/components/main/parts/task-panel.cjsx
@@ -29,7 +29,7 @@
       return '#ffffff'
     when 1
       return '#19BB2E'
-    when 2
+    when 2, 8
       return '#e73939'
     when 3
       return '#87da61'


Fix warning

Fix warning

--- views/components/settings/parts/others.cjsx
+++ views/components/settings/parts/others.cjsx
@@ -17,10 +17,10 @@

{__ "poi-description %s %s", POI_VERSION, process.versions.electron}

           {
             if window.language is "zh-CN" || window.language is "zh-TW"
-              [
+

微博:  @ 今天 poi 出新版本了吗

开发讨论与意见交流群: 378320628

-              ]
+

           }

{__ "Database"}: http://db.kcwiki.moe

{__ "Wiki"}:  https://github.com/poooi/poi/wiki


view helper.

view helper.

--- example/view_helper.coffee
+++ example/view_helper.coffee
@@ -2,7 +2,8 @@
 # Globals variables.
 # Get the domains definition as globals.
 domains_definition = require('./domains')
-metadaBuilder = require('./metadata_builder').metadataBuilder
+
+metadaBuilder = Fmk.Helpers.metadataBuilder# require('./metadata_builder').metadataBuilder

 Handlebars.registerHelper 'pick', (val, options) ->
   return options.hash[val]
@@ -68,13 +69,12 @@
       if metadata.format?
         propValue =  metadata.format(propValue)
       if metadata.symbol?
-        propValue = propValue + i18n.t(metadata.symbol)
+        propValue = propValue + " " + i18n.t(metadata.symbol)
       if dataType is "checkbox"
          iconChecked = if this[property] then "-check" else ""
          return ""
       if dataType is "date" and this[property] isnt ""
-        formatedDate = moment(this[property]).format("YYYY-MM-DD")
-        return "
#{formatedDate}
"
+        return "
#{propValue}
"
       else return "
#{_.escape(propValue)}
"
     return ""
   html = "

@@ -107,10 +107,12 @@
     return if isDisplayRequired then "*" else ""
   symbol = ()=>
     isSymbol = false
+    console.log(metadata, isSymbol)
     if opt.symbol?
       isSymbol = opt.symbol
     else if metadata.symbol?
       isSymbol = metadata.symbol
+
     return if isSymbol then "#{isSymbol}" else ""
   #isRequired = if !opt.isRequired? or !opt.isRequired then "" else "*"
   translationRoot = opt.translationRoot or undefined
@@ -201,6 +203,114 @@

         "
   new Handlebars.SafeString(html)
+
+
+Handlebars.registerHelper "radio_for", (property, options) ->
+  #Initialize the variables which are options
+  html = undefined
+  translationRoot = undefined
+  dataType = undefined
+  #Read all the options if they exists
+  opt = options.hash or {}
+  metadata = Fmk.Helpers.metadataBuilder.getMetadataForAttribute(this,property)
+  #console.log "metadata",metadata
+  domain = domains_definition[metadata.domain] or {}
+  #possible values for the radio group
+  possibleValues = [] ;
+  isDisplayRequired = false
+  isRequired = ()=>
+    isDisplayRequired = false
+    if opt.isRequired?
+      isDisplayRequired = opt.isRequired
+    else if metadata.required?
+      isDisplayRequired = metadata.required
+    return if isDisplayRequired then "" else generateRadioButton(i18n.t('search.labels.all'), "null", defaultValue == "")
+
+  translationRoot = opt.translationRoot or undefined
+  dataType = opt.dataType or domain.type or "text"
+  #generate possible values or return classic input_for
+  switch dataType
+    when "boolean" then possibleValues = [true, false]
+    else return "Type " + dataType + " not supported by helper radio_for"
+  defaultValue = if opt.defaultValue != undefined then opt.defaultValue else ""
+  readonly = opt.readonly or false
+  readonly = if readonly then "readonly" else ""
+  disabled = opt.disabled or false
+  disabled = if disabled then "disabled" else ""
+  inputAttributes= opt.inputAttributes or ""
+  #Add bootstrap-switch css to checkbox input.
+  containerAttribs = opt.containerAttribs or ""
+  containerCss = opt.containerCss or ""
+  labelSizeValue = if opt.isNoLabel then 0 else if opt.labelSize then opt.labelSize else 3
+  labelSize = "col-sm-#{labelSizeValue} col-md-#{labelSizeValue} col-lg-#{labelSizeValue}"
+  radioSizeValue = opt.radioSize or 4
+  radioSize = "col-sm-#{radioSizeValue} col-md-#{radioSizeValue} col-lg-#{radioSizeValue}"
+  #Deal with the inputSize
+  inputSize = ()=>
+      inputSizeValue = 12 - labelSizeValue
+      inputSize = opt.inputSize or "col-sm-#{inputSizeValue} col-md-#{inputSizeValue} col-lg-#{inputSizeValue}"
+  #Deal with the label
+  label = ()=>
+    if opt.isNoLabel?
+      return ""
+    else
+      return "#{translationKey()}"
+  #Get the value of the transalated label.
+  translationKey =()=>
+    translation = metadata.label or ("#{this['modelName']}.#{property}" if this['modelName']?) or ""
+    if translationRoot?
+      translation = ((if (translationRoot?) and typeof translationRoot is "string" then translationRoot + "." else "")) + property
+
+    return if(translation is "") then "" else i18n.t(translation)
+
+    #if not opt.isNoLabel? then " #{translationKey} " else ""
+  #By default there is a placeholder or if the preperty is true.
+  placeholder = if (!opt.placeholder? and opt.isNoLabel) or opt.placeholder then "placeholder='#{translationKey()}'" else ""
+  #console.log "placeholder", placeholder, (!opt.isNoLabel? or !opt.isNolabel)
+
+  #Checked by default
+  checkedByDefault = () =>
+    return "checked='checked'"
+
+  #Basic layout for radio button
+  generateRadioButton = (label, value, isDefault) =>
+    return "
+

+

+
+                <input id='#{property + value}' name='#{property}' data-name='#{property}' type=radio value='#{value}' #{checkedByDefault() if isDefault} #{inputAttributes} #{placeholder} #{readonly} #{disabled}/>
+
+
+               #{label}
+
+

+
"
+
+  #Deal with error
+  #Initialize the errors variables => Is there an error, if yes what is the message.
+  error = ""
+  error = "has-error" if @errors? and @errors[property]?
+  errorValue = if @errors? and @errors[property]? then @errors[property] else ""
+  errorSize = ()=>
+    errorLength = 12 - labelSizeValue
+    offsetError = labelSizeValue
+    return "col-sm-#{errorLength} col-md-#{errorLength} col-lg-#{errorLength} col-sm-offset-#{offsetError} col-md-offset-#{offsetError} col-lg-offset-#{offsetError}"
+  #create the radios
+  radios = isRequired()
+  radios += generateRadioButton(i18n.t('search.labels.'+value), value, value == defaultValue) for value in possibleValues
+
+  errors = ()=>
+    if error == "has-error" then " #{errorValue } " else ""
+  html = "
+

+            #{label()}
+

+              #{radios}
+

+            #{errors()}
+

+        "
+  new Handlebars.SafeString(html)

 #Generate a optionset selector. https://developer.mozilla.org/en-US/docs/Web/API/HTMLSelectElement
 Handlebars.registerHelper "options_selected", (property, options) ->
@@ -244,7 +354,7 @@
   #
     if opt.icon? then " " else ""
     #if opt.icon? then " " else ""
-  isAddOnInput = opt.icon? or (opt.isRequired || metadata.required) is true
+  isAddOnInput = true or opt.icon? or (opt.isRequired || metadata.required) is true
    #Deal with the label
   label = ()=>
     if not opt.isNoLabel?
@@ -266,15 +376,16 @@
     id = elt.id
     prop = elt.label
     isSelected = if selected? and id? and id.toString() is selected.toString() then "selected" else ""
-    html+= "<option value= '#{id}' data-name='#{property}' #{isSelected}>#{prop}</option>"
+    html+= "<option value= '#{id}'  #{isSelected}>#{prop}</option>"
     return undefined
+  multiple = if opt.isMultiple then "multiple style='width:100%;'" else ""
   #Initialize the html
   html = "

             #{label()}

                 #{icon()}
-                <select id='#{property}' #{readonly} #{optName} #{optToTriggerName} #{optToTriggerListKey} #{dataMapping} class='form-control input-sm'>"
+                <select id='#{property}' data-name='#{property}' #{multiple} #{readonly} #{optName} #{optToTriggerName} #{optToTriggerListKey} #{dataMapping} class='form-control input-sm'>"
   #add options foreach options in the list
   addOption(elt) for elt in list
   html +=      "</select>#{isRequired()}
@@ -395,6 +506,16 @@
     return ""
   return new Handlebars.SafeString("#{i18n.t(translationKey)} #{generateSortPosition()}")

+
+Handlebars.registerHelper "statusIcon", (property, options)->
+  switch this[property]
+    when 0 then icon = "fa fa-ban";
+    when 1 then icon = "fa fa-exclamation";
+    when 2 then icon = "fa fa-clock-o";
+    when 3 then icon = "fa fa-check";
+    else icon = ""
+  return new Handlebars.SafeString("");
+
 # Currency helper in order to have a vizualization for the currency.
 ## Todo: reenable when number format is needed..
 ###Handlebars.registerHelper "currency",(property, options) ->


Workspace: Fixed toggleFolder callback.

Workspace: Fixed toggleFolder callback.

--- client/landing/app/CommonViews/workspace/panes/collaborativefinderpane.coffee
+++ client/landing/app/CommonViews/workspace/panes/collaborativefinderpane.coffee
@@ -27,8 +27,6 @@
     @finder = @finderController.getView()

     @workspaceRef.on "value", (snapshot) =>
-      return unless snapshot.val()
-
       log "everything is something happened in host filetree", snapshot.name(), snapshot.val()

       clientData = snapshot.val()?.ClientWantsToInteractWithRemoteFileTree
@@ -104,7 +102,7 @@
     @getDelegate().emit "FileTreeInteractionDone", @getSnapshot()

   toggleFolder: (nodeView, callback) ->
-    super nodeView, @syncInteraction()
+    super nodeView, @bound "syncInteraction"

   openFile: (nodeView) ->
     return unless nodeView