Make file permissions popup work.

Make file permissions popup work.

--- client/landing/app/MainApp/fs/fsitem.coffee
+++ client/landing/app/MainApp/fs/fsitem.coffee
@@ -224,7 +224,7 @@
       method      : "fs.setPermissions"
       vmName      : @vmName
       withArgs    :
-        path      : @path
+        path      : FSHelper.plainPath @path
         recursive : recursive
         mode      : permissions
     , (err, res)=>


kitecontroller: fix some OoO bugs

kitecontroller: fix some OoO bugs

--- client/landing/app/MainApp/kite/kite.coffee
+++ client/landing/app/MainApp/kite/kite.coffee
@@ -1,4 +1,4 @@
-class Kite extends KDEventEmitter
+class Kite extends KDObject

   [ NOTREADY, READY ] = [ 0, 1 ]

@@ -14,19 +14,20 @@
     @localStore   = new Store
     @remoteStore  = new Store

-    @entropy = @createId 128
-
     @readyState = NOTREADY

   createId: Bongo.createId

   initChannel: ->
-    @channelName = @getChannelName()
+    @entropy        = @createId 128
+    @qualifiedName  = "kite-#{@kiteName}"
+
+    @channelName  = @getChannelName()

     @channel = KD.remote.mq.subscribe @channelName
     @channel.setAuthenticationInfo
       serviceType       : 'kite'
-      name              : "kite-#{@kiteName}"
+      name              : @qualifiedName
       correlationName   : @correlationName
       clientId          : KD.remote.getSessionToken()

@@ -40,7 +41,9 @@
     @channel.on 'publish'               , @bound 'handleChannelPublish'
     @channel.on 'possibleUnresponsive'  , @bound 'handleSuspectChannel'
     @channel.on 'unresponsive'          , @bound 'handleUnresponsiveChannel'
-    @channel.once 'broker.subscribed'   , @bound 'handleBrokerSubscribed'
+
+  handleBrokerSubscribed:->
+

   cycleChannel: ->
     @setStopPinging()
@@ -50,11 +53,12 @@
     @emit 'destroy'

   pingChannel: (callback) ->
+    return if @stopPinging
     @channel.publish JSON.stringify
       method      : 'ping'
       arguments   : []
       callbacks   : {}
-    @once 'pong', callback  if callback
+    @channel.once 'pong', callback  if callback

   setStartPinging: -> @stopPinging = false

@@ -66,7 +70,7 @@
     @unresponded = 0

     @pingTimeoutId = setTimeout =>
-      @ping()
+      @pingChannel()
     , 5000

   handleChannelMessage: (args) ->
@@ -79,10 +83,8 @@
       when 'pong'             then @bound 'handlePong'
       when 'cycleChannel'     then @bound 'cycleChannel'
       else (@localStore.get method) ? ->
-
-    @unscrubbed = @unscrub args

-    callback.apply this, unscrubbed
+    callback.apply this, @unscrub args

   handleChannelPublish: ->
     clearTimeout @pingTimeoutId           if @pingTimeoutId?
@@ -109,8 +111,9 @@
     return KD.utils.defer callback  if @readyState is READY
     @once 'ready', callback

-  handleReady: ->
+  handleReady: (resourceName)->
     @readyState = READY
+    @channel.exchange = resourceName
     @emit 'ready'

   handleError: (err) ->
@@ -133,7 +136,7 @@
       scrubbed.method or= method
       callback scrubbed

-  unscrub:->
+  unscrub: (args) ->
     scrubber = new Scrubber @localStore
     return scrubber.unscrub args, (callbackId) =>
       unless remoteStore.has callbackId
@@ -146,7 +149,7 @@
     nickname  = delegate?.profile.nickname ?
                 if delegate.guestId then "guest#{delegate.guestId}" ?
                 'unknown'
-    channelName = "#{@entropy}.#{nickname}.#{@kiteName}"
+    channelName = "#{@entropy}.#{nickname}.#{@qualifiedName}"
     return channelName

   tell:(options, callback) ->


landing: build: remove dev server init gulp task

--- client/landing/site.landing/gulpfile.coffee
+++ client/landing/site.landing/gulpfile.coffee
@@ -10,7 +10,6 @@
 {
   STYLES_PATH, COFFEE_PATH
   INDEX_PATH, SPRITES_PATH
-  SERVER_FILE, SERVER_PATH
   BUILD_PATH
 } = req 'helper.constants'

@@ -18,11 +17,6 @@
 # HELPERS

 {watchLogger, log} = req 'helper.logger'
-
-
-# BUILD SERVER
-
-gulp.task 'serve', ['build'], -> server = nodemon script: SERVER_FILE

 # STYLUS COMPILATION


Use string key to initialise editor mode instead of boolean

--- services/web/public/coffee/ide/editor/EditorManager.coffee
+++ services/web/public/coffee/ide/editor/EditorManager.coffee
@@ -14,7 +14,7 @@
 				opening: true
 				trackChanges: false
 				wantTrackChanges: false
-				showRichText: localStorage("editor.mode.#{@$scope.project_id}") or false
+				showRichText: localStorage("editor.mode.#{@$scope.project_id}") == 'rich-text'
 			}

 			@$scope.$on "entity:selected", (event, entity) =>


Add comment explaining debounce

Add comment explaining debounce

--- services/web/public/coffee/ide/labels/LabelsManager.coffee
+++ services/web/public/coffee/ide/labels/LabelsManager.coffee
@@ -33,6 +33,7 @@
 			@state.documents[docId] = labels

 		scheduleLoadLabelsFromOpenDoc: () ->
+			# De-bounce loading labels with a timeout
 			if @loadLabelsTimeout
 				clearTimeout(@loadLabelsTimeout)
 			@loadLabelsTimeout = setTimeout(


workflow level dropdown

workflow level dropdown

--- app/pages/admin/project-status.cjsx
+++ app/pages/admin/project-status.cjsx
@@ -25,6 +25,7 @@
   'column'
   'grid'
   'invert'
+  'workflow level'
 ]

 ProjectToggle = React.createClass
@@ -146,8 +147,41 @@
 ProjectStatus = React.createClass
   displayName: "ProjectStatus"

-  getDefaultProps: ->
-    project: null
+  propTypes:
+    project: React.PropTypes.object.isRequired
+
+  getInitialState: ->
+    usedWorkflowLevels: []
+    workflows: []
+
+  getDefaultProps: ->
+    project: null
+
+  componentDidMount: ->
+    getWorkflowsInOrder @props.project, fields: 'display_name,active,configuration'
+      .then (workflows) =>
+        usedWorkflowLevels = @getUsedWorkflowLevels(workflows)
+        @setState usedWorkflowLevels: usedWorkflowLevels, workflows: workflows
+
+  onChangeWorkflowLevel: (workflow, event) ->
+    selected = event.target.value
+    if selected is 'none'
+      workflow.update({ 'configuration.level': undefined })
+    else
+      workflow.update({ 'configuration.level': selected })
+
+    usedWorkflowLevels = @getUsedWorkflowLevels(@state.workflows);
+
+    @setState usedWorkflowLevels: usedWorkflowLevels
+
+  getUsedWorkflowLevels: (workflows) ->
+    usedWorkflowLevels = []
+
+    for workflow in workflows
+      if workflow.configuration.level?
+        usedWorkflowLevels.push(workflow.configuration.level);
+
+    usedWorkflowLevels

   render: ->
     <ChangeListener target={@props.project}>{ =>
@@ -168,19 +202,37 @@
         <ProjectExperimentalFeatures project={@props.project} />

Workflow Settings

-          <PromiseRenderer promise={getWorkflowsInOrder @props.project, fields: 'display_name,active'}>{(workflows) =>
-            if workflows.length is 0
-
No workflows found

-            else
+          {if @state.workflows.length is 0
+
No workflows found

+          else
+

+
Workflow Settings

-                  {workflows.map (workflow) =>
+                  {@state.workflows.map (workflow) =>

                       <WorkflowToggle workflow={workflow} project={@props.project} field="active" />
+                      <AutoSave resource={workflow}>
+                        <select
+                          onChange={@onChangeWorkflowLevel.bind(null, workflow)}
+                          value={if workflow.configuration.level? then workflow.configuration.level}
+                        >
+                          <option value="none">none</option>
+                          {@state.workflows.map (workflow, i) =>
+                            value = String(i + 1)
+                            <option
+                              key={i + Math.random()}
+                              value={value}
+                              disabled={@state.usedWorkflowLevels.indexOf(value) > -1}
+                            >
+                              {value}
+                            </option>}
+                        </select>
+                      </AutoSave>

}

-

-          }</PromiseRenderer>
+

+
}


!= -> isnt

!= -> isnt

--- lib/document.coffee
+++ lib/document.coffee
@@ -63,7 +63,7 @@
     @_write "%\xFF\xFF\xFF\xFF"

     # Add the first page
-    if @options.autoFirstPage != false
+    if @options.autoFirstPage isnt false
       @addPage()

   mixin = (methods) =>


Fix scroll bug

Fix scroll bug

--- app/scripts/controllers/form.coffee
+++ app/scripts/controllers/form.coffee
@@ -1,6 +1,6 @@
 angular
     .module 'yuAmyWeddinggithubioApp'
-    .controller 'FormCtrl', ($timeout, $location, $anchorScroll, $window) ->
+    .controller 'FormCtrl', ($timeout, $anchorScroll, $window) ->
         vm = @

         WILL_ATTEND = 'yes'
@@ -45,17 +45,19 @@

             $timeout ->

+                target = null
+
                 if vm.hasChildSeatCountError = hasChildSeatCountError
-                    $location.hash('child-seat-form-group')
+                    target = 'child-seat-form-group'

                 if vm.hasAttendeeCountError = hasAttendeeCountError
-                    $location.hash('attendee-form-group')
+                    target = 'attendee-form-group'

                 if vm.hasNameError = hasNameError
-                    $location.hash('name-form-group')
+                    target = 'name-form-group'

-                if hasNameError || hasAttendeeCountError || hasChildSeatCountError
-                    $anchorScroll()
+                if target
+                    $anchorScroll(target)

             !hasNameError && !hasAttendeeCountError && !hasChildSeatCountError


AMD-ified elyssa/types/size

AMD-ified elyssa/types/size

--- src/types/size.coffee
+++ src/types/size.coffee
@@ -1,16 +1,16 @@
-do (window = @, Elyssa = @Elyssa or= {}) ->
+define 'elyssa/types/size', ['elyssa/types/rect', 'serialize', 'deserialize'], (Rect, serialize, deserialize) ->

-  class Elyssa.Size
+  class Size
     constructor: ({@w, @h} = {w: 0, h: 0}) ->
       @w = 0 unless @w?
       @h = 0 unless @h?

-    toRect: -> new Elyssa.Rect {x: 0, y: 0, @w, @h}
+    toRect: -> new Rect {x: 0, y: 0, @w, @h}

     center: ->
       x: @w / 2
       y: @h / 2

-    toString: -> Elyssa.serialize {@x, @y, @w, @h}, defaultValue
+    toString: -> serialize {@x, @y, @w, @h}, defaultValue

-    @fromString: (rectString) -> Elyssa.deserialize rectString, @name+    @fromString: (rectString) -> deserialize rectString, @name


"Ace" class is optional.

"Ace" class is optional.

--- client/ace/lib/aceview.coffee
+++ client/ace/lib/aceview.coffee
@@ -27,6 +27,7 @@

     super options, file

+    options.aceClass                or= Ace
     options.advancedSettings         ?= no
     options.createBottomBar          ?= yes
     options.createFindAndReplaceView ?= yes
@@ -37,7 +38,7 @@
       delegate: options.delegate or this
       createFindAndReplaceView: options.createFindAndReplaceView

-    @ace = new Ace aceOptions, file
+    @ace = new options.aceClass aceOptions, file

     if options.createFindAndReplaceView
       @findAndReplaceView = new AceFindAndReplaceView delegate: this


set time to force not due

set time to force not due

--- tutor/specs/components/scores/homework-cell.spec.cjsx
+++ tutor/specs/components/scores/homework-cell.spec.cjsx
@@ -30,7 +30,9 @@

   it 'renders score cell', ->
     @props.task.completed_exercise_count = @props.task.exercise_count - 1
-    @props.due_at = moment().add(10, 'day')
+    now = new Date
+    TimeActions.setNow(now)
+    @props.due_at = moment(now).subtract(10, 'day')
     expect(TH.isDue(@props.task)).to.eq(false)
     wrapper = mount(<Cell {...@props} />)
     expect(wrapper.find('.score a').text()).to.equal('---')


Add script tidbit

Add script tidbit

--- coffee/formats/package.coffee
+++ coffee/formats/package.coffee
@@ -258,6 +258,8 @@
       "prepublish": "rm -r build; gulp build"
     }

+
You can also create a script with any name you like, and run it with npm run SCRIPT_NAME.

+

More information about scripts

   '''


Scum Aces titles

Scum Aces titles

Added the ship tags for both titles, Kihraxz and Viper, as they were missing.

--- coffeescripts/cards-es.coffee
+++ coffeescripts/cards-es.coffee
@@ -1921,8 +1921,10 @@
         'Havoc':
             text: '''Scurrg H-6 Bomber only.%LINEBREAK%Your upgrade bar gains the %SYSTEM% and %SALVAGEDASTROMECH% icons and loses the %CREW% upgrade icon.%LINEBREAK%You cannot equip non-unique %SALVAGEDASTROMECH% Upgrade cards.'''
         'Vaksai':
+            ship: "Caza Kihraxz"
             text: '''Kihraxz Fighter only.%LINEBREAK%The squad point cost of each of your equipped upgrades is reduced by 1 (to a minimum of 0).%LINEBREAK%You may equip up to 3 different Modification upgrades.'''
         'StarViper Mk. II':
+            ship: "Víbora Estelar"
             text: '''StarViper only.%LINEBREAK%You may equip up to 2 different title Upgrades.%LINEBREAK%When performing a barrel roll action, you must use the (%BANKLEFT% 1) or (%BANKRIGHT% 1) template instead of the (%STRAIGHT% 1) template.'''

     condition_translations =


LongInt::minus() compares before subtracting.

--- lib/long_int.coffee
+++ lib/long_int.coffee
@@ -9,9 +9,11 @@

 if typeof(require) != 'undefined'
   require.paths.unshift __dirname
-  { Stream } = require 'stream'
+  { recur, resolve } = require 'trampoline'
+  { List }           = require 'list'
+  { Stream }         = require 'stream'
 else
-  { Stream } = this.pazy
+  { recur, resolve, List, Stream } = this.pazy

 class LongInt
@@ -40,6 +42,14 @@
       new LongInt(add(this.digits, other.digits), this.sign)

   minus: (other) ->
+    cmp = (diff, r, s) ->
+      if r and s
+        recur -> cmp(new List(r.first() - s.first(), diff), r.rest(), s.rest())
+      else if r or s
+        if r then 1 else -1
+      else
+        diff.drop_while((x) -> x == 0)?.first() or 0
+
     sub = (r, s, b = 0) ->
       if b or (r and s)
         [r_, s_] = [r or Z, s or Z]
@@ -51,12 +61,10 @@

     if this.sign != other.sign
       this.plus other.neg()
+    else if (resolve cmp(null, this.digits, other.digits)) < 0
+      new LongInt(sub(other.digits, this.digits), -this.sign)
     else
-      digits = sub(this.digits, other.digits)
-      if digits.last() < 0
-        new LongInt(sub(other.digits, this.digits), -this.sign)
-      else
-        new LongInt(digits, this.sign)
+      new LongInt(sub(this.digits, other.digits), this.sign)

   toString: (sep = '') ->
     rev = @digits.reverse().drop_while (d) -> d == 0
@@ -82,12 +90,15 @@

 a = new LongInt(Stream.fromArray([499, 999, 999, 999]), -1)
 b = new LongInt(Stream.fromArray [501])
+c = new LongInt(Stream.fromArray [500, 999, 999, 999])

 console.log "a   = #{a.toString('_')}"
 console.log "b   = #{b.toString('_')}"
+console.log "c   = #{c.toString('_')}"
 console.log "a+b = #{(a.plus b).toString('_')}"
 console.log "a-b = #{(a.minus b).toString('_')}"
 console.log "a-a = #{(a.minus a).toString('_')}"
 console.log "b-a = #{(b.minus a).toString('_')}"
+console.log "a+c = #{(a.plus c).toString('_')}"

 ###


When the configuration for the uncrustify config file path has no path atom-beautify had the annoying habit of prepending the path to the file open in the editor pane. This meant that in order to use a project specific configuration every directory would need to contain a config file -- unnacceptable. This changes the scheme to prepend the path to the project when no path is specified making it possible to have a single configuration file for an entire project and also being able to maintain the configuration under source control.

--- src/beautifiers/uncrustify/index.coffee
+++ src/beautifiers/uncrustify/index.coffee
@@ -10,7 +10,6 @@

 module.exports = class Uncrustify extends Beautifier
   name: "Uncrustify"
-  link: "https://github.com/uncrustify/uncrustify"
   options: {
     Apex: true
     C: true
@@ -38,15 +37,17 @@
         editor = atom.workspace.getActiveTextEditor()
         if editor?
           basePath = path.dirname(editor.getPath())
+          projectPath = atom.workspace.project.getPaths()[0]
+          # console.log(basePath);
           # Expand Home Directory in Config Path
-          configPath = expandHomeDir(configPath)
-          # console.log(basePath);
-          configPath = path.resolve(basePath, configPath)
+          expandedConfigPath = expandHomeDir(configPath)
+          configPath = path.resolve(projectPath, expandedConfigPath)
           resolve configPath
         else
           reject(new Error("No Uncrustify Config Path set! Please configure Uncrustify with Atom Beautify."))
     )
     .then((configPath) =>
+

       # Select Uncrustify language
       lang = "C" # Default is C


Hide API changed, now filtering by id only !

--- popupmodal.coffee
+++ popupmodal.coffee
@@ -1,8 +1,5 @@
 _popups = new ReactiveVar []
 _setup = new ReactiveVar
-
-getRandomInt = (min, max) ->
-  Math.floor(Math.random() * (max - min)) + min

 @Popups =
   # Default size with the same name as Bootstrap. But not the same meaning!
@@ -22,32 +19,34 @@
     _setup.set opts

   # Hide a modal (i.e. remove it from the list)
-  hide: (query, cb) ->
-    if typeof query == "function"
-      [cb, query] = [query, {}]
+  hide: (id, cb) ->
+    if typeof id is "function"
+      throw new Error "Must specify a popup id!"

     popups = _popups.get()
     setup = _setup.get()
-    {duration} = setup.lightbox.outTransition
-    shown = _.compact popups.map (popup) ->
-      shouldRemove = true
-      for k,v of query
-        if popup[k] != v then shouldRemove = false

-      if shouldRemove
-        fview = FView.byId popup.id
-        return unless fview
-        child = fview.children[0]
-        duration = popup.lightbox?.outTransition.duration ? duration
-        fview.surface.lightbox.hide()
-      else
-        popup
+    # We are looking for one (or more) popups to remove
+    duration = null
+    shown = popups.filter (popup) ->
+      if popup.id is id
+        duration = popup.lightbox?.outTransition.duration
+      popup.id isnt id
+
+    duration ?= setup.lightbox.outTransition.duration
+
+    fview = FView.byId id
+    return unless fview
+    child = fview.children[0]
+    fview.surface.lightbox.hide()

     # Trigger timeout only if needed.
     # Wait for out animation to complete and call back
     if shown.length isnt popups.length
       famous.utilities.Timer.setTimeout ->
         _popups.set shown
+        # remove ourselves
+        fview.destroy true
         cb && cb fview
       , duration

@@ -67,10 +66,8 @@
     unless _setup.get()
       @setOptions()

-    # FIXME: It apparently confuses with #View ID and modifies its reference
-    # Temp fix is to generate a random integer and expect it to not be already
-    # a FamousView ID.
-    opts.id ?= getRandomInt 1000, 2000
+    # We need to somehow identify which ones are ours..
+    opts.id = "popup_#{Random.id()}"

     # We need to set z-index property to make sure we will be in front
     p = _popups.get()


got AJAX responses working properly (needed extra newline)

--- lib/mobwrite.coffee
+++ lib/mobwrite.coffee
@@ -99,12 +99,21 @@

         daemonSocket.on "end", ->
           logger?.log(">>> socket finished reading from daemon")
+
+          # JSONP responses need to be serialized onto a single line and
+          # sent to the global mobwrite.callback() function.
+          # TODO: allow this callback to be customized with JS monkeypatching?
           if clientNeedsJsonp
             daemonResponse = daemonResponse.replace(new RegExp("\\\\", "g"), "\\\\")
             daemonResponse = daemonResponse.replace(new RegExp("\\\"", "g"), "\\\"")
             daemonResponse = daemonResponse.replace(new RegExp("\\n", "g"), "\\n")
             daemonResponse = daemonResponse.replace(new RegExp("\\r", "g"), "\\r")
             daemonResponse = "mobwrite.callback(\"#{daemonResponse}\");"
+
+          # AJAX responses need an extra newline at the end.
+          else
+            daemonResponse += "\n"
+
           console.warn(">> sending to client\n---\n#{daemonResponse}\n---")
           res.writeHead(200, {"Content-Type": "text/javascript"})
           res.end(daemonResponse)
@@ -171,7 +180,7 @@
               </table>
           </form>
           <script>
-              mobwrite.syncGateway = location.protocol + '//' + location.host + "/__mobwrite__/";
+              //mobwrite.syncGateway = location.protocol + '//' + location.host + "/__mobwrite__/";
               mobwrite.debug = true;
               mobwrite.share('mobwrite-form');
           </script>


#340 :beer: add-selection can get word from current-selection.

- Now can invoke in visual-mode.
- When cursor is on nonWordChar it won’t set \b for pattern since its
result in invalid regex. (BUG FIX)

--- lib/operator.coffee
+++ lib/operator.coffee
@@ -10,6 +10,7 @@
   isEndsWithNewLineForBufferRow
   isAllWhiteSpace
   isSingleLine
+  getCharacterAtCursor
 } = require './utils'
 swrap = require './selection-wrapper'
 settings = require './settings'
@@ -1040,20 +1041,36 @@

 class AddSelection extends Operator
   @extend()
+  selectedText: null
+
+  initialize: ->
+    if @selectedText = @editor.getSelectedText()
+      # When invoked from visual-mode, ensure start from 'normal' mode.
+      @vimState.activate('normal') unless @isMode('normal')
+
+  isNonWordCharacters: (char, scope) ->
+    char in atom.config.get('editor.nonWordCharacters', {scope})

   execute: ->
     lastSelection = @editor.getLastSelection()
-    lastSelection.selectWord() unless @isMode('visual')
-    word = @editor.getSelectedText()
-    return if word is ''
+    cursor = lastSelection.cursor
+
+    pattern = null
+    if @selectedText
+      pattern = ///#{_.escapeRegExp(@selectedText)}///g
+    else
+      char = getCharacterAtCursor(cursor)
+      scope = cursor.getScopeDescriptor().getScopesArray()
+      if @isNonWordCharacters(char, scope)
+        pattern = ///#{_.escapeRegExp(char)}///g
+      else
+        lastSelection.selectWord()
+        word = lastSelection.getText()
+        pattern = ///\b#{_.escapeRegExp(word)}\b///g
+
     return unless @selectTarget()

     ranges = []
-    pattern = if @isMode('visual')
-      ///#{_.escapeRegExp(word)}///g
-    else
-      ///\b#{_.escapeRegExp(word)}\b///g
-
     for selection in @editor.getSelections()
       scanRange = selection.getBufferRange()
       @editor.scanInBufferRange pattern, scanRange, ({range}) ->
@@ -1061,8 +1078,7 @@

     if ranges.length
       @editor.setSelectedBufferRanges(ranges)
-      unless @isMode('visual', 'characterwise')
-        @activateMode('visual', 'characterwise')
+      @activateMode('visual', 'characterwise') unless @isMode('visual', 'characterwise')

 class SelectAllInRangeMarker extends AddSelection
   @extend()


restorePoint support when withOccurrence

--- lib/operator.coffee
+++ lib/operator.coffee
@@ -71,15 +71,15 @@
     @setTarget(@new(@target)) if _.isString(@target)

   restorePoint: (selection) ->
-    if @isWithOccurrence()
-      return # [TODO] support restore
-
-    if @wasNeedStay
-      which = 'head'
-    else
-      which = 'start'
+    which = if @wasNeedStay then 'head' else 'start'
     if swrap(selection).getProperties().head?
-      swrap(selection).setBufferPositionTo(which, fromProperty: true)
+      @onDidFinishOperation =>
+        # Deffer restoring cursor point.
+        # restorePoint is processed one by one, so immediately updating cursorPosition result in intersecting range with other selection
+        # when intersecting selection is destroyed, cursor is moved automatically.
+        swrap(selection).setBufferPositionTo(which, fromProperty: true)
+    else
+      selection.destroy()

   # [debug]
   reportSelectionProperties: (subject) ->


add `indent-detective` to auto installed packages

Should release a tag with https://github.com/JunoLab/atom-indent-detective/pull/4 merged though, otherwise the package doesn't work properly.

--- lib/packages.coffee
+++ lib/packages.coffee
@@ -3,6 +3,7 @@
 requirements = [
   'tool-bar'
   'highlight-selected'
+  'indent-detective'
   'latex-completions'
   'language-julia'
   'ink'


Not export phone

Not export phone

--- lib/restivus.coffee
+++ lib/restivus.coffee
@@ -195,8 +195,9 @@
           entity = collection.findOne @urlParams.id, fields:
                                                         profile: 1
                                                         username: 1
-                                                        'profile.portrait': 1
+                                                        'profile': 1
                                                         fortune: 1
+          delete entity.profile.phone
           if entity
             {status: "success", data: entity}
           else


Error when duplicate keys are encountered

--- lib/snippets.coffee
+++ lib/snippets.coffee
@@ -170,7 +170,7 @@

   loadSnippetsFile: (filePath, callback) ->
     return callback({}) unless CSON.isObjectPath(filePath)
-    CSON.readFile filePath, (error, object={}) ->
+    CSON.readFile filePath, {allowDuplicateKeys: false}, (error, object={}) ->
       if error?
         console.warn "Error reading snippets file '#{filePath}': #{error.stack ? error}"
         atom.notifications.addError("Failed to load snippets from '#{filePath}'", {detail: error.message, dismissable: true})


Now BaseMolecule has a $/$$ object in "el" attribute

--- source/molecules/base.coffee
+++ source/molecules/base.coffee
@@ -10,19 +10,19 @@
   constructor: (@attributes) ->
     @className = @constructor.name
     @type = "Molecule"
+    @el = Atoms.$ Atoms.render(@template)(@attributes)
     if @attributes?.organism?
       @organism = Atoms.$ @attributes.organism
-
+      @organism.append @el
     @createAtoms()

   createAtoms: =>
-    console.log ">", @atoms
     for index of @atoms
       atom = @atoms[index]
       className = index[0].toUpperCase() + index[1..-1].toLowerCase()

       attributes = @attributes[index] or atom
-      attributes.molecule = @organism
+      attributes.molecule = @el

       @[index] = new Atoms.Atom[className] attributes
       @[index].bind "atom-#{event}", @[event] for event in atom.binds


store last icon name in an instance variable

--- lib/tab-view.coffee
+++ lib/tab-view.coffee
@@ -55,11 +55,11 @@
     @updatingTitle = false

   updateIcon: ->
-    @title.removeClass (index, css) ->
-      (css.match(/\bicon(-\w+)?/g) || []).join(' ')
+    if @iconName
+      @title.removeClass "icon icon-#{@iconName}"

-    if @item.getIcon && iconName = @item.getIcon()
-      @title.addClass "icon icon-#{iconName}"
+    if @iconName = @item.getIconName?()
+      @title.addClass "icon icon-#{@iconName}"

   getSiblingTabs: ->
     @siblings('.tab').views()


Add AngularJS 1.3.x to templates

Add AngularJS 1.3.x to templates

--- assets/js/directives/toolbar.coffee
+++ assets/js/directives/toolbar.coffee
@@ -70,6 +70,8 @@

1.1.x (unstable)

1.2.x

+

+
1.3.x


syntax--number -> syntax--numeric

syntax--number -> syntax--numeric

--- lib/ui/views.coffee
+++ lib/ui/views.coffee
@@ -77,7 +77,7 @@
   number: ({value, full}) ->
     rounded = value.toPrecision(3)
     rounded += '…' unless rounded.toString().length >= full.length
-    view = @render @tags.span 'syntax--constant syntax--number', rounded
+    view = @render @tags.span 'syntax--constant syntax--numeric', rounded
     isfull = false
     view.onclick = (e) ->
       view.innerText = if !isfull then full else rounded


Update omitted js file.

Update omitted js file.

--- test/spec/core/types/string.spec.coffee
+++ test/spec/core/types/string.spec.coffee
@@ -96,3 +96,8 @@

     expect('   foo'.strip()).toBe('foo')
     expect('foo   '.strip()).toBe('foo')
+
+describe 'String::empty', ->
+  it 'should returns true when the string has a length of 0', ->
+    expect(''.empty()).toBeTruthy()
+    expect('foo'.empty()).toBeFalsy()


Remove test obviated by HTML replacement tests

--- test/src/system/basic_input_test.coffee
+++ test/src/system/basic_input_test.coffee
@@ -47,18 +47,6 @@
           dragToCoordinates coordinates, ->
             expectDocument "acb\n"

-editorTest "deleting a line (command + backspace) in a list item", (expectDocument) ->
-  clickToolbarButton attribute: "bullet", ->
-    typeCharacters "a\nb", ->
-      triggerEvent(document.activeElement, "keydown", charCode: 0, keyCode: 8, which: 8, metaKey: true)
-      selectNode(document.activeElement.querySelectorAll("li")[1])
-      deleteSelection()
-      defer ->
-        assertLocationRange index: 1, offset: 0
-        expectBlockAttributes [0, 1], ["bulletList", "bullet"]
-        expectBlockAttributes [2, 3], ["bulletList", "bullet"]
-        expectDocument "a\n\n"
-
 editorTest "inserting newline after cursor (control + o)", (expectDocument) ->
   typeCharacters "ab", ->
     moveCursor "left", ->


Fix state traversal in statechart's changeValue

--- app/assets/javascripts/rglossa/react/statechart.coffee
+++ app/assets/javascripts/rglossa/react/statechart.coffee
@@ -50,6 +50,7 @@
         state.args[argname] = newValue
         @currentStateChangedHandler(@) if @currentStateChangedHandler
         return newValue
+      state = state.parent
     throw new Error("No state value #{argname} found")


use filename and s3_bucket from upload data

--- app/assets/javascripts/uploadcare/files/base.js.coffee
+++ app/assets/javascripts/uploadcare/files/base.js.coffee
@@ -19,12 +19,14 @@
     constructor: (@settings) ->
       @fileId = null
       @fileName = null
+      @sanitizedName = null
       @fileSize = null
       @isStored = null
       @cdnUrlModifiers = null
       @isImage = null
       @imageInfo = null
       @sourceInfo = null
+      @s3Bucket = null

       # this can be exposed in future
       @onInfoReady = $.Callbacks('once memory')
@@ -46,12 +48,17 @@

     __handleFileData: (data) =>
       @fileName = data.original_filename
+      @sanitizedName = data.filename
       @fileSize = data.size
       @isImage = data.is_image
       @imageInfo = data.image_info
       @isStored = data.is_stored
+      @s3Bucket = data.s3_bucket
       if data.default_effects
         @cdnUrlModifiers = "-/" + data.default_effects
+
+      if @s3Bucket and @cdnUrlModifiers
+        @__rejectApi('baddata')

       if not @onInfoReady.fired()
         @onInfoReady.fire(@__fileInfo())
@@ -77,18 +84,20 @@
       incompleteFileInfo: @__fileInfo()

     __fileInfo: =>
+      if @s3Bucket
+        urlBase = "https://#{@s3Bucket}.s3.amazonaws.com/#{@fileId}/#{@sanitizedName}"
+      else
+        urlBase = "#{@settings.cdnBase}/#{@fileId}/"
+
       uuid: @fileId
       name: @fileName
       size: @fileSize
       isStored: @isStored
-      isImage: @isImage
+      isImage: not @s3Bucket and @isImage
       originalImageInfo: @imageInfo
-      originalUrl: if @fileId
-          "#{@settings.cdnBase}/#{@fileId}/"
-        else
-          null
+      originalUrl: if @fileId then urlBase else null
       cdnUrl: if @fileId
-          "#{@settings.cdnBase}/#{@fileId}/#{@cdnUrlModifiers or ''}"
+          "#{urlBase}#{@cdnUrlModifiers or ''}"
         else
           null
       cdnUrlModifiers: @cdnUrlModifiers


SidebarMachineListItem: allow admins to open machine settings

--- client/app/lib/components/sidebarmachineslistitem/index.coffee
+++ client/app/lib/components/sidebarmachineslistitem/index.coffee
@@ -93,6 +93,14 @@
     else @props.machine.getIn key

+  isOwner: ->
+
+    users   = (@machine 'users').toJS()
+    isOwner = yes for user in users when user.owner
+
+    return !!isOwner
+
+
   handleMachineClick: (event) ->

     kd.utils.defer =>
@@ -179,7 +187,7 @@

     kd.utils.stopDOMEvent event

-    if @machine('type') is 'own'
+    if @isOwner()
       kd.singletons.router.handleRoute "/Machines/#{@machine 'uid'}"
     else
       sidebarListItem = ReactDOM.findDOMNode @refs.sidebarMachinesListItem
@@ -264,11 +272,9 @@

     { computeController } = kd.singletons

-    status  = @machine ['status', 'state']
-    users   = (@machine 'users').toJS()
-    isOwner = yes for user in users when user.owner
-
-    return  unless isOwner
+    return  unless @isOwner()
+
+    status = @machine ['status', 'state']
     return  unless status is Machine.State.Running

     workspaces = []


SidebarMachineListItem: set coordinates for managed vms by default

--- client/app/lib/components/sidebarmachineslistitem/index.coffee
+++ client/app/lib/components/sidebarmachineslistitem/index.coffee
@@ -74,7 +74,7 @@
             setActiveInvitationMachineId { machine: @props.machine }
             @setCoordinates()

-      if @state.activeInvitationMachineId
+      if @state.activeInvitationMachineId or @machine('provider') is 'managed'
         @setCoordinates()


chore(grunt): Update dev server hostname

--- misc/grunt/contrib-connect.coffee
+++ misc/grunt/contrib-connect.coffee
@@ -11,6 +11,7 @@
     example:
       options:
         port:       9001
+        hostname:   "0.0.0.0"
         base:       "example"
         middleware: (connect, options) ->
           fileHandler = (req, res, next) ->


fixed link to privacy-policy

fixed link to privacy-policy

--- community/browser/app/scripts/controllers/Notifications.coffee
+++ community/browser/app/scripts/controllers/Notifications.coffee
@@ -38,7 +38,7 @@
       $scope.defaultNotifications = [
           {
             setting:"shouldReportUdc"
-            message:"Hello and thanks for downloading Neo4j! We'd like your help building a feedback graph to improve Neo4j features. Are you willing to share non‑sensitive usage data?"
+            message:"Hello and thanks for downloading Neo4j! We'd like your help building a feedback graph to improve Neo4j features. Are you willing to share non‑sensitive usage data?"
             style:"warning"
             options:[
               {


Fixed up the confirm rewrite to class

Fixed up the confirm rewrite to class

--- app/coffee/plugins/confirm.coffee
+++ app/coffee/plugins/confirm.coffee
@@ -20,7 +20,7 @@
     confirm: (message) ->
         defered = @q.defer()

-        _.defer ->
+        _.defer =>
             res = @window.confirm(message)
             if res
                 defered.resolve()
@@ -32,4 +32,4 @@

 module = angular.module('gmConfirm', [])
-module.service('$confirm', SimpleConfirmService])
+module.service('$confirm', SimpleConfirmService)


Markers should inherit from Glyph.Collection

--- bokehjs/src/coffee/renderer/glyph/marker/marker.coffee
+++ bokehjs/src/coffee/renderer/glyph/marker/marker.coffee
@@ -87,7 +87,7 @@

   class Marker extends Glyph.Model

-  class Markers extends Glyph.Model
+  class Markers extends Glyph.Collection

   return {
     Model: Marker


poplist scrolls to make sure selection is visible when it opens, use indexOf to figure out index of highlight

--- app/components/eui-poplist.coffee
+++ app/components/eui-poplist.coffee
@@ -55,6 +55,9 @@
     # Focus on search after poplist is positioned or the page may scroll
     Ember.run.next this, -> @focusOnSearch()

+    # Ensure the selected option is visible
+    Ember.run.next this, -> @scrollToSelection @get('selection')
+
   # Focus on search input when poplist shows so we can catch key input
   focusOnSearch: ->
     @$().find('input:first').focus()
@@ -99,6 +102,20 @@
       @set('listHeight', (optionCount * rowHeight))
     else
       @set('listHeight', (10 * rowHeight))
+
+  # Scroll the list to make sure the given option is visible.
+  scrollToSelection: (option) ->
+    $listView  = @.$('.ember-list-view')
+    listView   = Ember.View.views[$listView.attr('id')]
+    startIndex = listView._startingIndex()
+    numRows    = listView._childViewCount() - 1
+    endIndex   = startIndex + numRows
+    index      = @get('options').indexOf(option)
+
+    if index < startIndex
+      $listView.scrollTop index * @get('listRowHeight')
+    else if index >= endIndex
+      $listView.scrollTop (index - (numRows / 2)) * @get('listRowHeight')

   # Keyboard controls
@@ -232,11 +249,7 @@
       mouseEnter: ->
         options = @get('filteredOptions')
         hoveredOption = @get('context')
-
-        for option, index in options
-          if option == hoveredOption
-            @set 'highlightedIndex', index
-            break
+        @set 'highlightedIndex', options.indexOf(hoveredOption)

 poplist.reopenClass


FIxed typo in specs

FIxed typo in specs

--- spec/body-parser-spec.coffee
+++ spec/body-parser-spec.coffee
@@ -48,7 +48,7 @@
         "index": 1,
         "content": []
       },
-      "escaped $2 \\",
+      " escaped $2 \\",
       {
         "index": 3,
         "content": []


Ensure dialog does not show on first visit after login.

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -96,6 +96,7 @@
       @tutorial.start()
     else
       @tutorial.end()
+      @tutorial.dialog.close()

   onNoLocalSubjects: =>
     getEmptySubject().select()


add basic artifact recording (still needs work)

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -45,17 +45,18 @@
   template: require '../views/classifier'

   events:
-    'click button[name="play-frames"]'    : 'onClickPlay'
-    'click button[name="invert"]'         : 'onClickInvert'
-    'click button[name="finish-marking"]' : 'onClickFinishMarking'
-    'click button[name="four-up"]'        : 'onClickFourUp'
-    'click button[name="flicker"]'        : 'onClickFlicker'
-    'click button[name="next-frame"]'     : 'onClickNextFrame'
-    'click button[name="asteroid-done"]'  : 'onClickAsteroidDone'
-    'click button[name="cancel"]'         : 'onClickCancel'
-    'change input[name="frame-slider"]'   : 'onChangeFrameSlider'
-    'keydown'                             : 'onKeyDown'
-    'change .asteroid-not-visible'        : 'onClickAsteroidNotVisible'
+    'click button[name="play-frames"]'      : 'onClickPlay'
+    'click button[name="invert"]'           : 'onClickInvert'
+    'click button[name="finish-marking"]'   : 'onClickFinishMarking'
+    'click button[name="four-up"]'          : 'onClickFourUp'
+    'click button[name="flicker"]'          : 'onClickFlicker'
+    'click button[name="next-frame"]'       : 'onClickNextFrame'
+    'click button[name="asteroid-done"]'    : 'onClickAsteroidDone'
+    'click button[name="cancel"]'           : 'onClickCancel'
+    'change input[name="frame-slider"]'     : 'onChangeFrameSlider'
+    'change input[name="selected-artifact"]': 'onSelectArtifact'
+    'change .asteroid-not-visible'          : 'onClickAsteroidNotVisible'
+    'keydown'                               : 'onKeyDown'

     # state controller events
     'change input[name="classifier-type"]': (e) ->
@@ -67,9 +68,6 @@
         @finishButton.prop 'disabled', false
       else
         console.log("Error: unknown classifier-type")
-
-    'change input[name="selected-artifact"]': ->
-      @artifactSubtype = @selectedArtifactRadios.filter(':checked').val()

     'click button[name="asteroid-delete"]': ->
       currentFrame = +document.getElementById('frame-slider').value
@@ -91,9 +89,8 @@
     'button[name="asteroid-delete"]' : 'deleteButton'
     'button[name="next-frame"]'      : 'nextFrame'
     'button[name="cancel"]'          : 'cancel'
-    'input[name="selected-artifact"]': 'selectedArtifactRadios'
+    'input[name="selected-artifact"]': 'artifactSelector'
     'input[name="classifier-type"]'  : 'classifierTypeRadios'
-    # 'input[name="frame-slider"]'     : 'frameSlider'
     '.asteroid-not-visible'          : 'asteroidVisibilityCheckboxes'
     '.asteroid-checkbox'             : 'asteroidCompleteCheckboxes'
     '.current-frame'                 : 'frameSlider'
@@ -117,7 +114,7 @@
       enter: ->
         if @el.attr('flicker') is 'true' then @activateFrame 0 else @showAllTrackingIcons()
         @enableMarkingSurfaces()
-        @currAsteroid = new Sighting({type:"asteroid"})
+        @currSighting = new Sighting({type:"asteroid"})
         @el.find('.asteroid-classifier').show()
         @finishButton.hide()
         @doneButton.show()
@@ -132,10 +129,16 @@
     artifactTool:
       enter: ->
         @enableMarkingSurfaces()
+        @currSighting = new Sighting({type:"artifact"})
         @el.find('.artifact-classifier').show()
+        @finishButton.hide()
+        @doneButton.show()
+        @doneButton.prop 'disabled', true
       exit: ->
         @disableMarkingSurfaces()
         @el.find('.artifact-classifier').hide()
+        @doneButton.hide()
+        @finishButton.show()

   constructor: ->
     super
@@ -147,7 +150,7 @@

     window.classifier = @
     @setOfSightings = []
-    @currAsteroid = null
+    @currSighting = null

     @flickerButton.attr 'disabled', true
     @finishButton.prop 'disabled', true
@@ -158,6 +161,9 @@
     User.on 'change', @onUserChange
     Subject.on 'fetch', @onSubjectFetch
     Subject.on 'select', @onSubjectSelect
+
+  onSelectArtifact: ->
+    @currSighting?.subType = @artifactSelector.filter(':checked').val()

   createMarkingSurfaces: ->
     @numFrames = 4
@@ -190,7 +196,7 @@
       @el.find('a, button, input, textarea, select').filter('section *:visible').first().focus()

   onCreateMark: (mark) =>
-    @currAsteroid.pushSighting mark
+    @currSighting.pushSighting mark

     setTimeout => # otherwise mark properties undefined
       @removeGhostMarks() # remove unworthy ghosts
@@ -202,7 +208,7 @@
       if i isnt +mark.frame
         svgElement = surface.addShape 'circle', class: "ghost-mark", opacity: 1, cx: mark.x, cy: mark.y, r: 16, fill: "none", stroke: "#25b4c5", strokewidth: 1
         svgElement.el.setAttribute 'from-frame', mark.frame
-        svgElement.el.setAttribute 'from-asteroid', @currAsteroid.id
+        svgElement.el.setAttribute 'from-asteroid', @currSighting.id

   removeGhostMarks: ->
     for ghostMark in [ @el.find(".ghost-mark")... ]
@@ -211,26 +217,27 @@
   onDestroyMark: (mark) =>
     @destroyMarksInFrame mark.frame
     @updateIconsForDestroyMark mark.frame
-    @currAsteroid.clearSightingsInFrame mark.frame
+    @currSighting.clearSightingsInFrame mark.frame
     @removeGhostMarks()
-    if @state is 'asteroidTool' and @currAsteroid.allSightings.length < @numFrames
+    if @state is 'asteroidTool' and @currSighting.allSightings.length < @numFrames
       @doneButton.prop 'disabled', true

   onCreateTool: (tool) =>
     surfaceIndex = +@markingSurfaceList.indexOf tool.surface

     if @asteroidMarkedInFrame[surfaceIndex]
-      @currAsteroid.clearSightingsInFrame surfaceIndex
+      @currSighting.clearSightingsInFrame surfaceIndex
       @destroyMarksInFrame(surfaceIndex)
     else
       @el.find(".asteroid-frame-complete-#{surfaceIndex}").prop 'checked', true
       @asteroidMarkedInFrame[surfaceIndex] = true
     @updateIconsForCreateMark(surfaceIndex)

-    if @state is 'asteroidTool' and @currAsteroid.allSightings.length is @numFrames
-      @doneButton.prop 'disabled', false
-
-    tool.controls.controller.setMark(surfaceIndex, @currAsteroid.id)
+    if @state is 'asteroidTool' and @currSighting.allSightings.length is @numFrames \
+      or @state is 'artifactTool' and @currSighting.allSightings.length > 0
+        @doneButton.prop 'disabled', false
+
+    tool.controls.controller.setMark(surfaceIndex, @currSighting.id)

   onChangeFrameSlider: =>
     frame = document.getElementById('frame-slider').value
@@ -340,14 +347,14 @@
   destroyMarksInFrame: (frame_idx) ->
     for surface in @markingSurfaceList
       for theMark in surface.marks
-        theMark?.destroy() if theMark?.frame is frame_idx and theMark?.asteroid_id is @currAsteroid.id
+        theMark?.destroy() if theMark?.frame is frame_idx and theMark?.asteroid_id is @currSighting.id

   onClickAsteroidNotVisible: (e) ->
     frameNum = +e.target.id.slice(-1)
     visibilityChecked = @asteroidVisibilityCheckboxes[frameNum].checked

     if @asteroidMarkedInFrame[frameNum]
-      @currAsteroid.clearSightingsInFrame frameNum
+      @currSighting.clearSightingsInFrame frameNum
       @destroyMarksInFrame frameNum

     @updateIconsForNotVisible(frameNum)
@@ -358,9 +365,9 @@
       y: null
       visible: false
       inverted: @invert
-    @currAsteroid.pushSighting newAnnotation
-
-    if @state is 'asteroidTool' and @currAsteroid.allSightings.length is @numFrames
+    @currSighting.pushSighting newAnnotation
+
+    if @state is 'asteroidTool' and @currSighting.allSightings.length is @numFrames
       @doneButton.prop 'disabled', false

   updateIconsForCreateMark: (frameNum) =>
@@ -408,12 +415,12 @@

   onClickAsteroidDone: ->
     @removeGhostMarks()
-    @currAsteroid.displaySummary()
-    if @currAsteroid.allSightings.length is 0
-      @currAsteroid = null
+    @currSighting.displaySummary()
+    if @currSighting.allSightings.length is 0
+      @currSighting = null
     else
       @finishButton.prop 'disabled', false
-      @setOfSightings.push @currAsteroid
+      @setOfSightings.push @currSighting

     @resetAsteroidCheckboxes()
     @setState 'whatKind'
@@ -433,7 +440,7 @@
     @activateFrame(nextFrame) unless nextFrame is @numFrames

   onClickCancel: ->
-    @resetMarkingSurfaces() if @state is 'asteroidTool'
+    @resetMarkingSurfaces() if @state is 'asteroidTool' or 'artifactTool'
     @resetAsteroidCheckboxes()
     @setState 'whatKind' # return to initial state


Expand the task from anywhere, not just the name

--- app/controllers/tasks.item.coffee
+++ app/controllers/tasks.item.coffee
@@ -16,7 +16,7 @@
     'click .checkbox': 'toggleStatus'

     # Editing the actual task
-    'click .name': 'startEdit'
+    'click': 'startEdit'
     'blur .name': 'endEdit'
     'keypress .name': 'endEditOnEnter'

@@ -64,11 +64,13 @@
     @task.completed = !@task.completed
     @task.save()

-  startEdit: ->
-    @el.addClass("expanded")
-    # @el.draggable({ disabled: true })
-    @name.attr("contenteditable", "true")
-    @name.focus()
+  startEdit: (e) ->
+    # Don't expand the task if they clicked the checkbox
+    if e.target.className isnt "checkbox"
+      @el.addClass("expanded")
+      # @el.draggable({ disabled: true })
+      @name.attr("contenteditable", "true")
+      @name.focus()

   endEdit: ->
     # @name.removeAttr("contenteditable")


Add spec

Add spec

--- spec/editor-view-spec.coffee
+++ spec/editor-view-spec.coffee
@@ -2894,6 +2894,21 @@
       for rowNumber in [1..5]
         expect(editorView.lineElementForScreenRow(rowNumber).text()).toBe buffer.lineForRow(rowNumber)

+    it "correctly calculates the position left for non-monospaced invisibles", ->
+      window.debugContent = true
+      editorView.setShowInvisibles(true)
+      editorView.setInvisibles tab: '♘'
+      editor.setText('\tx')
+
+      editorView.setFontFamily('serif')
+      editorView.setFontSize(10)
+      editorView.attachToDom()
+      editorView.setWidthInChars(5)
+
+      expect(editorView.pixelPositionForScreenPosition([0, 0]).left).toEqual 0
+      expect(editorView.pixelPositionForScreenPosition([0, 1]).left).toEqual 10
+      expect(editorView.pixelPositionForScreenPosition([0, 2]).left).toEqual 13
+
   describe "when the window is resized", ->
     it "updates the active edit session with the current soft wrap column", ->
       editorView.attachToDom()


diable perf test spec

diable perf test spec

--- spec/motion-find-spec.coffee
+++ spec/motion-find-spec.coffee
@@ -13,7 +13,7 @@
       {editor, editorElement} = vimState
       {set, ensure, keystroke} = _vim

-  describe 'the f performance', ->
+  xdescribe 'the f performance', ->
     timesToExecute = 500
     # timesToExecute = 1
     measureWithTimeEnd = (fn) ->


Unload the coffee grammar after using

Unload the coffee grammar after using

--- spec/text-editor-spec.coffee
+++ spec/text-editor-spec.coffee
@@ -3262,6 +3262,9 @@

       runs ->
         expect(editor.softTabs).toBe false
+
+        atom.packages.deactivatePackage('language-coffee-script')
+        atom.packages.unloadPackage('language-coffee-script')

   describe ".destroy()", ->
     it "destroys all markers associated with the edit session", ->


Remove stray usages of atom.config in text-editor-spec

--- spec/text-editor-spec.coffee
+++ spec/text-editor-spec.coffee
@@ -648,7 +648,7 @@

         describe "when invisible characters are enabled with soft tabs", ->
           it "moves to the first character of the current line without being confused by the invisible characters", ->
-            atom.config.set('editor.showInvisibles', true)
+            editor.setShowInvisibles(true)
             editor.setCursorScreenPosition [1, 7]
             editor.moveToFirstCharacterOfLine()
             expect(editor.getCursorBufferPosition()).toEqual [1, 2]
@@ -657,7 +657,7 @@

         describe "when invisible characters are enabled with hard tabs", ->
           it "moves to the first character of the current line without being confused by the invisible characters", ->
-            atom.config.set('editor.showInvisibles', true)
+            editor.setShowInvisibles(true)
             buffer.setTextInRange([[1, 0], [1, Infinity]], '\t\t\ta', normalizeLineEndings: false)

             editor.setCursorScreenPosition [1, 7]
@@ -5388,7 +5388,7 @@

     describe "when the editor has a height and lineHeightInPixels", ->
       beforeEach ->
-        atom.config.set('editor.scrollPastEnd', true)
+        editor.setScrollPastEnd(true)
         editor.setHeight(100, true)
         editor.setLineHeightInPixels(10)

@@ -5427,8 +5427,7 @@

       describe "when the 'editor.scrollPastEnd' option is set to false", ->
         it "ensures that the bottom row is less than the buffer's line count", ->
-          atom.config.set('editor.scrollPastEnd', false)
-
+          editor.setScrollPastEnd(false)
           editor.setFirstVisibleScreenRow(95)
           expect(editor.getFirstVisibleScreenRow()).toEqual 89
           expect(editor.getVisibleRowRange()).toEqual [89, 99]


feat(routes/alarms.coffee): 添加告警队列排序

feat(routes/alarms.coffee): 添加告警队列排序

--- routes/alarms.coffee
+++ routes/alarms.coffee
@@ -37,6 +37,7 @@
       criteria['datetime'] = req.query.datetime

     options =
+      sort: _id: '-1'
       page: parseInt(req.query.page)
       limit: if req.query.limit? then parseInt(req.query.limit) else 30


Update webgrabplus-siteini.cson

Update webgrabplus-siteini.cson

--- grammars/webgrabplus-siteini.cson
+++ grammars/webgrabplus-siteini.cson
@@ -1,20 +1,10 @@
-{
-# url index elements
-'match': '(\\|)(urldate|channel|subpage|\'title\'|\'credits\'|\'productiondate\')(\\|)'
-'captures':
-  '1':
-    'name': 'entity.name.tag.siteini'
-  '2':
-    'name': 'entity.name.type.siteini'
-  '3':
-    'name': 'entity.name.tag.siteini'
-}'scopeName': 'source.webgrabplus-siteini'
+'scopeName': 'source.webgrabplus-siteini'
 'name': 'WG++ siteini'
 'fileTypes': ['ini']
 'firstLineMatch': '^\\s*\\*[\\s*\\*]*WebGrab\\+Plus'
 'patterns': [
   {
-    'match': '^(\\s*\\*[\\*\\s]*(@(Revision|MinSWversion|Site|Remarks)).*?)$'
+    'match': '^(\\s*\\*[\\*\\s]*(@(Revision|MinSWversion|Remarks)\|\\$\\$encrypted).*?)$'
     'captures':
       '1':
         'name': 'comment.block'
@@ -27,48 +17,61 @@
   }
   {
     #elements
-    'match': '^\\s*(url_index|url_preload|index_urlshow|index_urlsubdetail|site|urldate|subpage|urlshow|urlsubdetail|url_primarysearch|url_mdb(_p[1-8])?|showsplit|date|start|stop|duration|(mdb_)?title(original)?|(index_)?temp_[1-9]|site_channel|site_id|sort_by|index_showsplit|index_date|index_start|index_stop|index_duration|index_title(original)?|(index|mdb)_variable_element|index_site_channel|index_site_id|index_sort_by|detail_urlshow|detail_urlsubdetail|detail_showsplit|detail_date|detail_start|detail_stop|detail_duration|detail_site_channel|detail_site_id|detail_sort_by|(index_|detail_|subdetail_|mdb_)?(title(original)?|subtitles?|description|director|actor|presenter|writer|producer|composer|commentator|rating(icon)?|category|productiondate|country|starrating(votes)?|episode(_id|numlist|titlelist)?|premiere|previousshown|videoaspect|videoquality|showicon|urlchannellogo)|previous_start|previous_stop|previous_duration|channel|urldate|now|showdate|config_(site_id|site_channel|xmltv_id|display_name|timespan_days|credentials_user|credentials_password)|global_temp_[1-9]|(previous_)?(index_|detail_|subdetail_|mdb_)?temp_[1-9]|mdb_(show_id|plot|commentsummary|review)|x_((sub)?title|productiondate|actor|director))'
+    'match': '^\\s*(site|url_index|index_url(show|subdetail)|urldate|subpage(_mdb_p[1-9])?|url(show|subdetail)|url_primarysearch|url_mdb(_p[1-9])?|start|stop|duration|(mdb_)?title(original)?|(previous_)?(index_)?temp_[1-9]|sort_by|index_showsplit|index_date|(previous_)?index_start|(previous_)?index_stop|(previous_)?index_duration|index_title(original)?|(index|mdb)_variable_element|index_site_channel|index_site_id|detail_url(show|subdetail)|detail_(start|stop|duration)|(index_|detail_|subdetail_|mdb_)?(title(original)?|subtitles?|description|director|actor|presenter|writer|producer|composer|commentator|rating(icon)?|category|productiondate|country|starrating(votes)?|episode(_id|numlist|titlelist)?|premiere|previousshown|videoaspect|videoquality|showicon|urlchannellogo)|previous_start|previous_stop|previous_duration|channel|urldate|now|showdate|config_(site_(id|channel)|xmltv_id|display_name|timespan_days|credentials_user|credentials_password)|global_temp_[1-9]|(previous_)?(index_|detail_|subdetail_|mdb_)?temp_[1-9]|mdb_(show_id|plot|commentsummary|review)|x_((sub)?title|productiondate|actor|director))'
     'captures':
       '1':
         'name': 'storage.type.siteini'
   }
   {
-    #elements (in single quotes)
-    'match': '([\'\"](url_index|url_preload|index_urlshow|index_urlsubdetail|site|urldate|subpage|urlshow|urlsubdetail|url_primarysearch|url_mdb(_p[1-8])?|showsplit|date|start|stop|duration|(mdb_)?title(original)?|(index_)?temp_[1-9]|site_channel|site_id|sort_by|index_showsplit|index_date|index_start|index_stop|index_duration|index_title(original)?|(index|mdb)_variable_element|index_site_channel|index_site_id|index_sort_by|detail_urlshow|detail_urlsubdetail|detail_showsplit|detail_date|detail_start|detail_stop|detail_duration|detail_site_channel|detail_site_id|detail_sort_by|(index_|detail_|subdetail_|mdb_)?(title(original)?|subtitles?|description|director|actor|presenter|writer|producer|composer|commentator|rating(icon)?|category|productiondate|country|starrating(votes)?|episode(_id|numlist|titlelist)?|premiere|previousshown|videoaspect|videoquality|showicon|urlchannellogo)|previous_start|previous_stop|previous_duration|channel|urldate|now|showdate|config_(site_id|site_channel|xmltv_id|display_name|timespan_days|credentials_user|credentials_password)|global_temp_[1-9]|(previous_)?(index_|detail_|subdetail_|mdb_)?temp_[1-9]|mdb_(show_id|plot|commentsummary|review)|x_((sub)?title|productiondate|actor|director))[\'\"])'
+    #elements (in quotes)
+    'match': '([\'\"](url_index|index_url(show|subdetail)|urldate(_formatted)?|subpage(_formatted)?|url(show|subdetail)|url_primarysearch|url_mdb(_p[1-9])?|start|stop|duration|(mdb_)?title(original)?|(previous_)?(index_)?temp_[1-9]|sort_by|index_showsplit|index_date|(previous_)?index_start|(previous_)?index_stop|(previous_)?index_duration|index_title(original)?|(index|mdb)_variable_element|index_site_channel|index_site_id|detail_url(show|subdetail)|detail_(start|stop|duration)|(index_|detail_|subdetail_|mdb_)?(title(original)?|subtitles?|description|director|actor|presenter|writer|producer|composer|commentator|rating(icon)?|category|productiondate|country|starrating(votes)?|episode(_id|numlist|titlelist)?|premiere|previousshown|videoaspect|videoquality|showicon|urlchannellogo)|previous_start|previous_stop|previous_duration|channel|urldate|now|showdate|config_(site_(id|channel)|xmltv_id|display_name|timespan_days|credentials_user|credentials_password)|global_temp_[1-9]|(previous_)?(index_|detail_|subdetail_|mdb_)?temp_[1-9]|mdb_(show_id|plot|commentsummary|review)|x_((sub)?title|productiondate|actor|director))[\'\"])'
     'captures':
       '2':
         'name': 'storage.type.siteini'
   }
   {
     #modifier after modify
-    'match': '\\.(modify)(\\s*\{\\s*(set|calculate|clear|cleanup|substring|remove|addstart|addend|single|replace|select))'
-    'captures':
-      '1':
-        'name': 'entity.name.type.siteini'
-      '3':
+    'match': '\\.(modify)\\s*\\{\\s*(set|calculate|clear|cleanup|substring|remove|addstart|addend|single|replace|select)'
+    'captures':
+      '1':
+        'name': 'entity.name.type.siteini'
+      '2':
         'name': 'entity.name.function.siteini'
   }
   {
     #modifier after scrub
-    'match': '\\.(scrub)\\s*\{\\s*(regex|multi|single|url)?'
-    'captures':
-      '1':
-        'name': 'entity.name.type.siteini'
-      '2':
-        'name': 'entity.name.function.siteini'
-  }
-  {
-    #modifier of .headers
-    'match': '[\\{\\|](method|customheader|accept|referer|contenttype|postdata|host|credentials|allowautoredirect|expect)\\s*=(POST(_BACK)?|GET|REST|SOAP)?'
-    'captures':
-      '1':
-        'name': 'entity.name.tag.siteini'
-      '2':
-        'name': 'entity.name.function.siteini'
+    'match': '\\.(scrub)\\s*\\{\\s*(\\||regex|multi|single)'
+    'captures':
+      '1':
+        'name': 'entity.name.type.siteini'
+      '2':
+        'name': 'entity.name.function.siteini'
+  }
+  {
+    #modifier of .headers method=
+    'match': '(method)\\s*=(MULTIPART|GET|REST|SOAP|POST_BACK|POST)(\\()?(GET,GET|GET,POST|POST,GET|POST,POST)?(\\))?'
+    'captures':
+      '1':
+        'name': 'entity.name.tag.siteini'
+      '2':
+        'name': 'entity.name.function.siteini'
+      '3':
+        'name': 'entity.name.tag.siteini'
+      '4':
+        'name': 'entity.name.type.siteini'
+      '5':
+        'name': 'entity.name.tag.siteini'
+  }
+  {
+    #modifier of other .headers
+    'match': '[\\{\\|](customheader|accept|referer|contenttype|postdata|host|credentials|allowautoredirect|expect|list)'
+    'captures':
+      '1':
+        'name': 'entity.name.tag.siteini'
   }
   {
     #modifier of .site (with =)
-    'match': '[\\{\\|](compression|subtitlestype|loadcookie|episodesystem|ratingsystem|url|timezone|maxdays|cultureinfo|charset|(title)?matchfactor|firstday|firstshow|retry|skip|langoverrule|mdbinitype|searchsite)\\s*='
+    'match': '[\\{\\|\\s](compression|subtitlestype|loadcookie|episodesystem|ratingsystem|url|timezone|maxdays|cultureinfo|charset|(title)?matchfactor|firstday|firstshow|retry|skip|langoverrule|mdbinitype|searchsite|channel-delay|show-delay|index-delay|time-out)\\s*='
     'captures':
       '1':
         'name': 'entity.name.tag.siteini'
@@ -89,34 +92,43 @@
   }
   {
     #specials url
-    'match': '\\s*\{\\s*(url)\\s*[\\(\\|]'
-    'captures':
-      '1':
-        'name': 'entity.name.function.siteini'
-  }
-  {
-    #specials url with preload
-    'match': '(preload)\\s*=\\s*".+?"\\s*\\)\\s*\\|'
-    'captures':
-      '1':
-        'name': 'entity.name.tag.siteini'
-  }
+    'match': '\\s*\\{\\s*(url)'
+    'captures':
+      '1':
+        'name': 'entity.name.function.siteini'
+  }
+  {
+    #specials url with urlencode(mdb)
+    'match': '\\((urlencode)=\\s*\\d(,\\d)?(,\\d)?(,\\d)?\\s*\\)\\s*\\|'
+    'captures':
+      '1':
+        'name': 'entity.name.tag.siteini'
+    }
   {
     # specials within ( =param)
-    'match': '[\\s+\\(](separator|span|step|count|pattern|target|tags|timespan|alloc|max|link|timezone)\\s*='
-    'captures':
-      '1':
+    'match': '[\\s+\\(](separator|span|step|count|pattern|target|tags|timespan|alloc|max|link|timezone)'
+    'captures':
+      '1':
+        'name': 'entity.name.tag.siteini'
+  }
+  {
+    # specials removeduplicates
+    'match': '[\\s+\\(](removeduplicates)=?(name|title|equal)?,?(\\d+)?'
+    'captures':
+      '1':
+        'name': 'entity.name.tag.siteini'
+      '2':
+        'name': 'entity.name.function.siteini'
+      '3':
         'name': 'entity.name.tag.siteini'
   }
   {
     # specials within ()
-    'match': '(removeduplicates|keepfirst|keeplast|force)=*(name(,\\d+)?|title(,\\d+)?|equal(,\\d+)?)?'
-    'captures':
-      '1':
-        'name': 'entity.name.tag.siteini'
-      '2':
-        'name': 'entity.name.function.siteini'
-  }
+    'match': '(force|(not)?null|keep(first|last)|each)'
+    'captures':
+      '1':
+        'name': 'entity.name.tag.siteini'
+    }
   {
     # xmltv attributes
     'match': '\\((lang|system|role|src|width|height|start|channel)\\s*='
@@ -126,7 +138,7 @@
   }
   {
     # style=
-    'match': '[\\s+\\(](style)=(sentence|name|upper|lower|regex|urlencode|urldecode|unidecode|uniencode|htmlencodespecialchar|htmldecodespecialchar|jsondecode|base64encode|base64decode(,decimal)?|sha256encode|md5encode|roman2arabicnumeral)'
+    'match': '[\\s*\\(](style)=\\s*(sentence|name|upper|lower|regex|url(en|de)code|uni(de|en)code|jsondecode|html(en|de)codespecialchar|base64encode|base64decode(,decimal)?|(sha256|md5)encode|roman2arabicnumeral)'
     'captures':
       '1':
         'name': 'entity.name.tag.siteini'
@@ -135,7 +147,7 @@
   }
   {
     # type=
-    'match': '[\\s+\\(](type)=(string|char|word|sentence|paragraph|element|regex|run)'
+    'match': '[\\s*\\(](type)=\\s*(string|char|word|sentence|paragraph|element|regex|run)'
     'captures':
       '1':
         'name': 'entity.name.tag.siteini'
@@ -153,112 +165,162 @@
   }
   {
     # specials (urldate|subpage).format
-    'match': '\\.(format)\\s*\{\\s*(daycounter|date(string|number)|weekday(number|name)|letter|list|number)'
-    'captures':
-      '1':
-        'name': 'entity.name.type.siteini'
-      '2':
-        'name': 'entity.name.function.siteini'
-  }
-  {
-    # include/exlcude=
-    'match': "(include|exclude)\\s*=\\s*(\\d+|first|last|\".+?\")(\\d+)?"
-    'captures':
-      '1':
-        'name': 'entity.name.tag.siteini'
-      '2':
-        'name': 'entity.name.function.siteini'
-      '3':
-        'name': 'entity.name.tag.siteini'
-  }
-  {
-    # (include/exlcude)block=
-    'match': "(includeblock|excludeblock)\\s*=\\s*((\\d+|'.+?')(/\\d+|/'.+?')?|\".+?\")"
-    'captures':
-      '1':
-        'name': 'entity.name.tag.siteini'
-      '2':
-        'name': 'entity.name.function.siteini'
-      '3':
-        'name': 'entity.name.tag.siteini'
-  }
-  {
-    # pattern(episodesystem)=
-    'match': "('(S|St|E|Et|P|Pt)[01]')"
-    'captures':
-      '1':
-        'name': 'entity.name.function.siteini'
-  }
-  {
-    # scope= as argument
-    'match': '(scope)\\s*=\\s*(urlindex|datelogo|splitindex|indexshowdetails|showdetails|showsubdetails|channellist|primarysearch|match|getelements)'
-    'captures':
-      '1':
-        'name': 'entity.name.tag.siteini'
-      '2':
-        'name': 'entity.name.function.siteini'
-  }
-  {
-    # scope.range
-    'match': '(scope\\.range)\\s*\{\\s*\\((urlindex|datelogo|splitindex|indexshowdetails|showdetails|showsubdetails|channellist|primarysearch|match|getelements)\\s*\\)\\s*\\|\\s*(end|\\d+)\\s*\}'
-    'captures':
-      '1':
-        'name': 'entity.name.tag.siteini'
-      '2':
-        'name': 'entity.name.function.siteini'
-      '3':
-        'name': 'entity.name.tag.siteini'
-  }
-  {
-    # end_scope or end_scope.range
-    'match': '(end_scope(\\.range)?)'
-    'captures':
-      '1':
-        'name': 'entity.name.tag.siteini'
-  }
-  {
-    # debug as argument
-    'match': '(debug)'
-    'captures':
-      '1':
-        'name': 'entity.name.tag.siteini'
-  }
-  {
-    # debug stand alone
-    'match': '\\.(modify)(\\s*\{\\s*\\((debug)\\))'
-    'captures':
-      '1':
-        'name': 'entity.name.type.siteini'
-      '3':
-        'name': 'entity.name.tag.siteini'
-  }
-  {
-    # loop
-    'match': '^\\s*(loop)\\s*\{\\s*\\(\\s*(each\\s*\".+?\"|\'.+?\')\\s*(in|<|>|=<|=>|<=|>=|not)?\\s*(\'.+?\'|\".*?\")\\s*(.*?)?\\)\\s*\\|(end|\\d+)\}'
-    'captures':
-      '1':
-        'name': 'entity.name.tag.siteini'
-      '2':
-        'name': 'entity.name.function.siteini'
-      '3':
-        'name': 'storage.type.siteini'
+    'match': '\\.(format)\\s*\\{\\s*(daycounter|date(string|number)|weekday(number|name)|letter|list|number)'
+    'captures':
+      '1':
+        'name': 'entity.name.type.siteini'
+      '2':
+        'name': 'entity.name.function.siteini'
+  }
+  {
+    # include/exlcude="aaaa""bbbb""cccc",ect
+    'match': '(include|exclude)\\s*=\\s*(\".+?\")(\".+?\")?(\".+?\")?(\".+?\")?(\".+?\")?(\".+?\")?(\".+?\")?'
+    'captures':
+      '1':
+        'name': 'entity.name.tag.siteini'
+      '2':
+        'name': 'entity.name.function.siteini'
+      '3':
+        'name': 'entity.name.type.siteini'
       '4':
-        'name': 'entity.name.function.siteini'
+        'name': 'entity.name.type.siteinitag.siteini'
       '5':
         'name': 'storage.type.siteini'
       '6':
-        'name': 'entity.name.tag.siteini'
-    }
-    {
-    # end_loop
-    'match': '^\\s*(end_loop)'
-    'captures':
-      '1':
-        'name': 'entity.name.tag.siteini'
-  }
+        'name': 'entity.name.function.siteini'
+      '7':
+        'name': 'entity.name.type.siteini'
+      '8':
+        'name': 'entity.name.tag.siteini'
+  }
+  {
+    # include/exlcude=x/firstx/lastx
+    'match': '(include|exclude)\\s*=\\s*(first|last)?(\\d+)?'
+    'captures':
+      '1':
+        'name': 'entity.name.tag.siteini'
+      '2':
+        'name': 'entity.name.function.siteini'
+      '3':
+        'name': 'entity.name.tag.siteini'
+  }
+  {
+    # (include/exlcude)block="aaaa""bbbb""cccc",ect
+    'match': '(includeblock|excludeblock)\\s*=\\s*(\".+?\")(\".+?\")?(\".+?\")?(\".+?\")?(\".+?\")?(\".+?\")?(\".+?\")?'
+    'captures':
+      '1':
+        'name': 'entity.name.tag.siteini'
+      '2':
+        'name': 'entity.name.function.siteini'
+      '3':
+        'name': 'entity.name.type.siteini'
+      '4':
+        'name': 'entity.name.type.siteinitag.siteini'
+      '5':
+        'name': 'storage.type.siteini'
+      '6':
+        'name': 'entity.name.function.siteini'
+      '7':
+        'name': 'entity.name.type.siteini'
+      '8':
+        'name': 'entity.name.tag.siteini'
+  }
+  {
+    # (include/exlcude)block=x/y or 'element/'element' or combination of both
+    'match': '(includeblock|excludeblock)\\s*=\\s*(\\d+|\'.+?\')\\s*(?:(?:(\\d+|/\'.+?\'))?/(\\d+|/\'.+?\'))?'
+    'captures':
+      '1':
+        'name': 'entity.name.tag.siteini'
+      '2':
+        'name': 'entity.name.function.siteini'
+      '3':
+        'name': 'entity.name.type.siteini'
+      '4':
+        'name': 'entity.name.type.siteinitag.siteini'
+  }
+  {
+    # pattern(episodesystem)=
+    'match': '\'(S|St|E|Et|P|Pt)([01])\''
+    'captures':
+      '1':
+        'name': 'entity.name.function.siteini'
+      '2':
+        'name': 'entity.name.tag.siteini'
+  }
+  {
+    # scope= as argument
+    'match': '(scope)\\s*=\\s*(urlindex|datelogo|splitindex|indexshowdetails|showdetails|showsubdetails|channellist|primarysearch|match|getelements|postback)'
+    'captures':
+      '1':
+        'name': 'entity.name.tag.siteini'
+      '2':
+        'name': 'entity.name.function.siteini'
+  }
+  {
+    # scope.range
+    'match': '^\\s*(scope\\.range)\\s*\\{\\s*\\((urlindex|datelogo|splitindex|indexshowdetails|showdetails|showsubdetails|channellist|primarysearch|match|getelements|postback)\\s*\\)\\s*\\|\\s*(end|\\d+)\\s*\\}'
+    'captures':
+      '1':
+        'name': 'entity.name.tag.siteini'
+      '2':
+        'name': 'entity.name.function.siteini'
+      '3':
+        'name': 'entity.name.tag.siteini'
+  }
+  {
+    # end_scope or end_scope.range
+    'match': '^\\s*(end_scope(\\.range)?)'
+    'captures':
+      '1':
+        'name': 'entity.name.tag.siteini'
+  }
+  {
+    # debug as argument
+    'match': '(debug)'
+    'captures':
+      '1':
+        'name': 'entity.name.tag.siteini'
+  }
+  {
+    # debug alone
+    'match': '.(modify)\\s*\\{\\s*(?:\\(?\\s*(debug)\\s*\\)?)\\s*\\}'
+    'captures':
+      '1':
+        'name': 'entity.name.type.siteini'
+      '2':
+        'name': 'entity.name.tag.siteini'
+  }
+  {
+    # if,loop ***space required after(not optional)
+    'match': '^\\s*(if|loop)\\s+\\{\\('
+    'captures':
+      '1':
+        'name': 'entity.name.tag.siteini'
+      }
+      {
+      # else ***space required after(not optional)
+      'match': '^\\s*(else)\\s+\\('
+      'captures':
+        '1':
+          'name': 'entity.name.tag.siteini'
+      }
+      {
+      # if/loop end or number of lines
+      'match': '\\)\\s*\\|(end|\\d+)\\s*\\}'
+      'captures':
+        '1':
+          'name': 'entity.name.tag.siteini'
+      }
+      {
+      # end_loop,end_if
+      'match': '^\\s*(end_(loop|if))'
+      'captures':
+        '1':
+          'name': 'entity.name.tag.siteini'
+      }
   {
   # sort
-  'match': '\\.(modify)\\s*\{(sort)\\((ascending|descending),(string|integer|datetime)\\)'
+  'match': '\\.(modify)\\s*\\{\\s*(sort)\\(\\s*(ascending|descending)\\s*,\\s*(string|integer|datetime)\\s*\\)'
   'captures':
     '1':
       'name': 'entity.name.type.siteini'
@@ -268,16 +330,56 @@
       'name': 'entity.name.function.siteini'
     '4':
       'name': 'entity.name.function.siteini'
-}
-{
-# url index elements
-'match': '(\\|)(urldate|channel|subpage|\'title\'|\'credits\'|\'productiondate\')(\\|)'
-'captures':
-  '1':
-    'name': 'entity.name.tag.siteini'
-  '2':
-    'name': 'entity.name.type.siteini'
-  '3':
-    'name': 'entity.name.tag.siteini'
-}
+    }
+    {
+      # sort@
+      'match': '\\((sort)@([0-9]{2}:[0-9]{2}),([0-9]{2}:[0-9]{2}),?([0-9]{2}:[0-9]{2})?,?([0-9]{2}:[0-9]{2})?,?([0-9]{2}:[0-9]{2})?,?([0-9]{2}:[0-9]{2})?\\)'
+      'captures':
+        '1':
+          'name': 'entity.name.tag.siteini'
+        '2':
+          'name': 'entity.name.function.siteini'
+        '3':
+          'name': 'entity.name.type.siteini'
+        '4':
+          'name': 'entity.name.type.siteinitag.siteini'
+        '5':
+          'name': 'storage.type.siteini'
+        '6':
+          'name': 'entity.name.function.siteini'
+        '7':
+          'name': 'entity.name.type.siteini'
+        '8':
+          'name': 'entity.name.tag.siteini'
+    }
+   {
+     # mdb url index elements
+     'match': '(\\|)(urldate|channel|subpage|\'title\'|\'credit\'|\'productiondate\')(\\|)'
+     'captures':
+       '1':
+         'name': 'entity.name.tag.siteini'
+       '2':
+         'name': 'entity.name.type.siteini'
+       '3':
+         'name': 'entity.name.tag.siteini'
+    }
+    {
+      # mdb index elements
+      'match': '(\\|)(primary|mdb_show_id|mdb_episode_id|p[1-9])(\\|)'
+      'captures':
+        '1':
+          'name': 'entity.name.tag.siteini'
+        '2':
+          'name': 'entity.name.type.siteini'
+        '3':
+          'name': 'entity.name.tag.siteini'
+     }
+     {
+       # bitwise calculations
+       'match': '\\s*(and|or|xor|shiftl|shiftr|not)\\}'
+       'captures':
+         '1':
+           'name': 'entity.name.type.siteini'
+     }
 ]
+


Replace kontrol postgres username in GRANT statement

Signed-off-by: Sonmez Kartal <d2d15c3d37396a5f2a09f8b42cf5f27d43a3fbde@koding.com>

--- config/generateRunFile.coffee
+++ config/generateRunFile.coffee
@@ -405,6 +405,7 @@
       mkdir -p kontrol
       sed -i -e "s/USER kontrolapplication/USER $KONFIG_KONTROL_POSTGRES_USERNAME/" kontrol/001-schema.sql
       sed -i -e "s/PASSWORD 'kontrolapplication'/PASSWORD '$KONFIG_KONTROL_POSTGRES_PASSWORD'/" kontrol/001-schema.sql
+      sed -i -e "s/GRANT kontrol TO kontrolapplication/GRANT kontrol TO $KONFIG_KONTROL_POSTGRES_USERNAME/" kontrol/001-schema.sql
       docker build -t koding/postgres .
       runMongoDocker
       docker run -d -p 5672:5672 -p 15672:15672                           --name=rabbitmq rabbitmq:3-management


runFile: use cd instead of pushd command

--- config/generateRunFile.coffee
+++ config/generateRunFile.coffee
@@ -396,7 +396,7 @@

         go list -f '{{if len .TestGoFiles}}"go test -v -cover -o={{.Dir}}/{{.Name}}.test -c {{.ImportPath}} "{{end}}' $1 | grep -v vendor | xargs -L 1 -I{} $action {}$COMPILE_FLAGS
         # pushd is required for test folders/paths
-        go list -f '{{if len .TestGoFiles}}"pushd {{.Dir}} && ./{{.Name}}.test -test.coverprofile={{.Dir}}/coverage.txt "{{end}}' $1 | xargs -L 1 -I{} $action {}$RUN_FLAGS
+        go list -f '{{if len .TestGoFiles}}"cd {{.Dir}} && ./{{.Name}}.test -test.coverprofile={{.Dir}}/coverage.txt "{{end}}' $1 | xargs -L 1 -I{} $action {}$RUN_FLAGS
         go list -f '{{if len .TestGoFiles}}"rm {{.Dir}}/{{.Name}}.test "{{end}}' $1 | xargs -L 1 $action

     }


activityticker: comment view added to view map

--- client/Social/Activity/views/activitytickeritem.coffee
+++ client/Social/Activity/views/activitytickeritem.coffee
@@ -201,13 +201,14 @@

 class ActivityTickerItem extends KDListItemView
   itemClassMap =
-    "JGroup_member_JAccount"           : ActivityTickerMemberItem
-    "JAccount_like_JAccount"           : ActivityTickerLikeItem
-    "JTag_follower_JAccount"           : ActivityTickerFollowItem
-    "JAccount_follower_JAccount"       : ActivityTickerFollowItem
-    "JNewApp_user_JAccount"            : ActivityTickerAppUserItem
-    "JAccount_reply_JAccount"          : ActivityTickerCommentItem
-    "JNewStatusUpdate_author_JAccount" : ActivityTickerStatusUpdateItem
+    "JGroup_member_JAccount"              : ActivityTickerMemberItem
+    "JAccount_like_JAccount"              : ActivityTickerLikeItem
+    "JTag_follower_JAccount"              : ActivityTickerFollowItem
+    "JAccount_follower_JAccount"          : ActivityTickerFollowItem
+    "JNewApp_user_JAccount"               : ActivityTickerAppUserItem
+    "JAccount_reply_JAccount"             : ActivityTickerCommentItem
+    "JNewStatusUpdate_author_JAccount"    : ActivityTickerStatusUpdateItem
+    "JNewStatusUpdate_commenter_JAccount" : ActivityTickerUserCommentItem

   constructor: (options = {}, data) ->
     options.type = "activity-ticker-item"


Updated field tutorial

Updated field tutorial

--- content/field-tutorial.coffee
+++ content/field-tutorial.coffee
@@ -2,7 +2,7 @@
   image: 'tutorial/step1.jpg'
   alt: 'This will be a tutorial step'
   content: '''
-    Use the photos to identify the type of orchid in the photo. You can use the cursor keys to quickly move back and forward through the thumbnails.
+    Use the photos to identify the type of orchid. You can use the cursor keys to quickly move back and forward through the thumbnails.
   '''
   },{
   image: 'tutorial/step2.jpg'
@@ -14,6 +14,6 @@
   image: 'tutorial/step2.jpg'
   alt: 'This will be a tutorial step'
   content: '''
-    If there are any insects in the photo, click on those to mark them too. Insects can tell us about the species that prefer to pollinate these plants.
+    If there are any insects on the orchid, click on those to mark them too. Insects can tell us which species may pollinate these plants.
   '''
   }]


groupcoverphoto: resize image before displaying.

--- client/Social/Activity/views/feedcoverphotoview.coffee
+++ client/Social/Activity/views/feedcoverphotoview.coffee
@@ -17,6 +17,13 @@
         if @group.slug isnt "koding"
           @decorateHeader()

+  getResizedImage:(imageToCrop)->
+    proxifyOptions =
+      crop         : yes
+      width        : 950
+      height       : 315
+    KD.utils.proxifyUrl imageToCrop, proxifyOptions
+
   decorateHeader:->

     unless @container
@@ -24,10 +31,11 @@
         cssClass : "container"
         size     : height : 315

+      resizedImg = @getResizedImage(@group.customize?.coverPhoto)
       @coverView = new KDCustomHTMLView
         tagName    : 'figure'
         attributes :
-          style    : "background-image: url(#{@group.customize?.coverPhoto});"
+          style    : "background-image: url(#{resizedImg});"

       @listController = new KDListViewController
         startWithLazyLoader : no
@@ -48,7 +56,8 @@

     if @group.customize?.coverPhoto
       @listController.getView().hide()
-      @coverView.setCss 'background-image', "url(#{@group.customize.coverPhoto})"
+      resizedImg = @getResizedImage(@group.customize?.coverPhoto)
+      @coverView.setCss 'background-image', "url(#{resizedImg})"
       @coverView.show()
     else
       @coverView.hide()


teams: minor copy fix

teams: minor copy fix

--- client/app/lib/activity/sidebar/activitysidebar.coffee
+++ client/app/lib/activity/sidebar/activitysidebar.coffee
@@ -546,8 +546,8 @@

STACKS

-            Your team page has not been

-            fully configured yet please

+            Your stacks has not been

+            fully configured yet, please

             contact your team admin.

           Message admin


PopupNotifications: disable back Team Settings for members

--- client/app/lib/notifications/popupnotifications.coffee
+++ client/app/lib/notifications/popupnotifications.coffee
@@ -55,10 +55,18 @@
       click    : (event) => if event.target.tagName is 'A' then @hide()
       partial  : """

Account

-
Team Settings

+
Team Settings

Support

Logout

         """
+
+    { groupsController } = kd.singletons
+    groupsController.ready ->
+      group = groupsController.getCurrentGroup()
+      group.canEditGroup (err, success) ->
+        unless success
+        then ul.$('li.admin').remove()
+        else ul.$('li.admin').removeClass('hidden')

   hide: ->


Add progress callback in transaction validation

--- app/src/wallet/transaction.coffee
+++ app/src/wallet/transaction.coffee
@@ -150,8 +150,7 @@
       currentStep = progress.currentPublicKey + progress.currentSignTransaction + progress.currentTrustedInput + progress.currentTrustedInputProgress + progress.currentHashOutputBase58 + progress.currentUntrustedHash
       stepsCount = progress.publicKeyCount + progress.transactionSignCount + progress.trustedInputsCount + progress.trustedInputsProgressTotal + progress.hashOutputBase58Count + progress.untrustedHashCount
       percent = Math.ceil(currentStep / stepsCount * 100)
-      l percent + "%"
-      l progress
+      d.notify(progress)
       progressCallback?({currentStep, stepsCount, percent})
     .then (@_resumeData) =>
       @_validationMode = @_resumeData.authorizationRequired
@@ -165,17 +164,17 @@
   # @param [String] validationKey 4 chars ASCII encoded
   # @param [Function] callback
   # @return [Q.Promise]
-  validateWithPinCode: (validationPinCode, callback=undefined) -> @_validate((char.charCodeAt(0).toString(16) for char in validationPinCode).join(''), callback)
+  validateWithPinCode: (validationPinCode, callback=undefined, progressCallback=undefined) -> @_validate((char.charCodeAt(0).toString(16) for char in validationPinCode).join(''), callback, progressCallback)

   # @param [String] validationKey 4 chars ASCII encoded
   # @param [Function] callback
   # @return [Q.Promise]
-  validateWithKeycard: (validationKey, callback=undefined) -> @_validate(("0#{char}" for char in validationKey).join(''), callback)
+  validateWithKeycard: (validationKey, callback=undefined, progressCallback=undefined) -> @_validate(("0#{char}" for char in validationKey).join(''), callback, progressCallback)

   # @param [String] validationKey 4 bytes hex encoded
   # @param [Function] callback
   # @return [Q.Promise]
-  _validate: (validationKey, callback=undefined) ->
+  _validate: (validationKey, callback=undefined, progressCallback=undefined) ->
     if not @_resumeData? or not @_validationMode?
       Errors.throw('Transaction must me prepared before validation')
     d = ledger.defer(callback)
@@ -185,13 +184,19 @@
       undefined, # Default sigHash
       validationKey,
       @_resumeData
-    ).then( (@_signedRawTransaction) =>
+    ).progress (progress) =>
+      currentStep = progress.currentPublicKey + progress.currentSignTransaction + progress.currentTrustedInput + progress.currentTrustedInputProgress + progress.currentHashOutputBase58 + progress.currentUntrustedHash
+      stepsCount = progress.publicKeyCount + progress.transactionSignCount + progress.trustedInputsCount + progress.trustedInputsProgressTotal + progress.hashOutputBase58Count + progress.untrustedHashCount
+      percent = Math.ceil(currentStep / stepsCount * 100)
+      d.notify({currentStep, stepsCount, percent})
+      progressCallback?({currentStep, stepsCount, percent})
+    .then (@_signedRawTransaction) =>
       @_isValidated = yes
       $info("Raw TX: ", @getSignedTransaction())
       _.defer => d.resolve(@)
-    ).catch( (error) =>
+    .catch (error) =>
       _.defer => d.rejectWithError(Errors.SignatureError, error)
-    ).done()
+    .done()
     d.promise

   ###


disable attributionControl

disable attributionControl

--- wm-map/source/javascripts/_angular/MapController.js.coffee
+++ wm-map/source/javascripts/_angular/MapController.js.coffee
@@ -31,6 +31,7 @@
       defaults:
         minZoom: 16
         maxZoom: 22
+        attributionControl: false
       tiles:
           url: 'http://{s}.tiles.mapbox.com/v3/tomrocket.k93e7pp4/{z}/{x}/{y}.png'
           options:


dont make templates on clonetempalte

dont make templates on clonetempalte

--- vue/src/shared/models/poll_model.coffee
+++ vue/src/shared/models/poll_model.coffee
@@ -87,7 +87,8 @@
     clone.groupId = null
     clone.discussionId = null

-    clone.template = !@template
+    clone.template = false
+
     if clone.template
       clone.closingAt = null
     else


Revert "Revert "Trying to get timestamps working""

This reverts commit 99ab7ff3225da9c0b86081dd6fe4a8bd747dce2c.

--- src/adapters/campfire.coffee
+++ src/adapters/campfire.coffee
@@ -50,9 +50,9 @@
       unless bot.info.id == author.id
         self.receive new Robot.LeaveMessage(author)

-    bot.on "TimestampMessage", withAuthor (id, created, room, user, body, author) ->
-      unless bot.info.id == author.id
-        self.receive new Robot.TimestampMessage(author)
+    bot.on "TimestampMessage", () ->
+      console.log "Timestamp"
+      self.receive new Robot.TimestampMessage(null)

     bot.Me (err, data) ->
       bot.info = data.user


update comment

update comment

--- src/app/language-mode.coffee
+++ src/app/language-mode.coffee
@@ -182,7 +182,7 @@

     desiredIndentLevel

-  # Calculate a minimum indent level for a range of lines.
+  # Calculate a minimum indent level for a range of lines excluding empty lines.
   #
   # startRow - The row {Number} to start at
   # endRow - The row {Number} to end at


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/animation.coffee
+++ test/animation.coffee
@@ -1,2 +1,3 @@
 test "Animation should default to first frame", ->
-  animation = Animation.fromPixieId 45, -> equals animation.active(), 0+  animation = Animation.fromPixieId 45, ->
+    equals animation.active(), 0


Kloud: ignore klient.ping if machine document not found

--- client/Main/kite/kites/kloud.coffee
+++ client/Main/kite/kites/kloud.coffee
@@ -64,7 +64,7 @@
     {klient} = kontrol.kites
     machine  = computeController.findMachineFromMachineId machineId

-    unless machineId?
+    if not machine or not machineId
       return callback null

     klientKite = klient?[machine.uid]


Fix command-installer-spec

Fix command-installer-spec

--- spec/command-installer-spec.coffee
+++ spec/command-installer-spec.coffee
@@ -20,7 +20,7 @@

       installDone = false
       installError = null
-      installer.install commandFilePath, false, (error) ->
+      installer.createSymlink commandFilePath, false, (error) ->
         installDone = true
         installError = error


Only pause cursor blink when cursors actually move

Not when they are just repositioned due to lineHeight/charWidth changes

--- src/cursors-component.coffee
+++ src/cursors-component.coffee
@@ -37,7 +37,11 @@
       not isEqualForProperties(newProps, @props, 'cursorPixelRects', 'scrollTop', 'scrollLeft', 'defaultCharWidth')

   componentWillUpdate: (newProps) ->
-    @pauseCursorBlinking() if @props.cursorPixelRects? and not isEqual(newProps.cursorPixelRects, @props.cursorPixelRects)
+    cursorsMoved = @props.cursorPixelRects? and
+      isEqualForProperties(newProps, @props, 'defaultCharWidth', 'scopedCharacterWidthsChangeCount') and
+      not isEqual(newProps.cursorPixelRects, @props.cursorPixelRects)
+
+    @pauseCursorBlinking() if cursorsMoved

   startBlinkingCursors: ->
     @toggleCursorBlinkHandle = setInterval(@toggleCursorBlink, @props.cursorBlinkPeriod / 2) if @isMounted()


Remove unused dependencies

Remove unused dependencies

--- src/scripts/models/featured-book.coffee
+++ src/scripts/models/featured-book.coffee
@@ -1,8 +1,6 @@
 define [
-  'jquery'
-  'underscore'
   'backbone'
-], ($, _, Backbone) ->
+], (Backbone) ->

   return class Book extends Backbone.Model
     defaults:


Added game to navbar

Added game to navbar

--- web/coffee/navbar.coffee
+++ web/coffee/navbar.coffee
@@ -3,6 +3,7 @@
   News: "/news"

 teacherLoggedIn =
+  Game: "/game"
   Problems: "/problems"
   Scoreboard: "/scoreboard"
   Classroom: "/classroom"
@@ -13,6 +14,7 @@
   Logout: "/logout"

 userLoggedIn =
+  Game: "/game"
   Problems: "/problems"
   Team: "/team"
   Scoreboard: "/scoreboard"


more function rearranging

more function rearranging

--- server/battle.coffee
+++ server/battle.coffee
@@ -79,17 +79,6 @@
       pokemon.push(player.team.getActivePokemon()...)
     pokemon

-  getAction: (clientId) =>
-    @playerActions[clientId]
-
-  hasWeather: (weatherName) =>
-    weather = (if @hasWeatherCancelAbilityOnField() then "None" else @weather)
-    weatherName == weather
-
-  hasWeatherCancelAbilityOnField: =>
-    _.any @getActivePokemon(), (pokemon) ->
-      pokemon.hasAbility('Air Lock') || pokemon.hasAbility('Cloud Nine')
-
   # Add `string` to a buffer that will be sent to each client.
   message: (string) =>
     @buffer.push(string)
@@ -105,6 +94,80 @@
     for id, player of @players
       player.updateChat('SERVER', @buffer.join("<br>"))
     @clearMessages()
+
+  hasWeather: (weatherName) =>
+    weather = (if @hasWeatherCancelAbilityOnField() then "None" else @weather)
+    weatherName == weather
+
+  hasWeatherCancelAbilityOnField: =>
+    _.any @getActivePokemon(), (pokemon) ->
+      pokemon.hasAbility('Air Lock') || pokemon.hasAbility('Cloud Nine')
+
+  startNewTurn: =>
+    @turn++
+
+    # Send appropriate requests to players
+    for id, player of @players
+      poke_moves = player.team.at(0).moves
+      switches = player.team.getAlivePokemon().map((p) -> p.name)
+      @requestAction(player, moves: poke_moves, switches: switches)
+
+  # Tells the battle to continue the turn. This is called once all requests
+  # have been submitted and the battle is ready to continue. Once
+  # there are no more requests the next turn begins.
+  continueTurn: =>
+    skipIds = []
+    for id in @orderIds()
+      continue  if id in skipIds
+
+      switch @getAction(id).type
+        when 'switch' then @performSwitch(id)
+        when 'move'   then @performMove(id)
+
+      faintedIds = @requestFaintedReplacements()
+      skipIds.push(faintedIds...)
+
+      # Clean up playerActions hash.
+      delete @playerActions[id]
+
+    # TODO: THE FOLLOWING SHOULD BE SKIPPED IF THIS IS JUST REPLACEMENTS
+    if @isOver()
+      @sendMessages()
+      @endBattle()
+      return
+
+    # END TURN effects (skipped if replacements phase)
+
+    # TODO: Check to see if a pokemon fainted? Test if its over again?
+
+    if @requestQueue.length > 0
+      {player, validActions} = @requestQueue.shift()
+      @requestAction(player, validActions)
+
+      # Send a message to each player.
+      @sendMessages()
+
+    # If no replacements are left, then continue the turn
+    if @areAllRequestsCompleted() then @startNewTurn()
+
+  endBattle: =>
+    winner = @getWinner()
+    for id, player of @players
+      player.updateChat('SERVER', "#{winner.username} won!")
+      player.updateChat('SERVER', "END BATTLE.")
+
+  getWinner: =>
+    winner = null
+    length = 0
+    for id, player of @players
+      newLength = player.team.getAlivePokemon().length
+      if newLength > length
+        length = newLength
+        winner = player
+    player
+
+  isOver: =>
+    _.any(@players, (player) -> player.team.getAlivePokemon().length == 0)

   # Tells the player to execute a certain move by name. The move is added
   # to the list of player actions, which are executed once the turn continues.
@@ -164,76 +227,19 @@

     @makeSwitch(player, index)

+  getAction: (clientId) =>
+    @playerActions[clientId]
+
+  requestAction: (player, validActions) =>
+    # TODO: Delegate this kind of logic to the Player class.
+    @requests[player.id] = validActions
+    player.requestAction(@id, validActions)
+
   # Returns true if all requests have been completed. False otherwise.
   areAllRequestsCompleted: =>
     requests = 0
     requests += 1  for id of @requests
     requests == 0
-
-  startNewTurn: =>
-    @turn++
-
-    # Send appropriate requests to players
-    for id, player of @players
-      poke_moves = player.team.at(0).moves
-      switches = player.team.getAlivePokemon().map((p) -> p.name)
-      @requestAction(player, moves: poke_moves, switches: switches)
-
-  requestAction: (player, validActions) =>
-    # TODO: Delegate this kind of logic to the Player class.
-    @requests[player.id] = validActions
-    player.requestAction(@id, validActions)
-
-  continueTurn: =>
-    skipIds = []
-    for id in @orderIds()
-      continue  if id in skipIds
-
-      switch @getAction(id).type
-        when 'switch' then @performSwitch(id)
-        when 'move'   then @performMove(id)
-
-      faintedIds = @requestFaintedReplacements()
-      skipIds.push(faintedIds...)
-
-      # Clean up playerActions hash.
-      delete @playerActions[id]
-
-    # TODO: THE FOLLOWING SHOULD BE SKIPPED IF THIS IS JUST REPLACEMENTS
-    if @isOver()
-      @sendMessages()
-      @endBattle()
-      return
-
-    # END TURN effects (skipped if replacements phase)
-
-    if @requestQueue.length > 0
-      {player, validActions} = @requestQueue.shift()
-      @requestAction(player, validActions)
-
-    # Send a message to each player.
-    @sendMessages()
-
-    if @areAllRequestsCompleted() then @startNewTurn()
-
-  endBattle: =>
-    winner = @getWinner()
-    for id, player of @players
-      player.updateChat('SERVER', "#{winner.username} won!")
-      player.updateChat('SERVER', "END BATTLE.")
-
-  getWinner: =>
-    winner = null
-    length = 0
-    for id, player of @players
-      newLength = player.team.getAlivePokemon().length
-      if newLength > length
-        length = newLength
-        winner = player
-    player
-
-  isOver: =>
-    _.any(@players, (player) -> player.team.getAlivePokemon().length == 0)

   # If a Pokemon faints, add the player to the action queue.
   requestFaintedReplacements: =>


Split load package settings so config can be altered first for Grunt

--- server/config.coffee
+++ server/config.coffee
@@ -34,13 +34,20 @@

 	nconf.defaults path: "#{__dirname}/.."

+	return
+
+# ### loadPackageSettings
+#
+# *Load package settings as defaults in the configuration*
+exports.loadPackageSettings = ->
+
 	# } Register packages.
 	debug "Registering packages..."

 	pkgman.registerPackageList nconf.get 'packageList'

 	debug "Packages registered."
-
+
 	packageSettings = new Config()
 	for key, value of pkgman.invoke 'packageSettings'
 		packageSettings.set key.replace(/\//g, ':'), value
@@ -53,7 +60,9 @@
 		packageSettings: packageSettings.toJSON()

 		path: "#{__dirname}/.."
-
+
+	return
+
 # ### set
 #
 # *Set a configuration value by key.*


Use watchFile instead of watch.

Use watchFile instead of watch.

--- server/server.coffee
+++ server/server.coffee
@@ -81,7 +81,9 @@
   timestamp = process.hrtime().join "-"
   suffix = if request.isContentEditable then "html" else "txt"
   filename = "#{directory}/#{username}-text-aid-too-#{timestamp}.#{suffix}"
+
   console.log "edit:", filename
+  onExit.push -> console.log "  done:", filename

   fs.writeFile filename, request.text, (error) ->
     return exit() if error
@@ -90,6 +92,7 @@
     sendText = (continuation = null) ->
       fs.readFile filename, "utf8", (error, data) ->
         return exit() if error
+        console.log "  send:", filename
         request.text = data
         ws.send JSON.stringify request
         continuation?()
@@ -97,7 +100,12 @@
     monitor = watchr.watch
       path: filename
       listener: sendText
+      # This is only used for the "watch" method.
       catchupDelay: 400
+      # Unfortunately, the "watch" method isn't reliable.  So we're actually using the "watchFile" method
+      # instead.  See https://github.com/bevry/watchr/issues/33.
+      preferredMethods: [ 'watchFile', 'watch' ]
+      interval: 500
     onExit.push -> monitor.close()

     child = child_process.exec getEditCommand filename


add snippet

add snippet

--- src/symlinks/atom.symlink/snippets.cson
+++ src/symlinks/atom.symlink/snippets.cson
@@ -19,3 +19,8 @@
 # If you are unfamiliar with CSON, you can read more about it in the
 # Atom Flight Manual:
 # https://atom.io/docs/latest/using-atom-basic-customization#cson
+
+'.source.elixir':
+  'Log':
+    'prefix': 'log'
+    'body': '|> IO.inspect()'


Keep the workers sorted

Keep the workers sorted

--- javascripts/application.js.coffee
+++ javascripts/application.js.coffee
@@ -16,7 +16,7 @@
 class Flatware extends Backbone.Model
   initialize: ->
     @jobs    = new Backbone.Collection [], model: Job, comparator: (model)-> model.id
-    @workers = new Backbone.Collection [], model: Worker, comparator: (model)-> model.id
+    @workers = new Workers []
     @on 'jobs', (jobs)=> @jobs.set jobs
     @on 'started', (work)=>
       {worker, job} = work
@@ -64,6 +64,12 @@
   initialize: ->
     @on 'change', -> @get('job')?.set @pick 'status', 'dots'
     @on 'change:job', -> @set dots: []
+
+class Workers extends Backbone.Collection
+  model: Worker
+  initialize: ->
+    @on 'add', @sort
+  comparator: (model)-> model.id

 View = {}


small fix

small fix

--- src/driver/driverUtil.coffee
+++ src/driver/driverUtil.coffee
@@ -39,7 +39,7 @@
            isAdditiveApply(apply.operands[1]))

 getPropFromSegment = (segment, prop) ->
-  return null unless segment
+  return null unless segment and segment.prop
   return segment.prop[prop] or getPropFromSegment(segment.parent, prop)

 bucketFilterFns = {


Fixed the ach hans run bug

Fixed the ach hans run bug

--- scripts/card-fetcher.coffee
+++ scripts/card-fetcher.coffee
@@ -13,26 +13,18 @@

 cardDatabaseUrl = "http://mtgjson.com/json/AllCards.json"
 urlBase = "http://gatherer.wizards.com/Handlers/Image.ashx?type=card&name="
-replacements = [
-  {from : '“', to : '"'},
-  {from : '”', to : '"'}
-]
+uniQuotes = /“|”/

-doReplacement = (string, pair) ->
-  return string.replace /#{pair.from}/, /#{pair.to}/, 'g'
+doReplacement = (str) ->
+  return str.replace uniQuotes, "\"", "g"

 verifyCard = (cardName, cardDB) ->
-  for pair in replacements
-    cardName = doReplacement cardName, pair
+  while uniQuotes.test cardName
+    cardName = doReplacement cardName
   if cardName.search(" // ") isnt -1
     splitCards = cardName.split " // "
     return encodeURIComponent(cardName) if splitCards[0] of cardDB and splitCards[1] of cardDB
   else
-    console.log "Encoded: " + encodeURIComponent(cardName)
-    console.log "Not encoded: " + cardName
-    matches = Object.keys(cardDB).filter (key) ->
-      /Ach! Hans, Run!/.test key
-    #console.log matches
     return encodeURIComponent(cardName) if cardName of cardDB
   "Dismal%20Failure"

@@ -45,5 +37,4 @@
     robot.http(cardDatabaseUrl)
       .get() (err, res, body) ->
         cards = JSON.parse body
-        console.log msg.match[1]
         msg.send getCardImageUrl(msg.match[1], cards)


engine#add

engine#add

--- src/engine_game_state.coffee
+++ src/engine_game_state.coffee
@@ -33,7 +33,7 @@
     I.currentState.trigger "overlay", canvas

   # We must always return self as the last line
-  return {
+  return
     # Just pass through to the current state
     add: (className, entityData) ->
       #
@@ -77,5 +77,4 @@
       I.currentState.loadState(newState)
     reload: ->
       I.currentState.reload()
-  }


Adds better bridge snippet

Adds better bridge snippet

--- snippets/bridge.cson
+++ snippets/bridge.cson
@@ -1,4 +1,4 @@
 '.source.inspec':
   'Desscribe bridge':
     'prefix': 'bridge'
-    'body': "# http://inspec.io/docs/reference/resources/bridge/\ndescribe bridge('${1:name}') do\n  it { should${2:_not} ${3:exist} }${4}\nend"
+    'body': "# http://inspec.io/docs/reference/resources/bridge/\ndescribe bridge('${1:name}') do\n  it { should${2:_not} ${3:exist} }\n  it { should${4:_not} have_interface '${5:eth0}' }${6}\nend"


Fixed bug causing player_disconnect verb from not running.

--- socket_server.coffee
+++ socket_server.coffee
@@ -28,10 +28,10 @@
   # or calling the `disconnect` method of the socket.
   onDisconnect: =>
     # remove the socket from our list of logged in players, if it exists
+    player = connections.playerFor @socket
     connections.remove @socket

     # if there was a player logged in on the socket, call the `player_disconnected` verb on $sys
-    player = connections.playerFor @socket
     if player?
       verb = @db.sys.findVerbByName 'player_disconnected'
       if verb?


a select tool that should send less over the wire

--- static/coffee/tools/selecttool.coffee
+++ static/coffee/tools/selecttool.coffee
@@ -27,12 +27,15 @@
         select_callback)
       safebind(this, renderer.get_obj('ydata_range'), 'change',
         select_callback)
+
   eventGeneratorClass : TwoPointEventGenerator
   evgen_options : {keyName:"ctrlKey", buttonText:"Select"}
   tool_events : {
-    UpdatingMouseMove: "box_selecting",
     SetBasepoint : "_start_selecting",
-    DragEnd: "_selecting",
+    #UpdatingMouseMove: "box_selecting",
+    UpdatingMouseMove: "_selecting",
+
+    #DragEnd: "_selecting",
     deactivated : "_stop_selecting"}

   mouse_coords : (e, x, y) ->
@@ -78,7 +81,7 @@
   _selecting : (e, x_, y_) ->
     [x, y] = @mouse_coords(e, e.bokehX, e.bokehY)
     @mset({'current_x' : x, 'current_y' : y})
-    [@xrange, @yrange] = @_get_selection_range_fast(x, y)
+    [@xrange, @yrange] = @_get_selection_range(x, y)
     @trigger('boxselect', @xrange, @yrange)
     return null

@@ -100,13 +103,25 @@
     for renderer in @mget_obj('renderers')
       datasource_id = renderer.get_obj('data_source').id
       _.setdefault(datasource_selections, datasource_id, [])
+      #the select call of the render converts the screen coordinates
+      #of @xrange and @yrange into data space coordinates
       selected = @plot_view.renderers[renderer.id].select(@xrange, @yrange)
       datasource_selections[datasource_id].push(selected)

     for own k,v of datasource_selections
+
+      #FIXME: I'm not sure why this is here, when will v have more than one element?
+      #
+      # This next line is the equivalent of calling
+      #_.intersection(v[0], v[1], v[2]...) for however many
+      #subelements v has.  each member of the v list will have another
+      #list inside it.  thus this line finds the intersection of the
+      #lists of v.
       selected = _.intersection.apply(_, v)
-      datasources[k].set('selected', selected)
-      datasources[k].save()
+      ds = datasources[k]
+      ds.set('selected', selected)
+      #console.log("datasource_selections", k, v, selected)
+      #ds.save()
     return null