Update the controller tests to do a better job on before/afterFilter.

--- tests/batman/controller_test.coffee
+++ tests/batman/controller_test.coffee
@@ -201,23 +201,69 @@
   @controller.dispatch 'test1'
   @controller.dispatch 'test2'

-test '[before/after]Filter', 3, ->
-  class FilterController extends Batman.Controller
-    @beforeFilter only: 'withBefore', except: 'withoutBefore', ->
-      ok true, 'beforeFilter called'
-    @afterFilter 'testAfter'
-
-    withBefore: ->
-      @render false
-    withoutBefore: ->
-      @render false
-    testAfter: ->
-      ok true, 'afterFilter called'
-
-  controller = new FilterController
-
-  controller.dispatch 'withoutBefore'
+test 'filters specifying no restrictions should be called on all actions', 2, ->
+  spy = createSpy()
+  class FilterController extends Batman.Controller
+    @beforeFilter spy
+
+    show: -> @render false
+    index: -> @render false
+
+  controller = new FilterController
+
+  controller.dispatch 'show'
+  equal spy.callCount, 1
+  controller.dispatch 'index'
+  equal spy.callCount, 2
+
+test 'filters specifying only should only be called on those actions', 2, ->
+  spy = createSpy()
+
+  class FilterController extends Batman.Controller
+    @beforeFilter only: 'withBefore', spy
+
+    withBefore: -> @render false
+    all: -> @render false
+
+  controller = new FilterController
+
   controller.dispatch 'withBefore'
+  equal spy.callCount, 1
+  controller.dispatch 'all'
+  equal spy.callCount, 1
+
+test 'filters specifying except should not be called on those actions', 2, ->
+  spy = createSpy()
+  class FilterController extends Batman.Controller
+    @beforeFilter except: 'index', spy
+
+    show: -> @render false
+    index: -> @render false
+
+  controller = new FilterController
+
+  controller.dispatch 'show'
+  equal spy.callCount, 1
+  controller.dispatch 'index'
+  equal spy.callCount, 1
+
+test 'filters specifying options in arrays should apply to all/none of those options', 3, ->
+  spy = createSpy()
+  class FilterController extends Batman.Controller
+    @beforeFilter except: ['index', 'edit'], spy
+
+    show: -> @render false
+    index: -> @render false
+    edit: -> @render false
+
+  controller = new FilterController
+
+  controller.dispatch 'edit'
+  equal spy.callCount, 0
+  controller.dispatch 'show'
+  equal spy.callCount, 1
+  controller.dispatch 'index'
+  equal spy.callCount, 1

 test 'actions executed by other actions implicitly render', ->
   mockClassDuring Batman ,'View', MockView, (mockClass) =>
@@ -233,7 +279,8 @@
 test 'actions executed by other actions have their filters run', ->
   beforeSpy = createSpy()
   afterSpy = createSpy()
-  class TestController extends @controller.constructor
+
+  class TestController extends Batman.Controller
     @beforeFilter 'show', beforeSpy
     @afterFilter 'show', afterSpy


Fix #237 - race condition in doc feed

Fix #237 - race condition in doc feed

When a document is updated and deleted just after that, the documents
feed can got an update and be unable to fetch the document to analyze
its update.

--- server/lib/feed.coffee
+++ server/lib/feed.coffee
@@ -134,11 +134,12 @@
                 @logger.error err if err
                 doctype = doc?.docType?.toLowerCase()

-                @_publish "#{doctype}.#{operation}", doc._id if doctype
-                indexer.onDocumentUpdate doc, change.seq
+                if doctype
+                    @_publish "#{doctype}.#{operation}", doc._id
+                    indexer.onDocumentUpdate doc, change.seq

                 # Special case for received shared documents.
-                if doc.shareID? and (doctype isnt 'sharing')
+                if doc?.shareID? and (doctype isnt 'sharing')
                     event = "sharing.#{doctype}.#{operation}"
                     @_publish event, "#{change.id}:#{doc.shareID}"


updated rich core parsing to handle lzo files

--- server/richcore.coffee
+++ server/richcore.coffee
@@ -1,10 +1,26 @@

 lazy = require('lazy')
+temp = require('temp')
+
+extract_rich_core = (rcorefile, cb) ->
+    fn = temp.path(suffix: '.core')
+    cmd = "lzop -d #{rcorefile.path} -o#{fn}"
+    exec cmd, (err, stdout, stderr) ->
+        cb?(err, {path: fn})

 parse_rich_core = (rcorefile, cb) ->
     console.log "parsing rich core: #{rcorefile.path}"
+    path = rcorefile.path

-    lines = new lazy(fs.createReadStream rcorefile.path).lines
+    if /\.lzo$/.test(path)
+        extract_rich_core rcorefile, (err, corefile) ->
+            return cb? err if err?
+            parse_rich_core corefile, (err, core) ->
+               fs.unlink(corefile.path)
+               cb?(err, core)
+        return
+
+    lines = new lazy(fs.createReadStream path).lines

     groups = group_lines(lines)

@@ -18,7 +34,7 @@
             switch g.name
                 when 'date' then core.date = parse_core_date g.lines[0]

-    groups.on 'end', () -> cb? core
+    groups.on 'end', () -> cb? null, core

 date_regexp = /(\w+)\s(\w+)\s(\d+)\s([0-9:]+)\s([A-Z]+)\s(\d+)/


ensure that editor uses new script names

--- salvus/smc-webapp/editor.coffee
+++ salvus/smc-webapp/editor.coffee
@@ -782,7 +782,7 @@
             (cb) =>
                 salvus_client.exec
                     project_id : @project_id
-                    command    : "sws2sagews.py"
+                    command    : "sws2sagews"
                     args       : [filename]
                     cb         : (err, output) =>
                         cb(err)
@@ -796,7 +796,7 @@
     convert_docx_file: (filename, cb) =>
         salvus_client.exec
             project_id : @project_id
-            command    : "docx2txt.py"
+            command    : "docx2txt"
             args       : [filename]
             cb         : (err, output) =>
                 if err


Workers: run only 1 NotificationWorker instance by default

If it's required to have more than 1 instance of notification worker,
sticky sessions should be enabled on the load balancer if it's willing
to use long polling (xhr-polling/stream)

If client supports WS everything will work as expected with multiple NW
instances. But if client needs to fallback to long polling somehow this
will break the NW auth mechanism; current nginx load balancer is working
with round-robin and sends each request to next worker which breaks xhr.
To be able to fix that load balancer should route the all requests from
same source to same NW instance. This can be handled by using sticky
sessions. If client support WS everything will be fine since the auth is
getting handled in already open socket connection which won’t be changed
before closed.

--- config/workers.coffee
+++ config/workers.coffee
@@ -189,7 +189,10 @@
             internalOnly: yes
           }
         ]
-      instances         : 3
+      # if it's required to have more than 1 instance of notification worker
+      # sticky sessions should be enabled on the load balancer if it's willing
+      # to use long polling (xhr-polling/stream) ~ GG
+      instances         : 1
       instanceAsArgument: '-i'

     socialapi           :


minor fix

minor fix

--- console/runner.coffee
+++ console/runner.coffee
@@ -155,6 +155,7 @@
                 break
             catch e
               # Do nothing; iterate.
+        return  # avoid accumulating results
     when argv.non_standard['count-logs']
       count = 0
       old_log = console.log


:bug: Fix error raised when creating a decoration

Fixes #335

--- lib/mixins/decoration-management.coffee
+++ lib/mixins/decoration-management.coffee
@@ -307,6 +307,10 @@
     @decorationUpdatedSubscriptions = {}
     @decorationDestroyedSubscriptions = {}

+  # Internal: Introduced in the DisplayBuffer in v0.206.0 to handle change
+  # in overlay decorations. It does nothing here because we don't use the
+  # overlay type.
+  decorationDidChangeType: (decoration) ->

   # Internal: Receive the update event of a decoration and trigger
   # a `minimap:decoration-updated` event.


Add shortcut for accessing currently-active editor

--- user/init.coffee
+++ user/init.coffee
@@ -89,6 +89,10 @@
 	set: -> atom.workspace.getActiveTextEditor().buffer.setText arguments[0]

+# Access the currently active editor
+Object.defineProperty global, "ed",
+	get: -> atom.workspace.getActiveTextEditor()
+

 # "Trendy Faggot" mode
 trendy = atom.config.get("core.themes").includes "seti-ui"


try commenting out stuff

try commenting out stuff

--- web.coffee
+++ web.coffee
@@ -19,8 +19,8 @@
 app = express()
 	.use(morgan('combined'))
 	.use(favicon(__dirname + '/public/favicon.ico'))
-	.use(bodyParser.urlencoded({ extended: false }))
-	.use(bodyParser.json())
+#	.use(bodyParser.urlencoded({ extended: false }))
+#	.use(bodyParser.json())
 	.use(multer(multerConfig))
 	.use(serveStatic(__dirname + '/public'))


added possibility to start minimized.

added possibility to start minimized.

--- modules/gui/lib/widgets/LoginForm.coffee
+++ modules/gui/lib/widgets/LoginForm.coffee
@@ -16,6 +16,8 @@
                         pass: 'Password'
                         login: 'Login'
                         logout:'Logout'
+
+                    minimized: false

                 content: ->
@@ -27,9 +29,10 @@
                         user = @identity.login()

                         if !user
-                            input name:'user', value:'', placeholder: @data.strings.user
+                            div (if @data.minimized then style:'display:none' else {}), ->
+                                input name:'user', value:'', placeholder: @data.strings.user

-                            input name:'pass', value:'', type:'password', placeholder:@data.strings.pass
+                                input name:'pass', value:'', type:'password', placeholder:@data.strings.pass

                         button type: 'submit', name:'button', ->
                             if user then @data.strings.logout else @data.strings.login
@@ -51,7 +54,9 @@

                 user = login.find('input[name=user]')
                 pass = login.find('input[name=pass]')
-
+
+                div = login.find('> div')
+
                 login.on 'submit', ()=>

                     if @identity.login()
@@ -59,15 +64,17 @@
                         @_getAuthManager().logout (err)=> location.reload()

                     else
-
-                        @_getAuthManager().login user.val(), pass.val(), (err)=>
-                            if err
-                                pass.val ''
-                                hint.addClass('error').text(err.message)
+                        if div.is ':visible'
+                            @_getAuthManager().login user.val(), pass.val(), (err)=>
+                                if err
+                                    pass.val ''
+                                    hint.addClass('error').text(err.message)

-                            else
-                                 location.reload()
-
+                                else
+                                    location.reload()
+
+                        else div.show()
+
                     return false

                 done()


Pass model's objectCount directly in onRender instead of storing locally

--- src/coffee/cilantro/ui/workflows/results.coffee
+++ src/coffee/cilantro/ui/workflows/results.coffee
@@ -33,17 +33,11 @@
         modelEvents:
             'change:objectcount': 'renderCount'

-        initialize: ->
-            super
-
-            @count = @model.objectCount if @model.objectCount? or ''
-
-        onRender: ->
-            @renderCount(@model, @count)
+        onRender: =>
+            @renderCount(@model, @model.objectCount if @model.objectCount? or '')

         renderCount: (model, count, options) ->
-            @count = count
-            numbers.renderCount(@ui.count, @count)
+            numbers.renderCount(@ui.count, count)
             @ui.label.text('records')


Bug in `type` validator fixed: now undefined values are omitted

--- validators/type.coffee
+++ validators/type.coffee
@@ -8,5 +8,5 @@
 	if value is NaN or value is null
 		value = undefined

-	if typeof value isnt expected
+	if value? and typeof value isnt expected
 		throw new TypeError "Schema: #{row} must be a #{expected}"


Fix switching rotated image to raw view

Fix switching rotated image to raw view

--- client/message.coffee
+++ client/message.coffee
@@ -612,8 +612,11 @@
   data = messageHistory.get(@data._id) ? @data
   if data.file and 'image' == fileType data.file
     image = @find '.message-file img'
-    return unless image?
-    imageTransform image, messageRotate data
+    if image?
+      imageTransform image, messageRotate data
+    else
+      ## This case occurs e.g. when switching to Raw mode.
+      @find('.message-file')?.style.height = null

 scrollDelay = 750


Better logging

Better logging

--- client/routing.coffee
+++ client/routing.coffee
@@ -66,7 +66,8 @@
           logger.error("Server failed to verify Discourse call: #{error.reason}")
           renderError(error.reason)
         else
-          logger.info("Server successfully verified Discourse call")
+          logger.info(
+            "Server successfully verified Discourse call - redirecting to #{discourseUrl}")
           [respPayload, respSig] = result
           @redirect("#{discourseUrl}/session/sso_login?sso=#{respPayload}&sig=#{respSig}")
       )


don't explode curly braces in session

don't explode curly braces in session

--- client/session.coffee
+++ client/session.coffee
@@ -23,7 +23,7 @@
     return

   cm = CodeMirror ($e = $ e)[0],
-    autofocus: true, mode: 'aplsession', matchBrackets: true, autoCloseBrackets: {pairs: '()[]{}', explode: '{}'}
+    autofocus: true, mode: 'aplsession', matchBrackets: true, autoCloseBrackets: {pairs: '()[]{}', explode: ''}
     readOnly: true, keyMap: 'dyalog', lineWrapping: !!prefs.sessionLineWrapping(), indentUnit: 4
     extraKeys:
       'Shift-Tab': 'indentLess'


bumping version to 2.10.3

bumping version to 2.10.3

--- client/version.coffee
+++ client/version.coffee
@@ -1 +1 @@
-@version = '2.10.2'
+@version = '2.10.3'


update qa specs

update qa specs

--- tutor/specs/components/qa.spec.cjsx
+++ tutor/specs/components/qa.spec.cjsx
@@ -1,16 +1,16 @@
-{Testing, expect, sinon, _, ReactTestUtils} = require '../helpers/component-testing'
+{Testing, expect, sinon, _, ReactTestUtils} = require './helpers/component-testing'

 ld = require 'lodash'

+Exercises = require '../../src/components/qa/exercises'

-Exercises = require '../../../src/components/qa/exercises'
-{ExerciseActions, ExerciseStore} = require '../../../src/flux/exercise'
-{EcosystemsActions, EcosystemsStore} = require '../../../src/flux/ecosystems'
-{ReferenceBookActions, ReferenceBookStore} = require '../../../src/flux/reference-book'
+{ExerciseActions, ExerciseStore} = require '../../src/flux/exercise'
+{EcosystemsActions, EcosystemsStore} = require '../../src/flux/ecosystems'
+{ReferenceBookActions, ReferenceBookStore} = require '../../src/flux/reference-book'

-EXERCISES  = require '../../../api/exercises.json'
-ECOSYSTEMS = require '../../../api/ecosystems.json'
-PAGE = require '../../../api/ecosystems/3/readings.json'
+EXERCISES  = require '../../api/exercises.json'
+ECOSYSTEMS = require '../../api/ecosystems.json'
+PAGE = require '../../api/ecosystems/3/readings.json'
 COURSE_ID = '1'
 ECOSYSTEM_ID = '3'
 CNX_ID = '17f6ff53-2d92-4669-acdd-9a958ea7fd0a@12'
@@ -33,7 +33,6 @@
     Testing.renderComponent( Exercises, props: @props ).then ({dom}) ->
       questions = _.map EXERCISES.items, (e) ->
         e.content.questions[0].stem_html
-
       renderedQs = _.pluck dom.querySelectorAll('.panel-body .question-stem'), 'textContent'
       expect(renderedQs).to.deep.equal(questions)


fix for #891: incorrect inversion of chained comparisons

--- nodes.coffee
+++ nodes.coffee
@@ -1179,6 +1179,7 @@
   invert: ->
     if op = INVERSIONS[@operator]
       @operator = op
+      @first.invert() if @first.isChainable()
       this
     else if @second
       new Parens(this).invert()
@@ -1207,7 +1208,7 @@
   #     true
   compileChain: (o) ->
     [@first.second, shared] = @first.second.cache o
-    fst  = @first .compile o, LEVEL_OP
+    fst  = @first.compile o, LEVEL_OP
     fst  = fst.slice 1, -1 if fst.charAt(0) is '('
     code = "#{fst} && #{ shared.compile o } #{@operator} #{ @second.compile o, LEVEL_OP }"
     if o.level < LEVEL_OP then code else "(#{code})"


Adds fix for suraido slider on captions

Adds fix for suraido slider on captions

--- coffee/suraido.coffee
+++ coffee/suraido.coffee
@@ -56,6 +56,8 @@
       #current item position
       @currentItemIndex = 0

+      #fix for alpha on captions
+      @items.find('.caption').css(width: "#{100 / @itemsLength}%")

       #set the main element
       @_setsMainElement()


better help

better help

--- command-online.coffee
+++ command-online.coffee
@@ -5,12 +5,13 @@

 DEFAULT_HOST = 'meshblu.octoblu.com'
 DEFAULT_PORT = 80
+DEFAULT_TIMEOUT = 10000

 class KeygenCommand
   parseOptions: =>
     commander
-      .option '-s, --server <host[:port]>', 'Meshblu host'
-      .option '-t, --timeout <milliseconds>', 'Request timeout'
+      .option '-s, --server <host[:port]>', "Meshblu host (default #{DEFAULT_HOST}:#{DEFAULT_PORT})"
+      .option '-t, --timeout <milliseconds>', "Request timeout (default #{DEFAULT_TIMEOUT})"
       .parse process.argv

       @data = JSON.parse(commander.data) if commander.data?
@@ -18,7 +19,7 @@

   parseConfig: =>
     {server, port} = @parseServer()
-    {server: server, port: port, timeout: commander.timeout || 10000}
+    {server: server, port: port, timeout: commander.timeout || DEFAULT_TIMEOUT}

   parseServer: =>
     unless commander.server?


enable treeview

enable treeview

--- .atom/config.cson
+++ .atom/config.cson
@@ -64,7 +64,6 @@
       "ide-go"
       "ide-yaml"
       "python-autopep8"
-      "tree-view"
     ]
     excludeVcsIgnoredPaths: false
     openEmptyEditorOnStart: false


renamed parseReturn to something which describes (more or less) what it does (since it does not parse a return)

--- core/app/backbone/views/autocompleted_add_to_channel_view.coffee
+++ core/app/backbone/views/autocompleted_add_to_channel_view.coffee
@@ -10,8 +10,8 @@
     "keyup input.typeahead": "autoComplete"
     "focus input.typeahead": "onFocusInput"
     "click div.fake-input": "focusInput"
-    "click div.auto_complete": "parseReturn"
-    "click div.fake-input a": "parseReturn"
+    "click div.auto_complete": "addCurrentlySelectedChannel"
+    "click div.fake-input a": "addCurrentlySelectedChannel"
     "blur input.typeahead": "blurInput"
     "click .show-input-button": "showInput"
     "mouseenter .auto_complete>div": "selectAddNew"
@@ -45,7 +45,7 @@
   parseKeyDown: (e) ->
     @_proceed = false
     switch e.keyCode
-      when 13 then @parseReturn e
+      when 13 then @addCurrentlySelectedChannel e
       when 40 then @_auto_completes_view.moveSelectionDown e
       when 38 then @_auto_completes_view.moveSelectionUp e
       when 27 then @hideAutoComplete()
@@ -81,7 +81,7 @@
     activeview.trigger "deactivate"  if activeview isnt `undefined`
     @setActiveChannelKey `undefined`

-  parseReturn: (e) ->
+  addCurrentlySelectedChannel: (e) ->
     e.preventDefault()
     e.stopPropagation()
     @disable()


Remove .coffee extension from package main

--- package.cson
+++ package.cson
@@ -1 +1 @@
-'main': 'lib/spell-check.coffee'
+'main': 'lib/spell-check'


Delete package.cson, it's no longer needed.

--- package.cson
+++ package.cson
@@ -1,363 +0,0 @@
-
-# NPM package file, see https://docs.npmjs.com/files/package.json
-# Oh, this is CSON, not JSON ( see CoffeeScript-Object-Notation https://github.com/bevry/cson )
-
-#####################################################################
-#####################################################################
-
-# Master source file used to generate package.json
-
-# DEFAULT METHOD (if package.json is intact):
-# npm run cson2json
-#
-# ALTERNATIVE METHOD (if package.json is corrupted):
-# npm install cson
-# node ./node_modules/cson/bin/cson2json package.cson > package.json
-# Note that on some Windows terminals (e.g. GitShell),
-# unfortunately the package.json file might need its encoding
-# converted to UTF-no-BOM :(
-# This is somehow due to the piped stdout-stdin result being UCS2-little-endian
-# (yet this works just fine when the same command is invoked from
-# "npm run cson2json" instead of from Node!)
-
-#####################################################################
-#####################################################################
-
-'/*': '----------------------------------------------------------------------------'
-'..': 'This package.json (JSON) is automatically generated from package.cson (CSON)'
-'.-': "DO NOT invoke 'npm init' or 'npm install --save' ('--save-dev' / '--save-optional')"
-'._': 'as this would overwrite / update the JSON, not the master CSON!'
-'*/': '----------------------------------------------------------------------------'
-
-#####################################################################
-
-name: "readium-cfi-js"
-
-version: "0.31.0-alpha"
-
-engines:
-    node: ">=4"
-    npm: ">=2"
-    yarn: ">=0.23"
-
-description: "Readium - CFI javascript library"
-
-keywords: [
-    "readium"
-    "epub"
-    "epub3"
-    "cfi"
-]
-
-author:
-    name: "Readium (Daniel Weck)"
-    email: "daniel.weck@gmail.com"
-    url: "http://github.com/readium"
-
-license: "BSD-3-Clause"
-
-licenses: [
-    type: "BSD-3-Clause"
-    url: "http://opensource.org/licenses/BSD-3-Clause"
-]
-
-homepage: "http://github.com/readium/readium-cfi-js"
-
-bugs:
-    url: "http://github.com/readium/readium-cfi-js/issues"
-    email: "daniel.weck@gmail.com"
-
-repository:
-    type: "git"
-    url: "git://github.com/readium/readium-cfi-js.git"
-
-#####################################################################
-files: [
-
-    "build-output/"
-
-    "dev/"
-
-    "license.txt"
-
-    "README.md"
-]
-
-# "main" is designed for Node/CommonJS modules, not RequireJS/AMD ones.
-# See http://requirejs.org/docs/commonjs.html
-# See http://requirejs.org/docs/node.html
-#
-#main: "build-output/_multiple-bundles/readium-cfi-js.js"
-
-#####################################################################
-dependencies:
-
-    #"parallelshell": 'latest'
-    "concurrently": '^3.5.0'
-
-    "npm-run-all": '^4.1.2'
-
-    "opener": '^1.4.3'
-
-    #"http-server": 'latest'
-    "http-server": 'github:danielweck/http-server#master'
-    # SEE: https://github.com/indexzero/http-server/issues/154
-
-
-#####################################################################
-# These dependencies can be fetched by using the "--dev" option
-# in "npm install --dev readium-cfi-js".
-devDependencies:
-
-    "requirejs": '^2.3.5'
-
-    "almond": '^0.3.3'
-
-    "jquery": '2.x.x'
-
-    "underscore": '^1.8.3'
-
-
-    "pegjs": '^0.10.0'
-
-    "karma": '^1.7.1'
-
-    "karma-chrome-launcher": '^2.2.0'
-    "karma-firefox-launcher": '^1.0.1'
-
-    "jasmine-core": '^2.8.0'
-
-    "karma-jasmine": '^1.1.0'
-
-    "karma-sauce-launcher": '^1.2.0'
-
-
-#####################################################################
-scripts:
-
-#=====================
-    cson2json: '
-
-        node ./node_modules/cson/bin/cson2json package.cson > package.json
-        &&
-        node readium-build-tools/optimizePackageJsonScripts.js
-    '
-
-#=====================
-    'prepare:all': '
-
-        npm run
-            prepare:local
-    '
-
-#=====================
-    'prepare:local': '
-
-        (npm outdated || echo outdated)
-        &&
-        npm install
-        &&
-        npm run prepare:local:common
-    '
-
-#=====================
-    'prepare:yarn:all': '
-
-        yarn run
-            prepare:yarn:local
-    '
-
-#=====================
-    'prepare:yarn:local': '
-
-        (yarn outdated || echo outdated)
-        &&
-        yarn install
-        &&
-        yarn run prepare:local:common
-    '
-
-#=====================
-    'prepare:local:common': '
-
-        node
-            readium-build-tools/patchRequireJS.js
-        &&
-        node
-            readium-build-tools/gitHubForksUpdater.js
-    '
-
-#=====================
-    clean: '
-
-        (yarn --version || echo noyarn)
-        &&
-        node --version
-        &&
-        npm --version
-        &&
-        npm run rimraf
-            build-output/*
-        &&
-        npm run rimraf
-            api-docs/*
-        &&
-        npm run rimraf
-            gen/*
-    '
-
-#=====================
-    apidocs: '
-
-        npm run yuidocjs
-    '
-
-#=====================
-    'prebuild': '
-
-        npm run clean
-    '
-
-#=====================
-    build: '
-
-        npm run
-            build:scripts
-    '
-
-#=====================
-    lint: '
-
-        npm run jshint
-            "js/**.js"
-    '
-
-#=====================
-    'prebuild:scripts': '
-
-        npm run build:scripts:peg
-    '
-
-#=====================
-    'build:scripts': '
-
-        npm run
-            build:scripts:multiple
-        &&
-        npm run
-            build:scripts:single
-    '
-
-#=====================
-    'build:scripts:peg': '
-
-        npm run pegjs
-            --format globals --export-var window.EPUBcfiParser -o gen/cfi_parser_gen.js cfi_parser.pegjs
-    '
-
-#=====================
-    'build:scripts:multiple': '
-
-        npm run rjs
-             --rjs_bundle=multiple
-    '
-
-#=====================
-    'postbuild:scripts:multiple': '
-
-        npm run rimraf
-            build-output/_multiple-bundles/RequireJS.js.src.js
-    '
-
-#=====================
-    'build:scripts:single': '
-
-        npm run rjs
-             --rjs_bundle=single
-    '
-
-#=====================
-#### BLOCKING! (CTRL-C) [watch][live-reload][http]
-    'http:watch': '
-
-        echo > open_webbrowser.js
-        &&
-        run-p
-            \"dowatch\"
-            \"http\"
-    '
-#    \"live-reload\"
-
-#=====================
-#### BLOCKING! (CTRL-C) [http]
-    'http': '
-
-        node
-            readium-build-tools/http-server.js
-
-            -LOGFILE
-            http.log
-
-            -OPEN
-            /dev/
-
-            -c-1
-            -a 127.0.0.1
-            -p 9090
-            --cors
-            .
-    '
-
-#=====================
-    'test': '
-
-       npm run karma
-          start tests/karma.conf.js --singleRun --no-auto-watch
-    '
-
-#=====================
-    'test:travis': '
-
-       npm run karma
-          start tests/karma.travis.conf.js --singleRun --no-auto-watch
-    '
-
-#=====================
-    'test:travis:firefox': '
-
-       npm run test:travis --readium-cfi-js:MODE=firefox
-    '
-
-#=====================
-    'test:travis:chrome': '
-
-       npm run test:travis --readium-cfi-js:MODE=chrome
-    '
-
-
-
-#=====================
-#### BLOCKING! (CTRL-C) [watch or nodemon]
-    'dowatch': '
-
-        npm run watch
-            \"npm run build
-                && npm run test
-                && node readium-build-tools/webbrowser.js
-                && npm run rimraf
-                      open_webbrowser.js\"
-            dev/ js/ tests/
-    '
-    # unfortunately, cannot watch single file cfi_parser.pegjs :(
-
-    #    nodemon -q
-    #    -w dev/
-    #    --ext \".\"
-    #    --exec \" ... "
-
-
-#=====================
-#    'live-reload': '
-#
-#        live-reload --port 9091 --delay 1000 build-output/
-#    '


Added conflict-resolution to OPF file

Added conflict-resolution to OPF file

--- scripts/gh-book/opf-file.coffee
+++ scripts/gh-book/opf-file.coffee
@@ -44,26 +44,43 @@
         # HACK: `?` is because `inherits/container.add` calls `trigger('change')`
         setNavModel(options)

-      @manifest.on 'add', (model, collection, options) =>
-        $manifest = @$xml.find('manifest')
-
-        relPath = Utils.relativePath(@id, model.id)
-
-        # Check if the item is not already in the manifest
-        return if $manifest.find("item[href='#{relPath}']")[0]
-
-        # Create a new `<item>` in the manifest
-        item = @$xml[0].createElementNS('http://www.idpf.org/2007/opf', 'item')
-        $item = $(item)
-        $item.attr
-          href:         relPath
-          id:           relPath # TODO: escape the slashes so it is a valid id
-          'media-type': model.mediaType
-
-        $manifest.append($item)
-        # TODO: Depending on the type add it to the spine for EPUB2
-
-        @_markDirty(options, true) # true == force because hasChanged == false
+      @manifest.on 'add', (model, collection, options) => @_addItem(model, options)
+
+      # These store the added items since last successful save.
+      # If this file was remotely updated then, when resolving conflicts,
+      # these items will be added back into the newly-updated OPF manifest
+      @_addedItems = {}
+
+    # Add an `<item>` to the OPF.
+    # Called from `@manifest.add` and `@resolveSaveConflict`
+    _addItem: (model, options={}, force=true) ->
+      $manifest = @$xml.find('manifest')
+
+      relPath = Utils.relativePath(@id, model.id)
+
+      # Check if the item is not already in the manifest
+      return if $manifest.find("item[href='#{relPath}']")[0]
+
+      # Create a new `<item>` in the manifest
+      item = @$xml[0].createElementNS('http://www.idpf.org/2007/opf', 'item')
+      $item = $(item)
+      $item.attr
+        href:         relPath
+        id:           relPath # TODO: escape the slashes so it is a valid id
+        'media-type': model.mediaType
+
+      # Randomly add the item into the manifest.
+      # Always appending results in commit conflicts at the bottom of the file
+      $manifestChildren = $manifest.children()
+      index = $manifestChildren.length * Math.random()
+      $item.insertBefore($manifestChildren.eq(index))
+      # TODO: Depending on the type add it to the spine for EPUB2
+
+      @_markDirty(options, force)
+
+      # Push it to the set of items that were added since last save.
+      # This is useful when the OPF file was remotely updated
+      @_addedItems[model.id] = model

     _loadComplex: (fetchPromise) ->
@@ -103,6 +120,7 @@

             path = Utils.resolvePath(contextPath, href)
             contentModel = allContent.get path
+            throw new Error 'ERROR: File in nav missing in OPF' if not contentModel

             # Set all the titles of models in the workspace based on the nav tree
             # XhtmlModel titles are not saved anyway.
@@ -222,7 +240,14 @@
       # **TODO:** Fall back on `toc.ncx` and then the `spine` to create a navTree if one does not exist
       return {title: title, bookId: bookId}

-    serialize: () -> serializer.serializeToString(@$xml[0])
+    serialize: () ->
+      serializer.serializeToString(@$xml[0])
+
+    resolveSaveConflict: () ->
+      @_addItem(model, {}, false) for model in _.values(@_addedItems)
+
+    onSaved: () ->
+      @_addedItems = {}

     newNode: (options) ->
       model = options.model
@@ -243,6 +268,5 @@
           model: @
         callback(view)

-
   # Mix in the loadable
   PackageFile = PackageFile.extend loadable


eliminate proposal page

eliminate proposal page

--- @client/app.coffee
+++ @client/app.coffee
@@ -279,22 +279,8 @@
                       fontSize: 16
                     "Check if the url is correct. The author may also have deleted it. Good luck!"

-              else if EXPAND_IN_PLACE
+              else
                 Homepage key: 'homepage'
-
-              else
-                result = null
-
-                if page.proposal
-                  result = Proposal key: page.proposal, proposal: page.proposal
-                else if !page.proposal? && arest.cache['/proposals']?.proposals?
-                  # search to see if we already have this proposal loaded
-                  for proposal in arest.cache['/proposals'].proposals
-                    if '/' + proposal.slug == loc.url
-                      result = Proposal key: "/proposal/#{proposal.id}", proposal: "/proposal/#{proposal.id}"
-                      break
-
-                result or LOADING_INDICATOR

       Footer(key: 'page_footer') if access_granted
@@ -397,13 +383,9 @@

           BrowserHacks()

-          if EXPAND_IN_PLACE
-            Page
-              page: "/page#{loc.url}"
-          else
-            Page
-              key: "/page#{loc.url}"
-              page: "/page#{loc.url}"
+          Page
+            page: "/page#{loc.url}"
+

       Tooltip()
       Popover()


Tweak test wording

Tweak test wording

--- components/article/test/templates.coffee
+++ components/article/test/templates.coffee
@@ -66,7 +66,7 @@
     html.should.not.containEql 'article-share-fixed'
     html.should.containEql 'article-social'

-  it 'includes the correct links for infinite scroll articles', ->
+  it 'includes the correct share links for infinite scroll articles', ->
     html = render('index')
       article: new Article title: 'hi', slug: 'foo', sections: [], section_ids: []
       crop: (url) -> url


yosemite-unity-ui them for Atom

yosemite-unity-ui them for Atom

--- components/atom/atom.symlink/config.cson
+++ components/atom/atom.symlink/config.cson
@@ -18,7 +18,7 @@
     'sublime-tabs'
   ]
   'themes': [
-    'unity-dark-ui'
+    'yosemite-unity-ui'
     'atom-light-syntax'
   ]
 'autosave': {}


Log render timing

Log render timing

--- src/trix/controllers/document_controller.coffee
+++ src/trix/controllers/document_controller.coffee
@@ -21,10 +21,12 @@
     @delegate?.documentControllerDidSelectAttachment?(attachment)

   render: ->
+    console.time?("DocumentController#render") if Trix.debug.logEditOperations
     @delegate?.documentControllerWillRender?()
     @documentView.render()
     @reinstallAttachmentEditor()
     @delegate?.documentControllerDidRender?()
+    console.timeEnd?("DocumentController#render") if Trix.debug.logEditOperations

   focus: ->
     @documentView.focus()


npm install fix

npm install fix

--- docs/components/home/HomeFeature.cjsx
+++ docs/components/home/HomeFeature.cjsx
@@ -19,6 +19,8 @@

       inside:
         textAlign: 'center'
+        zIndex: '2'
+        position: 'relative'

       headline:
         paddingLeft: '10px'
@@ -39,6 +41,7 @@
         fontSize: '20px'
         color: 'rgba(255, 255, 255, .87)'
         fontWeight: '200'
+        background: '#4A90E2'

       dollar:
         paddingRight: '10px'


bye bye coffee

bye bye coffee

--- bokehselectize/selectizeselect.coffee
+++ bokehselectize/selectizeselect.coffee
@@ -1,145 +0,0 @@
-import {logger} from "core/logging"
-import * as p from "core/properties"
-
-import {InputWidget, InputWidgetView} from "models/widgets/input_widget"
-import {empty, label, select, div} from "core/dom"
-
-
-export class SelectizeSelectView extends InputWidgetView
-
-  initialize: (options) ->
-    super(options)
-
-    empty(@el)
-    html = @template(@model.attributes)
-    @el.appendChild(html)
-    @selector = '#' + @model.attributes.id
-
-    if !!@model.options_external_json
-      options = @get_options_from_json()
-    else
-      options = @get_options()
-      @_finalise_initialisation(options)
-
-    @connect(@model.change, @update_value)
-
-    if !@model.options_external_json
-      @connect(@model.options.change, () =>
-        @update_options();
-      )
-
-  _finalise_initialisation: (options) ->
-    search_fields = @get_search_fields()
-
-    render = {}
-    if !!@model.render_option_template
-      render['option'] = (item, escape) =>
-        # based on http://stackoverflow.com/a/1408373
-        return @model.render_option_template.replace(/{([^{}]*)}/g,
-                                            (a, b) =>
-                                              return escape(item[b]))
-
-    if !!@model.render_item_template
-      render['item'] = (item, escape) =>
-        # based on http://stackoverflow.com/a/1408373
-        return @model.render_item_template.replace(/{([^{}]*)}/g,
-                                            (a, b) =>
-                                              return escape(item[b]))
-
-    # Since coffeescript 2 this has to be created in constructor
-    @_selectize_value_changed = (value) =>
-       @model.value = value
-
-    selectize_options = {
-      options: options
-      searchField: search_fields
-      valueField: @model.value_field
-      labelField: @model.label_field
-      maxItems: @model.max_items
-      onChange: @_selectize_value_changed
-      items: @model.value
-      render: render
-    }
-
-    # I am not entirely sure why I need to wrap in jQuery twice.
-    # but hey, it works
-    select = jQuery(jQuery(@el).find(@selector)[0]).selectize(selectize_options)
-    @_selectize = select[0].selectize;
-    @render()
-
-  get_options: () ->
-    options_source = @model.options
-
-    options = []
-
-    for i in [0...options_source.get_length()]
-      d = {}
-      for column in options_source.column_names
-        d[column] = options_source.get_column(column)[i]
-
-      options.push(d)
-
-    return options
-
-  get_options_from_json: () ->
-    url = @model.options_external_json
-    console.log("Populating selectize select from #{url}")
-
-    $.ajax url,
-      type: 'GET'
-      dataType: 'json'
-      error: (jqXHR, textStatus, errorThrown) ->
-        console.log "Error fetching data from #{url}: #{textStatus}"
-      success: (data, textStatus, jqXHR) =>
-        console.log "Successfully fetched data from #{url}"
-        @_finalise_initialisation(data)
-
-  update_options: () ->
-    options = @get_options()
-    @_selectize.clearOptions()
-    @_selectize.addOption(options)
-
-
-  get_search_fields: () ->
-    search_fields = @model.search_fields
-    if !search_fields?
-      console.log('Search fields were not provided, assuming all fields are searchable')
-      search_fields = @model.options.column_names
-
-    return search_fields
-
-  update_value: () =>
-    console.log('Updating value: '+ @model.value);
-    @_selectize.setValue(@model.value, true);
-
-
-  render: () ->
-    super()
-    return @
-
-
-  template: () ->
-    return (div({class: ""},
-      label({for: @model.id},
-            @model.title,
-            ),
-            select({class: "", id: @model.id, name: @model.name, placeholder: @model.placeholder})
-    ))
-
-
-export class SelectizeSelect extends InputWidget
-  type: "SelectizeSelect"
-  default_view: SelectizeSelectView
-
-  @define {
-    value: [p.Any]
-    placeholder: [p.String, '']
-    options: [ p.Instance ]
-    options_external_json: [p.String, '']
-    value_field: [ p.String ]
-    label_field: [ p.String ]
-    max_items: [ p.Int, 1]
-    search_fields: [p.Any]
-    render_item_template: [ p.String ]
-    render_option_template: [ p.String ]
-  }


Log view now display logs in chronological order

--- admin/static/coffee/log_view.coffee
+++ admin/static/coffee/log_view.coffee
@@ -5,66 +5,100 @@
         className: 'log-view'
         template: Handlebars.compile $('#log-container-template').html()
         header_template: Handlebars.compile $('#log-header-template').html()
-        max_log_entries: 20
+        max_log_entries: 10
+
+        current_logs: []
+        displayed_logs: 0
+        max_timestamp: 0
+
         log_route: '#logs'

         events: ->
             'click .next-log-entries': 'next_entries'
             'click .update-log-entries': 'update_log_entries'

-        #TODO Logs are not displayed in order (time)...
         initialize: ->
             log_initial '(initializing) events view: container'
-            @log_entries = new LogEntries
-
-            @set_interval = setInterval @check_for_new_updates, updateInterval
-
-        check_for_new_updates: =>
-            if window.location.hash is @log_route and @log_entries.length > 0
-                min_timestamp = @log_entries.at(0).get('timestamp')
-                route = "/ajax/log/_?min_timestamp=#{min_timestamp}" if min_timestamp?
-
-                @num_new_entries = 0
-                $.getJSON route, (log_data_from_server) =>
-                    for machine_uuid, log_entries of log_data_from_server
-                        @num_new_entries += log_entries.length
-                    @render_header()
+
+           #@set_interval = setInterval @check_for_new_updates, updateInterval

         render: =>
             @.$el.html @template({})
-            @$log_entries = @.$('ul.log-entries')
-            @fetch_log_entries(
-                {}, (new_log_entries) =>
-                    @log_entries.add new_log_entries.models[0...@max_log_entries]
-                    @render_header()
-
-                    # Render each log entry (append it to the list)
-                    @log_entries.each (entry) =>
+
+            @fetch_log_entries
+                max_length: @max_log_entries
+
+            @.delegateEvents()
+
+            return @
+
+        fetch_log_entries: (url_params) =>
+            route = "/ajax/log/_?"
+            for param, value of url_params
+                route+="&#{param}=#{value}"
+
+            $.getJSON route, @parse_log
+
+        parse_log: (log_data_from_server) =>
+            @max_timestamp = 0
+            for machine_uuid, log_entries of log_data_from_server
+
+                if log_entries.length > 0
+                    @max_timestamp = parseFloat(log_entries[log_entries.length-1].timestamp) if @max_timestamp < parseFloat(log_entries[log_entries.length-1].timestamp)
+
+                last_position = 0
+                for json in log_entries # For each new log
+                    log_saved = false
+                    # Looking if it has been already added
+                    for log, i in @current_logs
+                        is_same_log = true
+                        for attribute of log.attributes
+                            if not json.attribute? or log.get(attribute) isnt json.attribute
+                                is_same_log = false
+                        if is_same_log
+                            log_saved = true
+                            console.log 'break'
+                            break
+
+                    # If it wasn't saved before, look if its place is IN the list
+                    if log_saved is false
+                        for log, i in @current_logs
+                            if parseFloat(json.timestamp) > parseFloat(log.get('timestamp'))
+                                entry = new LogEntry json
+                                entry.set('machine_uuid',machine_uuid)
+                                @current_logs.splice i, 0, entry
+                                log_saved = true
+                                break
+
+                    # If it's not, just add it ad the end
+                    if log_saved is false
+                        entry = new LogEntry json
+                        entry.set('machine_uuid',machine_uuid)
+                        @current_logs.push entry
+
+            if @current_logs.length <= @displayed_logs
+                @.$('.no-more-entries').show()
+                @.$('.next-log-entries').hide()
+                return
+            else
+                for i in [0..@max_log_entries-1]
+                    if @current_logs[@displayed_logs]?
                         view = new LogView.LogEntry
-                            model: entry
-                        @$log_entries.append view.render().el
-            )
-
-            @.delegateEvents()
-
-            return @
+                            model: @current_logs[@displayed_logs]
+                        @.$('.log-entries').append view.render().el
+                        @displayed_logs++
+                    else
+                        @.$('.no-more-entries').show()
+                        @.$('.next-log-entries').hide()
+
+

         next_entries: (event) =>
             event.preventDefault()
-            # Ensure we have older entries (older than the oldest timestamp we're displaying)
-            @fetch_log_entries(
-                max_timestamp: @log_entries.at(@log_entries.length-1).get('timestamp')-1 # Use parseInt instead?
-                , (new_log_entries) =>
-                    last_rendered_entry_index = @log_entries.length - 1
-                    @log_entries.add new_log_entries.models[0...@max_log_entries]
-                    @render_header()
-
-                    # Render each log entry (append it to the list)
-                    for entry in @log_entries.models[last_rendered_entry_index...@log_entries.length]
-                        view = new LogView.LogEntry
-                            model: entry
-                        @$log_entries.append view.render().el
-            )
+
+            @fetch_log_entries
+                max_length: @max_log_entries
+                max_timestamp: @max_timestamp

         update_log_entries: (event) =>
             event.preventDefault()
@@ -91,6 +125,18 @@
                             , 2000
                 )

+        check_for_new_updates: =>
+            if window.location.hash is @log_route and @log_entries.length > 0
+                min_timestamp = @log_entries.at(0).get('timestamp')
+                route = "/ajax/log/_?min_timestamp=#{min_timestamp}" if min_timestamp?
+
+                @num_new_entries = 0
+                $.getJSON route, (log_data_from_server) =>
+                    for machine_uuid, log_entries of log_data_from_server
+                        @num_new_entries += log_entries.length
+                    @render_header()
+
+
         render_header: =>
             @.$('.header').html @header_template
                 new_entries: @num_new_entries > 0
@@ -100,28 +146,6 @@
                 from_date: new XDate(@log_entries.at(0).get('timestamp')*1000).toString("MMMM M, yyyy 'at' HH:mm:ss")
                 to_date: new XDate(@log_entries.at(@log_entries.length-1).get('timestamp')*1000).toString("MMMM M, yyyy 'at' HH:mm:ss")

-        fetch_log_entries: (url_params, callback) =>
-            route = "/ajax/log/_?&max_length=#{@max_log_entries * machines.length}"
-
-            for param, value of url_params
-                route+="&#{param}=#{value}"
-
-            $.getJSON route, (log_data_from_server) =>
-                new_log_entries = new LogEntries
-                for machine_uuid, log_entries of log_data_from_server
-                    # Otherwise, keep processing log entry json and prepare to render it
-                    for json in log_entries
-                        entry = new LogEntry json
-                        entry.set('machine_uuid',machine_uuid)
-                        new_log_entries.add entry
-
-                # Don't render anything if there are no new log entries.
-                if new_log_entries.length is 0
-                    @.$('.no-more-entries').show()
-                    @.$('.next-log-entries').hide()
-                    return
-
-                callback(new_log_entries)

         destroy: =>
             clearInterval @set_interval


Make ctags executable

Make ctags executable

--- build/tasks/generate-asar-task.coffee
+++ build/tasks/generate-asar-task.coffee
@@ -18,4 +18,9 @@

       rm appDir
       fs.renameSync path.resolve(appDir, '..', 'new-app'), appDir
+
+      ctagsFolder = path.join("#{appDir}.asar.unpacked", 'node_modules', 'symbols-view', 'vendor')
+      for ctagsFile in fs.readdirSync(ctagsFolder)
+        fs.chmodSync(path.join(ctagsFolder, ctagsFile), "755")
+
       done()


Fix for blaze

Fix for blaze

--- t9n.coffee
+++ t9n.coffee
@@ -1,7 +1,10 @@
 if Meteor.isClient
+  if Package.ui
+  ->
+    Handlebars = Package.ui.Handlebars
+
   Handlebars.registerHelper 't9n', (x, prefix='') ->
     T9n.get(x, prefix)
-

 class T9n

@@ -33,4 +36,4 @@
         destination = Object.getOwnPropertyDescriptor(from, name)
         Object.defineProperty(dest, name, destination)

-@T9n = T9n+@T9n = T9n


lastArr.lenth -> lastArrLen

lastArr.lenth -> lastArrLen

--- tds.coffee
+++ tds.coffee
@@ -220,6 +220,7 @@
  length = 0

  lastArr = null
+ lastArrLen = 0
  lastViews = null
  i_lastArr = i_lastPos = 0
  i_llp = -1
@@ -400,6 +401,7 @@
     allViews.push lastViews
     sum.push capacity

+   lastArrLen = arrays[i_lastArr].length
    if i_lastArr >= MAX_LINEAR_ARRAY_INDEX
     MAX_LINEAR_FIND_INDEX = sum[MAX_LINEAR_ARRAY_INDEX]

@@ -470,7 +472,7 @@

   push: (val) ->
    res = off
-   if i_lastPos is lastArr.length
+   if i_lastPos is lastArrLen
     @addArray()
     i_lla++
     i_llp = 0
@@ -490,12 +492,13 @@
    return res

   addArray: ->
-   n = lastArr.length
+   n = lastArrLen
    if n isnt MAX_SIZE
     n = n << 1

    capacity += n
    lastArr = TDS.Array struct, n
+   lastArrLen = n
    lastViews = lastArr.views
    arrays.push lastArr
    allViews.push lastViews


Modifies contact email

Modifies contact email

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -26,7 +26,7 @@
       macZip: true
       macPlist:
         NSHumanReadableCopyright: 'aluxian.com'
-        CFBundleIdentifier: 'com.aluxian.messengerfordesktop'
+        CFBundleIdentifier: 'com.sytten.messengerfordesktop'
     )

     nw.build()
@@ -88,7 +88,7 @@
           shelljs.mkdir '-p', '../../dist' # it fails if the dir doesn't exist
           shelljs.rm '-f', output # it fails if the package already exists

-          shelljs.exec "fpm -s dir -t #{target} -a #{port} --rpm-os linux -n messengerfordesktop --after-install ./opt/MessengerForDesktop/after-install.sh --after-remove ./opt/MessengerForDesktop/after-remove.sh --license MIT --category Chat --url \"https://messengerfordesktop.com\" --description \"A simple and beautiful app for Facebook Messenger. Chat without distractions on any OS.\" -m \"Alexandru Rosianu <me@aluxian.com>\" -p #{output} -v #{manifest.version} ."
+          shelljs.exec "fpm -s dir -t #{target} -a #{port} --rpm-os linux -n messengerfordesktop --after-install ./opt/MessengerForDesktop/after-install.sh --after-remove ./opt/MessengerForDesktop/after-remove.sh --license MIT --category Chat --url \"https://messengerfordesktop.com\" --description \"A simple and beautiful app for Facebook Messenger. Chat without distractions on any OS.\" -m \"Emile Fugulin <emilefugulin@gmail.com>\" -p #{output} -v #{manifest.version} ."
           shelljs.cd '../..'

 # Make packages for all platforms


don't initialize livereload when NODE_ENV=production

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -57,12 +57,15 @@
     .pipe gulp.dest paths.scripts.destination

 gulp.task 'templates', ->
-  gulp
+  pipeline = gulp
     .src paths.templates.source
     .pipe(jade(pretty: not production))
     .on 'error', handleError
     .pipe gulp.dest paths.templates.destination
-    .pipe livereload()
+
+  pipeline = pipeline.pipe livereload() unless production
+
+  pipeline

 gulp.task 'styles', ->
   styles = gulp
@@ -72,9 +75,9 @@
     .pipe prefix 'last 2 versions', 'Chrome 34', 'Firefox 28', 'iOS 7'

   styles = styles.pipe(CSSmin()) if production
-
-  styles.pipe gulp.dest paths.styles.destination
-    .pipe livereload()
+  styles = styles.pipe gulp.dest paths.styles.destination
+  styles = stypes.pipe livereload() unless production
+  styles

 gulp.task 'assets', ->
   gulp


Remove extraneous livereload triggers on watch.

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -132,22 +132,16 @@

 gulp.task 'watch', (cb) ->

-	gulp.watch([config.srcCss, config.srcSass, config.srcStylus], ['styles'])._watcher.on 'all', livereload
-	gulp.watch(config.srcClientScripts, ['clientScripts'])._watcher.on 'all', livereload
-	gulp.watch([config.srcAllJs, config.srcAllCoffee, '!' + config.srcClientScripts], ['serverScripts'])._watcher.on 'all', livereload
+	gulp.watch([config.srcCss, config.srcSass, config.srcStylus], ['styles'])
+	gulp.watch(config.srcClientScripts, ['clientScripts'])
+	gulp.watch([config.srcAllJs, config.srcAllCoffee, '!' + config.srcClientScripts], ['serverScripts'])
 	# gulp.watch([config.views], ['views'])._watcher.on 'all', livereload
 	# gulp.watch(config.srcImg, ['images'])._watcher.on 'all', livereload

 # default task -- run 'gulp' from cli
 gulp.task 'default', (cb) ->

-	runSequence 'clean', [
-		# 'plugins'
-		'clientScripts'
-		'serverScripts'
-		'styles'
-		# 'images'
-	], 'watch', cb
+	runSequence 'build', 'watch', cb

 	livereload.listen config.livereloadPort


Added postcss and autoprefixer -- closes #4

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -1,17 +1,35 @@
 # load required libraries
-gulp    = require 'gulp'
-connect = require 'gulp-connect'
-sass    = require 'gulp-sass'
+gulp         = require 'gulp'
+connect      = require 'gulp-connect'
+sass         = require 'gulp-sass'
+postcss      = require 'gulp-postcss'
+autoprefixer = require 'autoprefixer'
+uglify       = require 'gulp-uglify'
+cssmin       = require 'gulp-cssmin'
+rename       = require 'gulp-rename'

 # sass compile
 gulp.task 'sass', ->
+
+  processors = [
+    autoprefixer({browsers: ['last 3 versions']})
+  ]
+
   gulp.src('./src/sass/main.scss')
   .pipe(sass().on('error', sass.logError))
+  .pipe(postcss(processors))
   .pipe(gulp.dest('./dist/css'))
+  .pipe(cssmin())
+  .pipe(rename({suffix: '.min'}))
+  .pipe(gulp.dest('./dist/css'))
+

   gulp.src('./src/sass/docs.scss')
   .pipe(sass().on('error', sass.logError))
+  .pipe(postcss(processors))
+  .pipe(cssmin())
+  .pipe(rename({suffix: '.min'}))
   .pipe(gulp.dest('./dist/css'))

@@ -30,4 +48,4 @@
     }
   })

-gulp.task 'devel', ['server', 'sass:watch']
+gulp.task 'devel', ['sass','server', 'sass:watch']


Fixed syntax in gulpfile.coffee

Fixed syntax in gulpfile.coffee

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -11,8 +11,8 @@
   gulp.watch './src/**/*.jade', ['jade']

   # Webpack
-  gulp.watch '.src/**/*.coffee', ['webpack']
-  gulp.watch '.src/**/*.sass', ['webpack']
+  gulp.watch './src/**/*.coffee', ['webpack']
+  gulp.watch './src/**/*.sass', ['webpack']

 # Build
 gulp.task 'build', ['webpack', 'jade']
@@ -22,9 +22,7 @@
   gulp.src './src/*.jade', base: 'src'
     .pipe jade {}
     .pipe gulp.dest './dist'
-#  gulp.src './src/*.jade', base: 'src'
-#    .pipe jade pretty: true, locals: { web: true }
-#    .pipe gulp.dest './dist'
+

 gulp.task 'webpack', (done) ->
   webpack webpackConfig, (error, stats) ->
@@ -34,18 +32,3 @@
     gutil.log '[webpack]', stats.toString()

   done()
-
-
-###
-
-WOAH
-
-So you're not ggetting anything different because all you're doing is taking the input and putting it into ./dist.
-
-If you look at my code, you'll see after gulp.src is a line like this:
-.pipe jade
-Do I need the pretty: true {web: true}?
-
-Not necessaarily. Not for now.Save this and gulp jade
-jade is not defined. npm install gulp-jade?
-###


add gulp ship

add gulp ship

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -38,11 +38,15 @@
 all_src = ['src/*', 'src/*/*', '../reactive-coffee/src/*']
 gulp.task 'watch', ['sync'], ->
   gulp.watch all_src, all_builds
+
+# Watch when run
+
+gulp.task 'default', ['main', 'watch']

 # Upload to S3

 gulp.task 'upload', shell.task(['node aws/upload.js'])

-# Watch when run
+# Tag and upload new feature

-gulp.task 'default', ['main', 'watch']
+gulp.task 'ship', ['tag', 'upload']


Adds another test for clear_irrelevant_labels.

- Moves the other test to it's appropriate home.

--- spec/jumpy-spec.coffee
+++ spec/jumpy-spec.coffee
@@ -130,6 +130,9 @@
             editorView.trigger 'jumpy:a'
             expect(atom.workspaceView.statusBar
                 ?.find('#status-bar-jumpy #status').html()).toBe 'a'
+        it "removes all labels that don't begin with a", ->
+            editorView.trigger 'jumpy:a'
+            expect(editorView.find('.jumpy.label').length).toBe 26

     describe "when the jumpy:reset event is triggered", ->
         it "clears first entered key and lets a new jump take place", ->
@@ -147,8 +150,7 @@
             editorView.trigger 'jumpy:reset'
             expect(atom.workspaceView.statusBar
                 ?.find('#status-bar-jumpy #status').html()).toBe 'Jump Mode!'
-
-    describe "when the jumpy:a event is triggered", ->
-        it "removes all labels that don't begin with a", ->
+        it "resets all labels even those that don't begin with a", ->
             editorView.trigger 'jumpy:a'
-            expect(editorView.find('.jumpy.label').length).toBe 26
+            editorView.trigger 'jumpy:reset'
+            expect(editorView.find('.jumpy.label').length).toBe 84


WIP: Keymap

WIP: Keymap

--- public/app/game.coffee
+++ public/app/game.coffee
@@ -5,6 +5,21 @@
             @blocks = {}
             @player = {x: 0, y: 0, avatar: null}
             @camera = {x: 0, y: 0}
+
+            @key_codes =
+                38: "Up"
+                40: "Down"
+                37: "Left"
+                39: "Right"
+
+            document.onkeyup = @key_up
+            document.onkeydown = @key_down
+
+        key_up: (event) =>
+            console.log "Key up:", (@key_codes[event.which] or event.which)
+
+        key_down: (event) =>
+            console.log "Key down:", (@key_codes[event.which] or event.which)

         ready: () =>
             @emit "ready", @


Util: method CopyObject now clones the node when it's a HTMLElement.

--- src/base/util.coffee
+++ src/base/util.coffee
@@ -302,6 +302,8 @@
 				return obj

 		if obj instanceof HTMLElement
+			if obj.cloneNode
+				return obj.cloneNode(true)
 			return obj

 		if obj instanceof CUI.Dummy


testing

testing

--- electron/js/main.coffee
+++ electron/js/main.coffee
@@ -59,8 +59,11 @@

 createWindow = ->
  #console.log electron
- #autoUpdater = electron.autoUpdater
- #autoUpdater.setFeedURL "http://localhost:3000/update"
+ autoUpdater = electron.autoUpdater
+ try
+  autoUpdater.setFeedURL "http://localhost:3000/update"
+ catch e
+  console.log e
  mainWindow = new BrowserWindow width: 1200, height: 900
  mainWindow.setMenu null
  mainWindow.loadURL "file://#{__dirname}/index.html"
@@ -72,13 +75,17 @@
   # when you should delete the corresponding element.
   mainWindow = null

- #autoUpdater.addListener 'error', (e) ->
- # throw 'error'
+ autoUpdater.addListener 'error', (e) ->
+  console.log e
+  #throw 'error'

  mainWindow.webContents.once "did-frame-finish-load", (e) ->
   console.log 'frame load'
   #/update/RELEASES?id=analytics&localVersion=4.0.0&arch=amd64
-  #autoUpdater.checkForUpdates()
+  try
+   autoUpdater.checkForUpdates()
+  catch e
+   console.error e

 #return if handleSquirrelEvent()
@@ -95,4 +102,7 @@
  # On OS X it's common to re-create a window in the app when the
  # dock icon is clicked and there are no other windows open.
  if not mainWindow?
-  createWindow()
+  try
+   createWindow()
+  catch e
+   console.log e


Globalise common language elements

Globalise common language elements

This resolves the issue of missing highlighting in heredoc declarations.

--- grammars/apl.cson
+++ grammars/apl.cson
@@ -13,15 +13,6 @@
 	{
 		match: "\\A#!.*$"
 		name:  "comment.line.shebang.apl"
-	}
-
-
-	# System variables
-	{
-		match: "(⎕)[A-Z]*"
-		name: "support.system.variable.apl"
-		captures:
-			1: {name: "punctuation.definition.quad.apl"}
 	}

@@ -69,75 +60,6 @@
 		contentName: "text.embedded.html.basic"
 		patterns: [{ include: "text.html.basic" }]
 	}
-
-
-
-	# MISC
-	#=====================================================================
-	{match: ";",       name: "keyword.operator.semicolon.apl"}
-	{match: "¯",       name: "keyword.operator.high-minus.apl"}
-	{match: "←",       name: "keyword.operator.assignment.apl"}
-	{match: "→",       name: "keyword.control.goto.apl"}
-
-
-
-	# FUNCTIONS
-	#=====================================================================
-	# Monadic or Dyadic, depending on context
-	{match: "\\?",     name: "keyword.operator.random.apl"}
-	{match: "⌈",       name: "keyword.operator.ceiling.apl"}
-	{match: "⌊",       name: "keyword.operator.floor.apl"}
-	{match: "⍴",       name: "keyword.operator.rho.apl"}
-	{match: "[∼~]",    name: "keyword.operator.tilde.apl"}
-	{match: "[∣|]",    name: "keyword.operator.absolute.apl"}
-	{match: "⍳",       name: "keyword.operator.iota.apl"}
-	{match: "[⋆*]",    name: "keyword.operator.exponent.apl"}
-	{match: "[-−]",    name: "keyword.operator.minus.apl"}
-	{match: "\\+",     name: "keyword.operator.plus.apl"}
-	{match: "×",       name: "keyword.operator.times.apl"}
-	{match: "÷",       name: "keyword.operator.divide.apl"}
-	{match: ",",       name: "keyword.operator.catenate.apl"}
-	{match: "⌹",       name: "keyword.operator.quad-divide.apl"}
-	{match: "○",       name: "keyword.operator.circle.apl"}
-	{match: "⍟",       name: "keyword.operator.logarithm.apl"}
-	{match: "⌽",       name: "keyword.operator.rotate-last.apl"}
-	{match: "⊖",       name: "keyword.operator.rotate-first.apl"}
-	{match: "⍕",       name: "keyword.operator.format.apl"}
-	{match: "⍉",       name: "keyword.operator.transpose.apl"}
-	{match: "!",       name: "keyword.operator.factorial.apl"}
-
-
-	# Monadic-only
-	{match: "⍋",       name: "keyword.operator.grade-up.apl"}
-	{match: "⍒",       name: "keyword.operator.grade-down.apl"}
-	{match: "⍎",       name: "keyword.operator.execute.apl"}
-
-
-	# Dyadic-only functions
-	{match: "∈",       name: "keyword.operator.member-of.apl"}
-	{match: "↑",       name: "keyword.operator.take.apl"}
-	{match: "↓",       name: "keyword.operator.drop.apl"}
-	{match: "⊥",       name: "keyword.operator.decode.apl"}
-	{match: "⊤",       name: "keyword.operator.encode.apl"}
-	{match: "\\x5C",   name: "keyword.operator.backslash.apl"}
-	{match: "\\/",     name: "keyword.operator.slash.apl"}
-	{match: "¨",       name: "keyword.operator.each.apl"}
-	{match: "<",       name: "keyword.operator.less.apl"}
-	{match: "≤",       name: "keyword.operator.less-or-equal.apl"}
-	{match: "=",       name: "keyword.operator.equal.apl"}
-	{match: "≥",       name: "keyword.operator.greater-or-equal.apl"}
-	{match: ">",       name: "keyword.operator.greater.apl"}
-	{match: "≠",       name: "keyword.operator.not-equal.apl"}
-	{match: "∨",       name: "keyword.operator.or.apl"}
-	{match: "∧",       name: "keyword.operator.and.apl"}
-	{match: "⍱",       name: "keyword.operator.nor.apl"}
-	{match: "⍲",       name: "keyword.operator.nand.apl"}
-
-	# Operators
-	{match: "⌿",       name: "keyword.operator.slash-bar.apl"}
-	{match: "⍀",       name: "keyword.operator.backslash-bar.apl"}
-	{match: "\\.",     name: "keyword.operator.inner-product.apl"}
-	{match: "∘\\.",    name: "keyword.operator.outer-product.apl"}
 ]

@@ -153,6 +75,8 @@
 			{ include: "#int"     }
 			{ include: "#name"    }
 			{ include: "#lambda"  }
+			{ include: "#symbols" }
+			{ include: "#sysvars" }
 		]
 	}

@@ -240,6 +164,75 @@
 		}]

+
+	# Symbols
+	symbols:
+		patterns: [
+			{match: ";",       name: "keyword.operator.semicolon.apl"}
+			{match: "¯",       name: "keyword.operator.high-minus.apl"}
+			{match: "←",       name: "keyword.operator.assignment.apl"}
+			{match: "→",       name: "keyword.control.goto.apl"}
+
+
+			# Monadic or Dyadic functions, depending on context
+			{match: "\\?",     name: "keyword.operator.random.apl"}
+			{match: "⌈",       name: "keyword.operator.ceiling.apl"}
+			{match: "⌊",       name: "keyword.operator.floor.apl"}
+			{match: "⍴",       name: "keyword.operator.rho.apl"}
+			{match: "[∼~]",    name: "keyword.operator.tilde.apl"}
+			{match: "[∣|]",    name: "keyword.operator.absolute.apl"}
+			{match: "⍳",       name: "keyword.operator.iota.apl"}
+			{match: "[⋆*]",    name: "keyword.operator.exponent.apl"}
+			{match: "[-−]",    name: "keyword.operator.minus.apl"}
+			{match: "\\+",     name: "keyword.operator.plus.apl"}
+			{match: "×",       name: "keyword.operator.times.apl"}
+			{match: "÷",       name: "keyword.operator.divide.apl"}
+			{match: ",",       name: "keyword.operator.catenate.apl"}
+			{match: "⌹",       name: "keyword.operator.quad-divide.apl"}
+			{match: "○",       name: "keyword.operator.circle.apl"}
+			{match: "⍟",       name: "keyword.operator.logarithm.apl"}
+			{match: "⌽",       name: "keyword.operator.rotate-last.apl"}
+			{match: "⊖",       name: "keyword.operator.rotate-first.apl"}
+			{match: "⍕",       name: "keyword.operator.format.apl"}
+			{match: "⍉",       name: "keyword.operator.transpose.apl"}
+			{match: "!",       name: "keyword.operator.factorial.apl"}
+
+
+			# Monadic-only functions
+			{match: "⍋",       name: "keyword.operator.grade-up.apl"}
+			{match: "⍒",       name: "keyword.operator.grade-down.apl"}
+			{match: "⍎",       name: "keyword.operator.execute.apl"}
+
+
+			# Dyadic-only functions
+			{match: "∈",       name: "keyword.operator.member-of.apl"}
+			{match: "↑",       name: "keyword.operator.take.apl"}
+			{match: "↓",       name: "keyword.operator.drop.apl"}
+			{match: "⊥",       name: "keyword.operator.decode.apl"}
+			{match: "⊤",       name: "keyword.operator.encode.apl"}
+			{match: "\\x5C",   name: "keyword.operator.backslash.apl"}
+			{match: "\\/",     name: "keyword.operator.slash.apl"}
+			{match: "¨",       name: "keyword.operator.each.apl"}
+			{match: "<",       name: "keyword.operator.less.apl"}
+			{match: "≤",       name: "keyword.operator.less-or-equal.apl"}
+			{match: "=",       name: "keyword.operator.equal.apl"}
+			{match: "≥",       name: "keyword.operator.greater-or-equal.apl"}
+			{match: ">",       name: "keyword.operator.greater.apl"}
+			{match: "≠",       name: "keyword.operator.not-equal.apl"}
+			{match: "∨",       name: "keyword.operator.or.apl"}
+			{match: "∧",       name: "keyword.operator.and.apl"}
+			{match: "⍱",       name: "keyword.operator.nor.apl"}
+			{match: "⍲",       name: "keyword.operator.nand.apl"}
+
+			# Operators
+			{match: "⌿",       name: "keyword.operator.slash-bar.apl"}
+			{match: "⍀",       name: "keyword.operator.backslash-bar.apl"}
+			{match: "\\.",     name: "keyword.operator.inner-product.apl"}
+			{match: "∘\\.",    name: "keyword.operator.outer-product.apl"}
+		]
+
+
+
 	# Direct definition notation (Lambda functions)
 	lambda:
 		name: "meta.lambda.function.apl"
@@ -261,6 +254,14 @@
 			{match: "χ",    name: "variable.lambda.arguments.axis.apl"}
 			{match: "λ",    name: "variable.lambda.symbol.apl"}
 		]
+
+
+	# System variables
+	sysvars:
+		match: "(⎕)[A-Z]*"
+		name: "support.system.variable.apl"
+		captures:
+			1: {name: "punctuation.definition.quad.apl"}

 	# Command arguments


stripped _runner. just calling single report file for now

--- queries/_runner.coffee
+++ queries/_runner.coffee
@@ -1,73 +1,14 @@
-walk = require 'walk'
-async = require 'async'
-_ = require "underscore"
-fs = require "fs"
+# filenames = []
+# for lv_one in ["all", "year", "location", "player"]
+#   for lv_two in ["all", "month", "dow"]
+#     for lv_three in ["summary", "performance", "time-span", "match-span", "match-streak"]
+#       for lv_four in ["all", "4p", "3p", "2p"]
+#         filename = ([lv_one, lv_two, lv_three, lv_four].join "_") + ".tex"
+#         filenames.push filename
+#
+# console.log "filenames: ", filenames
+# console.log "filenames.length: ", filenames.length
+
+performance = require "#{__dirname}/individual/performance"

-queryDirs = ['combined', 'individual']
-
-longtableHeadText = "-- continued from previous page"
-longtableFootText = "Continued on next page"
-
-objectToTable = (opts) ->
-  data = opts.data
-  columns = opts.columns
-
-  warning = "% auto compiled by tom\n\n"
-
-  aligns = _.pluck columns, "align"
-  columnAligns = "| " + aligns.join(" | ") + " |"
-
-  preamble = "\\begin{center} \n \\begin{longtable}{ #{columnAligns} } \n \\hline \n"
-
-  for column, i in columns
-    align = if i is 0 then "|c|" else "c|"
-    preamble += "\\multicolumn{1}{#{align}}{\\textbf{#{column.title}}}"
-    preamble += " & \n" unless i is columns.length - 1
-
-  preamble += "\\\\ \\hline \n \\endfirsthead \n\n"
-
-  longtable = "\\multicolumn{#{columns.length}}{c} \n"
-  longtable += "{{\\bfseries \\tablename \\thetable{} #{longtableHeadText}}} \\\\ \n"
-  longtable += "\\hline \n"
-
-  for column, i in columns
-    align = if i is 0 then "|c|" else "c|"
-    longtable += "\\multicolumn{1}{#{align}}{\\textbf{#{column.title}}} "
-    longtable += " & \n" unless i is columns.length - 1
-
-  longtable += "\\\\ \\hline \n \\endhead \n\n"
-  longtable += "\\hline \\multicolumn{#{columns.length}}{|r|}{{#{longtableFootText}}} \\\\ \\hline \n"
-  longtable += "\\endfoot\n \\hline\n \\endlastfoot\n\n"
-
-  body = "\\hline\n"
-  for row in data
-    for column, j in columns
-      body += row[column.property]
-      body += if j is columns.length - 1 then " \\\\ \n" else " & "
-    body += "\\hline\n"
-
-
-  footer = "\n\n"
-  footer += "\\end{longtable}\n \\end{center}"
-
-  fullTable = warning + preamble + longtable + body + footer
-  return fullTable
-
-async.forEachSeries queryDirs, (dir, forEachNext) ->
-  walker = walk.walk "#{__dirname}/#{dir}"
-
-  walker.on "file", (root, stats, next) ->
-    source = "#{root}/#{stats.name}"
-    query = require source
-    query.run (err, rows) ->
-      latexTable = objectToTable
-        data: rows
-        columns: query.columns
-
-      fileName = "#{__dirname}/../parts/tables/" + stats.name.split(".coffee")[0] + ".tex"
-      fs.writeFile fileName, latexTable, "utf8", (err) ->
-        next()
-
-  walker.on "end", ->
-    console.log "all queries run in #{dir}"
-    forEachNext()+performance()


removing semi-colons ['b116039ad6ed94c2040de00d2b1727ca4ff0ae82']

--- review_front/app/components/copy-to-clipboard.coffee
+++ review_front/app/components/copy-to-clipboard.coffee
@@ -1,5 +1,5 @@
 `import Ember from 'ember'`
-`import ZeroClipboard from 'ember-cli-zero-clipboard/components/zero-clipboard';`
+`import ZeroClipboard from 'ember-cli-zero-clipboard/components/zero-clipboard'`

 CopyToClipboard = ZeroClipboard.extend
   tagName: 'button'


Showing the power column

Showing the power column

--- app/coffee/modules/team/main.coffee
+++ app/coffee/modules/team/main.coffee
@@ -65,13 +65,23 @@
     loadMembers: ->
         return @rs.memberships.list(@scope.projectId, {}, false).then (data) =>
             currentUser = @auth.getUser()
+            if not currentUser.photo?
+                currentUser.photo = "/images/unnamed.png"

             @scope.currentUser = _.find data, (membership) =>
                 return membership.user == currentUser.id
+
+            @scope.totals = {}
+            _.forEach data, (membership) =>
+                @scope.totals[membership.user] = 0

             @scope.memberships = _.filter data, (membership) =>
                 if membership.user && membership.user != currentUser.id
                     return membership
+
+            for membership in @scope.memberships
+                if not membership.user.photo?
+                    membership.user.photo = "/images/unnamed.png"

             return data

@@ -84,7 +94,14 @@

     loadMemberStats: ->
         return @rs.projects.memberStats(@scope.projectId).then (stats) =>
-            @scope.stats = @.processStats(stats)
+          totals = {}
+          _.forEach @scope.totals, (total, userId) =>
+              vals = _.map(stats, (memberStats, statsKey) -> memberStats[userId])
+              total = _.reduce(vals, (sum, el) -> sum + el)
+              @scope.totals[userId] = total
+
+          @scope.stats = @.processStats(stats)
+          @scope.stats.totals = @scope.totals

     processStat: (stat) ->
         max = _.max(stat)
@@ -165,7 +182,7 @@

-
+

     """
     return {
@@ -187,7 +204,7 @@

-
+

@@ -218,7 +235,7 @@

-
+


remove unused code

remove unused code

--- app/scripts/controllers/main.coffee
+++ app/scripts/controllers/main.coffee
@@ -1,30 +1,4 @@
 'use strict'
-
-# parse a date in mm/dd/yyyy format
-parseDate = (input)->
-  parts = input.split("/")
-  # new Date(year, month [, date [, hours[, minutes[, seconds[, ms]]]]])
-  new Date(parts[2], parts[0] - 1, parts[1]) # months are 0-based
-
-# Yes if dates are the same day
-daysBetween = (first, second) ->
-  # Copy date parts of the timestamps, discarding the time parts.
-  one = new Date(first.getFullYear(), first.getMonth(), 1)
-  two = new Date(second.getFullYear(), second.getMonth(), 1)
-  # Do the math.
-  millisecondsPerDay = 1000 * 60 * 60 * 24
-  millisBetween = two.getTime() - one.getTime()
-  days = millisBetween / millisecondsPerDay
-  # Round down.
-  Math.floor days
-
-# Yes if dates are the same month
-monthsBetween = (first, second) ->
-    months
-    months = (second.getFullYear() - first.getFullYear()) * 12
-    months -= first.getMonth() + 1
-    months += second.getMonth()
-    if months <= 0 then 0 else months

 angular.module('brokenPromisesApp')
     .controller 'MainCtrl', ($scope, $http, $filter, Restangular) ->


Add remove method

Add remove method

--- app/scripts/lib/bounce/index.coffee
+++ app/scripts/lib/bounce/index.coffee
@@ -35,6 +35,9 @@

     document.body.appendChild @styleElement
     this
+
+  remove: ->
+    @styleElement?.remove()

   getPrefixes: ->
     prefixes =
@@ -99,4 +102,6 @@

       return false unless propertyIsSupported

+    true
+
 module.exports = Bounce


Add yaxis label

Add yaxis label

--- app/scripts/regdashboardctrl.coffee
+++ app/scripts/regdashboardctrl.coffee
@@ -509,6 +509,7 @@
           .attr('dx', '-30')
           .attr('transform', "rotate(-65)"))
         .label((d) -> d.key)
+        .yAxisLabel(gettext("Average Payment"))
         .title((d) ->
           d.key +
             "\n% Functional: " + d.value.percFun.toPrecision(4) +


Update yarn permissions

Update yarn permissions

--- hdp/yarn.coffee
+++ hdp/yarn.coffee
@@ -239,7 +239,7 @@
     destination: '/etc/security/keytabs'
     uid: 'root'
     gid: 'hadoop'
-    mode: '0750'
+    mode: 0o750
   , (err, created) ->
     next null, if created then ctx.OK else ctx.PASS


common js support

common js support

--- hexaflip.coffee
+++ hexaflip.coffee
@@ -32,7 +32,7 @@
 faceSequence = faceNames[...4]
 urlRx        = /(^(?:(?:(?:(?:https?)|(?:file)):)?\/\/)|^(?:data:)|^(?:(?:\.\.?)?\/))(?:.+)/i

-class window.HexaFlip
+class HexaFlip

   constructor: (@el, @sets, @options = {}) ->
     return unless css.transform and @el
@@ -298,7 +298,15 @@
     @flip true

-return unless ($ = window.jQuery or window.$) and $.data
+if module?.exports
+  module.exports = HexaFlip
+else if define?.amd
+  define -> HexaFlip
+else
+  window.HexaFlip = HexaFlip
+
+return unless window? and ($ = window.jQuery or window.$) and $.data
+
 $::hexaFlip = (sets, options) ->
   return @ unless css.transform
   if typeof sets is 'string'


margins based on options

margins based on options

--- hexaflip.coffee
+++ hexaflip.coffee
@@ -60,6 +60,11 @@
           image = new Image
           image.src = val

+    setsKeys = Object.keys @sets
+    setsLength = setsKeys.length
+    @_cubes[setsKeys[0]].el.style.marginLeft = '0'
+    @_cubes[setsKeys[setsKeys.length - 1]].el.style.marginRight = '0'
+
     @el.classList.add @className
     @el.appendChild cubeFragment

@@ -74,6 +79,7 @@
       el: document.createElement 'div'

     cube.el.className = "#{ @className }-cube #{ @className }-cube-#{ set }"
+    cube.el.style.margin = "0 #{ @margin }px"

     for side in @_faceNames
       cube[side] = document.createElement 'div'


fixed touch event issues

fixed touch event issues

--- hexaflip.coffee
+++ hexaflip.coffee
@@ -197,16 +197,20 @@
     e.preventDefault()
     cube.touchStarted = true
     cube.holder.classList.add 'no-tween'
-    if e.type is 'mousedown'
+    if e.type is 'touchstart'
+      cube.start = e.touches[0][@eProp]
+    else
       cube.start = e[@eProp]
-    else
-      cube.start = e.touches[0][@eProp]

   _onTouchMove: (e, cube) ->
     return unless cube.touchStarted
     e.preventDefault()
-    cube.diff = (e[@eProp] - cube.start) * @touchSensitivity
+    if e.type is 'touchmove'
+      cube.diff = (e.touches[0][@eProp] - cube.start) * @touchSensitivity
+    else
+      cube.diff = (e[@eProp] - cube.start) * @touchSensitivity
+
     cube.delta = cube.last - cube.diff
     @_setSides cube


fixes #347

fixes #347

--- chrome/content/zotero-better-bibtex/schomd.coffee
+++ chrome/content/zotero-better-bibtex/schomd.coffee
@@ -93,6 +93,10 @@
   itemIDs = []
   for cluster in citekeys
     clusterIDs = (item for item in @itemIDs(cluster, {libraryID}) when item)
+
+    # the caller can't detect a key has not been resolved unless I refuse to resolve the whole cluster
+    clusterIDs = [] unless clusterIDs.length == cluster.length
+
     itemIDs = itemIDs.concat(clusterIDs)
     clusters.push(({id:itemID} for itemID in clusterIDs))
   cp.updateItems(itemIDs)


reactivity: chatinput: selected item formatting

--- client/activity/lib/flux/chatinput/getters.coffee
+++ client/activity/lib/flux/chatinput/getters.coffee
@@ -12,6 +12,8 @@
 Constants                     = require './constants'
 DropboxType                   = require './dropboxtype'
 isPublicChannel               = require 'app/util/isPublicChannel'
+formatEmojiName               = require 'activity/util/formatEmojiName'
+

 withEmptyMap  = (storeData) -> storeData or immutable.Map()
 withEmptyList = (storeData) -> storeData or immutable.List()
@@ -656,6 +658,30 @@
 ]

+dropboxFormattedSelectedItem = (stateId) -> [
+  dropboxType stateId
+  dropboxQuery stateId
+  dropboxSelectedItem stateId
+  (type, query, selectedItem) ->
+    return  unless selectedItem
+
+    switch type
+      when DropboxType.CHANNEL
+        return "##{selectedItem.get 'name'}"
+      when DropboxType.EMOJI
+        return formatEmojiName selectedItem
+      when DropboxType.MENTION
+        names = selectedItem.get('names')
+        if names
+          name = findNameByQuery(names.toJS(), query) ? names.first()
+        else
+          name = selectedItem.getIn ['profile', 'nickname']
+        return "@#{name}"
+      when DropboxType.COMMAND
+        return "#{selectedItem.get 'name'} #{selectedItem.get 'paramPrefix', ''}"
+]
+
+
 module.exports = {
   filteredEmojiList
   filteredEmojiListQuery
@@ -709,5 +735,6 @@
   dropboxItems
   dropboxSelectedIndex
   dropboxSelectedItem
+  dropboxFormattedSelectedItem
 }


Update

Update

--- src/cli/index.coffee
+++ src/cli/index.coffee
@@ -50,11 +50,11 @@
     help.status()

   install: ->
-    console.log chalk.green 'Installing katon system'
+    console.log chalk.green 'Installing katon'
     common.exec render 'shell/install.sh.eco', config

   uninstall: ->
-    console.log chalk.red 'Uninstalling katon system'
+    console.log chalk.red 'Uninstalling katon'
     common.exec render 'shell/uninstall.sh.eco', config

   # Called only by (un)install.sh scripts using sudo


Add performance counters to main loop.

Add performance counters to main loop.

--- engine/main_loop.coffee
+++ engine/main_loop.coffee
@@ -8,9 +8,18 @@
 class MainLoop

     constructor: (context)->
-        # All milliseconds
+        # All in milliseconds
         @frame_duration = 16
-        @last_frame_durations = [16, 16, 16, 16, 16, 16, 16, 16, 16, 16]
+        # Time from beginning of a tick to the next (including idle time)
+        @last_frame_durations = (new Float32Array(30)).fill(16)
+        # Time it takes for running (logic) JS code
+        @logic_durations = (new Float32Array(30)).fill(16)
+        # Time it takes for physics evaluations
+        @physics_durations = (new Float32Array(30)).fill(16)
+        # Time it takes for evaluating animations and constraints
+        @animation_durations = (new Float32Array(30)).fill(16)
+        # Time it takes for submitting GL commands
+        @render_durations = (new Float32Array(30)).fill(16)
         @_fdi = 0
         @timeout_time = context.MYOU_PARAMS.timeout
         @tasks_per_tick = context.MYOU_PARAMS.tasks_per_tick || 1
@@ -23,6 +32,7 @@
         @_bound_run = @run.bind @
         @_frame_callbacks = []
         @frame_number = 0
+        @update_fps = null # assign a function to be called every 30 frames

     run: ->
         @stopped = false
@@ -93,7 +103,6 @@
         if not @enabled
             return
         @last_frame_durations[@_fdi] = frame_duration
-        @_fdi = (@_fdi+1) % @last_frame_durations.length

         # Limit low speed of logic and physics
         frame_duration = Math.min(frame_duration, MAX_FRAME_DURATION)
@@ -108,20 +117,30 @@

             for p in scene.active_particle_systems
                 p._eval()
+
+        time2 = performance.now()

+        for scene_name in @context.loaded_scenes
             if scene.physics_enabled and (scene.rigid_bodies.length or scene.kinematic_characters.length)
                 get_last_char_phy scene.kinematic_characters
                 step_world scene.world, frame_duration * 0.001
                 phy_to_ob scene.rigid_bodies
+
+        time3 = performance.now()
+
+        evaluate_all_animations @context, frame_duration
+
+        time4 = performance.now()

         for name, video_texture of @context.video_textures
             #TODO: Optimize updating only the video_textures whose video is being played
             video_texture.update_texture?()

-        evaluate_all_animations @context, frame_duration
         # for s in @context.active_sprites
         #     s.evaluate_sprite frame_duration
         @context.render_manager.draw_all()
+
+        time5 = performance.now()

         for scene_name in @context.loaded_scenes
             scene = @context.scenes[scene_name]
@@ -131,4 +150,15 @@
         @context.events.reset_frame_events()
         @frame_number += 1

+        time6 = performance.now()
+
+        @logic_durations[@_fdi] = (time2 - time) + (time6 - time5)
+        @physics_durations[@_fdi] = time3 - time2
+        @animation_durations[@_fdi] = time4 - time3
+        @render_durations[@_fdi] = time5 - time4
+        @_fdi = (@_fdi+1) % @last_frame_durations.length
+        if @_fdi == 0 and @update_fps
+            @update_fps(@)
+
+
 module.exports = {MainLoop}


Flash bar hideable via touch

Flash bar hideable via touch

--- app/assets/javascripts/components/flash_bar.coffee
+++ app/assets/javascripts/components/flash_bar.coffee
@@ -1,4 +1,4 @@
 $(document)
-  .on("click", ".flash-bar-show", ->
+  .on("click touchstart", ".flash-bar-show", ->
     $(this).removeClass("flash-bar-show")
   )


Trigger properly with turbolinks

Trigger properly with turbolinks

--- vendor/assets/javascripts/consolation.js.coffee
+++ vendor/assets/javascripts/consolation.js.coffee
@@ -47,15 +47,10 @@
       @next_url = @element.dataset.nextLogPath
       @poll_for_new_logs()

-
 class Consolation
   constructor: () ->
     consoles = document.querySelectorAll('.consolation-console')
     new ConsolationConsole(c) for c in consoles
-
-document.addEventListener "DOMContentLoaded", ->
-  consolation_controller = new Consolation()
-

 class AsyncRequestDelegate
   constructor: (@options) ->
@@ -71,3 +66,13 @@
   start: ->
     @xmlhttp.send()

+ready = () ->
+  consolation_controller = new Consolation()
+
+document.addEventListener "DOMContentLoaded", ->
+  ready()
+
+document.addEventListener "page:load", ->
+  ready()
+
+


added NOFIX flag in fixer / removed simplification algo

--- app/assets/javascripts/polygonfix_inspector.coffee
+++ app/assets/javascripts/polygonfix_inspector.coffee
@@ -64,9 +64,9 @@

     flag_str = @getFixedPolygon()

+    flag_str = "NOFIX" if !flag_str
+
     # console.log flag_str
-
-    flag_str = "NOFIX" if !flag_str

     @submitSingleFlag(e, flag_str)

@@ -86,7 +86,7 @@
       coordinates.pop()
     transposed = ([coord[1],coord[0]] for coord in coordinates)

-    transposed = Simplify.whyattGeoJSON(transposed, maxCorners) if transposed.length > maxCorners
+    # transposed = Simplify.whyattGeoJSON(transposed, maxCorners) if transposed.length > maxCorners

     if (!@geo)
       # console.table coordinates #, @currentGeo
@@ -128,6 +128,7 @@
       # same coordinate for the first and last point / redundant
       coordinates.pop()
     # if there's no change return false to leave as is
+    # console.log p_array, coordinates
     return false if p_array.join(",") == coordinates.join(",")
     return "[[" + ("[#{p.join(",")}]" for p in p_array) + "]]"


Added JS callbacks for on Save, Enable and Disable

--- app/assets/javascripts/type_station/init.js.coffee
+++ app/assets/javascripts/type_station/init.js.coffee
@@ -1,5 +1,14 @@
 window.TS.models = new window.TS.Store
 window.TS.editors = new window.TS.Store
+
+window.TS.onSave = -> null
+window.TS.onSaved = -> null
+
+window.TS.onEnable = -> null
+window.TS.onEnabled = -> null
+
+window.TS.onDisable = -> null
+window.TS.onDisabled = -> null

 window.TS.getModel = (url) ->
   model = @models.get url
@@ -9,13 +18,19 @@
   model

 window.TS.enable = ->
+  @onEnable()
   @editors.each (id, editor) -> editor.enable()
+  @onEnabled()

 window.TS.disable = ->
+  @onDisable()
   @editors.each (id, editor) -> editor.disable()
+  @onDisabled()

 window.TS.save = ->
+  @onSave()
   @models.each (id, model) -> model.save()
+  @onSaved()

 window.TS.init = ->
   @editors.set 'ts-admin-bar', new window.TS.AdminBar $('#ts-admin-bar-template')
@@ -23,5 +38,5 @@
   @editors.set 'ts-editable-html', new window.TS.EditableHtml $('.ts-editable-html')
   @editors.set 'ts-editable-image', new window.TS.EditableImage $('.ts-editable-image')
   @editors.set 'ts-editable-file', new window.TS.EditableFile $('.ts-editable-file')
-  @editors.set 'ts-new-page', new window.TS.NewPage $('.ts-new-page')  @editors.set 'ts-new-page', new window.TS.NewPage $('.ts-new-page')
+  @editors.set 'ts-new-page', new window.TS.NewPage $('.ts-new-page')
   @editors.set 'ts-link-finder', new window.TS.LinkFinder null


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/collision.coffee
+++ src/collision.coffee
@@ -215,7 +215,7 @@
     @returns {Boolean} true if the line intersects the rectangle, false otherwise
     ###
     rayRectangle: (source, direction, target) ->
-      unless target.xw? and target.y
+      unless target.xw? and target.yw?

       xw = target.xw
       yw = target.yw


Added unique & index for zip-code

Added unique & index for zip-code

--- packages/custom/transparency/coffee/server/models/zipcode.coffee
+++ packages/custom/transparency/coffee/server/models/zipcode.coffee
@@ -10,11 +10,18 @@
     zipCode:
         type: String
         required: true
+        unique: true

     federalState:
         type: String
         required: true

+ZipcodeSchema.index(
+  {
+      zipCode: 1
+      unique: true
+  }
+)

 try
     m = mongoose.model 'Zipcode'


A more stable spec for slideshows

A more stable spec for slideshows

--- apps/about2/test/client/view.coffee
+++ apps/about2/test/client/view.coffee
@@ -33,16 +33,28 @@
     @view.initialize.restore()

   describe 'slideshow', ->
-    beforeEach ->
-      @view.cacheSelectors()
-      @view.heroUnitFrame = 0
+    before ->
+      @$fixture = $("""
+

+
a

+
b

+
c

+

+      """)

     describe '#stepSlide', ->
-      it 'toggles one unit at a time and loops back to the beginning', ->
-        @view.heroUnitFrame.should.equal 0
-        @view.stepSlide @view.$heroUnits, 'heroUnit'
-        @view.heroUnitFrame.should.equal 1
-        @view.stepSlide @view.$heroUnits, 'heroUnit'
-        @view.heroUnitFrame.should.equal 2
-        @view.stepSlide @view.$heroUnits, 'heroUnit'
-        @view.heroUnitFrame.should.equal 0
+      it 'toggles one frame at a time and loops back to the beginning', ->
+        @view.fixtureFrame = 0
+        $frames = @$fixture.find('.fixture')
+        @view.stepSlide $frames, 'fixture'
+        @view.fixtureFrame.should.equal 1
+        @$fixture.find('.is-active').text().should.equal 'a'
+        @view.stepSlide $frames, 'fixture'
+        @view.fixtureFrame.should.equal 2
+        @$fixture.find('.is-active').text().should.equal 'b'
+        @view.stepSlide $frames, 'fixture'
+        @view.fixtureFrame.should.equal 0
+        @$fixture.find('.is-active').text().should.equal 'c'
+        @view.stepSlide $frames, 'fixture'
+        @view.fixtureFrame.should.equal 1
+        @$fixture.find('.is-active').text().should.equal 'a'


simplify bid format

simplify bid format

--- apps/auction/client/bid_form.coffee
+++ apps/auction/client/bid_form.coffee
@@ -5,8 +5,8 @@
 ModalPageView      = require '../../../components/modal/page.coffee'
 BidderPosition     = require '../../../models/bidder_position.coffee'
 ErrorHandlingForm  = require('../../../components/credit_card/client/error_handling_form.coffee')
-
-{ SESSION_ID } = require('sharify').data
+{ unformat }       = require 'accounting'
+{ SESSION_ID }     = require('sharify').data

 module.exports = class BidForm extends ErrorHandlingForm

@@ -32,11 +32,7 @@
   getBidAmount: =>
     val = @$('input.max-bid').val()
     if val
-      # Format the input amount
-      # - '.' is for removing cents
-      # - '_' is also for removing cents
-      # - adding _00 on the end adds cents
-      val.split('.')[0].split('_')[0].replace(',','') + "_00"
+      unformat(val) * 100

   placeBid: =>
     @timesPolledForBidPlacement = 0


fix link tooltip flickering

fix link tooltip flickering

--- demo/scripts/pretty.coffee
+++ demo/scripts/pretty.coffee
@@ -40,7 +40,7 @@
     '.link-tooltip.editing .url-editor':
       'display': 'inline'
   )
-  linkTooltip = $('.link-tooltip').appendTo(editor.doc.root.parentNode)
+  linkTooltip = $('.link-tooltip')
   $(editor.doc.root).on('click', 'a', (event) ->
     showLinkEditor(editor, this)
   )
@@ -71,6 +71,9 @@
   editor.on(Scribe.Editor.events.SELECTION_CHANGE, (selection) ->
     formats = selection.getFormats()
     linkTooltip.hide() unless formats['link']?
+  )
+  _.defer( ->
+    linkTooltip.appendTo(editor.doc.root.parentNode)
   )

 initToolbar = (editor) ->


added params objects

added params objects

--- workers/social/lib/social/models/invitation.test.coffee
+++ workers/social/lib/social/models/invitation.test.coffee
@@ -68,7 +68,6 @@
         userFormData      = generateDummyUserFormData()
         adminUserFormData = generateDummyUserFormData()

-
         queue = [

           ->
@@ -106,7 +105,8 @@

           ->
             # expecting invitation to exist
-            JInvitation.one { email: userFormData.email, groupName: client.context.group }, (err, invitation) ->
+            params = { email: userFormData.email, groupName: client.context.group }
+            JInvitation.one params, (err, invitation) ->
               expect(err).to.not.exist
               expect(invitation).to.exist
               queue.next()
@@ -172,7 +172,8 @@

           ->
             # expecting invitation not to be created
-            JInvitation.one { email: userFormData.email, groupName: group.slug }, (err, invitation) ->
+            params = { email: userFormData.email, groupName: group.slug }
+            JInvitation.one params, (err, invitation) ->
               expect(err).to.not.exist
               expect(invitation).to.not.exist
               queue.next()


Place Electrowalls over other walls.

Place Electrowalls over other walls.

--- app/views/editor/level/thangs/ThangsTabView.coffee
+++ app/views/editor/level/thangs/ThangsTabView.coffee
@@ -16,7 +16,7 @@
 MOVE_SPEED = 13

 # Let us place these on top of other Thangs
-overlappableThangTypeNames = ['Torch', 'Chains', 'Bird', 'Cloud 1', 'Cloud 2', 'Cloud 3', 'Waterfall', 'Obstacle']
+overlappableThangTypeNames = ['Torch', 'Chains', 'Bird', 'Cloud 1', 'Cloud 2', 'Cloud 3', 'Waterfall', 'Obstacle', 'Electrowall']

 class ThangTypeSearchCollection extends CocoCollection
   url: '/db/thang.type?project=original,name,version,slug,kind,components'


Current user

Current user

--- apps/gallery_insights/routes.coffee
+++ apps/gallery_insights/routes.coffee
@@ -1,8 +1,8 @@
 request = require 'superagent'
-{ MAILCHIMP_KEY, CURRENT_USER, GALLERY_INSIGHTS_LIST } = require '../../config'
+{ MAILCHIMP_KEY, GALLERY_INSIGHTS_LIST } = require '../../config'

 @index = (req, res) ->
-  email = res.locals.CURRENT_USER?.email
+  email = req.user?.get('email')
   subscribed email, (cb) ->
     res.locals.sd.MAILCHIMP_SUBSCRIBED = if cb then 'subscribed' else 'unsubscribed'
     res.render 'index'


add comment for setTimeout quirk

add comment for setTimeout quirk

--- desktop/apps/artwork/components/clock/view.coffee
+++ desktop/apps/artwork/components/clock/view.coffee
@@ -8,6 +8,8 @@
     endAt = moment.utc(@timestamp)

     if endAt.isAfter()
+      # setTimeout fires immediately for any value 2^31 or greater, so prevent
+      # that from happening.
       reloadMs = Math.min(endAt.diff(moment.utc()), 0x7FFFFFFF)
       setTimeout ->
         location.reload()


More scrollParent cleanup

More scrollParent cleanup

--- drop.coffee
+++ drop.coffee
@@ -23,20 +23,26 @@
 # https://github.com/jquery/jquery-ui/blob/24756a978a977d7abbef5e5bce403837a01d964f/ui/jquery.ui.core.js#L60
 $.fn.extend
     scrollParent: ->
+        position = @css('position')
+
+        if position is 'fixed'
+            return true
+
         scrollParent = undefined

-        if (isIE and @css('position') in ['static', 'relative']) or @css('position') is 'absolute'
-            scrollParent = @parents().filter(=>
-                $.css(this, 'position') in ['relative', 'absolute', 'fixed'] and /(auto|scroll)/.test(@css('overflow') + @css('overflow-y') + @css('overflow-x'))
+        if position is 'absolute' or (isIE and position in ['static', 'relative'])
+            scrollParent = @parents().filter(->
+                $.css(@, 'position') in ['relative', 'absolute', 'fixed'] and /(auto|scroll)/.test($.css(@, 'overflow') + $.css(@, 'overflow-y') + $.css(@, 'overflow-x'))
             ).first()
-
-        else
-            scrollParent = @parents().filter(=>
-                /(auto|scroll)/.test(@css('overflow') + @css('overflow-y') + @css('overflow-x'))
+        else
+            scrollParent = @parents().filter(->
+                /(auto|scroll)/.test($.css(@, 'overflow') + $.css(@, 'overflow-y') + $.css(@, 'overflow-x'))
             ).first()

-        return @css('position') is 'fixed' or (if scrollParent.length then scrollParent else $('html'))
-
+        if scrollParent.length
+            return scrollParent
+        else
+            return $('html')

 $.fn.removeClassPrefix = (prefix) ->
     $(@).attr 'class', (index, className) ->


:lipstick:

:lipstick:

--- spec/workspace-view-spec.coffee
+++ spec/workspace-view-spec.coffee
@@ -299,38 +299,33 @@
   describe "saving the active item", ->
     describe "saveActivePaneItem", ->
       describe "when there is an error", ->
-        beforeEach ->
-
         it "emits a warning notification when the file cannot be saved", ->
           spyOn(Pane::, 'saveActiveItem').andCallFake ->
             throw new Error("'/some/file' is a directory")
-
-          atom.notifications.onDidAddNotification addedSpy = jasmine.createSpy()
-
-          atom.workspace.saveActivePaneItem()
-
-          expect(addedSpy).toHaveBeenCalled()
-          expect(addedSpy.mostRecentCall.args[0].getType()).toBe 'warning'
-
-        it "emits a warning notification when the directory cannot be written to", ->
-          spyOn(Pane::, 'saveActiveItem').andCallFake ->
-            throw new Error("ENOTDIR, not a directory '/Some/dir/and-a-file.js'")
-
-          atom.notifications.onDidAddNotification addedSpy = jasmine.createSpy()
-
-          atom.workspace.saveActivePaneItem()
-          expect(addedSpy).toHaveBeenCalled()
-          expect(addedSpy.mostRecentCall.args[0].getType()).toBe 'warning'
-
-        it "emits a warning notification when the user does not have permission", ->
-          spyOn(Pane::, 'saveActiveItem').andCallFake ->
-            throw new Error("EACCES, permission denied '/Some/dir/and-a-file.js'")

           atom.notifications.onDidAddNotification addedSpy = jasmine.createSpy()
           atom.workspace.saveActivePaneItem()
           expect(addedSpy).toHaveBeenCalled()
           expect(addedSpy.mostRecentCall.args[0].getType()).toBe 'warning'

+        it "emits a warning notification when the directory cannot be written to", ->
+          spyOn(Pane::, 'saveActiveItem').andCallFake ->
+            throw new Error("ENOTDIR, not a directory '/Some/dir/and-a-file.js'")
+
+          atom.notifications.onDidAddNotification addedSpy = jasmine.createSpy()
+          atom.workspace.saveActivePaneItem()
+          expect(addedSpy).toHaveBeenCalled()
+          expect(addedSpy.mostRecentCall.args[0].getType()).toBe 'warning'
+
+        it "emits a warning notification when the user does not have permission", ->
+          spyOn(Pane::, 'saveActiveItem').andCallFake ->
+            throw new Error("EACCES, permission denied '/Some/dir/and-a-file.js'")
+
+          atom.notifications.onDidAddNotification addedSpy = jasmine.createSpy()
+          atom.workspace.saveActivePaneItem()
+          expect(addedSpy).toHaveBeenCalled()
+          expect(addedSpy.mostRecentCall.args[0].getType()).toBe 'warning'
+
         it "emits a warning notification when the file cannot be saved", ->
           spyOn(Pane::, 'saveActiveItem').andCallFake ->
             throw new Error("no one knows")


Change relative date format for months

Change relative date format for months

--- assets/javascripts/config.js.coffee
+++ assets/javascripts/config.js.coffee
@@ -8,7 +8,7 @@
   hh: "%dh",
   d: "1d",
   dd: "%dd",
-  M: "1 mon",
+  M: "1mo",
   MM: "%d mon",
   y: "a year",
   yy: "%d years"


Fix ember models

Fix ember models

--- assets/javascripts/models.js.coffee
+++ assets/javascripts/models.js.coffee
@@ -1,15 +1,15 @@
+App.User = DS.Model.extend
+  username: DS.attr("string")
+  firstName: DS.attr("string")
+  lastName: DS.attr("string")
+
+App.CurrentUser = App.User.extend({})
+
 App.Channel = DS.Model.extend
   channel: DS.attr("string")
-  networkId: DS.attr("string")

 App.Activity = DS.Model.extend
   body: DS.attr("string")
   type: DS.attr("string")
-
-App.ActivityType = DS.Model.extend
-  name: DS.attr("string")
-
-App.User = DS.Model.extend
-  username: DS.attr("string")
-
-App.CurrentUser = App.User.extend({})
+  channelId: DS.attr("string")
+  userId: DS.attr("string")


fix line_width scaling with pixel_ratio (needs verification on other OS)

--- bokehjs/src/coffee/models/glyphs/webgl/line.coffee
+++ bokehjs/src/coffee/models/glyphs/webgl/line.coffee
@@ -139,7 +139,7 @@

           // Attributes and uniforms to varyings
           v_color = u_color;
-          v_linewidth = u_linewidth * u_pixel_ratio;
+          v_linewidth = u_linewidth;
           v_segment = a_segment * u_scale_length;
           v_length = u_length * u_scale_length;