Refactor sync logic.

Refactor sync logic.

--- src/index.coffee
+++ src/index.coffee
@@ -60,6 +60,53 @@
 # The main application entry point.
 #
 main = () ->
+    class SyncMaster
+        constructor: () ->
+            @data = '\n'
+        update: (source, data) ->
+            @data = data
+            amplify.publish 'data-updated', source, data
+
+    syncMaster = new SyncMaster()
+
+    class SyncView
+        constructor: (syncMaster) ->
+            @syncMaster = syncMaster
+            @data = '\n'
+
+        update: (data) ->
+            @data = data
+            @syncMaster.update(this, data)
+
+    class DocView extends SyncView
+        constructor: (syncMaster, doc) ->
+            super(syncMaster)
+            @doc = doc
+            @doc.setValue(@syncMaster.data)
+
+            @doc.on 'change', () =>
+                @syncMaster.update(@, @doc.getValue())
+
+            amplify.subscribe 'data-updated', (source, data) =>
+                if source isnt @
+                    if data isnt @doc.getValue()
+                        @doc.setValue(data)
+
+
+    class OutlineView extends SyncView
+        constructor: (syncMaster, outline) ->
+            super(syncMaster)
+            @outline = outline
+
+            outline.onDidEndChanges () =>
+                @syncMaster.update(@, @outline.serialize())
+
+            amplify.subscribe 'data-updated', (source, data) =>
+                if source isnt this
+                    if data isnt @outline.serialize()
+                        outline.reloadSerialization(data)
+
+
     panel = new DockPanel()
     panel.id = 'main'

@@ -77,15 +124,12 @@

     # Initialize CodeMirror document
     doc = cmTaskpaper.editor.doc
-    doc.on 'change', () =>
-        amplify.publish 'outline-changed', doc.changeGeneration(), 'doc.change'
+    docView = new DocView(syncMaster, doc)

     # Initialize outline
-    outline = new birch.Outline.createTaskPaperOutline(doc.getValue())
-    outline.generation = doc.changeGeneration()
-    outline.onDidEndChanges () ->
-        outline.generation++
-        amplify.publish 'outline-changed', outline.generation, 'outline.onDidEndChanges'
+    outline = new birch.Outline.createTaskPaperOutline(syncMaster.data)
+    outlineView = new OutlineView(syncMaster, outline)
+

     cmTaskpaper.title.text = 'CodeMirror View'
     panel.insertLeft(cmTaskpaper)
@@ -93,13 +137,6 @@
     panel.attach(document.body)

     window.onresize = () -> panel.update()
-
-    amplify.subscribe 'outline-changed', (generation, source) ->
-        # console.log 'outline-changed', generation, source
-        if not doc.isClean(generation)
-            doc.setValue(outline.serialize())
-        if generation > outline.generation # and expose.doc and expose.outline
-            outline.reloadSerialization(doc.getValue())

     loadDefault = () ->
         cmTaskpaper.loadTarget './todo.taskpaper', () ->


Correct stop the server when exit

Correct stop the server when exit

--- src/index.coffee
+++ src/index.coffee
@@ -34,11 +34,29 @@
     else
       server = http.createServer app
     app.use connect.directory(if typeof opt.root == "object" then opt.root[0] else opt.root)
-    server.listen opt.port, (err) ->
+    server.listen opt.port, (err) =>
       if err
         @log "Error on starting server: #{err}"
       else
         @log "Server started http://#{opt.host}:#{opt.port}"
+
+        stoped = false;
+
+        server.on 'close', =>
+          if (!stoped)
+            stoped = true
+            @log "Server stopped"
+
+        stopServer = =>
+          if (!stoped)
+            server.close()
+            process.nextTick( ->
+              process.exit(0);
+            )
+
+        process.on("SIGINT", stopServer);
+        process.on("exit", stopServer);
+
         if opt.livereload
           tiny_lr.Server::error = ->
           if opt.https?


Fix the bugfix: don't choke on null values.

--- src/index.coffee
+++ src/index.coffee
@@ -45,7 +45,7 @@
     refract = _.partial module.exports, _, context
     update ?= _.partial updateAt, context

-    switch template.constructor
+    switch template?.constructor
         when Object
             _.object _.map template, (value, key) ->
                 updateHere = _.partial update, key


Fix bug in JSONP not called error handler on error

--- src/jsonp.coffee
+++ src/jsonp.coffee
@@ -33,9 +33,12 @@
     callbackName = "callback" + (JsonP.callbackCounter++)
     window.Myna.callbacks[callbackName] =
       (args) ->
-        if !returned
-          returned = true
-          options.success.apply(this, arguments)
+    window.Myna.callbacks[callbackName] = (response) ->
+      unless returned
+        returned = true
+      switch response.typename
+        when "problem" then options.error.call(this, response)
+        else options.success.call(this, response)

     url = options.url + "?"
     url += "#{key}=#{value}&" for key, value of options.data


Remove console.log

Remove console.log

--- www/js/views/checkins.coffee
+++ www/js/views/checkins.coffee
@@ -137,7 +137,6 @@
       @map = L.mapbox.map('map', window.GLOBALS.MAP_ID, {
         zoomControl: false
       }).setView([@position.coords.latitude, @position.coords.longitude], 14)
-      console.log @map

       # Disable drag and zoom handlers
       @map.dragging.disable()
@@ -149,7 +148,6 @@

     _geoSuccess: (position, coords, accuracy) ->
       @position = position
-      console.log position, coords, accuracy

       @showMap()


Fixes #924. Prevent infinite loop with beautify on save

With the option `Ensure Single Trailing Newline` option enabled
in the Whitespace package, Atom would append a newline character after
beautification had occured and when the beautified code was saved it
would be changed and never settle on a stable text that would not change
the previously saved text in the editor. Thus an infinite loop on save.

--- src/beautify.coffee
+++ src/beautify.coffee
@@ -484,8 +484,12 @@

 handleSaveEvent = ->
   atom.workspace.observeTextEditors (editor) ->
-    disposable = editor.onDidSave(({path : filePath}) ->
+    pendingPaths = {}
+    beautifyOnSaveHandler = ({path: filePath}) ->
       logger.verbose('Should beautify on this save?')
+      if pendingPaths[filePath]
+        logger.verbose("Editor with file path #{filePath} already beautified!")
+        return
       buffer = editor.getBuffer()
       path ?= require('path')
       # Get Grammar
@@ -511,13 +515,22 @@
           logger.verbose('Done beautifying file', filePath)
           if editor.isAlive() is true
             logger.verbose('Saving TextEditor...')
+            # Store the filePath to prevent infinite looping
+            # When Whitespace package has option "Ensure Single Trailing Newline" enabled
+            # It will add a newline and keep the file from converging on a beautified form
+            # and saving without emitting onDidSave event, because there were no changes.
+            pendingPaths[filePath] = true
             editor.save()
+            delete pendingPaths[filePath]
             logger.verbose('Saved TextEditor.')
         )
         .catch((error) ->
           return showError(error)
         )
-      )
+    disposable = editor.onDidSave(({path : filePath}) ->
+      # TODO: Implement debouncing
+      beautifyOnSaveHandler({path: filePath})
+    )
     plugin.subscriptions.add disposable

 getUnsupportedOptions = ->


Improve implementation

Improve implementation

- inner workings seems solid
Next up:
- wrap up api in factory/init function
- productionize

--- src/game.coffee
+++ src/game.coffee
@@ -1,27 +1,34 @@
-# TODO
-# - have states return a key for the state instead of the function
-# - or use the transition mapper
-# - log state name instead of function
-# - test with more states
-# - pull out into separate file?
-# - use actual states
+# FRPFSM - Functionally Reactive Programming Finite State Machine

+
+# TODO - wrap this up in a factory function
 STATES =
-START: (data) -> Kefir.later(1000, [STATES.PLAY])
-PLAY: (data) -> Kefir.later(2000, [STATES.START, "You won"])
+  START: (data) -> Kefir.later(1000, ["begin", 0])
+  PLAY: (count) ->
+    Kefir
+      .later(1000)
+      .map -> if count < 3 then ["replay", count + 1] else ["finish"]
+  END: (data) -> Kefir.never()

-count = 0
-nextState = ([next, scope]) ->
-  # temp infinite loop breaker
-  if count++ > 3
-    console.log "finish"
-    return Kefir.never()
-  transitionTo = next scope
-  transitionTo
+STATES.START["begin"] = STATES.PLAY
+STATES.PLAY["replay"] = STATES.PLAY
+STATES.PLAY["finish"] = STATES.END
+
+STATES.START.label = "START"
+STATES.PLAY.label = "PLAY"
+STATES.END.label = "END"
+
+enterState = ([state, initialData]) ->
+  console.log "entering #{state.label} with data #{initialData}"
+  state(initialData)
     .take(1)
-    .flatMap(nextState)
-    .toProperty(-> [next, scope])
+    .map ([transition, exitData]) =>
+      console.log "\"#{transition}\" transition with data #{exitData}"
+      [state[transition], exitData]
+    .flatMap(enterState)
+    .toProperty(-> [state.label, initialData])

-currentState = nextState [STATES.START, "start"]
+currentState = enterState [STATES.START]

-currentState.log("Entering state")
+# subscribe to initiate the stream
+currentState.onAny(->)


pre-release bug scope name was incorrect

--- keymaps/vim-mode-plus.cson
+++ keymaps/vim-mode-plus.cson
@@ -453,7 +453,7 @@
   'q': 'vim-mode-plus:auto-flow'

 # `g / /` to comment-in commented region, `g / /` to comment-out paragraph.
-'atom-text-editor.vim-mode-plus.operator-pending.toggle-line-comments-pending':
+'atom-text-editor.vim-mode-plus.operator-pending-mode.toggle-line-comments-pending':
   '/': 'vim-mode-plus:inner-comment-or-paragraph'

 # operator-pending.has-occurrence


got the if-case to work

got the if-case to work

--- src/expander.coffee
+++ src/expander.coffee
@@ -1,11 +1,43 @@
 _ = require 'underscore'
 estools = require './estools'

+
+noopDef = {
+  kind: 'var'
+  type: 'VariableDeclaration'
+  declarations: [{
+    type: 'VariableDeclarator'
+    id: { type: 'Identifier', name: 'noop' }
+    init:
+      type: 'FunctionExpression'
+      id: null
+      params: []
+      defaults: []
+      body:
+        type: 'BlockStatement'
+        body: [{
+          type: 'ReturnStatement'
+          argument: { type: 'Literal', value: null }
+        }]
+    rest: null
+    generator: false
+    expression: false
+  }]
+}
+
+
+# ToDo:
+# noop not as block-statement
+
 exports.expand = (ast) ->
+
+
+  addNoop = false

   estools.traverse ast, ['BlockStatement', 'Program'], (node) ->
     node.body.forEach (x, i) ->
       if x.type == 'IfStatement' && !x.alternate?
+        addNoop = true
         x.alternate =
           type: 'BlockStatement'
           body: [{
@@ -18,4 +50,12 @@
                 name: 'noop'
           }]

+
+
+
+  if addNoop
+    estools.traverse ast, ['Program'], (node) ->
+      node.body = [noopDef].concat(node.body)
+
+
   ast


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/fadeable.coffee
+++ src/fadeable.coffee
@@ -47,7 +47,7 @@
   </pre></code>

   @name fadeOut
-  @methodOf GameObject#
+  @methodOf Fadeable#
   @param {Number} [duration=30] How long the effect lasts
   @param {Function} [callback=null] The function to execute when the sprite has finished fading.
   ###


oauth: rmeove google updated-min parameter

--- server/lib/server/google_callback.coffee
+++ server/lib/server/google_callback.coffee
@@ -50,7 +50,7 @@

           path  = "/m8/feeds/contacts/default/full?"
           path += "access_token=#{access_token}&"
-          path += "updated-min=2010-01-01T00:00:00" # just a random date to get latest contacts
+          # path += "updated-min=2010-01-01T00:00:00" # just a random date to get latest contacts

           options =
             host   : "www.google.com"


Now passes tests

Now passes tests

--- src/lexer.coffee
+++ src/lexer.coffee
@@ -109,22 +109,27 @@
     m = ///
       (^|\n)
       @@@@
-      (?:
-        \W*\r?\n((?:\r?\n\W+.*)+)
-      |
-        ( .* )
+      (
+        .*\r?(:?\n|$) # Head line of directives
+        (?:
+          (:?
+            \r?\n
+          )* # Any empty lines in-between
+          (?:
+            \s.*\r?(:?\n|$) # Any more indented lines
+          )+
+        )*
       )
       ///g
-    code = code.replace m, (m, start, multiline, singleline)=>
-      # console.log { m, start, multiline, singleline }; return m
-      compiler = @compiler.clone() # Generate a subcompiler
-      if multiline?
-        compiler.eval('@> ' + multiline).call compiler
-        start + multiline.replace /.+/g, ''
-      else
-        singleline = singleline.replace /^\w/, (m)-> '@lexer.' + m
-        compiler.eval('@> ' + singleline).call compiler
-        start + "# Pre-lexer directive: #{ singleline }"
+    # m = /(^|\n)@@@@(.*)/g;
+    code = code.replace m, (m, start, line)=>
+      # @@console.log @@JSON.stringify { m, start, line }
+      # compiler = @compiler.clone() # Generate a subcompiler
+      @ .> compiler
+      # console.log '@>' + line.replace(/^\S/, (m)-> ' @lexer.' + m)
+      compiler.eval('@>' + line.replace(/^\S/, (m)-> ' @lexer.' + m)).call compiler
+      start + line.replace /.+/g, (x)-> '# Processed: ' + x
+    # @@console.log  @@JSON.stringify( { code } )
     code

   # Preprocess the code to remove leading and trailing whitespace, carriage


Try wraps catch in a do block.

Try wraps catch in a do block.

--- src/nodes.coffee
+++ src/nodes.coffee
@@ -1462,7 +1462,7 @@

     catchPart = if @recovery
       o.scope.add @error.value, 'param' unless o.scope.check @error.value
-      " #{errorPart}\n#{ @recovery.compile o, LEVEL_TOP }\n#{@tab}"
+      " #{errorPart}\n(do #{ @recovery.compile o, LEVEL_TOP })\n#{@tab}"
     else unless @ensure or @recovery
       ' (arglist e) (do)' # empty body


#1534 class then "use strict"

#1534 class then "use strict"

--- src/nodes.coffee
+++ src/nodes.coffee
@@ -915,6 +915,12 @@
           if node instanceof Value and node.isObject(true)
             exps[i] = @addProperties node, name, o
         child.expressions = exps = flatten exps
+
+  checkStrict: ->
+    if (node = @body.expressions[0]) instanceof Value
+      if /^['"]use strict['"]$/.test node.unwrapAll().value
+        @strict = yes
+        @body.expressions.shift()

   # Make sure that a constructor is defined for the class, and properly
   # configured.
@@ -938,6 +944,7 @@
     name = "_#{name}" if name.reserved
     lname = new Literal name

+    @checkStrict()
     @setContext name
     @walkBody name, o
     @ensureConstructor name
@@ -946,6 +953,7 @@
     if decl
       @body.expressions.unshift new Assign (new Value (new Literal name), [new Access new Literal 'name']), (new Literal "'#{name}'")
     @body.expressions.push lname
+    @body.expressions.unshift(new Value(new Literal "'use strict'")) if @strict
     @addBoundFunctions o

     call  = Closure.wrap @body


annotation tweaks

annotation tweaks

--- oriDomi.coffee
+++ oriDomi.coffee
@@ -1,5 +1,5 @@
 # [oriDomi](http://oridomi.com)
-# =======
+# =============================
 # #### by [Dan Motzenbecker](http://oxism.com)
 # Fold up the DOM like paper.

@@ -8,7 +8,7 @@
 # Copyright 2012, MIT License

 # Setup
-# =========
+# =====

 # Enable strict mode in this scope to keep us in line.
 'use strict'


added bleed to panel dimensions

added bleed to panel dimensions

--- oriDomi.coffee
+++ oriDomi.coffee
@@ -205,10 +205,11 @@
       hMask.appendChild topShader
       hMask.appendChild bottomShader

+    bleed = 2
     hPanel = document.createElement 'div'
     hPanel.className = 'oridomi-panel-h'
     hPanel.style.width = '100%'
-    hPanel.style.height = @panelHeight + 'px'
+    hPanel.style.height = @panelHeight + bleed + 'px'
     hPanel.style.padding = '0'
     hPanel.style.position = 'relative'
     hPanel.style[css.transitionProp] = css.transformProp


added ramp method

added ramp method

--- oriDomi.coffee
+++ oriDomi.coffee
@@ -552,6 +552,36 @@
     @_callback options

+  _rampDefaults:
+    anchor: 'left'
+
+
+  ramp: (angle, options) ->
+    options = extendObj options, @_rampDefaults
+    {anchor} = options
+
+    if anchor isnt @lastAnchor
+      return @reset =>
+        @_showStage anchor
+        setTimeout =>
+          @ramp angle, options
+        , 0
+
+    @lastAngle = angle = @_normalizeAngle angle
+
+    if anchor is 'right' or anchor is 'bottom'
+      angle = -angle
+
+    if anchor is 'left' or anchor is 'right'
+      rotation = [0, 1, 0, angle]
+    else
+      rotation = [1, 0, 0, -angle]
+
+    @panels[anchor][1].style[css.transform] = @_transform @_getXy(1, anchor), rotation
+
+    @_callback options
+
+
   setAngles: (angles, options = {}) ->
     if !Array.isArray angles
       return !silent and console?.warn 'oriDomi: Argument must be an array of angles'


syntax tweaks

syntax tweaks

--- oridomi.coffee
+++ oridomi.coffee
@@ -318,8 +318,7 @@
     # The panel element holds both its respective mask and all subsequent sibling panels.
     hPanel = document.createElement 'div'
     hPanel.className = 'oridomi-panel-h'
-    hPanel.style.width = '100%'
-    hPanel.style.height = @panelHeight + bleed + 'px'
+    hPanel.style.width = hPanel.style.height = '100%'
     hPanel.style.padding = '0'
     hPanel.style.position = 'relative'
     # The panel element is the target of the transforms.
@@ -332,8 +331,7 @@

     # Apply a transparent border to force edge smoothing on Firefox.
     # (This setting hurts performance significantly.)
-    if @settings.forceAntialiasing
-      hPanel.style.outline = '1px solid transparent'
+    hPanel.style.outline = '1px solid transparent' if @settings.forceAntialiasing

     # Add the horizontal mask prototype to the horizontal panel prototype.
     hPanel.appendChild hMask


underscore prefix @settings

underscore prefix @settings

--- oridomi.coffee
+++ oridomi.coffee
@@ -390,7 +390,7 @@
       console.warn 'oriDomi: First argument must be a DOM element' if devMode
       return

-    @settings = new ->
+    @_settings = new ->
       for k, v of defaults
         if options[k]?
           @[k] = options[k]
@@ -403,17 +403,17 @@
     @stages = {}
     @lastOp = anchor: anchorList[0]
     @_xLast = @_yLast = 0
-    {@shading, @vPanels, @hPanels} = @settings
+    {@shading, @vPanels, @hPanels} = @_settings

     if @shading
       @_shaders    = {}
       shaderProtos = {}
       shaderProto  = createEl 'shader'
-      shaderProto.style[css.transitionDuration]       = @settings.speed + 'ms'
-      shaderProto.style[css.transitionTimingFunction] = @settings.easingMethod
+      shaderProto.style[css.transitionDuration]       = @_settings.speed + 'ms'
+      shaderProto.style[css.transitionTimingFunction] = @_settings.easingMethod

     stageProto = createEl 'stage'
-    stageProto.style[css.perspective] = @settings.perspective + 'px'
+    stageProto.style[css.perspective] = @_settings.perspective + 'px'

     for anchor in anchorList
       @panels[anchor] = []
@@ -433,8 +433,8 @@
     maskProto.appendChild contentHolder

     panelProto = createEl 'panel'
-    panelProto.style[css.transitionDuration]       = @settings.speed + 'ms'
-    panelProto.style[css.transitionTimingFunction] = @settings.easingMethod
+    panelProto.style[css.transitionDuration]       = @_settings.speed + 'ms'
+    panelProto.style[css.transitionTimingFunction] = @_settings.easingMethod

     for axis in ['x', 'y']
       if axis is 'x'
@@ -497,7 +497,7 @@
     @el.appendChild @stageHolder
     @$el = $ @el if $
     @accordion 0
-    @enableTouch() if @settings.touchEnabled
+    @enableTouch() if @_settings.touchEnabled

   # Internal Methods
@@ -586,7 +586,7 @@
   # keeping it within the maximum range specified in the instance settings.
   _normalizeAngle: (angle) ->
     angle = parseFloat angle, 10
-    max   = @settings.maxAngle
+    max   = @_settings.maxAngle
     if isNaN angle
       0
     else if angle > max
@@ -616,7 +616,7 @@
   _setShader: (n, anchor, angle) ->
     # Store the angle's absolute value and generate an opacity based on `shadingIntensity`.
     abs     = Math.abs angle
-    opacity = abs / 90 * @settings.shadingIntensity
+    opacity = abs / 90 * @_settings.shadingIntensity

     # With hard shading, opacity is reduced and `angle` is based on the global
     # `lastAngle` so all panels' shaders share the same direction. Soft shaders
@@ -770,7 +770,7 @@
       @[axis1] = e.targetTouches[0]["page#{ @_touchAxis.toUpperCase() }"]

     # Return that value to an external listener.
-    @settings.touchStartCallback @[axis1]
+    @_settings.touchStartCallback @[axis1]

   # Called on touch/mouse movement.
@@ -784,7 +784,7 @@
       current = e.targetTouches[0]["page#{ @_touchAxis.toUpperCase() }"]

     # Calculate distance and multiply by `touchSensitivity`.
-    distance = (current - @["_#{ @_touchAxis }1"]) * @settings.touchSensitivity
+    distance = (current - @["_#{ @_touchAxis }1"]) * @_settings.touchSensitivity

     # Calculate final delta based on starting angle, anchor, and what side of zero
     # the last operation was on.
@@ -819,7 +819,7 @@
     # Enable tweening again.
     @_setTweening @settings.speed
     # Pass callback final value.
-    @settings.touchEndCallback @["_#{ @_touchAxis }Last"]
+    @_settings.touchEndCallback @["_#{ @_touchAxis }Last"]

   # End folding when the mouse or finger leaves the composition.
@@ -907,7 +907,7 @@

   # Setter method for `maxAngle`.
   constrainAngle: (angle) ->
-    @settings.maxAngle = parseFloat(angle, 10) or defaults.maxAngle
+    @_settings.maxAngle = parseFloat(angle, 10) or defaults.maxAngle
     @

@@ -986,9 +986,9 @@
       len       = @panels[anchor].length

       for panel, i in @panels[anchor]
-        delay = @settings.speed / len * (len - i - 1)
+        delay = @_settings.speed / len * (len - i - 1)
         panel.style[css.transitionDelay] = delay + 'ms'
-        panel.style[css.transitionDuration] = @settings.speed / 2 + 'ms' if i is 0
+        panel.style[css.transitionDuration] = @_settings.speed / 2 + 'ms' if i is 0

         do (panel, i, delay) =>
           defer =>
@@ -1000,7 +1000,7 @@
               else
                 hideEl panel.children[0]

-            , delay + @settings.speed * .25
+            , delay + @_settings.speed * .25

   # The inverse of `foldUp`.
@@ -1010,7 +1010,7 @@
     len       = @panels[anchor].length

     for panel, i in @panels[anchor]
-      delay = @settings.speed / len * i
+      delay = @_settings.speed / len * i
       panel.style[css.transitionDelay] = delay + 'ms'

       do (panel, i, delay) =>
@@ -1022,8 +1022,8 @@
               @_inTrans = @isFoldedUp = false
               callback?()
               @lastOp.fn = @accordion
-            defer => panel.style[css.transitionDuration] = @settings.speed
-          , delay + @settings.speed * .25
+            defer => panel.style[css.transitionDuration] = @_settings.speed
+          , delay + @_settings.speed * .25

   # Convenience Methods


esteWatch was moved into own grunt plugin 'grunt-este-watch'

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -259,6 +259,7 @@
   grunt.loadNpmTasks 'grunt-contrib-stylus'
   grunt.loadNpmTasks 'grunt-env'
   grunt.loadNpmTasks 'grunt-este'
+  grunt.loadNpmTasks 'grunt-este-watch'
   grunt.loadNpmTasks 'grunt-release'

   grunt.registerTask 'build', 'Build app.', (app = 'app') ->


let require.resolve be require.resolve

let require.resolve be require.resolve

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -11,28 +11,31 @@
     '!node_modules/es6-promise/dist/promise-*.min.js'
   ]

-freedom = require './node_modules/freedom/Gruntfile'
-freedomForChrome = require './node_modules/freedom-for-chrome/Gruntfile'
-freedomForFirefox = require './node_modules/freedom-for-firefox/Gruntfile'
+freedom = require 'freedom/Gruntfile'
+freedomForChrome = require 'freedom-for-chrome/Gruntfile'
+freedomForFirefox = require 'freedom-for-firefox/Gruntfile'

 # Files comprising "core Freedom".
-# TODO: Figure out why these get the full paths while freedom-for-chrome
-#       and freedom-for-firefox files don't.
 freedomSrc = [].concat(
   freedom.FILES.srcCore
   freedom.FILES.srcPlatform
 )

+# Like Unix's dirname, e.g. 'a/b/c.js' -> 'a/b'
+dirname = (path) -> path.substr(0, path.lastIndexOf('/'))
+
 # Chrome app-specific Freedom providers.
+# TODO: Figure out why this doesn't contain the full path.
 freedomForChromeSrc = [].concat(
   freedomForChrome.FILES.platform
-).map (fileName) -> path.join('node_modules/freedom-for-chrome', fileName)
+).map (fileName) -> path.join(dirname(require.resolve('freedom-for-chrome/Gruntfile')), fileName)

 # Firefox addon-specific files and Freedom providers.
+# TODO: Figure out why this doesn't contain the full path.
 freedomForFirefoxSrc = [].concat(
   'src/backgroundframe-link.js'
   'providers/*.js'
-).map (fileName) -> path.join('node_modules/freedom-for-firefox', fileName)
+).map (fileName) -> path.join(dirname(require.resolve('freedom-for-firefox/Gruntfile')), fileName)

 # Our custom core providers, plus dependencies.
 # These files are included with our custom builds of Freedom.


updated gruntfile

updated gruntfile

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -40,7 +40,7 @@
     exec:
       tagRelease: 'git tag v<%= pkg.version %>'
       publish: 'git push origin master && git push --tags && npm publish'
-      commitRelease: 'git commit bower.json date-diff.min.js date-diff-light.min.js package.json -m "Release v<%= pkg.version %>"'
+      commitRelease: 'git commit bower.json date-diff.min.js package.json -m "Release v<%= pkg.version %>"'

     bump:
       options:


Don't auto-submit search if active element is no longer the input element.

Prevents search from auto-submitting and refreshing the entire page if
focus has changed to a different element.

--- resources/assets/coffee/_classes/search.coffee
+++ resources/assets/coffee/_classes/search.coffee
@@ -41,6 +41,8 @@

   submit: (e) =>
     input = e.currentTarget
+    return if input != document.activeElement
+
     value = input.value.trim()

     return if value in ['', input.dataset.searchCurrent?.trim()]


modify Gruntfile

modify Gruntfile

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -135,7 +135,6 @@
   grunt.registerTask 'doc', 'Make jsdoc', ()->
     done = this.async
     sh = require('child_process').exec
-    ;

     sh 'java -jar jsdoc-toolkit/jsrun.jar jsdoc-toolkit/app/run.js ja/enchant.js -t=doc/template -d=doc/core/ja'
     sh 'java -jar jsdoc-toolkit/jsrun.jar jsdoc-toolkit/app/run.js enchant.js -t=doc/template -d=doc/core/en'
@@ -146,18 +145,25 @@

   grunt.registerTask 'build', '', ()->
     fs = require('fs')
-    grunt.task.run('lang:en')
-    grunt.file.delete('./build', {force: true})
+    grunt.task.run [ 'lang:en', 'cp:lang/en/:build/' ]

-    grunt.file.recurse('./lang/en', (abspath, rootdir, subdir, filename)->
+  grunt.registerTask 'cp', '', ()->
+    from = @args[0]
+    to = @args[1]
+    console.log(from, to)
+
+    if grunt.file.exists to
+      grunt.file.delete(to, { force: true })
+
+    grunt.file.recurse(from, (abspath, rootdir, subdir, filename)->
       path = (subdir || '') + '/' + filename
-      grunt.file.copy('./lang/en/' + path, 'build/' + path)
+      grunt.file.copy(from + path, to + path)
     )

   grunt.registerTask 'lang', 'Make lang files.', ()->
     lang = @args[0] || 'en'
-    path = @args[1] || 'lang/' + lang;
+    path = @args[1] || 'lang/' + lang
     grunt.log.writeln 'processing..'
     done = @async
     fs = require('fs')


remove ie 10 for now

remove ie 10 for now

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -125,10 +125,10 @@
             browserName: 'safari'
             platform: 'OS X 10.9'
             version: '7'
-          ,
-            browserName: 'internet explorer'
-            platform: 'Windows 8.1',
-            version: '11'
+          #,
+          #  browserName: 'internet explorer'
+          #  platform: 'Windows 8.1',
+          #  version: '11'
           ,
             browserName: 'internet explorer'
             version: '10'


update Gruntfile for other releases

update Gruntfile for other releases

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -91,6 +91,8 @@
   grunt.registerTask 'release:commit', [ 'git:add:package.json', 'git:commit:release', 'git:tag:release' ]

   grunt.registerTask 'release:patch', [ 'release:prep', 'release:bump:patch', 'release:commit' ]
+  grunt.registerTask 'release:minor', [ 'release:prep', 'release:bump:minor', 'release:commit' ]
+  grunt.registerTask 'release:major', [ 'release:prep', 'release:bump:major', 'release:commit' ]

   grunt.registerTask 'release:push', ['git:push', 'git:pushTags' ]
   grunt.registerTask 'release:publish', ['release:push', 'npm:publish' ]


Clean up linter output

Clean up linter output

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -76,7 +76,7 @@
         options:
           max_line_length:
             value: 80
-            level: 'warn'
+            level: 'ignore'
           no_trailing_semicolons:
             level: 'warn'
       components:
@@ -85,7 +85,7 @@
         options:
           max_line_length:
             value: 80
-            level: 'warn'
+            level: 'ignore'

   # Grunt plugins used for building
   @loadNpmTasks 'grunt-contrib-coffee'


Enable source maps for CoffeeScript using the --maps=true option

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -43,6 +43,7 @@
   spec = (grunt.option('spec') || grunt.option('jsspec') || '*').replace(/(Spec|Test)$/, '')
   debug = grunt.option 'verbose' || false
   version = grunt.option 'version' || 'dev'
+  maps = grunt.option 'maps' || false

   appsdk_path = 'lib/sdk'
   served_paths = [path.resolve(__dirname)]
@@ -232,6 +233,8 @@
         src: ['**/*.coffee']
         dest: 'test/gen'
         ext: '.js'
+        options:
+          sourceMap: maps

     watch:
       test:


Fix: Define correctly the assets path

Fix: Define correctly the assets path

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -69,7 +69,7 @@
     # Directories:
     rootDir:      __dirname
     srcDir:       './src'
-    assetsDir:    '<%= config.frontendAppPublicDir %>/assets'
+    assetsDir:    config.frontendAppPublicDir + '/assets'
     docsDir:      'docs'

     # Dev. server settings:


reupgrade gruntfile

reupgrade gruntfile

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -6,9 +6,9 @@
   ]

   coffeeScripts = [
+    'bower_components/este-library/**/*.coffee'
     '!bower_components/este-library/Gruntfile.coffee'
     '!bower_components/este-library/node_modules/**/*.coffee'
-    'bower_components/este-library/**/*.coffee'
     'client/app/js/**/*.coffee'
     'server/**/*.coffee'
   ]
@@ -39,9 +39,9 @@
         options:
           force: true
         src: [
-          '!bower_components/este-library/node_modules/**/*.js'
           'bower_components/este-library/**/*.css'
           'bower_components/este-library/**/*.js'
+          '!bower_components/este-library/node_modules/**/*.js'
           'client/**/css/**/*.css'
           'client/**/js/**/*.js'
           'server/**/*.js'


Boyscout - remove extra enter

Boyscout - remove extra enter

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -31,7 +31,6 @@
         src: [
           'build/js/libs/jquery-1.7.2.js'
           'build/js/core.js'
-
           'build/js/wrap/first.js'
           'build/js/plugins/*.js'
           'build/js/classes/*.js'


fix release task

fix release task

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -125,7 +125,7 @@
       options:
         file: 'bower.json'
         npm: false
-        afterRelease: ['s3']
+        afterReleaseTasks: ['s3:version']

     s3:
       options:
@@ -171,5 +171,3 @@
   grunt.registerTask 'dist', ['cssmin:dist', 'uglify:dist']
   grunt.registerTask 'all', ['default', 'dist']
   grunt.registerTask 'test', ['karma:main']
-  grunt.renameTask 'release', 'oldRelease'
-  grunt.registerTask 'release', ['oldRelease', 's3']


Optional list.json for decks, for @nhoizey ;)

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -185,17 +185,20 @@
                     deckTemplate = grunt.file.read 'templates/_deck.html'
                     sectionTemplate = grunt.file.read 'templates/_section.html'

-                    options = options || {}
-                    options.cwd = 'slides/' + chapter + '/'
-                    options.ignore = '*.json'
-                    slides = glob.sync '[^_]*', options
-                    slides = slides.map (f)->
-                        elementPath = path.join options.cwd, f
-                        fileStats = fs.lstatSync elementPath
-                        if fileStats.isDirectory()
-                            insideFiles = glob.sync f + '/[^_]*.{md,html}', options
-                            return insideFiles
-                        return f
+                    if grunt.file.exists 'slides/' + chapter + '/list.json'
+                        slides = grunt.file.readJSON 'slides/' + chapter + '/list.json'
+                    else
+                        options = options || {}
+                        options.cwd = 'slides/' + chapter + '/'
+                        options.ignore = '*.json'
+                        slides = glob.sync '[^_]*', options
+                        slides = slides.map (f)->
+                            elementPath = path.join options.cwd, f
+                            fileStats = fs.lstatSync elementPath
+                            if fileStats.isDirectory()
+                                insideFiles = glob.sync f + '/[^_]*.{md,html}', options
+                                return insideFiles
+                            return f

                     html = grunt.template.process deckTemplate, data:
                         slides:


Tweak Gruntfile.

Tweak Gruntfile.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -59,7 +59,7 @@
 				paths: ['.']
 				urlfunc: 'embedurl'
 				use: [
-					() -> (require 'autoprefixer-stylus')('last 2 versions', 'ie 8', 'ie 9')
+					-> (require 'autoprefixer-stylus')('last 2 versions', 'ie 8', 'ie 9')
 				]
 				define:
 					import_tree: (require 'stylus-import-tree')


Add stub config for grunt-release

Add stub config for grunt-release

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -14,6 +14,7 @@
     testFiles = grunt.expandFileArg('lib/')

     grunt.Config =
+        release: {}
         mochaTest:
             server:
                 options:


prevent concurrent calls to zooming functions

--- app/lib/canvas-graph.coffee
+++ app/lib/canvas-graph.coffee
@@ -322,19 +322,21 @@
       @ctx.stroke()

   zoomInTo: (wMin, wMax) ->
-
+    classifier.el.find('#graph').addClass('is-zooming')
     [cMin, cMax] = [@xMin, @xMax]

     zoom = setInterval (=>
       @plotPoints(cMin,cMax)
       cMin += 1.5 unless cMin >= wMin
       cMax -= 1.5 unless cMax <= wMax
-      if cMin >= wMin and cMax <= wMax
+      if cMin >= wMin and cMax <= wMax # when 'animation' is done...
         clearInterval zoom
+        classifier.el.find('#graph').removeClass('is-zooming')
         @plotPoints(wMin,wMax)
     ), 30

   zoomOut: (callback) ->
+    classifier.el.find('#graph').addClass('is-zooming')
     [cMin, cMax] = [@xMin, @xMax]
     [wMin, wMax] = [@smallestX, @largestX]
     zoom = setInterval (=>
@@ -343,6 +345,7 @@
       cMax += 1.5 unless cMax >= wMax
       if cMin <= wMin and cMax >= wMax  # finished zooming
         clearInterval zoom
+        classifier.el.find('#graph').removeClass('is-zooming')
         @plotPoints(wMin, wMax)
         unless callback is undefined
           callback.apply()
@@ -368,6 +371,12 @@

   onMouseMove: (e) =>
     console.log 'onMouseMove()'
+
+    e.preventDefault()
+    @draw(e) if @dragging
+    @move(e) if @moving
+
+    return if classifier.el.find('#graph').hasClass('is-zooming')

     zoomLevel = classifier.zoomLevel
     zoomRanges = classifier.zoomRanges
@@ -381,9 +390,8 @@

     @plotPoints(classifier.prevZoomMin, classifier.prevZoomMax)

-
     if xClick < 80
-      @plotPoints(classifier.prevZoomMin, classifier.prevZoomMax)
+      # @plotPoints(classifier.prevZoomMin, classifier.prevZoomMax)
       @ctx.font = '10pt Arial'
       @ctx.textAlign = 'left'
       @ctx.fillStyle = '#FC4542'
@@ -557,11 +565,6 @@
     @element.children[1].children[0].style.visibility = "hidden"
     @element.children[2].children[0].style.visibility = "hidden"

-  onMouseMove: (e) =>
-    e.preventDefault()
-    @draw(e) if @dragging
-    @move(e) if @moving
-
   onMouseDown: (e) =>
     e.preventDefault()
     window.addEventListener 'mousemove', @onMouseMove


removed BaseDomain class

removed BaseDomain class

--- src/patch.coffee
+++ src/patch.coffee
@@ -2,10 +2,6 @@
 {Transform} = require "readable-stream"

 {isFunction} = require "core-util-is"
-
-class BaseDomain
-  constructor: ->
-    return domain.create()

 isTransform = ->
   return true
@@ -17,8 +13,9 @@
   if userDomain instanceof domain.Domain
     dom = userDomain
   else
-    dom = new BaseDomain()
+    dom = domain.create()
     dom.on "error", (e) -> console.log "EE:patch domain", e
+

   if isTransform stream
     for i, fn of stream


dust goes in and out at the same time

dust goes in and out at the same time

--- layers/midground/dust/main.coffee
+++ layers/midground/dust/main.coffee
@@ -1,17 +1,22 @@
-module.exports = class Dust extends Lysertron.Layer
+module.exports = class Dust extends Lysertron.LayerStack
+  initialize: ->
+    @push new SingleDust()
+    @push new SingleDust direction: -1
+
+class SingleDust extends Lysertron.Layer
   uniformAttrs:
     baseColor:     'c'
     size:          'f'
     particleAlpha: 'f'

-  initialize: ->
-    @direction = 1
+  initialize: (options = {}) ->
+    @direction = options.direction || 1

     @size = THREE.Math.randFloat(3, 8)
     @position.z = Math.random()
     @scale.setLength 0

-    @particleAlpha = THREE.Math.randFloat(0.4, 0.75)
+    @particleAlpha = THREE.Math.randFloat(0.3, 0.7)

     @baseColor = new THREE.Color().setHSV(
       THREE.Math.randFloat(0, 1)
@@ -26,7 +31,9 @@
     )

     @geom = new THREE.Geometry
-    for i in [0..THREE.Math.randFloat(250, 1000)]
+    qty = THREE.Math.randInt(150, 600)
+    console.log qty
+    for i in [0..qty]
       @geom.vertices.push new THREE.Vector3(
         THREE.Math.randFloat(-1, 1)
         THREE.Math.randFloat(-1, 1)


comments

comments

--- client/src/index.coffee
+++ client/src/index.coffee
@@ -3,7 +3,9 @@

 SideBar = require './sidebar.coffee'
 MyMap = require './mymap.coffee'
-# API CONFIGURATION -> backended -> homedata: @props.homedata
+# API CONFIGURATION -> backended ->
+#homedata: @props.homedata
+#peacemarker: @props.peacemarker
 backend = require './backend.coffee'

@@ -14,6 +16,7 @@
         div null,
             SideBar
                 homedata: @props.homedata
+                # peacemarker: @props.peacemarker
             MyMap


ace: underscore :arrow_right: lodash

ace: underscore :arrow_right: lodash

--- client/ace/lib/ace.coffee
+++ client/ace/lib/ace.coffee
@@ -1,5 +1,5 @@
 $                    = require 'jquery'
-_                    = require 'underscore'
+_                    = require 'lodash'
 getscript            = require 'getscript'
 kd                   = require 'kd'
 KDButtonView         = kd.ButtonView


Add events for all kinds of stuff

Add events for all kinds of stuff

--- coffee/core/core.coffee
+++ coffee/core/core.coffee
@@ -57,15 +57,20 @@
     newPos = @clientCoordsToDrawingCoords(x, y)
     @tool.begin newPos.x, newPos.y, this
     @isDragging = true
+    @trigger("drawingStart", {tool: @tool})

   continue: (x, y) ->
     newPos = @clientCoordsToDrawingCoords(x, y)
-    @tool.continue newPos.x, newPos.y, this if @isDragging
+    if @isDragging
+      @tool.continue newPos.x, newPos.y, this
+      @trigger("drawingContinue", {tool: @tool})

   end: (x, y) ->
     newPos = @clientCoordsToDrawingCoords(x, y)
-    @tool.end newPos.x, newPos.y, this if @isDragging
-    @isDragging = false
+    if @isDragging
+      @tool.end newPos.x, newPos.y, this
+      @isDragging = false
+      @trigger("drawingEnd", {tool: @tool})

   setColor: (name, color) ->
     @colors[name] = color
@@ -77,11 +82,13 @@

   saveShape: (shape) ->
     @execute(new LC.AddShapeAction(this, shape))
+    @trigger('saveShape', {shape: shape})

   pan: (x, y) ->
     # Subtract because we are moving the viewport
     @position.x = @position.x - x
     @position.y = @position.y - y
+    @trigger('pan', {x: @position.x, y: @position.y})

   zoom: (factor) ->
     oldScale = @scale
@@ -96,6 +103,7 @@
       @position.y, @canvas.height, oldScale, @scale)

     @repaint()
+    @trigger('zoom', {oldScale: oldScale, newScale: @scale})

   # Repaints the canvas.
   # If dirty is true then all saved shapes are completely redrawn,
@@ -116,6 +124,7 @@
     @ctx.clearRect(0, 0, @canvas.width, @canvas.height)
     if @canvas.width > 0 and @canvas.height > 0
       @ctx.drawImage @buffer, 0, 0
+    @trigger('repaint', null)

   # Redraws the back buffer to the screen in its current state
   # then draws the given shape translated and scaled on top of that.
@@ -149,6 +158,7 @@
     @execute(new LC.ClearAction(this))
     @shapes = []
     @repaint()
+    @trigger('clear', null)

   execute: (action) ->
     @undoStack.push(action)
@@ -160,12 +170,14 @@
     action = @undoStack.pop()
     action.undo()
     @redoStack.push(action)
+    @trigger('undo', {action})

   redo: ->
     return unless @redoStack.length
     action = @redoStack.pop()
     @undoStack.push(action)
     action.do()
+    @trigger('redo', {action})

   getPixel: (x, y) ->
     p = @drawingCoordsToClientCoords x, y


fix #132 and change test to include case

--- src/quill.coffee
+++ src/quill.coffee
@@ -197,6 +197,7 @@
     @editor.selection.setRange(range, source)

   updateContents: (delta, source = Quill.sources.API) ->
+    delta = Tandem.Delta.makeDelta(delta)
     @editor.applyDelta(delta, source)

   # fn(Number start, Number end, String name, String value, String source)


Temporary debugging stuff.

Temporary debugging stuff.

Isn't testing in production great?

--- src/quote.coffee
+++ src/quote.coffee
@@ -108,13 +108,16 @@
     for line in q.split(/\n/)
       m = line.match /(\S+) \[(\d?\d):(\d\d) ([AP]M)\]/
       if m?
+        msg.reply "match! - <#{line}> => <#{m.join ', '}>"
         [x, speaker, hours, minutes, ampm] = m
         hours += 12 if ampm is 'PM'
         timestamp = moment {hours: hours, minutes: minutes}
         ts = timestamp.format('[h:mm A d MMM YYYY]')
       else if speaker?
-        processed.push "#{ts} #{speaker}: #{line}"
+        msg.reply "regular line, with speaker"
+        processed.push "#{ts} #{speaker}: <#{line}>"
       else
+        msg.reply "regular line, no speaker <#{line}>"
         processed.push line
     processed.push ''


whitespace

whitespace

--- src/range.coffee
+++ src/range.coffee
@@ -104,7 +104,6 @@
   range.setEnd(end.container, end.offset)

   return range
-

 serialize = (range, root, ignoreSelector) ->


Prepare to open and close highlight.

Prepare to open and close highlight.

--- client/publication.coffee
+++ client/publication.coffee
@@ -31,6 +31,9 @@

   dims

+normalizeStartEnd = (start, end) ->
+  [Math.min(start, end), Math.max(start, end)]
+
 hideHiglight = ($textLayer) ->
   $textLayer.children().css
     'background-color': 'rgba(0,0,0,0)'
@@ -40,7 +43,7 @@

   return if start is -1 or end is -1

-  [start, end] = [Math.min(start, end), Math.max(start, end)]
+  [start, end] = normalizeStartEnd start, end

   $textLayer.children().slice(start, end + 1).css
     'background-color': 'rgba(255,0,0,0.3)'
@@ -73,21 +76,34 @@

   closestElementIndex

+openHihglight = (publication, page, $textLayer, start, end) ->
+  return if start is -1 or end is -1
+
+  [start, end] = normalizeStartEnd start, end
+
+  # TODO: Implement
+
+closeHighlight = (publication, page, $textLayer, start, end) ->
+  hideHiglight $textLayer
+
+  # TODO: Implement
+
 setupTextSelection = (publication, page, $textLayer) ->
   highlightStartPosition = null
   highlightStartIndex = -1
+  highlightEndIndex = -1

   $textLayer.mousemove (e) ->
     return if highlightStartIndex is -1

     offset = $textLayer.offset()
-    currentPositionIndex = findClosestElement $textLayer,
+    highlightEndIndex = findClosestElement $textLayer,
       left: e.pageX - offset.left
       top: e.pageY - offset.top

-    return if currentPositionIndex is -1
-
-    showHighlight $textLayer, highlightStartIndex, currentPositionIndex
+    return if highlightEndIndex is -1
+
+    showHighlight $textLayer, highlightStartIndex, highlightEndIndex

   $textLayer.mousedown (e) ->
     offset = $textLayer.offset()
@@ -101,10 +117,13 @@

     offset = $textLayer.offset()
     if highlightStartPosition.left is e.pageX - offset.left and highlightStartPosition.top is e.pageY - offset.top
-      hideHiglight $textLayer
+      closeHighlight publication, page, $textLayer, highlightStartIndex, highlightEndIndex
+    else
+      openHihglight publication, page, $textLayer, highlightStartIndex, highlightEndIndex

     highlightStartPosition = null
     highlightStartIndex = -1
+    highlightEndIndex = -1

   $textLayer.mouseleave (e) ->
     return if highlightStartIndex is -1
@@ -113,6 +132,7 @@

     highlightStartPosition = null
     highlightStartIndex = -1
+    highlightEndIndex = -1

 displayPublication = (publication) ->
   PDFJS.getDocument(publication.url()).then (pdf) ->


Fix RangeError tests in Gecko.

Fix RangeError tests in Gecko.

--- src/range.coffee
+++ src/range.coffee
@@ -79,7 +79,7 @@
     node

 class Range.RangeError extends Error
-  constructor: (@type, @message) ->
+  constructor: (@type, @message, @parent=null) ->
     super(@message)

 # Public: Creates a wrapper around a range object obtained from a DOMSelection.
@@ -332,7 +332,11 @@
     range = {}

     for p in ['start', 'end']
-      node = Range.nodeFromXPath(this[p], root)
+      try
+        node = Range.nodeFromXPath(this[p], root)
+      catch e
+        throw new Range.RangeError(p, "Error while finding #{p} node: #{this[p]}: " + e, e)
+
       if not node
         throw new Range.RangeError(p, "Couldn't find #{p} node: #{this[p]}")


changed parameter names for client move functions

--- client/src/Network.coffee
+++ client/src/Network.coffee
@@ -54,12 +54,12 @@
 now.badGoal = (message) ->
   console.log message

-moveToRequired = (resource) ->
-  now.moveToRequired(resource)
+moveToRequired = (index) ->
+  now.moveToRequired(index)

-moveToOptional = (resource) ->
-  now.moveToOptional(resource)
+moveToOptional = (index) ->
+  now.moveToOptional(index)

-moveToForbidden = (resource) ->
-  now.moveToForbidden(resource)
+moveToForbidden = (index) ->
+  now.moveToForbidden(index)


fix alias handling

fix alias handling

--- src/robot.coffee
+++ src/robot.coffee
@@ -183,7 +183,7 @@
     else
       @aliasRegex = false

-    @nameRegex.test message.text or (@aliasRegex and @aliasRegex.test message.text)
+    @nameRegex.test(message.text) or (@aliasRegex and @aliasRegex.test(message.text))

   # Public: Passes the given message to any interested Listeners.
   #


log errors in detail when file cannot be removed

--- services/clsi/app/coffee/ResourceWriter.coffee
+++ services/clsi/app/coffee/ResourceWriter.coffee
@@ -54,9 +54,18 @@

 	_deleteFileIfNotDirectory: (path, callback = (error) ->) ->
 		fs.stat path, (error, stat) ->
-			return callback(error) if error?
-			if stat.isFile()
-				fs.unlink path, callback
+			if error? and error.code is 'ENOENT'
+				return callback()
+			else if error?
+				logger.err {err: error, path: path}, "error stating file in deleteFileIfNotDirectory"
+				return callback(error)
+			else if stat.isFile()
+				fs.unlink path, (error) ->
+					if error?
+						logger.err {err: error, path: path}, "error removing file in deleteFileIfNotDirectory"
+						callback(error)
+					else
+						callback()
 			else
 				callback()


Cleanup profiling data

Cleanup profiling data

--- src/index.coffee
+++ src/index.coffee
@@ -247,40 +247,36 @@
               "Accept": JSON_TYPE
               "Authorization": "Bearer #{token}"
             url = "https://api.producthunt.com/v1/posts/#{id}"
-            dfpstart = Date.now()
             cacheGet url, token, headers, (err, body) ->
               if err
                 callback err
               else
-                console.log "#{Date.now() - dfpstart} to get post with ID #{id}"
                 results = JSON.parse(body)
                 callback null, results.post
           getPostsForDay = (day, callback) ->
             async.waterfall [
               (callback) ->
-                gpidsstart = Date.now()
                 getPostIDs req.clientOnlyToken, day, (err, ids) ->
                   if err
                     callback err
                   else
-                    console.log "#{Date.now() - gpidsstart} to get post ids for #{day}"
                     callback null, ids
               (ids, callback) ->
-                mdfpstart = Date.now()
                 async.map ids, downloadFullPost, (err, posts) ->
                   if err
                     callback err
                   else
-                    console.log "#{Date.now() - mdfpstart} to download posts for #{day}"
                     posts = _.filter posts, (post) ->
                       !(votedFor(req.user, post) || commentedOn(req.user, post))
                     callback null, posts
             ], callback
+          gpfdmstart = Date.now()
           async.map days, getPostsForDay, (err, postses) ->
             if err
               callback err
             else
               posts = _.flatten postses
+              console.log "#{Date.now() - gpfdmstart} to retrieve #{posts.length} posts"
               callback null, posts
       ], callback
     (results, callback) ->
@@ -433,8 +429,6 @@
   token = null
   user = null

-  last = start = Date.now()
-
   firstTime = null

   async.waterfall [
@@ -464,9 +458,6 @@
         else
           results = JSON.parse(body)
           token = results.access_token
-          now = Date.now()
-          console.log "#{now - start} (#{now - last}) to get token"
-          last = now
           callback null
     (callback) ->
       url = 'https://api.producthunt.com/v1/me'
@@ -478,9 +469,6 @@
         else
           results = JSON.parse(body)
           user = results.user
-          now = Date.now()
-          console.log "#{now - start} (#{now - last}) to get user"
-          last = now
           callback null
     (callback) ->
       User.get user.id, (err, got) ->
@@ -497,15 +485,9 @@
             else
               callback null, updated
     (ensured, callback) ->
-      now = Date.now()
-      console.log "#{now - start} (#{now - last}) to get ensured"
-      last = now
       at = new AccessToken {token: token, user: user.id}
       at.save callback
     (saved, callback) ->
-      now = Date.now()
-      console.log "#{now - start} (#{now - last}) to save access token"
-      last = now
       UserAgent.get user.id, (err, agent) ->
         if err
           if err.name == "NoSuchThingError"
@@ -519,10 +501,6 @@
       next err
     else

-      now = Date.now()
-      console.log "#{now - start} (#{now - last}) to get user token"
-      last = now
-
       req.session.userID = user.id

       setImmediate ->


(- fix) Don't trim the first line of files with no shebang

--- Paws/Source/parser.coffee
+++ Paws/Source/parser.coffee
@@ -26,7 +26,7 @@
       # Keep track of the current position into the text
       @i = 0

-      if opts.root
+      if opts.root and @text.slice(0,2) == '#!'
          @text = @text.split("\n").slice(1).join("\n")

    # Accept a single character. If the given +char+ is at the
@@ -139,7 +139,7 @@
       reset   = '\x1b[' + '39m'
       [before, after] = if use_colour then [magenta, reset] else ['|', '|']

-      '{ ' + @source.before() + before + @source.contents() + after + @source.after() + ' }'
+      '{ ' + (@source.before() + before + @source.contents() + after + @source.after()).trim() + ' }'

    else
       @source.contents()


Ensure @db in state connected when trying reuse it.

--- src/index.coffee
+++ src/index.coffee
@@ -25,8 +25,8 @@
   getInstance: (table, after) ->
     throw new Error 'The database name must be configured (options.db)' unless @options.db?

-    if @db isnt null
-      @getCollection table, @db, after
+    if @db isnt null and @db.state is 'connected'
+      @getCollection table, after
     else
       instance = new Db @options.db, new Server @options.host, @options.port, {}

@@ -34,20 +34,20 @@
         console.log 'Error with connection to MongoDB server: ' + error if error

         @db = db
-        @getCollection table, db, after
+        @getCollection table, after

-  getCollection: (table, db, after) ->
+  getCollection: (table, after) ->
     if @collection.object isnt null and @collection.name is table
-      after @db, @collection.object
+      after @collection.object
     else
-      db.collection table, (error, collection) =>
+      @db.collection table, (error, collection) =>
         console.log 'Error with fetching collection: ' + error if error

         @collection = name: table, object: collection
-        after @db, @collection.object
+        after collection

   findById: (table, id, after = ->) ->
-    @getInstance table, (db, collection) =>
+    @getInstance table, (collection) =>
       try
         params = _id: ensureObjectId id
       catch exception
@@ -69,7 +69,7 @@
   find: (table, params, options, after) ->
     [params, options, after] = @_normalizeArguments params, options, after

-    @getInstance table, (db, collection) =>
+    @getInstance table, (collection) =>
       try
         if params?._id?
           if isObject(params._id) and params._id.$in?
@@ -105,7 +105,7 @@

     after = (->) if after is null

-    @getInstance table, (db, collection) =>
+    @getInstance table, (collection) =>
       collection.count params, (error, results) =>
         if error
           console.log 'Error with fetching counts: ' + error
@@ -117,7 +117,7 @@
         after parseInt results, 10

   save: (table, params, after = ->) ->
-    @getInstance table, (db, collection) =>
+    @getInstance table, (collection) =>
       try
         params._id = ensureObjectId params._id if params._id?
       catch exception
@@ -144,11 +144,11 @@
     @_closeAfterRequest = true

     if @db isnt null
+      if @collection.object isnt null
+        @collection = name: null, object: null
+
       @db.close()
       @db = null
-
-    if @collection.object isnt null
-      @collection = name: null, object: null

     @


oauth: fix facebook signup

oauth: fix facebook signup

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -715,6 +715,7 @@

             if session.foreignAuth
               { foreignAuth } = user
+              foreignAuth or= {}
               foreignAuth = extend foreignAuth, session.foreignAuth
               options.foreignAuth = foreignAuth


Find tests passing

Find tests passing

--- spec/adaptorMethods/findSpec.coffee
+++ spec/adaptorMethods/findSpec.coffee
@@ -1,9 +1,53 @@
+create = require '../../src/adaptorMethods/create'
 find = require '../../src/adaptorMethods/find'
 Promise = require 'promise'
 _ = require 'lodash'
+redis = require 'redis'

 describe 'oomphRedisAdaptor#find', ->

+  beforeAll (done) ->
+    @redis = redis.createClient(1111, 'localhost')
+    done()
+
+  beforeEach ->
+    @parentObject =
+      className: 'TestUpdateClass'
+      redis: @redis
+      create: create
+      classAttributes:
+        name:
+          dataType: 'string'
+        url:
+          dataType: 'string'
+          url: true
+          urlBaseAttribute: 'name'
+        integer:
+          dataType: 'integer'
+        manyReferences:
+          dataType: 'reference'
+          many: true
+          referenceModelName: 'Reference'
+    @find = find.bind(@parentObject)
+    referenceModelparentObject =
+      className: 'Reference'
+      redis: @redis
+      classAttributes:
+        secondId:
+          dataType: 'string'
+          identifiable: true
+    @referenceModelCreate = create.bind(referenceModelparentObject)
+
+  afterEach (done) ->
+    @redis.flushdb()
+    done()
+
+  afterAll (done) ->
+    @redis.flushdb()
+    @redis.end()
+    done()
+
+
   beforeAll ->
     parentObject =
       classAttributes: {}
@@ -15,9 +59,9 @@

   it 'should resolve to a valid instance of the modules class when given an id', (done) ->
     pending()
-    #redisObjectClassDataStoreClass = @redisObjectClassDataStoreClass
+    #lass = @lass
     #testProps = { one: 1, two: 2, three: 3 }
-    #test1Object = new redisObjectClassDataStoreClass(testProps)
+    #test1Object = new lass(testProps)
     #create(testProps).then (returnObject) =>
     #  id = returnObject.id
     #  findPromise = find(id)
@@ -39,35 +83,35 @@

   it 'should return an integer correctly', (done) ->
     testProps = { integer: '1'}
-    create(testProps).then (createdObject) =>
+    @parentObject.create(testProps).then (createdObject) =>
       findPromise = @find(createdObject.id)
       findPromise.done (returnValue) ->
         expect(returnValue.integer).toEqual jasmine.any(Number)
         done()

   it 'should return the reference id when many is false', (done) ->
-    @redisObjectClassDataStore.attributes =
+    @parentObject.classAttributes =
       singleReference:
         dataType: 'reference'
         referenceModelName: 'Reference'
         many: false
-    createReferencePromise =  @referenceModel.create(secondId: 'id1')
+    createReferencePromise =  @referenceModelCreate(secondId: 'id1')
     createReferencePromise.then (ref1) =>
-      create(singleReference: ref1.id).then (createdObject) =>
+      @parentObject.create(singleReference: ref1.id).then (createdObject) =>
         findPromise = @find(createdObject.id)
         findPromise.done (foundObj) ->
           expect(foundObj.singleReference).toEqual ref1
           done()

   it 'should return an array of reference ids when many is true', (done) ->
-    ref1 = @referenceModel.create(secondId: 'id1')
-    ref2 = @referenceModel.create(secondId: 'id2')
-    ref3 = @referenceModel.create(secondId: 'id3')
+    ref1 = @referenceModelCreate(secondId: 'id1')
+    ref2 = @referenceModelCreate(secondId: 'id2')
+    ref3 = @referenceModelCreate(secondId: 'id3')
     createReferencesPromise = Promise.all([ref1, ref2, ref3])
     createReferencesPromise.done (referencesObjects) =>
       referenceIds = _.map referencesObjects, 'id'
       testProps =  manyReferences: referenceIds, url: 'new'
-      create(testProps).then (createdObject) =>
+      @parentObject.create(testProps).then (createdObject) =>
         findPromise = @find(createdObject.id)
         findPromise.done (foundObj) ->
           expect(foundObj.manyReferences).toContain referencesObjects[0]


(Kwaak's solution) Fixed preview not allowing external scripts and css from a relative directory

--- lib/atom-html-preview-view.coffee
+++ lib/atom-html-preview-view.coffee
@@ -102,7 +102,10 @@
     </html>
     """
     iframe = document.createElement("iframe")
-    iframe.src = "data:text/html;charset=utf-8,#{encodeURI(text)}"
+    #Fix courtesy of github user kwaak (https://github.com/webBoxio/atom-html-preview/issues/1/#issuecomment-49639162)
+    #This allows use of relative scripts and css files
+    iframe.setAttribute("sandbox", "allow-scripts allow-same-origin")
+    iframe.src = @getPath()
     @html $ iframe
     @trigger('atom-html-preview:html-changed')


Slightly more efficient 'never match' regex

http://jsperf.com/unmatchable-regex/2

Not an important difference, but I became curious about it today

--- src/watch.coffee
+++ src/watch.coffee
@@ -102,7 +102,7 @@
   else if plugin.extension
     RegExp "\\.#{plugin.extension}$"
   else
-    /$.^/
+    /$0^/ # never match
   pattern.test(path)

 # Determine which compiler should be used for path and


Correctly check values

Correctly check values

--- src/nodes.coffee
+++ src/nodes.coffee
@@ -549,6 +549,8 @@
           snd.base = ref
         return new If new Existence(fst), snd, soak: on
       no
+
+   makeReturn: (res) -> new Return this

 #### Comment


:bug: Fix variables and colors not handled for file not in project

--- lib/color-buffer.coffee
+++ lib/color-buffer.coffee
@@ -152,7 +152,9 @@

   isVariablesSource: -> @project.isVariablesSourcePath(@editor.getPath())

-  isIgnored: -> @project.isIgnoredPath(@editor.getPath())
+  isIgnored: ->
+    p = @editor.getPath()
+    @project.isIgnoredPath(p) or not atom.project.contains(p)

   isDestroyed: -> @destroyed

@@ -339,7 +341,7 @@
     variables = if @isVariablesSource()
       (options.variables ? []).concat(@project.getVariables() ? [])
     else
-      []
+      options.variables ? []

     config =
       buffer: @editor.getText()


clearer name

clearer name

--- lib/console/view.coffee
+++ lib/console/view.coffee
@@ -24,7 +24,7 @@
     "terminal"

   addItem: (view, {divider}={divider:true}) ->
-    scroll = @shouldScroll()
+    scroll = @lastCellVisible()
     @fadeIn view
     @items.appendChild view
     if divider then @divider()
@@ -39,7 +39,7 @@
     @getInput()?.querySelector('atom-text-editor')?.getModel()

   addBeforeInput: (view, {divider}={divider:true}) ->
-    if @shouldScroll() then @lock 200
+    if @lastCellVisible() then @lock 200
     @items.insertBefore view, @getInput()
     if divider then @divider(true)
     @slideIn view
@@ -177,7 +177,7 @@

   last: (xs) -> xs[xs.length-1]

-  shouldScroll: ->
+  lastCellVisible: ->
     items = @items.querySelectorAll('.cell')
     return false unless items[0]
     @isVisible @scrollView, @last items


add missing formats

add missing formats

--- src/quill.coffee
+++ src/quill.coffee
@@ -27,9 +27,9 @@

   @DEFAULTS:
     formats: [
-      'align',
-      'bullet', 'list',
-      'bold', 'italic', 'strike', 'underline',
+      'align', 'direction',
+      'bullet', 'header', 'list',
+      'bold', 'code', 'italic', 'script', 'strike', 'underline',
       'link',
       'background', 'color', 'font', 'size',
       'image'


Totally untested slack integration using incoming/outgoing webhooks (wrapped in an upcoming hubot services handler)

--- src/slack.coffee
+++ src/slack.coffee
@@ -1,11 +1,12 @@
 {Adapter,TextMessage} = require 'hubot'
+HTTPS = require 'https'

 class Slack extends Adapter
-   send: (envelope, strings...) ->
+   send: (params, strings...) ->
       user = @userFromParams(params)
-      @bot.sendMessage user.room, str for str in strings
+      @post "/services/hooks/hubot" JSON.stringify({channel: user.reply_to, text: str}) for str in strings

-   reply: (envelope, strings...) ->
+   reply: (params, strings...) ->
       user = @userFromParams(params)
       strings.forEach (str) =>
          @send params, "#{user.name}: #{str}"
@@ -23,9 +24,85 @@
          team:    process.env.HUBOT_SLACK_TEAM
          name:    process.env.HUBOT_SLACK_BOTNAME

-      # Create bot here
+      # Listen to incoming webhooks from slack
+      self.robot.router.post "/hubot/slack-webhook", (req, res) ->
+         # Parse the payload
+         from = req.param('user_id')
+         from_name = req.param('user_name')
+         channel = req.param('channel_id')
+         channel_name = req.param('channel_name')
+         hubot_msg = req.param('text')

+         # Construct an author object
+         author = {}
+         author.id = from
+         author.name = from_name
+         author.reply_to = channel
+         author.room = channel_name
+
+         # Pass to the robot
+         self.receive new TextMessage(author, hubot_msg)
+
+         # Just send back an empty reply, since our actual reply, if any, will be async above
+         res.end
+
+      # Provide our name to Hubot
+      self.robot.name = options.name
+
+      # Tell Hubot we're connected so it can load scripts
       self.emit "connected"
+
+   # Convenience HTTP Methods for sending data back to slack
+   get: (path, callback) ->
+      @request "GET", path, null, callback
+
+   post: (path, body, callback) ->
+      @request "POST", path, body, callback
+
+   request: (method, path, body, callback) ->
+      console.log method, path, body
+      host = @options.team + '.dev.hny.co'
+      headers = "Host": host
+
+      unless @options.token
+         callback "No services token provided to Hubot", null
+         return
+
+      options =
+         "agent"  : false
+         "host"   : host
+         "port"   : 443
+         "path"   : path += "?token=#{@options.token}"
+         "method" : method
+         "headers": headers
+
+      if method is "POST"
+         headers["Content-Type"] = "application/x-www-form-urlencoded"
+         options.headers["Content-Length"] = body.length
+
+      request = HTTPS.request options, (response) ->
+         data = ""
+         response.on "data", (chunk) ->
+            data += chunk
+
+         response.on "end", ->
+            if response.statusCode >= 400
+               console.log "Slack services error: #{response.statusCode}"
+
+            if (callback) callback null, data
+
+         response.on "error", (err) ->
+            if (callback) callback err, null
+
+      if method is "POST"
+         request.end(body, 'binary')
+      else
+         request.end()
+
+      request.on "error", (err) ->
+         console.log err
+         console.log err.stack
+         callback err

 exports.use = (robot) ->
    new Slack robot


Update sound.coffee

Update sound.coffee

fix a few minor logical errors

--- src/sound.coffee
+++ src/sound.coffee
@@ -21,9 +21,12 @@
     @_playAll = ->
         for snd in _list
             if snd._config.playOnLoad
-                snd.play()
+                if env.USE_AUDIO_TAG
+                    snd._audio.autoplay = true
+                else
+                    snd.play()
             else
-                snd.play {volume: 0}
+                snd.play {volume: 0, loop: false}
                 setTimeout snd.stop, 50
     @_stopAll = ->
         snd.stop() for snd in _list
@@ -55,15 +58,15 @@
             wav_src.type = 'audio/wav'

             #assign proper srcs
-            if config.url.indexOf('.mp3') isnt -1
+            if @_config.url.match /.mp3$/
                 mp3_src.src = @_config.url
                 ogg_src.src = @_config.url.replace '.mp3', '.ogg'
                 wav_src.src = @_config.url.replace '.mp3', '.wav'
-            else if config.url.indexOf('.ogg') isnt -1
+            else if @_config.url.match /.ogg$/
                 ogg_src.src = @_config.url
                 mp3_src.src = @_config.url.replace '.ogg', '.mp3'
                 wav_src.src = @_config.url.replace '.ogg', '.wav'
-            else if config.url.indexOf('.wav') isnt -1
+            else if @_config.url.match /.wav$/
                 wav_src.src = @_config.url
                 mp3_src.src = @_config.url.replace '.wav', '.mp3'
                 ogg_src.src = @_config.url.replace '.wav', '.ogg'
@@ -82,8 +85,8 @@
         #web audio API is supported
         else
             #instance variables
+            @_source = null
             @_buffer = null
-            @_source = null
             @_isEnded = true

             #request setup
@@ -94,7 +97,7 @@
             #request event handlers
             request.successCallback = (buffer) =>
                 @_buffer = buffer
-                if config.playOnLoad and Greenhorn.isRunning()
+                if Greenhorn.isRunning() and @_config.playOnLoad
                     @play()
             request.errorCallback = ->
                 throw new Error "AJAX request Error"
@@ -146,4 +149,4 @@
 #end class Sound

 #more natural alias for calling class methods
-@Sounds = @Sound+@Sounds = @Sound


Fix the npm loading (knocked out bower by mistake)

--- config/application.coffee
+++ config/application.coffee
@@ -20,6 +20,8 @@
   _ = grunt.util._
   app = lineman.config.application

+  app.loadNpmTasks.push("grunt-jasmine-bundle")
+
   if libConfig.generateBowerJson
     app.loadNpmTasks.push("grunt-write-bower-json")
     app.appendTasks.dist.push("writeBowerJson")
@@ -35,8 +37,6 @@
              */

             """
-
-  loadNpmTasks: ["grunt-jasmine-bundle"]

   hooks:
     loadNpmTasks:


Formatting.

Formatting.

--- src/map-view.coffee
+++ src/map-view.coffee
@@ -479,8 +479,7 @@
         showAllUnitsAtHighZoom: ->
             if $(window).innerWidth() <= appSettings.mobile_ui_breakpoint
                 return
-            zoom = @map.getZoom()
-            if zoom >= map.MapUtils.getZoomlevelToShowAllMarkers()
+            if @map.getZoom() >= map.MapUtils.getZoomlevelToShowAllMarkers()
                 if @selectedUnits.isSet() and not @selectedUnits.first().collection?.filters?.bbox?
                     return
                 if @selectedServices.isSet()


:new: Implement efficient count updates

:new: Implement efficient count updates

--- lib/linter-views.coffee
+++ lib/linter-views.coffee
@@ -61,21 +61,20 @@
       @panel.refresh(@state.scope)

   classifyMessages: (messages) ->
-    filePath = atom.workspace.getActiveTextEditor()?.getPath()
-    @count.File = 0
-    @count.Project = 0
-    for key, message of messages
-      if message.currentFile = (filePath and message.filePath is filePath)
-        @count.File++
-      @count.Project++
+    activeEditorLinter = @linter.getActiveEditorLinter()
+    @count.File = if activeEditorLinter then activeEditorLinter.getMessages().size else 0
+    @count.Project = @messages.length
     return @classifyMessagesByLine(messages)

   classifyMessagesByLine: (messages) ->
-    row = atom.workspace.getActiveTextEditor()?.getCursorBufferPosition().row
+    activeEditorLinter = @linter.getActiveEditorLinter()
+    return unless activeEditorLinter
+    row = atom.workspace.getActiveTextEditor().getCursorBufferPosition().row
     @count.Line = 0
-    for key, message of messages
-      if message.currentLine = (message.currentFile and message.range and message.range.intersectsRow(row))
+    activeEditorLinter.getMessages().forEach((message) =>
+      if message.currentLine = (message.range and message.range.intersectsRow(row))
         @count.Line++
+    )
     return messages

   renderBubble: ->


append rather than destroying the original file

--- lib/misc/history.coffee
+++ lib/misc/history.coffee
@@ -17,7 +17,7 @@
       lineReader.on 'line', (line) =>
         if line.startsWith '#'
           if !readingMeta
-            entries.push {}
+            entries.push {loaded: true}
             readingMeta = true
           [_, key, val] = line.match /# (.+?): (.*)/
           entries[entries.length-1][key] = val
@@ -33,11 +33,12 @@
         resolve entries

   write: (entries) ->
-    out = fs.createWriteStream @path, flags: 'w'
+    out = fs.createWriteStream @path, flags: 'a'
     for entry in entries
-      for k, v of entry
-        if k isnt 'input'
-          out.write "# #{k}: #{v}\n"
-      for line in entry.input.split '\n'
-        out.write "\t#{line}\n"
+      writeKey =  (k, v) -> out.write "# #{k}: #{v}\n"
+      if not entry.loaded
+        if entry.time? then writeKey 'time', entry.time
+        writeKey 'mode', entry.mode or 'julia'
+        for line in entry.input.split '\n'
+          out.write "\t#{line}\n"
     return


:bug: Fixes build failure

:bug: Fixes build failure

--- lib/tab-bar-view.coffee
+++ lib/tab-bar-view.coffee
@@ -48,7 +48,7 @@

     @subscriptions.add @pane.onDidChangeActiveItem (item) =>
       if atom.config.get('tabs.useTransientBehavior') and item instanceof TextEditor
-        if @tab instanceof TabView and @getTabs().length > 1 and @tab.item isnt item and !@tab.keepOpen and @tab.item instanceof TextEditor
+        if @tab instanceof TabView and @getTabs().length > 1 and @tab.item isnt item and not @tab.keepOpen and @tab.item instanceof TextEditor
           @pane.destroyItem @tab.item
         @tab = @tabForItem(item)
       @updateActiveTab()
@@ -92,7 +92,7 @@
   addTabForItem: (item, index) ->
     tabView = new TabView()
     tabView.initialize(item)
-    if atom.config.get('tabs.useTransientBehavior') and !@tab and item instanceof TextEditor
+    if atom.config.get('tabs.useTransientBehavior') and not @tab and item instanceof TextEditor
       @tab = tabView
     @insertTabAtIndex(tabView, index)


refactoring for detecting alloy view(xml)

--- lib/viewProvider.coffee
+++ lib/viewProvider.coffee
@@ -18,11 +18,9 @@
     if scopes.indexOf('text.xml') isnt -1
       editor = request.editor
       lineCnt = editor.getLineCount()
-      for i in [0...lineCnt] by 1
-        lineText = editor.lineTextForBufferRow(i).trim()
-        if lineText && lineText.indexOf('<Alloy') isnt -1
-          editor.setGrammar(atom.grammars.grammarForScopeName('text.alloyxml'))
-        break if lineText.length > 0;
+      for i in [0...lineCnt] by 1 when editor.lineTextForBufferRow(i).indexOf('<Alloy') isnt -1
+        editor.setGrammar(atom.grammars.grammarForScopeName('text.alloyxml'))
+        break

     {prefix} = request
     if @isAttributeValueStartWithNoPrefix(request)


removed extra Grunt require

removed extra Grunt require

--- src/steroids.coffee
+++ src/steroids.coffee
@@ -1,5 +1,4 @@
 Help = require "./steroids/Help"
-Grunt = require "./steroids/Grunt"
 paths = require "./steroids/paths"

 argv = require('optimist').argv


Export more: extensionMap, Style et al., escapeId, convertSVG

--- src/svgtiler.coffee
+++ src/svgtiler.coffee
@@ -1287,13 +1287,18 @@
     console.log 'Not enough filename arguments'
     help()

-exports = {Symbol, StaticSymbol, DynamicSymbol, unrecognizedSymbol,
+svgtiler = {
+  Symbol, StaticSymbol, DynamicSymbol, unrecognizedSymbol,
   Mapping, ASCIIMapping, JSMapping, CoffeeMapping,
   Drawing, ASCIIDrawing, DSVDrawing, SSVDrawing, CSVDrawing, TSVDrawing,
   Drawings, XLSXDrawings,
-  Input, Mappings, Context, SVGTilerException, SVGNS, XLINKNS, main}
-module?.exports ?= exports
-window?.svgtiler ?= exports
+  Style, CSSStyle, StylusStyle,
+  extensionMap, Input, Mappings, Context,
+  SVGTilerException, SVGNS, XLINKNS, escapeId,
+  main, convertSVG
+}
+module?.exports ?= svgtiler
+window?.svgtiler ?= svgtiler

 unless window?
   main()


:green_heart: Fix broken specs

:green_heart: Fix broken specs

--- spec/formatters/pull-request-spec.coffee
+++ spec/formatters/pull-request-spec.coffee
@@ -19,10 +19,10 @@
       type: 'pull_request'
       data:
         action: 'opened'
-        html_url: url
         number: number
         pull_request:
           title: title
+          html_url: url
         repository:
           full_name: repo
         sender:


Demo works!

Demo works!

--- src/wrangler.coffee
+++ src/wrangler.coffee
@@ -26,20 +26,40 @@

 window.wrangle = (model, template, options) ->

-	# Get the Wrangler to return
+	# If the page hasn't loaded, then let's wait a bit
+	if document.readyState isnt 'complete'
+		if window.addEventListener
+			window.addEventListener 'load', ->
+				window.wrangle(model, template, options)
+			, false
+		else
+			window.attachEvent 'onload', ->
+				window.wrangle(model, template, options)
+		return
+
+	# Get the object we'll be returning
 	wrangler = {}

-	# Give the Wrangler a model
+	# Give this wrangler the model
 	wrangler.model = model

 	# Get template element
 	if ($?) and (template instanceof $)
 		template = template[0]
-	if template.toUppercase
+	if (typeof template is 'string') or (template instanceof String)
 		template = document.getElementById(template)
+	wrangler.element = template
+	wrangler.template = template.innerHTML

 	# Update the DOM
 	wrangler.update = ->
+		if @model instanceof Array
+			output = ''
+			for m in @model
+				output += window.Milk.render(@template, m)
+			@element.innerHTML = output
+		else
+			@element.innerHTML = window.Milk.render(@template, @model)

 	# Set a property and then update
 	wrangler.set = (index, property, value) ->
@@ -49,7 +69,7 @@
 			@model[property] = value
 		@update()

-	# Do an initial update, which starts the timeout
+	# Do an initial update
 	wrangler.update()

 	# We're done!


atom: Improve js formatting

atom: Improve js formatting

--- home-atom/.atom/config.cson
+++ home-atom/.atom/config.cson
@@ -1,4 +1,46 @@
 "*":
+  "atom-beautify":
+    apex: {}
+    arduino:
+      beautify_on_save: true
+    bash:
+      beautify_on_save: true
+    blade: {}
+    c:
+      beautify_on_save: true
+      default_beautifier: "clang-format"
+    cfml: {}
+    clj: {}
+    coffeescript: {}
+    cpp:
+      beautify_on_save: true
+      default_beautifier: "clang-format"
+    crystal: {}
+    cs: {}
+    css:
+      beautify_on_save: true
+    csv: {}
+    d: {}
+    ejs: {}
+    elm: {}
+    erb: {}
+    erlang: {}
+    fortran: {}
+    general:
+      _analyticsUserId: "1bf7e8b7-d845-4117-a645-39738e02df3f"
+    js:
+      beautify_on_save: true
+      break_chained_methods: true
+      end_with_comma: true
+      end_with_newline: true
+      indent_size: 2
+      jslint_happy: true
+    json:
+      beautify_on_save: true
+      indent_size: 2
+    markdown:
+      beautify_on_save: true
+      yaml: false
   "autocomplete-clang": {}
   "autocomplete-plus":
     autoActivationDelay: 100
@@ -36,6 +78,15 @@
     tabLength: 4
   "exception-reporting":
     userId: "49eb801d-9dd0-494b-a71e-7a886a0f40c8"
+  "formatter-jsbeautify":
+    css:
+      enable: false
+    html:
+      enable: false
+    less:
+      enable: false
+    sass:
+      enable: false
   "git-blame":
     colorCommitAuthors: true
     ignoreWhiteSpaceDiffs: true
@@ -44,7 +95,11 @@
   "linter-clang":
     clangDefaultCFlags: "-Wall -Wextra -Wno-unused-parameter"
     clangDefaultCppFlags: "-Wall -std=c++11 -Wextra -Wno-unused-parameter"
-  "linter-ui-default": {}
+  "linter-js-standard":
+    checkForEslintConfig: false
+    showEslintRules: true
+  "linter-ui-default":
+    showPanel: true
   "markdown-preview":
     useGitHubStyle: true
   "spell-check":
@@ -70,6 +125,9 @@
       "ifndef"
       "cpp"
     ]
+  "standard-formatter":
+    formatOnSave: true
+    honorPackageConfig: false
   "tree-view":
     hideVcsIgnoredFiles: true
     squashDirectoryNames: true


Only show intercom to logged in customers

--- js/outer/_page.coffee
+++ js/outer/_page.coffee
@@ -7,7 +7,6 @@
     @maybeTrackMixpanel()

     # Render content
-    @clearIntercom()
     @render(cx)

     # Land at the right anchor on the page
@@ -56,9 +55,6 @@
   setPageTitle: (cx) =>
     document.title = @title(cx) + " - CircleCI"

-  clearIntercom: =>
-    $('#IntercomTab').text "" # clear the intercom tab
-
   placeholder: () =>
     $("input, textarea").placeholder()


Fix incorrect reference to paddingTop/Bottom

--- src/coffee/plugins/jquery-stacked.coffee
+++ src/coffee/plugins/jquery-stacked.coffee
@@ -85,7 +85,7 @@
             paddingTop = parsePixelString($elem.css('paddingTop'))
             paddingBottom = parsePixelString($elem.css('paddingBottom'))

-            height = windowHeight - offsetTop - marginTop - marginBottom
+            height = windowHeight - offsetTop - paddingTop - paddingBottom

         height = applyRelativeSize(height, maxHeight)
         return height


guard

guard

--- apps/partner/client/articles.coffee
+++ apps/partner/client/articles.coffee
@@ -17,7 +17,7 @@
   $.ajax
     url: "#{sd.POSITRON_URL}/api/articles"
     data:
-      partner_id: sd.PROFILE.owner._id
+      partner_id: sd.PROFILE && sd.PROFILE.owner._id
       published: true
       limit: 1
     success: (res) ->


Priority Toggle Implementation

Priority Toggle Implementation

--- source/scripts/views/task_item_expanded.coffee
+++ source/scripts/views/task_item_expanded.coffee
@@ -31,6 +31,7 @@
     'keypress .input-name'       : 'endEditOnEnter'
     'focus .notes'               : 'notesEdit'
     'blur .notes'                : 'notesSave'
+    'click .priority-toggle'     : 'togglePriority'
     'click .input-date'          : 'datesOpen'
     'change .input-date'         : 'datesSave'
     'mousedown': 'mousedown'
@@ -132,6 +133,12 @@
   # PRIORITIES
   # ---------------------------------------------------------------------------

+  togglePriority: =>
+    if @task.priority is 3
+      @task.priority = 1
+    else
+      @task.priority++
+
   updatePriority: =>
     @el
       .removeClass('p1 p2 p3')


cannot mix hear and respond

cannot mix hear and respond


Slightly better implementation

Slightly better implementation

--- lib/omni-sharp-server/omni.coffee
+++ lib/omni-sharp-server/omni.coffee
@@ -30,21 +30,23 @@
         query: query

     @req: (path, event, d, editor) =>
-      @_req(path, event, d, editor)
+      context = @getEditorContext(editor)
+      return Promise.reject "no editor context found" unless context
+
+      fullData = _.extend({}, context, d)
+
+      @_req(path, event, fullData, editor)
       .catch (data) ->
         console.error data.statusCode?, data.options?.uri if typeof data isnt 'string'

-    @_req: (path, event, d, editor) =>
+    @_req: (path, event, fullData, editor) =>
       return Promise.reject "omnisharp not ready" if OmniSharpServer.vm.isNotReady
-
-      context = @getEditorContext(editor)
-      return Promise.reject "no editor context found" unless context

       rp
         uri: @_uri path
         method: "POST",
         json: true,
-        body: _.extend({}, context, d)
+        body: fullData
       .then (data) ->
         try
           parsedData = JSON.parse(data)
@@ -57,31 +59,14 @@
         parsedData

     @reql: (path, event, d, editor) =>
-      @_reql(path, event, d, editor)
-      .catch (data) ->
-        console.error data.statusCode?, data.options?.uri if typeof data isnt 'string'
-
-    @_reql: (path, event, d, editor) =>
-      return Promise.reject "omnisharp not ready" if OmniSharpServer.vm.isNotReady
-
       context = @getEditorContext(editor)
       return Promise.reject "no editor context found" unless context

-      rp
-        uri: @_uri path
-        method: "POST",
-        json: true,
-        body: _.extend([], [context], d)
-      .then (data) ->
-        try
-          parsedData = JSON.parse(data)
-        catch
-          parsedData = data
-        finally
-          atom.emit "omni:#{event}", parsedData
-          console.log "omni:#{event}", parsedData
+      fullData = _.extend([], [context], d)

-        parsedData
+      @_req(path, event, fullData, editor)
+      .catch (data) ->
+        console.error data.statusCode?, data.options?.uri if typeof data isnt 'string'

     @syntaxErrors: => @req "syntaxErrors", "syntax-errors"


cleanup

cleanup

--- lib/provider/provider-base.coffee
+++ lib/provider/provider-base.coffee
@@ -106,8 +106,6 @@
     isActiveEditor(@editor)

   saveState: ->
-    name = @dashName
-    options = {query: @ui.lastQuery}
     properties = {
       @searchWholeWord
       @searchWholeWordChangedManually
@@ -118,7 +116,11 @@
     for property in  @saveProperties ? []
       properties[property] = this[property]

-    {name, options, properties}
+    {
+      name: @dashName
+      options: {query: @ui.lastQuery}
+      properties: properties
+    }

   constructor: (editor, @options={}, properties) ->
     _.extend(this, properties) if properties?


Make tests independent from browsers statistics update

--- test/info.coffee
+++ test/info.coffee
@@ -1,6 +1,7 @@
-Browsers = require('../lib/browsers')
-Prefixes = require('../lib/prefixes')
-info     = require('../lib/info')
+browserslist = require('browserslist')
+Browsers     = require('../lib/browsers')
+Prefixes     = require('../lib/prefixes')
+info         = require('../lib/info')

 data =
   browsers: require('caniuse-db/data.json').agents
@@ -29,12 +30,16 @@
       ['chrome 30', 'firefox 21', 'firefox 20', 'ie 6'])
     prefixes = new Prefixes(data.prefixes, browsers)

+    coverage = browserslist.coverage(
+      ['chrome 30', 'firefox 21', 'firefox 20', 'ie 6'])
+    round = Math.round(coverage * 100) / 100.0
+
     info(prefixes).should.eql "Browsers:\n" +
                               "  Chrome: 30\n" +
                               "  Firefox: 21, 20\n" +
                               "  IE: 6\n" +
                               "\n" +
-                              "These browsers account for 0.13% " +
+                              "These browsers account for #{round}% " +
                                 "of all users globally\n" +
                               "\n" +
                               "At-Rules:\n" +
@@ -55,12 +60,15 @@
     browsers = new Browsers(data.browsers, ['chrome 30', 'firefox 20', 'ie 6'])
     prefixes = new Prefixes(data.prefixes, browsers)

+    coverage = browserslist.coverage(['chrome 30', 'firefox 20', 'ie 6'])
+    round    = Math.round(coverage * 100) / 100.0
+
     info(prefixes).should.eql "Browsers:\n" +
                               "  Chrome: 30\n" +
                               "  Firefox: 20\n" +
                               "  IE: 6\n" +
                               "\n" +
-                              "These browsers account for 0.09% " +
+                              "These browsers account for #{ round }% " +
                                 "of all users globally\n" +
                               "\n" +
                               "Properties:\n" +


fixing path to polyglot in the test

fixing path to polyglot in the test

--- test/main.coffee
+++ test/main.coffee
@@ -1,4 +1,4 @@
-Polyglot = require("../lib/pg")
+Polyglot = require("../lib/polyglot")
 should = require('chai').should()

 describe "t", ->


silent tests

silent tests

--- test/node.coffee
+++ test/node.coffee
@@ -13,11 +13,8 @@

   exec 'git describe --exact-match', (err) ->
     if err
-      console.log("skipping git tag check")
       # Not a proper tag; no need to check it
       return done()
-
-    console.log("running git tag check")

     exec 'git describe --tags', propagate done, (stdout, stderr) ->
       gitVersion = stdout.split('\n')[0].match(/^v(\d+\.\d+\.\d+)$/)[1]


Unit tests for 12af4a811d3990c5ab83c26caa222e72ec2d6734

--- test/page.coffee
+++ test/page.coffee
@@ -73,6 +73,26 @@
           "which works correctly": (cookies) ->
             assert.ok cookies, "cookies should not be empty"

+        "can add cookies":
+            topic: t (page) ->
+                page.setCookie
+                    name: "cookieName"
+                    value: "cookieValue"
+                    path: "/testPath", (status) =>
+                        @callback null, status
+                page.getCookies (cookies) =>
+                    @callback null, cookies
+
+            "which succeeds": (status) ->
+                assert.ok status, "addCookie should succeed"
+
+            "and the cookie should be available": (cookies) ->
+                assert.ok (c for c in cookies when (c) ->
+                    c.name == "cookieName" and
+                    c.value == "cookieValue" and
+                    c.path == "/testPath"),  "cookie must be in phantom.cookies"
+
+
         "can inject Javascript from a file":
           topic: t (page) ->
             page.injectJs 'test/inject.js', (success) =>


add failing test for #9

add failing test for #9

--- test/test.coffee
+++ test/test.coffee
@@ -124,3 +124,16 @@
       err.fileName = '/a/b/c/foo.txt'
       testLine = err.stack.toString().split(/\r?\n/g)[1]
       testLine.should.equal '    in /a/b/c/foo.txt'
+
+describe 'bluebird support', ->
+  it 'should allow the stack to be set', ->
+    bluebirdPropertyWritable = (obj, prop)->
+        descriptor = Object.getOwnPropertyDescriptor(obj, prop)
+        return !!(!descriptor || descriptor.writable || descriptor.set)
+
+    TestError = errorEx 'TestError', fileName: errorEx.line 'in %s'
+    err = new TestError 'error'
+    err_native = new Error 'hello'
+
+    (bluebirdPropertyWritable err_native, 'stack').should.be.ok()
+    (bluebirdPropertyWritable err, 'stack').should.be.ok()


deleted unnecessary code

deleted unnecessary code

--- test/test.coffee
+++ test/test.coffee
@@ -51,9 +51,6 @@
         @pf.get(port:7, protocol:'test')
       ).should.throw()

-  # xit 'get service from port number and specifying the protocol', ->
-  #   @pf.get(port:7, protocol:'tcp').service.should.eql 'echo'
-
   # it 'get a one free port', (done) ->
   #   @pf.free (port) ->
   #     console.log port


Maybe need to refer to lib?

Maybe need to refer to lib?

--- test/test.coffee
+++ test/test.coffee
@@ -1,8 +1,8 @@
 require './helper'
 extend = require 'extend'

-Ref   = require '../src/ref'
-refer = require '../src/refer'
+Ref   = require '../lib/ref'
+refer = require '../lib/refer'

 clone = (obj) ->
   extend true, {}, obj


removes pesky middlewares that our proxy does not need

- these were causing HTTP PUT not to work due to (as far as we can tell) the way they buffer things

--- tasks/server.coffee
+++ tasks/server.coffee
@@ -26,8 +26,6 @@
     app = express()
     userConfig.drawRoutes app  if userConfig.drawRoutes
     app.configure ->
-      app.use express.json()
-      app.use express.multipart()
       app.use express.static(process.cwd() + "/" + webRoot)
       app.use apiProxy(apiProxyHost, apiPort, new httpProxy.RoutingProxy())  if apiProxyEnabled
       app.use express.errorHandler()


title is no longer given as a prop

title is no longer given as a prop

--- src/course/confirm-join.cjsx
+++ src/course/confirm-join.cjsx
@@ -10,7 +10,6 @@
 ConfirmJoin = React.createClass

   propTypes:
-    title: React.PropTypes.element.isRequired
     course: React.PropTypes.instanceOf(Course).isRequired
     optionalStudentId: React.PropTypes.bool


Deligate button click event (instead of driectly binding)

--- vendor/assets/javascripts/modal.js.coffee
+++ vendor/assets/javascripts/modal.js.coffee
@@ -50,10 +50,12 @@

 $ ->

+  console.log 'modals'
+
   $('script[data-path]').each ->
     $(this).modal 'precache'

-  $('*[data-open-modal]').on 'click', (e) ->
+  $(document.body).on 'click', '*[data-open-modal]', (e) ->
     e.preventDefault()
     e.stopPropagation()


[atom] telemetry

[atom] telemetry

--- .atom/config.cson
+++ .atom/config.cson
@@ -12,6 +12,7 @@
       "CVS"
     ]
     projectHome: "$HOME/Code"
+    telemetryConsent: "no"
     themes: [
       "atom-material-ui"
       "base16-syntax"


Add special case for L==0 and L==100

Add special case for L==0 and L==100

--- src/main.coffee
+++ src/main.coffee
@@ -189,6 +189,9 @@
   ctx.clearRect 0, 0, width, height
   ctx.globalCompositeOperation = 'source-over'

+  if L == 0 or L == 100
+    return
+
   xn = width / dim
   yn = height / dim

@@ -246,57 +249,67 @@
     .attr("transform", "translate(200, 200)")

   redrawBackground = ->
-
-    pairs = _.map hs(L), (hrad) ->
-      C = $.husl._maxChroma L, hrad * 180 / Math.PI
-      return [hrad, C]
-
-    Cs = _.map pairs, (pair) -> pair[1]
-
-    maxC = Math.max Cs...
-    minC = Math.min Cs...
-
-    bounds = getBounds L
-
-    intersections = []
-    for i in getIntersections _.pairs bounds
-      good = true
-      for [name, bound] in _.pairs bounds
-        if name in i.names
-          continue
-        int = intersection2 bound, i.point
-        if int != null
-          good = false
-      if good
-        intersections.push(i)
-
-    cleanBounds = []
-    for {point, names} in intersections
-      cleanBounds = _.union cleanBounds, names
-
-    longest = 0
-    for {point} in intersections
-      length = distanceFromPole point
-      if length > longest
-        longest = length
-
-    scale = 190 / longest
-
-    sortedIntersections = _.pluck sortIntersections(intersections), 'point'
-
-    shape = d3.geom.polygon sortedIntersections
-    if shape.area() < 0
-      sortedIntersections.reverse()
+    if L != 0 and L != 100
+
+      pairs = _.map hs(L), (hrad) ->
+        C = $.husl._maxChroma L, hrad * 180 / Math.PI
+        return [hrad, C]
+
+      Cs = _.map pairs, (pair) -> pair[1]
+
+      maxC = Math.max Cs...
+      minC = Math.min Cs...
+
+      bounds = getBounds L
+
+      intersections = []
+      for i in getIntersections _.pairs bounds
+        good = true
+        for [name, bound] in _.pairs bounds
+          if name in i.names
+            continue
+          int = intersection2 bound, i.point
+          if int != null
+            good = false
+        if good
+          intersections.push(i)
+
+      cleanBounds = []
+      for {point, names} in intersections
+        cleanBounds = _.union cleanBounds, names
+
+      longest = 0
+      for {point} in intersections
+        length = distanceFromPole point
+        if length > longest
+          longest = length
+
+      scale = 190 / longest
+
+      sortedIntersections = _.pluck sortIntersections(intersections), 'point'
+
       shape = d3.geom.polygon sortedIntersections
-
-    contrasting = if L > 70 then '#1b1b1b' else '#ffffff'
-
-    pastelBoundary
-      .attr("r", scale * minC)
-      .attr("stroke", contrasting)
-
-    center
-      .attr("fill", contrasting)
+      if shape.area() < 0
+        sortedIntersections.reverse()
+        shape = d3.geom.polygon sortedIntersections
+
+      contrasting = if L > 70 then '#1b1b1b' else '#ffffff'
+
+      pastelBoundary
+        .attr("r", scale * minC)
+        .attr("stroke", contrasting)
+
+      center
+        .attr("fill", contrasting)
+
+    else
+      pastelBoundary
+        .attr("r", 0)
+        .attr("stroke", contrasting)
+
+      center
+        .attr("fill", contrasting)
+

   background.redraw = redrawBackground

@@ -324,6 +337,7 @@
     .attr("cx", 0)
     .attr("cy", 0)
     .attr("r", 4)
+    .attr("style", "display:none")
     .attr("transform", "translate(200, 200)")
     .attr("fill", "none")
     .attr("stroke-width", 2)
@@ -349,14 +363,23 @@

   redrawForeground = ->

-    maxChroma = $.husl._maxChroma L, H
-    chroma = maxChroma * S / 100
-    hrad = H / 360 * 2 * Math.PI
-
-    pickerScope
-      .attr("cx", chroma * Math.cos(hrad) * scale)
-      .attr("cy", chroma * Math.sin(hrad) * scale)
-      .attr("stroke", contrasting)
+    if L != 0 and L != 100
+
+      maxChroma = $.husl._maxChroma L, H
+      chroma = maxChroma * S / 100
+      hrad = H / 360 * 2 * Math.PI
+
+      pickerScope
+        .attr("cx", chroma * Math.cos(hrad) * scale)
+        .attr("cy", chroma * Math.sin(hrad) * scale)
+        .attr("stroke", contrasting)
+        .attr("style", "display:inline")
+
+    else
+
+      pickerScope
+        .attr("style", "display:none")
+

     colors = d3.range(0, 360, 10).map (_) -> $.husl.toHex _, S, L
     d3.select("#picker div.control-hue").style {
@@ -436,8 +459,8 @@
     redrawSwatch()

 sliderLightness = d3.slider()
-  .min(1)
-  .max(99)
+  .min(0)
+  .max(100)
   .on 'slide', (e, value) ->
     L = value
     background.redraw()


tweaked the layout of the panes

tweaked the layout of the panes

--- src/main.coffee
+++ src/main.coffee
@@ -7,8 +7,9 @@
     textarea = $('<textarea>');
     textarea.css({
       position: 'absolute'
+      right:    '0'
       width:    '50%'
-      height:   '100%'
+      height:   '98%'
     });
     textarea.appendTo('#App');


:penguin: Add linux keymaps.

:penguin: Add linux keymaps.

--- keymaps/tree-view.cson
+++ keymaps/tree-view.cson
@@ -4,7 +4,7 @@
   'cmd-|': 'tree-view:reveal-active-file'
   'ctrl-0': 'tree-view:toggle-focus'

-'.platform-win32':
+'.platform-win32, .platform-linux':
   'ctrl-\\': 'tree-view:toggle'
   'ctrl-k ctrl-b': 'tree-view:toggle'
   'ctrl-|': 'tree-view:reveal-active-file'
@@ -15,7 +15,7 @@
   'cmd-x': 'tree-view:cut'
   'cmd-v': 'tree-view:paste'

-'.platform-win32 .tree-view':
+'.platform-win32 .tree-view, .platform-linux .tree-view':
   'ctrl-c': 'tree-view:copy'
   'ctrl-x': 'tree-view:cut'
   'ctrl-v': 'tree-view:paste'


Add precise animation loop

Add precise animation loop

--- framer/AnimationLoop.coffee
+++ framer/AnimationLoop.coffee
@@ -5,25 +5,34 @@
 {Config} = require "./Config"
 {EventEmitter} = require "./EventEmitter"

+if window.performance
+	getTime = -> window.performance.now()
+else
+	getTime = -> Date.now()
+
+
 class exports.AnimationLoop extends EventEmitter
+
+	constructor: ->
+		@_delta = 1/60

 	start: =>

 		animationLoop = @

-		_timestamp = null
+		_timestamp = getTime()

 		tick = (timestamp) ->

 			window.requestAnimationFrame(tick)

-			# if _timestamp
-			# 	delta = (timestamp - _timestamp) / 1000
-			# else
-			# 	delta = 1/60
-
-			delta = 1/60
+			if animationLoop._delta
+				delta = animationLoop._delta
+			else
+				timestamp = getTime()
+				delta = (timestamp - _timestamp) / 1000
+				_timestamp = timestamp

 			animationLoop.emit("update", delta)
 			animationLoop.emit("render", delta)


Start working on fits viewer. Maybe this will happen?

--- src/ubret/fits_viewer.coffee
+++ src/ubret/fits_viewer.coffee
@@ -1,3 +1,4 @@
 BaseTool = window.Ubret.BaseTool or require('./base_tool')

 class FitsViewer extends BaseTool
+  constructor:


caching object search pairs

caching object search pairs

--- js/src/http/SharingApi.coffee
+++ js/src/http/SharingApi.coffee
@@ -3,6 +3,7 @@
   'axios'
   'bluebird'
   'kryptnostic.binary-utils'
+  'kryptnostic.caching-service'
   'kryptnostic.configuration'
   'kryptnostic.logger'
   'kryptnostic.requests'
@@ -15,6 +16,7 @@

   # utils
   BinaryUtils = require 'kryptnostic.binary-utils'
+  Cache       = require 'kryptnostic.caching-service'
   Config      = require 'kryptnostic.configuration'
   Logger      = require 'kryptnostic.logger'
   Requests    = require 'kryptnostic.requests'
@@ -87,9 +89,17 @@
       if not validateVersionedObjectKey(versionedObjectKey)
         return Promise.resolve(null)

+      cachedObjectSearchPair = Cache.get(Cache.OBJECT_SEARCH_PAIRS, versionedObjectKey.objectId)
+      if cachedObjectSearchPair
+        return Promise.resolve(cachedObjectSearchPair)
+
       Requests.getAsUint8FromUrl(
         getObjectSearchPairUrl(versionedObjectKey.objectId, versionedObjectKey.objectVersion)
       )
+      .then (objectSearchPair) ->
+        if objectSearchPair?
+          Cache.store(Cache.OBJECT_SEARCH_PAIRS, versionedObjectKey.objectId, objectSearchPair)
+        return objectSearchPair

     addObjectSearchPair: (versionedObjectKey, objectSearchPair) ->


fix markdown/html editor on safari

fix markdown/html editor on safari

--- src/smc-webapp/editor-html-md/editor-html-md.coffee
+++ src/smc-webapp/editor-html-md/editor-html-md.coffee
@@ -569,7 +569,9 @@
     _set: (content) =>
         @source_editor._set(content)

-    _show: (opts={}) =>
+    _show: =>
+        if $.browser.safari  # safari flex bug: https://github.com/philipwalton/flexbugs/issues/132
+            @element.make_height_defined()
         return

     focus: () =>