NEW: altSources feature lets you specify already-loaded assets to use while this one is loading - if they in fact are already loaded

--- src/art/engine/elements/shapes/bitmap_element.coffee
+++ src/art/engine/elements/shapes/bitmap_element.coffee
@@ -4,7 +4,7 @@
 FillableBase = require '../fillable_base'

 {ceil, round} = Math
-{inspect, min, max, bound, log, createWithPostCreate, isString, isNumber, BaseObject} = Foundation
+{inspect, min, max, bound, log, createWithPostCreate, isString, isNumber, BaseObject, isPlainArray} = Foundation
 {point, rect, Matrix, point0, point1} = Atomic

 module.exports = createWithPostCreate class BitmapElement extends FillableBase
@@ -15,6 +15,7 @@
     constructor: ->
       @_cache = {}
       @_referenceCounts = {}
+      @_loaded = {}

     # OUT: promise.then (bitmap) ->
     get: (url, initializerPromise) ->
@@ -22,7 +23,11 @@
         log "SourceToBitmapCache#get: cached:#{!!@_cache[url]} #{url}"
         log "initializerPromise: #{!!initializerPromise}"
       @_referenceCounts[url] = (@_referenceCounts[url] || 0) + 1
-      @_cache[url] ||= initializerPromise || Canvas.Bitmap.get url
+      out = @_cache[url] ||= initializerPromise || Canvas.Bitmap.get url
+      out.then (bitmap) => @_loaded[url] = bitmap
+      out
+
+    loaded: (url) -> @_loaded[url]

     # returns true if the bitmap was released
     # returns false if there are still other references
@@ -36,6 +41,7 @@
       @_referenceCounts[url]--
       if @_referenceCounts[url] == 0
         delete @_cache[url]
+        delete @_loaded[url]
         true
       else
         false
@@ -86,6 +92,10 @@
       validate:   (v) -> !v || isString v
       postSetter: (v) -> v && @_loadBitmapFromSource v

+    altSources:
+      default:    null
+      validate:   (v) -> !v || isPlainArray v
+
   _loadBitmapFromSource: (source) ->
     sourceToBitmapCache.get source
     .then (bitmap) =>
@@ -100,9 +110,10 @@

   _drawPropertiesChanged: ->
     super
-    return unless @_bitmap
-    bitmapSize = @_bitmap.size
-    @_drawOptions.sourceArea = if @_sourceArea then @_sourceArea.mul(@_bitmap.pixelsPerPoint) else null
+    {currentBitmap} = @
+    return unless currentBitmap
+    bitmapSize = currentBitmap.size
+    @_drawOptions.sourceArea = if @_sourceArea then @_sourceArea.mul(currentBitmap.pixelsPerPoint) else null
     sourceSize = if @_drawOptions.sourceArea then @_drawOptions.sourceArea.size else bitmapSize
     sourceLoc = if @_drawOptions.sourceArea then @_drawOptions.sourceArea.location else point()
     {currentSize} = @
@@ -149,5 +160,13 @@
       else
         throw new Error "unknown mode: #{@_mode}"

+  @getter
+    currentBitmap: ->
+      return @_bitmap if @_bitmap
+      if @_altSources
+        for url in @_altSources
+          return loaded if loaded = sourceToBitmapCache.loaded url
+
   fillShape: (target, elementToTargetMatrix, options) ->
-    @_bitmap && target.drawBitmap @_bitmapToElementMatrix.mul(elementToTargetMatrix), @_bitmap, options
+    if bitmap = @getCurrentBitmap()
+      target.drawBitmap @_bitmapToElementMatrix.mul(elementToTargetMatrix), bitmap, options


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color2.coffee
+++ src/color2.coffee
@@ -240,7 +240,7 @@
           when g then hue = ((b - r) / chroma) + 2.0
           when b then hue = ((r - g) / chroma) + 4.0

-        hue *= 60.0
+        hue = (hue * 60.0).floor()

       return [hue, saturation, lightness, @a]


add queryParameters parse from rail

add queryParameters parse from rail

--- lib/add-tests.coffee
+++ lib/add-tests.coffee
@@ -36,16 +36,26 @@
   async.each raml.resources, (resource, callback) ->
     path = resource.relativeUri
     params = {}
+    query = {}
+    console.log('callback addtests' +'dir resoure :/n')
+    # console.log(JSON.stringify(resource, null, 2))
+    # console.log(JSON.stringify(resource.resources[0].methods, null, 2))

     # Apply parent properties
     if parent
       path = parent.path + path
       params = _.clone parent.params
+      queryparent = _.clone parent.query
+      console.log('parent query is :')
+      console.log(JSON.stringify(queryparent, null, 2))
+

     # Setup param
     if resource.uriParameters
+      console.dir('your uriParameters' + resource.uriParameters)
       for key, param of resource.uriParameters
         params[key] = param.example
+

     # In case of issue #8, resource does not define methods
     resource.methods ?= []
@@ -53,6 +63,19 @@
     # Iterate response method
     async.each resource.methods, (api, callback) ->
       method = api.method.toUpperCase()
+      # Setup query
+      if api.queryParameters
+
+        console.log('your methods.queryParameters is ' + api.queryParameters )
+        console.log(JSON.stringify(api.queryParameters, null, 2))
+
+        for qkey, qvalue of api.queryParameters
+          query[qkey] = qvalue.example
+          # console.log('qkey is :'  + 'qvalue is : qvalue.example' )
+        console.log('the queryParameters is:' + query )
+        console.log(JSON.stringify(query, null, 2))
+
+

       # Iterate response status
       for status, res of api.responses
@@ -77,6 +100,12 @@
           catch
             console.warn "cannot parse JSON example request body for #{test.name}"
         test.request.params = params
+        console.log('this test queryParameters is:' + query )
+        console.log(JSON.stringify(query, null, 2))
+        console.log(test.request)
+        test.request.query = query
+        console.log('this test queryParameters is:' + query )
+        console.log(JSON.stringify(query, null, 2))

         # Update test.response
         test.response.status = status


Use weaker sections component

Use weaker sections component

--- src/components/student-dashboard/progress-guide.cjsx
+++ src/components/student-dashboard/progress-guide.cjsx
@@ -10,7 +10,7 @@
 LearningGuideSection = require '../learning-guide/section'
 LearningGuideColorKey = require '../learning-guide/color-key'
 PracticeButton = require '../learning-guide/practice-button'
-
+WeakerSections = require '../learning-guide/weaker-sections'

 # Number of sections to display
 NUM_SECTIONS = 4
@@ -31,18 +31,16 @@
     courseId = @props.courseId
     guide = LearningGuide.Student.store.get(courseId)

-    sections = for section, i in _.first(LearningGuide.Student.store.getAllSections(courseId), NUM_SECTIONS)
-      <LearningGuideSection key={i}
-        section={section}
-        onPractice={@onPractice}
-        courseId={courseId} />
-

Performance Forecast

Recent topics

-        {_.first(sections, 4)}
+        <WeakerSections {...@props}
+          sections={LearningGuide.Student.store.getAllSections(courseId)}
+          weakerEmptyMessage="You haven't worked enough problems for Tutor to predict your weakest topics."
+          onPractice={@onPractice}
+        />

       <LearningGuideColorKey />
@@ -74,17 +72,20 @@

   render: ->
-    sections = LearningGuide.Student.store.getAllSections(@props.courseId)
-    return @renderEmpty() if _.isEmpty(sections)
+    sections = LearningGuide.Helpers.weakestSections(
+      LearningGuide.Student.store.getAllSections(@props.courseId)
+    )
+    canPractice = LearningGuide.Helpers.canPractice({sections})
+
+    return @renderEmpty() unless canPractice

         <ProgressGuide courseId={@props.courseId} />

-        <PracticeButton
-          title='Practice my weakest topics'
-          courseId={@props.courseId} />
+        <PracticeButton title='Practice my weakest topics'
+            courseId={@props.courseId} sections={sections} />

         <BS.Button
           onClick={@viewGuide}


[Completes #26378063] itemerge name: lookup via sid

--- lib/ephemeris.coffee
+++ lib/ephemeris.coffee
@@ -6,6 +6,7 @@
 cliff   = require("cliff")
 degrees = require("lin").degrees
 # FFI   = require("node-ffi/lib/ffi")
+

 class Ephemeris

@@ -81,6 +82,7 @@
     else if @settings.out is "phase"
       # this is a bit ugly because it's easier to not change the precious output
       # will need to at least add an input method to lin's itemerge (soon)
+      ensemble = new (require "lin").Ensemble
       ephemeris.stdout.on "data", (data) ->
         rpad = ' ' # pad on the right of each column (the values)
         labels =
@@ -92,8 +94,9 @@
         for i, group of json
           if i is "1" or i is "2"
             for id, it of group
+              item = ensemble.sid id
               objs.push
-                "what": id + rpad
+                "what": (if item.get('id') isnt '?' then item.get('name').white else id) + rpad
                 " ": if i is "2" then "+" else ""
               for key, val of it
                 label = labels[key] ? key


added some comments - about how Ephemeris is @run

--- lib/ephemeris.coffee
+++ lib/ephemeris.coffee
@@ -50,12 +50,32 @@
   run: (stream, treats) ->
     ephemeris = spawn "python", ["ephemeris.py", "#{JSON.stringify(@settings)}"]
                               , { cwd: __dirname + "/../node_modules/precious/lib" }
-    treats = @settings.out if @settings.out instanceof Array and not treats?
-
+
+    # The `treats` parameter means Massage will be applied to the data
+    # and contains the massage (sequence). These instructions can also come
+    # from `@settings.out` - provided it's an array.  The `eden` (cli)
+    # uses `@settings.out` - `String` or `Array` if `--out` asks for a sequence.
+    treats = @settings.out if not treats? _.isArray @settings.out
+
+    # This is probably an array of massage steps, unless
+    # a single step snuck in through the `treats` parameter.
+    # It's expected that Massage can handle the value(s) of `treats`.
     if treats?
       massage = new Massage treats
       massage.pipe ephemeris.stdout, stream, "ascii"

+    # The rest of these are special cases or else straight output of whatever
+    # precious returns.
+
+    # These are the non-array single massage steps for which json is assumed
+    # being implied for a starting point.  Basically a list of valid,
+    # single massage steps for a more readable json output.
+    else if _.include ["inspect", "indent"], @settings.out
+      massage = new Massage ["json", @settings.out]
+      massage.pipe ephemeris.stdout, stream, "ascii"
+
+    # The most readable output of `eden` and
+    # the default in the context of cli usage.
     else if @settings.out is "phase"
       # TODO: add points to the treats / massage.
       # This is just points presentation,
@@ -225,18 +245,15 @@
         else stream.write "Given no data."
         stream.write "\n\n"

-    else if _.include ["inspect", "indent"], @settings.out
-      massage = new Massage ["json", @settings.out]
-      massage.pipe ephemeris.stdout, stream, "ascii"
-
+    # Unprocessed - straight from *precious*, whatever didn't get caught above.
+    # For example `eden -o pprint`.
     else
       ephemeris.stdout.pipe stream

-    ephemeris.stderr.on "data", (data) ->
-      console.log data.toString("ascii")
+    # Special (error) cases.
+    ephemeris.stderr.on "data", (data) -> console.log data.toString("ascii")
     ephemeris.on "exit", (code) ->
-      if code isnt 0
-        console.log 'ephemeris exited with code ' + code;
+      if code isnt 0 then console.log 'ephemeris exited with code ' + code;

 module.exports = Ephemeris


Flash the next selected item

Flash the next selected item

--- lib/find-view.coffee
+++ lib/find-view.coffee
@@ -226,7 +226,7 @@
     return unless @markers?.length > 0

     if marker = @markers[markerIndex]
-      @findModel.getEditSession().setSelectedBufferRange marker.getBufferRange()
+      @findModel.getEditSession().setSelectedBufferRange(marker.getBufferRange(), autoscroll: true)

   setCurrentMarkerFromSelection: =>
     @currentResultMarker = null


Fixed all the things. Should work like a charm now

--- lib/d-struct.coffee
+++ lib/d-struct.coffee
@@ -1,32 +1,26 @@
 # Grab a few objects that we'll need
 popen = require('child_process').exec
 platform = require('os').platform
-path = atom.project.getPaths().shift()

-# TODO:--linux-support-still-needs-to-be-added-- Nope! All done
 commands =
     build:
-        win32: "start cmd /k \"cd #{path} && dub run\""
-        darwin: "osascript -e \'tell application \"Terminal\" to do script \"cd #{path}; dub run\"\'"
-        linux: "xterm -e \"bash -ic \'cd #{path} && dub run && read -s -n 1 line\'\""
+        win32: -> "start cmd /k \"cd #{atom.project.getPaths().shift()} && dub build\""
+        darwin: -> "osascript -e \'tell application \"Terminal\" to do script \"cd #{atom.project.getPaths().shift()}; dub build\"\'"
+        linux: -> "xterm -e \"bash -c \'cd #{atom.project.getPaths().shift()}; dub build; echo $?; read -s -n 1 line\'\""
     run:
-        win32: "start cmd /k \"cd \"#{path}\" && dub build\""
-        darwin: "osascript -e \'tell application \"Terminal\" to do script \"cd #{path}; dub build\"\'"
-        linux: "xterm -e \"bash -ic \'cd #{path} && dub build && read -s -n 1 line\'\""
+        win32: -> "start cmd /k \"cd \"#{atom.project.getPaths().shift()}\" && dub run\""
+        darwin: -> "osascript -e \'tell application \"Terminal\" to do script \"cd #{atom.project.getPaths().shift()}; dub run\"\'"
+        linux: -> "xterm -e \"bash -c \'cd #{atom.project.getPaths().shift()}; dub run; echo $?; read -s -n 1 line\'\""

 # Open a terminal and run dub
 dub_build = ->
-  # Make sure path is correct
-  path = atom.project.getPaths().shift()
   # Run the terminal app
-  popen commands.build[platform()] if platform() of commands.build
+  popen commands.build[platform()]() if platform() of commands.build

 # Open a terminal and run the app with dub
 dub_run = ->
-  # Make sure path is correct
-  path = atom.project.getPaths().shift()
   # Run the terminal app
-  popen commands.run[platform()] if platform() of commands.run
+  popen commands.run[platform()]() if platform() of commands.run

 module.exports =
   # Bind workspace command to run_dub


One more

One more

--- src/dedupe.coffee
+++ src/dedupe.coffee
@@ -18,7 +18,7 @@
     @atomPackagesDirectory = path.join(@atomDirectory, 'packages')
     @atomNodeDirectory = path.join(@atomDirectory, '.node-gyp')
     @atomNpmPath = require.resolve('npm/bin/npm-cli')
-    @atomNodeGypPath = require.resolve('node-gyp/bin/node-gyp')
+    @atomNodeGypPath = require.resolve('npm/node_modules/node-gyp/bin/node-gyp')

   parseOptions: (argv) ->
     options = yargs(argv).wrap(100)


debug

debug

--- src/device.coffee
+++ src/device.coffee
@@ -10,7 +10,9 @@
 fs = Promise.promisifyAll(require('fs'))

 exports.getUUID = getUUID = (appId) ->
-	knex('app').select('uuid').where({ appId }).get(0).get('uuid')
+	knex('app').select('uuid').where({ appId })
+	.tap(console.log)
+	.get(0).get('uuid')

 exports.getID = do ->
 	deviceIdPromises = {}


fix bug where applying list attributes would not propagate

--- src/editor.coffee
+++ src/editor.coffee
@@ -202,8 +202,7 @@
           offset -= (delta.start - index)
         else
           _.each(delta.attributes, (value, attr) =>
-            if delta.end - delta.start > 0
-              this.applyAttribute(index + offset, delta.end - delta.start, attr, value, false)
+            this.applyAttribute(index + offset, delta.end - delta.start, attr, value, false)
           )
         index = delta.end
       else
@@ -214,8 +213,8 @@
       this.deleteAt(delta.endLength, delta.startLength + offset - delta.endLength, false)

   applyLineAttribute: (line, attr, value) ->
-    value = 1 if value == true
     curIndent = if line.attributes[attr]? then line.attributes[attr] else 0
+    nextIndent = if _.isNumber(value) then value else (if value then 1 else 0)
     if _.indexOf(Tandem.Constants.LIST_ATTRIBUTES, attr, true) > -1
       lineNode = line.node
       expectedTag = if value then (if attr == 'list' then 'OL' else 'UL') else 'DIV'


course editor: assignment selections -- improved to allow listing everything

--- salvus/page/editor_course.cjsx
+++ salvus/page/editor_course.cjsx
@@ -54,7 +54,7 @@
 {Alert, Button, ButtonToolbar, ButtonGroup, Input, Row, Col,
     Panel, Popover, TabbedArea, TabPane, Well} = require('react-bootstrap')

-{ActivityDisplay, CloseX, DateTimePicker, ErrorDisplay, Help, Icon, LabeledRow, Loading, MarkdownInput,
+{ActivityDisplay, CloseX, DateTimePicker, DirectoryInput, ErrorDisplay, Help, Icon, LabeledRow, Loading, MarkdownInput,
     SaveButton, SearchInput, SelectorInput, TextInput, TimeAgo, Tip} = require('r_misc')

 {User} = require('users')
@@ -2138,9 +2138,9 @@
         if @state.add_searching # already searching
             return
         search = @state.add_search.trim()
-        if search.length == 0
-            @setState(err:undefined, add_select:undefined)
-            return
+        #if search.length == 0
+        #    @setState(err:undefined, add_select:undefined)
+        #    return
         @setState(add_searching:true, add_select:undefined)
         add_search = @state.add_search
         salvus_client.find_directories
@@ -2167,6 +2167,7 @@
                                 return true
                         return false
                     resp.directories = (path for path in resp.directories when not omit(path))
+                    resp.directories.sort()
                 @setState(add_searching:false, add_select:resp.directories)

     clear_and_focus_assignment_add_search_input : ->
@@ -2244,7 +2245,7 @@
                         <Input
                             ref         = 'assignment_add_input'
                             type        = 'text'
-                            placeholder = "Add assignment by folder name..."
+                            placeholder = "Add assignment by folder name (enter to see available folders)..."
                             value       = {@state.add_search}
                             buttonAfter = {@assignment_add_search_button()}
                             onChange    = {=>@setState(add_select:undefined, add_search:@refs.assignment_add_input.getValue())}


Adapt Script for tests in go If selected file ends with _test.go the executed command is go test in the same directory as the selected file

--- lib/grammars.coffee
+++ lib/grammars.coffee
@@ -137,7 +137,11 @@
   Go:
     "File Based":
       command: "go"
-      args: (context) -> ['run', context.filepath]
+        args: (context) ->
+          if context.filepath.match(/_test.go/)
+            ['test', '']
+          else ['run', context.filepath]
+      workingDirectory: atom.workspace.getActivePaneItem()?.buffer?.file?.getParent?().getPath?()

   Groovy:
     "Selection Based":


extracting Engine.Clear module

extracting Engine.Clear module

--- src/engine.coffee
+++ src/engine.coffee
@@ -3,9 +3,7 @@
     FPS: 30
     age: 0
     ambientLight: 1
-    backgroundColor: "#00010D"
     cameraTransform: Matrix.IDENTITY
-    clear: false
     excludedModules: []
     includedModules: []
     paused: false
@@ -162,13 +160,10 @@
     draw = ->
       return unless I.canvas

-      if I.clear
-        I.canvas.clear()
-      else if I.backgroundColor
-        I.canvas.fill(I.backgroundColor)
+      self.trigger "beforeDraw", canvas

       I.canvas.withTransform I.cameraTransform, (canvas) ->
-        self.trigger "beforeDraw", canvas
+

         if I.zSort
           drawObjects = I.objects.copy().sort (a, b) ->
@@ -343,10 +338,10 @@
       draw: draw
     }

-    self.attrAccessor "ambientLight", "backgroundColor", "cameraTransform", "clear"
+    self.attrAccessor "ambientLight", "cameraTransform"
     self.include Bindable

-    defaultModules = ["Keyboard", "Delay", "SaveState", "Selector", "Collision"]
+    defaultModules = ["Keyboard", "Clear", "Delay", "SaveState", "Selector", "Collision"]
     modules = defaultModules.concat(I.includedModules)
     modules = modules.without([].concat(I.excludedModules))


nicer handling of windows

nicer handling of windows

--- lib/php-debug.coffee
+++ lib/php-debug.coffee
@@ -72,7 +72,7 @@
         when PhpDebugWatchUri
           createWatchView(uri: PhpDebugWatchUri)
         when PhpDebugUnifiedUri
-          @window = @createUnifiedView(uri: PhpDebugUnifiedUri, context: @GlobalContext)
+          @createUnifiedView(uri: PhpDebugUnifiedUri, context: @GlobalContext)
     Dbgp = require './engines/dbgp/dbgp'
     @dbgp = new Dbgp(context: @GlobalContext, serverPort: atom.config.get('php-debug.ServerPort'))
     @GlobalContext.onBreak (breakpoint) =>
@@ -122,11 +122,15 @@
     marker = editor.markBufferRange(range)

   toggleDebugging: ->
-    if not @window
+    pane = atom.workspace.paneForItem(@unifiedWindow)
+    if !pane
       @showWindows()
-      @dbgp.listen()
+      if !@dbgp.listening()
+        @dbgp.listen()
     else
-      console.dir @window
+      pane.destroy()
+      delete @unifiedWindow
+      @dbgp.close()

   run: ->
     if @GlobalContext.getCurrentDebugContext()
@@ -156,6 +160,8 @@
     editor = atom.workspace.getActivePane()
     editor.splitDown()
     atom.workspace.open(PhpDebugUnifiedUri)
+      .then (unifiedWindow) =>
+        @unifiedWindow = unifiedWindow

   toggleBreakpoint: ->
     editor = atom.workspace.getActivePaneItem()


follow docs

follow docs

--- src/follow.coffee
+++ src/follow.coffee
@@ -1,7 +1,7 @@
 ###*
 The Follow module provides a simple method to set an object's
 velocity so that it will approach another object. This method
-relies on the objects having `bounds` method.
+relies on the objects having `bounds` methods.

 player = GameObject


update when supply airplane

update when supply airplane

--- views/components/main/parts/resource-panel.cjsx
+++ views/components/main/parts/resource-panel.cjsx
@@ -94,6 +94,12 @@
         material[4] = body.api_after_bauxite
         @setState
           material: material
+      when '/kcsapi/api_req_air_corps/supply'
+        {material} = @state
+        material[1] = body.api_after_fuel
+        material[4] = body.api_after_bauxite
+        @setState
+          material: material
   componentDidMount: ->
     window.addEventListener 'game.response', @handleResponse
     window.addEventListener 'view.main.visible', @handleVisibleResponse


resolve names without dependencies a bit faster

--- src/hinoki.coffee
+++ src/hinoki.coffee
@@ -115,7 +115,11 @@
     # this code is reached synchronously from the start of the function call
     # without interleaving.

-    dependenciesPromise = hinoki.get remainingContainers, dependencyPaths, debug
+    dependenciesPromise =
+      if dependencyPaths.length isnt 0
+        hinoki.get remainingContainers, dependencyPaths, debug
+      else
+        Promise.resolve([])

     factoryCallResultPromise = dependenciesPromise.then (dependencyValues) ->


abbreviate path names, hover to see the full path

--- lib/rendering.coffee
+++ lib/rendering.coffee
@@ -1,4 +1,4 @@
-
+path = require('path')

 row = (caption, content) ->
   cap = if caption then "#{caption}" else " "
@@ -224,9 +224,10 @@
   msgh = "
#{error.msg}
"
   # line,char
   uri = "#{error.file}:#{error.line},#{error.char}"
+  abbrev = path.basename(error.file)
   file = """

-      in file #{error.file}:#{error.line},#{error.char}
+      in file #{abbrev}:#{error.line},#{error.char}

   """
   ret = """


Makes changes per @kevinsawicki

Makes changes per @kevinsawicki

* Deconstruct event paramaters
* Scope `right-clicked` class removal
* Use `?=` operator in Coffeescript to check if tab is null
* Check `classList` property on mousedown target for .close-icon

--- lib/tab-bar-view.coffee
+++ lib/tab-bar-view.coffee
@@ -8,7 +8,7 @@
     @ul tabindex: -1, class: "list-inline tab-bar inset-panel"

   initialize: (@pane) ->
-    @command 'tabs:close-tab', => @closeTab(true)
+    @command 'tabs:close-tab', => @closeTab()
     @command 'tabs:close-other-tabs', => @closeOtherTabs()
     @command 'tabs:close-tabs-to-right', => @closeTabsToRight()

@@ -42,13 +42,13 @@

     @updateActiveTab()

-    @on 'mousedown', '.tab', (e) =>
-      tab = $(e.target).closest('.tab')
+    @on 'mousedown', '.tab', ({target, which, ctrlKey}) =>
+      tab = $(target).closest('.tab')
       view = tab.view()
-      if e.which is 3 or (e.which is 1 and e.ctrlKey is true)
-        $('.right-clicked').removeClass('right-clicked')
+      if which is 3 or (which is 1 and ctrlKey is true)
+        @find('.right-clicked').removeClass('right-clicked')
         tab.addClass('right-clicked')
-      else if e.which is 1 and $(e.target).attr('class') isnt 'close-icon'
+      else if which is 1 and not target.classList.contains('close-icon')
         @pane.showItem(view.item)
         @pane.focus()

@@ -57,8 +57,8 @@
         @pane.trigger('application:new-file')
         false

-    @on 'click', '.tab .close-icon', (e) =>
-      tab = $(e.target).closest('.tab').view()
+    @on 'click', '.tab .close-icon', ({target}) =>
+      tab = $(target).closest('.tab').view()
       @pane.destroyItem(tab.item)
       false

@@ -108,7 +108,7 @@
     @setActiveTab(@tabForItem(@pane.activeItem))

   closeTab: (tab) ->
-    if tab is true then tab = @children('.right-clicked').view()
+    tab ?= @children('.right-clicked').view()
     @pane.destroyItem(tab.item)

   closeOtherTabs: ->


Client: Sidebar: Updated renderLeaveSharedMachine method

--- client/app/lib/components/sidebarmachineslistitem/index.coffee
+++ client/app/lib/components/sidebarmachineslistitem/index.coffee
@@ -143,18 +143,13 @@

   renderLeaveSharedMachine: ->

-    return null  unless @state.showLeaveSharedMachineWidget
+    return null  if @props.activeLeavingSharedMachineId isnt @machine('_id')
     return null  if @machine('type') is 'own'
     return null  unless @machine 'isApproved'

     <LeaveSharedMachineWidget
       coordinates={@state.coordinates}
-      machine={@props.machine}
-      isOpened={@state.showLeaveSharedMachineWidget}
-      onClose={@bound 'handlePopoverOnClose'}
-      />
-
-
+      machine={@props.machine} />

   renderMachineSettingsIcon: ->


sidebar: open workspaces modal on workspaces title click

--- client/app/lib/components/sidebarmachineslistitem/index.coffee
+++ client/app/lib/components/sidebarmachineslistitem/index.coffee
@@ -1,8 +1,11 @@
-kd          = require 'kd'
-React       = require 'kd-react'
-classnames  = require 'classnames'
-toImmutable = require 'app/util/toImmutable'
-Link        = require 'app/components/common/link'
+kd                  = require 'kd'
+React               = require 'kd-react'
+classnames          = require 'classnames'
+toImmutable         = require 'app/util/toImmutable'
+Link                = require 'app/components/common/link'
+Machine             = require 'app/providers/machine'
+remote              = require('app/remote').getInstance()
+MoreWorkspacesModal = require 'app/activity/sidebar/moreworkspacesmodal'

 module.exports = class SidebarMachinesListItem extends React.Component

@@ -69,7 +72,7 @@
     return null  if @state.collapsed

-
WORKSPACES

+
WORKSPACES

       {@renderWorkspaces()}

@@ -96,3 +99,28 @@
       {@renderWorkspaceSection()}

+
+  #
+  # LEGACY METHODS
+  #
+
+  handleWorkspacesTitleClick: (event) ->
+
+    { computeController } = kd.singletons
+
+    status  = @machine ['status', 'state']
+    users   = (@machine 'users').toJS()
+    isOwner = yes for user in users when user.owner
+
+    return  unless isOwner
+    return  unless status is Machine.State.Running
+
+    workspaces = []
+    @props.machine.get('workspaces').map (ws) ->
+      workspaces.push remote.revive ws.toJS()
+    modal = new MoreWorkspacesModal {}, workspaces
+
+    # TODO: handle new workspace creation
+    # modal.once 'NewWorkspaceRequested', @bound 'createAddWorkspaceInput'
+
+


:memo: Add settings descripion

:memo: Add settings descripion

--- lib/pigments.coffee
+++ lib/pigments.coffee
@@ -14,9 +14,11 @@
         '**/*.sass'
         '**/*.scss'
       ]
+      description: "Glob patterns of files to scan for variables."
     ignoredNames:
       type: 'array'
       default: []
+      description: "Glob patterns of files to ignore when scanning the project for variables."
     markerType:
       type: 'string'
       default: 'background'


Spawn explorer.exe directly

Spawn explorer.exe directly

Previously this was wrapping the command in cmd.exe which appears to be
no longer necessary now that BufferedProcess wraps all commands in a
cmd.exe

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -330,9 +330,9 @@
         label: 'Finder'
         args: ['-R', pathToOpen]
       when 'win32'
-        command: 'cmd.exe'
+        command: 'explorer.exe'
         label: 'Explorer'
-        args: ['/c', 'start', 'explorer.exe', pathToOpen]
+        args: [pathToOpen]
       else
         # Strip the filename from the path to make sure we pass a directory
         # path. If we pass xdg-open a file path, it will open that file in the
@@ -353,10 +353,17 @@
     errorLines = []
     stderr = (lines) -> errorLines.push(lines)
     exit = (code) ->
-      if code isnt 0
+      failed = code isnt 0
+      error = errorLines.join('\n')
+
+      # Windows 8 seems to return a 1 with no error output even on success
+      if process.platform is 'win32' and code is 1 and not error
+        failed = false
+
+      if failed
         atom.confirm
           message: "Opening #{if isFile then 'file' else 'folder'} in #{label} failed"
-          detailedMessage: errorLines.join('\n')
+          detailedMessage: error
           buttons: ['OK']

     new BufferedProcess({command, args, stderr, exit})


Add removeDiacritics to the provider

Add removeDiacritics to the provider

When the list of words is built, what is added is the version of the author name
with the diacritics replaces by ASCII.

--- lib/provider.coffee
+++ lib/provider.coffee
@@ -3,6 +3,7 @@
 XRegExp = require('xregexp').XRegExp
 titlecaps = require "./titlecaps"
 yaml = require "yaml-js"
+removeDiacritics = require('diacritics').remove

 module.exports =
 class referencesProvider
@@ -152,9 +153,9 @@
           for author in citation.author
             new_word = (JSON.parse(JSON.stringify(template)));
             if author.family?
-              new_word.author = author.family
+              new_word.author = removeDiacritics(author.family)
             if author.litteral?
-              new_word.author = author.litteral
+              new_word.author = removeDiacritics(author.litteral)

             possibleWords.push new_word


Refactor entering/leaving insert mode

Refactor entering/leaving insert mode

--- lib/vim-state.coffee
+++ lib/vim-state.coffee
@@ -265,29 +265,34 @@
   #
   # Returns nothing.
   activateCommandMode: ->
+    @deactivateInsertMode()
+    @deactivateVisualMode()
     @mode = 'command'
-    @submode = null

     if @editorView.is(".insert-mode")
       cursor = @editor.getCursor()
       cursor.moveLeft() unless cursor.isAtBeginningOfLine()

-    @editorView.removeClass('insert-mode visual-mode')
     @editorView.addClass('command-mode')
     @editor.clearSelections()

     @editorView.on 'cursor:position-changed', @moveCursorBeforeNewline

+  deactivateCommandMode: ->
+    @editorView.off 'cursor:position-changed', @moveCursorBeforeNewline
+    @editorView.removeClass('command-mode')
+
   # Private: Used to enable insert mode.
   #
   # Returns nothing.
   activateInsertMode: ->
+    @deactivateCommandMode()
+    @deactivateVisualMode()
     @mode = 'insert'
-    @submode = null
-    @editorView.removeClass('command-mode visual-mode')
     @editorView.addClass('insert-mode')

-    @editorView.off 'cursor:position-changed', @moveCursorBeforeNewline
+  deactivateInsertMode: ->
+    @editorView.removeClass('insert-mode')

   # Private: Used to enable visual mode.
   #
@@ -295,14 +300,18 @@
   #
   # Returns nothing.
   activateVisualMode: (type) ->
+    @deactivateInsertMode()
+    @deactivateCommandMode()
     @mode = 'visual'
     @submode = type
-    @editorView.removeClass('command-mode insert-mode')
     @editorView.addClass('visual-mode')
-    @editor.off 'cursor:position-changed', @moveCursorBeforeNewline

     if @submode == 'linewise'
       @editor.selectLine()
+
+  deactivateVisualMode: ->
+    @editorView.removeClass('visual-mode')
+    @submode = null

   # Private: Resets the command mode back to it's initial state.
   #


#395 fix bug and deactivate manually by escape

--- lib/vim-state.coffee
+++ lib/vim-state.coffee
@@ -12,6 +12,7 @@
   getVisibleBufferRange
   matchScopes
   isRangeContainsSomePoint
+  getWordPatternAtCursor

   debug
 } = require './utils'
@@ -247,6 +248,8 @@
       unless @editor.hasMultipleCursors()
         @clearRangeMarkers() if settings.get('clearRangeMarkerOnResetNormalMode')
         @main.clearHighlightSearchForEditors() if settings.get('clearHighlightSearchOnResetNormalMode')
+        @deactivatePresetOccurrenceMode() if @hasPresetOccurrence()
+
     @editor.clearSelections()
     @activate('normal')

@@ -370,23 +373,32 @@

   # Occurrence request for next operation
   # -------------------------
+  hasPresetOccurrence: ->
+    @presetOccurrenceDeactivator?
+
+  deactivatePresetOccurrenceMode: (clearMarkers=true) ->
+    @presetOccurrenceDeactivator?.dispose()
+    @presetOccurrenceDeactivator = null
+
+    @editorElement.classList.remove("occurrence-preset")
+    @operationStack.clearOccurrenceMarkers() if clearMarkers
+
+  presetOccurrenceDeactivator: null
   presetOccurrence: (pattern=null) ->
     if not pattern? and @isMode('visual') and text = @editor.getSelectedText()
       pattern = new RegExp(_.escapeRegExp(text), 'g')
       @activate('normal')

+    pattern ?= getWordPatternAtCursor(@editor.getLastCursor(), singleNonWordChar: true)
+
     @editorElement.classList.add("occurrence-preset")

-    disposable = null
-    presetOccurrenceIfPossible = (operation) =>
+    @presetOccurrenceDeactivator = @emitter.on 'did-push-operation', (operation) =>
       if operation.isOperator() and operation.canAcceptPresetOccurrence()
-        disposable.dispose()
-        @editorElement.classList.remove("occurrence-preset")
+        @deactivatePresetOccurrenceMode(false)
+        @operationStack.clearOccurrenceMarkersOnReset()
         operation.patternForOccurence = pattern
         operation.occurrence = true
-        @operationStack.clearOccurrenceMarkersOnReset()
-
-    disposable = @emitter.on('did-push-operation', presetOccurrenceIfPossible)

     # refresh
     @operationStack.clearOccurrenceMarkers()


un-fat arrow

un-fat arrow

--- lib/searcher.coffee
+++ lib/searcher.coffee
@@ -41,7 +41,7 @@
     get: ->
       @_range ?= @getRange()

-  getRange: =>
+  getRange: ->
     if @useRegex
       # FIXME: Maybe because of BUG of ag?
       # when I search \) in regexp, it find next line of line which ends with `)`.


Only check panes in center

Only check panes in center

atom/atom#13977 adds multiple pane locations and updates the
`atom.workspace.getPanes()` API to return the panes from all of them.
This fixes the resultant test failures by only checking the panes in
the center.

--- spec/fuzzy-finder-spec.coffee
+++ spec/fuzzy-finder-spec.coffee
@@ -18,6 +18,9 @@
   else
     fs.unlinkSync(_path)

+# TODO: Remove this after atom/atom#13977 lands in favor of unguarded `getCenter()` calls
+getCenter = -> atom.workspace.getCenter?() ? atom.workspace
+
 describe 'FuzzyFinder', ->
   [rootDir1, rootDir2] = []
   [fuzzyFinder, projectView, bufferView, gitStatusView, workspaceElement, fixturesPath] = []
@@ -792,7 +795,7 @@

   describe "opening a path into a split", ->
     it "opens the path by splitting the active editor left", ->
-      expect(atom.workspace.getPanes().length).toBe 1
+      expect(getCenter().getPanes().length).toBe 1
       pane = atom.workspace.getActivePane()
       filePath = null

@@ -804,18 +807,18 @@
         atom.commands.dispatch bufferView.element, 'pane:split-left'

       waitsFor ->
-        atom.workspace.getPanes().length is 2
+        getCenter().getPanes().length is 2

       waitsFor ->
         atom.workspace.getActiveTextEditor()

       runs ->
-        [leftPane, rightPane] = atom.workspace.getPanes()
+        [leftPane, rightPane] = getCenter().getPanes()
         expect(atom.workspace.getActivePane()).toBe leftPane
         expect(atom.workspace.getActiveTextEditor().getPath()).toBe atom.project.getDirectories()[0].resolve(filePath)

     it "opens the path by splitting the active editor right", ->
-      expect(atom.workspace.getPanes().length).toBe 1
+      expect(getCenter().getPanes().length).toBe 1
       pane = atom.workspace.getActivePane()
       filePath = null

@@ -827,18 +830,18 @@
         atom.commands.dispatch bufferView.element, 'pane:split-right'

       waitsFor ->
-        atom.workspace.getPanes().length is 2
+        getCenter().getPanes().length is 2

       waitsFor ->
         atom.workspace.getActiveTextEditor()

       runs ->
-        [leftPane, rightPane] = atom.workspace.getPanes()
+        [leftPane, rightPane] = getCenter().getPanes()
         expect(atom.workspace.getActivePane()).toBe rightPane
         expect(atom.workspace.getActiveTextEditor().getPath()).toBe atom.project.getDirectories()[0].resolve(filePath)

     it "opens the path by splitting the active editor up", ->
-      expect(atom.workspace.getPanes().length).toBe 1
+      expect(getCenter().getPanes().length).toBe 1
       pane = atom.workspace.getActivePane()
       filePath = null

@@ -850,18 +853,18 @@
         atom.commands.dispatch bufferView.element, 'pane:split-up'

       waitsFor ->
-        atom.workspace.getPanes().length is 2
+        getCenter().getPanes().length is 2

       waitsFor ->
         atom.workspace.getActiveTextEditor()

       runs ->
-        [topPane, bottomPane] = atom.workspace.getPanes()
+        [topPane, bottomPane] = getCenter().getPanes()
         expect(atom.workspace.getActivePane()).toBe topPane
         expect(atom.workspace.getActiveTextEditor().getPath()).toBe atom.project.getDirectories()[0].resolve(filePath)

     it "opens the path by splitting the active editor down", ->
-      expect(atom.workspace.getPanes().length).toBe 1
+      expect(getCenter().getPanes().length).toBe 1
       pane = atom.workspace.getActivePane()
       filePath = null

@@ -873,13 +876,13 @@
         atom.commands.dispatch bufferView.element, 'pane:split-down'

       waitsFor ->
-        atom.workspace.getPanes().length is 2
+        getCenter().getPanes().length is 2

       waitsFor ->
         atom.workspace.getActiveTextEditor()

       runs ->
-        [topPane, bottomPane] = atom.workspace.getPanes()
+        [topPane, bottomPane] = getCenter().getPanes()
         expect(atom.workspace.getActivePane()).toBe bottomPane
         expect(atom.workspace.getActiveTextEditor().getPath()).toBe atom.project.getDirectories()[0].resolve(filePath)


Fix specs on windows

Fix specs on windows

--- spec/fuzzy-finder-spec.coffee
+++ spec/fuzzy-finder-spec.coffee
@@ -59,7 +59,7 @@
             expect(finderView.list.children().first()).toHaveClass 'selected'
             expect(finderView.find(".loading")).not.toBeVisible()

-        describe "symlinks", ->
+        describe "symlinks on #darwin or #linux", ->
           beforeEach ->
             fs.symlinkSync(project.resolve('sample.txt'), project.resolve('symlink-to-file'))
             fs.symlinkSync(project.resolve('dir'), project.resolve('symlink-to-dir'))
@@ -191,7 +191,7 @@
           paths = [ 'sample-with-tabs.coffee', 'sample.txt', 'sample.js' ]

           for [time, bufferPath] in states
-            expect(_.last bufferPath.split '/').toBe paths.shift()
+            expect(_.last bufferPath.split path.sep).toBe paths.shift()
             expect(time).toBeGreaterThan 50000

       describe "when there are only panes with anonymous items", ->


Drop support for Python 2.6

Drop support for Python 2.6

--- lib/wakatime.coffee
+++ lib/wakatime.coffee
@@ -287,7 +287,7 @@
         '/usr/bin/python',
       ]
       i = 39
-      while i >= 26
+      while i >= 27
         locations.push '\\python' + i + '\\pythonw'
         locations.push '\\Python' + i + '\\pythonw'
         i--


commented out invite by username. will be re-enabled once we have a proper solution to prevent spam

--- client/landing/app/Applications/Groups.kdapplication/Views/groupsinvitationrequestsview.coffee
+++ client/landing/app/Applications/Groups.kdapplication/Views/groupsinvitationrequestsview.coffee
@@ -21,10 +21,10 @@
       title    : 'Invite by Email'
       cssClass : 'clean-gray'
       callback : @bound 'showInviteByEmailModal'
-    @inviteByUsernameButton = new KDButtonView
-      title    : 'Invite by Username'
-      cssClass : 'clean-gray'
-      callback : @bound 'showInviteByUsernameModal'
+    # @inviteByUsernameButton = new KDButtonView
+    #   title    : 'Invite by Username'
+    #   cssClass : 'clean-gray'
+    #   callback : @bound 'showInviteByUsernameModal'
     @batchApproveButton = new KDButtonView
       title    : 'Batch Approve Requests'
       cssClass : 'clean-gray'
@@ -205,43 +205,43 @@

     return modal

-  showInviteByUsernameModal:->
-    @inviteByUsername = @showModalForm
-      cssClass         : 'invite-by-username'
-      title            : 'Invite by Username'
-      callback         : @emit.bind @, 'InviteByUsername'
-      fields           :
-        recipient      :
-          label        : 'Username'
-          type         : 'hidden'
-
-    recipientField = @inviteByUsername.modalTabs.forms.invite.fields.recipient
-    recipientsWrapper = new KDView
-      cssClass: 'completed-items'
-
-    @inviteByUsername.on 'AutoCompleteNeedsMemberData', (event)=>
-      {callback,blacklist,inputValue} = event
-      @fetchBlacklistForInviteByUsernameModal (ids)->
-        blacklist.push id for id in ids
-        KD.remote.api.JAccount.byRelevance inputValue, {blacklist}, (err,accounts)->
-          callback accounts
-
-    recipient = new KDAutoCompleteController
-      name                : 'recipient'
-      itemClass           : InviteByUsernameAutoCompleteItemView
-      selectedItemClass   : InviteByUsernameAutoCompletedItemView
-      outputWrapper       : recipientsWrapper
-      form                : @inviteByUsername.modalTabs.forms.invite
-      itemDataPath        : 'profile.nickname'
-      listWrapperCssClass : 'users'
-      submitValuesAsText  : yes
-      dataSource          : (args, callback)=>
-        {inputValue} = args
-        blacklist = (data.getId() for data in recipient.getSelectedItemData())
-        @inviteByUsername.emit 'AutoCompleteNeedsMemberData', {inputValue,blacklist,callback}
-
-    recipientField.addSubView recipient.getView()
-    recipientField.addSubView recipientsWrapper
+  # showInviteByUsernameModal:->
+  #   @inviteByUsername = @showModalForm
+  #     cssClass         : 'invite-by-username'
+  #     title            : 'Invite by Username'
+  #     callback         : @emit.bind @, 'InviteByUsername'
+  #     fields           :
+  #       recipient      :
+  #         label        : 'Username'
+  #         type         : 'hidden'
+
+  #   recipientField = @inviteByUsername.modalTabs.forms.invite.fields.recipient
+  #   recipientsWrapper = new KDView
+  #     cssClass: 'completed-items'
+
+  #   @inviteByUsername.on 'AutoCompleteNeedsMemberData', (event)=>
+  #     {callback,blacklist,inputValue} = event
+  #     @fetchBlacklistForInviteByUsernameModal (ids)->
+  #       blacklist.push id for id in ids
+  #       KD.remote.api.JAccount.byRelevance inputValue, {blacklist}, (err,accounts)->
+  #         callback accounts
+
+  #   recipient = new KDAutoCompleteController
+  #     name                : 'recipient'
+  #     itemClass           : InviteByUsernameAutoCompleteItemView
+  #     selectedItemClass   : InviteByUsernameAutoCompletedItemView
+  #     outputWrapper       : recipientsWrapper
+  #     form                : @inviteByUsername.modalTabs.forms.invite
+  #     itemDataPath        : 'profile.nickname'
+  #     listWrapperCssClass : 'users'
+  #     submitValuesAsText  : yes
+  #     dataSource          : (args, callback)=>
+  #       {inputValue} = args
+  #       blacklist = (data.getId() for data in recipient.getSelectedItemData())
+  #       @inviteByUsername.emit 'AutoCompleteNeedsMemberData', {inputValue,blacklist,callback}
+
+  #   recipientField.addSubView recipient.getView()
+  #   recipientField.addSubView recipientsWrapper

   showInviteByEmailModal:->
     @inviteByEmail = @showModalForm
@@ -292,7 +292,7 @@
     """

       {{> @createMultiuseButton}} {{> @batchApproveButton}}
-      {{> @inviteByEmailButton}} {{> @inviteByUsernameButton}}
+      {{> @inviteByEmailButton}}

Status quo


Make the dub commands play nice with Windows.

* The dub build/run commands are now compatible with Windows as well as OS X.
* Fix code to get project path.
* Remove the path import since it's not needed.

--- lib/d-struct.coffee
+++ lib/d-struct.coffee
@@ -1,19 +1,25 @@
 # Grab a few objects that we'll need
 popen = require('child_process').exec
-path = require('path')
 platform = require('os').platform
+
+# TODO: linux support still needs to be added
+commands =
+    build:
+        win32: "start cmd /k \"cd #{atom.project.getPaths().shift()} && dub run\""
+        darwin: "osascript -e \'tell application \"Terminal\" to do script \"cd #{atom.project.getPaths().shift()}; dub run\"\'"
+    run:
+        win32: "start cmd /k \"cd \"#{atom.project.getPaths().shift()}\" && dub build\""
+        darwin: "osascript -e \'tell application \"Terminal\" to do script \"cd #{atom.project.getPaths().shift()}; dub build\"\'"

 # Open a terminal and run dub
 dub_build = ->
   # Run the terminal app
-  cmdline = "osascript -e \'tell application \"Terminal\" to do script \"cd #{atom.project.path}; dub build\"\'"
-  popen cmdline
+  popen commands.build[platform()] if platform() of commands.build

 # Open a terminal and run the app with dub
 dub_run = ->
   # Run the terminal app
-  cmdline = "osascript -e \'tell application \"Terminal\" to do script \"cd #{atom.project.path}; dub run\"\'"
-  popen cmdline
+  popen commands.run[platform()] if platform() of commands.run

 module.exports =
   # Bind workspace command to run_dub


server websockets connected/disconnected events handling

--- components/notifications/KDNotificationView.coffee
+++ components/notifications/KDNotificationView.coffee
@@ -33,13 +33,18 @@

   #OWN METHODS
   notificationSetDefaults:(options)->
-    options.duration      = options.duration ? 1500
-    options.closeManually = yes if options.duration > 2999 or options.duration is 0
-    options
+    options.duration      ?= 1500
+    if options.duration > 2999 or options.duration is 0
+      options.closeManually ?= yes
+    return options

   notificationSetTitle:(title)->
+    unless title instanceof KDView
+      @$().find(".kdnotification-title").html title
+    else
+      @notificationTitle.destroy() if @notificationTitle and @notificationTitle instanceof KDView
+      @addSubView title, ".kdnotification-title"
     @notificationTitle = title
-    @getDomElement().find(".kdnotification-title").html title

   notificationSetType:(type = "main")->
     @notificationType = type


Bug fix on isSimulation

Bug fix on isSimulation

--- lib/messages.coffee
+++ lib/messages.coffee
@@ -166,7 +166,7 @@
   message.id = id
   diffid = MessagesDiff.insert message
   message._id = diffid
-  notifyMessageUpdate message unless Meteor.isSimulation
+  notifyMessageUpdate message unless @isSimulation
   diffid

 _messageParent = (child, parent, position = null, oldParent = true, importing = false) ->


Avoid ??? in title

Avoid ??? in title

--- lib/messages.coffee
+++ lib/messages.coffee
@@ -2,7 +2,7 @@

 @titleOrUntitled = (title) ->
   unless title?
-    '???'
+    title #'???'
   else if title.trim().length == 0
     untitledMessage
   else


Preventing buttons in header from changing expand state.

--- web/coffee/management-problems.coffee
+++ web/coffee/management-problems.coffee
@@ -244,8 +244,8 @@
     .done @props.onProblemChange

   handleExpand: (e) ->
-    e.preventDefault()
-    @setState {expanded: !@state.expanded}
+    if !$(e.target).hasClass "btn"
+      @setState {expanded: !@state.expanded}

   render: ->


Remove logs to console

Remove logs to console

--- lib/provider.coffee
+++ lib/provider.coffee
@@ -52,7 +52,6 @@
                 type: "value"
                 iconHTML: '<i class="icon-mortar-board"></i>'
               }
-              console.log suggestion
               suggestions = suggestions.concat suggestion
             resolve(suggestions)

@@ -105,7 +104,6 @@
             by: "#{citation.entryTags.prettyAuthors}"
           }

-    console.log possibleWords
     @possibleWords = possibleWords

   buildWordListFromFiles: (referenceFiles) =>
@@ -145,7 +143,6 @@
         else
           console.warn("'#{file}' does not appear to be a file, so autocomplete-bibtex will not try to parse it.")

-      console.log references
       @references = references
     catch error
       console.error error


Fixes #90. Fix unsafe-eval error for Jade previewing

--- lib/renderer.coffee
+++ lib/renderer.coffee
@@ -92,7 +92,7 @@
       exts: /\.(less)$/i
     'Jade':
       render: (text, filepath, cb) ->
-        jade = require 'jade'
+        jade = allowUnsafeNewFunction -> allowUnsafeEval -> require 'jade'
         options = {
           pretty: true
         }


Adding support for Aglio's "include" directive for incorporating multiple files into a single API Blueprint document.

NOTE: This is dependent on the incorporation and release of https://github.com/danielgtaylor/aglio/pull/165.

--- lib/renderer.coffee
+++ lib/renderer.coffee
@@ -30,7 +30,8 @@
   npm_bin = atom.project.getPaths().map (p) -> path.join(p, 'node_modules', '.bin')
   env.PATH = npm_bin.concat(env.PATH, '/usr/local/bin').join(path.delimiter)
   template = "#{path.dirname __dirname}/templates/api-blueprint-preview.jade"
-  exec "aglio -i /tmp/atom.apib -t #{template} -o -", {env, options}, (err, stdout, stderr) =>
+  includePath = path.dirname filePath # for Aglio include directives
+  exec "aglio -i #{tempFile} -t #{template} -n #{includePath} -o -", {env, options}, (err, stdout, stderr) =>
     if err then return callback(err)
     console.log stderr
     callback null, resolveImagePaths(stdout)


add where method to resources

add where method to resources

--- lib/resource.coffee
+++ lib/resource.coffee
@@ -62,6 +62,13 @@
     @member_route.member = true
     this

+  where: ( conditions )->
+    if kindof(conditions) != 'object'
+      throw new Error 'conditions must be an object'
+    # recursively apply all conditions to sub-parts
+    route.where? conditions for route in @routes
+    this # chainable
+
   collection: ( cb )->
     @collection_route.nest cb
     this # for chaining
@@ -79,6 +86,16 @@
 # Helper methods
 # =============================================

+# better than typeof
+kindof = ( o )->
+  switch
+    when typeof o != "object"     then typeof o
+    when o == null                then "null"
+    when o.constructor == Array   then "array"
+    when o.constructor == Date    then "date"
+    when o.constructor == RegExp  then "regex"
+    else "object"
+
 # builds route names
 # TODO: clean this up
 nomenclate = ->


Social: added Khaled's account as dummy admin

--- workers/social/lib/social/models/account.coffee
+++ workers/social/lib/social/models/account.coffee
@@ -659,7 +659,7 @@

   dummyAdmins = [ 'sinan', 'devrim', 'gokmen', 'fatihacet',
                   'sent-hil', 'cihangirsavas', 'leeolayvar', 'stefanbc',
-                  'szkl', 'nitin', 'usirin', 'kodinglearn', 'rjeczalik' ] # kodinglearn is nitin's impersonation account
+                  'szkl', 'nitin', 'usirin', 'kodinglearn', 'rjeczalik', 'caikoding'] # kodinglearn is nitin's impersonation account

   isEmailVerified: (callback) ->


KiteSubscription: add method signatures and relationships to account model

--- workers/social/lib/social/models/account.coffee
+++ workers/social/lib/social/models/account.coffee
@@ -259,6 +259,8 @@
           (signature Object, Function)
         likeMember:
           (signature String, Function)
+        fetchKites :
+          (signature Object, Function)

     schema                  :
       skillTags             : [String]
@@ -431,6 +433,14 @@
         as          : 'badge'
         targetType  : 'JBadge'

+      kite          :
+        as          : 'owner'
+        targetType  : JKite
+
+      kiteSubscription :
+        as             : 'kiteSubscription'
+        targetType     : JKitePlan
+
   constructor:->
     super
     @notifyOriginWhen 'PrivateMessageSent', 'FollowHappened'


Implement emails by username fetcher method

--- workers/social/lib/social/models/account.coffee
+++ workers/social/lib/social/models/account.coffee
@@ -91,6 +91,8 @@
         verifyEmailByUsername:
           (signature String, Function)
         fetchBlockedUsers:
+          (signature Object, Function)
+        fetchEmailsByUsername:
           (signature Object, Function)

       instance:
@@ -1120,6 +1122,28 @@
     else
       callback new KodingError 'Access denied'

+  @fetchEmailsByUsername = permit 'grant permissions',
+    success: (client, usernames = [], callback) ->
+      return callback null, []  unless usernames.length
+
+      selector = { username: { $in: usernames } }
+      options  = { email: 1, username: 1 }
+
+      JUser = require './user'
+      JUser.someData selector, options, (err, cursor) ->
+        return callback err  if err
+
+        cursor.toArray (err, list) ->
+          return callback err  if err
+
+          data = {}
+
+          for item in list
+            { email, username } = item
+            data[username] = email
+
+          callback null, data
+
   fetchDecoratedPaymentMethods: (callback) ->
     JPaymentMethod = require './payment/method'
     @fetchPaymentMethods (err, paymentMethods) ->
@@ -1385,5 +1409,3 @@

       user.update $set: twofactorkey: key, (err) ->
         callback err
-
-


fixed deprecated invitation code for migrating kodingen users. this code has been created on koding.com with maximum usage 50.000

--- workers/social/lib/social/models/olduser.coffee
+++ workers/social/lib/social/models/olduser.coffee
@@ -137,7 +137,7 @@
               passwordConfirm : pass
               username        : user.nickname
               agree           : "on"
-              inviteCode      : "twitterfriends"
+              inviteCode      : "3adf0f2067"
               silence         : yes
             , (error, account, token)=>
               if not err and account
@@ -238,4 +238,4 @@

     *if you fall in love with this project, please let us know - http://blog.koding.com/2012/06/we-want-to-date-not-hire/

-    """+    """


ontrac client bugfix

ontrac client bugfix

--- src/ontrac.coffee
+++ src/ontrac.coffee
@@ -106,15 +106,16 @@
     status = @presentStatus @extractSummaryField shipment, 'Delivery Status'
     $ = shipment?.details
     return {activities, status} unless $?
-    $("#trkdetail").find('tr').each (rowIndex, row) =>
-      return unless rowIndex > 1
+    $("#trkdetail table table").find('tr').each (rowIndex, row) =>
+      return unless rowIndex > 0
       fields = []
       $(row).find('td').each (colIndex, col) ->
         fields.push $(col).text().trim()
-      details = upperCaseFirst(lowerCase(fields[0])) if fields[0]?.length
-      timestamp = @presentTimestamp fields[1]
-      location = @presentAddress fields[2] if fields[2]?.length
-      activities.push {details, timestamp, location} if details? and timestamp? and location?
+      if fields.length
+        details = upperCaseFirst(lowerCase(fields[0])) if fields[0].length
+        timestamp = @presentTimestamp fields[1]
+        location = @presentAddress fields[2] if fields[2].length
+        activities.push {details, timestamp, location} if details? and timestamp? and location?
     {activities, status}

   getDestination: (shipment) ->


Small bug with ontario drivers license

Small bug with ontario drivers license

--- src/zfields/ontario_drivers_license_number.coffee
+++ src/zfields/ontario_drivers_license_number.coffee
@@ -12,7 +12,7 @@
   value = $target.val() + char
   value = value.replace(/[^a-zA-Z\d]/g, '')

-  return false if value.length > 12
+  return false if value.length > 15

 formatOntarioDriversLicenseNumber = (e) ->
   char = String.fromCharCode(e.which)
@@ -23,7 +23,7 @@
   val = old_val + char.toUpperCase()

   if old_val is ''
-    e.preventDefault
+    e.preventDefault()
     $target.val(val) if /^[A-Za-z]$/.test(val)

   else if /^[A-Za-z]\d{0,3}$/.test(old_val)


setContents and updateContents needs options param

--- src/scribe.coffee
+++ src/scribe.coffee
@@ -122,10 +122,11 @@
     delta = Tandem.Delta.makeInsertDelta(this.getLength(), index, text, formats)
     @editor.applyDelta(delta, options)

-  setContents: (delta) ->
+  setContents: (delta, options = {}) ->
+    options = _.defaults(options, DEFAULT_API_OPTIONS)
     delta = if _.isArray(delta) then new Tandem.Delta(0, delta) else Tandem.Delta.makeDelta(delta)
     delta.startLength = this.getLength()
-    @editor.applyDelta(delta)
+    @editor.applyDelta(delta, options)

   setHTML: (html) ->
     @editor.root.innerHTML = html
@@ -134,8 +135,9 @@
     options = _.defaults(options, DEFAULT_API_OPTIONS)
     @editor.selection.setRange(range, options.silent)

-  updateContents: (delta) ->
-    @editor.applyDelta(delta)
+  updateContents: (delta, options = {}) ->
+    options = _.defaults(options, DEFAULT_API_OPTIONS)
+    @editor.applyDelta(delta, options)

   updateSelection: (options = {}) ->
     options = _.defaults(options, DEFAULT_API_OPTIONS)


Remove unnecessary clearTimeout

Remove unnecessary clearTimeout

--- src/server.coffee
+++ src/server.coffee
@@ -60,9 +60,6 @@
   #
   _write_pipe: ->
     @timer = setTimeout (=>
-      # clear @timer (memory leak!)
-      clearTimeout(@timer)
-
       # recurse
       if @buffer.length == 0
         @_write_pipe()
@@ -103,5 +100,4 @@
   _to_buffer: (data) ->
     @buffer = @_data_to_instruction(data)

-
 module.exports = Server


Cleaned up ReactComment

Cleaned up ReactComment

--- core/app/backbone/views/evidence/comment_view.coffee
+++ core/app/backbone/views/evidence/comment_view.coffee
@@ -7,15 +7,10 @@
           className:"heading-avatar-image"
       R.a
         href: @model().link()
-        className:"discussion-evidenceish-name popover-link js-user-link"
+        className:"discussion-evidenceish-name popover-link"
         rel: "backbone"
         @model().get('name')

-ReactCommentReply = React.createBackboneClass
-  render: ->
-    count = @model().get('sub_comments_count')
-    R.span {},
-      "(#{count}) Reply"

 window.ReactComment = React.createBackboneClass
   getInitialState: ->
@@ -33,6 +28,36 @@
   _toggleSubcomments: ->
     @setState show_subcomments: !@state.show_subcomments

+  _content: ->
+    R.div
+      className:"discussion-evidenceish-content discussion-evidenceish-text",
+      dangerouslySetInnerHTML: {__html: @model().get('formatted_comment_content')}
+
+  _bottom: ->
+    sub_comment_count = @model().get('sub_comments_count')
+
+    R.div className: 'comment-bottom',
+      R.ul className: "comment-bottom-actions", [
+        R.li className: "comment-bottom-action",
+          ReactEvidenceVote model: @model().argumentTally()
+        R.li className:"comment-bottom-action comment-bottom-action-time",
+          R.i className:"icon-time"
+          @model().get('time_ago')
+          " "
+          Factlink.Global.t.ago
+        if @model().can_destroy()
+          R.li className: "comment-bottom-action comment-bottom-action-delete",
+            ReactDeleteButton
+              model: @model()
+              onDelete: @_onDelete
+        R.li className:"comment-bottom-action",
+          R.a
+            className:"spec-sub-comments-link"
+            href:"javascript:"
+            onClick: @_toggleSubcomments
+
+            "(#{sub_comment_count}) Reply"
+      ]
   render: ->
     relevant = @model().argumentTally().relevance() >= 0

@@ -44,34 +69,9 @@

     R.div className: top_classes,
       R.div className:"discussion-evidenceish-box spec-evidence-box",
-        R.div className: "js-heading-region",
-          ReactCommentHeading(model: @model().creator())
-        R.div
-          className:"discussion-evidenceish-content discussion-evidenceish-text",
-          dangerouslySetInnerHTML: {__html: @model().get('formatted_comment_content')}
-        R.div className: 'comment-bottom',
-          R.ul className: "comment-bottom-actions", [
-            R.li className: "comment-bottom-action js-vote-region",
-              ReactEvidenceVote model: @model().argumentTally()
-            R.li className:"comment-bottom-action comment-bottom-action-time",
-              R.i className:"icon-time"
-              @model().get('time_ago')
-              " "
-              Factlink.Global.t.ago
-            if @model().can_destroy()
-              R.li className: "comment-bottom-action comment-bottom-action-delete js-delete-region",
-                ReactDeleteButton
-                  model: @model()
-                  onDelete: @_onDelete
-            R.li className:"js-sub-comments-link-container comment-bottom-action",
-              R.a
-                className:"js-sub-comments-link js-sub-comments-link-region"
-                href:"javascript:"
-                onClick: @_toggleSubcomments
-
-                ReactCommentReply model: @model()
-          ]
+        ReactCommentHeading(model: @model().creator())
+        @_content()
+        @_bottom()
       if @state.show_subcomments
-        R.div className: "js-sub-comments-region",
-          ReactSubComments
-            model: new SubComments([], parentModel: @model())
+        ReactSubComments
+          model: new SubComments([], parentModel: @model())


Filter also the topfact from autocomplete suggestions, to avoid direct loops

--- core/app/backbone/views/evidence/add_evidence_form_view.coffee
+++ core/app/backbone/views/evidence/add_evidence_form_view.coffee
@@ -20,13 +20,19 @@

     @switchToCommentView()

-  fact_relations_masquerading_as_facts: ->
-    @_fact_relations_masquerading_as_facts ?= collectionMap new Backbone.Collection, @collection, (model) ->
-      new Fact model.get('from_fact')
+  _filtered_facts: ->
+    fact_relations_masquerading_as_facts = @_collectionUtils().map new Backbone.Collection,
+      @collection, (model) -> new Fact model.get('from_fact')
+
+    @_collectionUtils().union new Backbone.Collection, fact_relations_masquerading_as_facts,
+      new Backbone.Collection [@collection.fact.clone()]
+
+  _collectionUtils: ->
+    @__collectionUtils ?= new CollectionUtils this

   searchView: ->
     searchView = new AutoCompleteFactRelationsView
-      collection: @fact_relations_masquerading_as_facts()
+      collection: @_filtered_facts()
       fact_id: @collection.fact.id
       type: @collection.believesType()
       recent_collection: @_recent_collection


Add context menu to atom-text-editor selector

--- menus/symbols-view.cson
+++ menus/symbols-view.cson
@@ -12,6 +12,6 @@
 ]

 'context-menu':
-  '.overlayer': [
+  'atom-text-editor': [
     { 'label': 'Go to Declaration', 'command': 'symbols-view:go-to-declaration' }
   ]


`session.signOut()`.

`session.signOut()`.

--- src/api/session.coffee
+++ src/api/session.coffee
@@ -30,7 +30,7 @@
     token: { type: 'string' }
   }

-  @throw 'ALREADY_AUTHENTICATED' if @session.has('user_id')
+  @throw 'ALREADY_AUTHENTICATED' if @session.has 'user_id'

   # Gets the token.
   token = $wait @tokens.first token
@@ -38,12 +38,18 @@

   # Stores the user and the token identifiers in the session.
   user_id = token.get('user_id')
-  @session.set 'token_id', token.get('id')
+  @session.set 'token_id', token.get 'id'
   @session.set 'user_id', user_id

   # Returns the user.
   user = $wait @users.first user_id
   @getUserPublicProperties user
+
+exports.signOut = ->
+  @session.unset 'token_id'
+  @session.unset 'user_id'
+
+  true

 # Gets the the currently signed in user.
 exports.getUser = ->


Fetch the image using dockerode.

Fetch the image using dockerode.

--- src/application.coffee
+++ src/application.coffee
@@ -1,12 +1,10 @@
 _ = require 'lodash'
 es = require 'event-stream'
 url = require 'url'
-http = require 'http'
 knex = require './db'
 path = require 'path'
 Docker = require 'dockerode'
 Promise = require 'bluebird'
-request = Promise.promisify require 'request'
 JSONStream = require 'JSONStream'
 PlatformAPI = require 'resin-platform-api/request'

@@ -37,30 +35,20 @@
 	docker.getImage(app.imageId).inspectAsync()
 	.catch (error) ->
 		console.log("Pulling image:", app.imageId)
-		deferred = Promise.defer()
-		options =
-			method: 'POST'
-			path: "/v1.8/images/create?fromImage=#{app.imageId}"
-			socketPath: DOCKER_SOCKET
+		docker.createImageAsync(fromImage: app.imageId)
+		.then (stream) ->
+			deferred = Promise.defer()

-		req = http.request options, (res) ->
-			if res.headers['content-type'] is 'application/json'
-				res.pipe(JSONStream.parse('error'))
+			if stream.headers['content-type'] is 'application/json'
+				stream.pipe(JSONStream.parse('error'))
 				.pipe es.mapSync (error) ->
 					deferred.reject(error)
 			else
-				res.pipe es.wait (error, text) -> deferred.reject(text)
+				stream.pipe es.wait (error, text) -> deferred.reject(text)

-			res.on 'end', ->
-				if res.statusCode is 200
-					deferred.resolve()
-				else
-					deferred.reject(res.statusCode)
+			stream.on 'end', -> deferred.resolve()

-		req.end()
-		req.on 'error', (error) -> deferred.reject(error)
-
-		return deferred.promise
+			return deferred.promise
 	.then ->
 		console.log("Creating container:", app.imageId)
 		ports = {}


fix coffeescript n00b bug :-P

fix coffeescript n00b bug :-P

--- test/integration/api.coffee
+++ test/integration/api.coffee
@@ -8,7 +8,10 @@
 redis = require 'redis'

 settings = require '../settings.json'
-serverURL = "http://#{process.env.CU_TEST_SERVER}:3001" or settings.serverURL
+if process.env.CU_TEST_SERVER
+  serverURL = "http://#{process.env.CU_TEST_SERVER}:3001"
+else
+  serverURL = settings.serverURL

 # Timeout period in milliseconds for duplicate request
 # debouncing. (see underscore debounce and test that uses these)


Bug fix

Bug fix

--- test/lib/build_mocks.coffee
+++ test/lib/build_mocks.coffee
@@ -16,6 +16,8 @@
         field = field() if _.isFunction(field)
         field = _.clone(field)
         relation = model_type.relation(key)
+        field[1] = createMockModel(mock_model_types, relation.reverse_model_type)
+
         if relation.join_table
           mock_join_table = createMockModel(mock_model_types, relation.join_table)
           mock_model_types[mock_join_table.model_name] = mock_join_table
@@ -23,11 +25,17 @@
           if field.length is 2 then field.push(join_table_info) else _.extend(field[2], join_table_info)
         return field

-  mock_model_types[model_type.model_name] = class MockModel extends Backbone.Model
+  class MockModel extends Backbone.Model
     _orm_never_cache: true # so the correct models are found in the cache
     @model_name: model_type.model_name
     @schema: schema
     sync: require('backbone-orm/memory_sync')(MockModel)
+
+    constructor: ->
+      super
+      console.trace "Mock Flat: #{@cid}" if @constructor.model_name is 'Flat'
+
+  mock_model_types[model_type.model_name] = MockModel

   # configure the mock request
   app = express(); app.use(express.bodyParser())


Remove arguments from ActionsManager._reportAction callback

--- src/actions_manager.coffee
+++ src/actions_manager.coffee
@@ -23,9 +23,9 @@
       yogurt:
         productClick: (data, redirect_callback, redirect_url, url_mode = { type: 'default' }) ->
           clearTimeout @pageview_timeout
-          @_reportAction 'yogurt', 'productClick', data, (analytics_session) =>
+          @_reportAction 'yogurt', 'productClick', data, () =>
             if redirect_callback and redirect_url
-              redirect_url = new AnalyticsUrl(redirect_url).format_params(url_mode, analytics_session, @session.metadata)
+              redirect_url = new AnalyticsUrl(redirect_url).format_params(url_mode, @session.analytics_session, @session.metadata)

               try redirect_callback(redirect_url) catch then Settings.redirectTo(redirect_url)

@@ -65,8 +65,8 @@
         url = Settings.url.beacon(@session.analytics_session)
         payload = @_buildBeaconPayload(category, type, data)

-        ## TODO: THROW ERROR ON REJECT
-        @reporter.sendBeacon(url, payload).then => cb and cb(@session.analytics_session)
+        ## TODO: HANDLE ERROR ON REJECT
+        @reporter.sendBeacon(url, payload).then => cb and cb()

     # TODO: implement multiple actions per beacon maybe??
     _buildBeaconPayload: (category, type, data = '{}') ->


make backbone's router less greedy

make backbone's router less greedy

--- scripts/gh-book/app.coffee
+++ scripts/gh-book/app.coffee
@@ -89,7 +89,7 @@

     # Views use anchors with hrefs so catch the click and send it to Backbone
-    $(document).on 'click', 'a:not([data-bypass])', (e) ->
+    $(document).on 'click', 'a:not([data-bypass]):not([href="#"])', (e) ->
       external = new RegExp('^((f|ht)tps?:)?//')
       href = $(@).attr('href')


Added template helpers for capitalized translation

--- core/app/backbone/views/generic/tourstepbox.coffee
+++ core/app/backbone/views/generic/tourstepbox.coffee
@@ -16,6 +16,10 @@

 class window.AddChannelsTourStep2 extends TourStepBox
   template: 'tour/add_channel_step2'
+  templateHelpers: ->
+    topics: Factlink.Global.t.topics.capitalize()
+    channels: Factlink.Global.t.channels.capitalize()
+

 class window.AddChannelsTourStep3 extends TourStepBox
   template: 'tour/add_channel_step3'


Refactor sort environment stacks utility

--- client/app/lib/util/sortEnvironmentStacks.coffee
+++ client/app/lib/util/sortEnvironmentStacks.coffee
@@ -1,10 +1,10 @@
-MANAGED_VMS = 'Managed VMs'
+isManagedVMStack = require './isManagedVMStack'

 module.exports = (stacks = []) ->

   stacks.sort (a, b) ->

-    return  1  if a.title is MANAGED_VMS
-    return -1  if b.title is MANAGED_VMS
+    return  1  if isManagedVMStack a
+    return -1  if isManagedVMStack b

     return new Date(a.meta.createdAt) - new Date(b.meta.createdAt)


Remove unnecessary line break

Remove unnecessary line break

--- server/test/units/deploy.coffee
+++ server/test/units/deploy.coffee
@@ -14,7 +14,6 @@
 test('.updateFromTag sets the gits username,
 pulls the given tag,
 runs npm install in both client and server,
-
 notifying github at each step', (done) ->
   sandbox = sinon.sandbox.create()


LayoutManager: Fix resurrection for TailerPane.

--- client/ide/lib/workspace/idelayoutmanager.coffee
+++ client/ide/lib/workspace/idelayoutmanager.coffee
@@ -227,7 +227,7 @@
       else
         # Don't use `active tab view` logic for new pane creation.
         # Because `The Editors` (saved editors) are loading async.
-        item.targetTabView = tabView  if item.context.paneType is 'editor'
+        item.targetTabView = tabView  if item.context.paneType in [ 'editor', 'tailer' ]
         ideApp.createPaneFromChange item, yes


cycleChannel

cycleChannel

--- client/landing/app/MainApp/account-mixins.coffee
+++ client/landing/app/MainApp/account-mixins.coffee
@@ -48,6 +48,8 @@
         cycleChannel.call this
         console.error err

+      pong =-> @lastPong = Date.now()
+
       cycleChannel =->
         @off()
         @stopPinging()
@@ -61,6 +63,8 @@
             ready.bind this
           else if method is 'error'
             error.bind this
+          else if method is 'pong'
+            pong.bind this
           else if method is 'cycleChannel'
             cycleChannel.bind this
           else
@@ -87,6 +91,10 @@
         channelName = getChannelName "kite-#{kiteName}"
         return callback channels[channelName]  if channels[channelName]
         channel = KD.remote.mq.subscribe channelName
+        kiteController = KD.getSingleton 'kiteController'
+        kiteController.channels ?= {}
+        kiteController.channels[kiteName] = channel
+        channel.cycleChannel = -> cycleChannel.call this
         channels[channelName] = channel
         channel.once 'broker.subscribed', ->
           onReady channel, -> callback channel
@@ -96,6 +104,9 @@
           clientId    : KD.remote.getSessionToken()
         channel.on "message", messageHandler.bind channel, kiteName
         i = setInterval ->
+          now = Date.now()
+          isUnresponsive = channel.lastPong? and (now - channel.lastPong > 6000)
+          cycleChannel.call channel  if isUnresponsive
           channel.publish JSON.stringify
             method      : 'ping'
             arguments   : []
@@ -103,10 +114,7 @@
         , 5000
         channel.stopPinging =-> clearInterval i

-      # {utils} = KD
-
       tellKite =(options, callback=->)->
-        # kallback = utils.getCancellableCallback kallback
         scrubber = new Scrubber localStore
         args = [options, callback]
         {method} = options


use finders

use finders

--- client/skr/screens/vendor-maint/VendorMaint.cjsx
+++ client/skr/screens/vendor-maint/VendorMaint.cjsx
@@ -10,45 +10,29 @@
     modelForAccess: 'vendor'

     render: ->
-        <div className="vendor-maint">
+        <LC.ScreenWrapper identifier="vendor-maint">
             <Lanes.Screens.CommonComponents
                 activity={@state} commands={@state.commands} model={@vendor} />
             <BS.Row>
-                <Skr.Components.VendorFinder sm=4 editOnly
-                    commands={@state.commands} vendor={@vendor} />
-
+                <SC.VendorFinder sm=4 editOnly
+                    commands={@state.commands} model={@vendor} />
                 <LC.Input sm=8 name="name" model={@vendor} />
             </BS.Row>
             <BS.Row>
-               <LC.Input sm=12
-                   type='textarea'
-                   name="notes"
-                   model={@vendor} />
+               <LC.Input sm=12 type='textarea' name="notes" model={@vendor} />
             </BS.Row>
             <BS.Row>
-                <LC.SelectField sm=4
-                    label="Payables Account"
-                    name="gl_payables_account"
-                    labelField="combined_name"
-                    model={@vendor} />
-                <LC.SelectField sm=4
-                    label="Freight Account"
-                    name="gl_freight_account"
-                    labelField="combined_name"
-                    model={@vendor} />
-                <LC.SelectField sm=4
-                    label="Payment Terms"
-                    name="terms"
-                    labelField="code"
-                    model={@vendor} />
+                <SC.GlAccountChooser sm=4 label="Payables Account"
+                    name="gl_payables_account" model={@vendor} />
+                <SC.GlAccountChooser sm=4 label="Freight Account"
+                    name="gl_freight_account" model={@vendor} />
+                <SC.TermsChooser sm=4 model={@vendor} />
             </BS.Row>
             <BS.Row>
                 <LC.FieldSet sm=12 title="Address">
-                    <Skr.Components.Address lg=6 title="Billing"
-                        model={@vendor.billing_address}  />
-                    <Skr.Components.Address lg=6 title="Shipping"
-                        model={@vendor.shipping_address} />
+                    <SC.Address lg=6 title="Billing"  model={@vendor.billing_address}  />
+                    <SC.Address lg=6 title="Shipping" model={@vendor.shipping_address} />
                 </LC.FieldSet>
             </BS.Row>

-        </div>
+        </LC.ScreenWrapper>


Fixed a off-by-one bug where undefined points were drawn.

This made drawing less efficient.

--- media/js/blokjes.coffee
+++ media/js/blokjes.coffee
@@ -36,22 +36,26 @@

 drawPolygon = (coords) ->
   ctx.beginPath()
-  for index in [0..coords.length] by 2
+  for index in [0...coords.length] by 2
     ctx.moveTo(coords[index], coords[index+1]) if index == 0
     ctx.lineTo(coords[index], coords[index+1]) if index != 0
   ctx.fill()

 drawBlock = (block) ->
   ctx.fillStyle = colors[block.color-1]
+  drawPolygon pointsForBlock block
+
+pointsForBlock = (block) ->
   x1 = block.x * GRID_SCALE
   y1 = block.y * GRID_SCALE
   x2 = x1 + GRID_SCALE
   y2 = y1 + GRID_SCALE
-  drawPolygon [x1, y1, x2, y1, x2, y2, x1, y2] if block.type == 1
-  drawPolygon [x2, y1, x2, y2, x1, y2] if block.type == 2
-  drawPolygon [x1, y1, x1, y2, x2, y2] if block.type == 3
-  drawPolygon [x1, y1, x2, y1, x1, y2] if block.type == 4
-  drawPolygon [x1, y1, x2, y1, x2, y2] if block.type == 5
+
+  return [x1, y1, x2, y1, x2, y2, x1, y2] if block.type == 1
+  return [x2, y1, x2, y2, x1, y2] if block.type == 2
+  return [x1, y1, x1, y2, x2, y2] if block.type == 3
+  return [x1, y1, x2, y1, x1, y2] if block.type == 4
+  return [x1, y1, x2, y1, x2, y2] if block.type == 5

 drawLayer = (layer) ->
   if layer.visible


fix failing tests

fix failing tests

--- test/unit/selectSpec.coffee
+++ test/unit/selectSpec.coffee
@@ -59,4 +59,4 @@
   it 'should apply custom template for item', ->
     element = compile('
{{ item }}
')
     $scope.$apply(-> $scope.value = $scope.items.first)
-    expect(element.find(".fs-select-active .bang").length).toBe(1)
+    expect(element.find(".activate-button .bang").length).toBe(1)


Fixed register

Fixed register

--- server/api/register.coffee
+++ server/api/register.coffee
@@ -35,6 +35,7 @@
             name: informaticsData.name,
             graduateYear: informaticsData.graduateYear,
             userList: "unknown",
+            lastActivated: new Date()
             registerDate: new Date()
         )
         if cfLogin


add background option to `Renderer`

add background option to `Renderer`

--- src/core/Renderer.coffee
+++ src/core/Renderer.coffee
@@ -13,6 +13,7 @@
 			fillParent: off
 			showKeyPoints: no
 			border: off
+			background: '#eee'
 		@width = options.width
 		@height = options.height
 		@fps = options.fps
@@ -41,7 +42,7 @@
 		@dom.style.border = 'solid 1px gray' if options.border? and options.border
 		@dom.style.cursor = 'crosshair'
 		@dom.style.boxSizing = 'content-box'
-		@dom.style.background = '#eee'
+		@dom.style.background = options.background
 		@dom.oncontextmenu = -> false

 		Bu.animationRunner?.hookUp @


Fix Danbooru NSFW

Fix Danbooru NSFW

--- modules/danbooru.coffee
+++ modules/danbooru.coffee
@@ -34,7 +34,7 @@
       if d.danbooruDate
         return msg.reply 'Rate limit excedeed. Wait a few seconds.' if (new Date() - d.danbooruDate) < 3000
       d.danbooruDate = new Date()
-      tags = 'rating:safe ' + tags if not d.data.allowNSFW or msg.channel.name.indexOf('nsfw') < 0
+      tags = 'rating:safe ' + tags if not d.data.allowNSFW and msg.channel.name.indexOf('nsfw') < 0
       qs = {
         random: true
         tags


Fix prefix on danbooru module

Fix prefix on danbooru module

--- modules/danbooru.coffee
+++ modules/danbooru.coffee
@@ -25,7 +25,7 @@

 class DanbooruModule extends BotModule
   init: =>
-    {@prefix} = @engine
+    {@prefix} = @engine.settings

     @registerCommand 'danbooru', {
       allowDM: true


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/sprite.coffee
+++ src/sprite.coffee
@@ -18,7 +18,7 @@
     width: null
     height: null

-  # Hold
+  # Hold loaded images
   spriteCache = {}

   Sprite = (image, sourceX, sourceY, width, height) ->


slight performance bump

slight performance bump

--- src/coffee/bokeh_views.coffee
+++ src/coffee/bokeh_views.coffee
@@ -33,7 +33,6 @@
     @plot_view.ctx.arc(x, y, 5, 0, Math.PI*2)
     @plot_view.ctx.closePath()
     @plot_view.ctx.fill()
-    @plot_view.ctx.stroke()

 tojq = (d3selection) ->
   return $(d3selection[0][0])
@@ -590,8 +589,6 @@
       else
         @addCircle(@screenx[idx], @screeny[idx])

-
-
     @plot_view.ctx.stroke()
     return null


Remove dead code

Remove dead code

--- src/coffee/controllers.coffee
+++ src/coffee/controllers.coffee
@@ -340,8 +340,6 @@

                 $scope.centerPosition position.coords.latitude, position.coords.longitude if center

-                getSuggestedPlaces pos, $scope.places
-
     eob_data.placesPromise.success (data) ->
         $scope.places = data

@@ -401,29 +399,6 @@
         mc.clearMarkers()
         mc.addMarkers(mm)

-    getSuggestedPlaces = (pos, places) ->
-        suggestions = {}
-        i = 0
-        min = 999
-
-        for place, i in places
-            placePos = new google.maps.LatLng(place.lat, place.lng)
-            distance = (google.maps.geometry.spherical.computeDistanceBetween(pos, placePos) / 1000).toFixed(2)
-            place.distance = distance
-            if min > distance then min = distance
-
-        if min >= 999 then suggestions = null
-        # Get the closest places
-        newPlaces = places.slice 0
-        newPlaces.sort compareDistances
-        suggestions = newPlaces.slice 0,5
-        return suggestions
-
-    compareDistances = (a,b) ->
-        if a.distance < b.distance then return -1
-        if a.distance > b.distanc then return 1
-        return 0
-
     $scope.fitBounds = (markersToFit) ->
         markersToFit ?= markers
         bounds = new google.maps.LatLngBounds()


better test

better test

--- test/basic.coffee
+++ test/basic.coffee
@@ -24,9 +24,12 @@
         up: (cb) ->
           coll.insert name: 'tobi', cb
       migrator.migrate (err, res) ->
-        (!err).should.be.ok
+        return done(err) if err
         res.should.be.ok
         res[0].id.should.be.equal 1
         res[0].result.status.should.be.equal 'ok'
-        done()
+        coll.find({name: 'tobi'}).count (err, count) ->
+          return done(err) if err
+          count.should.be.equal 1
+          done()


test: extra #

test: extra #

--- test/index.coffee
+++ test/index.coffee
@@ -139,7 +139,7 @@

 describe 'joins', ->
   _(configs).each (config, i) ->
-    # # If on is a string, also try the hash version of on to verify it's the same
+    # If on is a string, also try the hash version of on to verify it's the same
     if _(config.on).isString()
       mod_configs = [_(config).deepClone(), _(config).deepClone()]
       mod_configs[1].on = _.object config.on, config.on


fix calculation of overlay to be executed after tmp object has been inserted.

--- plugins/image.coffee
+++ plugins/image.coffee
@@ -298,8 +298,6 @@
                     editable.append overlay.left
                     editable.append overlay.right

-                    helper.showOverlay position
-
                     helper.removeFeedbackElements()
                     $(event.target).prepend(dnd.createTmpFeedback ui.draggable[0], position)

@@ -311,6 +309,8 @@
                         $(event.target).prepend(dnd.createTmpFeedback ui.draggable[0], 'middle')
                         $('.tmpLine', $(event.target)).hide()

+                    helper.showOverlay position
+
                 handleDragEvent: (event, ui) ->
                     position = helper.calcPosition(offset, event)

@@ -320,8 +320,6 @@

                     dnd.lastPositionDrag = position

-                    helper.showOverlay position
-
                     tmpFeedbackLR = $('.tmp', editable)
                     tmpFeedbackMiddle = $('.tmpLine', editable)

@@ -331,6 +329,8 @@
                     else
                         tmpFeedbackMiddle.hide()
                         tmpFeedbackLR.removeClass("inlineImage-left inlineImage-right").addClass("inlineImage-" + position).show()
+
+                    helper.showOverlay position

                 handleLeaveEvent: (event, ui) ->
                     if not $('div.trashcan', ui.helper).length


Add more mixin tests

Add more mixin tests

--- test/mixin.coffee
+++ test/mixin.coffee
@@ -36,6 +36,25 @@
 		}
 	'''

+test "mixin later ruleset", ->
+	assert.compileTo '''
+		.submit {
+			@mixin .btn;
+		}
+
+		.btn {
+			display: inline-block;
+		}
+	''', '''
+		.submit {
+			display: inline-block;
+		}
+
+		.btn {
+			display: inline-block;
+		}
+	'''
+
 test "mixin rulesets with selector list", ->
 	assert.compileTo '''
 		.btn {
@@ -152,4 +171,52 @@
 		a {
 			width: 80px;
 		}
+	'''
+
+test "mixin ruleset from another file", ->
+	assert.compileTo {
+		'/btn.roo': '''
+			.btn {
+				display: inline-block;
+			}
+		'''
+		'/index.roo': '''
+			@import './btn.roo';
+
+			.submit {
+				@mixin .btn;
+			}
+		'''
+	}, '''
+		.btn {
+			display: inline-block;
+		}
+
+		.submit {
+			display: inline-block;
+		}
+	'''
+
+test "mixin ruleset from later file", ->
+	assert.compileTo {
+		'/btn.roo': '''
+			.btn {
+				display: inline-block;
+			}
+		'''
+		'/index.roo': '''
+			.submit {
+				@mixin .btn;
+			}
+
+			@import './btn.roo';
+		'''
+	}, '''
+		.submit {
+			display: inline-block;
+		}
+
+		.btn {
+			display: inline-block;
+		}
 	'''


test 2

test 2

--- modules/danbooru.coffee
+++ modules/danbooru.coffee
@@ -37,7 +37,6 @@
       tags = 'rating:safe ' + tags if not d.data.allowNSFW
       qs = {
         random: true
-        limit: 1
         tags
       }
       danbooru.get '/posts.json', { json: true, qs }
@@ -60,7 +59,6 @@
       d.danbooruDate = new Date()
       qs = {
         random: true
-        limit: 1
         tags
       }
       safebooru.get '/posts.json', { json: true, qs }
@@ -110,7 +108,6 @@
       .then (results)=>
         return Promise.reject { msg: "Run the #{@prefix}setWaifu command first." } if not results[0]?
         qs = {
-          limit: 1
           random: true
           tags: 'solo ' + results[0].waifu
         }


mongodb: add comments to the coffeescript script

--- mongodb/anscombe.coffee
+++ mongodb/anscombe.coffee
@@ -1,11 +1,11 @@
 # plot Anscombe's quartet, with mongoDB on the back end

-height = 500
-width  = 800
-left_width = 80
-gap = 10
-button_size = 20
-rad = 3
+height = 500      # height of right panel
+width  = 800      # width of right panel
+left_width = 80   # width of left bit, with the buttons
+gap = 10          # gap to left of buttons
+button_size = 20  # width of buttons
+rad = 3           # radius of circles in main plot
 color = "#6A5ACD" # slateblue
 hilit = "#C71585" # violetred
 margin = {left:60, top:40, right:40, bottom:40, inner:5}
@@ -16,10 +16,11 @@
         .attr("height", height)
         .attr("width", width + left_width)

-
+# the four sets, and where the buttons will go
 sets = ["I", "II", "III", "IV"]
 y = (margin.top + i*button_size*1.5 for i of sets)

+# add the four buttons
 svg.selectAll("empty")
    .data(sets)
    .enter()
@@ -31,8 +32,9 @@
    .attr("fill", color)
    .on("mouseover", (d) -> d3.select(this).attr("fill", hilit))
    .on("mouseout", (d) -> d3.select(this).attr("fill", color))
-   .on("click", (d) -> make_plot(d))
+   .on("click", (d) -> make_plot(d)) # <- click a button and make the plot

+# add text next to the buttons
 svg.selectAll("empty")
    .data(sets)
    .enter()
@@ -44,6 +46,8 @@
    .style("text-anchor", "middle")
    .style("font-size", "20px")

+# function to create the scatterplot
+# (see http://kbroman.org/d3panels)
 myscatter = scatterplot().xvar("x")
                          .yvar("y")
                          .xlab("X")
@@ -56,6 +60,10 @@
                          .pointsize(rad)
                          .pointcolor(color)

+# function that actually makes the plot of one of the 4 sets
+#   - the URL passed to d3.json causes the request to the database, and data as JSON back
+#   - we remove previous plot and any d3-tip objects
+#   - then make the scatterplot
 make_plot = (set) ->
     d3.json("http://localhost:8080/anscombe/#{set}", (data) ->
         d3.select("svg#scatterplot").remove()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/point.coffee
+++ test/point.coffee
@@ -163,7 +163,7 @@
     equals p.y, 1

   test ".random", ->
-
+    p = Point.random

   test ".interpolate", ->
     p1 = Point(10, 7)


Make it easier to write tests for queue optimization

--- test/queue.coffee
+++ test/queue.coffee
@@ -1,7 +1,7 @@
 queue = null
 should = require 'should'

-describe 'Queue', ->
+describe '[Queue]', ->

   before ->
     global.localStorage = {}
@@ -28,48 +28,84 @@
     queue.toJSON().should.eql []
     global.localStorage.queue.should.equal '[]'

-  describe '#optimize', ->
+  describe '[optimize]', ->
+
+    test = (events, result) ->
+      timestamps = []
+      for event, i in events
+        timestamps[i] = queue.push.apply queue, event
+
+      replace = (text) ->
+        match = text.match /^ts_(\d+)$/
+        if match then return timestamps[match[1]]
+        match = text.match /^ts_(\d+)_(\w+)$/
+        if match then return timestamps[match[1]][match[2]]
+        return text
+
+      for item in result
+        for value, i in item
+          if typeof value is 'string'
+            item[i] = replace(value)
+          else if typeof value is 'object' and not Array.isArray value
+            for key, text of value
+              continue unless typeof text is 'string'
+              item[i][key] = replace(text)
+
+
+      queue.toJSON().should.eql result
+      queue.clear()

     it 'create + update = create', ->
-      queue.push 'task', 'create', {id: 20, name: 'Test'}
-      queue.push 'task', 'update', {id: 20, name: 'Test - Updated'}
-      timestamp = queue.push('task', 'update', {id: 20, name: 'Test - Updated Again'}).name
-      queue.toJSON().should.eql [['task', 'create', {id: 20, name: 'Test - Updated Again'}, timestamp]]
-      queue.clear()
+
+      test [
+        ['task', 'create', {id: 20, name: 'Test'}]
+        ['task', 'update', {id: 20, name: 'Test - Updated'}]
+        ['task', 'update', {id: 20, name: 'Test - Updated Again'}]
+      ], [
+        ['task', 'create', {id: 20, name: 'Test - Updated Again'}, 'ts_2_name']
+      ]

     it 'create + destroy = nothing', ->
-      queue.push 'list', 'create', {id: 30, name: 'List'}
-      queue.push 'list', 'destroy', 30
-      queue.toJSON().should.eql []
-      queue.clear()
+
+      test [
+        ['list', 'create', {id: 30, name: 'List'}]
+        ['list', 'destroy', 30]
+      ], []

     it 'create + update + destroy = nothing', ->
-      queue.push 'task', 'create', {id: 10, name: 'Task'}
-      queue.push 'task', 'update', {id: 10, name: 'Task - Updated'}
-      queue.push 'task', 'destroy', 10
-      queue.toJSON().should.eql []
-      queue.clear()
+
+      test [
+        ['task', 'create', {id: 10, name: 'Task'}]
+        ['task', 'update', {id: 10, name: 'Task - Updated'}]
+        ['task', 'destroy', 10]
+      ], []

     it 'update + destroy = destroy', ->
-      queue.push 'list', 'update', {id:15, name: 'List - Updated'}
-      timestamp = queue.push 'list', 'destroy', 15
-      queue.toJSON().should.eql [['list', 'destroy', 15, timestamp]]
-      queue.clear()
+
+      test [
+        ['list', 'update', {id:15, name: 'List - Updated'}]
+        ['list', 'destroy', 15]
+      ], [
+        ['list', 'destroy', 15, 'ts_1']
+      ]

     it 'update + update = update', ->
-      name = queue.push('task', 'update', {id: 5, name: 'Task'}).name
-      listId = queue.push('task', 'update', {id: 5, listId: 30}).listId
-      notes = queue.push('task', 'update', {id: 5, notes: 'notes'}).notes
-      queue.toJSON().should.eql [['task', 'update', {
-        id: 5
-        name: 'Task'
-        listId: 30
-        notes: 'notes'
-      }, {
-        name: name
-        listId: listId
-        notes: notes
-      }]]
-      queue.clear()
+
+      test [
+        ['task', 'update', {id: 5, name: 'Task'}]
+        ['task', 'update', {id: 5, listId: 30}]
+        ['task', 'update', {id: 5, notes: 'notes'}]
+      ], [
+        ['task', 'update', {
+          id: 5
+          name: 'Task'
+          listId: 30
+          notes: 'notes'
+        }, {
+          name: 'ts_0_name'
+          listId: 'ts_1_listId'
+          notes: 'ts_2_notes'
+        }]
+      ]


Remove debug prints in logger

Remove debug prints in logger

--- src/core/logger.coffee
+++ src/core/logger.coffee
@@ -14,9 +14,6 @@
     @quiet = options.quiet or false

   log: (info, callback) ->
-    if Object.keys(info).length != 2
-      console.log('---- info ----')
-      console.log(info)
     {level, message} = info
     meta = info.meta ? {}
     if level == 'error'


Date now lines up with the rest of the search form

--- SingularityUI/app/components/common/input/DateEntry.cjsx
+++ SingularityUI/app/components/common/input/DateEntry.cjsx
@@ -1,9 +1,6 @@
 FormField = require './FormField'

 DateEntry = React.createClass
-
-    blah: ->
-        'startedAfter'

     componentDidMount: ->
         id = '#' + @props.id
@@ -19,25 +16,19 @@
             }).on('dp.change', changeFn) # value will be in event.date

     render: ->
-

-

-

-

-

-                            <FormField
-                                className = 'form-control'
-                                placeholder = {@props.title}
-                                type = {@props.inputType}
-                                disabled = {@props.disabled}
-                                size = {@props.size}
-                                id = {@props.id}
-                            />
-
-
-
-

-

-

+

+

+                <FormField
+                    className = 'form-control'
+                    placeholder = {@props.title}
+                    type = {@props.inputType}
+                    disabled = {@props.disabled}
+                    size = {@props.size}
+                    id = {@props.id}
+                />
+
+
+


Adding 'assert' to the compiler module since coffee does not import this by default, even though node does. This helps run the unit tests, though we may remove it later.

--- source/compiler.coffee
+++ source/compiler.coffee
@@ -12,6 +12,7 @@

 fs = require 'fs'
+assert = require 'assert'
 code = exports.compile fs.readFileSync process.argv[2] if process.argv[2]?
 console.log code
 console.log eval(code)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/state.coffee
+++ test/state.coffee
@@ -10,7 +10,7 @@

 test "State should transition to a different state after current state finishes", ->
   animation = GameObject
-    initialState: 'stand'
+    initialState: 'run'

   animation.include(StateMachine)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/core_object.coffee
+++ src/core_object.coffee
@@ -84,10 +84,10 @@
     myObject = Core()
     myObject.include(Bindable)

-    # now you can bind handlers to functions! Look how
-    # easy that was. You've hardly written any code
+    # now you can bind handlers to functions and
+    # y you've hardly written any code
     myObject.bind "someEvent", ->
-      aFunction()
+      alert("wow. that was easy.")

     </pre></code>


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/engine_levels.coffee
+++ src/engine_levels.coffee
@@ -59,7 +59,7 @@
     loadLevel level

   ###*
-  Reload the
+  Reload the current level.

   engine.goToLevel 'bossFight'


Remove custom transform

Remove custom transform

--- src/config/controls.coffee
+++ src/config/controls.coffee
@@ -11,10 +11,6 @@
     fill: my.color.button
     x: (world, args) -> world.index * my.control.spacing + world.get('margin')
     y: (world, args) -> world.get('margin')
-    transform: (world, args) ->
-      x = world.index * my.control.spacing + world.get('margin')
-      y = world.get('margin')
-      "translate(#{x},#{y})" # TODO: Refactor
     height: my.control.size
     width: my.control.size
     name_style: (world) ->


pass useLocalStorage=true to a Luca.Collection to persist your collection in localStorage

--- src/core/collection.coffee
+++ src/core/collection.coffee
@@ -32,6 +32,7 @@
     # usually done with the bootstrap functionality provided.
     if @cached
       @bootstrap_cache_key = if _.isFunction( @cached ) then @cached() else @cached
+

     # If we are going to be registering this collection with the CollectionManager
     # class, then we need to specify a key to register ourselves under. @registerAs can be
@@ -44,6 +45,14 @@

       @bind "after:initialize", ()=>
         @register( @registerWith, @registerAs, @)
+
+    # by passing useLocalStorage = true to your collection definition
+    # you will bypass the RESTful persistence layer and just persist everything
+    # locally in localStorage
+    if @useLocalStorage is true and window.localStorage?
+      table = @bootstrap_cache_key || @registerAs
+      throw "Must specify either a cached or registerAs property to use localStorage"
+      @localStorage = new Luca.LocalStore( table )

     # Populating a collection with local data
     #


close #26

close #26

--- src/couchdb-adapter.coffee
+++ src/couchdb-adapter.coffee
@@ -99,6 +99,7 @@
       data[key] = value.getEach(attr_key)

   addBelongsTo: (hash, record, key, relationship) ->
+    return if key == "history"
     id_key = record.get("#{relationship.key}_key") || "id"
     id = Ember.get(record, "#{relationship.key}.#{id_key}")
     hash[key] = id if @get('addEmptyBelongsTo') || !Ember.isEmpty(id)
@@ -286,8 +287,6 @@

   updateRecord: (store, type, record) ->
     json = @serialize(record, {associations: true, includeId: true })
-
-    delete json.history

     @_updateAttachmnets(record, json)
     @ajax(record.get('id'), 'PUT', {


c.coffee: tuning project closing a little bit

--- src/c.coffee
+++ src/c.coffee
@@ -107,9 +107,9 @@
                 return
             s.close_open_unused_projects
                 dry_run      : false
-                min_age_days : 60
-                max_age_days : 300
-                threads      : 5
+                min_age_days : 50
+                max_age_days : 1000
+                threads      : 2
                 host         : host
                 cb           : done()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/engine_selector.coffee
+++ src/engine_selector.coffee
@@ -100,6 +100,8 @@

   @name find
   @methodOf Engine.selector
+
+  @param
   ###
   find: (selector) ->
     results = []


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/engine_selector.coffee
+++ src/engine_selector.coffee
@@ -69,7 +69,8 @@

     Object.extend results, instanceMethods

-  first: (selector) -
+  first: (selector) ->
+    @

 Object.extend Engine.Selector,
   parse: (selector) ->


remove base64 image, we have asset uploader now

--- app/assets/javascripts/effective_ckeditor/init.js.coffee
+++ app/assets/javascripts/effective_ckeditor/init.js.coffee
@@ -70,7 +70,7 @@
     sharedSpaces:
       top: 'effective-ckeditor-top'
     startupShowBorders: true
-    extraPlugins: 'base64image,effective_regions,effective_assets,effective_menus,effective_references'
+    extraPlugins: 'effective_regions,effective_assets,effective_menus,effective_references'
     format_tags: 'p;h1;h2;h3;h4;h5;h6;pre;div'
     templates: 'effective_regions'
     templates_files: []