psuedo code

psuedo code

 On branch master

	modified:   capsulate.coffee

--- capsulate.coffee
+++ capsulate.coffee
@@ -12,9 +12,9 @@
         return no
 })

-exports.proto = proto = Object.create(null)
-
-proto.create = ->
+exports.proto = gProto = Object.create(null)
+
+gProto.create = ->
     defs = @definitions
     rv = Object.keys(defs).reduce((rv, key) ->
         rv[key] = if _.hasProperty(defs[key], 'defaultValue')
@@ -26,7 +26,7 @@
     , Object.create(null))
     return rv

-proto.clean = (aObject) ->
+gProto.clean = (aObject) ->
     defs = @definitions
     rv = Object.keys(defs).reduce((rv, key) ->
         rv[key] = aObject[key]
@@ -34,7 +34,7 @@
     , Object.create(null))
     return rv

-proto.coerce = (aObject) ->
+gProto.coerce = (aObject) ->
     defs = @definitions
     rv = Object.keys(aObject).reduce((rv, key) ->
         value = aObject[key]
@@ -45,7 +45,7 @@
     , Object.create(null))
     return rv

-proto.validate = (aObject) ->
+gProto.validate = (aObject) ->
     errors = Object.create(null)
     defs = @definitions

@@ -67,47 +67,52 @@
     if Object.keys(errors).length then return errors
     return null

-proto.extend = (aProto) ->
-    return
+gProto.extend = (aDefinitions) ->
+    if not _.isObject(aDefinitions) or Array.isArray(aDefinitions)
+        msg = "Definitions passed to .extend(aDefinitions) must be an Object."
+        throw new Error(msg)
+
+    return createModel(@, aDefinitions)

 exports.create = (aDefinitions) ->
-    definitions = Object.create(null)
-
-    for own key, val of aDefinitions
-        try
-            def = normalizeDefinition(val)
-        catch message
-            throw new Error("Definition error '#{key}': #{message}")
-
-        Object.defineProperty(definitions, key, {
-            enumerable: yes
-            value: def
-        })
-
-    self = Object.create(null, {
-        definitions:
+    if not _.isObject(aDefinitions) or Array.isArray(aDefinitions)
+        msg = "Definitions passed to create(aDefinitions) must be an Object."
+        throw new Error(msg)
+
+    return createModel(gProto, aDefinitions)
+
+
+createModel = do ->
+
+    # Extend an object using Object.defineProperty().
+    extend = (target, source) ->
+        for own key, val of source
+            if key isnt 'definitions'
+                Object.defineProperty(target, key, {
+                    enumerable: yes
+                    value: val
+                })
+        return target
+
+    create = (aParent, aChild) ->
+        model = Object.create(null)
+        # Only extend the methods of the parent object. The child object is
+        # just a definition dictionary.
+        model = extend(model, aParent)
+
+        # Create and extend the definitions with the .definitions property of
+        # the parent, the the child Object, which is the new definition
+        # dictionary.
+        definitions = extendDefinitions(aParent.definitions, aChild)
+        Object.defineProperty(model, 'definitions', {
             value: Object.freeze(definitions)
-        create:
-            enumerable: yes
-            value: proto.create
-        clean:
-            enumerable: yes
-            value: proto.clean
-        coerce:
-            enumerable: yes
-            value: proto.coerce
-        validate:
-            enumerable: yes
-            value: proto.validate
-    })
-
-    return Object.freeze(self)
-
-
-create = () ->
-    model = Object.create(null)
-    return Object.freeze(model)
+        })
+
+        # Freeze it to prevent accidental tampering.
+        return Object.freeze(model)
+
+    return create

 extendDefinitions = do ->
@@ -124,15 +129,15 @@
         })
         return definitions

-    extend = (aTarget, aSource) ->
+    extend = (aParent, aChild) ->
         defs = Object.create(null)

-        defs = Object.keys(aSource).reduce((defs, key) ->
-            return define(defs, key, aSource[key])
+        defs = Object.keys(aParent).reduce((defs, key) ->
+            return define(defs, key, aParent[key])
         , Object.create(null))

-        defs = Object.keys(aTarget).reduce((defs, key) ->
-            return define(defs, key, aTarget[key])
+        defs = Object.keys(aChild).reduce((defs, key) ->
+            return define(defs, key, aChild[key])
         , defs)

         return defs


Jaconda support

Jaconda support

--- plugins/jaconda/index.coffee
+++ plugins/jaconda/index.coffee
@@ -16,6 +16,8 @@
         error: event.error
         spiking: event.trigger.type == 'projectSpiking'
         rate: event.trigger.rate
+        comment: event.comment
+        user: event.user

     else

@@ -46,15 +48,20 @@
           id: res.body.message.id

   @render: Handlebars.compile(
-    '{{#if spiking}}' +
-      'Spike of {{rate}} exceptions/minute in {{project.name}}
' +
-      'Most recent error: {{error_string}} (details)' +
+    '{{#if comment}}' +
+      '{{user.name}} commented on {{error_string}} (details)' +
+      '
   "{{comment.message}}"' +
     '{{else}}' +
-      '{{title}} from {{project.name}}' +
-      '{{#if error}}' +
-        ' in {{error.context}} (details)' +
-        '
   {{error_string}}' +
-        '{{#if stack_trace_line}}
   {{stack_trace_line}}{{/if}}' +
+      '{{#if spiking}}' +
+        'Spike of {{rate}} exceptions/minute in {{project.name}}
' +
+        'Most recent error: {{error_string}} (details)' +
+      '{{else}}' +
+        '{{title}} from {{project.name}}' +
+        '{{#if error}}' +
+          ' in {{error.context}} (details)' +
+          '
   {{error_string}}' +
+          '{{#if stack_trace_line}}
   {{stack_trace_line}}{{/if}}' +
+        '{{/if}}' +
       '{{/if}}' +
     '{{/if}}'
   )


Add APIResults::copy method.

Add APIResults::copy method.

Creates a duplicate of the APIResults. The objects are copied by reference.

--- compiler/ContentAPI.coffee
+++ compiler/ContentAPI.coffee
@@ -188,7 +188,7 @@
         _res = @_items.pop()
         @length = @_items.length
         return _res
-
+    copy: -> new APIResults([@_items...])


(- fix CLI UX) interact: Exit on ⌃D, even with partially-entered text

--- Paws/Source/interactive.coffee
+++ Paws/Source/interactive.coffee
@@ -179,25 +179,28 @@
          @prevRows = cursorPos.rows

-      _ttyWrite = _interface._ttyWrite
       # These replace the usual behavior of pausing the input-stream (which means all input while
       # paused is buffered, and will eventually be dumped back out), and simply *ignores* all input
       # until unpaused. No buffering.
       _interface.pause = ->
          return if @paused
-         @_ttyWrite = haxTtyWrite
+         @_ttyWrite = paused_ttyWrite
          @paused = true
          @emit 'pause'
          return this

       _interface.resume = ->
          return unless @paused
-         @_ttyWrite = _ttyWrite
+         @_ttyWrite = active_ttyWrite
          @paused = false
          @emit 'resume'
          return this

-      haxTtyWrite = (s, key)->
+      active_ttyWrite = (s, key)->
+         if key.ctrl and key.name == 'd' then @close()
+         else _ttyWrite.apply this, arguments
+
+      paused_ttyWrite = (s, key)->
          if key.ctrl
             switch key.name
                when 'c' then @emit 'SIGINT'     # ^c (interrupt)
@@ -205,3 +208,5 @@
                when 'z'                         # ^z (process backgrounding)
                   return if process.platform == 'win32'
                   @emit 'SIGTSTP'
+
+      [_ttyWrite, _interface._ttyWrite] = [_interface._ttyWrite, active_ttyWrite]


Fix "athorized" typo

Fix "athorized" typo

--- packages/rocketchat-oauth2-server-config/oauth/client/oauth2-client.coffee
+++ packages/rocketchat-oauth2-server-config/oauth/client/oauth2-client.coffee
@@ -42,6 +42,6 @@

 Template.authorize.onRendered ->
 	@autorun (c) =>
-		if Meteor.user()?.oauth?.athorizedClients?.indexOf(@data.client_id()) > -1
+		if Meteor.user()?.oauth?.authorizedClients?.indexOf(@data.client_id()) > -1
 			c.stop()
 			$('button[type=submit]').click()


How about a heading

How about a heading

--- src/ThreeCSG.coffee
+++ src/ThreeCSG.coffee
@@ -42,6 +42,7 @@
         polygons.push polygon.calculateProperties()
     new ThreeBSP.Node polygons

+  # CSG Operations
   subtract: (other) =>
     [us, them] = [@tree.clone(), other.tree.clone()]
     us


Remove new_ prefix from MockMethod and ExpectsMethod closures.

--- src/TinyMock.coffee
+++ src/TinyMock.coffee
@@ -70,7 +70,7 @@
     fail(messages.ExpectsMethodAlreadyExists, expects_method_name) if Object.prototype[expects_method_name]?

   install_expects_method = ->
-    Object.prototype[expects_method_name] = new_ExpectsMethod(expects_method_name, mock_methods)
+    Object.prototype[expects_method_name] = ExpectsMethod(expects_method_name, mock_methods)

   uninstall_expects_method = ->
     delete Object.prototype[expects_method_name]
@@ -90,7 +90,7 @@
     cleanup_environment()

-new_ExpectsMethod = (expects_method_name, mock_methods)->
+ExpectsMethod = (expects_method_name, mock_methods)->

   expects_method = (method_name) ->
     check_usage(@, method_name, arguments.length)
@@ -105,7 +105,7 @@
     fail(messages.ReservedMethodName, method_name) if is_reserved_method_name(method_name)

   install_mock_method = (self, method_name) ->
-    mock_method = new_MockMethod(self, method_name)
+    mock_method = MockMethod(self, method_name)
     mock_method.install()
     mock_methods.register(mock_method)

@@ -118,7 +118,7 @@
   expects_method

-new_MockMethod = (object, method_name) ->
+MockMethod = (object, method_name) ->

   expectations = new ExpectationList()
   original_method = undefined


Add a safety

Add a safety

--- components/SetProps.coffee
+++ components/SetProps.coffee
@@ -22,6 +22,7 @@
     process: (event, payload) ->
       return unless event is 'data'
       unless instance
+        return unless typeof props is 'object'
         props = payload
         return
       instance.setProps payload


remove unnecessary require

remove unnecessary require

--- apps/order/test/routes.coffee
+++ apps/order/test/routes.coffee
@@ -2,7 +2,6 @@
 sinon     = require 'sinon'
 Backbone  = require 'backbone'
 routes    = require '../routes'
-Order     = require '../../../models/order'
 { fabricate } = require 'antigravity'
 { APP_URL } = require('../../../config')


specs: fix test names to describe what is tested

@jbenet be careful that you name tests to describe the functionality
they are actually testing

--- coffee/test/views/shell_editor_view.spec.coffee
+++ coffee/test/views/shell_editor_view.spec.coffee
@@ -244,7 +244,7 @@

     describe 'on Remixer:Toolbar:Click:Duplicate', ->

-      it 'should be triggered by a Remixer:Toolbar:Click:Duplicate event', ->
+      it 'should call add shell', ->
         view = new ShellEditorView options
         view.render()
         remixer = view.remixerViews[0]
@@ -267,7 +267,7 @@

     describe 'on Remixer:Toolbar:Click:Delete', ->

-      it 'should be triggered by a Remixer:Toolbar:Click:Delete event', ->
+      it 'should call remove shell', ->
         view = new ShellEditorView options
         view.render()
         remixer = view.remixerViews[0]
@@ -287,7 +287,7 @@

     describe 'on Remixer:SwapShell', ->

-      it 'should be triggered by a Remixer:SwapShell event ', ->
+      it 'should call swapSubShell', ->
         oldShell = new acorn.shells.LinkShell.Model
         newShell = new acorn.shells.ImageLinkShell.Model
         view = new ShellEditorView model: oldShell
@@ -336,7 +336,7 @@

     describe 'on Remixer:LinkChanged', ->

-      it 'should be triggered by a Remixer:LinkChanged event ', ->
+      it 'should trigger ShellEditor:ShellsUpdated', ->
         oldShell = new acorn.shells.LinkShell.Model
         newShell = new acorn.shells.ImageLinkShell.Model
         view = new ShellEditorView model: oldShell


Activity: private message input cssclass added

--- client/Social/Activity/views/privatemessage/form.coffee
+++ client/Social/Activity/views/privatemessage/form.coffee
@@ -98,5 +98,7 @@

     @inputs.recipient.addSubView @chatHeads
     @createUserAutoComplete()
-    @addSubView @inputWidget = new PrivateMessageInputWidget form : this
+    @addSubView @inputWidget = new PrivateMessageInputWidget
+      form     : this
+      cssClass : 'private'
     KD.utils.defer => @autoComplete.getView().setFocus()


Adding logs

Adding logs

--- src/meme-generator.coffee
+++ src/meme-generator.coffee
@@ -68,6 +68,7 @@

 memeResponder = () -> (robot, meme) ->
   robot.respond meme.regex, (msg) ->
+    console.log 'calling meme generator'
     memeGenerator msg, meme.generatorID, msg.match[2], msg.match[3], (img) ->
       msg.send img

@@ -81,13 +82,19 @@
     text0: text0
     text1: text1

+  console.log imgFlipUrl
+
   request.get imgFlipUrl, (e, res, body) ->
+    console.log arguments
+
     if e
       msg.reply err
       return

     jsonBody = JSON.parse(body)
     success = jsonBody?.success
+
+    console.log jsonBody, success

     unless success
       msg.reply jsonBody


make methods chainable, remove unnessessary returns

--- src/jquery.language.coffee
+++ src/jquery.language.coffee
@@ -15,6 +15,8 @@
     else
       l.config arguments[0]

+    this
+
   l.options =
     active: 'auto'
     fallback: 'en'
@@ -28,6 +30,7 @@
     l.options = $.extend(l.options, options)
     l.autodetectLanguageAndSet()
     initialized = true
+    this

   l.autodetectLanguageAndSet = ->
     if l.options.active == 'auto'
@@ -36,6 +39,7 @@
       possibleLanguage = l.options.active

     l.setLanguage possibleLanguage
+    this

   l.autodetectLanguage = ->
     $.url("?#{l.options.urlParam}") || $.cookie(l.options.cookieName) ||
@@ -63,14 +67,17 @@
       active: activeLanguage
       old: oldLanguage
     }]
-    return activeLanguage
+    activeLanguage

   l.isValid = (language) ->
     l.options.available.indexOf(language) != -1

+  l.fallback = () ->
+    l.options.fallback
+
   l.normalize = (language) ->
     return null if !language or typeof language != 'string'
-    return language.trim().substr(0, 2).toLowerCase()
+    language.trim().substr(0, 2).toLowerCase()

   return l


Ajout des actions spécifiques Arduino à la toolbar

--- atom/toolbar.cson
+++ atom/toolbar.cson
@@ -59,6 +59,38 @@
   }
   {
     type: "spacer"
+    show: "arduino"
+  }
+  {
+    type: "button"
+    callback: "arduino-upload:verify"
+    tooltip: "Verifies the sketches"
+    icon: "check"
+    show: "arduino"
+  }
+  {
+    type: "button"
+    callback: "arduino-upload:build"
+    tooltip: "Builds the current sketch"
+    icon: "rocket"
+    show: "arduino"
+  }
+  {
+    type: "button"
+    callback: "arduino-upload:upload"
+    tooltip: "Uploads the current sketch to a connected arduino"
+    icon: "cloud-upload"
+    show: "arduino"
+  }
+  {
+    type: "button"
+    callback: "arduino-upload:serial-monitor"
+    tooltip: "Opens the serial monitor"
+    icon: "browser"
+    show: "arduino"
+  }
+  {
+    type: "spacer"
   }
   {
     type: "button"


Better time output.

Better time output.

--- app/assets/javascripts/convert_timestamps.js.coffee
+++ app/assets/javascripts/convert_timestamps.js.coffee
@@ -1,11 +1,23 @@
 pretty_wording = (date) ->
   now = moment(Date.now())
-  relative = now.diff date, "days"
+  days = now.diff date, "days"
+  hours = now.diff date, "hours"
+  minutes = now.diff date, "minutes"

-  if relative < 3
+  if minutes < 30
     date.fromNow()
+  else if hours < 5
+    date.fromNow() + date.format "(HH:mm)"
+  else if hours < 24
+    date.format "h:mm a"
+  else if days < 6
+    date.format "ddd \\at HH:mm"
+  else if days < 365
+    date.format "MMM Do (HH:mm)"
   else
-    date.calendar()
+    date.format "MMM Do, YY"
+
+

 update_timestamps = () ->


Refactor Models.Profile.fetch using HTTP.Client

--- assets/javascripts/models/profile.js.coffee
+++ assets/javascripts/models/profile.js.coffee
@@ -15,37 +15,31 @@
   @fetch: (params = {}, options = {}) ->
     return unless (entity = params.entity)

-    # TODO: Add caching (just name and avatar)
+    # TODO: Add caching

     if (cid = @entity_mapping[entity])
       profile = @find(cid: cid)
       options.success?(profile)
       return

-    auth_middleware = []
-    if TentStatus.Helpers.isDomainEntity(entity)
-      api_root = TentStatus.config.tent_api_root
-      auth_middleware = @middleware.auth
-    else if TentStatus.Helpers.isEntityOnTentHostDomain(entity)
-      api_root = entity + TentStatus.config.tent_host_domain_tent_api_path
-    else
-      api_root = "#{TentStatus.config.tent_proxy_root}/#{encodeURIComponent entity}"
+    unless options.client
+      return HTTP.TentClient.find entity: entity, (client) =>
+        @fetch(params, _.extend(options, {client: client}))

-    new HTTP 'GET', api_root + '/profile', null, (res, xhr) =>
+    options.client.get '/profile', null, (res, xhr) =>
       if xhr.status != 200 || !res
         @trigger('fetch:failed', entity, res, xhr)
         options.error?(res, xhr)
         return

       if (cid = @entity_mapping[entity])
-        profile = @find(cid: cid)
+        profile = @find(cid: cid, fetch: false)
       else
         profile = new @(res)
         @entity_mapping[entity] = profile.cid

       @trigger('fetch:success', entity, profile, xhr)
       options.success?(profile, xhr)
-    , auth_middleware.concat(@middleware.tent)

   parseAttributes: (attributes) =>
     core_profile = attributes[TentStatus.config.CORE_PROFILE_TYPE]


Fix keymap not binding problem

Fix keymap not binding problem

--- keymaps/atom-latex.cson
+++ keymaps/atom-latex.cson
@@ -1,8 +1,8 @@
-'atom-text-editor[data-grammar~="latex"]':
-  'ctrl-alt-b': 'Atom-LaTeX:build'
+'atom-text-editor[data-grammar="text tex latex"]':
+  'ctrl-alt-b': 'atom-latex:build'
   'ctrl-alt-h': 'atom-latex:build-here'
-  'ctrl-alt-p': 'Atom-LaTeX:preview'
-  'ctrl-alt-t': 'Atom-LaTeX:preview-tab'
-  'ctrl-alt-k': 'Atom-LaTeX:kill'
+  'ctrl-alt-p': 'atom-latex:preview'
+  'ctrl-alt-t': 'atom-latex:preview-tab'
+  'ctrl-alt-k': 'atom-latex:kill'
   'ctrl-alt-s': 'atom-latex:synctex'
-  'ctrl-alt-l': 'Atom-LaTeX:show-log'
+  'ctrl-alt-l': 'atom-latex:show-log'


Add new keyboard shortcuts (#602)

Add new keyboard shortcuts (#602)

For disconnecting & reconnecting

--- keymaps/remote-ftp.cson
+++ keymaps/remote-ftp.cson
@@ -1,2 +1,4 @@
 'atom-workspace':
   'ctrl-alt-o': 'remote-ftp:toggle'
+  'ctrl-alt-d': 'remote-ftp:disconnect'
+  'ctrl-alt-c': 'remote-ftp:connect'


Command needs to be a String.

Command needs to be a String.

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -5,6 +5,7 @@
         app: 'app'
         dist: 'dist'
         test: 'test'
+        coverage: 'coverage'

     # Show elapsed time at the end.
     if grunt.option 'timing'
@@ -55,7 +56,7 @@
                         '!<%= yeoman.dist %>/.git*'
                     ]
                 ]
-            coverage: ['coverage']
+            coverage: ['<%= yeoman.coverage %>']
             compress: ['zip/TabAhead.zip']
             coffee:
                 src: ['<%= yeoman.app %>/scripts/*{.js,.js.map}']
@@ -186,7 +187,7 @@

         shell:
             coveralls:
-                command: ['cat coverage/*/lcov.info | ./node_modules/coveralls/bin/coveralls.js']
+                command: 'cat <%= yeoman.coverage %>/*/lcov.info | ./node_modules/coveralls/bin/coveralls.js'

     grunt.registerMultiTask 'updateVersion',
         'Update the version key of .json files.', ->


Using matchdep to get dev packages

Using matchdep to get dev packages

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -19,10 +19,7 @@
           'dist/<%= pkg.name %>.min.js': 'dist/<%= pkg.name %>.js'

-  grunt.loadNpmTasks 'grunt-coffeelint'
-  grunt.loadNpmTasks 'grunt-contrib-concat'
-  grunt.loadNpmTasks 'grunt-contrib-uglify'
-  grunt.loadNpmTasks 'grunt-contrib-coffee'
+  require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks)

   grunt.registerTask 'test', 'Lints and unit tests', ['coffeelint']
   grunt.registerTask 'default', 'Default task', ['test', 'coffee', 'uglify']


show number of all similar crashes in crashreport view

--- client/coffee/crash_report.coffee
+++ client/coffee/crash_report.coffee
@@ -45,6 +45,10 @@
         frag = frag.substring(1)
         return null if frag == ""
         decodeURIComponent frag
+
+fetch_all_similar_crashes = (id, cb) ->
+    $.getJSON "/similarcrashes/#{id}", (result) ->
+        cb? result

 load_crashreport_data = (id, callback) ->
      $.getJSON "/crashreports/#{id}", (data) ->
@@ -160,7 +164,7 @@
             row.find('.stack_trace_info').text line
         else
             [_, frameNr, _, address, _, func, args, context, _, isLib, location] = match
-
+
             row = row_template.clone()
             row.find('.stack_trace_frame').text("#" + frameNr)
             row.find('.stack_trace_address').text if address then address else "<unknown>"
@@ -191,6 +195,11 @@
     #Analysis
     $('#app_similar_crashes_num').text "-"
     $('#all_similar_crashes_num').text "-"
+    fetch_all_similar_crashes crashreport.id, (result) ->
+        console.log result
+        console.log result.data.length
+        if result?.data
+            $('#all_similar_crashes_num').text result.data.length

     # Related Bugs
     $('#related_bugs .tab1').empty()
@@ -219,7 +228,7 @@
     for name, thread of crashreport["stack-trace"]["threads"]
         render_stacktrace name, thread["pid"], null, thread["stack"]
     $('.stack_trace:first').detach()
-
+
     #Process state
     render_process_state crashreport


Update Checkins.coffee

Update Checkins.coffee

--- client/components/Checkins.coffee
+++ client/components/Checkins.coffee
@@ -56,6 +56,10 @@
             <p>
             Не забудьте с собой паспорт (если паспорта еще нет, то свидетельство о рождении) -- его могут спросить охранники на входе!
             </p>
+
+            <Alert bsStyle="danger">
+                1 марта занятия не будет из-за городской олимпиады, ниже запись на 8 марта.
+            </Alert>

             {
             if @state.result?.error


Add payment link to bottom panel

Add payment link to bottom panel

--- client/components/Sceleton.coffee
+++ client/components/Sceleton.coffee
@@ -54,7 +54,9 @@

 class PaidTill extends React.Component
     render: () ->
+        href = "/pay"
         if @props.myUser?.userList == "stud" or @props.myUser?.userList == "notnnov"
+            href = "/payment"
             if @props.myUser?.paidTill && isPaid(@props.myUser)
                 preLink = "Занятия оплачены до " + moment(@props.myUser.paidTill).format("DD.MM.YYYY") + " "
                 inLink = "Продлить"
@@ -69,7 +71,7 @@
             inLink = "Поддержать занятия"
         <span>
             {preLink}
-            <Link to="/material/pay">
+            <Link to={href}>
                 {inLink}
                 {" "}
                 <FontAwesome name="cc-visa"/>


Updated test templates

Updated test templates

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -46,28 +46,28 @@
       grunt: ['gruntfile.coffee']
     template:
       signature:
-        src: 'templates/browsertest.html'
+        src: 'test/templates/browsertest.html'
         dest: 'test/browser/signature.html'
         engine: 'handlebars'
         variables:
           title: 'Signature'
           script: '../all/signature.js'
       environment:
-        src: 'templates/browsertest.html'
+        src: 'test/templates/browsertest.html'
         dest: 'test/browser/environment.html'
         engine: 'handlebars'
         variables:
           title: 'Environment'
           script: '../all/environment.js'
       injection:
-        src: 'templates/browsertest.html'
+        src: 'test/templates/browsertest.html'
         dest: 'test/browser/injection.html'
         engine: 'handlebars'
         variables:
           title: 'Injection'
           script: '../all/injection.js'
       module:
-        src: 'templates/browsertest.html'
+        src: 'test/templates/browsertest.html'
         dest: 'test/browser/module.html'
         engine: 'handlebars'
         variables:


Small path bugfix

Small path bugfix

--- src/lib/packages.coffee
+++ src/lib/packages.coffee
@@ -41,7 +41,7 @@
             for sVersion, oVersionInfos of oPackage.versions
                 if oVersionInfos.dist and oVersionInfos.dist.tarball
                     oPackage.versions[ sVersion ].dist.tarball = oPackage.versions[ sVersion ].dist.tarball.replace config.npm.registry, "http://localhost:#{ config.server.port }"
-            if not fs.existsSync sPackageCachePath
+            if not fs.existsSync sPackageCacheFolder
                 fs.mkdirSync sPackageCacheFolder
             fs.writeFileSync sPackageCachePath, JSON.stringify oPackage
             oResponse.json oPackage


Zone offset parsing, more docco.

Zone offset parsing, more docco.

--- src/lib/timezone.coffee
+++ src/lib/timezone.coffee
@@ -352,14 +352,24 @@
       # Gather up our output into a string.
       output.join ""

-  # Create a date, filling in the blanks.
+  ##### makeDate(year, month, day, hours, minutes, seconds, milliseconds)
+  #
+  # Create a wallclock time record from the given units extracted from a parsed
+  # date string.
+
+  # Wrap helper methods in a function scope.
   makeDate = do ->
+    # Push a zero onto date fields if the value is undefined.
     zeroUnless = (date, value) ->
       if value?
         date.push parseInt value, 10
       else
         date.push 0

+    # This method will substitute for missing values. If given only a time, it
+    # will use the current date. If given only a date, it will use midnight for
+    # the time. If given a year without a month or day, it is the first of the
+    # year. If given a month with no day, it will use the first of the month.
     (year, month, day, hours, minutes, seconds, milliseconds) ->
       date = []

@@ -385,7 +395,7 @@
       # todays date. If we have a year, then we have a date with no day, so we
       # assume the first of the month.
       if not day?
-        if not year?
+        if not month?
           date.push now.getUTCDate()
         else
           date.push 1
@@ -440,12 +450,12 @@
       )?
       (?:           # optional zone
         (?:\s+|Z)     # zone delimiter
-        (?:
-          ([+-])      # sign
-          (\d{2})     # hours
+        (
+          [+-]      # sign
+          \d{2}     # hours
           (?:         # optional minutes
             :?          # optional colon
-            (\d{2})     # minutes
+            \d{2}       # minutes
           )?
         )
       )?
@@ -468,8 +478,8 @@
       [ hours, minutes, seconds, milliseconds ] = time if time[0]?

       # See if we matched a zone offset.
-      zone = match.splice(0, 3)
-      [ sign, zoneHours, zoneMinutes ] = zone if zone[0]?
+      zone = match.shift()
+      zoneOffset = offsetInMilliseconds(zone) if zone?

       # Stuff fater the matched ISO date.
       after = match.pop()


Guard against badly saved observable

Guard against badly saved observable

--- assets/javascripts/app.coffee
+++ assets/javascripts/app.coffee
@@ -1,6 +1,13 @@
 React = require 'react'
 Page = require './react/page'
+Persister = require './persistency/persister'

-document.addEventListener "DOMContentLoaded", ->
-  React.render(<Page />, document.getElementById('content'))
+renderPage = ->
+  try
+    React.render(<Page />, document.getElementById('content'))
+  catch e
+    console.error "Couldn't render page, retrying", e
+    Persister.save(null)
+    renderPage()

+document.addEventListener "DOMContentLoaded", renderPage


replace selector .workspace with atom-workspace

--- keymaps/csso.cson
+++ keymaps/csso.cson
@@ -1,3 +1,3 @@
-'.workspace':
+'atom-workspace':
   'ctrl-o': 'csso:execute'
   'ctrl-shift-o': 'csso:restructure'


fixes #243: Survey doesn't load when the device ID question is checked

--- dkobo/koboform/static/js/xlform/model_xlform.coffee
+++ dkobo/koboform/static/js/xlform/model_xlform.coffee
@@ -337,7 +337,11 @@
 class XLF.Rows extends Backbone.Collection
   model: (obj, ctxt)->
     type = obj?.type
-    if type in ["start", "end"]
+    formSettingsTypes = do ->
+      for key, val of XLF.defaultSurveyDetails
+        val.asJson.type
+
+    if type in formSettingsTypes
       new XLF.SurveyDetail(obj)
     else if type is "group"
       new XLF.Group(obj)


remove stray console.log

remove stray console.log

--- client/skr/api/EventCheckout.cjsx
+++ client/skr/api/EventCheckout.cjsx
@@ -33,7 +33,6 @@
         )

     render: ->
-        console.log @getSaleOptions()
         <div>
             <Lanes.Components.NetworkActivityOverlay model={@event} />
             <Skr.Api.SingleItemCheckout


Add a keys method for BaseClass

Add a keys method for BaseClass

--- framer/BaseClass.coffee
+++ framer/BaseClass.coffee
@@ -26,7 +26,7 @@
 		Object.defineProperty @prototype, propertyName, descriptor
 		Object.__

-	@SimpleProperty = (name, fallback, exportable=true) ->
+	@simpleProperty = (name, fallback, exportable=true) ->
 		exportable: exportable
 		default: fallback
 		get: ->  @_getPropertyValue name
@@ -44,6 +44,9 @@

 	_propertyList: ->
 		@constructor[DefinedPropertiesKey]
+
+	keys: ->
+		_.keys @properties

 	@define "properties",
 		get: ->


Repairing tests

Repairing tests

--- framer/TextLayer.coffee
+++ framer/TextLayer.coffee
@@ -50,6 +50,7 @@

 		super options

+		@font ?= @fontFamily
 		# Set padding
 		@padding = options.padding or Utils.rectZero()


Properly dispose of most subscriptions

Properly dispose of most subscriptions

--- src/beautify.coffee
+++ src/beautify.coffee
@@ -4,6 +4,7 @@

 # Dependencies
 plugin = module.exports
+{CompositeDisposable} = require 'atom'
 _ = require("lodash")
 Beautifiers = require("./beautifiers")
 beautifier = new Beautifiers()
@@ -447,9 +448,13 @@
 Subscriber.extend plugin
 plugin.config = _.merge(require('./config.coffee'), defaultLanguageOptions)
 plugin.activate = ->
-  handleSaveEvent()
+  @subscriptions = new CompositeDisposable
+  @subscriptions.add handleSaveEvent()
   plugin.subscribe atom.config.observe("atom-beautify.beautifyOnSave", handleSaveEvent)
-  atom.commands.add "atom-workspace", "atom-beautify:beautify-editor", beautify
-  atom.commands.add "atom-workspace", "atom-beautify:help-debug-editor", debug
-  atom.commands.add ".tree-view .file .name", "atom-beautify:beautify-file", beautifyFile
-  atom.commands.add ".tree-view .directory .name", "atom-beautify:beautify-directory", beautifyDirectory
+  @subscriptions.add atom.commands.add "atom-workspace", "atom-beautify:beautify-editor", beautify
+  @subscriptions.add atom.commands.add "atom-workspace", "atom-beautify:help-debug-editor", debug
+  @subscriptions.add atom.commands.add ".tree-view .file .name", "atom-beautify:beautify-file", beautifyFile
+  @subscriptions.add atom.commands.add ".tree-view .directory .name", "atom-beautify:beautify-directory", beautifyDirectory
+
+plugin.deactivate = ->
+  @subscriptions.dispose()


refactoring:use UserEnvironmentDataProvider.fetchMachineByUId

--- client/ide/lib/idehelpers.coffee
+++ client/ide/lib/idehelpers.coffee
@@ -4,6 +4,7 @@
 globals = require 'globals'
 FSHelper = require 'app/util/fs/fshelper'
 FilePermissionsModal = require './views/modals/filepermissionsmodal'
+dataProvider = require 'app/userenvironmentdataprovider'

 WORKSPACE_WELCOME_TXT = """
   # Awesome, you've just made a new workspace!
@@ -49,37 +50,36 @@
       err = mesage: "Machine not found."
       return helpers.handleWorkspaceCreateError_ eventObj, err

-    #look for workspace by rootPath in globals.userEnvironmentData
-    workspaces = o.workspaces for o in globals?.userEnvironmentData?.own when o.machine.uid is machineUId
-    workspace = w for w in workspaces when w.rootPath is rootPath
+    dataProvider.fetchMachineByUId machineUId, (machine, workspaces) =>
+      workspace = w for w in workspaces when w.rootPath is rootPath

-    handleRoute = (machine, workspace) ->
-      href = "/IDE/#{machine.slug or machine.label}/#{workspace.slug}"
-      router.handleRoute href
+      handleRoute = (machine, workspace) ->
+        href = "/IDE/#{machine.slug or machine.label}/#{workspace.slug}"
+        router.handleRoute href

-    return handleRoute(machine, workspace) if workspace
+      return handleRoute(machine, workspace) if workspace

-    remote.api.JWorkspace.create data, (err, workspace) =>
-      return helpers.handleWorkspaceCreateError_ eventObj, err  if err
-
-      folderOptions  =
-        type         : 'folder'
-        path         : workspace.rootPath
-        recursive    : yes
-        samePathOnly : yes
-
-      machine.fs.create folderOptions, (err, folder) =>
+      remote.api.JWorkspace.create data, (err, workspace) =>
         return helpers.handleWorkspaceCreateError_ eventObj, err  if err

-        filePath   = "#{workspace.rootPath}/README.md"
-        readMeFile = FSHelper.createFileInstance { path: filePath, machine }
+        folderOptions  =
+          type         : 'folder'
+          path         : workspace.rootPath
+          recursive    : yes
+          samePathOnly : yes

-        readMeFile.save WORKSPACE_WELCOME_TXT, (err) =>
+        machine.fs.create folderOptions, (err, folder) =>
           return helpers.handleWorkspaceCreateError_ eventObj, err  if err

-          eventObj.emit 'WorkspaceCreated', workspace
+          filePath   = "#{workspace.rootPath}/README.md"
+          readMeFile = FSHelper.createFileInstance { path: filePath, machine }

-          handleRoute(machine, workspace)
+          readMeFile.save WORKSPACE_WELCOME_TXT, (err) =>
+            return helpers.handleWorkspaceCreateError_ eventObj, err  if err
+
+            eventObj.emit 'WorkspaceCreated', workspace
+
+            handleRoute(machine, workspace)

   handleWorkspaceCreateError_: (eventObj, error) ->


UI Copy: white label => white-label

UI Copy: white label => white-label

--- client/landing/Intro/home.coffee
+++ client/landing/Intro/home.coffee
@@ -86,7 +86,7 @@
       client    : '#home-page .browser'
       container : this
       wait      : 2200
-      message   : 'WHITE LABEL KODING'
+      message   : 'WHITE-LABEL KODING'
       offset    :
         top     : 25
         left    : 25
@@ -146,7 +146,7 @@

-
WHITE LABEL KODING

+
WHITE-LABEL KODING

               You can have your private Koding in the cloud, with your rules, your apps and your own members. Please contact us for further information.


fix: `notify` should be `realtime`

fix: `notify` should be `realtime`

--- app/initializers/irene.coffee
+++ app/initializers/irene.coffee
@@ -11,7 +11,7 @@
   application.inject 'authenticator', 'notify', 'service:notification-messages'

   # Inject realtime
-  application.inject 'component', 'notify', 'service:realtime'
+  application.inject 'component', 'realtime', 'service:realtime'

   # Inject Store
   application.inject 'component', 'store', 'service:store'


Flash red for invalid inputs

Flash red for invalid inputs

--- app/js/directives/misc.coffee
+++ app/js/directives/misc.coffee
@@ -39,6 +39,10 @@
       transformedInput = text.replace(/[^0-9]/g, "")
       console.log transformedInput
       if transformedInput isnt text
+        element.css('background-color', 'pink');
+        setTimeout (->
+            element.css('background-color', 'none');
+        ), 500
         ngModelCtrl.$setViewValue transformedInput
         ngModelCtrl.$render()
       transformedInput # or return Number(transformedInput)


Set timeout via AJAX when app starts

Set timeout via AJAX when app starts

--- app/js/services/wallet.coffee
+++ app/js/services/wallet.coffee
@@ -14,16 +14,19 @@

     pendingRegistrations: {}

+    console.log('executing wallet')
+
     check_wallet_status : ()->
       @wallet_get_info().then (result) =>
         if result.state == "open"
-            #redirection
-            #remove the rpc call
             if result.locked
                 @location.path("/unlockwallet")
             @get_setting('timeout').then (result) =>
                 if result && result.value
                     @timeout=result.value
+                    @idle._options().idleDuration=@timeout
+                    @idle.watch()
+                    console.log('@idle._options()', @idle._options())
         else
             @open().then =>
                 #redirection
@@ -245,8 +248,8 @@
               owner_key: val.owner_key
           reg

-    constructor: (@q, @log, @location, @growl, @rpc, @blockchain, @utils, @wallet_api, @blockchain_api, @interval) ->
+    constructor: (@q, @log, @location, @growl, @rpc, @blockchain, @utils, @wallet_api, @blockchain_api, @interval, @idle) ->
         @log.info "---- Wallet Constructor ----"
         @wallet_name = ""

-angular.module("app").service("Wallet", ["$q", "$log", "$location", "Growl", "RpcService", "Blockchain", "Utils", "WalletAPI", "BlockchainAPI", "$interval", Wallet])
+angular.module("app").service("Wallet", ["$q", "$log", "$location", "Growl", "RpcService", "Blockchain", "Utils", "WalletAPI", "BlockchainAPI", "$interval", "$idle", Wallet])


added search handler to appointment

added search handler to appointment

--- app/models/appointment.coffee
+++ app/models/appointment.coffee
@@ -1,6 +1,7 @@
 `import DS from 'ember-data'`
 `import {Mixins} from 'autox'`
 `import History from 'apiv4/utils/history'`
+`import Query from 'autox/utils/query'`
 {Relateable, Realtime, Timestamps, Historical, Multiaction, Paranoia} = Mixins
 Model = DS.Model.extend Paranoia, Timestamps, Relateable, Realtime, Historical, Multiaction,
   description: DS.attr "string",
@@ -19,6 +20,12 @@
     description: "The associated company with whom this appointment is for"
     display: ["show", "index"]
     modify: ["new", "edit"]
+    search: (name) ->
+      q = new Query
+      .filterBy "name", "i~", name
+      .orderBy "name"
+      .pageBy limit: 25, offset: 0
+      @store.query "company", q.toParams()
     among: -> @store.findAll "company"
     proxyKey: "name"
     async: true


解答権ハンドリング

解答権ハンドリング

--- src/progress/index.coffee
+++ src/progress/index.coffee
@@ -15,6 +15,7 @@
   tryAnswer: (e) ->
     elm = @_findColumnNode(e)
     console.debug(elm.getAttribute('data-playerid') + ': try answer.')
+    @dispatch 'try-answer', elm.getAttribute('data-playerid')

 class Player
@@ -35,3 +36,14 @@
     programName: props.programName
     players: state.players
     quizCount: state.quizCount
+
+  delegate: (subscribe) ->
+    super
+
+    subscribe 'try-answer', (playerId) ->
+      hasAnswer = @state.players.some (val, idc, arr) ->
+        return val.isAnswer
+      if hasAnswer
+        console.debug('already answers')
+      @state.players[playerId].isAnswer = true
+


Re-enabled snippets package in atom

Re-enabled snippets package in atom

--- atom/atom.symlink/config.cson
+++ atom/atom.symlink/config.cson
@@ -32,7 +32,6 @@
       "spell-check"
       "language-perl"
       "welcome"
-      "snippets"
       "bookmarks"
       "autoflow"
       "autosave"


refactored inspections URL and fetch options

--- app/models/inspections.coffee
+++ app/models/inspections.coffee
@@ -9,10 +9,15 @@
 module.exports = class Inspections extends Collection

   model: Inspection
-  url: ->
-    "http://data.cityofchicago.org/resource/4ijn-s7e5.json?$limit=#{@limit}&$select=#{DEFAULT_COLUMNS.join(',')}"
+  url: "http://data.cityofchicago.org/resource/4ijn-s7e5.json"

   limit: DEFAULT_LIMIT
+  columns: do -> DEFAULT_COLUMNS.join(',')
+
+  getDefaultData: ->
+    '$limit'  : @limit
+    '$select' : @columns
+
   responseLength: 0

   searchType: null
@@ -60,6 +65,7 @@
     if options.query?
       options.data['$q'] = options.query
       @_setSearchString(options.query)
+      delete options.query

     # was there a search query in the past, and should we use it?
     else if @searchString?
@@ -95,14 +101,15 @@
     @_fetchSearch(options)
     @searchType = 'zip'

-  _fetchSearch: (options)->
+  _fetchSearch: (options={})->
     # is this fetch looking for more results of an existing search?
     if @responseLength >= @limit
       options.data = options.data || {}
       options.data['$offset'] = @responseLength
       options.remove = false

+    options.data = _.extend @getDefaultData(), options.data
     @fetch(options)

   _setSearchString: (searchString)->
-    @searchString = decodeURI searchString+    @searchString = decodeURI searchString


Add link to status page to footer

Add link to status page to footer

Also make privacy policy title case to match other links.

--- app/partials/main-footer.cjsx
+++ app/partials/main-footer.cjsx
@@ -29,7 +29,8 @@
     boilerplate:
       contact: 'Contact Us'
       jobs: 'Jobs'
-      privacyPolicy: 'Privacy policy'
+      privacyPolicy: 'Privacy Policy'
+      status: 'System Status'

 AdminToggle = React.createClass
   displayName: 'AdminToggle'
@@ -120,6 +121,8 @@
           <Link to="/privacy"><Translate content="footer.boilerplate.privacyPolicy" /></Link>

           <Translate content="footer.boilerplate.jobs" />
+
+          <Translate content="footer.boilerplate.status" />


Added ga beacon build option

Added ga beacon build option

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -40,6 +40,18 @@
             'Contrib/PhantomJS/runner.js'
             'Auditor/HTMLCSAuditor.js'
           ],
+      beacon:
+        options:
+          banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %> */\n' + grunt.file.read('Contrib/Build/umd-header.js')
+          footer: grunt.file.read('Contrib/Build/umd-footer.js')
+        files:
+          'build/HTMLCS.js': [
+            'Standards/**/*.js'
+            'HTMLCS.js'
+            'HTMLCS.Util.js'
+            'Contrib/PhantomJS/runner.js'
+            'Auditor/Auditor_with_beacon.js'
+          ],

     copy:
       dist:
@@ -77,4 +89,5 @@

   grunt.registerTask 'default', ['jshint']
   grunt.registerTask 'build',   ['uglify:dist', 'copy:dist']
+  grunt.registerTask 'build-beacon', ['uglify:beacon', 'copy:dist']
   grunt.registerTask 'build-debug', ['uglify:debug', 'copy:dist']


Add grunt watch for test resources

Add grunt watch for test resources

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -22,6 +22,9 @@
         files: ['gruntfile.coffee']
         options:
           reload: true
+      testResources:
+        files: ['spec/**/*.xml']
+        tasks: ['test']
     jshint:
       javaScript: ['src/**/*.js', 'spec/**/*.js']


Show org unit tree selector as a jQuery Mobile menu

--- src/opac/ou_tree.coffee
+++ src/opac/ou_tree.coffee
@@ -112,7 +112,7 @@
 						value: ou.id

 				# Using the options list, we build a selector.
-				$select = $('<select>').prop 'name', rc.name
+				$select = $('<select data-native-menu="false">').prop 'name', rc.name
 				$select.append x for x in options

 				# For the default option,


remove var no longer needed with simplified logic

--- coffee/directives/fixtable.coffee
+++ coffee/directives/fixtable.coffee
@@ -165,7 +165,7 @@

 			scope.pagePartiallySelected = ->
 				unless scope.selectedItems?.length and scope.data?.length then return false
-				if pageSelected = scope.pageSelected() then return false
+				if scope.pageSelected() then return false
 				for row in scope.data
 					if scope.rowSelected(row) then return true
 				return false


Black Eight squad pilot spoiled.

Black Eight squad pilot spoiled.

--- coffeescripts/cards-common.coffee
+++ coffeescripts/cards-common.coffee
@@ -2625,7 +2625,7 @@
             points: 26
         }
         {
-            name: 'Unspoiled PS4 TIE Punisher Pilot'
+            name: 'Black Eight Squadron Pilot'
             faction: "Galactic Empire"
             id: 145
             ship: "TIE Punisher"
@@ -2639,7 +2639,7 @@
                 "Bomb"
                 "Bomb"
             ]
-            points: 99
+            points: 23
         }
         {
             name: 'Cutlass Squadron Pilot'


handle no release repo

handle no release repo

--- legilimens.coffee
+++ legilimens.coffee
@@ -28,13 +28,17 @@
   callGithubAPI
     url: repoUrl + LATEST_RELEASE_PATH
     callback: (error, response, body) ->
-      if (!error and response.statusCode isnt 200)
-        console.log error, body
-        reject error
-      else
-        lastedReleaseTime = new Date(JSON.parse(body).created_at)
-        console.log "Last release time is #{lastedReleaseTime.toLocaleTimeString()} #{lastedReleaseTime.toLocaleDateString()}"
-        resolve(lastedReleaseTime)
+      switch response.statusCode
+        when 200
+          lastedReleaseTime = new Date(JSON.parse(body).created_at)
+          console.log "Last release time is #{lastedReleaseTime.toLocaleTimeString()} #{lastedReleaseTime.toLocaleDateString()}\n"
+          resolve(lastedReleaseTime)
+        when 404
+          console.log "No releases before"
+          resolve(new Date(1970,1,1))
+        else
+          console.log error, body, response.statusCode
+          reject error

 getClosedPullRequestsAfter = (time) ->
   callGithubAPI


Refactored code to make it work outside of poi

--- lib/flash-loader.coffee
+++ lib/flash-loader.coffee
@@ -1,14 +1,14 @@
 {app} = require 'electron'
-{join} = require 'path-extra'
-fs = require 'fs-extra'
-{error} = require './utils'
+{join} = require 'path'
+fs = require 'fs'
+
+error = console.error.bind console, '[ERROR] %s'

 PLATFORM = process.platform
 FILENAME = switch PLATFORM
   when 'darwin' then 'PepperFlashPlayer.plugin'
   when 'linux'  then 'libpepflashplayer.so'
   when 'win32'  then 'pepflashplayer.dll'
-INNER_PATH = join 'PepperFlash', PLATFORM, FILENAME

 validatePath = (path) ->
   return false if typeof path isnt 'string' or not path.endsWith FILENAME
@@ -17,11 +17,6 @@
   catch
     return false
   true
-
-getBuiltInFlashPath = ->
-  builtInPath = join EXECROOT, INNER_PATH if EXECROOT?
-  builtInPath = join ROOT, INNER_PATH if not validatePath builtInPath
-  if validatePath builtInPath then builtInPath else null

 reVerNum = /(\d+)\.(\d+)\.(\d+)\.(\d+)/
 getNewerVersion = (ver1, ver2) ->
@@ -79,64 +74,38 @@
     else ''

 class FlashPlayerVersions
-  constructor: (builtInFlashPath, chromeFlashPath, systemFlashPath) ->
-    @builtin = getFlashVersion builtInFlashPath if builtInFlashPath?
+  constructor: (chromeFlashPath, systemFlashPath) ->
     @chrome = getFlashVersion chromeFlashPath if chromeFlashPath?
     @system = getFlashVersion systemFlashPath if systemFlashPath?

 getAllVersions = ->
-  new FlashPlayerVersions getBuiltInFlashPath(), findChromeFlashPath(), findSystemFlashPath()
+  new FlashPlayerVersions findChromeFlashPath(), findSystemFlashPath()

-useFlashLoc = 'auto'
-CLIFlashPath = ''
-reCLIArg = /^--flash=(.+)$/i
-parseCLIArg = (arg) ->
-  if (m = reCLIArg.exec arg)?
-    value = m[1]
-    if value.startsWith '@'
-      useFlashLoc = value.slice 1
-    else
-      useFlashLoc = 'cli'
-      CLIFlashPath = value
-  m?
+flashSources = []
+addSource = (location) ->
+  flashSources.push location

-getPath = (loc = 'auto') ->
-  # The order of detecting Pepper Flash Player:
-  # 1. If '@builtin'/'@chrome'/'@system'/[a file path] is passed from CLI,
-  #    try to find flash player from the specified location.
-  # 2. If 1 failed, or '@auto' or nothing is passed from CLI (i.e., auto-detection),
-  #    try to find built-in flash player.
-  # 3. If 2 failed, try to find Google Chrome integrated flash player.
-  # 4. If 3 failed, try to find globally installed flash player.
-  switch loc.toLowerCase()
-    when 'auto'
-      flashPath = getBuiltInFlashPath()
-      flashPath ?= findChromeFlashPath()
-      flashPath ?= findSystemFlashPath()
-      error 'No installed Pepper Flash Player found' if not flashPath?
-      return flashPath
-    when 'builtin'
-      flashPath = getBuiltInFlashPath()
-      errMsg = 'Could not load built-in Pepper Flash Player'
-    when 'chrome'
-      flashPath = findChromeFlashPath()
-      errMsg = 'Could not load Chrome Pepper Flash Player'
-    when 'system'
-      flashPath = findSystemFlashPath()
-      errMsg = 'Could not load system Pepper Flash Player plug-in'
-    when 'cli'
-      flashPath = CLIFlashPath if validatePath CLIFlashPath
-      errMsg = "Invalid path to '#{FILENAME}': \n#{CLIFlashPath}"
-    else
-      errMsg = "Invalid Pepper Flash Player location: '@#{loc}'."
-  if not flashPath?
-    error errMsg
-    dbg.ex.flashLoader?.log 'Falling back to auto-detection'
-    flashPath = getPath()
+getPath = (loc) ->
+  if loc?
+    switch loc.toLowerCase()
+      when '@chrome'
+        flashPath = findChromeFlashPath()
+        errMsg = 'Could not load Chrome Pepper Flash Player'
+      when '@system'
+        flashPath = findSystemFlashPath()
+        errMsg = 'Could not load system Pepper Flash Player plug-in'
+      else
+        flashPath = loc if validatePath loc
+        errMsg = "Invalid path to '#{FILENAME}': \n#{loc}"
+  else
+    for loc in flashSources
+      flashPath = getPath loc
+      break if flashPath?
+  error errMsg if errMsg? and not flashPath?
   flashPath

 load = ->
-  flashPath = getPath useFlashLoc
+  flashPath = getPath()
   if flashPath?
     dbg.ex.flashLoader?.log "Loading Pepper Flash Player from:"
     dbg.ex.flashLoader?.log flashPath
@@ -147,8 +116,9 @@
     # But it's useless here, so we just ignore it.

 if process.type is 'browser'
-  exports.parseCLIArg = parseCLIArg
+  exports.addSource = addSource
   exports.load = load

+exports.FLASH_PLAYER_FILENAME = FILENAME
 exports.getFlashVersion = getFlashVersion
 exports.getAllVersions = getAllVersions


Set clicked ide view active view before closing it

--- client/ide/lib/views/tabview/ideview.coffee
+++ client/ide/lib/views/tabview/ideview.coffee
@@ -559,6 +559,8 @@
   closeSplitView: ->

     { frontApp } = kd.singletons.appManager
+    frontApp.setActiveTabView @tabView
+
     frontApp.mergeSplitView()


broker-client: use "services endpoint" to select a broker

--- client/landing/app/MainApp/mq.config.coffee
+++ client/landing/app/MainApp/mq.config.coffee
@@ -48,7 +48,6 @@
         dash queue, -> callback err, models, name

   mq: do->
-    {broker} = KD.config
-    options =
-      autoReconnect   : yes
-    broker = new KDBroker.Broker broker.sockJS, options+    { broker:{ servicesEndpoint } } = KD.config
+    options = { servicesEndpoint, autoReconnect: yes }
+    broker = new KDBroker.Broker null, options


return name on register

return name on register

--- lib/Server.coffee
+++ lib/Server.coffee
@@ -175,9 +175,9 @@
           if @options.redis
             @options.redis.store.hset "clients", name, socket.id, (err) =>
               return cb err if err?
-              cb()
+              cb null, name
           else
             @clients[name] = socket.id
-            cb()
+            cb null, name

 module.exports = Server


fuck,fucker,fucking,fucked,fucks

fuck,fucker,fucking,fucked,fucks

--- coffeeApp.coffee
+++ coffeeApp.coffee
@@ -74,7 +74,7 @@

-stream = Twitter.stream 'statuses/filter', {track:'fuck'}
+stream = Twitter.stream 'statuses/filter', {track:'fuck,fucker,fucking,fucked,fucks'}

 id = 0
 # Logic to get tweets


Add few types

Add few types

--- src/schema/index.coffee
+++ src/schema/index.coffee
@@ -53,22 +53,31 @@
 #
 # The schema API however ALWAYS WORKS WITH NAMES to ensure sanity. Names are immutable.

-class JsDate
-    padNumber = (number, l = 2) ->
+class JsTime
+    @padNumber: (number, l = 2) ->
         r = String(number)
         r = "0#{r}" while r.length < l
         return r

+class JsDate
     constructor: (@value) ->

     toString: () ->
-        dt = new Date(@value)
-        y = padNumber(dt.getFullYear(),4)
-        m = padNumber(dt.getMonth() + 1)
-        d = padNumber(dt.getDate())
-        h = padNumber(dt.getHours())
-        n = padNumber(dt.getMinutes())
-        s = padNumber(dt.getSeconds())
+        dt = new Date(@value.valueOf())
+        y = JsTime.padNumber(dt.getFullYear(),4)
+        m = JsTime.padNumber(dt.getMonth() + 1)
+        d = JsTime.padNumber(dt.getDate())
+        return "'#{y}-#{m}-#{d}'"
+
+class JsDateTime extends JsDate
+    toString: () ->
+        dt = new Date(@value.valueOf())
+        y = JsTime.padNumber(dt.getFullYear(),4)
+        m = JsTime.padNumber(dt.getMonth() + 1)
+        d = JsTime.padNumber(dt.getDate())
+        h = JsTime.padNumber(dt.getHours())
+        n = JsTime.padNumber(dt.getMinutes())
+        s = JsTime.padNumber(dt.getSeconds())
         return "'#{y}-#{m}-#{d} #{h}:#{n}:#{s}'"

 jsTypes = {
@@ -93,10 +102,10 @@
         numeric: false
         string: true

-    date:
+    datetime:
         matchesType: _.isDate
         # MUST: beef date conversion way up
-        convert: JsDate
+        convert: JsDateTime
         name: 'Date'
         numeric: false
 }
@@ -104,7 +113,7 @@

 dbTypeToJsType = {
     varchar: 'string'
-    datetime: 'date'
+    datetime: 'datetime'
     int: 'number'
 }


Fix: route name in help.coffee (change ' -> ")

--- src/scripts/help.coffee
+++ src/scripts/help.coffee
@@ -65,7 +65,7 @@

     msg.send emit

-  robot.router.get '/#{robot.name}/help', (req, res) ->
+  robot.router.get "/#{robot.name}/help", (req, res) ->
     cmds = robot.helpCommands().map (cmd) ->
       cmd.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')


add args type handle.

add args type handle.

--- lib/messageProxy.coffee
+++ lib/messageProxy.coffee
@@ -1,17 +1,23 @@
 do (window) ->
   messageProxy =
     on: (sources, listener) ->
-      sources = [ sources ]  unless _isArray(sources)
-      window.addEventListener "message", (event) ->
-        if event.origin in sources
-          listener(event.data)
-      , false
+      return  unless typeof listener is 'function'
+      if sources = _parseToArray(sources)
+        window.addEventListener "message", (event) ->
+          if event.origin in sources
+            listener(event.data)
+        , false

     emit: (targets, message) ->
-      targets = [ targets ]  unless _isArray(targets)
-      window.postMessage message, target for target in targets
-
+      return  unless typeof message is 'string' #todo 应支持任意obj
+      if targets = _parseToArray(targets)
+        window.postMessage message, target for target in targets
+
   _isArray = (obj) ->
     Object.prototype.toString.call(obj) is '[object Array]'

+  _parseToArray = (obj) ->
+    obj = [ obj ] if typeof handler is 'string'
+    if _isArray(obj) then obj else false
+
   window.messageProxy = window.messageProxy || messageProxy


Subjects for demo

Subjects for demo

--- src/scripts/site.coffee
+++ src/scripts/site.coffee
@@ -17,14 +17,15 @@
   config.set
     name: 'Bat Detective'
     slug: 'bat-detective'
-    description: 'PROJECT DESCRIPTION'
+    description: 'Help track bats across Europe'
+    talkHost: 'http://talk.batdetective.org'

-    talkHost: 'http://talk.batdetective.org'
+    domain: 'batdetective.org'
+    # googleAnalytics: 'X'

     cartoUser: 'CARTODB USER'
     cartoApiKey: 'CARTODB API KEY'
     cartoTable: 'CARTODB TABLE'
-    facebookId: 'FACEBOOK APP ID'

   config.set
     app: new App
@@ -42,11 +43,39 @@
               id: 'WORKFLOW ID'

               subjects: [
-                # For development
+                # For development only!
                 new Subject
                   location:
-                    standard: 'examples/images/bat-social.jpg'
-                    audio: 'examples/audio/bat-social.mp3'
+                    standard: 'examples/images/bat-feeding-buzz.jpg'
+                    audio: 'examples/audio/bat-feeding-buzz.mp3'
+                  coords: [0, 0]
+                  metadata: {}
+
+                new Subject
+                  location:
+                    standard: 'examples/images/bat-searching-horizontal-hockey-stick.jpg'
+                    audio: 'examples/audio/bat-searching-horizontal-hockey-stick.mp3'
+                  coords: [0, 0]
+                  metadata: {}
+
+                new Subject
+                  location:
+                    standard: 'examples/images/bat-searching-plateau.jpg'
+                    audio: 'examples/audio/bat-searching-plateau.mp3'
+                  coords: [0, 0]
+                  metadata: {}
+
+                new Subject
+                  location:
+                    standard: 'examples/images/bat-searching-vertical-hockey-stick.jpg'
+                    audio: 'examples/audio/bat-searching-vertical-hockey-stick.mp3'
+                  coords: [0, 0]
+                  metadata: {}
+
+                new Subject
+                  location:
+                    standard: 'examples/images/bat-searching-vertical-line.jpg'
+                    audio: 'examples/audio/bat-searching-vertical-line.mp3'
                   coords: [0, 0]
                   metadata: {}
               ]


Fixed wrong function argument

Fixed wrong function argument

--- src/modules/Sockets.coffee
+++ src/modules/Sockets.coffee
@@ -17,7 +17,7 @@

 class Sockets
   constructor: (dataServer, @scene, @players) ->
-    @socket = io.connect(dataServer)
+    @socket = io.connect(path: dataServer)
     @socket.on("players-position", @onPlayersPosition)
     @socket.on("player-disconnect", @onPlayerDisconnect)


Check runtime type before sending it, fixes #143

--- components/ConnectRuntime.coffee
+++ components/ConnectRuntime.coffee
@@ -45,6 +45,12 @@

   sendComponent: (runtime, component) ->
     return unless component.code
+
+    # Check for platform-specific components
+    runtimeType = component.code.match /@runtime ([a-z\-]+)/
+    if runtimeType and runtimeType[1] isnt runtime.definition.type
+      return
+
     runtime.sendComponent 'source',
       name: component.name
       language: component.language


time :-/

time :-/

--- components/clock/test/view.coffee
+++ components/clock/test/view.coffee
@@ -46,7 +46,8 @@
       @view.model.set
         is_auction: true
         start_at: moment().subtract(1, 'minutes').format()
-        end_at: moment().add(3, 'minutes').add(1, 'months').add(1, 'hours').add(1, 'seconds').format()
+        end_at: moment().add(3, 'minutes').add(1, 'months').add(1, 'hours')
+          .add(1, 'days').add(1, 'seconds').format()

       @view.model.calculateOffsetTimes()
       Backbone.sync.args[0][2].success { time: moment().format() }


KDDiaObject: Small improvements

KDDiaObject: Small improvements

--- components/dia/kddiaobject.coffee
+++ components/dia/kddiaobject.coffee
@@ -71,7 +71,7 @@
     [p, s, j]  = [@parent.getElement(), @getElement(), joint.getElement()]
     [ x , y  ] = [p.offsetLeft + s.offsetLeft, p.offsetTop + s.offsetTop]
     [ jx, jy ] = [j.offsetLeft, j.offsetTop]
-    [ dx, dy ] = if joint.type in ['left', 'right'] then [10, 2] else [2, 10]
+    [ dx, dy ] = if joint.type in ['left', 'right'] then [10, 4] else [4, 10]

     x:x + jx + dx, y: y + jy + dy


EnvironmentMachineItem: show publicUrl when needed

--- client/Environments/views/scene/environmentmachineitem.coffee
+++ client/Environments/views/scene/environmentmachineitem.coffee
@@ -150,15 +150,21 @@

   pistachio:->

-    {label, provider, ipAddress } = @getData()
+    {label, provider, ipAddress, status:{state} } = @getData()

-    title  = label or provider
+    title = label or provider

-    publicUrl = if ipAddress? then """
-
-        #{ipAddress}
-
-    """ else ""
+    publicUrl = ""
+
+    { NotInitialized, Terminated } = Machine.State
+
+    if state not in [ NotInitialized, Terminated ] and ipAddress?
+
+      publicUrl = """
+
+          #{ipAddress}
+
+      """

     """


Client: Activity: Feed: Formatting

Client: Activity: Feed: Formatting

--- client/activity/lib/components/publicfeedpane/feeditem.coffee
+++ client/activity/lib/components/publicfeedpane/feeditem.coffee
@@ -1,31 +1,33 @@
 kd                   = require 'kd'
 React                = require 'kd-react'
-ReactDOM             = require 'react-dom'
 Link                 = require 'app/components/common/link'
 TimeAgo              = require 'app/components/common/timeago'
 immutable            = require 'immutable'
 MessageLink          = require 'activity/components/messagelink'
 MessageBody          = require 'activity/components/common/messagebody'
 ProfileText          = require 'app/components/profile/profiletext'
-MessageLikeSummary   = require 'activity/components/common/messagelikesummary'
-ProfileLinkContainer = require 'app/components/profile/profilelinkcontainer'
-AutoSizeTextarea     = require 'app/components/common/autosizetextarea'
-ActivityLikeLink     = require 'activity/components/chatlistitem/activitylikelink'
+ActivityFlux         = require 'activity/flux'
 Comments             = require 'activity/components/comments'
 ActivitySharePopup   = require 'activity/components/activitysharepopup'
 Avatar               = require 'app/components/profile/avatar'
+MessageItemMenu      = require 'activity/components/messageitemmenu'
+classnames           = require 'classnames'
+FeedItemInputWidget  = require './feediteminputwidget'
+MessageLikeSummary   = require 'activity/components/common/messagelikesummary'
+ProfileLinkContainer = require 'app/components/profile/profilelinkcontainer'
+ActivityLikeLink     = require 'activity/components/chatlistitem/activitylikelink'

 module.exports = class FeedItem extends React.Component

   @defaultProps =
-    message: immutable.Map()
+    channelId : null
+    message   : immutable.Map()

   constructor: (props) ->

     super props

-    @state =
-      isPopupOpen  : no
+    @state = { isPopupOpen : no }

   shouldComponentUpdate: (nextProps, nextState) ->
@@ -36,6 +38,7 @@
   handleCommentLinkClick: (event) ->

     kd.utils.stopDOMEvent event
+
     @refs.Comments.focusCommentInput()


Fix bug where extend class defs were incorrect

--- src/support/Widget.coffee
+++ src/support/Widget.coffee
@@ -3,12 +3,12 @@
   @Widgets = {}

   class @Widget
-
     @extend: (protoProps, staticProps) ->
+      parent = @
       if Object.hasOwnProperty(protoProps, 'constructor')
         child = protoProps.constructor
       else
-        child = => @apply(@, arguments)
+        child = -> parent.apply(@, arguments)

       child.copyProperties @
       child.copyProperties staticProps


home/teamstacks: filter out managed vm stack

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

--- client/home/lib/stacks/components/teamstackslist/index.coffee
+++ client/home/lib/stacks/components/teamstackslist/index.coffee
@@ -21,6 +21,8 @@
     resources = calculateOwnedResources(props)
       .filter (resource) ->
         resource.stack and resource.template?.accessLevel isnt 'private'
+      .filter (resource) ->
+        resource.stack.title isnt 'Managed VMs'
       .map (resource) ->
         isVisible = if resource.stack
         then sidebar.isVisible 'stack', resource.stack.getId()


bidirectional nav: simplify

bidirectional nav: simplify

--- client/landing/app/CommonViews/bidirectionalnavigation.coffee
+++ client/landing/app/CommonViews/bidirectionalnavigation.coffee
@@ -3,17 +3,10 @@
   viewAppended: ->
     @setClass 'navigation'

-    @backButton = new KDButtonView
-      cssClass  : 'back'
-      title     : 'Back'
-      callback  : => @emit 'Back'
+    @addSubView @createButton 'Back'
+    @addSubView @createButton 'Next'

-    @forwardButton = new KDButtonView
-      cssClass  : 'next'
-      title     : 'Next'
-      callback  : => @emit 'Forward'
-
-    @addSubView @backButton
-    @addSubView @forwardButton
-
-      +  createButton: (action) -> new KDButtonView
+    cssClass  : action.toLowerCase()
+    title     : action
+    callback  : => @emit action


Path oopses

Path oopses

--- build/tasks/build-task.coffee
+++ build/tasks/build-task.coffee
@@ -20,7 +20,9 @@
     switch process.platform
       when 'darwin'
         cp 'atom-shell/Atom.app', shellAppDir, filter: /default_app/
-        exeDir = path.join(shellAppDir, 'Contents', 'MacOS')
+        fs.renameSync(path.join(shellAppDir, 'Atom.app'), path.join(shellAppDir, appName))
+
+        exeDir = path.join(shellAppDir, appName, 'Contents', 'MacOS')
         fs.renameSync path.join(exeDir, 'Atom'), path.join(exeDir, executableName)
       when 'linux'
         cp 'atom-shell', shellAppDir, filter: /default_app/


Added tests for data-bind="foo={id,name}"

--- src/test/bind-spec.coffee
+++ src/test/bind-spec.coffee
@@ -124,19 +124,27 @@
       '''

+
+
       '''
     )

     expect(Myna.$("#v1a").html()).toEqual("")
     expect(Myna.$("#v1b").html()).toEqual("")
+    expect(Myna.$("#v1c").html()).toEqual("")
+    expect(Myna.$("#v1d").html()).toEqual("")

     client.experiments.expt1.view("variant1")
     expect(Myna.$("#v1a").html()).toEqual("Variant 1.1")
     expect(Myna.$("#v1b").html()).toEqual("<v1>")
+    expect(Myna.$("#v1c").html()).toEqual("Variant 1.1")
+    expect(Myna.$("#v1d").html()).toEqual("variant1")

     client.experiments.expt1.view("variant2")
     expect(Myna.$("#v1a").html()).toEqual("Variant 1.2")
     expect(Myna.$("#v1b").html()).toEqual("<v2>")
+    expect(Myna.$("#v1c").html()).toEqual("Variant 1.2")
+    expect(Myna.$("#v1d").html()).toEqual("variant2")

     return

@@ -145,19 +153,27 @@
       '''

+
+
       '''
     )

     expect(Myna.$("#v1a").html()).toEqual("")
     expect(Myna.$("#v1b").html()).toEqual("")
+    expect(Myna.$("#v1c").html()).toEqual("")
+    expect(Myna.$("#v1d").html()).toEqual("")

     client.experiments.expt1.view("variant1")
     expect(Myna.$("#v1a").html()).toEqual("Variant 1.1")
     expect(Myna.$("#v1b").html()).toEqual("<v1></v1>")
+    expect(Myna.$("#v1c").html()).toEqual("Variant 1.1")
+    expect(Myna.$("#v1d").html()).toEqual("variant1")

     client.experiments.expt1.view("variant2")
     expect(Myna.$("#v1a").html()).toEqual("Variant 1.2")
     expect(Myna.$("#v1b").html()).toEqual("<v2></v2>")
+    expect(Myna.$("#v1c").html()).toEqual("Variant 1.2")
+    expect(Myna.$("#v1d").html()).toEqual("variant2")

     return

@@ -166,19 +182,27 @@
       '''

+
+
       '''
     )

     expect(Myna.$("#v1a").attr("class")).toEqual("myna-expt1")
     expect(Myna.$("#v1b").attr("class")).toEqual("myna-expt1")
+    expect(Myna.$("#v1c").attr("class")).toEqual("myna-expt1")
+    expect(Myna.$("#v1d").attr("class")).toEqual("myna-expt1")

     client.experiments.expt1.view("variant1")
     expect(Myna.$("#v1a").attr("class")).toEqual("myna-expt1 Variant 1.1")
     expect(Myna.$("#v1b").attr("class")).toEqual("myna-expt1 class1")
+    expect(Myna.$("#v1c").attr("class")).toEqual("myna-expt1 Variant 1.1")
+    expect(Myna.$("#v1d").attr("class")).toEqual("myna-expt1 variant1")

     client.experiments.expt1.view("variant2")
     expect(Myna.$("#v1a").attr("class")).toEqual("myna-expt1 Variant 1.1 1.2")
     expect(Myna.$("#v1b").attr("class")).toEqual("myna-expt1 class1 class2")
+    expect(Myna.$("#v1c").attr("class")).toEqual("myna-expt1 Variant 1.1 1.2")
+    expect(Myna.$("#v1d").attr("class")).toEqual("myna-expt1 variant1 variant2")

     return

@@ -187,19 +211,27 @@
       '''

+
+
       '''
     )

     expect(Myna.$("#v1a").attr("title")).toEqual(null)
     expect(Myna.$("#v1b").attr("title")).toEqual(null)
+    expect(Myna.$("#v1c").attr("title")).toEqual(null)
+    expect(Myna.$("#v1d").attr("title")).toEqual(null)

     client.experiments.expt1.view("variant1")
     expect(Myna.$("#v1a").attr("title")).toEqual("Variant 1.1")
     expect(Myna.$("#v1b").attr("title")).toEqual("title1")
+    expect(Myna.$("#v1c").attr("title")).toEqual("Variant 1.1")
+    expect(Myna.$("#v1d").attr("title")).toEqual("variant1")

     client.experiments.expt1.view("variant2")
     expect(Myna.$("#v1a").attr("title")).toEqual("Variant 1.2")
     expect(Myna.$("#v1b").attr("title")).toEqual("title2")
+    expect(Myna.$("#v1c").attr("title")).toEqual("Variant 1.2")
+    expect(Myna.$("#v1d").attr("title")).toEqual("variant2")

     return


compile client from roots-cms-client

compile client from roots-cms-client

- until roots-cms-client is published on npm, you'll need to clone roots-cms-client locally and then change `client_path` to load from the correct location

--- lib/server/index.coffee
+++ lib/server/index.coffee
@@ -2,8 +2,10 @@
 path    = require('path')
 api     = require('../api')
 W       = require 'when'
+Roots   = require 'roots'

-Roots   = require 'roots'
+client_path = path.join(__dirname, '..', '..', '..', 'roots-cms-client')
+new_client  = require(path.join(client_path, 'lib'))

 class Server

@@ -24,8 +26,7 @@
   ### private ###

   compile_client = ->
-    client = new Roots(path.join(__dirname, '..', 'client'))
-    client.compile()
+    new_client(env: 'production').compile()

   configure_server = ->
     if @config.env == 'development'
@@ -37,7 +38,7 @@
       @_server.use(express.basicAuth(@config.basic_auth.username, @config.basic_auth.password))

     # roots-cms client assets
-    @_server.use(express.static(path.join(__dirname, '..', 'client', 'public')))
+    @_server.use(express.static(path.join(client_path, 'public')))

     # roots project assets
     @_server.use(express.static(path.join(@cms.root, 'assets')))
@@ -46,6 +47,6 @@

     # roots-cms marionette SPA
     @_server.get "*", (req, res) ->
-      res.sendfile(path.join(__dirname, '..', 'client', 'public', 'index.html'))
+      res.sendfile(path.join(client_path, 'public', 'index.html'))

 module.exports = Server


(fix) create empty nodes with ()

(fix) create empty nodes with ()

--- src/tree_processor.coffee
+++ src/tree_processor.coffee
@@ -5,6 +5,7 @@
 uuid = require 'node-uuid'

 parseTree = (string, graph) ->
+  brackets = 0
   count = 0
   parrents = []
   tempName = ""
@@ -21,41 +22,56 @@

   addToGraph = (graph) ->
     shape = "circle"
-    if name == "#"
+    if name == "#" || name == ''
       shape = "rect"
       name = ""
+    #console.log "adding name '#{name}' with countname '#{countName}'"
     graph.setNode(countName, { label: name, shape: shape})
+
     if parrent
-      #console.log name, " -> ", parrent
       graph.setEdge(parrent, countName, {label:""})
-    #else
-      #console.log name, " -> NONE"

   prevChar = ""
-  for c in string
+  pos = 0
+  console.log string.length
+  # i should refactor this
+  while pos < string.length
+    c = string[pos++]
     switch c
       when ' '
         if tempName
           addName()
           addToGraph(graph)
       when '('
-        addName()
-        addToGraph(graph) # add root
-        parrent = countName
-        parrents.push(parrent)
-        #console.log(parrent, "PUSH\n")
+        brackets++
+        if string[pos] == ')'
+          brackets--
+          console.log pos, string
+          tempName = '#'
+          addName()
+          addToGraph(graph)
+          pos++
+        else
+          addName()
+          addToGraph(graph) # add root
+          parrent = countName
+          parrents.push(parrent)
       when ')'
+        brackets--
         addName()
         if prevChar != ')'
           addToGraph(graph)
         parrents.pop()
         parrent = parrents[parrents.length-1]
-        #console.log(parrent, "POP\n")
       else
         if c != '\n'
           tempName += c
     if c != ' '
       prevChar = c
+
+  if brackets > 0
+    console.log "wrong number of brackets #{brackets}"
+

 treeProcessor = (tokens, graph_template, error_template) ->
   render = dagreD3.render()


SocialWorker: Tests: export KONFIG

SocialWorker: Tests: export KONFIG

--- workers/social/testhelper/index.coffee
+++ workers/social/testhelper/index.coffee
@@ -205,6 +205,7 @@
   _
   daisy
   expect
+  KONFIG
   ObjectId
   fetchGroup
   expectRelation


lxd: cluster start headers

lxd: cluster start headers

--- packages/lxd/src/cluster/cli/start.coffee
+++ packages/lxd/src/cluster/cli/start.coffee
@@ -16,17 +16,20 @@
     vagrant plugin install vagrant-vbguest
     '''
   .execute
+    header: 'Vagrant'
     cwd: "#{__dirname}/../../../assets"
     cmd: '''
     vagrant up
     '''
   .execute
+    header: 'LXC remote'
     cmd: '''
     lxc remote add nikita 127.0.0.1:8443 --accept-certificate --password secret
     lxc remote switch nikita
     '''
   .execute
-    debug: true
+    header: 'LXC remote (update)'
+    # todo: use condition for `lxc ls`
     cmd: '''
     lxc ls || {
       lxc remote switch local
@@ -44,6 +47,7 @@
     stderr: process.stderr
     stdout: process.stdout
   .call ->
+    header: 'Connection'
     process.stdout.write """
     ssh -i #{key} -qtt -p 2222 vagrant@127.0.0.1 -- "cd /nikita && bash"\n
     """


Fixed webterm size bug.

Fixed webterm size bug.

--- client/app/Applications/WebTerm.kdapplication/AppView.coffee
+++ client/app/Applications/WebTerm.kdapplication/AppView.coffee
@@ -123,6 +123,7 @@
         sizeY: @terminal.sizeY
     , (err, remote) =>
       @terminal.server = remote
+      remote.setSize @terminal.sizeX, @terminal.sizeY # might have changed in the meantime
       @setKeyView()
       @emit "WebTermConnected", remote


webterm: Avoid errors until timeout is removed.

--- client/app/Applications/WebTerm.kdapplication/AppView.coffee
+++ client/app/Applications/WebTerm.kdapplication/AppView.coffee
@@ -43,17 +43,17 @@

     @on "ReceivedClickElsewhere", =>
       @focused = false
-      @terminal.setFocused false if @terminal?
+      @terminal?.setFocused false
       @getSingleton('windowController').removeLayer @

     $(window).bind "blur", =>
-      @terminal.setFocused false
+      @terminal?.setFocused false

     $(window).bind "focus", =>
-      @terminal.setFocused @focused
+      @terminal?.setFocused @focused

     $(document).on "paste", (event) =>
-      @terminal.server.input event.originalEvent.clipboardData.getData("text/plain") if @focused
+      @terminal?.server.input event.originalEvent.clipboardData.getData("text/plain") if @focused

     KD.whoami().tellKite
       kiteName: 'webterm',
@@ -94,10 +94,8 @@

   setKeyView:->
     @getSingleton('windowController').addLayer @
-    # checking existence of terminal because of the initial timeout
-    # this may run before it is created
     @focused = true
-    @terminal.setFocused true if @terminal?
+    @terminal?.setFocused true
     super

   click: ->


app: route to teams activity link if is not koding

--- client/app/lib/notifications/notificationlistitemview.coffee
+++ client/app/lib/notifications/notificationlistitemview.coffee
@@ -8,6 +8,7 @@
 getFullnameFromAccount  = require '../util/getFullnameFromAccount'
 remote                  = require('../remote').getInstance()
 whoami                  = require '../util/whoami'
+isKoding                = require '../util/isKoding'
 AvatarView              = require '../commonviews/avatarviews/avatarview'
 JView                   = require '../jview'
 LinkGroup               = require '../commonviews/linkviews/linkgroup'
@@ -78,7 +79,9 @@
       when 'comment', 'like', 'mention'
         socialapi.message.byId { id: @getData().targetId }, (err, post) =>
           return kd.warn err  if err
-          @setAttribute 'href', groupifyLink "/Activity/Post/#{post.slug}"
+          if isKoding()
+          then @setAttribute 'href', groupifyLink "/Activity/Post/#{post.slug}"
+          else @setAttribute 'href', calculateReactivityLink post
       when 'follow'
         @setAttribute 'href', groupifyLink "/#{@actors.first.profile.nickname}"
       when 'join', 'leave'
@@ -94,7 +97,9 @@
       if post
         # TODO group slug must be prepended after groups are implemented
         # groupSlug = if post.group is "koding" then "" else "/#{post.group}"
-        kd.singletons.router.handleRoute "/Activity/Post/#{post.slug}", { state: post }
+        if isKoding()
+        then kd.singletons.router.handleRoute "/Activity/Post/#{post.slug}", { state: post }
+        else kd.singletons.router.handleRoute calculateReactivityLink post
       else
         new KDNotificationView
           title : "This post has been deleted!"


BaseStackTemplatesView: Remove unneeded and old lines

--- client/stacks/lib/views/stacks/basestacktemplatesview.coffee
+++ client/stacks/lib/views/stacks/basestacktemplatesview.coffee
@@ -39,17 +39,6 @@
             @requestEditStack identifier
           else
             @setRoute()
-
-
-    groupsController.on 'StackTemplateChanged', (params) =>
-
-      stackTemplate = params.contents
-
-      items   = @initialView.stackTemplateList.listController.getListItems()
-      [item]  = items.filter (i) -> i.getData()._id is stackTemplate._id
-
-      @initialView.stackTemplateList.listController.addItem stackTemplate  unless item
-

     groupsController.on 'GroupStackTemplateRemoved', (params) =>


Start classless remotes

Start classless remotes

--- src/objects/remotes.coffee
+++ src/objects/remotes.coffee
@@ -1,5 +1,5 @@
 ###
-Get available distributions list
+Operations with remote (available to install) Node.js versions
 ###

 module.exports =
@@ -8,11 +8,14 @@
     do fetch.versions
   r = []
   for k, v of dists when (f = file cache, "#{k}.json").y()
-    for z in json2 f.load() when msi z
-      r.push new Remote z, k
+    for z in json2 f.load() when msiAvailable z
+      r.push
+        $: [semver(z.version), [k], [!!line.lts]]
+        dist: k
+        src: z
   r.sort semver.$

-msi = (line)->
+msiAvailable = (line)->
   ~line.files.join().indexOf '-msi'

 # Latest version available to upgrade to
@@ -20,9 +23,3 @@
   return unless remote = exports(true).pop()
   return if remote.local '*'
   remote.$[0].join '.'
-
-Remote = (line, dist)->
-  @$ = [semver((@src = line).version), [@dist = dist], [!!line.lts]]
-  return
-
-Remote:: = remote.proto


Allow v in filter

Allow v in filter

--- src/objects/vfilter.coffee
+++ src/objects/vfilter.coffee
@@ -20,7 +20,7 @@
       dist = x
     else if (is64 = parse64 arg)?
       $64 = is64
-    else if /^\d/.test arg
+    else if /^v?\d/i.test arg
       ver = semver arg
   new Filter ver, dist, $64

@@ -41,4 +41,4 @@
   x = ['x86','x64']
   alt if x64 then x.reverse() else x

-exports.$ = "#{alt keys()} [n[.n[.n]]] #{$6}"
+exports.$ = "#{alt keys()} [[v]N[.N[.N]]] #{$6}"


Move fullscreenAllowed into coffee

Move fullscreenAllowed into coffee

--- src/videoLightning.coffee
+++ src/videoLightning.coffee
@@ -45,7 +45,7 @@
       @opts.width = if @opts.width then parseInt(@opts.width, 10) else 640
       @opts.height = if @opts.height then parseInt(@opts.height, 10) else 390
       display_ratio = this.opts.height / this.opts.width
-      if (@opts.width > (window.innerWidth - 90))
+      if (!@opts.fullscreenAllowed && @opts.width > (window.innerWidth - 90))
         @opts.width = window.innerWidth - 90
         @opts.height = Math.round(display_ratio * @opts.width)
       @opts.id ?= 'y-dQw4w9WgXcQ'


use streaming stats api

use streaming stats api

--- lib/docker.coffee
+++ lib/docker.coffee
@@ -15,7 +15,7 @@
   publishContainerStats = ->
     docker.listContainers { all: 1 }, (err, containers) ->
       containers.forEach (containerInfo) ->
-        docker.getContainer(containerInfo.Id).stats stream:0, (err, data) ->
+        docker.getContainer(containerInfo.Id).stats stream:1, (err, data) ->
           if err
             console.log err
           else
@@ -28,7 +28,7 @@
                   container: containerInfo
                   stats: parsed
               catch e
-                console.error 'Error during parsing JSON stats', e
+                console.error 'Error during parsing JSON stats', e, "Chunks I got so far: #{chunks}"

   publishDockerInfo = ->
     docker.info (err, info) ->


adding default options

adding default options

--- src/views/defaults.coffee
+++ src/views/defaults.coffee
@@ -23,7 +23,7 @@
   fetch: false
   pagination: null

-  initialize: (options) ->
+  initialize: (options={}) ->
     super(options)
     if @fetch or options.fetch
       @collection.fetch()


Change the tileSource URL to point to the correct directory for embeds.

--- lib/embed._coffee
+++ lib/embed._coffee
@@ -17,11 +17,10 @@

 createTileSourceBlock = (base_path, dzi, _) ->
     attribs = dziparser.parse path.join(base_path, 'dzi', "#{dzi}.dzi"), _
-    console.log attribs
     "var tileSource = {
         Image: {
             xmlns: 'http://schemas.microsoft.com/deepzoom/2008',
-            Url: '#{config.STATIC_DIR}#{config.DZI_DIR}/#{dzi}.dzi',
+            Url: '#{config.STATIC_DIR}#{config.DZI_DIR}/#{dzi}_files/',
             Format: '#{attribs.tileFormat}',
             Overlap: '#{attribs.tileOverlap}',
             TileSize: '#{attribs.tileSize}',


Improve missing UID handling in Entity#findUid

--- lib/entity.coffee
+++ lib/entity.coffee
@@ -95,7 +95,10 @@
   Base.findByUid = (uid, callback) ->
     deferred = Q.defer()
     deferred.promise.nodeify callback
-    @$domainRepository.findEntityByUid @, uid, deferred.makeNodeResolver()
+    if uid?
+      @$domainRepository.findEntityByUid @, uid, deferred.makeNodeResolver()
+    else
+      deferred.reject(new Error 'Please provide a UID to find')
     deferred.promise

   Base.findAllEvents = (uid, callback) ->


remove item on kdlistview improved

remove item on kdlistview improved

--- components/list/KDListView.coffee
+++ components/list/KDListView.coffee
@@ -59,8 +59,7 @@
       return
     else
       for item,i in @items
-        if itemInstance and itemInstance is item or
-           itemData and itemData is item.getData()
+        if itemInstance is item or itemData is item.getData()
           @emit 'ItemIsBeingDestroyed', { view : item, index : i }
           @items.splice i,1
           item.destroy()


made photo demo horizontal

made photo demo horizontal

--- demo/demo.coffee
+++ demo/demo.coffee
@@ -31,6 +31,7 @@
   hexaPhoto = new HexaFlip document.getElementById('photo-demo'),
     {photos: ("./demo/images/#{ i }.jpg" for i in [1..18])},
     size: 400
+    horizontalFlip: true

   photoControls = document.getElementById 'photo-controls'


:art:

:art:

--- lib/tab-bar-view.coffee
+++ lib/tab-bar-view.coffee
@@ -82,10 +82,10 @@
     @addEventListener "dblclick", @onDoubleClick
     @addEventListener "click", @onClick

-    RendererIpc.on('tab:dropped', => @onDropOnOtherWindow(arguments...))
+    RendererIpc.on('tab:dropped', @onDropOnOtherWindow.bind(this))

   unsubscribe: ->
-    RendererIpc.removeListener('tab:dropped', => @onDropOnOtherWindow(arguments...))
+    RendererIpc.removeListener('tab:dropped', @onDropOnOtherWindow.bind(this))
     @subscriptions.dispose()

   handleTreeViewEvents: ->


:shirt: Lint error #382

:shirt: Lint error #382

--- lib/motion.coffee
+++ lib/motion.coffee
@@ -657,7 +657,7 @@
     point = new Point(row, 0)
     @editor.element.pixelRectForScreenRange(new Range(point, point)).top

-  smoothScroll: (fromRow, toRow, options)->
+  smoothScroll: (fromRow, toRow, options) ->
     topPixelFrom = {top: @getPixelRectTopForSceenRow(fromRow)}
     topPixelTo = {top: @getPixelRectTopForSceenRow(toRow)}
     options.step = (newTop) => @editor.element.setScrollTop(newTop)


indicate to modul8 that the value is pre-serialized

--- lib/plugin.coffee
+++ lib/plugin.coffee
@@ -19,7 +19,7 @@

 Plugin::data = ->
   files = (file for file in fsx.readDirSync(tempFolder).files when path.extname(file) is '.json')
-  [@o.key, '{'+('"'+toName(file)+'":'+read(file) for file in files).join(',')+'}']
+  [@o.key, '{'+('"'+toName(file)+'":'+read(file) for file in files).join(',')+'}', true] # this is pre-serialized

 Plugin::domain = ->


Verify settings exist

Verify settings exist

--- lib/ripper.coffee
+++ lib/ripper.coffee
@@ -201,7 +201,7 @@
   serialize: ->

   parse: (code, callback) ->
-    if code.length > config.getSettings()['coffee-refactor']['disable in large files (chars)']
+    if config.getSettings()['coffee-refactor']? and code.length > config.getSettings()['coffee-refactor']['disable in large files (chars)']
       console.warn 'coffe-refactor is disabled in large file: You can cange the threshold in preference pane.'
       return


Better code reuse with the title

Better code reuse with the title

--- lib/routes.coffee
+++ lib/routes.coffee
@@ -7,6 +7,14 @@
   routeControllerNameConverter: 'upperCamelCase'
   # trackPageView: true

+seo = (title) ->
+  t = []
+  if title
+    t.push(title)
+  t.push('Reversim Summit 2015')
+  t = t.join(' | ')
+  document.title = t
+
 Router.map ->
   @route 'home',
     path: '/'
@@ -14,7 +22,7 @@
     tempalte: 'home'
     data: -> page: 'home'
     onAfterAction: ->
-      document.title = "Reversim Summit 2015"
+      seo('')

   @route 'about',
     path: '/about'
@@ -23,7 +31,7 @@
     tempalte: 'about'
     data: -> page: 'about'
     onAfterAction: ->
-      document.title = "About Reversim Summit 2015"
+      seo('About')

   # @route 'infographics', path: '/infographics' # TODO later

@@ -32,8 +40,7 @@
     tempalte: 'register'
     fastRender: true
     data: -> page: 'register'
-    onAfterAction: ->
-      document.title = "Register | Reversim Summit 2015"
+    onAfterAction: -> seo('Register')

   @route 'propose',
     path: '/propose'
@@ -41,7 +48,7 @@
     template: 'propose'
     fastRender: true
     data: -> page: 'propose'
-    onAfterAction: -> document.title = "Propose | Reversim Summit 2015"
+    onAfterAction: -> seo('Propose')

   @route 'wishes',
     path: '/wishes'
@@ -49,7 +56,7 @@
     fastRender: true
     data: -> page: 'wishes'
     waitOn: -> Meteor.subscribe('wishes')
-    onAfterAction: -> document.title = "Wishes | Reversim Summit 2015"
+    onAfterAction: -> seo('Wishes')

   @route 'agenda',
     path: '/agenda'
@@ -59,7 +66,7 @@
     data: ->
       page: 'agenda'
       items: AgendaItem.all()
-    onAfterAction: -> document.title = "Agenda | Reversim Summit 2015"
+    onAfterAction: -> seo('Agenda')

 #  @route 'proposal1', _.extend(path: '/proposal/:id/:title*', proposalRouteConfig)
 #  @route 'proposal2', _.extend(path: '/proposal/:id', proposalRouteConfig)
@@ -73,7 +80,7 @@
     data: ->
       proposal = Proposal.find(@params.id)
       if not proposal then return null
-      document.title = "#{proposal.title} | Reversim Summit 2015"
+      seo(proposal.title)
       {page: 'proposal', proposal: proposal}

@@ -94,8 +101,7 @@
       proposals: Proposal.where(q, {createdAt: -1})
       filterType: @params.query?.filterType
       filterTag: @params.query?.filterTag
-    onAfterAction: ->
-      document.title = "Proposals | Reversim Summit 2015"
+    onAfterAction: -> seo('Proposals')

   @route 'vote',
     path: '/vote'
@@ -124,7 +130,7 @@
         filterType: @params.filterType
         filterTag: @params.filterTag
       }
-    onAfterAction: -> document.title = "Vote | Reversim Summit 2015"
+    onAfterAction: -> seo('Vote')

   @route 'speaker',
@@ -137,7 +143,7 @@
     data: ->
       speaker = User.find(@params.id)
       if not speaker then return null
-      document.title = "#{speaker.name()} | Reversim Summit 2015"
+      seo(speaker.name())
       {page: 'speaker', speaker: speaker}

   @route 'speakers',
@@ -147,14 +153,14 @@
     data: ->
       page: 'speakers'
       speakers: User.allSpeakers()
-    onAfterAction: -> document.title = "Speakers | Reversim Summit 2015"
+    onAfterAction: -> seo('Speakers')

   @route 'users',
     waitOn: -> Meteor.subscribe('users')
     tempalte: 'users'
     fastRender: true
     data: -> page: 'users'
-    onAfterAction: -> document.title = "Users | Reversim Summit 2015"
+    onAfterAction: -> seo('Users')

   @route 'user',
     path: '/user/:id/:name?'
@@ -166,7 +172,7 @@
     data: ->
       speaker = User.find(@params.id)
       if not speaker then return null
-      document.title = "#{speaker.name()} | Reversim Summit 2015"
+      seo(speaker.name())
       {page: 'speaker', speaker: speaker}

   @route 'wish',
@@ -178,7 +184,7 @@
     data: ->
       wish = Wishes.findOne(_id: @params.id)
       if not wish then return null
-      document.title = "#{wish.title} | Reversim Summit 2015"
+      seo(wish.title)
       {page: 'wish', wish: wish}

   @route 's2013',
@@ -186,28 +192,28 @@
     tempalte: 's2013'
     fastRender: true
     data: -> page: 's2013'
-    onAfterAction: -> document.title = "2013 | Reversim Summit"
+    onAfterAction: -> seo('2013')

   @route 's2014',
     path: '/s2014'
     tempalte: 's2014'
     fastRender: true
     data: -> page: 's2014'
-    onAfterAction: -> document.title = "2014 | Reversim Summit"
+    onAfterAction: -> seo('2014')

   @route 'info',
     path: '/info'
     tempalte: 'info'
     fastRender: true
     data: -> page: 'info'
-    onAfterAction: -> document.title = "Info | Reversim Summit 2015"
+    onAfterAction: -> seo('Info')

   @route 'coc',
     path: '/coc'
     tempalte: 'coc'
     fastRender: true
     data: -> page: 'coc'
-    onAfterAction: -> document.title = "Code of Conduct | Reversim Summit 2015"
+    onAfterAction: -> seo('Code of Conduct')

   @route 'sponsors',
     waitOn: -> Meteor.subscribe('sponsors')
@@ -216,14 +222,14 @@
     data: ->
       page: 'sponsors'
       sponsors: _.shuffle(Sponsor.all())
-    onAfterAction: -> document.title = "Sponsors | Reversim Summit 2015"
+    onAfterAction: -> seo('Sponsors')

   @route 'community',
     path: '/community'
     tempalte: 'community'
     data: -> page: 'community'
     fastRender: true
-    onAfterAction: -> document.title = "Community | Reversim Summit 2015"
+    onAfterAction: -> seo('Community')

 Router.fullPath = (routeName, params) ->
   Meteor.absoluteUrl().slice(0, -1) + Router.path(routeName, params)


Use mkdirp.sync to create sprout directory.

--- lib/sprout.coffee
+++ lib/sprout.coffee
@@ -7,7 +7,7 @@
 fs            = require 'fs'

 module.exports = (p = sprout_user_path()) ->
-  if p == sprout_user_path() and not fs.existsSync(p) then mkdirp(p)
+  if p == sprout_user_path() and not fs.existsSync(p) then mkdirp.sync(p)
   new Sprout(p)

 sprout_user_path = ->


KDDiaScene: Add fakeConnection support KDDiaScene which keeps a seperate list of connections

--- components/dia/kddiascene.coffee
+++ components/dia/kddiascene.coffee
@@ -16,10 +16,11 @@

     super

-    @containers    = []
-    @connections   = []
-    @activeDias    = []
-    @activeJoints  = []
+    @containers      = []
+    @connections     = []
+    @activeDias      = []
+    @activeJoints    = []
+    @fakeConnections = []

   diaAdded:(container, diaObj)->
     diaObj.on "JointRequestsLine",   @bound "handleLineRequest"
@@ -182,56 +183,68 @@
     @connections.push {source, target}
     @highlightLines target.dia

+  resetScene:->
+    @fakeConnections = []
+    @updateScene()
+
   updateScene:->

     @cleanup @realCanvas
-
+    @drawConnectionLine connection for connection in @connections
+    @drawConnectionLine connection for connection in @fakeConnections
+
+  drawConnectionLine:({source, target, options})->
+
+    return unless source or target
+
+    options    or= {}
     activeColor  = @getOption 'lineColorActive'
     lineDashes   = @getOption 'lineDashes'
-    defaultColor = @getOption 'lineColor'
-
-    for connection in @connections
-
-      @realContext.beginPath()
-
-      {source, target} = connection
-
-      if source.dia in @activeDias
-        lineColor = (source.dia.getOption 'colorTag')   or activeColor
-        lineDashes  = (source.dia.getOption 'lineDashes') or lineDashes
-      else if target.dia in @activeDias
-        lineColor = (target.dia.getOption 'colorTag')   or activeColor
-        lineDashes  = (target.dia.getOption 'lineDashes') or lineDashes
-      else
-        lineColor = defaultColor
-
-      sJoint = source.dia.getJointPos source.joint
-      tJoint = target.dia.getJointPos target.joint
-
-      @realContext.strokeStyle = lineColor
-      @realContext.setLineDash lineDashes  if lineDashes.length > 0
-
-      @realContext.moveTo sJoint.x, sJoint.y
-
-      cd = @getOption 'curveDistance'
-      [sx, sy, tx, ty] = [0, 0, 0, 0]
-      if source.joint in ["top", "bottom"]
-        sy = if source.joint is "top" then -cd else cd
-      else if source.joint in ["left", "right"]
-        sx = if source.joint is "left" then -cd else cd
-      if target.joint in ["top", "bottom"]
-        ty = if target.joint is "top" then -cd else cd
-      else if target.joint in ["left", "right"]
-        tx = if target.joint is "left" then -cd else cd
-
-      @realContext.bezierCurveTo(sJoint.x + sx, sJoint.y + sy, \
-                                 tJoint.x + tx, tJoint.y + ty, \
-                                 tJoint.x, tJoint.y)
-
-      @realContext.lineWidth = @getOption 'lineWidth'
-      @realContext.stroke()
+    lineColor    = @getOption 'lineColor'
+
+    @realContext.beginPath()
+
+    activeDia = if source.dia in @activeDias then source \
+                else if target.dia in @activeDias then target
+
+    if activeDia
+      lineColor  = options.lineColor  or \
+                   (activeDia.dia.getOption 'colorTag') or activeColor
+      lineDashes = options.lineDashes or \
+                   (activeDia.dia.getOption 'lineDashes') or lineDashes
+
+    sJoint = source.dia.getJointPos source.joint
+    tJoint = target.dia.getJointPos target.joint
+
+    @realContext.strokeStyle = lineColor
+    @realContext.setLineDash lineDashes  if lineDashes.length > 0
+
+    @realContext.moveTo sJoint.x, sJoint.y
+
+    cd = @getOption 'curveDistance'
+    [sx, sy, tx, ty] = [0, 0, 0, 0]
+    if source.joint in ["top", "bottom"]
+      sy = if source.joint is "top" then -cd else cd
+    else if source.joint in ["left", "right"]
+      sx = if source.joint is "left" then -cd else cd
+    if target.joint in ["top", "bottom"]
+      ty = if target.joint is "top" then -cd else cd
+    else if target.joint in ["left", "right"]
+      tx = if target.joint is "left" then -cd else cd
+
+    @realContext.bezierCurveTo(sJoint.x + sx, sJoint.y + sy, \
+                               tJoint.x + tx, tJoint.y + ty, \
+                               tJoint.x, tJoint.y)
+
+    @realContext.lineWidth = @getOption 'lineWidth'
+    @realContext.stroke()
+
+  addFakeConnection:(connection)->
+    @drawConnectionLine connection
+    @fakeConnections.push connection

   createCanvas:->
+
     @realCanvas?.destroy()
     @fakeCanvas?.destroy()

@@ -239,6 +252,11 @@
       tagName    : "canvas"
       attributes : @getSceneSize()
     @realContext = @realCanvas.getElement().getContext "2d"
+
+    # Fallback function for Firefox and Safari
+    # which does not support lineDashes correctly ~ GG
+    @realContext.setLineDash = noop  unless @realContext.setLineDash?
+
     @addSubView @fakeCanvas = new KDCustomHTMLView
       tagName    : "canvas"
       cssClass   : "fakeCanvas"


adding beforeDraw and afterDraw

adding beforeDraw and afterDraw

--- src/drawable.coffee
+++ src/drawable.coffee
@@ -141,11 +141,12 @@
   @returns self
   ###
   draw: (canvas) ->
-
     self.trigger 'beforeTransform', canvas

     canvas.withTransform self.transform(), (canvas) ->
+      self.trigger 'beforeDraw', canvas
       self.trigger 'draw', canvas
+      self.trigger 'afterDraw', canvas

     self.trigger 'afterTransform', canvas


submit button saves the report and redirects to it (WIP)

--- app/assets/javascripts/controllers/expense_report_form_controller.js.coffee
+++ app/assets/javascripts/controllers/expense_report_form_controller.js.coffee
@@ -1,6 +1,7 @@
-@ExpenseReportFormController = ($scope, $routeParams, ExpenseReport, Expense) ->
+@ExpenseReportFormController = ($scope, $routeParams, ExpenseReport, Expense, $location) ->

-  $scope.newExpenseReport = new ExpenseReport()
+  $scope.newExpenseReport = new ExpenseReport
+    userId: $routeParams.userId

   $scope.addExpense = ->
     $scope.newExpenseReport.addExpense()
@@ -30,4 +31,7 @@
   )

   $scope.submitReport = ->
-    $scope.newExpenseReport.create()
+    $scope.newExpenseReport.create().then (expenseReport) ->
+      debugger
+      $location.path("/users/#{expenseReport.userId}/expense_reports/#{expenseReport.id}")
+


Global link hints; reinstate mode toggling.

--- content_scripts/link_hints.coffee
+++ content_scripts/link_hints.coffee
@@ -69,11 +69,10 @@
     @onExit = [] unless frameId == activateModeFrameId
     @linkHintsMode = new LinkHintsMode hints, availableModes[modeIndex]

-  postKeyState: (request) ->
-    @linkHintsMode.postKeyState request
-
-  activateActiveHintMarker: ->
-    @linkHintsMode.activateLink @linkHintsMode.markerMatcher.activeHintMarker
+  postKeyState: (request) -> @linkHintsMode.postKeyState request
+  activateActiveHintMarker: -> @linkHintsMode.activateLink @linkHintsMode.markerMatcher.activeHintMarker
+  setMode: ({modeIndex}) -> @linkHintsMode.setOpenLinkMode availableModes[modeIndex], true
+  getLocalHintMarker: (hint) -> if hint.frameId == frameId then @localHints[hint.localIndex] else null

   exit: ->
     @onExit.pop()() while 0 < @onExit.length
@@ -82,9 +81,6 @@
   exitFailure: ->
     @onExit = [=> @linkHintsMode.deactivateMode()]
     @exit()
-
-  getLocalHintMarker: (hint) ->
-    if hint.frameId == frameId then @localHints[hint.localIndex] else null

 LinkHints =
   activateMode: (count = 1, mode = OPEN_IN_CURRENT_TAB) ->
@@ -144,7 +140,7 @@
           (event?.type == "keydown" and event.keyCode in [ keyCodes.backspace, keyCodes.deleteKey ])
         HintCoordinator.sendMessage "exitFailure"

-    @setOpenLinkMode mode
+    @setOpenLinkMode mode, true

     # Note(philc): Append these markers as top level children instead of as child nodes to the link itself,
     # because some clickable elements cannot contain children, e.g. submit buttons.
@@ -154,8 +150,10 @@
     @hideMarker marker for marker in hintMarkers when marker.hint.frameId != frameId
     @postKeyState = @postKeyState.bind this, hintMarkers

-  setOpenLinkMode: (@mode) ->
+  setOpenLinkMode: (@mode, doNotPropagate = false) ->
     @hintMode.setIndicator @mode.indicator if DomUtils.isTopFrame()
+    unless doNotPropagate
+      HintCoordinator.sendMessage "setMode", modeIndex: availableModes.indexOf @mode

   #
   # Creates a link marker for the given link.


Ensure hint handler is always removed.

Ensure hint handler is always removed.

For some (unknown) reason, we do not receive the keyup event for Shift
when activating link hints.  Consequently, we are not correctly removing
the keyup handler.  And that handler is blocking subsequent keyup
events, which has the effect of endless scrolling (because the scroller
relies on keyup events to stop scrolling).

Here, we correcttly remove the keyup handler when link-hints mode exits.

Fixes #1911.

--- content_scripts/link_hints.coffee
+++ content_scripts/link_hints.coffee
@@ -304,11 +304,16 @@
           when keyCodes.ctrlKey
             @setOpenLinkMode(if @mode is OPEN_IN_NEW_FG_TAB then OPEN_IN_NEW_BG_TAB else OPEN_IN_NEW_FG_TAB)

-        handlerStack.push
+        handlerId = handlerStack.push
           keyup: (event) =>
             if event.keyCode == keyCode
               handlerStack.remove()
               @setOpenLinkMode previousMode if @isActive
+            true # Continue bubbling the event.
+
+        # For some (unknown) reason, we don't always receive the keyup event needed to remove this handler.
+        # Therefore, we ensure that it's always removed when hint mode exits.  See #1911 and #1926.
+        @hintMode.onExit -> handlerStack.remove handlerId

     else if event.keyCode in [ keyCodes.backspace, keyCodes.deleteKey ]
       if @markerMatcher.popKeyChar()