Separate out clauses in NinjaBuilder

Separate out clauses in NinjaBuilder

I found this useful for generating groups of targets e.g.

    all = ninja.edges.filter (c) ->
        'clean' not in c.targets
    .reduce (prev, c) ->
        prev.concat(c.targets)
    , []

    ninja.edge('all').from(all)

--- source/ninja-build-gen.coffee
+++ source/ninja-build-gen.coffee
@@ -134,7 +134,9 @@
     # build directory (where Ninja put logs and where you can put
     # intermediary products).
     constructor: (@version, @buildDir) ->
-        @clauses = []
+        @edges = []
+        @rules = []
+        @variables = []
         @edgeCount = 0
         @ruleCount = 0

@@ -151,20 +153,20 @@
     # Add a variable assignation into `name` from the `value`.
     assign: (name, value) ->
         clause = new NinjaAssignBuilder(name, value)
-        @clauses.push clause
+        @variables.push clause
         clause

     # Add a rule and return it.
     rule: (name) ->
         clause = new NinjaRuleBuilder(name)
-        @clauses.push clause
+        @rules.push clause
         @ruleCount++
         clause

     # Add an edge and return it.
     edge: (targets) ->
         clause = new NinjaEdgeBuilder(targets)
-        @clauses.push clause
+        @edges.push clause
         @edgeCount++
         clause

@@ -173,7 +175,7 @@
         stream.write @headerValue + '\n\n' if @headerValue?
         stream.write "ninja_required_version = #{@version}\n" if @version?
         stream.write "builddir=#{@buildDir}\n" if @buildDir?
-        for clause in @clauses
+        for clause in [].concat(@rules, @edges, @variables)
             clause.write stream
         stream.write "default #{@defaultRule}\n" if @defaultRule?


ForcedRegister: use a generic handler for redirection

--- client/Account/AppController.coffee
+++ client/Account/AppController.coffee
@@ -231,8 +231,13 @@

   showRegistrationNeededModal:->
     return if @modal
+
+    handler = (modal, route)->
+      modal.destroy()
+      KD.utils.wait 5000, KD.getSingleton("router").handleRoute route
+
     message = "Please login to proceed to the next step"
-    @modal = modal = new KDBlockingModalView
+    @modal = new KDBlockingModalView
       title           : "Koding Registration"
       content         : "
#{message}
"
       height          : "auto"
@@ -240,11 +245,10 @@
       buttons         :
         "Login"       :
           style       : "modal-clean-gray"
-          callback    : ->
-            modal.destroy()
-            KD.utils.wait 5000, KD.getSingleton("router").handleRoute "/Login"
+          callback    : => handler @modal, "/Login"
         "Register"    :
           style       : "modal-clean-gray"
-          callback    : ->
-            modal.destroy()
-            KD.utils.wait 5000, KD.getSingleton("router").handleRoute "/Register"
+          callback    : => handler @modal, "/Register"
+
+
+    @modal.on "KDObjectWillBeDestroyed", => @modal = null


remove todo

remove todo

--- client/app/MainApp/fs/fsfile.coffee
+++ client/app/MainApp/fs/fsfile.coffee
@@ -100,7 +100,6 @@

     return queue

-  # TODO: This method has too many logic with chunks etc. should be retought later. -- fka
   saveBinary:(contents, callback)->

     @abortRequested     = no


Don't clip screen positions that are directly translated from buffer positions.

The translation process is guaranteed to return a valid screen position so there's no need to clip the position again.

--- src/atom/cursor.coffee
+++ src/atom/cursor.coffee
@@ -17,9 +17,11 @@
   bufferChanged: (e) ->
     @setBufferPosition(e.newRange.end)

-  setScreenPosition: (position) ->
+  setScreenPosition: (position, options={}) ->
     position = Point.fromObject(position)
-    @screenPosition = @editor.clipScreenPosition(position)
+    clip = options.clip ? true
+
+    @screenPosition = if clip then @editor.clipScreenPosition(position) else position
     @bufferPosition = @editor.bufferPositionForScreenPosition(position)

     Object.freeze @screenPosition
@@ -34,7 +36,7 @@
     @idleTimeout = window.setTimeout (=> @addClass 'idle'), 200

   setBufferPosition: (bufferPosition) ->
-    @setScreenPosition(@editor.screenPositionForBufferPosition(bufferPosition))
+    @setScreenPosition(@editor.screenPositionForBufferPosition(bufferPosition), clip: false)

   refreshScreenPosition: ->
     @setBufferPosition(@bufferPosition)


Refresh line numbers in gutter by setting innerHTML for speed

--- src/atom/gutter.coffee
+++ src/atom/gutter.coffee
@@ -1,4 +1,4 @@
-{View, $$} = require 'space-pen'
+{View, $$$} = require 'space-pen'

 $ = require 'jquery'
 _ = require 'underscore'
@@ -11,7 +11,8 @@
   renderLineNumbers: ->
     lastRow = -1
     screenRows = @parentView.bufferRowsForScreenRows()
-    @html $$ ->
+
+    this.innerHTML = $$$ ->
       for row in screenRows
         @div {class: 'line-number'}, if row == lastRow then '•' else row + 1
         lastRow = row


fix coderwall url

fix coderwall url

--- src/scripts/coderwall.coffee
+++ src/scripts/coderwall.coffee
@@ -25,7 +25,7 @@
       """
     .join '\n'
     message = """
-      http://coderwall.com/#{profile.username}
+      https://coderwall.com/#{profile.username}
       github: https://github.com/#{profile.accounts.github}
       badges: #{profile.badges.length} (#{pager.page}/#{pager.pages})
       #{badgesString}


Fix "who is on call for xxx"

Fix "who is on call for xxx"

commit c6f15fa713b627f262710d02240c5b70800d1e61 changed the signature
of the callback used in this command, and no longer passes the message.

Change the callback for the single schedule case to match the
all-schedules case and remove the unused parameter.

--- src/scripts/pagerduty.coffee
+++ src/scripts/pagerduty.coffee
@@ -586,11 +586,11 @@

     if scheduleName?
       withScheduleMatching msg, scheduleName, (s) ->
-        renderSchedule s, (err, text) ->
+        renderSchedule s, (err) ->
           if err?
             robot.emit 'error'
             return
-          msg.send text
+          msg.send messages.join("\n")
     else
       pagerduty.getSchedules (err, schedules) ->
         if err?


Add debuggable property

Add debuggable property

--- src/board/board.coffee
+++ src/board/board.coffee
@@ -93,6 +93,15 @@
   )

   ###*
+  @property {boolean} debuggable
+    Debug support
+  @readonly
+  ###
+  @property("debuggable",
+    get: -> false
+  )
+
+  ###*
   @property {boolean} connected
   ###
   @property("connected", get: -> @_connected)
@@ -144,6 +153,7 @@
     Firmware instance
   ###
   loadFirmware: ->
+    return I18n.rejectPromise("Firmware_is_not_selected") unless @_firmwareId?
     return Promise.resolve(@_firmware) if @_firmware?
     return Promise.resolve(
     ).then(=>


Fix incomplete sample list bug. Fix #70

Fix incomplete sample list bug. Fix #70

--- src/taxonomyViz.coffee
+++ src/taxonomyViz.coffee
@@ -1453,7 +1453,7 @@
 					content += 'no samples'
 				else if count[i].length == 1
 					content += count[i][0]
-				else if count[i].length > 2
+				else if count[i].length >= 2
 					for j in [0..count[i].length-2]
 						content += count[i][j] + ', '
 					content += count[i][count[i].length - 1]
@@ -1526,7 +1526,7 @@
 						content += '' + attributes_array[i] + ':  '
 						if count[i].length == 1
 							content += selected_phinchID_array[count[i][0]] + ' (' + count[i][0] + ')'
-						else if count[i].length > 2
+						else if count[i].length >= 2
 							for j in [0..count[i].length-2]
 								content += selected_phinchID_array[count[i][j]] + ' (' + count[i][j] + '), '
 							content += selected_phinchID_array[count[i][count[i].length - 1]] + ' (' + count[i][count[i].length - 1] + ')'


Fix color error on startup message

Fix color error on startup message

--- bin/www.coffee
+++ bin/www.coffee
@@ -69,7 +69,7 @@
 bind = if typeof port == 'string' then "Pipe #{port}" else "Port #{port}"

 console.log chalk.cyan 'SiseTV is now running'
-console.log chalk.cyan "Visit #{chalk.magenta 'http://localhost:' + app.get 'port'}/admin to see it"
+console.log chalk.cyan "Visit #{chalk.magenta 'http://localhost:' + (app.get 'port') + /admin} to see it"


fix tests

fix tests

--- spec/pane-element-spec.coffee
+++ spec/pane-element-spec.coffee
@@ -122,11 +122,11 @@
           item1.changePathCallbacks = []
           item1.setPath = (path) ->
             @path = path
-            callback() for callback in changePathCallbacks
+            callback() for callback in @changePathCallbacks
             return
           item1.getPath = -> @path
           item1.onDidChangePath = (callback) ->
-            @changePathCallbacks.push(callback)
+            @changePathCallbacks.push callback
             return dispose: =>
               @changePathCallbacks = @changePathCallbacks.filter (f) -> f isnt callback


:bug: fix selection of penultimate row to retrieve line ending

--- src/text-editor.coffee
+++ src/text-editor.coffee
@@ -1132,7 +1132,7 @@

         # Delete lines spanned by selection and insert them on the preceding buffer row
         lines = @buffer.getTextInRange(linesRange)
-        lines += @buffer.lineEndingForRow(linesRange.end.row - 1) unless lines[lines.length - 1] is '\n'
+        lines += @buffer.lineEndingForRow(linesRange.end.row - 2) unless lines[lines.length - 1] is '\n'
         @buffer.delete(linesRange)
         @buffer.insert([precedingRow, 0], lines)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/text_effect.coffee
+++ src/text_effect.coffee
@@ -24,6 +24,7 @@

 @name draw
 @methodOf TextEffect#
+@params
 @event
 ###
 TextEffect = (I={}) ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/text_effect.coffee
+++ src/text_effect.coffee
@@ -11,7 +11,8 @@

 ###*
 Updates the position of the text based on the effect velocity. Updates the
-alpha based on the elapsed time since the effect creation. If rotationalVeloci
+alpha based on the elapsed time since the effect creation. If `rotationalVelocity
+is provided

 @name update
 @methodOf TextEffect#


Remove shims that call `super`

Remove shims that call `super`

They were previously there for documentation purposes, but we've decided
to document them in the text-buffer npm directly and link it into the
documentation. This drops some of the docs on delegated methods, because
our docs generator doesn't understand `delegatesMethods` declarations,
but TextBuffer was marked private anyway so we can get back to it later.

--- src/text-buffer.coffee
+++ src/text-buffer.coffee
@@ -262,44 +262,11 @@
     lastRow = @getLastRow()
     new Range([0, 0], [lastRow, @lineLengthForRow(lastRow)])

-  # Given a range, returns the lines of text within it.
-  #
-  # range - A {Range} object specifying your points of interest
-  #
-  # Returns a {String} of the combined lines.
-  getTextInRange: (range) -> super
-
-  # Gets all the lines in a file.
-  #
-  # Returns an {Array} of {String}s.
-  getLines: -> super
-
-  # Given a row, returns the line of text.
-  #
-  # row - A {Number} indicating the row.
-  #
-  # Returns a {String}.
-  lineForRow: (row) -> super
-
-  # Given a row, returns its line ending.
-  #
-  # row - A {Number} indicating the row.
-  #
-  # Returns a {String}, or `undefined` if `row` is the final row.
-  lineEndingForRow: (row) -> super
-
   suggestedLineEndingForRow: (row) ->
     if row is @getLastRow()
       @lineEndingForRow(row - 1)
     else
       @lineEndingForRow(row)
-
-  # Given a row, returns the length of the line of text.
-  #
-  # row - A {Number} indicating the row.
-  #
-  # Returns a {Number}.
-  lineLengthForRow: (row) -> super

   # Given a row, returns the length of the line ending
   #
@@ -322,16 +289,6 @@
     else
       new Range([row, 0], [row, @lineLengthForRow(row)])

-  # Gets the number of lines in a file.
-  #
-  # Returns a {Number}.
-  getLineCount: -> super
-
-  # Gets the row number of the last line.
-  #
-  # Returns a {Number}.
-  getLastRow: -> super
-
   # Finds the last line in the current buffer.
   #
   # Returns a {String}.
@@ -344,10 +301,6 @@
   getEofPosition: ->
     lastRow = @getLastRow()
     new Point(lastRow, @lineLengthForRow(lastRow))
-
-  characterIndexForPosition: (position) -> super
-
-  positionForCharacterIndex: (index) -> super

   # Given a row, this deletes it from the buffer.
   #
@@ -392,32 +345,6 @@
   delete: (range) ->
     @change(range, '')

-  # Given a position, this clips it to a real position.
-  #
-  # For example, if `position`'s row exceeds the row count of the buffer,
-  # or if its column goes beyond a line's length, this "sanitizes" the value
-  # to a real position.
-  #
-  # Returns the new, clipped {Point}. Note that this could be the same as `position` if no clipping was performed.
-  clipPosition: (position) -> super
-
-  # Given a range, this clips it to a real range.
-  #
-  # For example, if `range`'s row exceeds the row count of the buffer,
-  # or if its column goes beyond a line's length, this "sanitizes" the value
-  # to a real range.
-  #
-  # range - The {Range} to clip
-  #
-  # Returns the new, clipped {Range}. Note that this could be the same as `range` if no clipping was performed.
-  clipRange: (range) ->
-    range = Range.fromObject(range)
-    new Range(@clipPosition(range.start), @clipPosition(range.end))
-
-  undo: -> super
-
-  redo: -> super
-
   # Saves the buffer.
   save: ->
     @saveAs(@getPath()) if @isModified()
@@ -453,61 +380,14 @@
   # Returns a {Boolean}.
   isInConflict: -> @conflict

-  # Identifies if a buffer is empty.
-  #
-  # Returns a {Boolean}.
-  isEmpty: -> super
-
-  # Returns all valid {Marker}s on the buffer.
-  getMarkers: -> super
-
-  # Returns the {Marker} with the given id.
-  getMarker: (id) -> super
-
   destroyMarker: (id) ->
     @getMarker(id)?.destroy()
-
-  # Public: Finds the first marker satisfying the given attributes
-  #
-  # Returns a {String} marker-identifier
-  findMarker: (attributes) -> super
-
-  # Public: Finds all markers satisfying the given attributes
-  #
-  # attributes - The attributes against which to compare the markers' attributes
-  #   There are some reserved keys that match against derived marker properties:
-  #   startRow - The row at which the marker starts
-  #   endRow - The row at which the marker ends
-  #
-  # Returns an {Array} of {Marker}s
-  findMarkers: (attributes) -> super

   # Retrieves the quantity of markers in a buffer.
   #
   # Returns a {Number}.
   getMarkerCount: ->
     @getMarkers().length
-
-  # Constructs a new marker at a given range.
-  #
-  # range - The marker {Range} (representing the distance between the head and tail)
-  # attributes - An optional hash of serializable attributes
-  #   Any attributes you pass will be associated with the marker and can be retrieved
-  #   or used in marker queries.
-  #   The following attribute keys reserved, and control the marker's initial range
-  #   isReversed - if `true`, the marker is reversed; that is, its head precedes the tail
-  #   hasTail - if `false`, the marker is created without a tail
-  #
-  # Returns a {Number} representing the new marker's ID.
-  markRange: (range, options={}) -> super
-
-  # Constructs a new marker at a given position.
-  #
-  # position - The marker {Point}; there won't be a tail
-  # options - Options to pass to the {Marker} constructor
-  #
-  # Returns a {Number} representing the new marker's ID.
-  markPosition: (position, options) -> super

   # Identifies if a character sequence is within a certain range.
   #


:memo: Convert TextBuffer docs

:memo: Convert TextBuffer docs

--- src/text-buffer.coffee
+++ src/text-buffer.coffee
@@ -19,52 +19,67 @@
 #
 # ## Events
 #
-# * `changed` -
-#      Emitted synchronously whenever the buffer changes. Binding a slow handler
-#      to this event has the potential to destroy typing performance. Consider
-#      using `contents-modified` instead and aim for extremely fast performance
-#      (< 2 ms) if you must bind to it. Your handler will be called with an
-#      object containing the following keys.
-#      * `oldRange` - The {Range} of the old text
-#      * `newRange` - The {Range} of the new text
-#      * `oldText` - A {String} containing the text that was replaced
-#      * `newText` - A {String} containing the text that was inserted
-#
-# * `markers-updated` -
-#      Emitted synchronously when the `changed` events of all markers have been
-#      fired for a change. The order of events is as follows:
-#      * The text of the buffer is changed
-#      * All markers are updated accordingly, but their `changed` events are not
-#        emited
-#      * The `changed` event is emitted
-#      * The `changed` events of all updated markers are emitted
-#      * The `markers-updated` event is emitted.
-#
-# * `contents-modified` -
-#      Emitted asynchronously 300ms (or `TextBuffer::stoppedChangingDelay`)
-#      after the last buffer change. This is a good place to handle changes to
-#      the buffer without compromising typing performance.
-#
-# * `modified-status-changed` -
-#      Emitted with a {Boolean} when the result of {::isModified} changes.
-#
-# * `contents-conflicted` -
-#      Emitted when the buffer's underlying file changes on disk at a moment
-#      when the result of {::isModified} is true.
-#
-# * `will-reload` -
-#      Emitted before the in-memory contents of the buffer are refreshed from
-#      the contents of the file on disk.
-#
-# * `reloaded` -
-#      Emitted after the in-memory contents of the buffer are refreshed from
-#      the contents of the file on disk.
-#
-# * `will-be-saved` - Emitted before the buffer is saved to disk.
-#
-# * `saved` - Emitted after the buffer is saved to disk.
-#
-# * `destroyed` - Emitted when the buffer is destroyed.
+# ### contents-modified
+#
+# Public: Emitted asynchronously 300ms (or `TextBuffer::stoppedChangingDelay`)
+# after the last buffer change. This is a good place to handle changes to
+# the buffer without compromising typing performance.
+#
+# ### contents-conflicted
+#
+# Public: Emitted when the buffer's underlying file changes on disk at a moment
+# when the result of {::isModified} is true.
+#
+# ### will-reload
+#
+# Public: Emitted before the in-memory contents of the buffer are refreshed from
+# the contents of the file on disk.
+#
+# ### reloaded
+#
+# Public: Emitted after the in-memory contents of the buffer are refreshed from
+# the contents of the file on disk.
+#
+# ### will-be-saved
+#
+# Public: Emitted before the buffer is saved to disk.
+#
+# ### saved
+#
+# Public: Emitted after the buffer is saved to disk.
+#
+# ### changed
+#
+# Public: Emitted synchronously whenever the buffer changes. Binding a slow handler
+# to this event has the potential to destroy typing performance. Consider
+# using {:contents-modified} instead and aim for extremely fast performance
+# (< 2 ms) if you must bind to it. Your handler will be called with an
+# object containing the following keys.
+#
+# * `event` {Object}
+#   * `oldRange` The {Range} of the old text
+#   * `newRange` The {Range} of the new text
+#   * `oldText` A {String} containing the text that was replaced
+#   * `newText` A {String} containing the text that was inserted
+#
+# ### markers-updated
+#
+# Public: Emitted synchronously when the `changed` events of all markers have been
+# fired for a change. The order of events is as follows:
+#
+# * The text of the buffer is changed
+# * All markers are updated accordingly, but their `changed` events are not emited
+# * The `changed` event is emitted
+# * The `changed` events of all updated markers are emitted
+# * The `markers-updated` event is emitted.
+#
+# ### modified-status-changed
+#
+# Public: Emitted with a {Boolean} when the result of {::isModified} changes.
+#
+# ### destroyed
+#
+# Public: Emitted when the buffer is destroyed.
 module.exports =
 class TextBuffer
   @Point: Point
@@ -86,10 +101,10 @@

   # Public: Create a new buffer with the given params.
   #
-  # params - A {String} of text or an {Object} with the following keys:
-  #   :load - A {Boolean}, `true` to asynchronously load the buffer from disk
-  #           after initialization.
-  #   :text - The initial {String} text of the buffer.
+  # * `params` A {String} of text or an {Object} with the following keys:
+  #   * `load` A {Boolean}, `true` to asynchronously load the buffer from disk
+  #     after initialization.
+  #   * `text` The initial {String} text of the buffer.
   constructor: (params) ->
     text = params if typeof params is 'string'

@@ -163,7 +178,7 @@

   # Public: Get the text of the line at the given row, without its line ending.
   #
-  # row - A {Number} representing a 0-indexed row.
+  # * `row` A {Number} representing a 0-indexed row.
   #
   # Returns a {String}.
   lineForRow: (row) ->
@@ -178,7 +193,7 @@

   # Public: Get the line ending for the given 0-indexed row.
   #
-  # row - A {Number} indicating the row.
+  # * `row` A {Number} indicating the row.
   #
   # The returned newline is represented as a literal string: `'\n'`, `'\r'`,
   # `'\r\n'`, or `''` for the last line of the buffer, which doesn't end in a
@@ -191,7 +206,7 @@
   # Public: Get the length of the line for the given 0-indexed row, without its
   # line ending.
   #
-  # row - A {Number} indicating the row.
+  # * `row` A {Number} indicating the row.
   #
   # Returns a {Number}.
   lineLengthForRow: (row) ->
@@ -199,7 +214,7 @@

   # Public: Replace the entire contents of the buffer with the given text.
   #
-  # text - A {String}
+  # * `text` A {String}
   #
   # Returns a {Range} spanning the new buffer contents.
   setText: (text) ->
@@ -208,7 +223,7 @@
   # Public: Replace the current buffer contents by applying a diff based on the
   # given text.
   #
-  # text - A {String} containing the new buffer contents.
+  # * `text` A {String} containing the new buffer contents.
   setTextViaDiff: (text) ->
     currentText = @getText()
     return if currentText == text
@@ -254,8 +269,9 @@

   # Public: Set the text in the given range.
   #
-  # range - A {Range}.
-  # text - A {String}.
+  # * `range` A {Range}
+  # * `text` A {String}
+  # * `normalizeLineEndings` (optional) {Boolean} (default: true)
   #
   # Returns the {Range} of the inserted text.
   setTextInRange: (range, text, normalizeLineEndings=true) ->
@@ -266,9 +282,10 @@

   # Public: Insert text at the given position.
   #
-  # position - A {Point} representing the insertion location. The position is
-  #            clipped before insertion.
-  # text - A {String} representing the text to insert.
+  # * `position` A {Point} representing the insertion location. The position is
+  #   clipped before insertion.
+  # * `text` A {String} representing the text to insert.
+  # * `normalizeLineEndings` (optional) {Boolean} (default: true)
   #
   # Returns the {Range} of the inserted text.
   insert: (position, text, normalizeLineEndings) ->
@@ -276,7 +293,7 @@

   # Public: Append text to the end of the buffer.
   #
-  # text - A {String} representing the text text to append.
+  # * `text` A {String} representing the text text to append.
   #
   # Returns the {Range} of the inserted text
   append: (text, normalizeLineEndings) ->
@@ -284,7 +301,7 @@

   # Public: Delete the text in the given range.
   #
-  # range - A {Range} in which to delete. The range is clipped before deleting.
+  # * `range` A {Range} in which to delete. The range is clipped before deleting.
   #
   # Returns an empty {Range} starting at the start of deleted range.
   delete: (range) ->
@@ -292,7 +309,7 @@

   # Public: Delete the line associated with a specified row.
   #
-  # row - A {Number} representing the 0-indexed row to delete.
+  # * `row` A {Number} representing the 0-indexed row to delete.
   #
   # Returns the {Range} of the deleted text.
   deleteRow: (row) ->
@@ -300,11 +317,11 @@

   # Public: Delete the lines associated with the specified row range.
   #
-  # startRow - A {Number} representing the first row to delete.
-  # endRow - A {Number} representing the last row to delete, inclusive.
-  #
   # If the row range is out of bounds, it will be clipped. If the startRow is
   # greater than the end row, they will be reordered.
+  #
+  # * `startRow` A {Number} representing the first row to delete.
+  # * `endRow` A {Number} representing the last row to delete, inclusive.
   #
   # Returns the {Range} of the deleted text.
   deleteRows: (startRow, endRow) ->
@@ -401,7 +418,7 @@

   # Public: Get the text in a range.
   #
-  # range - A {Range}
+  # * `range` A {Range}
   #
   # Returns a {String}
   getTextInRange: (range) ->
@@ -427,10 +444,10 @@

   # Public: Clip the given range so it starts and ends at valid positions.
   #
-  # For example, the position [1, 100] is out of bounds if the line at row 1 is
-  # only 10 characters long, and it would be clipped to (1, 10).
-  #
-  # range - A {Range} or range-compatible {Array} to clip.
+  # For example, the position `[1, 100]` is out of bounds if the line at row 1 is
+  # only 10 characters long, and it would be clipped to `(1, 10)`.
+  #
+  # * `range` A {Range} or range-compatible {Array} to clip.
   #
   # Returns the given {Range} if it is already in bounds, or a new clipped
   # {Range} if the given range is out-of-bounds.
@@ -448,7 +465,7 @@
   # For example, the position (1, 100) is out of bounds if the line at row 1 is
   # only 10 characters long, and it would be clipped to (1, 10)
   #
-  # position - A {Point} or point-compatible {Array}.
+  # * `position` A {Point} or point-compatible {Array}.
   #
   # Returns a new {Point} if the given position is invalid, otherwise returns
   # the given position.
@@ -488,10 +505,10 @@

   # Public: Get the range for the given row
   #
-  # row - A {Number} representing a 0-indexed row.
-  # includeNewline - A {Boolean} indicating whether or not to include the
-  #                  newline, which results in a range that extends to the start
-  #                  of the next line.
+  # * `row` A {Number} representing a 0-indexed row.
+  # * `includeNewline` A {Boolean} indicating whether or not to include the
+  #   newline, which results in a range that extends to the start
+  #   of the next line.
   #
   # Returns a {Range}.
   rangeForRow: (row, includeNewline) ->
@@ -510,7 +527,7 @@
   #
   # The position is clipped prior to translating.
   #
-  # position - A {Point}.
+  # * `position` A {Point}.
   #
   # Returns a {Number}.
   characterIndexForPosition: (position) ->
@@ -527,7 +544,7 @@
   #
   # The offset is clipped prior to translating.
   #
-  # offset - A {Number}.
+  # * `offset` A {Number}.
   #
   # Returns a {Point}.
   positionForCharacterIndex: (offset) ->
@@ -665,7 +682,7 @@

   # Public: Set the path for the buffer's associated file.
   #
-  # filePath - A {String} representing the new file path
+  # * `filePath` A {String} representing the new file path
   setPath: (filePath) ->
     return if filePath == @getPath()

@@ -690,7 +707,7 @@

   # Public: Save the buffer at a specific path.
   #
-  # filePath - The path to save at.
+  # * `filePath` The path to save at.
   saveAs: (filePath) ->
     unless filePath then throw new Error("Can't save buffer with no file path")

@@ -729,9 +746,9 @@

   # Identifies if a character sequence is within a certain range.
   #
-  # regex - The {RegExp} to match.
-  # startIndex - A {Number} representing the starting character offset.
-  # endIndex - A {Number} representing the ending character offset.
+  # * `regex` The {RegExp} to match.
+  # * `startIndex` A {Number} representing the starting character offset.
+  # * `endIndex` A {Number} representing the ending character offset.
   #
   # Returns an {Array} of matches for the given regex.
   matchesInCharacterRange: (regex, startIndex, endIndex) ->
@@ -763,15 +780,13 @@
   # If you're programmatically modifying the results, you may want to try
   # {::backwardsScan} to avoid tripping over your own changes.
   #
-  # regex - A {RegExp} to search for.
-  # iterator -
-  #   A {Function} that's called on each match with an {Object} containing the.
-  #   following keys:
-  #   :match - The current regular expression match.
-  #   :matchText - A {String} with the text of the match.
-  #   :range - The {Range} of the match.
-  #   :stop - Call this {Function} to terminate the scan.
-  #   :replace - Call this {Function} with a {String} to replace the match.
+  # * `regex` A {RegExp} to search for.
+  # * `iterator` A {Function} that's called on each match with an {Object} containing the following keys:
+  #   * `match` The current regular expression match.
+  #   * `matchText` A {String} with the text of the match.
+  #   * `range` The {Range} of the match.
+  #   * `stop` Call this {Function} to terminate the scan.
+  #   * `replace` Call this {Function} with a {String} to replace the match.
   scan: (regex, iterator) ->
     @scanInRange regex, @getRange(), (result) =>
       result.lineText = @lineForRow(result.range.start.row)
@@ -781,15 +796,13 @@
   # Public: Scan regular expression matches in the entire buffer in reverse
   # order, calling the given iterator function on each match.
   #
-  # regex - A {RegExp} to search for.
-  # iterator -
-  #   A {Function} that's called on each match with an {Object} containing the.
-  #   following keys:
-  #   :match - The current regular expression match.
-  #   :matchText - A {String} with the text of the match.
-  #   :range - The {Range} of the match.
-  #   :stop - Call this {Function} to terminate the scan.
-  #   :replace - Call this {Function} with a {String} to replace the match.
+  # * `regex` A {RegExp} to search for.
+  # * `iterator` A {Function} that's called on each match with an {Object} containing the following keys:
+  #   * `match` The current regular expression match.
+  #   * `matchText` A {String} with the text of the match.
+  #   * `range` The {Range} of the match.
+  #   * `stop` Call this {Function} to terminate the scan.
+  #   * `replace` Call this {Function} with a {String} to replace the match.
   backwardsScan: (regex, iterator) ->
     @backwardsScanInRange regex, @getRange(), (result) =>
       result.lineText = @lineForRow(result.range.start.row)
@@ -798,8 +811,8 @@

   # Public: Replace all regular expression matches in the entire buffer.
   #
-  # regex - A {RegExp} representing the matches to be replaced.
-  # replacementText - A {String} representing the text to replace each match.
+  # * `regex` A {RegExp} representing the matches to be replaced.
+  # * `replacementText` A {String} representing the text to replace each match.
   #
   # Returns a {Number} representing the number of replacements made.
   replace: (regex, replacementText) ->
@@ -818,16 +831,14 @@
   # Public: Scan regular expression matches in a given range , calling the given
   # iterator function on each match.
   #
-  # regex - A {RegExp} to search for.
-  # range - A {Range} in which to search.
-  # iterator -
-  #   A {Function} that's called on each match with an {Object} containing the.
-  #   following keys:
-  #   :match - The current regular expression match.
-  #   :matchText - A {String} with the text of the match.
-  #   :range - The {Range} of the match.
-  #   :stop - Call this {Function} to terminate the scan.
-  #   :replace - Call this {Function} with a {String} to replace the match.
+  # * `regex` A {RegExp} to search for.
+  # * `range` A {Range} in which to search.
+  # * `iterator` A {Function} that's called on each match with an {Object} containing the following keys
+  #   * `match` The current regular expression match.
+  #   * `matchText` A {String} with the text of the match.
+  #   * `range` The {Range} of the match.
+  #   * `stop` Call this {Function} to terminate the scan.
+  #   * `replace` Call this {Function} with a {String} to replace the match.
   scanInRange: (regex, range, iterator, reverse=false) ->
     range = @clipRange(range)
     global = regex.global
@@ -869,22 +880,20 @@
   # Public: Scan regular expression matches in a given range in reverse order,
   # calling the given iterator function on each match.
   #
-  # regex - A {RegExp} to search for.
-  # range - A {Range} in which to search.
-  # iterator -
-  #   A {Function} that's called on each match with an {Object} containing the.
-  #   following keys:
-  #   :match - The current regular expression match.
-  #   :matchText - A {String} with the text of the match.
-  #   :range - The {Range} of the match.
-  #   :stop - Call this {Function} to terminate the scan.
-  #   :replace - Call this {Function} with a {String} to replace the match.
+  # * `regex` A {RegExp} to search for.
+  # * `range` A {Range} in which to search.
+  # * `iterator` A {Function} that's called on each match with an {Object} containing the following keys:
+  #   * `match` The current regular expression match.
+  #   * `matchText` A {String} with the text of the match.
+  #   * `range` The {Range} of the match.
+  #   * `stop` Call this {Function} to terminate the scan.
+  #   * `replace` Call this {Function} with a {String} to replace the match.
   backwardsScanInRange: (regex, range, iterator) ->
     @scanInRange regex, range, iterator, true

   # Public: Determine if the given row contains only whitespace.
   #
-  # row - A {Number} representing a 0-indexed row.
+  # * `row` A {Number} representing a 0-indexed row.
   #
   # Returns a {Boolean}.
   isRowBlank: (row) ->
@@ -892,7 +901,7 @@

   # Public: Given a row, find the first preceding row that's not blank.
   #
-  # startRow - A {Number} identifying the row to start checking at.
+  # * `startRow` A {Number} identifying the row to start checking at.
   #
   # Returns a {Number} or `null` if there's no preceding non-blank row.
   previousNonBlankRow: (startRow) ->
@@ -905,7 +914,7 @@

   # Public: Given a row, find the next row that's not blank.
   #
-  # startRow - A {Number} identifying the row to start checking at.
+  # * `startRow` A {Number} identifying the row to start checking at.
   #
   # Returns a {Number} or `null` if there's no next non-blank row.
   nextNonBlankRow: (startRow) ->
@@ -966,7 +975,7 @@
   # abort the transaction, call {::abortTransaction} to terminate the function's
   # execution and revert any changes performed up to the abortion.
   #
-  # fn - A {Function} to call inside the transaction.
+  # * `fn` A {Function} to call inside the transaction.
   transact: (fn) -> @history.transact(fn)

   # Public: Start an open-ended transaction.
@@ -995,44 +1004,33 @@
   # word, the marker will remain over that word even if the word's location in
   # the buffer changes.
   #
-  # * range: A {Range} or range-compatible {Array}
-  # * properties:
-  #     A hash of key-value pairs to associate with the marker. There are also
-  #     reserved property names that have marker-specific meaning:
-  #       :reversed -
-  #         Creates the marker in a reversed orientation. Defaults to false.
-  #       :persistent -
-  #         Whether to include this marker when serializing the buffer. Defaults
-  #         to true.
-  #       :invalidate -
-  #         Determines the rules by which changes to the buffer *invalidate* the
-  #         marker. Defaults to 'overlap', but can be any of the following
-  #         strategies, in order of fragility:
-  #         * 'never':
-  #             The marker is never marked as invalid. This is a good choice for
-  #             markers representing selections in an editor.
-  #         * 'surround':
-  #             The marker is invalidated by changes that completely surround it.
-  #         * 'overlap':
-  #             The marker is invalidated by changes that surround the start or
-  #             end of the marker. This is the default.
-  #         * 'inside':
-  #             The marker is invalidated by changes that extend into the
-  #             inside of the marker. Changes that end at the marker's start or
-  #             start at the marker's end do not invalidate the marker.
-  #         * 'touch':
-  #             The marker is invalidated by a change that touches the marked
-  #             region in any way, including changes that end at the marker's
-  #             start or start at the marker's end. This is the most fragile
-  #             strategy.
+  # * `range` A {Range} or range-compatible {Array}
+  # * `properties` A hash of key-value pairs to associate with the marker. There are also reserved property names that have marker-specific meaning.
+  #   * `reversed` (optional) Creates the marker in a reversed orientation. (default: false)
+  #   * `persistent` (optional) Whether to include this marker when serializing the buffer. (default: true)
+  #   * `invalidate` (optional) Determines the rules by which changes to the
+  #     buffer *invalidate* the marker. (default: 'overlap') It can be any of
+  #     the following strategies, in order of fragility
+  #     * __never__: The marker is never marked as invalid. This is a good choice for
+  #       markers representing selections in an editor.
+  #     * __surround__: The marker is invalidated by changes that completely surround it.
+  #     * __overlap__: The marker is invalidated by changes that surround the
+  #       start or end of the marker. This is the default.
+  #     * __inside__: The marker is invalidated by changes that extend into the
+  #       inside of the marker. Changes that end at the marker's start or
+  #       start at the marker's end do not invalidate the marker.
+  #     * __touch__: The marker is invalidated by a change that touches the marked
+  #       region in any way, including changes that end at the marker's
+  #       start or start at the marker's end. This is the most fragile
+  #       strategy.
   #
   # Returns a {Marker}.
   markRange: (range, properties) -> @markers.markRange(range, properties)

   # Public: Create a marker at the given position with no tail.
   #
-  # :position - {Point} or point-compatible {Array}
-  # :properties - This is the same as the `properties` parameter in {::markRange}
+  #   * `position` {Point} or point-compatible {Array}
+  #   * `properties` This is the same as the `properties` parameter in {::markRange}
   #
   # Returns a {Marker}.
   markPosition: (position, properties) -> @markers.markPosition(position, properties)
@@ -1049,18 +1047,17 @@

   # Public: Find markers conforming to the given parameters.
   #
-  # :params -
-  #   A hash of key-value pairs constraining the set of returned markers. You
+  # * `params` A hash of key-value pairs constraining the set of returned markers. You
   #   can query against custom marker properties by listing the desired
   #   key-value pairs here. In addition, the following keys are reserved and
   #   have special semantics:
-  #   * 'startPosition': Only include markers that start at the given {Point}.
-  #   * 'endPosition': Only include markers that end at the given {Point}.
-  #   * 'containsPoint': Only include markers that contain the given {Point}, inclusive.
-  #   * 'containsRange': Only include markers that contain the given {Range}, inclusive.
-  #   * 'startRow': Only include markers that start at the given row {Number}.
-  #   * 'endRow': Only include markers that end at the given row {Number}.
-  #   * 'intersectsRow': Only include markers that intersect the given row {Number}.
+  #   * `startPosition` Only include markers that start at the given {Point}.
+  #   * `endPosition` Only include markers that end at the given {Point}.
+  #   * `containsPoint` Only include markers that contain the given {Point}, inclusive.
+  #   * `containsRange` Only include markers that contain the given {Range}, inclusive.
+  #   * `startRow` Only include markers that start at the given row {Number}.
+  #   * `endRow` Only include markers that end at the given row {Number}.
+  #   * `intersectsRow` Only include markers that intersect the given row {Number}.
   #
   # Finds markers that conform to all of the given parameters. Markers are
   # sorted based on their position in the buffer. If two markers start at the


Minor spell fix `conlict` to `conflict`

Minor spell fix `conlict` to `conflict`

--- src/text-buffer.coffee
+++ src/text-buffer.coffee
@@ -1421,7 +1421,7 @@
       previousContents = @cachedDiskContents

       # Synchrounously update the disk contents because the {File} has already cached them. If the
-      # contents updated asynchrounously multiple `conlict` events could trigger for the same disk
+      # contents updated asynchrounously multiple `conflict` events could trigger for the same disk
       # contents.
       @updateCachedDiskContentsSync()
       return if previousContents == @cachedDiskContents


update typo in comment

update typo in comment

--- src/text-editor.coffee
+++ src/text-editor.coffee
@@ -912,7 +912,7 @@
         else
           linesRange = new Range(linesRangeStart, [selection.end.row + 1, 0])

-        # If selected line range is preceded by a fold, one line above on screen
+        # If selected line range is followed by a fold, one line below on screen
         # could be multiple lines in the buffer.
         followingScreenRow = @screenRowForBufferRow(linesRange.end.row) + 1
         followingBufferRow = @bufferRowForScreenRow(followingScreenRow)


:racehorse: Speed up `destroyFoldsIntersectingBufferRange`

--- src/text-editor.coffee
+++ src/text-editor.coffee
@@ -2776,8 +2776,11 @@

   # Remove any {Fold}s found that intersect the given buffer row.
   destroyFoldsIntersectingBufferRange: (bufferRange) ->
-    for row in [bufferRange.start.row..bufferRange.end.row]
-      @unfoldBufferRow(row)
+    @unfoldBufferRow(bufferRange.start.row)
+    @unfoldBufferRow(bufferRange.end.row)
+
+    for row in [bufferRange.end.row..bufferRange.start.row]
+      fold.destroy() for fold in @displayBuffer.foldsStartingAtBufferRow(row)

   # {Delegates to: DisplayBuffer.largestFoldContainingBufferRow}
   largestFoldContainingBufferRow: (bufferRow) ->


Handles canceling plan with new endpoint

--- apps/settings/components/cancel_plan/view.coffee
+++ apps/settings/components/cancel_plan/view.coffee
@@ -1,3 +1,4 @@
+Promise = require 'bluebird-q'
 Backbone = require 'backbone'
 template = -> require('./index.jade') arguments...

@@ -14,24 +15,27 @@

     $target.text 'Canceling'

-    @model.related().subscription.destroy()
-      .then =>
-        location.reload()
+    Promise $.ajax
+      method: 'POST'
+      url: "#{@model.related().subscriptions.url}/cancel"

-        $target.text 'Canceled'
+    .then =>
+      location.reload()

-      , (error) =>
-        $target.text label
+      $target.text 'Canceled'

+    .catch (error) =>
+      $target.text label
+
+      @els.errors
+        .show()
+        .text error.message
+
+      setTimeout () =>
         @els.errors
-          .show()
-          .text error.message
-
-        setTimeout () =>
-          @els.errors
-            .empty()
-            .hide()
-        , 2000
+          .empty()
+          .hide()
+      , 2000

   postRender: ->
     @els =


Fix extensions path

Fix extensions path

--- src/hull.coffee
+++ src/hull.coffee
@@ -7,8 +7,8 @@
     hull = { config }
     hull.app = Aura(config)
     hull.app
-        .use('extensions/aura-handlebars')
-        .use('extensions/aura-backbone')
+        .use('aura-extensions/aura-handlebars')
+        .use('aura-extensions/aura-backbone')
         .use('lib/ext/api')
         .use('lib/ext/auth')
         .use('lib/ext/widget')


SyncController: rename SyncLocalController to LocalSyncController and change the singleton name.

--- client/landing/Login/AppView.coffee
+++ client/landing/Login/AppView.coffee
@@ -389,7 +389,7 @@
     (KD.getSingleton 'mainController').handleFinishRegistration formData, @bound 'afterLoginCallback'

   doLogin:(credentials)->
-    KD.singletons.syncLocalController.removeLocalContents()
+    KD.singletons.localSync.removeLocalContents()
     (KD.getSingleton 'mainController').handleLogin credentials, @bound 'afterLoginCallback'

   runExternal = (token)->


use string path instead of object with url

--- app/assets/javascripts/gmaps/presenter.coffee
+++ app/assets/javascripts/gmaps/presenter.coffee
@@ -6,8 +6,8 @@
     @markers = $('#map-data').data('markers') unless @markers
     @infowindow = new (google.maps.InfoWindow)({ maxWidth: 200 })

-    @marker_image_default = { url: Clarat.GMaps.Operation.ConstructMap.markerUrl('gmaps_marker_1') }
-    @marker_image_highlighted = { url: Clarat.GMaps.Operation.ConstructMap.markerUrl('gmaps_marker_highlighted') }
+    @marker_image_default = Clarat.GMaps.Operation.ConstructMap.markerUrl('gmaps_marker_1')
+    @marker_image_highlighted = Clarat.GMaps.Operation.ConstructMap.markerUrl('gmaps_marker_highlighted')

     return unless @markers


EDSC-430: fix the tour from breaking the logo link

--- app/assets/javascripts/modules/help.js.coffee
+++ app/assets/javascripts/modules/help.js.coffee
@@ -201,6 +201,7 @@
     if queue[index]?
       queue[index].cleanup?(next, close)
       $(queue[index].element).popover('destroy')
+      $('.popover-advance').removeClass('popover-advance')

   close = ->
     hideCurrent()


Show progress bars

Show progress bars

--- app/assets/javascripts/photo_upload.js.coffee
+++ app/assets/javascripts/photo_upload.js.coffee
@@ -1,6 +1,6 @@
 previewFile = (file) ->
   reader = new FileReader()
-  el = $('

' + file.name + '

')
+  el = $('

' + file.name + '

')
   $("#previews").append(el)
   img = $("img", el)
   progress = $("progress", el)
@@ -15,6 +15,10 @@
 handleUploadProgress = (file, el, event) ->
   if event.lengthComputable
     percent = Math.floor((event.loaded / event.total) * 100)
+    $(".progress", el)
+      .toggleClass("active", true)
+    $(".bar", el)
+      .css("width": "#{percent}%")
     $(".caption p", el)
       .text("#{percent}% uploaded")

@@ -28,6 +32,10 @@
     xhr.upload.addEventListener "progress", ((e) -> handleUploadProgress(file, el, e)), false
     xhr.addEventListener "load", (e) ->
       if xhr.status == 204
+        $(".progress", el)
+          .removeClass("active")
+          .removeClass("progress-striped")
+          .addClass("progress-success")
         $(".caption p", el)
           .text("Upload complete! ")
           .append($("View on S3"))


test fromPromise with Bluebird

test fromPromise with Bluebird

--- spec/specs/frompromise.coffee
+++ spec/specs/frompromise.coffee
@@ -1,88 +1,100 @@
+Bluebird = require("bluebird")
+
 describe "Bacon.fromPromise", ->
-  success = undefined
-  fail = undefined
-  calls = 0
-  promise = {
-    then: (s, f) ->
-      success = s
-      fail = f
-      calls = calls + 1
-  }
-  _ = Bacon._
-  nop = ->
+  describe "With Bluebird", ->
+    describe "on success", ->
+      expectStreamEvents(
+        -> Bacon.fromPromise(new Bluebird((res, rej) -> res("ok")))
+        ["ok"])
+    describe "on error", ->
+      expectStreamEvents(
+        -> Bacon.fromPromise(new Bluebird((res, rej) -> rej("fail")))
+        [error("fail")])
+  describe "With mock Promise", ->
+    success = undefined
+    fail = undefined
+    calls = 0
+    promise = {
+      then: (s, f) ->
+        success = s
+        fail = f
+        calls = calls + 1
+    }
+    _ = Bacon._
+    nop = ->

-  it "should produce value and end on success", ->
-    events = []
-    Bacon.fromPromise(promise).subscribe( (e) => events.push(e))
-    success("a")
-    expect(_.map(((e) -> e.toString()), events)).to.deep.equal(["a", "<end>"])
+    it "should produce value and end on success", ->
+      events = []
+      Bacon.fromPromise(promise).subscribe( (e) => events.push(e))
+      success("a")
+      expect(_.map(((e) -> e.toString()), events)).to.deep.equal(["a", "<end>"])

-  it "should produce error and end on error", ->
-    events = []
-    Bacon.fromPromise(promise).subscribe( (e) => events.push(e))
-    fail("a")
-    expect(events.map((e) -> e.toString())).to.deep.equal(["<error> a", "<end>"])
+    it "should produce error and end on error", ->
+      events = []
+      Bacon.fromPromise(promise).subscribe( (e) => events.push(e))
+      fail("a")
+      expect(events.map((e) -> e.toString())).to.deep.equal(["<error> a", "<end>"])

-  it "should respect unsubscription", ->
-    events = []
-    dispose = Bacon.fromPromise(promise).subscribe( (e) => events.push(e))
-    dispose()
-    success("a")
-    expect(events).to.deep.equal([])
+    it "should respect unsubscription", ->
+      events = []
+      dispose = Bacon.fromPromise(promise).subscribe( (e) => events.push(e))
+      dispose()
+      success("a")
+      expect(events).to.deep.equal([])

-  it "should abort ajax promise on unsub, if abort flag is set", ->
-    isAborted = false
-    promise.abort = ->
-      isAborted = true
-    dispose = Bacon.fromPromise(promise, true).subscribe(nop)
-    dispose()
-    delete promise.abort
-    expect(isAborted).to.deep.equal(true)
-
-  it "should not abort ajax promise on unsub, if abort flag is not set", ->
-    isAborted = false
-    promise.abort = ->
-      isAborted = true
-    dispose = Bacon.fromPromise(promise).subscribe(nop)
-    dispose()
-    delete promise.abort
-    expect(isAborted).to.deep.equal(false)
+    it "should abort ajax promise on unsub, if abort flag is set", ->
+      isAborted = false
+      promise.abort = ->
+        isAborted = true
+      dispose = Bacon.fromPromise(promise, true).subscribe(nop)
+      dispose()
+      delete promise.abort
+      expect(isAborted).to.deep.equal(true)
+
+    it "should not abort ajax promise on unsub, if abort flag is not set", ->
+      isAborted = false
+      promise.abort = ->
+        isAborted = true
+      dispose = Bacon.fromPromise(promise).subscribe(nop)
+      dispose()
+      delete promise.abort
+      expect(isAborted).to.deep.equal(false)

-  it "should not abort non-ajax promise", ->
-    isAborted = false
-    dispose = Bacon.fromPromise(promise).subscribe(nop)
-    dispose()
-    expect(isAborted).to.deep.equal(false)
+    it "should not abort non-ajax promise", ->
+      isAborted = false
+      dispose = Bacon.fromPromise(promise).subscribe(nop)
+      dispose()
+      expect(isAborted).to.deep.equal(false)

-  describe 'with kind of promise-chain that ends with .done()', ->
-    stream = null
-    chainEnd = false
-    beforeEach ->
+    describe 'with kind of promise-chain that ends with .done()', ->
+      stream = null
       chainEnd = false
-      # this promise tries to mimick the behaviour of kriskowal/q, where
-      # not calling .done() at the end of the chain will result in
-      # exceptions going nowhere.
-      promise = {
-        then: (s, f) ->
-          fail = (v) ->
-            try
-              f(v)
-            catch err
-              throw err if chainEnd
-          success = (v) ->
-            try
-              s(v)
-            catch err
-              if chainEnd then throw err else fail(err)
-          done: -> chainEnd = true
-      }
-      stream = Bacon.fromPromise promise
+      beforeEach ->
+        chainEnd = false
+        # this promise tries to mimick the behaviour of kriskowal/q, where
+        # not calling .done() at the end of the chain will result in
+        # exceptions going nowhere.
+        promise = {
+          then: (s, f) ->
+            fail = (v) ->
+              try
+                f(v)
+              catch err
+                throw err if chainEnd
+            success = (v) ->
+              try
+                s(v)
+              catch err
+                if chainEnd then throw err else fail(err)
+            done: -> chainEnd = true
+        }
+        stream = Bacon.fromPromise promise

-    it "should not swallow .onValue() errors", ->
-      stream.onValue (e) -> throw new Error("fail value")
-      expect(->success('success')).to.throw 'fail value'
+      it "should not swallow .onValue() errors", ->
+        stream.onValue (e) -> throw new Error("fail value")
+        expect(->success('success')).to.throw 'fail value'

-    it "should not swallow .onError() errors", ->
-      stream.onError (e) -> throw new Error("fail error")
-      expect(->fail('fail')).to.throw 'fail error'
+      it "should not swallow .onError() errors", ->
+        stream.onError (e) -> throw new Error("fail error")
+        expect(->fail('fail')).to.throw 'fail error'


Add specs for highlighting matches

Add specs for highlighting matches

--- spec/symbols-view-spec.coffee
+++ spec/symbols-view-spec.coffee
@@ -478,3 +478,76 @@
         expect($(getWorkspaceView()).find('.symbols-view')).toExist()
         expect(symbolsView.list.children('li:first').find('.primary-line')).toHaveText 'test'
         expect(symbolsView.list.children('li:first').find('.secondary-line')).toHaveText 'Line 1'
+
+  describe "match highlighting", ->
+    beforeEach ->
+      waitsForPromise ->
+        atom.workspace.open(directory.resolve('sample.js'))
+
+    it "highlights an exact match", ->
+      runs ->
+        atom.commands.dispatch(getEditorView(), "symbols-view:toggle-file-symbols")
+
+      waitsForPromise ->
+        activationPromise
+
+      runs ->
+        symbolsView = $(getWorkspaceView()).find('.symbols-view').view()
+
+      waitsFor ->
+        symbolsView.list.children('li').length > 0
+
+      runs ->
+        symbolsView.filterEditorView.getModel().setText('quicksort')
+        expect(symbolsView.filterEditorView.getModel().getText()).toBe 'quicksort'
+        symbolsView.populateList()
+        resultView = symbolsView.getSelectedItemView()
+
+        matches = resultView.find('.character-match')
+        expect(matches.length).toBe 1
+        expect(matches.last().text()).toBe 'quicksort'
+
+    it "highlights a partial match", ->
+      runs ->
+        atom.commands.dispatch(getEditorView(), "symbols-view:toggle-file-symbols")
+
+      waitsForPromise ->
+        activationPromise
+
+      runs ->
+        symbolsView = $(getWorkspaceView()).find('.symbols-view').view()
+
+      waitsFor ->
+        symbolsView.list.children('li').length > 0
+
+      runs ->
+        symbolsView.filterEditorView.getModel().setText('quick')
+        symbolsView.populateList()
+        resultView = symbolsView.getSelectedItemView()
+
+        matches = resultView.find('.character-match')
+        expect(matches.length).toBe 1
+        expect(matches.last().text()).toBe 'quick'
+
+    it "highlights multiple matches in the symbol name", ->
+      runs ->
+        atom.commands.dispatch(getEditorView(), "symbols-view:toggle-file-symbols")
+
+      waitsForPromise ->
+        activationPromise
+
+      runs ->
+        symbolsView = $(getWorkspaceView()).find('.symbols-view').view()
+
+      waitsFor ->
+        symbolsView.list.children('li').length > 0
+
+      runs ->
+        symbolsView.filterEditorView.getModel().setText('quicort')
+        symbolsView.populateList()
+        resultView = symbolsView.getSelectedItemView()
+
+        matches = resultView.find('.character-match')
+        expect(matches.length).toBe 2
+        expect(matches.first().text()).toBe 'quic'
+        expect(matches.last().text()).toBe 'ort'


don't console.warn on file_use set error

mark_file console.warn thing is pointless. I’m going to remove it. It’s expected that table.set can fail to save – it’ll happen whenever the user is offline. The table will save right when the user reconnects (and things sync up again). Also, loss of a file_use notification would of course not be at all “data loss/corruption”.

--- src/smc-webapp/file_use.cjsx
+++ src/smc-webapp/file_use.cjsx
@@ -120,11 +120,7 @@
             # Update the overall "last_edited" field for the file; this is used for sorting,
             # and grabbing only recent files from database for file use notifications.
             obj.last_edited = timestamp
-        table.set obj, (err)=>
-            if err
-                if err != "not connected" # ignore "not connected", since save will happen once connection goes through.
-                    err += " (project_id=#{project_id}, path=#{path})"
-                    console.warn("FileUseActions.mark_file error: ", err)
+        table.set(obj)

 class FileUseStore extends Store
     get_errors: =>


EDSC-594 added facets to tracking

EDSC-594 added facets to tracking

--- app/assets/javascripts/util/metrics.js.coffee
+++ app/assets/javascripts/util/metrics.js.coffee
@@ -45,6 +45,26 @@
       ga('set', 'dimension4', d4)
       ga('set', 'dimension5', d5)

+      # Dimension 6, Search facets
+      facets = []
+      facets.push("Features/#{state.features.join('Features/')}") if state.features?
+      facets.push("Archive Center/#{state.archive_center.join('Archive Center/')}") if state.archive_center?
+      facets.push("Project/#{state.project.join('Project/')}") if state.project?
+      facets.push("Platform/#{state.platform.join('Platform/')}") if state.platform?
+      facets.push("Instrument/#{state.instrument.join('Instrument/')}") if state.instrument?
+      facets.push("Sensor/#{state.sensor.join('Sensor/')}") if state.sensor?
+      facets.push("2D Coordinate Name/#{state.two_d_coordinate_system_name.join('2D Coordinate Name/')}") if state.two_d_coordinate_system_name?
+      facets.push("Processing Level/#{state.sensor.join('Processing Level/')}") if state.sensor?
+      if state.science_keywords?
+        facets.push("Category Keyword/#{state.science_keywords[0].category.join('Category Keyword/')}") if state.science_keywords[0].category?
+        facets.push("Topic Keyword/#{state.science_keywords[0].topic.join('Topic Keyword/')}") if state.science_keywords[0].topic?
+        facets.push("Term Keyword/#{state.science_keywords[0].term.join('Term Keyword/')}") if state.science_keywords[0].term?
+        facets.push("Variable Level 1 Keyword/#{state.variable_level_1.join('Variable Level 1 Keyword/')}") if state.variable_level_1?
+        facets.push("Variable Level 2 Keyword/#{state.variable_level_2.join('Variable Level 2 Keyword/')}") if state.variable_level_2?
+        facets.push("Variable Level 3 Keyword/#{state.variable_level_3.join('Variable Level 3 Keyword/')}") if state.variable_level_3?
+        facets.push("Detailed Variable Keyword/#{state.detailed_variable.join('Detailed Variable Keyword/')}") if state.detailed_variable?
+      ga('set', 'dimension6', if facets.length > 0 then facets.join(' ') + ' ' else null)
+

       # Send the page view
       ga('send', 'pageview', path)


added a test for Batman.Property.withoutTracking

--- tests/batman/property/property_test.coffee
+++ tests/batman/property/property_test.coffee
@@ -76,6 +76,18 @@
   equal getter.callCount, 0
   @object.set 'foo', 12345
   equal getter.callCount, 1
+
+test "Property.withoutTracking(block) runs the block and returns its return value, while preventing it from registering property sources", ->
+  barVal = null
+  @class.accessor 'foo', ->
+    Batman.Property.withoutTracking =>
+      barVal = @get('bar')
+  @object.set('bar', 'barVal')
+
+  equal @object.get('foo'), 'barVal'
+  equal barVal, 'barVal'
+
+  deepEqual @object.property('foo').sources.toArray(), []

 QUnit.module 'Batman.Property',


Use Promise suffix for promise to make it a little clearer.

--- src/supervisor-update.coffee
+++ src/supervisor-update.coffee
@@ -13,14 +13,15 @@
 localImage = config.localImage
 remoteImage = config.remoteImage

-getContainerId = ->
+containerIdPromise =
 	fs.readFileAsync( '/proc/1/cgroup' )
 	.then (data) ->
 		data.toString().match( /:cpu:\/docker\/(.*)$/m )[1]
 	.catch (err) ->
 		return process.env.HOSTNAME

-currentContainer = getContainerId().then (containerId) ->
+currentContainerPromise =
+	containerIdPromise.then (containerId) ->
 		docker.getContainer(containerId)

 startNewSupervisor = (currentSupervisor, waitForSuccess = true) ->
@@ -64,7 +65,7 @@
 	.then ->
 		# We've started the new container, so we're done here! #pray
 		console.log('Removing our container and letting the new supervisor take over')
-		currentContainer.then (container) ->
+		currentContainerPromise.then (container) ->
 			container.removeAsync(force: true)
 		.finally ->
 			# This should never really be reached as docker should already have terminated us,
@@ -72,7 +73,7 @@
 			console.log('And process.exiting..')
 			process.exit()

-currentSupervisorConfig = currentContainer.then (container) ->
+currentConfigPromise = currentContainerPromise.then (container) ->
 	container.inspectAsync()
 .tap (currentSupervisor) ->
 	# The volume keys are the important bit.
@@ -95,7 +96,7 @@
 		restart()

 # This is a promise that resolves when we have fully initialised.
-exports.initialised = currentSupervisorConfig.then (currentSupervisor) ->
+exports.initialised = currentConfigPromise.then (currentSupervisor) ->
 	utils = require './utils'
 	JSONStream = require 'JSONStream'


Fix file size calc

Fix file size calc

--- client/project/project.coffee
+++ client/project/project.coffee
@@ -6,9 +6,9 @@
   else if numBytes < 1024*1024
     sizeStr = "#{Math.ceil(numBytes / 1024.0)} KB"
   else if numBytes < 1024*1024*1024
-    sizeStr = "#{Math.ceil(numBytes / 1024*1024.0)} MB"
+    sizeStr = "#{Math.ceil(numBytes / (1024*1024.0))} MB"
   else if numBytes < 1024*1024*1024*1024
-    sizeStr = "#{Math.ceil(numBytes / 1024*1024*1024.0)} GB"
+    sizeStr = "#{Math.ceil(numBytes / (1024*1024*1024.0))} GB"
   else
     throw new Error("File size too large: #{numBytes}")
   sizeStr


Add a log entry that we're exiting on a supervisor update.

--- src/supervisor-update.coffee
+++ src/supervisor-update.coffee
@@ -74,6 +74,7 @@
 			)
 		.then ->
 			# We've started the new container, so we're done here! #pray
+			console.log('Exiting to let the new supervisor take over')
 			process.exit()
 	.catch (err) ->
 		utils.mixpanelTrack('Supervisor update failed', error: err)


minor fixes

minor fixes

--- static/coffee/renderers/properties.coffee
+++ static/coffee/renderers/properties.coffee
@@ -140,7 +140,7 @@

     # if the attribute is not on this property object at all, log a bad request
     if not (attrname of @)
-      console.log("requested unknown property '#{ attrname } on object: " + obj)
+      console.log("requested unknown property '#{ attrname }' on object: " + obj)
       return

     # if the attribute specifies a field, and the field exists on the object, return that value
@@ -149,7 +149,7 @@
         return obj[@[attrname].field]

     # otherwise, if the attribute exists on the object, return that value
-    if obj[attrname]?
+    if attrname of obj
       return obj[attrname]

     # finally, check for a default value on this property object that could be returned
@@ -158,13 +158,13 @@

     # failing that, just log a problem
     else
-      console.log "selection for attribute '#{ attrname } failed on object: #{ obj }"
+      console.log "selection for attribute '#{ attrname }' failed on object: #{ obj }"

   v_select: (attrname, objs) ->

     # if the attribute is not on this property object at all, log a bad request
     if not (attrname of @)
-      console.log("requested unknown property '#{ attrname } on objects")
+      console.log("requested unknown property '#{ attrname }' on objects")
       return

     result = new Array(objs.length)
@@ -178,7 +178,7 @@
         result[i] = obj[@[attrname].field]

       # otherwise, if the attribute exists on the object, return that value
-      else if obj[attrname]
+      else if attrname of obj
         result[i] = obj[attrname]

       # finally, check for a default value on this property object that could be returned
@@ -187,7 +187,7 @@

       # failing that, just log a problem
       else
-        console.log "vector selection for attribute '#{ attrname } failed on object: #{ obj }"
+        console.log "vector selection for attribute '#{ attrname }' failed on object: #{ obj }"
         return

     return result
@@ -256,6 +256,7 @@
     @number(styleprovider, glyphspec, @text_alpha_name)
     @enum(styleprovider, glyphspec, @text_align_name, "left right center")
     @enum(styleprovider, glyphspec, @text_baseline_name, "top middle bottom alphabetic hanging")
+
   font:(obj, font_size) ->
     if not font_size?
       font_size = @select(@text_font_size_name,  obj)


add ability to mask card number

add ability to mask card number

--- src/coffee/card.coffee
+++ src/coffee/card.coffee
@@ -75,6 +75,8 @@
       cvc: '•••'
       expiry: '••/••'
       name: 'Full Name'
+    masks:
+      cardNumber: false
     classes:
       valid: 'jp-card-valid'
       invalid: 'jp-card-invalid'
@@ -144,9 +146,12 @@
       QJ.addClass @$card, 'jp-card-ie-11'

   attachHandlers: ->
+    numberInputFilters = [@validToggler('cardNumber')]
+    numberInputFilters.push(@maskCardNumber) if @options.masks.cardNumber
+
     bindVal @$numberInput, @$numberDisplay,
       fill: false,
-      filters: @validToggler('cardNumber')
+      filters: numberInputFilters
     QJ.on @$numberInput, 'payment.cardType', @handle('setCardType')

     expiryFilters = [(val) -> val.replace /(\s+)/g, '']
@@ -202,6 +207,19 @@
   toggleValidClass: (el, test) ->
     QJ.toggleClass el, @options.classes.valid, test
     QJ.toggleClass el, @options.classes.invalid, !test
+
+  maskCardNumber: (val, el, out) =>
+    mask = @options.masks.cardNumber
+    cardType = payment.fns.cardType(val)
+    numbers = val.split(' ')
+
+    if (cardType == 'amex' && (numbers.length == 3)) || numbers.length >= 4
+      numbers.forEach (item, idx) ->
+        numbers[idx] = numbers[idx].replace(/\d/g, mask) unless idx == numbers.length - 1
+      numbers.join(' ')
+
+    else
+      val.replace /\d/g, mask

   handlers:
     setCardType: ($el, e) ->


add sanitize module

add sanitize module

--- src/coffee/init.coffee
+++ src/coffee/init.coffee
@@ -8,5 +8,5 @@

 angular.module('CoolFormValidators', [])
 angular.module('CoolFormServices', ['CoolFormValidators'])
-angular.module('CoolFormDirectives', ['CoolFormServices'])
+angular.module('CoolFormDirectives', ['CoolFormServices', 'ngSanitize'])
 angular.module('CoolForm', ['CoolFormDirectives', 'CoolFormServices'])


Consider only the last block attribute in Block#isListItem

--- src/trix/models/block.coffee
+++ src/trix/models/block.coffee
@@ -83,7 +83,7 @@
     @getListItemAttributes().length

   isListItem: ->
-    @getListLevel() > 0
+    getBlockConfig(@getLastAttribute())?.listAttribute

   isTerminalBlock: ->
     getBlockConfig(@getLastAttribute())?.terminal


seen avatar depends on group of messages and not on group in timestamp

--- src/ui/views/messages.coffee
+++ src/ui/views/messages.coffee
@@ -121,25 +121,26 @@
                         div class:clz.join(' '), ->
                             drawAvatar u, sender, viewstate, entity
                             if entity.isSelf(u.cid)
-                                drawSeenElement(c, u, entity, g)
+                                drawSeenElement(c, u, entity, events)
                             div class:'umessages', ->
                                 drawMessage(e, entity) for e in events
                             , onDOMSubtreeModified: (e) ->
                                 window.twemoji?.parse e.target if process.platform == 'win32'
                             unless entity.isSelf(u.cid)
-                                drawSeenElement(c, u, entity, g)
+                                drawSeenElement(c, u, entity, events)

     if lastConv != conv_id
         lastConv = conv_id
         later atTopIfSmall

-drawSeenElement = (c, u, entity, g) ->
+drawSeenElement = (c, u, entity, events) ->
     temp_set = new Set()
     for contacts in c.read_state
         other = contacts.participant_id.chat_id
         if other != u.cid &&
            !entity.isSelf(other) &&
-           contacts.latest_read_timestamp >= g.end
+           # only add "seen" avatar if last message from group is seen
+           contacts.latest_read_timestamp >= events[events.length - 1].timestamp
             if !temp_set.has(entity[other].id)
                 temp_set.add entity[other].id
                 drawSeenAvatar entity[other]


pager 控件 goto item做了最小值限定

pager 控件 goto item做了最小值限定

--- src/widget/list/pager.coffee
+++ src/widget/list/pager.coffee
@@ -2,270 +2,274 @@

 _pagesItems = ["firstPage", "prevPage", "info", "nextPage", "lastPage"]
 _pageCodeMap =
-    "|<": "firstPage"
-    "<": "prevPage"
-    ">": "nextPage"
-    ">|": "lastPage"
+	"|<": "firstPage"
+	"<": "prevPage"
+	">": "nextPage"
+	">|": "lastPage"

 class cola.Pager extends cola.Menu
-    @tagName: "c-pager"
-
-    @attributes:
-        bind:
-            setter: (bindStr) -> @_bindSetter(bindStr)
-
-    _getBindItems: ()-> @_getItems()?.items
-
-    constructor: (config) ->
-        @_pagerItemMap = {}
-        pager = @
-        #暂使用此方法获得总页数
-        _getPageCount = ()->
-            data = pager._getBindItems()
-            return parseInt((data.totalEntityCount + data.pageSize - 1) / data.pageSize)
-
-        @_pagerItemConfig =
-            firstPage:
-                icon: "large angle double left"
-                click: ()-> pager._getBindItems()?.firstPage()
-
-            prevPage:
-                icon: "large angle left"
-                click: ()->
-                    data = pager._getBindItems()
-                    data?.previousPage()
-
-            goto:
-                $type: "input"
-                class: "goto"
-                inputType: "number"
-                keyDown: (self, arg)->
-                    k = arg.keyCode
-                    if k is 190 then event.preventDefault()
-
-                change: (self, arg)->
-                    value = arg.value
-                    if value then value = parseInt(value)
-                    if value is @_targetPageNo then return
-                    data = pager._getBindItems()
-                    if data
-                        pageNo = data.pageNo
-                        pageCount = _getPageCount()
-
-                        if value > pageCount or value < 1
-                            if value > pageCount then value = pageCount
-                            if value < 1 then value = 1
-                            setTimeout(()->
-                                self.get$Dom().find("input").val(value)
-                            , 10)
-                        button = self.get("actionButton")
-                        setTimeout(()->
-                            button.set("disabled", value is pageNo)
-                        , 100)
-
-                        pager._targetPageNo = value
-
-                actionButton:
-                    $type: "Button"
-                    caption: "Go"
-                    click: ()->
-                        if pager._targetPageNo
-                            data = pager._getBindItems()
-                            data?.gotoPage(pager._targetPageNo)
-
-            nextPage:
-                icon: "large angle right"
-                click: ()->
-                    data = pager._getBindItems()
-                    data?.nextPage()
-            lastPage:
-                icon: "large angle double right"
-                click: ()->
-                    data = pager._getBindItems()
-                    data?.lastPage()
-
-        super(config)
-    _parseDom: (dom)->
-        super(dom)
-        hasPageItem = false
-        for name in _pagesItems
-
-            if @_pagerItemMap[name]
-                hasPageItem = true
-                break
-        unless hasPageItem
-            @addItem("pages")
-        @_items ?= []
-
-
-
-    _parsePageItem: (childNode, right)->
-        pageCode = $fly(childNode).attr("page-code")
-        unless pageCode then return
-        if pageCode is "pages"
-            for pageItemKey in _pagesItems
-                pageItem = @_pagerItemConfig[pageItemKey]
-
-                if pageItemKey is "firstPage"
-                    pageItem.dom = childNode
-                    menuItem = new cola.menu.MenuItem(pageItem)
-                    if right then @addRightItem(menuItem) else @addItem(menuItem)
-                    beforeChild = childNode
-                else
-                    if pageItemKey is "info"
-                        menuItem = new cola.menu.MenuItem()
-                    else
-                        menuItem = new cola.menu.MenuItem(pageItem)
-
-                    itemDom = menuItem.getDom()
-                    $fly(beforeChild).after(itemDom)
-                    itemDom._eachIgnore = true
-                    if right then @addRightItem(menuItem) else @addItem(menuItem)
-                    beforeChild = itemDom
-                @_pagerItemMap[pageItemKey] = menuItem
-        else
-            propName = _pageCodeMap[pageCode]
-            if propName
-                itemConfig = @_pagerItemConfig[propName]
-                itemConfig.dom = childNode
-                if $(childNode).text()
-                    delete itemConfig["icon"]
-                menuItem = new cola.menu.MenuItem(itemConfig)
-                if right then @addRightItem(menuItem) else @addItem(menuItem)
-            else if pageCode is "goto"
-                propName = "goto"
-                itemConfig = {dom: childNode, control: @_pagerItemConfig[pageCode]}
-                menuItem = new cola.menu.ControlMenuItem(itemConfig)
-                if right then @addRightItem(menuItem) else @addItem(menuItem)
-            else if pageCode is "info"
-                propName = "info"
-                itemConfig = {dom: childNode}
-                menuItem = new cola.menu.MenuItem(itemConfig)
-                if right then @addRightItem(menuItem) else @addItem(menuItem)
-            @_pagerItemMap[propName] = menuItem
-        return
-
-    _parseItems: (node)->
-        parseRightMenu = (node)=>
-            childNode = node.firstChild
-            @_rightItems ?= []
-            while childNode
-                if childNode.nodeType == 1
-                    pageCode = $fly(childNode).attr("page-code")
-                    if pageCode
-                        @_parsePageItem(childNode, true)
-                    else
-                        menuItem = cola.widget(childNode)
-                        if menuItem
-                            @addRightItem(menuItem)
-                        else if cola.util.hasClass(childNode, "item")
-                            menuItem = new cola.menu.MenuItem({dom: childNode})
-                            @addRightItem(menuItem)
-                childNode = childNode.nextSibling
-            return
-        childNode = node.firstChild
-
-        while childNode
-            if childNode._eachIgnore
-                childNode = childNode.nextSibling
-                continue
-            if childNode.nodeType == 1
-                pageCode = $fly(childNode).attr("page-code")
-                if pageCode
-                    @_parsePageItem(childNode)
-                else
-                    menuItem = cola.widget(childNode)
-                    if menuItem
-                        @addItem(menuItem)
-                    else if !@_rightMenuDom and cola.util.hasClass(childNode, "right menu")
-                        @_rightMenuDom = childNode
-                        parseRightMenu(childNode)
-                    else if cola.util.hasClass(childNode, "item")
-                        menuItem = new cola.menu.MenuItem({dom: childNode})
-                        @addItem(menuItem)
-            childNode = childNode.nextSibling
-
-    _createItem: (config, floatRight)->
-        if typeof config is "string"
-            if config is "pages"
-                for pageItemKey in _pagesItems
-                    pageItem = @_pagerItemConfig[pageItemKey]
-                    if pageItemKey is "info"
-                        menuItem = new cola.menu.ControlMenuItem()
-                    else
-                        menuItem = new cola.menu.MenuItem(pageItem)
-                    if floatRight then @addRightItem(menuItem) else @addItem(menuItem)
-                    @_pagerItemMap[pageItemKey] = menuItem
-            else
-                propName = _pageCodeMap[config]
-                if propName
-                    itemConfig = @_pagerItemConfig[propName]
-                    menuItem = new cola.menu.MenuItem(itemConfig)
-                else if config is "goto"
-                    propName = config
-                    itemConfig = {control: @_pagerItemConfig[config]}
-                    menuItem = new cola.menu.ControlMenuItem(itemConfig)
-                else if config is "info"
-                    propName = config
-                    menuItem = new cola.menu.MenuItem()
-                if floatRight then @addRightItem(menuItem) else @addItem(menuItem)
-
-                @_pagerItemMap[propName] = menuItem
-            return
-        menuItem = null
-        if config.constructor == Object.prototype.constructor
-            if config.$type
-                if config.$type is "dropdown"
-                    menuItem = new cola.menu.DropdownMenuItem(config)
-                else if config.$type is "headerItem"
-                    menuItem = new cola.menu.HeaderMenuItem(config)
-                else
-                    menuItem = new cola.menu.ControlMenuItem({
-                        control: config
-                    })
-            else
-                menuItem = new cola.menu.MenuItem(config)
-        else if config instanceof cola.menu.AbstractMenuItem
-            menuItem = config
-        return menuItem
-
-    _initDom: (dom)->
-        super(dom)
-        @pagerItemsRefresh()
-
-    pagerItemsRefresh: () ->
-        pager = @
-        data = pager._getBindItems()
-        hasPrev = false
-        hasNext = false
-        pageNo = 0
-        pageCount = 0
-
-        if data
-            pageCount = parseInt((data.totalEntityCount + data.pageSize - 1) / data.pageSize)
-            hasPrev = data.pageNo > 1
-            hasNext = pageCount > data.pageNo
-            pageNo = data.pageNo
-            pageCount = data.pageCount
-        @_pageNo = pageNo
-        pager._pagerItemMap["firstPage"]?.get$Dom().toggleClass("disabled", !hasPrev)
-        pager._pagerItemMap["prevPage"]?.get$Dom().toggleClass("disabled", !hasPrev)
-        pager._pagerItemMap["nextPage"]?.get$Dom().toggleClass("disabled", !hasNext)
-        pager._pagerItemMap["lastPage"]?.get$Dom().toggleClass("disabled", !hasNext)
-        infoItem = pager._pagerItemMap["info"]
-        if infoItem
-            infoItemDom = if infoItem.nodeType is 1 then infoItem else  infoItem.getDom()
-            $(infoItemDom).text(cola.resource("cola.pager.info", pageNo, pageCount))
-        gotoInput = pager._pagerItemMap["goto"]?.get("control")
-        if gotoInput then cola.widget(gotoInput)?.set("value", pageNo)
-    _onItemsRefresh: ()-> @pagerItemsRefresh()
-
-    _onItemRefresh: (arg)->
-    _onItemInsert: (arg) ->
-    _onItemRemove: (arg) ->
-    _onItemsLoadingStart: (arg)->
-    _onItemsLoadingEnd: (arg)->
-    _onCurrentItemChange: (arg)->
+	@tagName: "c-pager"
+
+	@attributes:
+		bind:
+			setter: (bindStr) -> @_bindSetter(bindStr)
+
+	_getBindItems: ()-> @_getItems()?.items
+
+	constructor: (config) ->
+		@_pagerItemMap = {}
+		pager = @
+		#暂使用此方法获得总页数
+		_getPageCount = ()->
+			data = pager._getBindItems()
+			return parseInt((data.totalEntityCount + data.pageSize - 1) / data.pageSize)
+
+		@_pagerItemConfig =
+			firstPage:
+				icon: "large angle double left"
+				click: ()-> pager._getBindItems()?.firstPage()
+
+			prevPage:
+				icon: "large angle left"
+				click: ()->
+					data = pager._getBindItems()
+					data?.previousPage()
+
+			goto:
+				$type: "input"
+				class: "goto"
+				inputType: "number",
+				initDom: (self, arg)->
+					self.get$Dom().find("input").attr("min",0)
+					return
+
+				keyDown: (self, arg)->
+					k = arg.keyCode
+					if k is 190 then event.preventDefault()
+
+				change: (self, arg)->
+					value = arg.value
+					if value then value = parseInt(value)
+					if value is @_targetPageNo then return
+					data = pager._getBindItems()
+					if data
+						pageNo = data.pageNo
+						pageCount = _getPageCount()
+
+						if value > pageCount or value < 1
+							if value > pageCount then value = pageCount
+							if value < 1 then value = 1
+							setTimeout(()->
+								self.get$Dom().find("input").val(value)
+							, 10)
+						button = self.get("actionButton")
+						setTimeout(()->
+							button.set("disabled", value is pageNo)
+						, 100)
+
+						pager._targetPageNo = value
+
+				actionButton:
+					$type: "Button"
+					caption: "Go"
+					click: ()->
+						if pager._targetPageNo
+							data = pager._getBindItems()
+							data?.gotoPage(pager._targetPageNo)
+
+			nextPage:
+				icon: "large angle right"
+				click: ()->
+					data = pager._getBindItems()
+					data?.nextPage()
+			lastPage:
+				icon: "large angle double right"
+				click: ()->
+					data = pager._getBindItems()
+					data?.lastPage()
+
+		super(config)
+	_parseDom: (dom)->
+		super(dom)
+		hasPageItem = false
+		for name in _pagesItems
+
+			if @_pagerItemMap[name]
+				hasPageItem = true
+				break
+		unless hasPageItem
+			@addItem("pages")
+		@_items ?= []
+
+
+
+	_parsePageItem: (childNode, right)->
+		pageCode = $fly(childNode).attr("page-code")
+		unless pageCode then return
+		if pageCode is "pages"
+			for pageItemKey in _pagesItems
+				pageItem = @_pagerItemConfig[pageItemKey]
+
+				if pageItemKey is "firstPage"
+					pageItem.dom = childNode
+					menuItem = new cola.menu.MenuItem(pageItem)
+					if right then @addRightItem(menuItem) else @addItem(menuItem)
+					beforeChild = childNode
+				else
+					if pageItemKey is "info"
+						menuItem = new cola.menu.MenuItem()
+					else
+						menuItem = new cola.menu.MenuItem(pageItem)
+
+					itemDom = menuItem.getDom()
+					$fly(beforeChild).after(itemDom)
+					itemDom._eachIgnore = true
+					if right then @addRightItem(menuItem) else @addItem(menuItem)
+					beforeChild = itemDom
+				@_pagerItemMap[pageItemKey] = menuItem
+		else
+			propName = _pageCodeMap[pageCode]
+			if propName
+				itemConfig = @_pagerItemConfig[propName]
+				itemConfig.dom = childNode
+				if $(childNode).text()
+					delete itemConfig["icon"]
+				menuItem = new cola.menu.MenuItem(itemConfig)
+				if right then @addRightItem(menuItem) else @addItem(menuItem)
+			else if pageCode is "goto"
+				propName = "goto"
+				itemConfig = {dom: childNode, control: @_pagerItemConfig[pageCode]}
+				menuItem = new cola.menu.ControlMenuItem(itemConfig)
+				if right then @addRightItem(menuItem) else @addItem(menuItem)
+			else if pageCode is "info"
+				propName = "info"
+				itemConfig = {dom: childNode}
+				menuItem = new cola.menu.MenuItem(itemConfig)
+				if right then @addRightItem(menuItem) else @addItem(menuItem)
+			@_pagerItemMap[propName] = menuItem
+		return
+
+	_parseItems: (node)->
+		parseRightMenu = (node)=>
+			childNode = node.firstChild
+			@_rightItems ?= []
+			while childNode
+				if childNode.nodeType == 1
+					pageCode = $fly(childNode).attr("page-code")
+					if pageCode
+						@_parsePageItem(childNode, true)
+					else
+						menuItem = cola.widget(childNode)
+						if menuItem
+							@addRightItem(menuItem)
+						else if cola.util.hasClass(childNode, "item")
+							menuItem = new cola.menu.MenuItem({dom: childNode})
+							@addRightItem(menuItem)
+				childNode = childNode.nextSibling
+			return
+		childNode = node.firstChild
+
+		while childNode
+			if childNode._eachIgnore
+				childNode = childNode.nextSibling
+				continue
+			if childNode.nodeType == 1
+				pageCode = $fly(childNode).attr("page-code")
+				if pageCode
+					@_parsePageItem(childNode)
+				else
+					menuItem = cola.widget(childNode)
+					if menuItem
+						@addItem(menuItem)
+					else if !@_rightMenuDom and cola.util.hasClass(childNode, "right menu")
+						@_rightMenuDom = childNode
+						parseRightMenu(childNode)
+					else if cola.util.hasClass(childNode, "item")
+						menuItem = new cola.menu.MenuItem({dom: childNode})
+						@addItem(menuItem)
+			childNode = childNode.nextSibling
+
+	_createItem: (config, floatRight)->
+		if typeof config is "string"
+			if config is "pages"
+				for pageItemKey in _pagesItems
+					pageItem = @_pagerItemConfig[pageItemKey]
+					if pageItemKey is "info"
+						menuItem = new cola.menu.ControlMenuItem()
+					else
+						menuItem = new cola.menu.MenuItem(pageItem)
+					if floatRight then @addRightItem(menuItem) else @addItem(menuItem)
+					@_pagerItemMap[pageItemKey] = menuItem
+			else
+				propName = _pageCodeMap[config]
+				if propName
+					itemConfig = @_pagerItemConfig[propName]
+					menuItem = new cola.menu.MenuItem(itemConfig)
+				else if config is "goto"
+					propName = config
+					itemConfig = {control: @_pagerItemConfig[config]}
+					menuItem = new cola.menu.ControlMenuItem(itemConfig)
+				else if config is "info"
+					propName = config
+					menuItem = new cola.menu.MenuItem()
+				if floatRight then @addRightItem(menuItem) else @addItem(menuItem)
+
+				@_pagerItemMap[propName] = menuItem
+			return
+		menuItem = null
+		if config.constructor == Object.prototype.constructor
+			if config.$type
+				if config.$type is "dropdown"
+					menuItem = new cola.menu.DropdownMenuItem(config)
+				else if config.$type is "headerItem"
+					menuItem = new cola.menu.HeaderMenuItem(config)
+				else
+					menuItem = new cola.menu.ControlMenuItem({
+						control: config
+					})
+			else
+				menuItem = new cola.menu.MenuItem(config)
+		else if config instanceof cola.menu.AbstractMenuItem
+			menuItem = config
+		return menuItem
+
+	_initDom: (dom)->
+		super(dom)
+		@pagerItemsRefresh()
+
+	pagerItemsRefresh: () ->
+		pager = @
+		data = pager._getBindItems()
+		hasPrev = false
+		hasNext = false
+		pageNo = 0
+		pageCount = 0
+
+		if data
+			pageCount = parseInt((data.totalEntityCount + data.pageSize - 1) / data.pageSize)
+			hasPrev = data.pageNo > 1
+			hasNext = pageCount > data.pageNo
+			pageNo = data.pageNo
+			pageCount = data.pageCount
+		@_pageNo = pageNo
+		pager._pagerItemMap["firstPage"]?.get$Dom().toggleClass("disabled", !hasPrev)
+		pager._pagerItemMap["prevPage"]?.get$Dom().toggleClass("disabled", !hasPrev)
+		pager._pagerItemMap["nextPage"]?.get$Dom().toggleClass("disabled", !hasNext)
+		pager._pagerItemMap["lastPage"]?.get$Dom().toggleClass("disabled", !hasNext)
+		infoItem = pager._pagerItemMap["info"]
+		if infoItem
+			infoItemDom = if infoItem.nodeType is 1 then infoItem else  infoItem.getDom()
+			$(infoItemDom).text(cola.resource("cola.pager.info", pageNo, pageCount))
+		gotoInput = pager._pagerItemMap["goto"]?.get("control")
+		if gotoInput then cola.widget(gotoInput)?.set("value", pageNo)
+	_onItemsRefresh: ()-> @pagerItemsRefresh()
+
+	_onItemRefresh: (arg)->
+	_onItemInsert: (arg) ->
+	_onItemRemove: (arg) ->
+	_onItemsLoadingStart: (arg)->
+	_onItemsLoadingEnd: (arg)->
+	_onCurrentItemChange: (arg)->

 cola.Element.mixin(cola.Pager, cola.DataItemsWidgetMixin)


Tests: Environments: Fixed makeAlwaysOnForNotPaidUser test.

--- tests/src/environments/environments.coffee
+++ tests/src/environments/environments.coffee
@@ -154,19 +154,24 @@
   makeAlwaysOnForNotPaidUser: (browser) ->

     buttonSelector = '.more-form .alwayson'
-
-    helpers.beginTest(browser)
-    helpers.waitForVMRunning(browser)
-
-    openVmSettingsModal(browser)
-
-    clickMoreButtonInVMSettingsModal(browser)
-
-    browser
-      .waitForElementVisible  buttonSelector, 20000
-      .click                  buttonSelector + ' .input-wrapper .koding-on-off a.knob'
-      .waitForElementVisible  '.kdmodal-content a.custom-link-view', 20000 # Assertion
-      .end()
+    vmSelector     = 'a[href="/IDE/koding-vm-1/my-workspace"]'
+
+    helpers.beginTest(browser)
+
+    browser.element 'css selector', vmSelector, (result) =>
+      if result.status is 0
+        browser.end()
+      else
+        helpers.waitForVMRunning(browser)
+        openVmSettingsModal(browser)
+        clickMoreButtonInVMSettingsModal(browser)
+
+        browser
+          .waitForElementVisible  buttonSelector, 20000
+          .click                  buttonSelector + ' .input-wrapper .koding-on-off a.knob'
+          .pause                  2000
+          .waitForElementVisible  '.kdmodal-content a.custom-link-view', 20000 # Assertion
+          .end()

   addDomain: (browser) ->


reverting 63ab88

reverting 63ab88

--- nodes.coffee
+++ nodes.coffee
@@ -232,7 +232,7 @@
         codes.push node.compile o, LEVEL_LIST
     if top
       if @spaced
-        return '\n' + codes.join('\n\n')
+        return "\n#{codes.join '\n\n'}\n"
       else
         return codes.join '\n'
     code = codes.join(', ') or 'void 0'


Reverts 56fe211b793a115ffe74f571947014226cb85abf

--- nodes.coffee
+++ nodes.coffee
@@ -2044,8 +2044,8 @@
       return new If(@condition.invert(), @elseBodyNode(), type: 'if').compileToFragments o

     indent   = o.indent + TAB
+    cond     = @condition.compileToFragments o, LEVEL_PAREN
     body     = @ensureBlock(@body).compileToFragments merge o, {indent}
-    cond     = @condition.compileToFragments o, LEVEL_PAREN
     ifPart   = [].concat @makeCode("if ("), cond, @makeCode(") {\n"), body, @makeCode("\n#{@tab}}")
     ifPart.unshift @makeCode @tab unless child
     return ifPart unless @elseBody
@@ -2059,8 +2059,8 @@

   # Compile the `If` as a conditional operator.
   compileExpression: (o) ->
+    cond = @condition.compileToFragments o, LEVEL_COND
     body = @bodyNode().compileToFragments o, LEVEL_LIST
-    cond = @condition.compileToFragments o, LEVEL_COND
     alt  = if @elseBodyNode() then @elseBodyNode().compileToFragments(o, LEVEL_LIST) else [@makeCode('void 0')]
     fragments = cond.concat @makeCode(" ? "), body, @makeCode(" : "), alt
     if o.level >= LEVEL_COND then @wrapInBraces fragments else fragments


adding the ability to have classes with functions pre-bound to the instance, using the fat arrow, and the constructor-function-definition pattern, avoiding prototypes.

--- nodes.coffee
+++ nodes.coffee
@@ -635,9 +635,9 @@
 # The CoffeeScript class definition.
 exports.ClassNode: class ClassNode extends BaseNode

-  class:         'ClassNode'
+  class:        'ClassNode'
   children:     ['variable', 'parent', 'properties']
-  isStatement: -> yes
+  isStatement:  -> yes

   # Initialize a **ClassNode** with its name, an optional superclass, and a
   # list of prototype property assignments.
@@ -656,18 +656,29 @@
   # constructor, property assignments, and inheritance getting built out below.
   compileNode: (o) ->
     extension:   @parent and new ExtendsNode(@variable, @parent)
-    constructor: null
     props:       new Expressions()
     o.top:       true
+
+    if @parent
+      applied: new ValueNode(@parent, [new AccessorNode(literal('apply'))])
+      constructor: new CodeNode([], new Expressions([
+        new CallNode(applied, [literal('this'), literal('arguments')])
+      ]))
+    else
+      constructor: new CodeNode()

     for prop in @properties
       [pvar, func]: [prop.variable, prop.value]
       if pvar and pvar.base.value is 'constructor' and func instanceof CodeNode
         func.name: @variable.compile(o)
-        func.body.push(new ReturnNode(literal('this')))
+        func.body.push new ReturnNode literal 'this'
         @variable: new ValueNode @variable
         @variable.namespaced: include func.name, '.'
-        constructor: new AssignNode(@variable, func)
+        constructor: func
+        continue
+      if func instanceof CodeNode and func.bound
+        constructor.body.push    new ReturnNode literal 'this' if constructor.body.empty()
+        constructor.body.unshift new AssignNode(new ValueNode(literal('this'), [new AccessorNode(pvar)]), func)
       else
         if pvar
           access: if prop.context is 'this' then pvar.base.properties[0] else new AccessorNode(pvar, 'prototype')
@@ -675,16 +686,7 @@
           prop:   new AssignNode(val, func)
         props.push prop

-    unless constructor
-      if @parent
-        applied: new ValueNode(@parent, [new AccessorNode(literal('apply'))])
-        constructor: new AssignNode(@variable, new CodeNode([], new Expressions([
-          new CallNode(applied, [literal('this'), literal('arguments')])
-        ])))
-      else
-        constructor: new AssignNode(@variable, new CodeNode())
-
-    construct:                       @idt() + constructor.compile(o) + ';\n'
+    construct: @idt() + (new AssignNode(@variable, constructor)).compile(o) + ';\n'
     props:     if props.empty() then '' else props.compile(o) + '\n'
     extension: if extension     then @idt() + extension.compile(o) + ';\n' else ''
     returns:   if @returns      then new ReturnNode(@variable).compile(o)  else ''


<model> Actuator - Correcting small bugs

--- server/models/actuator.coffee
+++ server/models/actuator.coffee
@@ -98,16 +98,20 @@
 				if err
 					callback err, null
 				else if actuators.length isnt 0 # Actuator already exists.
-					callback 'Device already added', actuator
+					callback 'Device already added', actuators[0]
 				else
 					superCreate data, (err, actuator) ->
+						if err
+							callback err, null
+							return
+
 						# Let the driver handle the integration of the device to the system:
 						actuatorsDrivers[type].add customId, actuator.id, (err) ->
-						if err
-							# Cancelling modif:
-							thisActuator.requestDestroy "all", {key: actuator.id}, (err) ->
-								callback err, null
-						else
-							callback null, actuator
+							if err
+								# Cancelling modif:
+								thisActuator.requestDestroy "all", {key: actuator.id}, (err) ->
+									callback err, null
+							else
+								callback null, actuator
 		else
 			callback 'Device not supported', null


styling works with normalization

styling works with normalization

--- src/line.coffee
+++ src/line.coffee
@@ -31,8 +31,12 @@
     Tandem.Utils.traversePreorder(root, 0, (node) ->
       if node.nodeType == node.ELEMENT_NODE && !TandemLine.isLineNode(node)
         next = node.nextSibling
-        if next? && node.tagName == next.tagName && node.className == next.className
-          node = Tandem.Utils.mergeNodes(node, next)
+        if next? && node.tagName == next.tagName
+          if _.union(node.classList, next.classList) >= Math.min(node.classList.length, node.classList.length)
+            if node.classList.length > next.classList.length
+              node = Tandem.Utils.mergeNodes(node, next)
+            else
+              node = Tandem.Utils.mergeNodes(next, node)
       return node
     )

@@ -53,9 +57,10 @@
         if node.textContent.length == 0
           return true
         attribute = Tandem.Utils.getAttributeForContainer(node)
-        if attribute? && node.parentNode[key]? && node.parentNode[key][attribute] == true
-          return true
-        if node.tagName == 'SPAN'
+        if attribute?
+          if node.parentNode[key]? && node.parentNode[key][attribute] == true
+            return true
+        else if node.tagName == 'SPAN'
           # Check if children need us
           if node.childNodes.length == 0 || !_.any(node.childNodes, (child) -> child.nodeType != child.ELEMENT_NODE)
             return true


solve dragging interruption to geolocation

--- coffee/app.coffee
+++ coffee/app.coffee
@@ -154,6 +154,8 @@
                         now = Math.floor(new Date() / 1000)
                         if watchState.lastUpdated + 3 > now
                             return false
+                        else if $rootScope.dragging
+                            return false
                         else
                             watchState.lastUpdate = now
                             $rootScope.current = (pos.coords.latitude + ',' + pos.coords.longitude)
@@ -353,6 +355,12 @@

             map.addListener 'click', $scope.pinSetCallback

+            # Dragging must not interrupts geolocational.
+            map.addListener 'dragstart', () ->
+                $rootScope.dragging = true
+            map.addListener 'dragend'
+                $rootScope.dragging = false
+
             # rewrite URL when map have finished moving
             map.addListener 'idle', () ->
                 $rootScope.serial.mapPosition =
@@ -362,6 +370,9 @@
                 urlEncoder.encode()

+
+
+
             #current position marker move
             $rootScope.$on 'currentMoved', () ->
                 $scope.current = $rootScope.current
@@ -374,70 +385,3 @@
                 $rootScope.$watch style ,() ->
                     map.data.setStyle $scope.mapStyler
 ]
-
-
-
-
-
-return
-
-#
-#
-# # 一回だけ現在地を取得
-# geolocatorOptions =
-#     enableHighAccuracy: true
-#     timeout: 8000
-#     maximumAge: 1000
-#
-# if navigator.geolocation
-#     # the device offer geolocation
-#     navigator.geolocation.getCurrentPosition (pos) ->
-#         # geolocation success
-#         console.log pos.coords.latitude, pos.coords.longitude
-#         # return new google.maps.LatLng pos.coords.latitude, pos.coords.longitude
-#     ,(error) ->
-#         # geolocation failed
-#         msg=
-#             0:'unknown error'
-#             1:'access permission denied'
-#             2:'due to device or environment'
-#             3:'timeout'
-#         console.log "error #{error.code}:#{msg[error.code]}"
-#     ,geolocatorOptions
-# else
-#     # the device donot offer geolocation
-#     console.log 'your device donot offer geolocation.'
-#
-#
-# # 現在地をwatch
-# watchState =
-#     id: null
-#     count: 0
-#     lastUpdated: 0
-#     map: null
-#     marker: null
-# if navigator.geolocation
-#     # the device offer geolocation
-#     watchState.id = navigator.geolocation.watchPosition (pos) ->
-#         # geolocation success
-#         watchState.count++
-#         now = Math.floor(new Date() / 1000)
-#         if watchState.lastUpdated + 3 > now
-#             return false
-#         else
-#             watchState.lastUpdate = now
-#         console.log pos.coords.latitude, pos.coords.longitude
-#         # return new google.maps.LatLng pos.coords.latitude, pos.coords.longitude
-#     ,(error) ->
-#         # geolocation failed
-#         msg=
-#             0:'unknown error'
-#             1:'access permission denied'
-#             2:'due to device or environment'
-#             3:'timeout'
-#         console.log "error #{error.code}:#{msg[error.code]}"
-#     ,geolocatorOptions
-# else
-#     # the device donot offer geolocation
-#     console.log 'your device donot offer geolocation.'
-#


Show main window on focus if it is hidden

--- src/main.coffee
+++ src/main.coffee
@@ -287,7 +287,10 @@

     ipc.on 'appfocus', ->
       app.focus()
-      mainWindow.focus()
+      if mainWindow.isVisible()
+        mainWindow.focus()
+      else
+        mainWindow.show()

     # no retries, dedupe on conv_id
     ipc.on 'settyping', seqreq (ev, conv_id, v) ->