Clean models.coffee

Clean models.coffee

--- admin/static/coffee/models.coffee
+++ admin/static/coffee/models.coffee
@@ -78,474 +78,6 @@

 class Log extends Backbone.Model

-class Namespace extends Backbone.Model
-    initialize: ->
-        # Add a computed shards property for convenience and metadata
-        @compute_shards()
-    compute_shards: =>
-        @.set 'computed_shards', new DataUtils.Shards [],@
-
-    regex:
-        string: /^S/
-        number: /^N.*%23(\-?[0-9]*(\.)?[0-9]*$)/
-        date: /^(PTIME).*%23\-?([0-9]*(\.)?[0-9]*$)/
-    transform_key: (a) ->
-        if a is null
-            a_new = a
-        else if a is ""
-            a_new = -Infinity
-        else if typeof a is "string"
-            if @regex.string.test(a) is true
-                a_new = a.slice(1)
-            else if @regex.number.test(a) is true
-                s =
-                    a.slice(a.indexOf("%23")+3)
-                if _.isNaN(parseFloat(s)) is false
-                    a_new = parseFloat(s)
-            else if @regex.date.test(a) is true
-                a_new = new Date(parseFloat(a.slice(a.indexOf("%23")+3)))
-        return a_new
-
-
-    compare_keys: (a, b) =>
-        #return 1 if a > b
-        a_new = @transform_key(a)
-        b_new = @transform_key(b)
-
-        if typeof a_new is 'number' and typeof b_new is 'number'
-            return a_new-b_new
-        else if (typeof a_new is 'string' and typeof b_new is 'string') or (a_new instanceof Date and b_new instanceof Date)
-            if a_new > b_new
-                return 1
-            else if a_new < b_new
-                return -1
-            else
-                return 0
-        else if typeof a_new isnt typeof b_new
-            if typeof a_new is 'number' and typeof b_new is 'string'
-                return -1
-            else if typeof a_new is 'number' and b_new instanceof Date
-                return -1
-            else if a_new instanceof Date and typeof b_new is 'string'
-                return -1
-            else if typeof a_new is 'string' and typeof b_new is 'number'
-                return 1
-            else if a_new instanceof Date and typeof b_new is 'number'
-                return 1
-            else if typeof a_new is 'string' and b_new instanceof Date
-                return 1
-
-            else if a_new is null and b_new isnt null
-                return 1
-            else if b_new is null and a_new isnt null
-                return -1
-        return 0
-
-
-    load_key_distr: =>
-        $.ajax
-            processData: false
-            url: "ajax/distribution?namespace=#{@get('id')}&depth=2"
-            type: 'GET'
-            contentType: 'application/json'
-            success: (distr_data) =>
-                # Cache the data
-                # Sort the keys and cache that too
-                distr_keys = []
-                for key, count of distr_data
-                    distr_keys.push(key)
-                distr_keys.sort(@compare_keys)
-
-                @set('key_distr_sorted', distr_keys)
-                @set('key_distr', distr_data)
-                @timeout = setTimeout @load_key_distr, 5000
-            error: =>
-                if namespaces.get(@get('id'))?
-                    @timeout = setTimeout @load_key_distr, 1000
-
-    clear_timeout: =>
-        if @timeout?
-            clearTimeout @timeout
-
-    # Some shard helpers
-    compute_shard_rows_approximation: (shard) =>
-        # first see if we can grab the distr data
-        shard = $.parseJSON(shard)
-        if not @get('key_distr')? or not @get('key_distr_sorted')?
-            return null
-
-        # some basic initialization
-        start_key = shard[0]
-        end_key = shard[1]
-
-        # TODO: we should interpolate once we will have decided how to order different type of keys
-
-        # find the first key greater than the beginning of our shard
-        # and keep summing until we get past our shard boundary.
-        count = 0
-
-        for key in @get('key_distr_sorted')
-            # TODO Might be unsafe when comparing string and integers. Need to be checked when the back end will have decided what to do.
-            if @compare_keys(key, start_key) >= 0 or start_key is ''
-                if @compare_keys(key, end_key) < 0 or end_key is null
-                    if @get('key_distr')[key]?
-                        count += @get('key_distr')[key]
-
-        return count.toString() # Return string since [] == 0 return true (for Handlebars)
-
-    # Is x between the lower and upper splitpoints (the open interval) for the given index?
-    splitpoint_between: (shard_index, sp) =>
-        all_sps = @.get('splitpoints')
-        return (shard_index == 0 || all_sps[shard_index - 1] < sp) && (shard_index == all_sps.length || sp < all_sps[shard_index])
-
-    # Computing btree stats based on machine stats
-    get_stats: =>
-        __s =
-            keys_read: 0
-            keys_set: 0
-        _.each DataUtils.get_namespace_machines(@get('id')), (mid) =>
-            _m = machines.get(mid)
-            if _m?
-                _s = _m.get_stats()[@get('id')]
-            if _s?.serializers?
-                keys_read = 0
-                keys_set = 0
-                for serializer_id of _s.serializers
-                    serializer = _s.serializers[serializer_id]
-                    if serializer['btree-primary']?
-                        keys_read = parseFloat(serializer['btree-primary'].keys_read)
-                        keys_set = parseFloat(serializer['btree-primary'].keys_set)
-                        if not isNaN(keys_read)
-                            __s.keys_read += keys_read
-                        if not isNaN(keys_set)
-                            __s.keys_set += keys_set
-        return __s
-
-    get_stats_for_performance: =>
-        # Ops/sec stats
-        __s =
-            keys_read: 0
-            keys_set: 0
-            global_disk_space: 0
-        _s = this.get_stats()
-        if _s?
-            __s.keys_read += _s.keys_read
-            __s.keys_set += _s.keys_set
-        # CPU, mem, disk
-        num_machines_in_namespace = 0
-        for machine in machines.models
-            if machine.get('stats')? and @get('id') of machine.get('stats') and machine.is_reachable
-                num_machines_in_namespace++
-                __s.global_disk_space += machine.get_used_disk_space()
-        return __s
-
-    get_durability: =>
-        if @get('hard_durability')?
-            return @get('hard_durability')
-        else
-            for dc, ack of @get('ack_expectations')
-                @set('hard_durability', ack.hard_durability)
-                return @get('hard_durability')
-            @set('hard_durability', true)
-            return @get('hard_durability')
-
-class Datacenter extends Backbone.Model
-    # Compute the number of machines not used by other datacenters for one namespace
-    compute_num_machines_not_used_by_other_datacenters: (namespace) =>
-        max_machines = machines.length
-        for datacenter_id of namespace.get('replica_affinities')
-            # The second condition is to make sure that the datacenter does exist (and was not deleted)
-            if datacenter_id isnt @get('id') and datacenters.get(datacenter_id)?
-                max_machines -= namespace.get('replica_affinities')[datacenter_id]
-        if namespace.get('primary_uuid') isnt @get('id')
-            max_machines -= 1
-
-        return max_machines
-
-
-    get_machines: =>
-        machines.filter (machine) => machine.get('datacenter_uuid') is @get('id')
-    get_stats: =>
-        stats =
-            dc_disk_space: 0
-        for machine in machines.models
-            if machine.get('datacenter_uuid') is @get('id')
-                stats.dc_disk_space += machine.get_used_disk_space()
-        return stats
-
-    get_stats_for_performance: =>
-        # Ops/sec stats
-        __s =
-            keys_read: 0
-            keys_set: 0
-            global_disk_space: 0
-        for namespace in namespaces.models
-            if not namespace.get('blueprint')? or not namespace.get('blueprint').peers_roles?
-                continue
-            for machine_id of namespace.get('blueprint').peers_roles
-                machine = machines.get(machine_id)
-                if not machine?
-                    continue
-                has_data = false
-                if machine.get('datacenter_uuid') is @get('id') and namespace.get('blueprint').peers_roles[machine_id]?
-                    for shard_key of namespace.get('blueprint').peers_roles[machine_id]
-                        if namespace.get('blueprint').peers_roles[machine_id][shard_key] isnt 'role_nothing'
-                            has_data = true
-                            break
-                    if has_data is true
-                        break
-            if has_data is true
-                _s = namespace.get_stats()
-                if not _s?
-                    continue
-                __s.keys_read += _s.keys_read
-                __s.keys_set += _s.keys_set
-
-        # CPU, mem, disk
-        num_machines_in_datacenter = 0
-        for machine in machines.models
-            if machine.get('datacenter_uuid') is @get('id') and machine.is_reachable
-                num_machines_in_datacenter++
-                __s.global_disk_space += machine.get_used_disk_space()
-        return __s
-
-
-class Machine extends Backbone.Model
-    get_stats: =>
-        stats = @get('stats')
-        if not stats?
-            stats = {}
-        if not stats.proc?
-            stats.proc = {}
-        return stats
-
-    get_used_disk_space: =>
-        machine_disk_space = 0
-        for nid, value of @get_stats()
-            # Check if nid is actually a namespace id
-            if namespaces.get(nid)?
-                stats = value.cache
-                if stats?
-                    machine_disk_space += parseInt(stats.block_size) * parseInt(stats.blocks_total)
-        return machine_disk_space
-
-    get_stats_for_performance: =>
-        stats_full = @get_stats()
-        __s =
-            keys_read: 0
-            keys_set: 0
-            global_disk_space: parseInt(@get_used_disk_space())
-
-        for namespace in namespaces.models
-            _s = namespace.get_stats()
-            if not _s?
-                continue
-
-            if namespace.get('id') of stats_full
-                __s.keys_read += _s.keys_read
-                __s.keys_set += _s.keys_set
-        # CPU, mem, disk
-        return __s
-
-    is_reachable: =>
-        reachable = directory.get(@get('id'))?
-        return reachable
-
-    # Returns a filtered list of namespaces whose shards use this server as master
-    is_master_for_namespaces: =>
-        namespaces.filter (namespace) =>
-            for machine_uuid, peer_roles of namespace.get('blueprint').peers_roles
-                if machine_uuid is @get('id')
-                    for shard, role of peer_roles
-                        if role is 'role_primary'
-                            return true
-
-    # Return an object with warnings if a server cannot be moved
-    get_data_for_moving: =>
-        data = {}
-        dc_uuid = @get('datacenter_uuid')
-        # If the server is in Universe, we can move it since it's always safe
-        if dc_uuid isnt universe_datacenter.get('id')
-            # Count the number of servers in the same datacenter as this server
-            num_machines_in_dc = 0
-            machines.each (machine) => num_machines_in_dc++ if dc_uuid is machine.get('datacenter_uuid')
-
-            # Looking for critical issues (long term loss of availability)
-            # (look for namespaces where the datacenter is primary and with just one machine)
-            namespaces_with_critical_issue = []
-            # Find the tables that won't have sufficient replicas if we unassign this server
-            namespaces_with_unsatisfiable_goals = []
-            for namespace in namespaces.models
-                # Does the datacenter of this server have responsibilities for the namespace?
-                if dc_uuid of namespace.get('replica_affinities')
-                    num_replicas = namespace.get('replica_affinities')[dc_uuid]
-                    # If the datacenter acts as primary for the namespace, bump the replica count by one
-                    num_replicas++ if namespace.get('primary_uuid') is dc_uuid
-
-                # There will be a unsatisfiable goals if we unassign the machine
-                # We take for granted that acks < num_replicas. We might want to increase the safety.
-                if num_machines_in_dc <= num_replicas
-                    namespaces_with_unsatisfiable_goals.push
-                        id: namespace.get('id')
-                        name: namespace.get('name')
-
-                # The last machine in the datacenter is primary so no new master can be elected if the master is moved
-                if num_machines_in_dc is 1 and dc_uuid is namespace.get('primary_uuid')
-                    namespaces_with_critical_issue.push
-                        id: namespace.get('id')
-                        name: namespace.get('name')
-
-
-                # That's all, if the machine that's going to replace universe is already working for universe,
-                # it will start working for the datacenter and the unassigned one will start working for universe
-            if namespaces_with_unsatisfiable_goals.length > 0
-                data = _.extend data,
-                    has_warning: true
-                    namespaces_with_unsatisfiable_goals: namespaces_with_unsatisfiable_goals
-                    num_namespaces_with_unsatisfiable_goals: namespaces_with_unsatisfiable_goals.length
-            if namespaces_with_critical_issue.length > 0
-                data = _.extend data,
-                    namespaces_with_critical_issue: namespaces_with_critical_issue
-                    num_namespaces_with_critical_issue: namespaces_with_critical_issue.length
-
-            if namespaces_with_unsatisfiable_goals.length > 0 or namespaces_with_critical_issue.length > 0
-                data = _.extend data,
-                    has_warning: true
-                    datacenter_id: @get('datacenter_uuid')
-                    datacenter_name: datacenters.get(@get('datacenter_uuid')).get('name')
-                    machine_id: @get('id')
-                    machine_name: @get('name')
-        return data
-
-class LogEntry extends Backbone.Model
-    get_iso_8601_timestamp: => new Date(@.get('timestamp') * 1000)
-    get_formatted_message: =>
-        msg = @.get('message')
-
-
-class IssueRedundancy extends Backbone.Model
-
-class Progress extends Backbone.Model
-
-# this is a hook into the directory
-class MachineAttributes extends Backbone.Model
-
-class ConnectionStatus extends Backbone.Model
-
-# This is a computed model for the cluster (mainly for stats right
-# now)
-class ComputedCluster extends Backbone.Model
-    get_stats: =>
-        # Ops/sec stats
-        __s =
-            keys_read: 0
-            keys_set: 0
-            global_disk_space: 0
-        for namespace in namespaces.models
-            _s = namespace.get_stats()
-            if not _s?
-                continue
-            __s.keys_read += _s.keys_read
-            __s.keys_set += _s.keys_set
-        # CPU, mem, disk
-        for m in machines.models
-            __s.global_disk_space += m.get_used_disk_space()
-
-        return __s
-
-
-
-
-# Collections for Backbone.js
-
-class Namespaces extends Backbone.Collection
-    model: Namespace
-    name: 'Namespaces'
-
-class Datacenters extends Backbone.Collection
-    model: Datacenter
-    name: 'Datacenters'
-
-class Machines extends Backbone.Collection
-    model: Machine
-    name: 'Machines'
-
-
-# We compare the directory and the blueprints to detect redundancy problems
-class IssuesRedundancy extends Backbone.Collection
-    model: IssueRedundancy
-    num_replicas : 0
-    initialize: ->
-        directory.on 'all', @compute_redundancy_errors
-        namespaces.on 'all', @compute_redundancy_errors
-
-
-
-    convert_activity: (role) ->
-        switch role
-            when 'role_secondary' then return 'secondary_up_to_date'
-            when 'role_nothing' then return 'nothing'
-            when 'role_primary' then return 'primary'
-
-    compute_redundancy_errors: =>
-        issues_redundancy_new = []
-
-        num_replicas = 0
-
-
-        directory_by_namespaces = DataUtils.get_directory_activities_by_namespaces()
-        for namespace in namespaces.models
-            namespace_id = namespace.get('id')
-            blueprint = namespace.get('blueprint').peers_roles
-            for machine_id of blueprint
-                if machines.get(machine_id)? and machines.get(machine_id).get('name')?
-                    machine_name = machines.get(machine_id).get('name')
-                else # can happen if machines is not loaded yet
-                    machine_name = machine_id
-
-                for key of blueprint[machine_id]
-                    value = blueprint[machine_id][key]
-                    if value is "role_primary" or value is "role_secondary"
-                        num_replicas++
-
-                        if !(directory_by_namespaces?) or !(directory_by_namespaces[namespace_id]?) or !(directory_by_namespaces[namespace_id][machine_id]?)
-                            issue_redundancy_param =
-                                machine_id: machine_id
-                                machine_name: machine_name
-                                namespace_uuid: namespace_id
-                                namespace_name: namespace.get('name')
-                                shard: key
-                                blueprint: value
-                                directory: 'not_found'
-                            issue_redundancy = new IssueRedundancy issue_redundancy_param
-                            issues_redundancy_new.push issue_redundancy
-                        else if directory_by_namespaces[namespace_id][machine_id][key] != @convert_activity(value)
-                            issue_redundancy_param =
-                                machine_id: machine_id
-                                machine_name: machine_name
-                                namespace_uuid: namespace_id
-                                namespace_name: namespace.get('name')
-                                shard: key
-                                blueprint: value
-                                directory: directory_by_namespaces[namespace_id][machine_id][key]
-                            issues_redundancy_new.push new IssueRedundancy issue_redundancy_param
-
-
-        if issues_redundancy_new.length > 0 or issues_redundancy_new.length isnt @.length
-            @.reset(issues_redundancy_new)
-
-        if num_replicas isnt @num_replicas
-            @num_replicas = num_replicas
-            @.trigger 'reset'
-
-class ProgressList extends Backbone.Collection
-    model: Progress
-    url: 'ajax/progress'
-
-# hook into directory
-class Directory extends Backbone.Collection
-    model: MachineAttributes
-    url: 'ajax/directory'

 # This module contains utility functions that compute and massage
 # commonly used data.
@@ -555,525 +87,12 @@
     # convenience, it's useful to pick data from several of these models: these
     # are computed models (and computed collections).

-    # Computed shard for a particular namespace: computed for convenience and for extra metadata
-    # Arguments:
-    #   shard: element of the namespace shards list: Namespace.get('shards')
-    #   namespace: namespace that this shard belongs to
-    # Computed attributes of a shard:
-    #   shard_boundaries: JSON string representing the shard boundaries
-    #   primary_uuid: machine uuid that is the master for this shard
-    #   secondary_uuids: list of machine_uuids that are the secondaries for this shard
-    class @Shard extends Backbone.Model
-        get_primary_uuid: => DataUtils.get_shard_primary_uuid @namespace.get('id'), @shard
-        get_secondary_uuids: => DataUtils.get_shard_secondary_uuids @namespace.get('id'), @shard
-
-        initialize: (shard, namespace) =>
-            @shard = shard
-            @namespace = namespace
-
-            @.set 'shard_boundaries', @shard
-            @.set 'primary_uuid', @get_primary_uuid()
-            @.set 'secondary_uuids',  @get_secondary_uuids()
-
-            namespace.on 'change:blueprint', @set_uuids
-
-        set_uuids: =>
-            new_primary_uuid = @get_primary_uuid()
-            new_secondary_uuids = @get_secondary_uuids()
-            @.set 'primary_uuid', new_primary_uuid if @.get('primary_uuid') isnt new_primary_uuid
-            @.set 'secondary_uuids', new_secondary_uuids if not _.isEqual @.get('secondary_uuids'), new_secondary_uuids
-
-        destroy: =>
-            namespace.on 'change:blueprint', @set_uuids
-
-    # The Shards collection maintains the computed shards for a given namespace.
-    # It will observe for any changes in the sharding scheme (or if the
-    # namespace has just been added) and maintains the list of computed shards,
-    # rebuilding if necessary.
-    class @Shards extends Backbone.Collection
-        model: DataUtils.Shard
-
-        initialize: (models, namespace) =>
-            @namespace = namespace
-            @namespace.on 'change:shards', @compute_shards_without_args
-            @namespace.on 'add', @compute_shards_without_args
-
-        compute_shards_without_args: =>
-            @compute_shards @namespace.get('shards')
-
-        compute_shards: (shards) =>
-            new_shards = []
-            for shard in shards
-                new_shards.push new DataUtils.Shard shard, @namespace
-            @.reset new_shards
-
-        destroy: =>
-            @namespace.off 'change:shards', @compute_shards_without_args
-            @namespace.off 'add', @compute_shards_without_args
-
-
     @stripslashes = (str) ->
         str=str.replace(/\\'/g,'\'')
         str=str.replace(/\\"/g,'"')
         str=str.replace(/\\0/g,"\x00")
         str=str.replace(/\\\\/g,'\\')
         return str
-
-    @get_machine_reachability = (machine_uuid) ->
-        reachable = directory.get(machine_uuid)?
-        if not reachable
-            _m = machines.get(machine_uuid)
-            if _m?
-                last_seen = _m.get('last_seen')
-            else
-                last_seen = null
-            if last_seen
-                last_seen = $.timeago(new Date(parseInt(last_seen) * 1000))
-        json =
-            reachable: reachable
-            last_seen: last_seen
-        return json
-
-    @get_datacenter_reachability = (datacenter_uuid) ->
-        total = (_.filter machines.models, (m) => m.get('datacenter_uuid') is datacenter_uuid).length
-        reachable = (_.filter directory.models, (m) =>
-            _m = machines.get(m.get('id'))
-            if _m?
-                _m.get('datacenter_uuid') is datacenter_uuid
-            else
-                return false
-            ).length
-
-        if reachable == 0 and total > 0
-            for machine in machines.models
-                if machine.get('datacenter_uuid') is datacenter_uuid
-                    _last_seen = machine.get('last_seen')
-                    if last_seen
-                        if _last_seen > last_seen
-                            last_seen = _last_seen
-                    else
-                        last_seen = _last_seen
-            last_seen = $.timeago(new Date(parseInt(last_seen) * 1000))
-
-        json =
-            total: total
-            reachable: reachable
-            last_seen: last_seen
-
-        return json
-
-    @get_shard_primary_uuid = (namespace_uuid, shard) ->
-        for machine_uuid, peers_roles of namespaces.get(namespace_uuid).get('blueprint').peers_roles
-            for _shard, role of peers_roles
-                if shard.toString() is _shard.toString() and role is 'role_primary'
-                    return machine_uuid
-        return null
-
-    @get_shard_secondary_uuids = (namespace_uuid, shard) ->
-        # We're organizing secondaries per datacenter
-        secondaries = {}
-        for machine_uuid, peers_roles of namespaces.get(namespace_uuid).get('blueprint').peers_roles
-            if !machines.get(machine_uuid)? # In case the machine is dead
-                continue
-
-            datacenter_uuid = machines.get(machine_uuid).get('datacenter_uuid')
-            for _shard, role of peers_roles
-                if shard.toString() is _shard.toString() and role is 'role_secondary'
-                    if not secondaries[datacenter_uuid]?
-                        secondaries[datacenter_uuid] = []
-                    secondaries[datacenter_uuid][secondaries[datacenter_uuid].length] = machine_uuid
-        return secondaries
-
-    @get_ack_expectations = (namespace_uuid, datacenter_uuid) ->
-        namespace = namespaces.get(namespace_uuid)
-        acks = namespace?.get('ack_expectations')?[datacenter_uuid]?.expectation
-        if acks?
-            return acks
-        else
-            return 0
-
-    @get_replica_affinities = (namespace_uuid, datacenter_uuid) ->
-        namespace = namespaces.get(namespace_uuid)
-        if datacenter_uuid is universe_datacenter.get('id')
-            datacenter = universe_datacenter
-        else
-            datacenter = datacenters.get(datacenter_uuid)
-        affs = namespace?.get('replica_affinities')?[datacenter?.get('id')]
-        if affs?
-            return affs
-        else
-            return 0
-
-    @get_datacenter_machines = (datacenter_uuid) ->
-        return _.filter(machines.models, (m) -> m.get('datacenter_uuid') is datacenter_uuid)
-
-    # Return a list of machines relevant to a namespace
-    @get_namespace_machines = (namespace_uuid) ->
-        mids = []
-        _n = namespaces.get(namespace_uuid)
-        if not _n
-            return []
-        _n = _n.get('blueprint').peers_roles
-        for mid, roles of _n
-            mids.push(mid)
-        return mids
-
-
-    # Cache the value of the refactored directory_activities
-    @directory_activities = null
-    @directory_activities_by_namespaces = null
-    @reset_directory_activities = ->
-        @directory_activities = null
-        @directory_activities_by_namespaces = null
-
-    # Organizes the directory as a map of activity ids
-    @get_directory_activities = ->
-        if not @directory_activities?
-            @directory_activities = {}
-            for machine in directory.models
-                bcards = machine.get('rdb_namespaces')['reactor_bcards']
-                for namespace_id, activity_map of bcards
-                    activity_map = activity_map['activity_map']
-                    for activity_id, activity of activity_map
-                        @directory_activities[activity_id] =
-                            value: activity
-                            machine_id: machine.get('id')
-                            namespace_id: namespace_id
-        return @directory_activities
-
-
-    @get_directory_activities_by_namespaces = ->
-        #TODO We report the status of the "last" internal hash shard as being the status of the whole shard
-        #We should eventually report the status of all internal shards
-        if not @directory_activities_by_namespaces?
-            @directory_activities_by_namespaces = {}
-            for machine in directory.models
-                bcards = machine.get('rdb_namespaces')['reactor_bcards']
-                for namespace_id, activity_map of bcards
-                    if not @directory_activities_by_namespaces[namespace_id]?
-                        @directory_activities_by_namespaces[namespace_id] = {}
-
-                    activity_map = activity_map['activity_map']
-                    for activity_id, activity of activity_map
-                        if not @directory_activities_by_namespaces[namespace_id][machine.get('id')]?
-                            @directory_activities_by_namespaces[namespace_id][machine.get('id')] = {}
-                        @directory_activities_by_namespaces[namespace_id][machine.get('id')][activity[0]] = activity[1]['type']
-
-        return @directory_activities_by_namespaces
-
-    # Computes backfill progress for a given (namespace, shard,
-    # machine) tripple. All arguments correspond to the objects that
-    # are *receiving* data.
-    @get_backfill_progress = (namespace_uuid, shard, machine_uuid) ->
-        activity_map = @get_directory_activities()
-        _output_json = []
-        if typeof(shard) isnt 'string'
-            shard = JSON.stringify(shard)
-
-        # grab the right machine from the progress list
-        progress = progress_list.get(machine_uuid)
-        if not progress?
-            return null
-
-        # next level of organization is by namespaces, grab the
-        # one we need
-        progress = progress.get(namespace_uuid)
-        if not progress?
-            return null
-
-        # The level of organization after that is by activity
-        # ids. Each activity corresponds to the shard we're
-        # backfilling into.
-        for activity_uuid, shard_map of progress
-            # Grab the activity from the activity map and break it up
-            # into some useful information.
-            activity = activity_map[activity_uuid]
-            if not activity?
-                # This probably means directory data hasn't caught up
-                # with the progress data on the client yet.
-                return null
-            activity_type = activity.value[1].type
-            into_shard = activity.value[0]
-
-            # Check that we're on the right shard (our C++ and JS JSON
-            # stringifiers have different policies with respect to
-            # spaces, so removing spaces here. We really need a
-            # standard way to compare shards - TODO here and in many
-            # many other places)
-            if into_shard.replace(/\s/g, '') isnt shard.replace(/\s/g, '')
-                continue
-
-            # Collect activity info about all the shards we're
-            # replicating from
-            senders_activity_id = []
-            if activity_type is 'secondary_backfilling'
-                senders_activity_id.push(activity.value[1].backfiller.activity_id)
-            else if activity_type is 'primary_when_safe'
-                for backfiller in activity.value[1].backfillers
-                    senders_activity_id.push(backfiller.activity_id)
-
-
-            # We now have a map from one shard (union of all shards
-            # we're replicating from), to an array of progress
-            # info. Grab these.
-            union_shard = null
-            progress_info_arr = null
-            for _us, _pia of shard_map
-                union_shard = _us
-                progress_info_arr = _pia
-                break
-
-            # We now have two arrays: senders and progress infos
-            # (which should theoretically be the same size). Combine
-            # them into data points, push each datapoint onto
-            # _output_json. We'll aggregate after the loop is done.
-            for i in [0..(progress_info_arr.length - 1)]
-                progress_info = progress_info_arr[i]
-                sender = activity_map[senders_activity_id[i]]
-                ratio_available = typeof(progress_info) isnt 'string'
-                json =
-                    replicated_blocks:    if ratio_available then progress_info[0] else null
-                    total_blocks:         if ratio_available then progress_info[1] else null
-                    block_info_available: ratio_available and progress_info[0] isnt -1 and progress_info[1] isnt -1
-                    ratio_available:      ratio_available
-                    machine_id:           sender.machine_id #TODO can fail
-                _output_json.push json
-
-        # Make sure there is stuff to report
-        if _output_json.length is 0
-            return null
-
-        # Now we can aggregate the results for this shard by summing
-        # progress from every shard it's replicating from
-        agg_start =
-            total_blocks: -1
-            replicated_blocks: -1
-            block_info_available: false
-            ratio_available: false
-            backfiller_machines: []
-        agg_json = _.reduce(_output_json, ((agg, val) ->
-            if val.block_info_available
-                agg.total_blocks      = 0 if agg.total_blocks is -1
-                agg.replicated_blocks = 0 if agg.replicated_blocks is -1
-                agg.total_blocks      += val.total_blocks
-                agg.replicated_blocks += val.replicated_blocks
-                agg.block_info_available = true
-            if val.ratio_available
-                agg.ratio_available = true
-            if typeof(val.machine_id) is 'string'
-                agg.backfiller_machines.push
-                    machine_id:   val.machine_id
-                    machine_name: machines.get(val.machine_id).get('name')
-            return agg
-            ), agg_start)
-        # Phew, final output
-        output_json =
-            ratio:             agg_json.replicated_blocks / agg_json.total_blocks
-            percentage:        Math.round(agg_json.replicated_blocks / agg_json.total_blocks * 100)
-        output_json = _.extend output_json, agg_json
-
-        return output_json
-
-    # Computes aggregate backfill progress for a namespace. The
-    # datacenter argument can optionally limit the aggreggate values
-    # to the datacenter.
-    @get_backfill_progress_agg = (namespace_uuid, datacenter_uuid) ->
-        # Find a list of machines we care about
-        namespace_machines = @get_namespace_machines(namespace_uuid)
-        if datacenter_uuid?
-            datacenter_machines = _.map @get_datacenter_machines(datacenter_uuid), (m) -> m.get('id')
-        else
-            datacenter_machines = namespace_machines
-        machines = _.intersection namespace_machines, datacenter_machines
-        # Collect backfill progress for each machine
-        backfills = []
-        for machine_uuid in machines
-            for shard in namespaces.get(namespace_uuid).get('shards')
-                backfills.push(@get_backfill_progress(namespace_uuid, shard, machine_uuid))
-        backfills = _.without backfills, null
-        if backfills.length is 0
-            return null
-        # Aggregate backfill results
-        agg_start =
-            total_blocks: -1
-            replicated_blocks: -1
-            block_info_available: false
-            ratio_available: false
-        agg_json = _.reduce(backfills, ((agg, val) ->
-            if not val?
-                return agg
-            if val.block_info_available
-                agg.total_blocks      = 0 if agg.total_blocks is -1
-                agg.replicated_blocks = 0 if agg.replicated_blocks is -1
-                agg.total_blocks      += val.total_blocks
-                agg.replicated_blocks += val.replicated_blocks
-                agg.block_info_available = true
-            if val.ratio_available
-                agg.ratio_available = true
-            return agg
-            ), agg_start)
-        # Phew, final output
-        output_json =
-            ratio:             agg_json.replicated_blocks / agg_json.total_blocks
-            percentage:        Math.round(agg_json.replicated_blocks / agg_json.total_blocks * 100)
-        output_json = _.extend output_json, agg_json
-
-        return output_json
-
-    # datacenter_uuid is optional and limits the information to a specific datacenter
-    # If datacenter is not defined, we get the status for all datacenters
-    # TODO We should clean this function. We don't need so much data most of the time
-    @get_namespace_status = (namespace_uuid, datacenter_uuid) ->
-        namespace = namespaces.get(namespace_uuid)
-        json =
-            nshards: 0
-            nreplicas: 0
-            nashards: 0 # Number of available shards
-            nareplicas: 0 # Number of available replicas
-            reachability: 'Live' # 'Live' if we can write on the table, 'Down' if we can't
-
-        # If we can't see the namespace...
-        if not namespace?
-            return null
-
-        # machine and datacenter counts
-        _machines = []
-        _datacenters = []
-
-        for machine_uuid, role of namespace.get('blueprint').peers_roles
-            if !machines.get(machine_uuid)? # If the machine is dead
-                continue
-            # We filter machines. If
-            if datacenter_uuid? and machines.get(machine_uuid)?.get('datacenter_uuid') isnt datacenter_uuid
-                continue
-            peer_accessible = directory.get(machine_uuid)
-            machine_active_for_namespace = false
-            for shard, role_name of role
-                if role_name is 'role_primary'
-                    machine_active_for_namespace = true
-                    json.nshards += 1
-                    json.nreplicas += 1
-                    if peer_accessible?
-                        json.nashards += 1
-                        json.nareplicas += 1
-                if role_name is 'role_secondary'
-                    machine_active_for_namespace = true
-                    json.nreplicas += 1
-                    if peer_accessible?
-                        json.nareplicas += 1
-            if machine_active_for_namespace
-                _machines.push machine_uuid
-                if not datacenter_uuid? or datacenter_uuid isnt universe_datacenter.get('id') # If datacenter_uuid is defined, we don't want to count universe
-                    _datacenters.push machines.get(machine_uuid).get('datacenter_uuid')
-
-        json.nmachines = _.uniq(_machines).length
-        json.ndatacenters = _.uniq(_datacenters).length
-
-        # Getting the directoy per namespace
-        directory_by_namespaces = DataUtils.get_directory_activities_by_namespaces()
-
-        # We now want to know if the table is available or not
-        # The table is available only if these two conditions are met:
-        # - A write can succeed, which means that for each shard, # the number of available
-        #   replicas in each datacenter should be at least the number of acks required in this datacenter
-        # - The master for this shard is available
-
-        # This is an object that will store the number of acks for each shard and datacenter
-        # Ex: _shard_required = {
-        #   ['', 'foo']: {
-        #       universe_id: 0,
-        #       datacenter_1: 3,
-        #       datacenter_2: 1
-        #   }
-        #   ['foo', null]: {
-        #       universe_id: 0,
-        #       datacenter_1: 3,
-        #       datacenter_2: 1
-        #  }
-        # }
-        _shard_required = {}
-
-        # An object that will store for each shard a boolean that represents whether its master is available or not
-        # Ex: _shard_has_master = {
-        #   ['', 'foo']: Boolean
-        #   ['foo', null]: Boolean
-        # }
-        _shard_has_master = {}
-
-
-        # Initialize all the two previous variable
-        for shard in namespace.get('shards')
-            _shard_required[shard] = {} # We will add the requirement per datacenter in the next loop
-            _shard_has_master[shard] = false # We haven't seen the master yet, so we consider it not available
-
-        # For each shard, we save the number of write required (per datacenter) to have a write acknowledge (in this datacenter)
-        for datacenter, value of namespace.get('ack_expectations')
-            for shard of _shard_required
-                _shard_required[shard][datacenter] = value.expectation
-
-        # Checking blueprint (goals) vs directory (real state of the cluster)
-        # For each shard assignments, we are going to
-        #     - Make sure there is a master
-        #     - Decrement the number of remaining ack we need to be available (per shard)
-        blueprint = namespace.get('blueprint').peers_roles
-        for machine_id of blueprint
-            if json.reachability isnt 'Live'
-                break
-
-            machine = machines.get(machine_id)
-            if not machine?
-                continue
-
-            datacenter_id = machine.get('datacenter_uuid')
-            machine_name = machine.get('name')
-
-            for shard, role of blueprint[machine_id]
-                if json.reachability isnt 'Live'
-                    break
-
-                if role is "role_primary" # This machine is the master for the current shard
-                    if directory_by_namespaces?[namespace.get('id')]?[machine_id]?[shard] is "primary"
-                        _shard_has_master[shard] = true
-
-                        if not _shard_required[shard]?[datacenter_id]?
-                            _shard_required[shard]?[datacenter_id] = 0
-                        _shard_required[shard]?[datacenter_id] -= 1
-                    else
-                        json.reachability = 'Down'
-                else if role is "role_secondary" # This machine is a secondary for the current shard
-                    if directory_by_namespaces?[namespace.get('id')]?[machine_id]?[shard] is "secondary_up_to_date"
-                        # The shard can be defined in shards but not in the blueprint if there were not yet reprinted
-                        if not _shard_required[shard]?[datacenter_id]?
-                            _shard_required[shard]?[datacenter_id] = 0
-                        _shard_required[shard]?[datacenter_id] -= 1
-
-        # By default we consider a machine to hold a replica because of a per datacenter requirement
-        # If a datacenter has more responsabilities than required, it's because some of these machines
-        # are working for the whole cluster (universe), so we move them to universe
-        for shard of _shard_required
-            for datacenter of _shard_required[shard]
-                if _shard_required[shard][datacenter] < 0
-                    _shard_required[shard][universe_datacenter.get('id')] += _shard_required[shard][datacenter]
-                    _shard_required[shard][datacenter] = 0
-
-
-        # Make sure that all shards have a master
-        if json.reachability = 'Live'
-            for shard of _shard_has_master
-                if _shard_has_master[shard] is false
-                    json.reachability = 'Down'
-                    break
-
-        # Make sure that all shards have enough replica to satisfy a write
-        if json.reachability = 'Live'
-            for shard of _shard_required
-                for datacenter of _shard_required[shard]
-                    if _shard_required[shard][datacenter] > 0
-                        json.reachability = 'Down'
-                    break
-
-        return json
-

     @is_integer = (data) ->
         return data.search(/^\d+$/) isnt -1


Fixed grunt watch

Fixed grunt watch

--- js-library/Gruntfile.coffee
+++ js-library/Gruntfile.coffee
@@ -58,7 +58,7 @@
       all: ['test/*.html']
     watch:
       files: ['src/js/**/*', 'src/css/**/*', 'test/**/*', 'grunt.js', 'libs/**/*.js', 'plugins/**/*.js', 'wrap/*.js']
-      tasks: 'jshint qunit concat less copy'
+      tasks: ['jshint', 'qunit', 'concat', 'less', 'copy']
     jshint:
       all: ['grunt.js', 'src/js/**/*.js', 'test/**/*.js']
       options:


Fixed the copy task in grunt

Fixed the copy task in grunt

--- js-library/Gruntfile.coffee
+++ js-library/Gruntfile.coffee
@@ -51,8 +51,8 @@
         files: [
           { src: ['dist/css/basic.css'],          dest: 'dist/server/css/basic.css' }
           { src: ['dist/easyXDM/easyXDM.min.js'], dest: 'public/easyXDM.min.js' }
-          { src: ['src/images/**'],               dest: 'dist/server/images/',      filter: 'isFile' }
-          { src: ['src/images/**'],               dest: 'dist/images/',             filter: 'isFile' }
+          { src: ['**'],                          dest: 'dist/server/images/', expand: true, cwd: 'src/images/', filter: 'isFile' }
+          { src: ['**'],                          dest: 'dist/images/',        expand: true, cwd: 'src/images/', filter: 'isFile' }
         ]
     less:
       development:


Environments: Fixed adding non-orphan items to default stack.

--- client/Environments/views/stackview.coffee
+++ client/Environments/views/stackview.coffee
@@ -100,7 +100,7 @@
     # Add domains
     @domains.removeAllItems()
     for domain in env.domains
-      if domain.stack is stack._id #or isDefault
+      if domain.stack is stack.getId() or (isDefault and not domain.stack)
         @domains.addDomain domain
       else
         orphans.domains.push domain
@@ -108,17 +108,17 @@
     # Add vms
     @vms.removeAllItems()
     for vm in env.vms
-      if vm.stack is stack._id #or isDefault
-      then @vms.addItem title:vm.alias
-      else orphans.vms.push vm
-
-    # log "orphans", orphans
+      if vm.stack is stack.getId() or (isDefault and not vm.stack)
+        vm.title = vm.hostnameAlias
+        @vms.addItem vm
+      else
+        orphans.vms.push vm

     # Add extras
     @extras.removeAllItems()
     @extras.addItem extra  for extra in env.extras

-    # log "ORPHANS", orphans
+    log "orphans", orphans

     @setHeight @getProperHeight()
     KD.utils.wait 300, =>


--- client/views/app/messagePopupConfig.coffee
+++ client/views/app/messagePopupConfig.coffee
@@ -116,6 +116,27 @@
 				getInput: self.getInput
 				getFilter: (collection, filter) ->
 					results = []
+
+					# show common used emojis, when use input a single ':'
+					if filter == ''
+						commonEmojis = [
+				            ':laughing:',
+				            ':smiley:',
+				            ':sunglasses:',
+				            ':wink:',
+				            ':innocent:',
+				            ':flushed:',
+				            ':disappointed:',
+				            ':cry:',
+				            ':heart:',
+				            ':broken_heart:',
+				        ]
+						for shortname in commonEmojis
+							results.push
+								_id: shortname
+								data: collection[shortname]
+						return results;
+
 					for shortname, data of collection
 						if shortname.indexOf(filter) > -1
 							results.push


Refactored inline Javascript to CoffeeScript in compile.coffee

--- alight/compile.coffee
+++ alight/compile.coffee
@@ -102,7 +102,11 @@
                 # text directive
                 result.push "((x=this[#{index}].value) || (x == null?'':x))"
         else if d.value
-            `result.push('"' + d.value.replace(/\\/g,'\\\\').replace(/"/g,'\\"').replace(/\n/g,'\\n') + '"')`
+            escapedValue = d.value
+                .replace(/\\/g, "\\\\")
+                .replace(/"/g, '\\"')
+                .replace(/\n/g, "\\n")
+            result.push '"' + escapedValue + '"'
     result = result.join ' + '
     fn = self.Function "var x; return (#{result})"
     self.cacheText[text] = fn


Drop erroneous console.log

Drop erroneous console.log

--- src/wrathnet/crypto/srp.coffee
+++ src/wrathnet/crypto/srp.coffee
@@ -8,8 +8,6 @@

   # Creates new SRP instance with given constant prime and generator
   constructor: (N, g) ->
-
-    console.log 'foo'

     # Constant prime (B)
     @_N = BigNum.fromArray(N)


backend_pubsub: PubsubBackend.notify(): readability

--- src/xmpp/backend_pubsub.coffee
+++ src/xmpp/backend_pubsub.coffee
@@ -40,14 +40,14 @@
                     else
                         cb null, result

-    notify: (notification) ->
-        nKlass = notifications.byEvent notification.event
-        return false unless nKlass
+    notify: (opts) ->
+        notificationClass = notifications.byEvent notification.event
+        return false unless notificationClass

-        n = new nKlass(notification)
+        notification = new notificationClass(opts)
         # is local? send to all resources...
         for onlineJid in @conn.getOnlineResources notification.listener
-            @conn.send n.toStanza(@conn.jid, onlineJid)
+            @conn.send notification.toStanza(@conn.jid, onlineJid)

 class BuddycloudDiscovery


Turned off editor gutter

Turned off editor gutter

--- taoblog/static/coffee/admin/compose.coffee
+++ taoblog/static/coffee/admin/compose.coffee
@@ -6,6 +6,7 @@
         text = $('div.post-editor textarea[name="text"]')

         editor = ace.edit('editor')
+        editor.renderer.setShowGutter(false)
         editor.setTheme("ace/theme/tomorrow")
         editor.getSession().setMode("ace/mode/markdown")
         editor.setValue(text.val())


delete some console logs

delete some console logs

--- static/coffee/simulator.coffee
+++ static/coffee/simulator.coffee
@@ -370,8 +370,6 @@
     showSimulator()

 $("#show-simulator").click ->
-  console.log(window.ussd)
-  console.log(window.has_ussd_channel)
   verifyNumberSimulator()

 # toggle simulator


<nav> elements are [up-nav] by default

<nav> elements are [up-nav] by default

--- lib/assets/javascripts/unpoly/feedback.coffee
+++ lib/assets/javascripts/unpoly/feedback.coffee
@@ -61,7 +61,7 @@
   ###
   config = new up.Config ->
     currentClasses: ['up-current']
-    navSelectors: ['[up-nav]']
+    navSelectors: ['[up-nav]', 'nav']

   reset = ->
     config.reset()


Fix compiler

Fix compiler

--- library/compilers/CoffeeScriptCompiler.coffee
+++ library/compilers/CoffeeScriptCompiler.coffee
@@ -1,8 +1,17 @@
-CoffeeScript = require 'coffee-script-redux'
+###CoffeeScript = require 'coffee-script-redux'
 module.exports = (input) ->
 	csAst = CoffeeScript.parse input, raw: true
 	jsAst = CoffeeScript.compile csAst
 	js = CoffeeScript.js jsAst
 	module = {}
 	eval js
+	module.exports
+
+###
+
+CoffeeScript = require 'coffee-script'
+module.exports = (input) ->
+	js = CoffeeScript.compile input
+	module = {}
+	eval js
 	module.exports


VideoLinkShell: elapsed time is now absolute.

--- coffee/src/shells/videolink.shell.coffee
+++ coffee/src/shells/videolink.shell.coffee
@@ -251,8 +251,8 @@
       model: tvModel

     tvModel.listenTo @_playerView, 'Media:Progress', (view, elapsed, total) =>
-      tvModel.set 'elapsed', elapsed
-      tvModel.set 'total', total
+      tvModel.set 'elapsed', elapsed + @model.timeStart()
+      tvModel.set 'total', (@model.timeTotal())

   _initializeLoopsButton: =>


more removal of data("id") in favor of attr("data-id")

--- lms/static/coffee/src/discussion/utils.coffee
+++ lms/static/coffee/src/discussion/utils.coffee
@@ -161,7 +161,7 @@
   @makeWmdEditor: ($content, $local, cls_identifier) ->
     elem = $local(".#{cls_identifier}")
     placeholder = elem.data('placeholder')
-    id = elem.data("id")
+    id = elem.attr("data-id") # use attr instead of data because we want to avoid type coercion
     appended_id = "-#{cls_identifier}-#{id}"
     imageUploadUrl = @urlFor('upload')
     _processor = (_this) ->
@@ -174,12 +174,12 @@

   @getWmdEditor: ($content, $local, cls_identifier) ->
     elem = $local(".#{cls_identifier}")
-    id = elem.data("id")
+    id = elem.attr("data-id") # use attr instead of data because we want to avoid type coercion
     @wmdEditors["#{cls_identifier}-#{id}"]

   @getWmdInput: ($content, $local, cls_identifier) ->
     elem = $local(".#{cls_identifier}")
-    id = elem.data("id")
+    id = elem.attr("data-id") # use attr instead of data because we want to avoid type coercion
     $local("#wmd-input-#{cls_identifier}-#{id}")

   @getWmdContent: ($content, $local, cls_identifier) ->


casper tested #67293514 : Disposable income adjusted accordingly - current allowance of 285.13 per child

--- tests/javascript/browser/means_test.coffee
+++ tests/javascript/browser/means_test.coffee
@@ -95,7 +95,113 @@
     @waitForUrl /.*result/

   casper.then ->
-    test.assertSelectorHasText "form h2", "might be", "test case is not eligible"
-
-  casper.run ->
-    test.done()
+    test.assertSelectorHasText "form h2", "might be", "test case might be eligible"
+
+  casper.run ->
+    test.done()
+
+
+casper.test.begin "Disposable Income not eligible because too much income and with children", (test) ->
+  phantom.clearCookies()
+  casper.start casper.base_url + "your_problem/", ->
+    test.assertSelectorHasText "h1", "Your problem", "page has correct title"
+
+
+  casper.thenEvaluate ->
+    $("input[name=your_problem-category]:first").click()
+
+  casper.then ->
+    @fill 'form', {}, true
+    @waitForUrl /.*your_details/
+
+  casper.thenEvaluate ->
+    $("input[value=0][type=radio]").click()
+    $("#id_your_details-has_children_0").click()
+
+  casper.then ->
+    @fill 'form', {}, true
+    @waitForUrl /.*your_capital/
+
+  casper.thenEvaluate ->
+    $("input[type=number]").val(0)
+
+  casper.then ->
+    @fill 'form', {}, true
+    @waitForUrl /.*your_income/
+
+  casper.thenEvaluate ->
+    $("input[type=number]").val(0)
+    $("#id_your_income-earnings").val(733.01+(285.13*3))
+    $("input[value=1][type=radio]").click()
+    $("#id_dependants-dependants_old").val(2)
+    $("#id_dependants-dependants_young").val(1)
+
+  casper.then ->
+    @fill 'form', {}, true
+    @waitForUrl /.*your_allowances/
+
+  casper.thenEvaluate ->
+    $("input[type=number]").val(0)
+
+  casper.then ->
+    @fill 'form', {}, true
+    @waitForUrl /.*result/
+
+  casper.then ->
+    test.assertSelectorHasText "form h2", "not", "test case is not eligible"
+
+  casper.run ->
+    test.done()
+
+
+casper.test.begin "Disposable Income is eligible because not enough income and with children", (test) ->
+  phantom.clearCookies()
+  casper.start casper.base_url + "your_problem/", ->
+    test.assertSelectorHasText "h1", "Your problem", "page has correct title"
+
+
+  casper.thenEvaluate ->
+    $("input[name=your_problem-category]:first").click()
+
+  casper.then ->
+    @fill 'form', {}, true
+    @waitForUrl /.*your_details/
+
+  casper.thenEvaluate ->
+    $("input[value=0][type=radio]").click()
+    $("#id_your_details-has_children_0").click()
+
+  casper.then ->
+    @fill 'form', {}, true
+    @waitForUrl /.*your_capital/
+
+  casper.thenEvaluate ->
+    $("input[type=number]").val(0)
+
+  casper.then ->
+    @fill 'form', {}, true
+    @waitForUrl /.*your_income/
+
+  casper.thenEvaluate ->
+    $("input[type=number]").val(0)
+    $("#id_your_income-earnings").val(732.99+(285.13*3))
+    $("input[value=1][type=radio]").click()
+    $("#id_dependants-dependants_old").val(2)
+    $("#id_dependants-dependants_young").val(1)
+
+  casper.then ->
+    @fill 'form', {}, true
+    @waitForUrl /.*your_allowances/
+
+  casper.thenEvaluate ->
+    $("input[type=number]").val(0)
+
+  casper.then ->
+    @fill 'form', {}, true
+    @waitForUrl /.*result/
+
+  casper.then ->
+    test.assertSelectorHasText "form h2", "might be", "test case might be eligible"
+
+  casper.run ->
+    test.done()


Fix link on comment entities

Fix link on comment entities

--- models/comment.coffee
+++ models/comment.coffee
@@ -30,7 +30,7 @@
     for entity in entities
       if entity.type == 'user'
         output = output + text.slice(lastPosition, entity.start) +
-          "[@#{entity.user_name}](#{entity.user_slug})"
+          "[@#{entity.user_name}](/#{entity.user_slug})"
         lastPosition = entity.end

     output += text.slice(lastPosition)


midway through adding POST/PUT, but had a different idea

--- node.js/server.coffee
+++ node.js/server.coffee
@@ -1,5 +1,6 @@
 http = require 'http'
 fs = require 'fs'
+qs = require 'querystring'

 port = 8888

@@ -26,18 +27,25 @@
 }

 process.serve_url = (req, res) ->
-#work out what resource the request is about
     file = req.url[1..]

-    if req.method == 'PUT'
+    if req.method == 'PUT'itemitem
        #nasty hack, as we're only putting pages atm, and the URI's are :(
        file = file.replace(/^page\//, '')    #TODO: mmm, substring anyone?
        file = file.replace(/\/action$/, '')
+       body = ''
+       req.on 'data', (data) -> body +=data;
+       req.on 'end', ->
+         POST =  qs.parse(body, true);
+         console.log(POST);
+

     if file == ''
       console.log 'getting / == welcome-visitors.html topic'
       file = 'welcome-visitors.html'

+    #TODO: use require('url').parse(request.url, true) to parse the url
+
     #TODO: security, don't allow non-topic id chars - avoid ../ etc
     file = file.replace(/\.\.[\/\\]/g, '')    #remove all instances of ../ and ..\ (talk about brutish and a tad simplistic
     file = file.replace(/\?.*$/i, '')     #remove ?random= - or other urlparams
@@ -62,7 +70,7 @@

     console.log req.method+' '+req.url+': '+filePath

-#fulfil the request
+    #fulfil the request
     #read
     fs.readFile filePath, encoding, (err, data) ->
       status = 200
@@ -73,16 +81,16 @@
           status = 404
         data = ""
       console.log 'status: '+ status
-      console.log ' contentType: '+ contentType
+      #console.log ' contentType: '+ contentType
       if status is 200
           res.writeHead 200,
               'Content-Type': contentType
               'Content-Length': data.length + 1
           if encoding == 'binary'
-            console.log 'sending binary'
+            #console.log 'sending binary'
             res.write data, encoding
           else
-            console.log 'sending string'
+            #console.log 'sending string'
             data = data.toString()
             res.write data + '\n'


Remove superfluous geofsh.js.coffee

Remove superfluous geofsh.js.coffee

--- vendor/assets/javascripts/geofsh.js.coffee
+++ vendor/assets/javascripts/geofsh.js.coffee
@@ -1,41 +0,0 @@
-googleMap = (geolat, geolng) ->
-  if document.getElementById("google-map")
-    geoCoords = new google.maps.LatLng(geolat, geolng)
-    mapOptions =
-      zoom: 16
-      center: geoCoords
-      mapTypeId: google.maps.MapTypeId.ROADMAP
-
-    map = new google.maps.Map(document.getElementById("google-map"), mapOptions)
-    addMarker geoCoords, map, "You are here!"
-
-addMarker = (position, map, title) ->
-  marker = new google.maps.Marker(
-    position: position
-    map: map
-    title: title
-  )
-
-geolocSuccess = (position) ->
-  geolat = position.coords.latitude
-  geolng = position.coords.longitude
-  $("input#latitude").val geolat
-  $("input#longitude").val geolng
-  googleMap geolat, geolng
-
-geolocError = ->
-  hideGeoLoc()
-  "Geolocation Unsuccessful"
-
-hideGeoLoc = ->
-  $(".geoloc-hide").parent().hide()
-
-getPosition = ->
-  if navigator.geolocation
-    navigator.geolocation.getCurrentPosition geolocSuccess, geolocError
-  else
-    hideGeoLoc()
-    "Geolocation not supported"
-
-$ ->
-  getPosition()


First cut at preloading.

First cut at preloading.

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -4,7 +4,19 @@
 #= require_self

 Gif =
-  load: ($gif) ->
+  load: (url, options) ->
+    xhr = new XMLHttpRequest()
+    xhr.open "GET", url
+
+    xhr.onload = (event) ->
+      options.completed()
+
+    xhr.onprogress = (event) ->
+      percentage = (event.loaded / event.total) * 100
+
+    xhr.send()
+
+  play: ($gif) ->
     @toggle $gif

   pause: ($gif) ->
@@ -14,8 +26,12 @@
     $image = $gif.find('img')
     url    = $image.data 'url'

-    $image.data 'url', $image.attr('src')
-    $image.attr 'src', url
+    Gif.load url,
+      progress: ->
+        console.log arguments
+      completed: ->
+        $image.data 'url', $image.attr('src')
+        $image.attr 'src', url

   find: (element) ->
     jQuery(element).closest('.gif')
@@ -23,6 +39,6 @@
 jQuery ->
   jQuery('.gif-inner').on
     mouseenter: (event) ->
-      Gif.load Gif.find(event.target)
+      Gif.play Gif.find(event.target)
     mouseleave: (event) ->
       Gif.pause Gif.find(event.target)


Moved places on konami require

Moved places on konami require

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -19,6 +19,7 @@
 #= require twitter-text
 #= require autolink
 #= require select2
+#= require konami-1.3.3
 # require pjax
 # require jquery.tokeninput
 # require jquery.daterangepicker
@@ -44,5 +45,3 @@
 #= require_tree ./views
 #= require_tree ./routers
 #= require_tree ./modules
-#
-#= require konami-1.3.3


Enabling asking about all simulations when the number of progress bar is lower than the number of simulations.

--- app/assets/javascripts/experiments.js.coffee
+++ app/assets/javascripts/experiments.js.coffee
@@ -152,7 +152,10 @@
     x -= canvas.offset().left

     cellWidth = canvas.width() / @cellCounter
-    cellId = Math.floor((x / cellWidth) + 1)
+    cellId = Math.floor((x / cellWidth) + 1) * @simsPerCell
+    if @simsPerCell > 0
+      randomNumber = Math.floor((Math.random()*@simsPerCell)) # 0..@simsPerCell - 1
+      cellId -= randomNumber

     $('#extension-dialog').load("/experiments/#{@experiment_id}/simulations/#{cellId}")
     $('#extension-dialog').foundation('reveal', 'open')
@@ -174,6 +177,8 @@

     @bar_cells = []
     @cellCounter = bar_colors.length
+    @simsPerCell = Math.floor(statistics.all / bar_colors.length)
+    console.log "Simulations per Cell: #{@simsPerCell}"

     for i in [0..bar_colors.length]
       context.fillStyle = if(bar_colors[i] == 0) then "#BDBDBD" else "rgb(0, #{bar_colors[i]}, 0)"


Typo fix

Typo fix

--- webapp/app/scripts/views/PostalCode.coffee
+++ webapp/app/scripts/views/PostalCode.coffee
@@ -1,6 +1,6 @@
 ErrorKeyToMessage =
   Invalid: 'Please enter a postal code of the form "H1H 1H1".'
-  NotFound: 'The postal code you entered does not seem to be in Montreal. Please try another.'
+  NotFound: 'The postal code you entered does not seem to be in Quebec. Please try another.'
   FusionError: 'We failed our search for your postal code. Please click your home on the map instead.'

 InfoKeyToMessage =


:rage: :poop: :poop:

:rage: :poop: :poop:

--- app/coffee/modules/userstories/detail.coffee
+++ app/coffee/modules/userstories/detail.coffee
@@ -382,8 +382,6 @@

             if saveAfterModify
                 # Edit in the detail page
-                $loading.start($el)
-
                 onSuccess = ->
                     $confirm.notify("success")
                     $rootScope.$broadcast("history:reload")


Groups: ownership information is deducted from vms while fetching vm via hostname

--- workers/social/lib/social/models/vm.coffee
+++ workers/social/lib/social/models/vm.coffee
@@ -143,7 +143,6 @@
     {vmName, status} = options
     fetchVmByHostname account, vmName, (err, vm) ->
       return callback err if err
-      return callback new KodingError "VM Not FOUND" unless vm

       [{ id: groupId }] = vm.groups
       JGroup = require './group'
@@ -660,8 +659,6 @@

     fetchVmByHostname delegate, hostnameAlias, (err, vm) ->
       return callback err  if err
-      return callback new KodingError 'No such VM'  unless vm
-
       [{ id: groupId }] = vm.groups
       JGroup = require './group'
       JGroup.one { _id: groupId }, (err, group)->
@@ -680,9 +677,17 @@

       selector =
         hostnameAlias : hostnameAlias
-        users         : { $elemMatch: id: user.getId(), owner: yes }
-
-      JVM.one selector, callback
+        users         : { $elemMatch: id: user.getId() }
+
+      JVM.one selector, (err, vm) ->
+        return callback err  if err
+        return callback new KodingError "VM not found"  unless vm
+
+        isOwner = vm.users.filter (vmUser) ->
+          vmUser.id.equals(user.getId()) && vmUser.owner is true
+
+        err = new KodingError("You are not owner of this VM", "NOTPERMITTED")  unless isOwner.length
+        callback err, vm

   @addVm = ({ account, target, user, sudo, groups, groupSlug
              type, planCode, planOwner, webHome, uid }, callback)->


LikeView: Add like tooltip

LikeView: Add like tooltip

--- client/Social/Activity/views/likeview.coffee
+++ client/Social/Activity/views/likeview.coffee
@@ -11,4 +11,12 @@

     @link = new ActivityLikeLink {}, data

-  pistachio: -> "{{> @link}}"
+    @count = new ActivityLikeCount
+      cssClass    : 'count'
+      tooltip     :
+        gravity   : @getOption "tooltipPosition"
+        title     : ""
+    , data
+
+
+  pistachio: -> "{{> @link}}{{> @count}}"


Teamwork: Bind remote events in a proper way for client and host.

--- client/Teamwork/Views/teamworktabview.coffee
+++ client/Teamwork/Views/teamworktabview.coffee
@@ -8,10 +8,16 @@
     @keysRef  = @workspaceRef.child "keys"
     @indexRef = @workspaceRef.child "index"

-    @listenIndexRef()
-    @listenChildAddedOnKeysRef()
     @listenChildRemovedOnKeysRef()
-    @listenPaneDidShow()
+    if @amIHost
+      @bindRemoteEvents()
+    else
+      @keysRef.once "value", (snapshot) =>
+        data = snapshot.val()
+        return unless data
+
+        @keysRefChildAddedCallback value  for key, value of data
+        @bindRemoteEvents()

   listenPaneDidShow: ->
     @tabView.on "PaneDidShow", (pane) =>
@@ -27,14 +33,21 @@
         if pane.getOptions().indexKey is indexKey
           @tabView.removePane pane

+  bindRemoteEvents: ->
+    @listenPaneDidShow()
+    @listenIndexRef()
+    @listenChildAddedOnKeysRef()
+
   listenChildAddedOnKeysRef: ->
     @keysRef.on "child_added", (snapshot) =>
-      data    = snapshot.val()
-      key     = data.indexKey
-      {panes} = @tabView
-      isExist = yes for pane in panes when pane.getOptions().indexKey is key
-
-      @createTabFromFirebaseData data  unless isExist
+      @keysRefChildAddedCallback snapshot.val()
+
+  keysRefChildAddedCallback: (data) ->
+    key     = data.indexKey
+    {panes} = @tabView
+    isExist = yes for pane in panes when pane.getOptions().indexKey is key
+
+    @createTabFromFirebaseData data  unless isExist

   listenIndexRef: ->
     @indexRef.on "value", (snapshot) =>


Teamwork: Deleted closed pane's paneIndex correctly.

--- client/Teamwork/Views/teamworktabview.coffee
+++ client/Teamwork/Views/teamworktabview.coffee
@@ -72,7 +72,9 @@
         paneIndexKey = pane.getOption "indexKey"
         if paneIndexKey is indexKey
           @tabView.removePane pane
-          delete @openIndexes[paneIndexKey]
+
+      for key, value of @openIndexes when value is indexKey
+        delete @openIndexes[key]

   bindRemoteEvents: ->
     @listenPaneDidShow()


added AppManager#quitAll

added AppManager#quitAll

--- client/app/MainApp/ApplicationManager.coffee
+++ client/app/MainApp/ApplicationManager.coffee
@@ -150,6 +150,10 @@
     @unregister appInstance
     callback()

+  quitAll:->
+    for own name, apps of @appControllers
+      @quit app  for app in apps
+
   get:(name)-> @appControllers[name]?.first or null

   getByView: (view)->


[Ban User] changed function signature to get account id or nickname

--- client/app/MainApp/activitycontroller.coffee
+++ client/app/MainApp/activitycontroller.coffee
@@ -30,7 +30,7 @@
   blockUser:(accountId, duration, callback)->
     KD.whoami().blockUser accountId, duration, callback

-  openBlockUserModal:(data)->
+  openBlockUserModal:(nicknameOrAccountId)->
     @modal = modal = new KDModalViewWithForms
       title                   : "Block User For a Time Period"
       content                 : """
@@ -49,7 +49,7 @@
           BlockUser           :
             callback          : =>
               blockingTime = calculateBlockingTime modal.modalTabs.forms.BlockUser.inputs.duration.getValue()
-              @blockUser data.originId, blockingTime, (err, res)->
+              @blockUser nicknameOrAccountId, blockingTime, (err, res)->
                 if err
                   warn err
                   modal.modalTabs.forms.BlockUser.buttons.blockUser.hideLoader()
@@ -211,4 +211,4 @@
     return no if @flags.liveUpdates and not isOnActivityPage

     @newItemsCount = 0
-    @emit "NewItemsCounterCleared"+    @emit "NewItemsCounterCleared"


Fix validation for passwords in register screen

--- client/app/MainApp/login/registerform.coffee
+++ client/app/MainApp/login/registerform.coffee
@@ -250,6 +250,7 @@
     {validate} = @password.input.getOptions()
     delete validate.rules.minLength
     @password.input.setValidation validate
+    @passwordConfirm.input.setValidation validate

     @passwordConfirm.setHeight 0
     @$('p.kodingen-user-notification b').text "#{@username.input.getValue()}"
@@ -266,6 +267,7 @@
     {validate} = @password.input.getOptions()
     validate.rules.minLength = 8
     @password.input.setValidation validate
+    @passwordConfirm.input.setValidation validate

     @$('p.kodingen-user-notification').height 0
     @passwordConfirm.setHeight 32


notif/ctrl: dont dispatch actions on machine updates

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

--- client/app/lib/notificationcontroller.coffee
+++ client/app/lib/notificationcontroller.coffee
@@ -138,16 +138,11 @@

           if machine = storage.machines.get 'uid', machineUId
             storage.machines.pop machine
-            reactor.dispatch actions.INVITATION_REJECTED, machine._id

           @emit 'MachineShare:Removed', { machine }

         when 'added'
           storage.machines.fetch 'uid', machineUId
-            .then (machine) ->
-              reactor.dispatch actions.LOAD_USER_ENVIRONMENT_SUCCESS, [ machine ]
-              return machine
-
             .then (machine) =>
               @emit 'MachineShare:Added', { machine }
               return machine


Correct spelling

Correct spelling

--- client/skr/models/mixins/HasVisibleId.coffee
+++ client/skr/models/mixins/HasVisibleId.coffee
@@ -2,6 +2,6 @@

     invalidChars: /[^A-Z0-9a-z]/

-    visibleIdentifier: -> @visual_id
+    visibleIdentifier: -> @visible_id

 }


Don't clear me before tests (needs more thought)

--- app/views/TestView.coffee
+++ app/views/TestView.coffee
@@ -78,7 +78,6 @@
     describe 'CodeCombat Client', =>
       jasmine.Ajax.install()
       beforeEach ->
-        me.clear()
         jasmine.Ajax.requests.reset()
         Backbone.Mediator.init()
         Backbone.Mediator.setValidationEnabled false


cookie expiration should take number of seconds so we can periodically check for updates

--- components/auction_reminder/index.coffee
+++ components/auction_reminder/index.coffee
@@ -13,6 +13,7 @@
 AuctionClock = require './clock/view.coffee'

 class AuctionReminderModal extends Backbone.View
+  cookieExpiration: 600

   events: ->
     'click .modal-close': 'close'
@@ -21,7 +22,7 @@
   initialize: ({ @auctionName, @auctionId, @auctionImage, @auctionEndat }) ->

     # Reminder doesn't show on auction page
-    if window.location.pathname == @auctionId
+    if window.location.pathname == "/auction/#{@auctionId}"
       return
     # Reminder only shows if 24 hours until end
     diff = moment(@auctionEndat).diff(moment(),'hours')
@@ -57,8 +58,7 @@
       @$('.modal-dialog').addClass('is-static-open')
     else
       cookieValue = "#{@auctionName}|#{@auctionId}|#{@auctionImage}|#{@auctionEndat}"
-      cookieExpiration = moment.duration(moment(@offsetEndAtMoment).diff(moment()))._milliseconds
-      Cookies.set('firstAuctionReminderSeen', cookieValue, { expires: cookieExpiration })
+      Cookies.set('firstAuctionReminderSeen', cookieValue, { expires: @cookieExpiration })
       activate = => @$dialog.addClass("is-spring-in")
       _.delay(activate,5000)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/point.coffee
+++ src/point.coffee
@@ -631,7 +631,7 @@
   @returns {Point}
   ###
   Point.random = ->
-    Point.fromAngle()
+    Point.fromAngle(Random.angle)
   ###*
   @name ZERO
   @fieldOf Point


add auth middleware to channel routes

add auth middleware to channel routes

--- apps/channel/index.coffee
+++ apps/channel/index.coffee
@@ -11,6 +11,6 @@
 app = module.exports = express()
 app.set "views", __dirname + "/templates"
 app.set "view engine", "jade"
-app.get "/:username/:channel_slug", routes.channel
-app.get "/:username/:channel_slug/followers", routes.followers
-app.get "/:username/:channel_slug/block/:block_id", routes.channel+app.get "/:username/:channel_slug", auth, routes.channel
+app.get "/:username/:channel_slug/followers", auth, routes.followers
+app.get "/:username/:channel_slug/block/:block_id", auth, routes.channel


Add default keymaps that bind to language-markdown package

--- keymaps/language-weave.cson
+++ keymaps/language-weave.cson
@@ -1,4 +1,34 @@
+'.platform-darwin atom-text-editor[data-grammar="source weave md"]':
+  'cmd-shift-x': 'markdown:toggle-task'
+'.platform-win32 atom-text-editor[data-grammar="source weave md"]':
+  'ctrl-shift-x': 'markdown:toggle-task'
+'.platform-linux atom-text-editor[data-grammar="source weave md"]':
+  'ctrl-shift-x': 'markdown:toggle-task'

+'atom-text-editor[data-grammar="source weave md"]':
+  'tab': 'markdown:indent-list-item'
+  'shift-tab': 'markdown:outdent-list-item'
+  '_': 'markdown:emphasis'
+  '*': 'markdown:strong-emphasis'
+  '~': 'markdown:strike-through'
+  '@': 'markdown:link'
+  '!': 'markdown:image'
+
+'.platform-darwin atom-text-editor[data-grammar="source pweave md"]':
+  'cmd-shift-x': 'markdown:toggle-task'
+'.platform-win32 atom-text-editor[data-grammar="source pweave md"]':
+  'ctrl-shift-x': 'markdown:toggle-task'
+'.platform-linux atom-text-editor[data-grammar="source pweave md"]':
+  'ctrl-shift-x': 'markdown:toggle-task'
+
+'atom-text-editor[data-grammar="source pweave md"]':
+  'tab': 'markdown:indent-list-item'
+  'shift-tab': 'markdown:outdent-list-item'
+  '_': 'markdown:emphasis'
+  '*': 'markdown:strong-emphasis'
+  '~': 'markdown:strike-through'
+  '@': 'markdown:link'
+  '!': 'markdown:image'

 '.platform-linux .item-views > atom-text-editor[data-grammar="source weave md"],
 .platform-linux .item-views > atom-text-editor[data-grammar="source weave latex"],


quotestats!

quotestats!

--- src/quote.coffee
+++ src/quote.coffee
@@ -52,7 +52,10 @@
       false

   queryFrom = (msg) ->
-    words = msg.match[1].trim().split /\s+/
+    if msg.match[1]?
+      words = msg.match[1].trim().split /\s+/
+    else
+      words = ['']
     _.filter words, (part) -> part.length > 0

   quotesMatching = (query) ->
@@ -65,7 +68,7 @@
   # Perform the initial load.
   reloadThen ->

-  robot.respond /quote(.*)/i, (msg) ->
+  robot.respond /quote(\s.*)?/i, (msg) ->
     return unless isLoaded(msg)

     potential = quotesMatching queryFrom msg
@@ -76,7 +79,7 @@
     else
       msg.send "That wasn't notable enough to quote. Try harder."

-  robot.respond /howmany(.*)/i, (msg) ->
+  robot.respond /howmany(\s.*)/i, (msg) ->
     return unless isLoaded(msg)

     matches = quotesMatching queryFrom msg
@@ -129,7 +132,7 @@
         else
           msg.send "#{quotes.length} quotes reloaded successfully."

-  robot.respond /top quotes$/i, (msg) ->
+  robot.respond /quotestats$/i, (msg) ->
     return unless isLoaded(msg)

     usernames = []


Fix for below the fold crash on non-auction works

--- desktop/apps/artwork/client/index.coffee
+++ desktop/apps/artwork/client/index.coffee
@@ -173,7 +173,7 @@
     }
       .then (data) ->
         renderTemplates(extend data, {
-          auctionContextId: AUCTION.id
+          auctionContextId: AUCTION && AUCTION.id
         })

         exec init, data


Allow hubot to react on events

Allow hubot to react on events

Events might get triggered by scripts.

For example: You have a script, which can trigger and event that some one committed to a repo. This script can be used by anyone, but the reaction should be different by every company. So it's possible to trigger a build, or just post a message in chat.

As a plus with the same event triggered by different scripts, so it wouldn't make a difference if you commit into github or just another (maybe SVN) repo. But other scripts can rely on that 'commit-received' command and act the same way.

--- src/robot.coffee
+++ src/robot.coffee
@@ -1,7 +1,8 @@
-Fs         = require 'fs'
-Log        = require 'log'
-Path       = require 'path'
-HttpClient = require 'scoped-http-client'
+Fs           = require 'fs'
+Log          = require 'log'
+Path         = require 'path'
+HttpClient   = require 'scoped-http-client'
+EventEmitter = require('events').EventEmitter;

 User                                                    = require './user'
 Brain                                                   = require './brain'
@@ -36,6 +37,7 @@
   constructor: (adapterPath, adapter, httpd, name = 'Hubot') ->
     @name         = name
     @brain        = new Brain
+    @events      = new EventEmitter
     @alias        = false
     @adapter      = null
     @Response     = Response


bugfix callback naming

bugfix callback naming

--- modules/http/platforms/node/lib/Server.coffee
+++ modules/http/platforms/node/lib/Server.coffee
@@ -95,7 +95,7 @@
             ##
             req.cookies = res.cookies = new Cookies req, res

-            super req, res, done
+            super req, res, next

         ##


bugfix callback naming

bugfix callback naming

--- modules/http/platforms/node/lib/Server.coffee
+++ modules/http/platforms/node/lib/Server.coffee
@@ -69,7 +69,7 @@
         ##
         ##
         ##
-        _prepareRequest: (req, res, done)->
+        _prepareRequest: (req, res, next)->

             ##
             ## there is a sneaky gzip module created in 2010 by TJ @Sencha


Only detect chrome extension when it hasn't been detected already

--- core/app/assets/javascripts/base/browser_extensions.coffee
+++ core/app/assets/javascripts/base/browser_extensions.coffee
@@ -1,11 +1,12 @@
-detectChromeExtension = ->
-  s = document.createElement("script")
-  s.onerror = -> $("html").addClass "chrome_extension_not_installed"
-  s.onload = -> $("html").addClass "chrome_extension_installed"
-  document.body.appendChild s
-  s.src = "chrome-extension://#{Factlink.Global.chrome_extension_id}/manifest.json"
+unless $('html').hasClass('chrome_extension_installed')
+  detectChromeExtension = ->
+    s = document.createElement("script")
+    s.onerror = -> $("html").addClass "chrome_extension_not_installed"
+    s.onload = -> $("html").addClass "chrome_extension_installed"
+    document.body.appendChild s
+    s.src = "chrome-extension://#{Factlink.Global.chrome_extension_id}/manifest.json"

-if Factlink.Global.chrome_extension_id
-  is_chrome = navigator.userAgent.toLowerCase().indexOf("chrome") > -1
-  if is_chrome
-    detectChromeExtension()
+  if Factlink.Global.chrome_extension_id
+    is_chrome = navigator.userAgent.toLowerCase().indexOf("chrome") > -1
+    if is_chrome
+      detectChromeExtension()


Remove spaces

Remove spaces

--- src/middleware.coffee
+++ src/middleware.coffee
@@ -22,27 +22,21 @@
 ###

 module.exports = (options = {}) ->
-
   # Accept src/dest dir
   if typeof options is 'string'
     options = src: options
-
   # Base directory
   baseDir = options.baseDir or process.cwd()
-
   # Source dir required
   src = options.src
   throw new Error 'Coffeescript middleware requires "src" directory' unless src
   src = path.resolve baseDir, src
-
   # Default dest dir to source
   dest = if options.dest then options.dest else src
   dest = path.resolve baseDir, dest
-
   # Default compile callback
   options.compile ?= (str, options) ->
     coffeeScript.compile str, clone(options)
-
   # Middleware
   (req, res, next) ->
     return next() if 'GET' isnt req.method and 'HEAD' isnt req.method
@@ -52,12 +46,10 @@
         pathname = pathname.substring options.prefix.length
       jsPath = path.join dest, pathname
       coffeePath = path.join src, pathname.replace '.js', '.coffee'
-
       # Ignore ENOENT to fall through as 404
       error = (err) ->
         arg = if 'ENOENT' is err.code then null else err
         next arg
-
       # Compile to jsPath
       compile = ->
         debug 'read %s', jsPath
@@ -83,19 +75,15 @@
             if map?
               mapFile = jsPath.replace /\.js$/, '.map'
               mapPath = pathname.replace /\.js$/, '.map'
-
               # Special comment at the end that is required to signify to WebKit dev tools
               # that a source map is available:
               mapHeader = "//@ sourceMappingURL=#{mapPath}\n"
               js = "#{mapHeader}\n#{js}"
-
             fs.writeFile jsPath, js, 'utf8', ->
               return next() unless map?
               fs.writeFile mapFile, map, 'utf8', next
-
       # Force compilation
       return compile() if options.force
-
       # Compare mtimes
       fs.stat coffeePath, (err, coffeeStats) ->
         return error err if err


highlight paratheses

highlight paratheses

--- lib/atom-ternjs-type.coffee
+++ lib/atom-ternjs-type.coffee
@@ -93,10 +93,12 @@

     @manager.client.update(editor.getURI(), editor.getText()).then =>
       @manager.client.type(editor, rangeBefore.start).then (data) =>
+        console.log data
         return if data.type is '?'
         return unless data and data.exprName
         data.type = @manager.helper.formatType(data)
-        matches = data.type.match(/(\w{1,}\?{0,}: (\w|\?|\[\?\]){1,})/g)
+        #matches = data.type.match(/(\w{1,}\?{0,}: (\w|\?|\[\?\]){1,})/g)
+        matches = data.type.match(/(\w{1,}\?{0,}: (\w|\?|\[\?\]){1,}(\(\))?)/g)
         if matches?[paramPosition]
           data.type = data.type.replace(matches[paramPosition], '' + matches[paramPosition] + '')
         @view.setData({


Allow crange be a Point or Range

Allow crange be a Point or Range

--- lib/ghc-modi-process.coffee
+++ lib/ghc-modi-process.coffee
@@ -202,6 +202,9 @@

   #buffer commands
   getTypeInBuffer: (buffer, crange, callback) =>
+    if crange instanceof Point
+      crange = new Range crange, crange
+
     @withTempFile buffer.getText(), (path,close) =>
       cpos = crange.start
       command = ["type",path,"",cpos.row+1,cpos.column+1]
@@ -222,6 +225,9 @@
         callback {range,type}

   getInfoInBuffer: (buffer, crange, callback) =>
+    if crange instanceof Point
+      crange = new Range crange, crange
+
     @withTempFile buffer.getText(), (path,close) =>
       if crange.isEmpty()
         {start,end}=buffer.getRange()


:art: Respect linter.showErrorPanel in LinterViews

--- lib/linter-views-new.coffee
+++ lib/linter-views-new.coffee
@@ -18,6 +18,9 @@

     @subscriptions.add atom.config.observe('linter.underlineIssues', (underlineIssues) =>
       @underlineIssues = underlineIssues
+    )
+    @subscriptions.add atom.config.observe('linter.showErrorPanel', (showPanel) =>
+      @panel.panelVisibility = showPanel
     )
     @subscriptions.add @linter.onDidClassifyMessages =>
       @render()


Invoke linter callback even if there's no violations

So that the clean check-icon can be shows in the status bar.

--- lib/linter/scss-lint.coffee
+++ lib/linter/scss-lint.coffee
@@ -14,10 +14,13 @@
     runner.run (commandError, result) =>
       return callback(commandError) if commandError?

-      if result.exitCode == 65
-        xml2js.parseString result.stdout, (xmlError, xml) =>
-          return callback(xmlError) if xmlError?
-          callback(null, @createViolationsFromXml(xml))
+      # https://github.com/causes/scss-lint/blob/v0.20.0/lib/scss_lint/cli.rb#L12-L17
+      unless result.exitCode == 0 || result.exitCode == 65
+        return callback(new Error("Process exited with code #{result.exitCode}"))
+
+      xml2js.parseString result.stdout, (xmlError, xml) =>
+        return callback(xmlError) if xmlError?
+        callback(null, @createViolationsFromXml(xml))

   createViolationsFromXml: (xml) ->
     return [] unless xml.lint.file?


Remove the dep on 'global'. Close #153

Remove the dep on 'global'. Close #153

--- src/Bacon.coffee
+++ src/Bacon.coffee
@@ -1047,7 +1047,7 @@
 Bacon._ = _

 Bacon.scheduler =
-  setTimeout: global.setTimeout
-  setInterval: global.setInterval
-  clearInterval: global.clearInterval
+  setTimeout: (f,d) -> setTimeout(f,d)
+  setInterval: (f, i) -> setInterval(f, i),
+  clearInterval: (id) -> clearInterval(id),
   now: -> new Date().getTime()


js state machine

js state machine

--- src/State.coffee
+++ src/State.coffee
@@ -226,3 +226,23 @@

   return I

+
+StateCollection = ->
+  states = []
+
+  self =
+    add: (name) ->
+      if typeof name == 'string'
+        state = State(name)
+        states.push(state)
+      else
+        name.each (item) ->
+
+        for(var i=0;i<name.length;i++){
+          this.add(name[i]);
+        }
+      }
+    },
+    all: function() {
+      return this.states;
+    }


Use splats... for Feature()

Use splats... for Feature()

--- src/cakes.coffee
+++ src/cakes.coffee
@@ -1,5 +1,10 @@
-exports.Feature = (feature, benefit, who, desire, callback)->
-	message = "Feature: #{feature} \n\n\t#{benefit}\n\t#{who}\n\t#{desire}"
+exports.Feature = (feature, story..., callback)->
+	#  exp. Feature 'new feature', 'in order to do good', 'as a user', 'I want to do good', ->
+	# message = "Feature: #{feature} \n\n\t#{benefit}\n\t#{who}\n\t#{desire}"
+
+	message = "Feature: #{feature} \n"
+	(message += '\n\t'+part for part in story)
+
 	describe(message, callback)
 	return


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -29,10 +29,10 @@
   parseRGB = (colorString) ->
     bits = rgbParser.exec(colorString)
     return [
-      bits[1]
-      bits[2]
-      bits[3]
-      if bits[4]? then bits[4] else 1.0
+      parseInt(bits[1])
+      parseInt(bits[2])
+      parseInt(bits[3])
+      if bits[4]? then parseFloat(bits[4]) else 1.0
     ]

   window.Color = (color) ->
@@ -45,9 +45,9 @@
     alpha = parsedColor[3]

     channels = [
-      parseInt(parsedColor[0])
-      parseInt(parsedColor[1])
-      parseInt(parsedColor[2])
+      parsedColor[0]
+      parsedColor[1]
+      parsedColor[2]
       if alpha? then parseFloat(alpha) else 0.0
     ]


added atBeginningOfMonth and atEndOfMonth

--- src/edate.coffee
+++ src/edate.coffee
@@ -109,6 +109,52 @@
 defineGetter(Date,   'atTheEndOfDay', atTheEndOfDay)
 defineGetter(Number, 'atTheEndOfDay', atTheEndOfDay)

+atTheBeginningOfMonth = ->
+  date = numberOrDate2Date(this)
+  date.setDate(1)
+  date.atTheBeginningOfDay
+
+defineGetter(Date,   'atTheBeginningOfMonth', atTheBeginningOfMonth)
+defineGetter(Number, 'atTheBeginningOfMonth', atTheBeginningOfMonth)
+
+# End-of-month has a bit of trickery
+
+isLeapYear = ->
+  date = numberOrDate2Date(this)
+  # no divmod guesswork needed if we let
+  # the date object handle itself :-D
+  feb29 = new Date(date.getFullYear(), 1, 29)
+  if 29 == feb29.getDate()
+    true
+  else
+    false
+
+daysInMonth = {
+    0 : 31 # Jan
+# , 1 : 28 # Feb (requires special attention)
+  , 2 : 31 # March
+  , 3 : 30 # April
+  , 4 : 31 # May
+  , 5 : 30 # June
+  , 6 : 31 # July
+  , 7 : 31 # August
+  , 8 : 30 # September
+  , 9 : 31 # October
+  , 10: 30 # November
+  , 11: 31 # December
+}
+
+atTheEndOfMonth = ->
+  date = numberOrDate2Date(this)
+  endOfMonth = daysInMonth[date.getMonth()]
+  if !endOfMonth
+    endOfMonth = date.isLeapYear() ? 29 : 28
+  date.setDate(endOfMonth)
+  date.atTheEndOfDay
+
+defineGetter(Date,   'atTheEndOfMonth', atTheEndOfMonth)
+defineGetter(Number, 'atTheEndOfMonth', atTheEndOfMonth)
+
 atTheBeginningOfYear = ->
   new Date(numberOrDate2Date(this).getFullYear(), 0)


fix mouse out

fix mouse out

--- src/graph.coffee
+++ src/graph.coffee
@@ -45,8 +45,8 @@
       mouse_out = new Bacon.Bus
       clicks = new Bacon.Bus
       observe_mouse = (s) ->
-        s.on('mouseover', (d, i) -> mouse_over.push i)
-         .on('mouseout', (d, i) -> mouse_out.push i)
+        s.on('mouseover', (d, i) -> mouse_over.push {index: i, event: d3.event, data: d})
+         .on('mouseout', (d, i) -> mouse_out.push {index: i, event: d3.event, data: d})
          .on('click', (d, i) -> clicks.push i)

       selected = clicks.scan _.map(data, -> true), (state, i) ->
@@ -63,11 +63,11 @@
             .data(s)
             .classed 'deselected', (d) -> !d

-      hover_selections = mouse_over.map (i) -> d3.select(container).selectAll ".target#{i}"
+      hover_selections = mouse_over.map ({index}) -> d3.select(container).selectAll ".target#{index}"
       hover_selections.onValue '.classed', 'hovered', true
       unhovers = hover_selections.merge(mouse_out)
         .withStateMachine([], (previous, event) -> [[event], previous])
-        .filter _.identity
+        .filter (e) -> e.classed?
       unhovers.onValue '.classed', 'hovered', false

       if type is 'line'


sanitize markdown

sanitize markdown

--- app/assets/javascripts/app.coffee
+++ app/assets/javascripts/app.coffee
@@ -149,7 +149,7 @@
   # markdown render functions
   # ------------------------------------------------------------------------------------------------------------------
   vm.render_html = (jq_html_div) ->
-    jq_html_div.html($window.marked(vm.get_md()))
+    jq_html_div.html($window.marked(vm.get_md(), {sanitize: true}))
     vm.html_post_process(jq_html_div)

   vm.html_post_process = (jq_html_div) ->


Adding user auto completion in wiki

Adding user auto completion in wiki

--- app/coffee/directives/wiki.coffee
+++ app/coffee/directives/wiki.coffee
@@ -119,7 +119,6 @@
         emojiStrategy = {
             match: /(^|\s):(\w*)$/,
             search: (term, callback) ->
-                console.log term
                 regexp = new RegExp('^' + term)
                 callback(
                     (key.substring(5) for key in _.keys($.emoticons.list) when regexp.test(key.substring(5)))
@@ -130,7 +129,29 @@
                 return "$1:#{value}: "
             maxCount: 5
         }
-        element.textcomplete([emojiStrategy])
+        usersStrategy = {
+            match: /(^|\s)@([\w-]*)$/,
+            search: (term, callback) ->
+                regexp = new RegExp('^' + term)
+                filterUser = (regexp, user) ->
+                    return _.any [
+                        regexp.test(user.full_name)
+                        regexp.test(user.first_name)
+                        regexp.test(user.last_name)
+                        regexp.test(user.username)
+                        regexp.test(user.email)
+                    ]
+                getUsersList = () ->
+                    users = $rootScope.constants.usersList
+                    return ({username: user.username, name: user.full_name} for user in users when filterUser(regexp, user))
+                callback(getUsersList())
+            template: (value) ->
+                return "#{value.name}"
+            replace: (value) ->
+                return "$1@#{value.username} "
+            maxCount: 5
+        }
+        element.textcomplete([emojiStrategy, usersStrategy])

         element.markItUp(markdownSettings)

@@ -193,5 +214,5 @@

 module = angular.module('taiga.directives.wiki', []).run ['$routeParams', '$rootScope', wikiInit ]
-module.directive('gmMarkitup', ["$rootScope", "$parse", "$i18next", "$sanitize", "$location", gmMarkitupConstructor])
+module.directive('gmMarkitup', ["$rootScope", "$parse", "$i18next", "$sanitize", "$location", "resource", gmMarkitupConstructor])
 module.directive("gmRenderMarkdown", ["$rootScope", "$parse", "$sanitize", GmRenderMarkdownDirective])


Changing wiki syntax help url

Changing wiki syntax help url

--- app/coffee/directives/wiki.coffee
+++ app/coffee/directives/wiki.coffee
@@ -2,7 +2,7 @@
 gmMarkitupConstructor = ($parse) ->
     require: "?ngModel",
     link: (scope, elm, attrs, ngModel) ->
-        wikiHelpUrl = "http://bywordapp.com/markdown/syntax.html"
+        wikiHelpUrl = "https://github.com/fletcher/MultiMarkdown/blob/master/Documentation/Markdown%20Syntax.md"
         openHelp = () ->
             window.open(wikiHelpUrl,'_blank')


who needs tests

who needs tests

--- test/octoblu-oauth-spec.coffee
+++ test/octoblu-oauth-spec.coffee
@@ -6,11 +6,11 @@
     @meshblu = generateAccessToken: sinon.stub()
     @Meshblu = sinon.stub().returns @meshblu

-    @meshbluJSON =
+    @meshbluJSON =
       uuid: 'head'
       token: 'earth'
       server: 'https://meshblu.octoblu.com'
-      port: 443
+      port: 443

     @dependencies = Meshblu: @Meshblu
     @sut = new OctobluOauth @meshbluJSON, @dependencies
@@ -19,26 +19,3 @@
     it 'should instantiate a OctobluOauth', ->
       expect(@sut).to.exist

-  describe '->generateAuthCode', ->
-    it 'should exist', ->
-      expect(@sut.generateAuthCode).to.exist
-
-    describe 'when called with a uuid and token', ->
-      beforeEach ->
-        @result = @sut.generateAuthCode 'cyborg', 'rebellion'
-      it 'should return a base64 encoded token containing the uuid, a colon, and the token', ->
-        expect(@result).to.equal "Y3lib3JnOnJlYmVsbGlvbg=="
-
-  describe '->exchangeAuthCodeForBearerToken', ->
-    it 'should exist', ->
-      expect(@sut.exchangeAuthCodeForBearerToken).to.exist
-
-    describe 'when called with an auth code', ->
-      beforeEach ->
-        @sut.exchangeAuthCodeForBearerToken 'c2Vhc2hlbGxzOnN0b3JteQ=='
-
-      it 'make a new meshblu with that access token', ->
-        expect(@Meshblu).to.have.been.calledWith 'seashells', 'stormy'
-
-      it 'should make a new meshblu with the uuid and token', ->
-


disable testing (for now)

disable testing (for now)

--- test/scripts-stock-test.coffee
+++ test/scripts-stock-test.coffee
@@ -12,8 +12,8 @@

     require('../src/stock')(@robot)

-  it 'registers a respond listener', ->
-    expect(@robot.respond).to.have.been.calledWith(/hello/)
-
-  it 'registers a hear listener', ->
-    expect(@robot.hear).to.have.been.calledWith(/orly/)
+#  it 'registers a respond listener', ->
+#    expect(@robot.respond).to.have.been.calledWith(/hello/)
+#
+#  it 'registers a hear listener', ->
+#    expect(@robot.hear).to.have.been.calledWith(/orly/)


version 1.1 is better

version 1.1 is better

--- client/landing/app/MainApp/filetree/controllers/findercontroller.coffee
+++ client/landing/app/MainApp/filetree/controllers/findercontroller.coffee
@@ -26,7 +26,7 @@
     TreeControllerClass = options.treeControllerClass or NFinderTreeController
     @treeController     = new TreeControllerClass treeOptions, []

-    @appStorage = KD.getSingleton('appStorageController').storage 'Finder', '1.0a'
+    @appStorage = KD.getSingleton('appStorageController').storage 'Finder', '1.1'

     if options.useStorage
       @appStorage.ready =>


rename arguments to args

rename arguments to args

--- src/hooks.coffee
+++ src/hooks.coffee
@@ -38,4 +38,4 @@
     # resolution: (chain, instance) ->

     # called right before a factory is called
-    # factory: (chain, factory, arguments) ->
+    # factory: (chain, factory, args) ->


New implementation of Rewriter#addImplicitBraces -- uses detectEnd..

--- rewriter.coffee
+++ rewriter.coffee
@@ -58,7 +58,8 @@
     levels = 0
     loop
       break unless token = @tokens[i]
-      return action token, i if !levels and condition token, i
+      return action token, i if levels is 0 and condition token, i
+      return action token, i - 1 if levels < 0
       levels += 1 if include EXPRESSION_START, token[0]
       levels -= 1 if include EXPRESSION_END, token[0]
       i += 1
@@ -103,56 +104,44 @@
   # its paired close.
   closeOpenCalls: ->
     @scanTokens (prev, token, post, i) =>
-      condition = (token, i) -> token[0] in [')', 'CALL_END']
-      action    = (token, i) -> token[0] = 'CALL_END'
-      @detectEnd(i + 1, condition, action) if token[0] is 'CALL_START'
+      if token[0] is 'CALL_START'
+        condition = (token, i) -> token[0] in [')', 'CALL_END']
+        action    = (token, i) -> token[0] = 'CALL_END'
+        @detectEnd i + 1, condition, action
       return 1

   # The lexer has tagged the opening parenthesis of an indexing operation call.
   # Match it with its paired close.
   closeOpenIndexes: ->
     @scanTokens (prev, token, post, i) =>
-      condition = (token, i) -> token[0] in [']', 'INDEX_END']
-      action    = (token, i) -> token[0] = 'INDEX_END'
-      @detectEnd(i + 1, condition, action) if token[0] is 'INDEX_START'
+      if token[0] is 'INDEX_START'
+        condition = (token, i) -> token[0] in [']', 'INDEX_END']
+        action    = (token, i) -> token[0] = 'INDEX_END'
+        @detectEnd i + 1, condition, action
       return 1

   # Object literals may be written with implicit braces, for simple cases.
   # Insert the missing braces here, so that the parser doesn't have to.
   addImplicitBraces: ->
-    stack   = [0]
-    running = no
-    closeBrackets = (i) =>
-      len = stack.length - 1
-      for tmp in [0...stack[len]]
-        @tokens.splice(i, 0, ['}', '}', @tokens[i][2]])
-      size = stack[len] + 1
-      stack[len] = 0
-      size
-    @scanTokens (prev, token, post, i) =>
-      tag    = token[0]
-      len    = stack.length - 1
+    stack = []
+    @scanTokens (prev, token, post, i) =>
+      if include EXPRESSION_START, token[0]
+        stack.push(if (token[0] is 'INDENT' and (prev and prev[0] is '{')) then '{' else token[0])
+      if include EXPRESSION_END, token[0]
+        stack.pop()
+      last = stack[stack.length - 1]
       before = @tokens[i - 2]
-      after  = @tokens[i + 2]
-      open   = stack[len] > 0
-      if (tag is 'TERMINATOR' and not ((after and after[0] is ':') or (post and post[0] is '@' and @tokens[i + 3] and @tokens[i + 3][0] is ':'))) or
-          (running and tag is ',' and post and (post[0] not in ['IDENTIFIER', 'STRING', '@', 'TERMINATOR']))
-        running = no
-        return closeBrackets(if post and post[0] is 'OUTDENT' then i + 1 else i)
-      else if include EXPRESSION_START, tag
-        stack.push(if tag is '{' then 1 else 0)
-        return 2 if tag is '{' and post and post[0] is 'INDENT'
-      else if include EXPRESSION_END, tag
-        return 1 if tag is 'OUTDENT' and post and post[0] is '}'
-        size = closeBrackets(i) if tag is 'OUTDENT'
-        stack[len - 1] += stack.pop()
-        stack[len - 1] -= 1 if tag is '}'
-        return size if tag is 'OUTDENT'
-      else if tag is ':' and not open
-        idx = if before and before[0] is '@' then i - 2 else i - 1
+      if token[0] is ':' and (not last or last[0] isnt '{')
+        stack.push '{'
+        idx = if before[0] is '@' then i - 2 else i - 1
         @tokens.splice idx, 0, ['{', '{', token[2]]
-        stack[stack.length - 1] += 1
-        running = yes
+        condition = (token, i) =>
+          [one, two, three] = @tokens.slice(i + 1, i + 4)
+          ((token[0] in ['TERMINATOR', 'OUTDENT']) and not ((two and two[0] is ':') or (one and one[0] is '@' and three and three[0] is ':'))) or
+            (token[0] is ',' and one and (one[0] not in ['IDENTIFIER', 'STRING', '@', 'TERMINATOR', 'OUTDENT']))
+        action = (token, i) =>
+          @tokens.splice i, 0, ['}', '}', token[2]]
+        @detectEnd i + 2, condition, action
         return 2
       return 1


Restructure src directory (consolidate with specs)

--- src/index.coffee
+++ src/index.coffee
@@ -15,9 +15,9 @@
     @_processManagerInstances = {}
     @_storeClasses = {}
     @_remoteEndpoints = []
-    @log = require './logger'
-    @addRemoteEndpoint 'inmemory', (require './remote/inmemory').endpoint
-    @addStore 'inmemory', require './store_inmemory'
+    @log = require 'eventric/src/logger'
+    @addRemoteEndpoint 'inmemory', (require 'eventric/src/remote/inmemory').endpoint
+    @addStore 'inmemory', require 'eventric/src/store_inmemory'
     @set 'default domain events store', 'inmemory'