tasks2: save view state to localStorage

tasks2: save view state to localStorage

--- src/smc-webapp/tasks/actions.coffee
+++ src/smc-webapp/tasks/actions.coffee
@@ -23,25 +23,37 @@
         @store      = store

         # TODO: local_task_state and local_view_state need to persist to localStorage
-        font_size = @redux.getStore('account')?.get('font_size') ? 14
+        x = localStorage[@name]
+        if x?
+            local_view_state = immutable.fromJS(JSON.parse(x))
+        else
+            font_size = @redux.getStore('account')?.get('font_size') ? 14
+            local_view_state = immutable.fromJS(show_deleted:false, show_done:false, font_size:font_size)
         @setState
-            local_task_state: immutable.Map()
-            local_view_state: immutable.fromJS(show_deleted:false, show_done:false, font_size:font_size)
-            counts          : immutable.fromJS(done:0, deleted:0)
+            local_task_state : immutable.Map()
+            local_view_state : local_view_state
+            counts           : immutable.fromJS(done:0, deleted:0)

         @_init_has_unsaved_changes()
         @syncdb.on('change', @_syncdb_change)
         @syncdb.once('change', @_ensure_positions_are_unique)
+        @_save_local_view_state = underscore.debounce(@__save_local_view_state, 3000)

     close: =>
         if @_state == 'closed'
             return
         @_state = 'closed'
+        @__save_local_view_state()
         @syncdb.close()
         delete @syncdb
         if @_key_handler?
             @redux.getActions('page').erase_active_key_handler(@_key_handler)
             delete @_key_handler
+
+    __save_local_view_state: =>
+        local_view_state = @store.get('local_view_state')
+        if local_view_state and localStorage?
+            localStorage[@name] = JSON.stringify(local_view_state)

     _init_has_unsaved_changes: => # basically copies from jupyter/actions.coffee -- opportunity to refactor
         do_set = =>
@@ -181,6 +193,7 @@
         @setState
             local_view_state : local
         @_update_visible()
+        @_save_local_view_state()

     save: =>
         @setState(has_unsaved_changes:false)


Switch to ScriptDb

Switch to ScriptDb

--- src/mailButler.appsscript.coffee
+++ src/mailButler.appsscript.coffee
@@ -1,13 +1,15 @@
 class MailButler

+  @VERSION:             '1.0.0'
+  @TYPE:                'mailbutler'
+  @LABEL_BASE:          'MailButler'
+  @LABEL_OUTBOX:        MailButler.LABEL_BASE + '/' + 'Outbox'
+  @FREQUENCY_MINUTES:   1
+  @DB:                  ScriptDb.getMyDb()
+
+  prefix: null
+
   constructor: (@prefix) ->
-
-  @LABEL_BASE: "MailButler"
-  @LABEL_OUTBOX: MailButler.LABEL_BASE + "/" + "Outbox"
-  @KEY_PREFIX_MESSAGE: "msg_"
-  @FREQUENCY_MINUTES: 1
-
-  prefix: null

   scheduleJson: (params) ->
     try
@@ -23,6 +25,23 @@
     catch e
       e

+  @uninstall: (automatic) ->
+    base = ScriptApp.getService().getUrl()
+    if not automatic and base
+      # Only if no automatic uninstall (e.g. the user has to confirm) and the script is published as a WebApp
+      target = base.substring 0, base.lastIndexOf '/'
+
+      HtmlService.createHtmlOutput  """Click here to uninstall."""
+    else
+      # Fallback, if this is not a published WebApp or automatic uninstall is wanted
+      # remove all triggers, so there are no errors when we invalidate the authentification
+      for trigger in ScriptApp.getScriptTriggers()
+        ScriptApp.deleteTrigger trigger
+
+      # invalidate authentication
+      ScriptApp.invalidateAuth()
+      ContentService.createTextOutput 'Application successfully uninstalled.'
+
   result: (err, result) ->
     ret = []
     if @prefix
@@ -42,23 +61,35 @@
       ScriptApp.newTrigger("process").timeBased().everyMinutes(MailButler.FREQUENCY_MINUTES).create()
     return

+  @getEmail: ->
+    Session.getEffectiveUser().getEmail()
+
+  @getScheduledMails: ->
+    q =
+      user:     @getEmail()
+      type:     @TYPE
+    result = @DB.query q
+    result.next() while result.hasNext()
+
   @processButlerMails: (d) ->
     Logger.log "Checking for scheduled mails on %s", d

-    validKeys = []
-    for key in UserProperties.getKeys()
-      if @isButlerMessage key
-        validKeys.push key
-
-    if validKeys.length > 0
-
+    q =
+      user:     @getEmail()
+      version:  @VERSION
+      type:     @TYPE
+      when:     @DB.lessThanOrEqualTo d.getTime()
+
+    Logger.log "Using the query #{q}"
+
+    result = @DB.query(q).sortBy 'when', @DB.ASCENDING
+
+    if (s = result.getSize()) > 0
       # yep there are some
-      Logger.log "Found %s possible candidates", validKeys.length
-
-      now = d.getTime()
-      for key in validKeys
-        props = @getButlerMail key, true
-        @processButlerMail props  if isNaN(props.when) or props.when <= now
+      Logger.log "Found %s candidates", s
+
+      while result.hasNext()
+        @processButlerMail result.next()

     else
       # No scheduled messages available
@@ -133,7 +164,7 @@
       Logger.log "Finished processing message with ID '%s'", messageId

     # delete the scheduled butler mail, because we couldn't find the real message
-    @deleteButlerMail messageId # pass "" + x in case messageId is undefined (should not happen)
+    @DB.remove props # pass "" + x in case messageId is undefined (should not happen)
     return

   # Get a label or create it
@@ -156,22 +187,12 @@
     @getLabel(name, false)?.removeFromThread thread
     return

-  @normalize: (messageId, noPrefix) ->
-    ((if noPrefix then "" else @KEY_PREFIX_MESSAGE)) + ("" + messageId).toLowerCase()
-
-  @isButlerMessage: (key) ->
-    key?.indexOf(@KEY_PREFIX_MESSAGE) is 0
-
   @storeButlerMail: (props) ->
-    UserProperties.setProperty @normalize(props.messageId), Utilities.jsonStringify props
-    return
-
-  @getButlerMail: (messageId, noPrefix) ->
-    Utilities.jsonParse UserProperties.getProperty (@normalize messageId, noPrefix)
-
-  @deleteButlerMail: (messageId) ->
-    Logger.log "Removing scheduled message with ID '%s'", messageId
-    UserProperties.deleteProperty @normalize messageId
+    props.user    = @getEmail()
+    props.version = @VERSION
+    props.type    = @TYPE
+
+    @DB.save props
     return

   addButlerMail: (form) ->
@@ -224,6 +245,10 @@
   butler = new MailButler request.parameter.callback

   switch request.parameter.action
+    when 'uninstall'
+      out = MailButler.uninstall true
+    when 'dump'
+      out = butler.result null, MailButler.getScheduledMails()
     when 'schedule'
       out = butler.scheduleJson request.parameter
     when 'setup'
@@ -279,22 +304,19 @@
 _test = ->
   doGet
     parameter:
-      msgId: "13a1a6948cb7471f" # works with joscha@feth.com only
-      when: "delta:"+ (2 * 1000 * 60)
-      inbox: true
-      unread: true
+      msgId:    "13a1a6948cb7471f" # works with joscha@feth.com only
+      when:     "delta:"+ (2 * 1000 * 60)
+      inbox:    true
+      unread:   true
       noanswer: true
-      action: 'schedule'
+      action:   'schedule'
+      archive:  false
   return

 # This is just a helper until the Google Apps Script Code Editor can deal with bla = function assignments
 `function test() {
   _test.apply(this, arguments);
 }`
-
-#deleteProps = ->
-#  UserProperties.deleteAllProperties()
-#  return

 #testLastMessageDate = ->
 #  message = GmailApp.getMessageById("13a1a6948cb7471f")
@@ -306,10 +328,16 @@
 #    Logger.log msg.getDate().toUTCString()
 #  return

-#testProps = ->
-#  Logger.log UserProperties.getProperties()
-#  return
-
 #getTriggers = ->
 #  Logger.log "triggers: %s", ScriptApp.getScriptTriggers()
 #  return
+
+`function showDb() {
+  var result = ScriptDb.getMyDb().query({type: MailButler.TYPE});
+  Logger.log('Size: %s', result.getSize());
+  while(result.hasNext()) {
+    Logger.log(result.next());
+  }
+}`
+
+


Load entries before trying to reload

Load entries before trying to reload

--- lib/directory.coffee
+++ lib/directory.coffee
@@ -68,6 +68,10 @@
       @subscribe(@watchSubscription)

   reload: ->
+    unless @entries?
+      @getEntries()
+      return
+
     newEntries = []
     removedEntries = _.clone(@entries) ? {}
     index = 0


Line spacing

Line spacing

--- lib/core.coffee
+++ lib/core.coffee
@@ -317,6 +317,7 @@
   # for sending interrupts
   signal: (channel, data) ->

+
 # Expose 'bus' access for internal methods, wiretaps.
 # Non-enumerable, non-configurable, non-assignable.
 Object.defineProperty core, 'bus', {value: bus}


fixed a piles involving the kitchen sink. The order of @_after_alls bwas being applied incorrectly, and to correctly limit the before_alls usage a fake(ish) implementation of report tracking needed to be added, so we could count the total tests being run. All tests currently pass.

--- lib/suite.coffee
+++ lib/suite.coffee
@@ -41,18 +41,19 @@
     if index is 0
       before_alls.push @_before_alls...
       before_eachs.push @_before_eachs...
-      after_eachs.push @_after_eachs...
+      after_eachs[...0] = @_after_eachs

     item = @_tests_and_suites[index]

     if not item?
       if report.total_tests > 0
-        @_run_runners @_after_alls, callback
+        @_run_runners @_after_alls, ->
+          callback report
       else
-          callback report
+        callback report
       return

-    before_alls_callback = (reports) =>
+    before_alls_callback = (runner_reports) =>
       before_alls = []
       item.run test_callback

@@ -61,11 +62,14 @@
       report.total_tests += 1
       @_run_runners after_eachs, after_eachs_callback

-    after_eachs_callback = (report) =>
-      @_run callback, before_alls, before_eachs, after_eachs, ++index
+    after_eachs_callback = (runner_reports) =>
+      @_run callback, before_alls, before_eachs, after_eachs, ++index, report

-    suite_callback = (reports) =>
-      @_run callback, before_alls, before_eachs, after_eachs, ++index
+    suite_callback = (suite_report) =>
+      if suite_report.total_tests > 0
+        before_alls = []
+        report.total_tests += suite_report.total_tests
+      @_run callback, before_alls, before_eachs, after_eachs, ++index, report

     if item instanceof Test
       runners = []


bridgeway to task runners

bridgeway to task runners

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -0,0 +1,10 @@
+gruntFunction = (grunt) ->
+    gruntConfig =
+        pkg:
+            grunt.file.readJSON 'package.json'
+    grunt.initConfig gruntConfig
+
+    grunt.loadNpmTasks 'grunt-contrib-coffee'
+    null
+
+module.exports = gruntFunction


fix(message-timestamps): avoid duplicate timestamps

F5 on the messages page will lead to duplicating the message times. Probably because `document.arrive` catches initial nodes.

Let's avoid this by checking that there's no time inserted already.

Closes #47.

--- packages/vk-x/src/message-timestamps/index.coffee
+++ packages/vk-x/src/message-timestamps/index.coffee
@@ -15,6 +15,7 @@

     onNewMessage = ->
       return if $( @ ).is ":first-child"
+      return if $( @ ).find( ".vkx-message-time" ).length # Fixes #47

       rawTimestamp = $( @ ).data "ts"
       parsedTime = moment.unix rawTimestamp


set camera via function nLookAt

set camera via function nLookAt

--- src/webapp/_scripts/r7/Stage4Render.coffee
+++ src/webapp/_scripts/r7/Stage4Render.coffee
@@ -7,14 +7,19 @@
       _areaBox = null
       _cube = null
       _cameraTargetObjId = null
-      #_camera = new THREE.PerspectiveCamera(75, 1, 1, 100)
-      _camera = new THREE.OrthographicCamera(10,10,10,10, 1, 100)
+      #_camera = new THREE.PerspectiveCamera(75, 1, 1, 500)
+      _camera = new THREE.OrthographicCamera(10,10,10,10, 1, 1000)

       #HACK to clean
       #see http://help.dottoro.com/ljorlllt.php
-      _camera.position.z = 10
+      nLookAt = (camera, v3) ->
+        camera.position.z = v3.z + 30
+        camera.position.y = v3.y + 2
+        camera.position.x = v3.x + 2
+        camera.lookAt(v3)
+
+      nLookAt(_camera, _scene.position)
       _scene.add(_camera)
-
       #_cameraControls = new THREE.DragPanControls(_camera)

       _renderer = if webglDetector.webgl
@@ -37,7 +42,7 @@
         w = container.clientWidth #window.innerWidth
         h = container.clientHeight #window.innerHeight
         _renderer.setSize(w, h)
-        #_camera.aspect = w /  h
+        _camera.aspect = w /  h
         unitperpixel = 0.1
         _camera.left = w / -2 * unitperpixel
         _camera.right = w / 2 * unitperpixel
@@ -87,6 +92,7 @@
             # pass
       start = ->
         spawnAxis()
+        _scene.add(makeLight())
         evt.SetupDatGui((gui) ->
           f2 = gui.addFolder("Camera")
           f2.add(_camera.position, "x")
@@ -124,13 +130,14 @@
           obj = _scene.getChildByName(_cameraTargetObjId, false)

           #
-          #        if (!!obj && !!_areaBox) {
-          #          cameraConstraint(obj.position, _camera.position.z, _camera, _areaBox);
-          #        } else {
-          #          _camera.lookAt(_scene.position);
+          #if (!!obj && !!_areaBox)
+          #  cameraConstraint(obj.position, _camera.position.z, _camera, _areaBox)
+          #else
+          #  _camera.lookAt(_scene.position)
           #        }
           #
           #_cameraControls.update()
+          nLookAt(_camera, obj.position)
           _renderer.render(_scene, _camera)

       spawnAxis = ->
@@ -157,6 +164,30 @@
         axis.scale.set(0.1, 0.1, 0.1) # default length of axis is 100
         _scene.add(axis)

+      makeLight = () ->
+        mainlight = new THREE.DirectionalLight( 0xffffff )
+        #shadow stuff
+        mainlight.shadowCameraNear = 50
+        mainlight.shadowCameraFar = 1000
+
+        mainlight.castShadow = true
+        mainlight.shadowDarkness = 0.5
+        mainlight.shadowCameraVisible = false
+        mainlight.shadowMapWidth = 2048
+        mainlight.shadowMapHeight = 2048
+
+        #light target
+        lightTarget = new THREE.Object3D()
+        lightTarget.name = "lightTarget"
+        mainlight.target = lightTarget
+        lightTarget.position.set(20,0,0)
+
+        lightTarget.add( mainlight ) #adding mainlight as child to lightTarget (easy rotation controls via parent)
+        mainlight.position.set(0,500,0) # moving mainlight away from its parent
+        lightTarget.rotation.set(0,-0.5,1) #setting elevation and azimuth via mainlight's parent
+        #lightTarget
+        mainlight
+
       #TODO support spawn animation
       spawnObj = (id, pos, obj3d) ->
         ids = id.split('>')
@@ -188,8 +219,6 @@
         #var s = w/75;
         #mesh.scale.set(s, s, s);
         parent.add(obj)
-        console.debug("spawn", ids, obj, parent)
-

       #TODO support despawn animation
       despawnObj = (id) ->
@@ -212,7 +241,9 @@
           _scene.add(light)
         )
         wall = scene3d.objects.wall
-        vertices = wall.geometry.vertices
+        updateAreaBox(wall.geometry.vertices, wall.scale, wall.position)
+
+      updateAreaBox = (vertices, scale, position) ->
         _areaBox = _.reduce(vertices, (acc, v) ->
           acc.xmin = Math.min(acc.xmin, v.x)
           acc.ymin = Math.min(acc.ymin, v.y)
@@ -225,10 +256,12 @@
           xmax: vertices[0].x
           ymax: vertices[0].y
         })
-        _areaBox.xmin = _areaBox.xmin * wall.scale.x + wall.position.x
-        _areaBox.ymin = _areaBox.ymin * wall.scale.y + wall.position.y
-        _areaBox.xmax = _areaBox.xmax * wall.scale.x + wall.position.x
-        _areaBox.ymax = _areaBox.ymax * wall.scale.y + wall.position.y
+        _areaBox.xmin = _areaBox.xmin * scale.x + position.x
+        _areaBox.ymin = _areaBox.ymin * scale.y + position.y
+        _areaBox.xmax = _areaBox.xmax * scale.x + position.x
+        _areaBox.ymax = _areaBox.ymax * scale.y + position.y
+        console.debug("_areaBox", _areaBox)
+        _areaBox

       #


Make `company_website` pass validation if the field is left the default value

--- apps/pro_buyer/pages/complete/client/view.coffee
+++ apps/pro_buyer/pages/complete/client/view.coffee
@@ -28,6 +28,11 @@

   submit: (e) ->
     e.preventDefault()
+
+    # If the URL is the default value; remove
+    # the http:// so that it passes validation
+    if ($url = @$('input[name="company_website"]')).val() is 'http://'
+      $url.val ''

     form = new Form $form: @$('form')
     return unless form.isReady()


Add username blacklist to admin UI

Add username blacklist to admin UI

--- picoCTF-web/web/coffee/management-settings.coffee
+++ picoCTF-web/web/coffee/management-settings.coffee
@@ -46,6 +46,11 @@
     @setState update @state,
       $set:
         max_team_size: parseInt(e.target.value)
+
+  updateUsernameBlacklist: (e) ->
+    @setState update @state,
+      $set:
+        username_blacklist: e.target.value.split('\n')

   pushUpdates: ->
     data = {
@@ -55,6 +60,7 @@
       start_time: new Date(@state.start_time).toISOString()
       end_time: new Date(@state.end_time).toISOString()
       max_team_size: @state.max_team_size
+      username_blacklist: @state.username_blacklist
     }
     apiCall "PATCH", "/api/v1/settings", data
     .success ((data) ->
@@ -76,6 +82,7 @@
     endTimeDescription = "After the competition has ended, users will no longer be able to submit keys to the challenges."

     maxTeamSizeDescription = "Maximum number of users that can join a single team."
+    usernameBlacklistDescription = "Usernames that are unavailable for registration."

     <Well>
       <BooleanEntry name="Receive Problem Feedback" value={@state.enable_feedback} onChange=@toggleFeedbackEnabled description={feedbackDescription}/>
@@ -84,6 +91,7 @@
       <TimeEntry name="Competition Start Time" value={@state.start_time} onChange=@updateStartTime description={startTimeDescription}/>
       <TimeEntry name="Competition End Time" value={@state.end_time} onChange=@updateEndTime description={endTimeDescription}/>
       <TextEntry name="Max Team Size" value={@state.max_team_size} type="number" onChange=@updateMaxTeamSize description={maxTeamSizeDescription} />
+      <TextEntry name="Username Blacklist" value={@state.username_blacklist.join('\n')} type="textarea" onChange=@updateUsernameBlacklist description={usernameBlacklistDescription}/>
       <Row>
         <div className="text-center">
           <ButtonToolbar>
@@ -474,6 +482,7 @@
       competition_url: ""
       enable_feedback: true
       max_team_size: 1
+      username_blacklist: []
       email:
         email_verification: false
         parent_verification_email: false
@@ -526,6 +535,7 @@
       start_time: @state.settings.start_time
       end_time: @state.settings.end_time
       max_team_size: @state.settings.max_team_size
+      username_blacklist: @state.settings.username_blacklist
     <Well>
       <Grid>
         <Row>


+ zoom animation

+ zoom animation

--- lib/controllers/scale_controller.coffee
+++ lib/controllers/scale_controller.coffee
@@ -5,8 +5,8 @@
     @bindEvents();

   bindEvents: () =>
-    $('#zoom-in').on('click', @zoomIn);
-    $('#zoom-out').on('click', @zoomOut);
+    $('#zoom-in').on('click', @zoomInAnim);
+    $('#zoom-out').on('click', @zoomOutAnim);

   zoomOut: () =>
     @zoom(@scale - 0.1);
@@ -17,3 +17,14 @@
   zoom: (scale) =>
     @scale = scale;
     @map.zoom(@scale);
+
+  zoomInAnim: () =>
+    @animZoom(0.01, 10);
+
+  zoomOutAnim: () =>
+    @animZoom(-0.01, 10);
+
+  animZoom: (step, counter) =>
+    if counter > 0
+      @zoom(@scale - step)
+      window.setTimeout(@animZoom, 50, step, counter - 1)


remove some keybindings

remove some keybindings

--- keymaps/atom-dart.cson
+++ keymaps/atom-dart.cson
@@ -14,13 +14,10 @@

 # Global commands (mac)
 'body.platform-darwin':
-  'ctrl-alt-cmd-;': 'dartlang:rebuild-restart-dev'
-  'ctrl-alt-cmd-\'': 'dartlang:rebuild-run-tests-dev'
   'ctrl-alt-cmd-t': 'dartlang:run-tests'

 # Global commands (windows and linux)
 'body.platform-win32, body.platform-linux':
-  'ctrl-alt-;': 'dartlang:rebuild-restart-dev'
   'ctrl-alt-t': 'dartlang:run-tests'

 # Dart editor commands (common to all platforms)


Add Windows & Linux key bindings

Add Windows & Linux key bindings

--- keymaps/list-edit.cson
+++ keymaps/list-edit.cson
@@ -7,8 +7,14 @@

 # For more detailed documentation see
 # https://atom.io/docs/latest/behind-atom-keymaps-in-depth
-'atom-workspace':
+'.platform-darwin atom-text-editor':
   'alt-cmd-s': 'list-edit:select'
   'alt-cmd-x': 'list-edit:cut'
   'alt-cmd-c': 'list-edit:copy'
   'alt-cmd-v': 'list-edit:paste'
+
+'.platform-win32 atom-text-editor, .platform-linux atom-text-editor':
+  'ctrl-alt-s': 'list-edit:select'
+  'ctrl-alt-x': 'list-edit:cut'
+  'ctrl-alt-c': 'list-edit:copy'
+  'ctrl-alt-v': 'list-edit:paste'


Consistent behavior of HOME and END keys

When we press the HOME key, the focus should be set on the topmost (or the bottommost, when END is pressed) file in the tree view. All the IDEs I've ever used behave this way.

--- keymaps/tree-view.cson
+++ keymaps/tree-view.cson
@@ -79,6 +79,8 @@
   'k': 'core:move-up'
   'j': 'core:move-down'
   'i': 'tree-view:toggle-vcs-ignored-files'
+  'home': 'core:move-to-top'
+  'end': 'core:move-to-bottom'

 '.tree-view-dialog atom-text-editor[mini]':
   'enter': 'core:confirm'


Landing: Team: Convert slug to lowercase

--- client/landing/site.landing/coffee/teams/teamselectorview.coffee
+++ client/landing/site.landing/coffee/teams/teamselectorview.coffee
@@ -63,6 +63,7 @@
   goToTeam: (formData) ->

     { slug } = formData
+    slug     = slug.toLowerCase()

     return notify()  if slug is 'koding'


more natural cmd-w for the tabs pane again

--- extensions/tabs/tabs-pane.coffee
+++ extensions/tabs/tabs-pane.coffee
@@ -50,8 +50,17 @@
     $("#tabs ul li:last").addClass 'active'

   closeActiveTab: ->
-    @removeTab $('#tabs ul .active').data 'path'
-    @prevTab()
+    tabsLength = $('#tabs ul li').length
+    activePath = $('#tabs ul .active').data 'path'
+
+    if tabsLength is 1
+      @removeTab activePath
+      $('#main-container').children().css 'display', 'none !important'
+    else if tabsLength > 0
+      @removeTab activePath
+      @prevTab()
+    else
+      window.close()

   removeTab: (path) ->
     tab = $("#tabs li[data-path='#{path}']")


mała aktualizacja

mała aktualizacja

--- def/pl/settings.cson
+++ def/pl/settings.cson
@@ -122,8 +122,8 @@
       }
       {
         _id: 'core.fileEncoding'
-        title: "File Encoding"
-        desc: "Default character set encoding to use when reading and writing files."
+        title: "Kodowanie pliku"
+        desc: "Domyślne ustawienie kodowania znaków używane podczas czytania i zapisywania plików."
       }
       {
         _id: 'core.fileSystemWatcher'


Make refreshing and decorating apps cleaner

--- client/app/Applications/StartTab.kdapplication/AppView.coffee
+++ client/app/Applications/StartTab.kdapplication/AppView.coffee
@@ -127,20 +127,22 @@

   decorateApps:(apps)->
     @removeAppIcons()
+    @showLoader()
     @refreshButton.hide()
     @putAppIcons apps

-    @getSingleton("kodingAppsController").appStorage.fetchValue 'shortcuts', (shortcuts)=>
-      for shortcut, manifest of shortcuts
-        do (shortcut, manifest)=>
-          @appItemContainer.addSubView @appIcons[manifest.name] = new AppShortcutButton
-            delegate : @
-          , manifest
-
-      @appItemContainer.addSubView @appIcons['GET_MORE_APPS'] = new GetMoreAppsButton
-        delegate : @
-      @hideLoader()
-      @refreshButton.show()
+    shortcuts = @getSingleton("kodingAppsController").appStorage.getValue 'shortcuts'
+
+    for shortcut, manifest of shortcuts
+      do (shortcut, manifest)=>
+        @appItemContainer.addSubView @appIcons[manifest.name] = new AppShortcutButton
+          delegate : @
+        , manifest
+
+    @appItemContainer.addSubView @appIcons['GET_MORE_APPS'] = new GetMoreAppsButton
+      delegate : @
+    @hideLoader()
+    @refreshButton.show()

   removeAppIcons:->

@@ -149,7 +151,6 @@

   putAppIcons:(apps)->

-    @refreshButton.show()
     for app, manifest of apps
       do (app, manifest)=>
         @appItemContainer.addSubView @appIcons[manifest.name] = new StartTabAppThumbView


Autocomplete keeping original order for callback's returns

--- lib/elixir-autocomplete-provider.coffee
+++ lib/elixir-autocomplete-provider.coffee
@@ -66,12 +66,12 @@
             modulesToAdd = (m for m,i in hintModules when m != prefixModules[i])
             lastModuleHint = hintModules[hintModules.length-1]

-        suggestions = suggestions.map (serverSuggestion) ->
+        suggestions = suggestions.map (serverSuggestion, index) ->
           fields = serverSuggestion.replace(/;/g, '\u000B').replace(/\\\u000B/g, ';').split('\u000B')
           name = fields[0]
           if lastModuleHint && (lastModuleHint not in [name, ":#{name}"]) && modulesToAdd.length > 0
             fields[0] = modulesToAdd.join('.') + '.' + name
-          createSuggestion(serverSuggestion, fields, prefix, pipeBefore, captureBefore, defBefore)
+          createSuggestion(serverSuggestion, index, fields, prefix, pipeBefore, captureBefore, defBefore)

         suggestions = sortSuggestions(suggestions)
         resolve(suggestions)
@@ -80,7 +80,7 @@
     regex = /[\w0-9\._!\?\:@]+$/
     textBeforeCursor.match(regex)?[0] or ''

-  createSuggestion = (serverSuggestion, fields, prefix, pipeBefore, captureBefore, defBefore) ->
+  createSuggestion = (serverSuggestion, index, fields, prefix, pipeBefore, captureBefore, defBefore) ->
     if fields[1] == 'module'
       [name, kind, subtype, desc] = fields
     if fields[1] == 'return'
@@ -92,28 +92,31 @@

     return "" if serverSuggestion.match(/^[\s\d]/)

-    if kind == 'attribute'
-      createSuggestionForAttribute(name, prefix)
-    else if kind == 'var'
-      createSuggestionForVariable(name)
-    else if kind == 'module'
-      createSuggestionForModule(serverSuggestion, name, desc, prefix, subtype)
-    else if kind == 'callback'
-      createSuggestionForCallback(serverSuggestion, name, kind, signature, mod, desc, spec, prefix, defBefore)
-    else if kind == 'return'
-      createSuggestionForReturn(serverSuggestion, name, kind)
-    else if kind in ['private_function', 'public_function', 'public_macro']
-      createSuggestionForFunction(serverSuggestion, name, kind, signature, "", desc, spec, prefix, pipeBefore, captureBefore)
-    else if kind in ['function', 'macro']
-      createSuggestionForFunction(serverSuggestion, name, kind, signature, mod, desc, spec, prefix, pipeBefore, captureBefore)
-    else
-      console.log("Unknown kind: #{serverSuggestion}")
-      {
-        text: serverSuggestion
-        type: 'exception'
-        iconHTML: '?'
-        rightLabel: kind || 'hint'
-      }
+    suggestion =
+      if kind == 'attribute'
+        createSuggestionForAttribute(name, prefix)
+      else if kind == 'var'
+        createSuggestionForVariable(name)
+      else if kind == 'module'
+        createSuggestionForModule(serverSuggestion, name, desc, prefix, subtype)
+      else if kind == 'callback'
+        createSuggestionForCallback(serverSuggestion, name, kind, signature, mod, desc, spec, prefix, defBefore)
+      else if kind == 'return'
+        createSuggestionForReturn(serverSuggestion, name, kind)
+      else if kind in ['private_function', 'public_function', 'public_macro']
+        createSuggestionForFunction(serverSuggestion, name, kind, signature, "", desc, spec, prefix, pipeBefore, captureBefore)
+      else if kind in ['function', 'macro']
+        createSuggestionForFunction(serverSuggestion, name, kind, signature, mod, desc, spec, prefix, pipeBefore, captureBefore)
+      else
+        console.log("Unknown kind: #{serverSuggestion}")
+        {
+          text: serverSuggestion
+          type: 'exception'
+          iconHTML: '?'
+          rightLabel: kind || 'hint'
+        }
+    suggestion.index = index
+    suggestion

   createSuggestionForAttribute = (name, prefix) ->
     if prefix.match(/^@/)
@@ -310,10 +313,13 @@

     sortFunctionByArity = (a, b) ->
       return 0 if !isFunc(a) || !isFunc(b)
-      if a.arity > b.arity then 1 else if a.arity < b.arity then -1 else 0
+      return a.arity - b.arity
+
+    sortByOriginalOrder = (a, b) ->
+      return a.index - b.index

     sortFunc = (a, b) ->
-      sortKind(a, b) || sortFunctionByType(a, b) || sortFunctionByName(a, b) || sortFunctionByArity(a, b)
+      sortKind(a, b) || sortFunctionByType(a, b) || sortFunctionByName(a, b) || sortFunctionByArity(a, b) || sortByOriginalOrder(a, b)

     suggestions.sort(sortFunc)


Fix twinkling anonymous proposal author

Fix twinkling anonymous proposal author

--- plugins/resolve/assets/resolve/js/frontend.coffee
+++ plugins/resolve/assets/resolve/js/frontend.coffee
@@ -275,7 +275,7 @@
               data-application='resolve'
               data-entity='#{resolve.model.id}'
               data-subentity='#{rev._id}'
-              data-recipient='#{rev.user_id}'
+              data-recipient='#{rev.user_id or ""}'
               data-url='#{resolve.model.get("url")}'></span>")

       @addView ".proposal .date-auto", new intertwinkles.AutoUpdatingDate(date: rev.date)


GC old views after each render

GC old views after each render

--- src/trix/views/document_view.coffee
+++ src/trix/views/document_view.coffee
@@ -5,13 +5,10 @@
 {defer} = Trix.Helpers

 class Trix.DocumentView extends Trix.ObjectView
-  GC_FREQUENCY = 50
-
   constructor: ->
     super
     @document = @object
     {@element} = @options
-    @renderCount = 0

   render: ->
     @childViews = []
@@ -22,7 +19,6 @@
         blockView = @findOrCreateCachedChildView(Trix.BlockView, block, {blockIndex})
         @element.appendChild(blockView.render())

-    @renderCount++
     @didRender()
     @element

@@ -30,8 +26,7 @@
     @findViewForObject(object)?.reRender()

   didRender: ->
-    if @renderCount % GC_FREQUENCY is 0
-      defer => @garbageCollectCachedViews()
+    defer => @garbageCollectCachedViews()

   focus: ->
     @element.focus()


Rollbar: re-enabling rollbar

Rollbar: re-enabling rollbar

--- client/Main/rollbar.coffee
+++ client/Main/rollbar.coffee
@@ -1,6 +1,5 @@
 # Wrapper for pushing events to Rollbar
 KD.logToExternal = (msg, args) ->
-  return
   return  unless KD.config.logToExternal and _rollbar
   return  if KD.isGuest()

@@ -13,7 +12,6 @@
 # log ping times so we know if failure was due to user's slow
 # internet or our internals timing out
 KD.logToExternalWithTime = (name, options)->
-  return
   KD.troubleshoot (times)->
     KD.logToExternal {
       options


[EVENTRIC-68] Updated docs gulp-task

[EVENTRIC-68] Updated docs gulp-task

- Handles with Jade/SCSS files
- Concats assets
- Handle with bower_components
- Added 'watch' Task
- Added 'build' Task

--- gulp/docs.coffee
+++ gulp/docs.coffee
@@ -1,15 +1,92 @@
 gulp        = require 'gulp'
 Dgeni       = require 'dgeni'
 runSequence = require 'run-sequence'
+coffee      = require 'gulp-coffee'
+jade        = require 'gulp-jade'
+path        = require 'canonical-path'
+webserver   = require 'gulp-webserver'
+gutil       = require 'gulp-util'
+scss        = require 'gulp-sass'
+concat      = require 'gulp-concat'
+

 module.exports = (gulp) ->

-  gulp.task 'docs', (next) ->
-    runSequence 'build', 'docs:generate', next
+  outputFolder = 'build/docs'
+  bowerFolder = 'bower_components'

-  gulp.task 'docs:generate', ->
+  copyComponent = (component, pattern, sourceFolder, packageFile) ->
+    pattern = pattern or "/**/*"
+    sourceFolder = sourceFolder or bowerFolder
+    packageFile = packageFile or "bower.json"
+    version = require(path.resolve(sourceFolder, component, packageFile)).version
+    gulp.src(sourceFolder + "/" + component + pattern).pipe gulp.dest(outputFolder + "/components/" + component + "-" + version)
+
+
+  gulp.task 'docs:build', (next) ->
+    runSequence 'build', 'docs:generate:dgeni', 'docs:generate:jade', 'docs:generate:coffee', 'docs:generate:scss', 'docs:assets', 'docs:webserver:start', next
+
+
+  gulp.task 'docs:watch', (next) ->
+    gulp.run [
+      "docs:build"
+    ]
+
+    gulp.watch [
+      "docs/app/**/*.jade"
+    ], ['docs:generate:jade']
+
+    gulp.watch [
+      "docs/app/**/*.coffee"
+    ], ['docs:generate:coffee']
+
+    gulp.watch [
+      "docs/app/**/*.scss"
+    ], ['docs:generate:scss']
+
+
+  gulp.task 'docs', ->
+    gutil.log "# Documentation Tasks:"
+    gutil.log gutil.colors.cyan("'$ gulp docs:build'"), "build all once"
+    gutil.log gutil.colors.cyan("'$ gulp docs:watch'"), "build all and start a webserver on port 8000 plus watch changing files"
+
+
+  gulp.task 'docs:assets', ->
+    copyComponent 'jquery', '/dist/*'
+    copyComponent 'angularjs', '/*'
+    copyComponent 'bootstrap', '/dist/**/*'
+    copyComponent 'angular-ui-router', '/release/*'
+
+
+  gulp.task 'docs:generate:jade', ->
+    gulp.src(['docs/app/**/*.jade'])
+      .pipe(jade())
+      .pipe(gulp.dest('build/docs'))
+
+
+  gulp.task 'docs:generate:coffee', ->
+    gulp.src(['docs/app/src/**/*.coffee'])
+      .pipe(coffee({bare: true}))
+      .pipe(concat('application.js'))
+      .pipe(gulp.dest('build/docs/scripts'))
+
+
+  gulp.task 'docs:generate:scss', ->
+    gulp.src(['docs/app/src/**/*.scss'])
+      .pipe(scss())
+      .pipe(concat('application.css'))
+      .pipe(gulp.dest('build/docs/styles'))
+
+
+  gulp.task 'docs:generate:dgeni', ->
     try
-      dgeni = new Dgeni([require('../docs/dgeni.conf')]);
+      dgeni = new Dgeni [require('../docs/config')]
       dgeni.generate();
     catch e
       throw e
+
+
+  gulp.task 'docs:webserver:start', ->
+    gulp.src("build/docs").pipe webserver
+      livereload: true
+    return


sort driver lines by (case-sensitive) name

--- client/driver_lines.coffee
+++ client/driver_lines.coffee
@@ -1,4 +1,4 @@
-driver_lines_sort = {'_id':1}
+driver_lines_sort = {'name':1}

 # ---- Template.driver_line_from_id_block -------------


fixed missing key error being displayed in t9n

--- client/i18n/english.coffee
+++ client/i18n/english.coffee
@@ -26,6 +26,8 @@
   with: "with"
   createAccount: "Create an Account"
   and: "and"
+  "Match failed":  "Match failed"
+  "User not found":  "User not found"

   error:
     minChar: "7 character minimum password."


Fix generator error seen in logs - #140

Fix generator error seen in logs - #140

--- src/middleware/rerunUpdateTransactionTask.coffee
+++ src/middleware/rerunUpdateTransactionTask.coffee
@@ -44,6 +44,5 @@

 	var updateTask = Q.denodeify(exports.updateTask);
 	yield updateTask(this);
-	yield next;

 }`


Added custom metadata

Added custom metadata

--- lib/utils.coffee
+++ lib/utils.coffee
@@ -178,6 +178,7 @@
       ,'email' : 'EmailTemplate'
       ,'globalValueSets' : 'GlobalValueSet'
       ,'globalValueSetTranslations' : 'GlobalValueSetTranslation'
+      ,'customMetadata' : 'CustomMetadata'
     }
     result = null
     if folderMapping.hasOwnProperty folderName


Fix the problem regarding to startup

Fix the problem regarding to startup

--- lib/utils.coffee
+++ lib/utils.coffee
@@ -111,8 +111,11 @@
     status

   @isTokamakProject: ->
-    proj_root = if atom.project.rootDirectories[0]? then atom.project.rootDirectories[0] else "/"
-    config_file = path.join(proj_root.path, 'tokamak.toml')
+    proj_path = "/"
+    if atom.project.getPaths()? && atom.project.getPaths() != []
+      proj_path = atom.project.getPaths()[0]
+    config_file = path.join(proj_path, 'tokamak.toml')
+    console.log(config_file)
     fs.existsSync(config_file)

   @parseTokamakConfig: ->


:bug: :tada: #95 finally track-down the cause and fixed it for the issue editor get blank on split after narrow:close

--- lib/utils.coffee
+++ lib/utils.coffee
@@ -44,11 +44,23 @@
     pane.activate()
     pane.activateItem(editor)

-    unless editor.getCursorBufferPosition().isEqual(cursorPosition)
-      editor.setCursorBufferPosition(cursorPosition)
-    for row in foldStartRows.reverse() when not editor.isFoldedAtBufferRow(row)
-      editor.foldBufferRow(row)
-    editor.element.setScrollTop(scrollTop)
+    restoreCursorAndScrollTop = ->
+      unless editor.getCursorBufferPosition().isEqual(cursorPosition)
+        editor.setCursorBufferPosition(cursorPosition)
+      for row in foldStartRows.reverse() when not editor.isFoldedAtBufferRow(row)
+        editor.foldBufferRow(row)
+      editor.element.setScrollTop(scrollTop)
+
+    # [BUG?] atom-narrow#95
+    # When editor is not active on narrow:close, immediate scrollTop setting get editor content blank.
+    # See detailed condition this happens in atom-narrow#95 for detail.
+    # In this state, component.getScrollTop() returns `undefined`, so checking this valueand delay if necessary.
+    unless editorElement.component.getScrollTop()?
+      disposable = editorElement.onDidChangeScrollTop ->
+        disposable.dispose()
+        restoreCursorAndScrollTop()
+    else
+      restoreCursorAndScrollTop()

 requireFrom = (pack, path) ->
   packPath = atom.packages.resolvePackagePath(pack)


:art: simpler root node return

:art: simpler root node return

--- src/observables/vdom-tree.coffee
+++ src/observables/vdom-tree.coffee
@@ -58,25 +58,20 @@
     begin + ((bodyHeight / rowHeight) | 0) + 2 # add to avoid visible gap when scrolling
   , visibleBeginProp, bodyHeightProp, rowHeightProp

-  return Bacon.combineWith (data, _) ->
-      renderRoot(data, columns, buses)
-    , Bacon.combineTemplate {
-      selectedItem: selectedItemProp
-      bodyHeight: bodyHeightProp
-      rowHeight: rowHeightProp
-      scrollTop: scrollTopProp
-
-      topOffset: Bacon.combineWith (scrollTop, rowHeight) ->
+  return Bacon.combineTemplate(
+    selectedItem: selectedItemProp
+    bodyHeight: bodyHeightProp
+    scrollTop: scrollTopProp
+    topOffset: Bacon.combineWith (scrollTop, rowHeight) ->
         -(scrollTop % rowHeight)
       , scrollTopProp, rowHeightProp
-
-      marginBottom: Bacon.combineWith (items, rowHeight, scrollTop, bodyHeight) ->
+    marginBottom: Bacon.combineWith (items, rowHeight, scrollTop, bodyHeight) ->
         items.length * rowHeight - scrollTop - bodyHeight
       , matchedItemsProp, rowHeightProp, scrollTopProp, bodyHeightProp
-
-      reverseStripes: visibleBeginProp.map (begin) -> begin % 2 is 0
-
-      items: Bacon.combineWith (items, begin, end) ->
+    reverseStripes: visibleBeginProp.map (begin) ->
+      begin % 2 is 0
+    items: Bacon.combineWith (items, begin, end) ->
         items.slice(begin, end)
       , matchedItemsProp, visibleBeginProp, visibleEndProp
-    }#, Bacon.interval(1000, undefined)
+  ).map (data) ->
+    renderRoot(data, columns, buses)


Simplified hook structure a bit

Simplified hook structure a bit

--- core/app/backbone/modules/discussion_modal_on_frontend.coffee
+++ core/app/backbone/modules/discussion_modal_on_frontend.coffee
@@ -27,12 +27,9 @@
     if !openingModalPage(fragment) && modalCurrentlyOpened()
       DiscussionModalOnFrontend.closeDiscussion()

-      already_on_the_background_page = (fragment == background_page_url)
-      setBackgroundPageUrlHook fragment
-
-      !already_on_the_background_page
+      fragment != background_page_url
     else
-      setBackgroundPageUrlHook fragment
+      true

   DiscussionModalOnFrontend.initializer = ->
     background_page_url = Backbone.history.getFragment currentUser.streamLink()
@@ -40,8 +37,9 @@
     FactlinkApp.vent.on 'close_discussion_modal', ->
       Backbone.history.navigate background_page_url, true

+    addBackboneHistoryNavigateHook setBackgroundPageUrlHook
+    addBackboneHistoryLoadUrlHook setBackgroundPageUrlHook
     addBackboneHistoryLoadUrlHook abortIfAlreadyOnBackgroundPageHook
-    addBackboneHistoryNavigateHook setBackgroundPageUrlHook

   DiscussionModalOnFrontend.openDiscussion = (fact) ->
     Backbone.history.navigate fact.get('url'), false


:fire: Remove bottom-tab-project-old

:fire: Remove bottom-tab-project-old

--- lib/views/bottom-tab-project-old.coffee
+++ lib/views/bottom-tab-project-old.coffee
@@ -1,13 +0,0 @@
-BottomTab = require './bottom-tab'
-
-class BottomTabProject extends BottomTab
-  initialize: (@linter) ->
-    BottomTab.prototype.initialize.call(this, "Project") # CoffeeScript's way of extending stuff is BAD
-  onClick: ->
-    @linter.views.scope = 'project'
-    @linter.views.bottomTabFile.active = false
-    @active = true
-    @linter.views.panel.render(@linter.views.messages)
-
-module.exports = BottomTabProject = document.registerElement('linter-bottom-tab-project',
-  {prototype: BottomTabProject.prototype})


Create both views in one shot

Create both views in one shot

Now cmd-g works when the find-in-project view is up, so the find-view
must be up as well

--- lib/find.coffee
+++ lib/find.coffee
@@ -20,12 +20,12 @@
     @subscriber = new Subscriber()

     @subscriber.subscribeToCommand atom.workspaceView, 'project-find:show', =>
-      @createProjectFindView()
+      @createViews()
       @findView?.detach()
       @projectFindView.attach()

     @subscriber.subscribeToCommand atom.workspaceView, 'project-find:toggle', =>
-      @createProjectFindView()
+      @createViews()
       @findView?.detach()

       if @projectFindView.hasParent()
@@ -34,7 +34,7 @@
         @projectFindView.attach()

     @subscriber.subscribeToCommand atom.workspaceView, 'project-find:show-in-current-directory', (e) =>
-      @createProjectFindView()
+      @createViews()
       @findView?.detach()
       @projectFindView.attach()
       @projectFindView.findInCurrentlySelectedDirectory($(e.target))
@@ -42,12 +42,12 @@
     @subscriber.subscribeToCommand atom.workspaceView, 'find-and-replace:use-selection-as-find-pattern', =>
       return if @projectFindView?.isOnDom() or @findView?.isOnDom()

-      @createFindView()
+      @createViews()
       @projectFindView?.detach()
       @findView.showFind()

     @subscriber.subscribeToCommand atom.workspaceView, 'find-and-replace:toggle', =>
-      @createFindView()
+      @createViews()
       @projectFindView?.detach()

       if @findView.hasParent()
@@ -56,12 +56,12 @@
         @findView.showFind()

     @subscriber.subscribeToCommand atom.workspaceView, 'find-and-replace:show', =>
-      @createFindView()
+      @createViews()
       @projectFindView?.detach()
       @findView.showFind()

     @subscriber.subscribeToCommand atom.workspaceView, 'find-and-replace:show-replace', =>
-      @createFindView()
+      @createViews()
       @projectFindView?.detach()
       @findView.showReplace()

@@ -81,9 +81,11 @@
       editorView.command 'find-and-replace:select-all', ->
         selectNext.findAndSelectAll()

-  createProjectFindView: ->
+  createViews: ->
+    @findView ?= new FindView(@findModel, _.extend({}, @viewState, {@findHistory, @replaceHistory}))
+
     @resultsModel ?= new ResultsModel(@resultsModelState)
-    @projectFindView ?= new ProjectFindView(@resultsModel, @projectViewState)
+    @projectFindView ?= new ProjectFindView(@findModel, @resultsModel, _.extend({}, @projectViewState, {@findHistory, @replaceHistory, @pathsHistory}))

     # HACK: Soooo, we need to get the model to the pane view whenever it is
     # created. Creation could come from the opener below, or, more problematic,
@@ -97,10 +99,6 @@
     #
     # See https://github.com/atom/find-and-replace/issues/63
     ResultsPaneView.model = @resultsModel
-
-  createFindView: ->
-    @findModel ?= new FindModel(@modelState)
-    @findView ?= new FindView(@findModel, @viewState)

   deactivate: ->
     @findView?.remove()


:art: Reorganize the initial model creation a bit

--- lib/find.coffee
+++ lib/find.coffee
@@ -34,19 +34,19 @@
       new ResultsPaneView() if filePath is ResultsPaneView.URI

     @subscriptions = new CompositeDisposable
+    @findHistory = new History(findHistory)
+    @replaceHistory = new History(replaceHistory)
+    @pathsHistory = new History(pathsHistory)

     @findOptions = new FindOptions(@modelState)
     @findModel = new BufferSearch(@findOptions)
+    @resultsModel = new ResultsModel(@findOptions)
+
     @subscriptions.add atom.workspace.observeActivePaneItem (paneItem) =>
       if paneItem?.getBuffer?()
         @findModel.setEditor(paneItem)
       else
         @findModel.setEditor(null)
-
-    @resultsModel = new ResultsModel(@resultsModelState)
-    @findHistory = new History(findHistory)
-    @replaceHistory = new History(replaceHistory)
-    @pathsHistory = new History(pathsHistory)

     @subscriptions.add atom.commands.add '.find-and-replace, .project-find', 'window:focus-next-pane', =>
       atom.views.getView(atom.workspace).focus()


Maneuvers modified by other cards (e.g. R2) are highlighted gold.

--- coffeescripts/xwing.coffee
+++ coffeescripts/xwing.coffee
@@ -842,7 +842,7 @@
         ({ id: title.id, text: "#{title.name} (#{title.points})", points: title.points } for title in unclaimed_titles).sort exportObj.sortHelper

     # Converts a maneuver table for into an HTML table.
-    getManeuverTableHTML: (maneuvers) ->
+    getManeuverTableHTML: (maneuvers, baseManeuvers) ->

         if not maneuvers?
           return "Missing maneuver info."
@@ -975,9 +975,13 @@
                                 arrowEndx = 60
                                 arrowEndy = 180

+                        outlineColor = "black"
+                        if maneuvers[speed][turn] != baseManeuvers[speed][turn]
+                          outlineColor = "gold" # highlight manuevers modified by another card (e.g. R2 Astromech makes all 1 & 2 speed maneuvers green)
+
                         outTable += $.trim """
-                          <path d='M#{arrowStartx},#{arrowStarty} #{arrowMove} L#{arrowEndx},#{arrowEndy} Z' fill='#{color}' #{transform} stroke-width='5' stroke='black' />
-                          <path stroke-width='25' fill='none' stroke='black' d='M#{startx},#{starty} #{turnType}#{smoothx},#{smoothy} #{extraPoint}#{endx},#{endy}' />
+                          <path d='M#{arrowStartx},#{arrowStarty} #{arrowMove} L#{arrowEndx},#{arrowEndy} Z' fill='#{color}' #{transform} stroke-width='5' stroke='#{outlineColor}' />
+                          <path stroke-width='25' fill='none' stroke='#{outlineColor}' d='M#{startx},#{starty} #{turnType}#{smoothx},#{smoothy} #{extraPoint}#{endx},#{endy}' />
                           <path stroke-width='15' fill='none' stroke='#{color}' d='M#{startx},#{starty} #{turnType}#{smoothx},#{smoothy} #{extraPoint}#{endx},#{endy}' />
                         """
                     outTable += "</svg>"
@@ -1016,7 +1020,7 @@
                     @info_container.find('tr.info-upgrades').show()
                     @info_container.find('tr.info-upgrades td.info-data').text((exportObj.translate(@language, 'slot', slot) for slot in data.pilot.slots).join(', ') or 'None')
                     @info_container.find('tr.info-maneuvers').show()
-                    @info_container.find('tr.info-maneuvers td.info-data').html(@getManeuverTableHTML(effective_stats.maneuvers))
+                    @info_container.find('tr.info-maneuvers td.info-data').html(@getManeuverTableHTML(effective_stats.maneuvers, data.data.maneuvers))
                 when 'Pilot'
                     @info_container.find('.info-sources').text (exportObj.translate(@language, 'sources', source) for source in data.sources).sort().join(', ')
                     @info_container.find('.info-name').html """#{if data.unique then "· " else ""}#{data.name}"""
@@ -1042,7 +1046,7 @@
                     @info_container.find('tr.info-upgrades').show()
                     @info_container.find('tr.info-upgrades td.info-data').text((exportObj.translate(@language, 'slot', slot) for slot in data.slots).join(', ') or 'None')
                     @info_container.find('tr.info-maneuvers').show()
-                    @info_container.find('tr.info-maneuvers td.info-data').html(@getManeuverTableHTML(ship.maneuvers))
+                    @info_container.find('tr.info-maneuvers td.info-data').html(@getManeuverTableHTML(ship.maneuvers, ship.maneuvers))
                 when 'Addon'
                     @info_container.find('.info-sources').text (exportObj.translate(@language, 'sources', source) for source in data.sources).sort().join(', ')
                     @info_container.find('.info-name').html """#{if data.unique then "· " else ""}#{data.name}"""


Replaced arrowhead using marker-end with a plain path fill.

The marker-end id business wasn't playing well when switching to empire.
I'm not really sure why, but it seems like more of a headache than it's
worth when I can just draw a triangle and transform it if necessary.

--- coffeescripts/xwing.coffee
+++ coffeescripts/xwing.coffee
@@ -847,25 +847,7 @@
         if not maneuvers?
           return "Missing maneuver info."

-        # first define the 3 arrowhead colors - these are global to all svg elements, so no need to redefine them each time an arrow is drawn
-        # however, there's no way to fill with the line color, so need one marker for each color
-        outTable = ""
-        outTable += $.trim '''
-          <svg width="0" height="0" xmlns="http://www.w3.org/2000/svg">
-            <defs>
-              <marker id='redarrowhead' orient='auto' markerWidth='4' markerHeight='4' refX='0.1' refY='2'>
-                <path d='M0,0 V4 L4,2 Z' fill='red' />
-              </marker>
-              <marker id='greenarrowhead' orient='auto' markerWidth='4' markerHeight='4' refX='0.1' refY='2'>
-                <path d='M0,0 V4 L4,2 Z' fill='green' />
-              </marker>
-              <marker id='whitearrowhead' orient='auto' markerWidth='4' markerHeight='4' refX='0.1' refY='2'>
-                <path d='M0,0 V4 L4,2 Z' fill='white' />
-              </marker>
-            </defs>
-          </svg>
-

-        '''
+        outTable = "

"

         viewWidth = 200
         viewHeight = 200
@@ -896,6 +878,12 @@
                     turnType = "S" # S = smooth, L = line, C = cubic bezier (for k-turn)
                     extraPoint = "" # extra inflection point for cubic bezier

+                    arrowStartx = 70
+                    arrowStarty = 80
+                    arrowMove = "H130"
+                    arrowEndx = 100
+                    arrowEndy = 30
+                    transform = ""

                     switch turn
                         when 0
@@ -906,6 +894,13 @@
                             endx = 80
                             endy = 70
                             turnType = "L"
+
+                            arrowStartx = 80
+                            arrowStarty = 100
+                            arrowMove = "V40"
+                            arrowEndx = 30
+                            arrowEndy = 70
+
                         when 1
                             # bank left
                             startx = 150
@@ -913,7 +908,16 @@
                             smoothy = 120
                             endx = 80
                             endy = 60
+
+                            arrowStartx = 80
+                            arrowStarty = 100
+                            arrowMove = "V40"
+                            arrowEndx = 30
+                            arrowEndy = 70
+                            transform = "transform='translate(-5 -15) rotate(45 70 90)' "
+
                         # when 2 # straight - this is the default value, don't need to do anything
+
                         when 3
                             # bank right
                             startx = 50
@@ -921,6 +925,14 @@
                             smoothy = 120
                             endx = 120
                             endy = 60
+
+                            arrowStartx = 120
+                            arrowStarty = 100
+                            arrowMove = "V40"
+                            arrowEndx = 170
+                            arrowEndy = 70
+                            transform = "transform='translate(5 -15) rotate(-45 130 90)' "
+
                         when 4
                             # turn right
                             startx = 40
@@ -929,6 +941,13 @@
                             endx = 120
                             endy = 70
                             turnType = "L"
+
+                            arrowStartx = 120
+                            arrowStarty = 100
+                            arrowMove = "V40"
+                            arrowEndx = 170
+                            arrowEndy = 70
+
                         when 5
                             # k-turn/u-turn
                             startx = 150
@@ -939,6 +958,13 @@
                             turnType = "C"
                             extraPoint = "60,-10 "

+                            arrowStartx = 30
+                            arrowStarty = 120
+                            arrowMove = "H90"
+                            arrowEndx = 60
+                            arrowEndy = 180
+
+
                     color = switch maneuvers[speed][turn]
                         when 1 then "white"
                         when 2 then "green"
@@ -946,7 +972,8 @@

                     outTable += $.trim """
                       <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 #{viewWidth} #{viewHeight}">
-                        <path marker-end='url(##{color}arrowhead)' stroke-width='15' fill='none' stroke='#{color}' d='M#{startx},#{starty} #{turnType}#{smoothx},#{smoothy} #{extraPoint}#{endx},#{endy}' />
+                        <path d='M#{arrowStartx},#{arrowStarty} #{arrowMove} L#{arrowEndx},#{arrowEndy} Z' fill='#{color}' #{transform}/>
+                        <path stroke-width='15' fill='none' stroke='#{color}' d='M#{startx},#{starty} #{turnType}#{smoothx},#{smoothy} #{extraPoint}#{endx},#{endy}' />
                       </svg>
                     """
                 outTable += "	"


Fixes #6 - JSON escaping in resource.curl

--- src/client.coffee
+++ src/client.coffee
@@ -212,7 +212,7 @@
         command.push "  -H '#{header}: #{value}'"

       if body?
-        command.push "  -d '#{JSON.stringify(body)}'"
+        command.push "  -d #{JSON.stringify(body)}"
       command.push "  #{url}"
       command.join(" \\\n")


Replace initialLoad call with me/preferences/teams

--- src/client.coffee
+++ src/client.coffee
@@ -60,16 +60,45 @@
                 @logger.info 'Websocket URL: ' + @socketUrl
                 @self = new User data
                 @emit 'loggedIn', @self
-                @getInitialLoad()
+                @getMe()
+                @getPreferences()
+                @getTeams()
         else
             @emit 'error', data
             @authenticated = false
             @reconnect()

-    _onInitialLoad: (data, headers) =>
-        if data && not data.error
-            @teams = data.teams
-
+    _onProfiles: (data, headers) =>
+        if data && not data.error
+            @users = data
+            @logger.debug 'Found '+Object.keys(@users).length+' profiles.'
+            @emit 'profilesLoaded', { profiles: @users }
+        else
+            @logger.error 'Failed to load profiles from server.'
+            @emit 'error', { msg: 'failed to load profiles'}
+
+    _onChannels: (data, headers) =>
+        if data && not data.error
+            @channels = data
+            @emit 'channelsLoaded', { channels: @channels }
+        else
+            @logger.error 'Failed to get subscribed channels list from server.'
+            @emit 'error', { msg: 'failed to get channel list'}
+
+    _onPreferences: (data, headers) =>
+        if data && not data.error
+            @preferences = data
+            @emit 'preferencesLoaded', { preferences: @preferences }
+
+    _onMe: (data, headers) =>
+        if data && not data.error
+            @me = data
+            @emit 'meLoaded', { me: @me }
+
+    _onTeams: (data, headers) =>
+        if data && not data.error
+            @teams = data
+            @emit 'teamsLoaded', { teams: @teams }
             @logger.debug 'Found '+Object.keys(@teams).length+' teams.'
             for t in @teams
                 @logger.debug "Testing #{t.name} == #{@group}"
@@ -77,36 +106,23 @@
                     @logger.debug "Found team! #{t.id}"
                     @teamID = t.id
                     break
-            @preferences = data.preferences
-            @config = data.client_cfg
-            @loadUsersList()
-            @connect()
-        else
-            @logger.error 'Failed to load teams from server.'
-            @emit 'error', { msg: 'failed to load teams.' }
-
-    _onProfiles: (data, headers) =>
-        if data && not data.error
-            @users = data
-            @logger.debug 'Found '+Object.keys(@users).length+' profiles.'
-            @emit 'profilesLoaded', { profiles: @users }
-        else
-            @logger.error 'Failed to load profiles from server.'
-            @emit 'error', { msg: 'failed to load profiles'}
-
-    _onChannels: (data, headers) =>
-        if data && not data.error
-            @channels = data
-            @emit 'channelsLoaded', { channels: @channels }
-        else
-            @logger.error 'Failed to get subscribed channels list from server.'
-            @emit 'error', { msg: 'failed to get channel list'}
+            @loadUsersList() # FIXME
+            @connect() # FIXME

     channelRoute: (channelId) ->
         @teamRoute() + '/channels/' + channelId

     teamRoute: ->
         teamsRoute + '/' + @teamID
+
+    getMe: ->
+        @_apiCall 'GET', usersRoute + '/me', null, @_onMe
+
+    getPreferences: ->
+        @_apiCall 'GET', usersRoute + '/me/preferences', null, @_onPreferences
+
+    getTeams: ->
+        @_apiCall 'GET', usersRoute + '/me/teams', null, @_onTeams

     getInitialLoad: ->
         @_apiCall 'GET', usersRoute + '/initial_load', null, @_onInitialLoad


add new efs resources

add new efs resources

--- src/coffin.coffee
+++ src/coffin.coffee
@@ -70,6 +70,9 @@
         Environment: null
       ElasticLoadBalancing:
         LoadBalancer: null
+      EFS:
+        FileSystem: null
+        MountTarget: null
       IAM:
         AccessKey: null
         Group: null


[Cartilage.Views.PopoverView] Dismiss popover views on document clicks.

--- app/assets/javascripts/cartilage/views/popover_view.js.coffee
+++ app/assets/javascripts/cartilage/views/popover_view.js.coffee
@@ -123,6 +123,8 @@
     ($ @el).hide().css { visibility: "visible" }
     ($ @el).fadeIn()

+    ($ document).unbind("click", @handleDocumentClickEvent).click @handleDocumentClickEvent
+
   resizeToFitContents: ->
     element = ($ @contentElement).children()[0]
     width = ($ element).outerWidth()
@@ -137,6 +139,15 @@
       width: "#{width}px",
       height: "#{height}px"
     }
+
+  handleDocumentClickEvent: (event) =>
+
+    return if event.target == @attachedElement
+
+    ($ document).unbind("click", @handleDocumentClickEvent)
+
+    ($ @el).fadeOut "fast", =>
+      ($ @el).remove()

 #   // Event Handlers ----------------------------------------------------------
 #


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color2.coffee
+++ src/color2.coffee
@@ -152,6 +152,14 @@

     lighten: (amount) ->
       shiftLightness(amount, this)
+
+    mix: (other, amount) ->
+      amount ||= 0.5
+
+      new_colors = [@r, @g, @b, @a].zip([other.r, other.g, other.b, other.a]).map (array) ->
+        (array[0] * amount) + (array[1] * (1 - amount))
+
+      return Color2(new_colors)

     saturate: (amount) ->
       hsl = @toHsl()


DockController: Instead returning back to /Activity all the time after a dock item removed from dock, instead try to go back first ( there may be better ways to do that with router )

--- client/Main/maincontroller/dockcontroller.coffee
+++ client/Main/maincontroller/dockcontroller.coffee
@@ -109,7 +109,9 @@
     appManager.quitByName item.name

     if (item.hasClass 'running') and (item.hasClass 'selected')
-    then router.handleRoute '/Activity'
+      if router.visitedRoutes.length > 1
+      then router.back()
+      else router.handleRoute '/Activity'

     @navController.removeItem item
     @saveItemOrders()


[Email Confirmation] Add proper event bindings and notification modals for EmailShouldBeConfirmed event

--- client/app/MainApp/notificationcontroller.coffee
+++ client/app/MainApp/notificationcontroller.coffee
@@ -37,30 +37,27 @@

     @on 'GuestTimePeriodHasEnded', ()->
       # todo add a notification to user
+      deleteUserCookie()
+
+    deleteUserCookie = ->
       $.cookie 'clientId', erase: yes

-    @on 'EmailShouldBeConfirmed', ()->
-      modal = new KDModalView
-        title         : "Email Confirmation needed."
-        overlay       : yes
-        content       :
-          """
-          <div class="modalformline">
-          You should confirm your email address to continue.
-          </div>
-          """
-        buttons       :
-          "Ok"        :
-            style     : "modal-clean-gray"
-            callback  : (event) ->
-              modal.destroy()
-
-      modal.on "KDObjectWillBeDestroyed", ->
-        $.cookie 'clientId', erase: yes
+    displayEmailConfirmedNotification = (modal)->
+      modal.off "KDObjectWillBeDestroyed"
+      new KDNotificationView
+        title   : "Thanks for confirming your e-mail address"
+        duration: 5000
+      modal.destroy()
+
+    @once 'EmailShouldBeConfirmed', ()->
+      {firstName, nickname} = KD.whoami().profile
+      modal = KD.getSingleton("mainController").displayConfirmEmailModal(firstName, nickname)
+      @once 'EmailConfirmed', displayEmailConfirmedNotification.bind this, modal
+      modal.on "KDObjectWillBeDestroyed", deleteUserCookie.bind this

     @on 'UsernameChanged', ({username, oldUsername}) ->
       # FIXME: because of this (https://app.asana.com/0/search/6604719544802/6432131515387)
-      $.cookie 'clientId', erase: yes
+      deleteUserCookie()

       new KDModalView
         title         : "Your username was changed"


Update documentation.

Update documentation.

--- spec/compiler.coffee
+++ spec/compiler.coffee
@@ -30,7 +30,7 @@
 # @param message [String] This should be provided when a rule exists to catch
 # invalid syntax, and omitted when an error is expected to be thrown by the PEG
 # parser.
-# @param [Boolean] Whether the spec should be treated as pending.
+# @param pending [Boolean] Whether the spec should be treated as pending.
 #
 expectError = (source, message, pending) ->
   itFn = if pending then xit else it


Test the empty conditions.

Test the empty conditions.

--- spec/git-spec.coffee
+++ spec/git-spec.coffee
@@ -471,6 +471,21 @@
         expect(_.keys(statuses).length).toBe 1
         expect(statuses['secret-stuff/b.txt']).toBe 1 << 7

+    describe 'when no path is specified', ->
+      it 'returns an empty object', ->
+        statuses = repo.getStatusForPaths()
+        expect(_.keys(statuses).length).toBe 0
+
+    describe 'when an empty array is specified', ->
+      it 'returns an empty object', ->
+        statuses = repo.getStatusForPaths([])
+        expect(_.keys(statuses).length).toBe 0
+
+    describe 'when an empty string is specified', ->
+      it 'returns an empty object', ->
+        statuses = repo.getStatusForPaths([''])
+        expect(_.keys(statuses).length).toBe 0
+
   describe '.getAheadBehindCount()', ->
     beforeEach ->
       repoDirectory = temp.mkdirSync('node-git-repo-')


adding a route

adding a route

--- src/controllers/Api-File.coffee
+++ src/controllers/Api-File.coffee
@@ -13,6 +13,7 @@

   add_Routes: ()=>
     @.router.get  '/file/list', @.list
+    @.router.get  '/file/list/AAAAAA', @.list
     @.router.get  '/file/get/:filename' , @.get
     @.router.post '/file/save/:filename', @.save
     @


SocialWorker: use PERMISSON_EDIT_GROUPS permissionset

--- workers/social/lib/social/models/apitoken.coffee
+++ workers/social/lib/social/models/apitoken.coffee
@@ -12,18 +12,26 @@

 module.exports = class JApiToken extends jraphical.Module

+  Validators = require './group/validators'
+
+  PERMISSION_EDIT_GROUPS = [
+    { permission: 'edit groups',     superadmin: yes }
+    { permission: 'edit own groups', validateWith: Validators.group.admin }
+  ]
+
+
   @share()

   @set
-    sharedEvents     :
-      static         : []
-      instance       : []
-    permissions      :
-      'create token' : ['admin']
-      'remove token' : ['admin']
-    indexes          :
-      code           : 'unique'
-    sharedMethods    :
+    sharedEvents       :
+      static           : []
+      instance         : []
+    permissions        :
+      'edit groups'    : ['moderator']
+      'edit own groups': ['member', 'moderator']
+    indexes            :
+      code             : 'unique'
+    sharedMethods      :
       static:
         create: [
           (signature Function)
@@ -32,23 +40,23 @@
       instance:
         remove:
           (signature Function)
-    schema           :
-      code           :
-        type         : String
-        required     : yes
-        default      : hat
-      group          :
-        type         : String
-        required     : yes
-      originId       :
-        type         : ObjectId
-        required     : yes
-      createdAt      :
-        type         : Date
-        default      : -> new Date
-      modifiedAt     :
-        type         : Date
-        default      : -> new Date
+    schema             :
+      code             :
+        type           : String
+        required       : yes
+        default        : hat
+      group            :
+        type           : String
+        required       : yes
+      originId         :
+        type           : ObjectId
+        required       : yes
+      createdAt        :
+        type           : Date
+        default        : -> new Date
+      modifiedAt       :
+        type           : Date
+        default        : -> new Date

   @create: (data, callback) ->
@@ -90,8 +98,8 @@
     daisy queue

-  @create$: permit 'create token',
-
+  @create$: permit
+    advanced: PERMISSION_EDIT_GROUPS
     success: (client, callback) ->

       group    = client?.context?.group
@@ -104,8 +112,8 @@
       JApiToken.create data, callback

-  remove$: permit 'remove token',
-
+  remove$: permit
+    advanced: PERMISSION_EDIT_GROUPS
     success: (client, callback) ->
       @remove callback


Make warn messages way better.

Make warn messages way better.

--- src/config.coffee
+++ src/config.coffee
@@ -38,7 +38,7 @@
       for name, validatorFunction of functions
         @addSchemaValidator(typeName, validatorFunction)

-  @executeSchemaValidators: (value, schema) ->
+  @executeSchemaValidators: (keyPath, value, schema) ->
     error = null
     types = schema.type
     types = [types] unless Array.isArray(types)
@@ -47,7 +47,7 @@
         if filterFunctions = @schemaValidators[type]
           filterFunctions = filterFunctions.concat(@schemaValidators['*'])
           for filter in filterFunctions
-            value = filter.call(this, value, schema)
+            value = filter.call(this, keyPath, value, schema)
           error = null
           break
       catch e
@@ -378,7 +378,7 @@
       defaults

   scrubValue: (keyPath, value) ->
-    value = @constructor.executeSchemaValidators(value, schema) if schema = @getSchema(keyPath)
+    value = @constructor.executeSchemaValidators(keyPath, value, schema) if schema = @getSchema(keyPath)
     value

 # Base schema validators. These will coerce raw input into the specified type,
@@ -391,19 +391,19 @@
 # specification.
 Config.addSchemaValidators
   'integer':
-    coercion: (value, schema) ->
+    coercion: (keyPath, value, schema) ->
       value = parseInt(value)
-      throw new Error('Value cannot be coerced into an int') if isNaN(value)
+      throw new Error("Cannot set #{keyPath}, #{JSON.stringify(value)} cannot be coerced into an int") if isNaN(value)
       value

   'number':
-    coercion: (value, schema) ->
+    coercion: (keyPath, value, schema) ->
       value = parseFloat(value)
-      throw new Error('Value cannot be coerced into a number') if isNaN(value)
+      throw new Error("Cannot set #{keyPath}, #{JSON.stringify(value)} cannot be coerced into a number") if isNaN(value)
       value

   'boolean':
-    coercion: (value, schema) ->
+    coercion: (keyPath, value, schema) ->
       switch typeof value
         when 'string'
           value.toLowerCase() in ['true', 't']
@@ -411,46 +411,47 @@
           !!value

   'string':
-    coercion: (value, schema) ->
-      throw new Error('Value must be a string') if typeof value isnt 'string'
+    coercion: (keyPath, value, schema) ->
+      throw new Error("Cannot set #{keyPath}, #{JSON.stringify(value)} must be a string") if typeof value isnt 'string'
       value

   'null':
     # null sort of isnt supported. It will just unset in this case
-    coercion: (value, schema) ->
-      throw new Error('Value must be an object') unless value == null
+    coercion: (keyPath, value, schema) ->
+      throw new Error("Cannot set #{keyPath}, #{JSON.stringify(value)} must be null") unless value == null
       value

   'object':
-    coercion: (value, schema) ->
-      throw new Error('Value must be an object') if typeof value isnt 'object'
+    coercion: (keyPath, value, schema) ->
+      throw new Error("Cannot set #{keyPath}, #{JSON.stringify(value)} must be an object") unless isPlainObject(value)
       return value unless schema.properties?
+
       newValue = {}
       for prop, childSchema of schema.properties
-        continue unless prop of value
+        continue unless value.hasOwnProperty(prop)
         try
-          newValue[prop] = @executeSchemaValidators(value[prop], childSchema)
+          newValue[prop] = @executeSchemaValidators("#{keyPath}.#{prop}", value[prop], childSchema)
         catch error
-          console.warn "Error setting value #{error.message}"
+          console.warn "Error setting item in object: #{error.message}"
       newValue

   'array':
-    coercion: (value, schema) ->
-      throw new Error('Value must be an array') unless Array.isArray(value)
+    coercion: (keyPath, value, schema) ->
+      throw new Error("Cannot set #{keyPath}, #{JSON.stringify(value)} must be an array") unless Array.isArray(value)
       itemSchema = schema.items
       if itemSchema?
         newValue = []
         for item in value
           try
-            newValue.push @executeSchemaValidators(item, itemSchema)
+            newValue.push @executeSchemaValidators(keyPath, item, itemSchema)
           catch error
-            console.warn "Error setting value: #{error.message}"
+            console.warn "Error setting item in array: #{error.message}"
         newValue
       else
         value

   '*':
-    minimumAndMaximumCoercion: (value, schema) ->
+    minimumAndMaximumCoercion: (keyPath, value, schema) ->
       return value unless typeof value is 'number'
       if schema.minimum? and typeof schema.minimum is 'number'
         value = Math.max(value, schema.minimum)
@@ -458,7 +459,7 @@
         value = Math.min(value, schema.maximum)
       value

-    enumValidation: (value, schema) ->
+    enumValidation: (keyPath, value, schema) ->
       possibleValues = schema.enum
       return value unless possibleValues? and Array.isArray(possibleValues) and possibleValues.length

@@ -466,7 +467,7 @@
         # Using `isEqual` for possibility of placing enums on array and object schemas
         return value if _.isEqual(possibleValue, value)

-      throw new Error('Value is not one of the possible values')
+      throw new Error("Cannot set #{keyPath}, #{JSON.stringify(value)} is not one of #{JSON.stringify(possibleValues)}")

 isPlainObject = (value) ->
   _.isObject(value) and not _.isArray(value) and not _.isFunction(value) and not _.isString(value)


New download URL

New download URL

--- src/config.coffee
+++ src/config.coffee
@@ -26,7 +26,7 @@
 config.timeout = 10000 # ms before giving up and failing the test
 config.callbackURL = "http://127.0.0.1:#{config.httpPort}/callback"

-config.downloadURL = "https://s3.amazonaws.com/node-webkit"
+config.downloadURL = "http://dl.node-webkit.org/"

 if process.env.npm_package_config_sockport and process.env.npm_package_config_sockport isnt 'false'
 	config.sockPort = process.env.npm_package_config_sockport


fix WEBROOT is not defined issue

fix WEBROOT is not defined issue

--- src/config.coffee
+++ src/config.coffee
@@ -25,12 +25,12 @@
 Logger  = require './core/logger'

 exports.WEBROOT = path.join __dirname, '..'
-APPROOT         = path.join WEBROOT, '..'
+APPROOT         = path.join exports.WEBROOT, '..'

 exports.SERVER_PORT         = 4242
 exports.LOG_PATH            = path.join APPROOT, 'log'
-exports.CORE_TPL_PATH       = path.join WEBROOT, 'public', 'core', 'tpl'
-exports.SERVICES_PATH       = path.join WEBROOT, 'public', 'services'
+exports.CORE_TPL_PATH       = path.join exports.WEBROOT, 'public', 'core', 'tpl'
+exports.SERVICES_PATH       = path.join exports.WEBROOT, 'public', 'services'
 exports.SERVICES_SRC_PATH   = path.join __dirname, 'services'
 exports.CORE_STATIC_PATH    = 'public'


version string

version string

--- spec/protocol.coffee
+++ spec/protocol.coffee
@@ -5,7 +5,7 @@

 #   GCP PROTOCOL SPECIFICATION   *StudioLounge Multiplayer Game*
-#                       v0.2.3    RFC                    (Draft)
+#                         v0.3    RFC                    (Draft)
 #   (in)formal description of digital message formats and rules,
 #   for exchanging of theese messages between computing systems,
 #   defines syntax, semantics, synchronization of communication;


tests: Check clearing graph

tests: Check clearing graph

--- spec/protocol.coffee
+++ spec/protocol.coffee
@@ -172,7 +172,6 @@
             ui.send 'component', 'getsource', { name: 'default/main' }
           , 500

-    # TODO: automatically represent multiple participants of same class as subgraph
   describe 'stopping a running network', ->
     it 'should succeed'
     it 'network:getstatus shows not running'
@@ -544,3 +543,31 @@
       chai.expect(addinitial.tgt).to.include.keys ['node', 'port']
       chai.expect(addinitial.tgt.node).to.equal 'iip-target'
       chai.expect(addinitial.tgt.port).to.equal 'interval'
+
+    it 'clearing the graph should remove processes and IIPs', (done) ->
+      responses = []
+      graphName = 'default/main'
+      checkMessage = (d, protocol, command, payload) ->
+        responses.push
+          protocol: protocol
+          command: command
+          payload: payload
+        if command == 'clear'
+          chai.expect(payload).to.include.keys ['id']
+          chai.expect(payload.id).to.equal graphName
+          ui.send 'component', 'getsource', { name: graphName }
+        else if command == 'source'
+          chai.expect(payload).to.include.keys ['name', 'code', 'language']
+          chai.expect(payload.name).to.equal 'main'
+          graph = JSON.parse payload.code
+          roles = Object.keys graph.processes
+          inports = Object.keys graph.inports
+          outports = Object.keys graph.outports
+          chai.expect(roles, JSON.stringify(roles)).to.have.length 0
+          chai.expect(graph.connections, JSON.stringify(graph.connections)).to.have.length 0
+          chai.expect(inports).to.have.length 0
+          chai.expect(outports).to.have.length 0
+          return done()
+
+      ui.on 'message', checkMessage
+      ui.send 'graph', 'clear', { id: graphName }


Use helpers.exec

Use helpers.exec

Instead of writing this manually, utilize the exec() function provided
in the helpers library.

--- init.coffee
+++ init.coffee
@@ -1,29 +1,18 @@
-{BufferedProcess, CompositeDisposable} = require 'atom'
+{CompositeDisposable} = require 'atom'

 lint = (editor, command, options) ->
   helpers = require('atom-linter')
   regex = '(?<file>.+):(?<line>\\d+):(?<col>\\d+):\\s(?<message>.+)'
   file = editor.getPath()

-  new Promise (resolve, reject) ->
-    stdout = ''
-    stderr = ''
-
-    new BufferedProcess
-      command: command
-      args: [options, file]
-      stdout: (data) -> stdout += data
-      stderr: (data) -> stderr += data
-      exit: ->
-        warnings = helpers.parse(stdout, regex).map (message) ->
-          message.type = 'warning'
-          message
-
-        errors = helpers.parse(stderr, regex).map (message) ->
-          message.type = 'error'
-          message
-
-        resolve warnings.concat(errors)
+  helpers.exec(command, [options, file], {stream: 'both'}).then (output) ->
+    warnings = helpers.parse(output.stdout, regex).map (message) ->
+      message.type = 'Warning'
+      message
+    errors = helpers.parse(output.stderr, regex).map (message) ->
+      message.type = 'Error'
+      message
+    return warnings.concat(errors)

 module.exports =
   config:


removed incorrect test

removed incorrect test

--- test/overall/expressionParser.mocha.coffee
+++ test/overall/expressionParser.mocha.coffee
@@ -77,12 +77,6 @@

       expect(ex1.toJS()).to.deep.equal(ex2.toJS())

-    it.skip "should not get confused with underscores", ->
-      ex1 = $('player').is('23_michael_jordan')
-      ex2 = $('player').is('"23_michael_jordan"')
-
-      expect(ex1.toJS()).to.deep.equal(ex2.toJS())
-
     it "should work with lots of keywords 1", ->
       ex1 = Expression.parse('$y and true and $z')
       ex2 = $('y').and(r(true), $('z'))


Use strict on function level.

Use strict on function level.

--- src/jquery.phoenix.coffee
+++ src/jquery.phoenix.coffee
@@ -25,9 +25,9 @@
 # – Configurable usage
 #

-"use strict"
-
 (($, window) ->
+  "use strict"
+
   pluginName = "phoenix"
   defaults =
     namespace: "phoenixStorage"


fixed box plot ordering

fixed box plot ordering

--- src/viz/types/helpers/graph/includes/axes.coffee
+++ src/viz/types/helpers/graph/includes/axes.coffee
@@ -1,3 +1,4 @@
+arraySort  = require "../../../../../array/sort.coffee"
 buffer     = require "./buffer.coffee"
 buckets    = require "../../../../../util/buckets.coffee"
 fetchData  = require "../../../../../core/fetch/data.js"
@@ -141,21 +142,24 @@
           obj
         , {}
         for d in vars.axes.dataset
-          for v in d.values
-            group = fetchValue vars, v, vars[axis].value
-            counts[group].push fetchValue vars, v, sortKey
+          if d.values
+            for v in d.values
+              group = fetchValue vars, v, vars[axis].value
+              counts[group].push fetchValue vars, v, sortKey
+          else
+            group = fetchValue vars, d, vars[axis].value
+            counts[group].push fetchValue vars, d, sortKey
         for k, v of counts
           if aggType is "string"
             counts[k] = d3[agg] v
           else if aggType is "function"
             counts[k] = agg v, sortKey
-        counts = d3.entries(counts)
-        counts.sort (a, b) ->
-          if sort is "desc" then b.value - a.value else a.value - b.value
-        counts.reduce (arr, v) ->
+        counts = arraySort d3.entries(counts), "value", sort
+        counts = counts.reduce (arr, v) ->
           arr.push v.key
           arr
         , []
+        counts
       else
         uniques values
     else


added brackets

added brackets

--- lib/atom-ternjs.coffee
+++ lib/atom-ternjs.coffee
@@ -61,7 +61,7 @@
       if (data?.name)
         start = [@start.line, @start.ch]
         end = [@end.line, @end.ch]
-        atom.workspace.getActiveEditor().getBuffer().setTextInRange [start, end], (data.name)
+        atom.workspace.getActiveEditor().getBuffer().setTextInRange([start, end], (data.name))

     atom.workspace.onDidAddTextEditor ({item, pane, index}) =>
       @registerEditor(pane.items[index])


fixed snaps counter, whoops

fixed snaps counter, whoops

--- src/Game.coffee
+++ src/Game.coffee
@@ -24,7 +24,7 @@
       @player.addPoints data.d_score
       @wordsDisplay.addWord("#{data.word} +#{data.d_score}", '#00ff00')
       @animateSnap(data.word)
-      @scoreText.text = "snaps: #{data.d_score}"
+      @scoreText.text = "snaps: #{@player.points}"

     @wordsDisplay = new WordsDisplay(game)
     @textBox      = new TextInput(game)


socket disconnect

socket disconnect

--- src/Game.coffee
+++ src/Game.coffee
@@ -61,6 +61,9 @@
       $('#wordList').append(''+data.word+' ('+data.player+')
')
       $('#score').html(@player.snaps)

+    @socket.on 'disconnect', () =>
+      location.reload()
+
   sendWord: (word) ->
     @socket.emit 'new word', word
     $('#wordList').append(''+word.toLowerCase()+'
')


fix insert api

fix insert api

--- src/http.coffee
+++ src/http.coffee
@@ -32,18 +32,14 @@
 app.post '/sensors', (req, res) ->
   content = req.body

-  sensors.insert content.type content, (err, result) ->
+  sensors.insert content.type, content, (err, result) ->
     if err
       res.json 405, {errorcode: 405, message: err.message}
     else
       if result.generated_keys?
-        timeline.get {id: result.generated_keys[0]}, (err, result) ->
-          if err
-            res.json 404, {errorcode: 404, message: err.message}
-          else
-            res.json result
+        res.json result
       else
-          res.json {message: 'insert failed...'}
+        res.json {message: 'insert failed...'}
 #start server

 exports.start = () ->


Node Modules Koding: MemoryLeakWatcher: improve logging

--- node_modules_koding/memoryleakwatcher/lib/index.coffee
+++ node_modules_koding/memoryleakwatcher/lib/index.coffee
@@ -7,15 +7,18 @@
 snapshotInterval   = null
 latestHeapSnapshot = {}

+log = (string) -> console.log "[MemoryLeakWatcher] #{string}"
+
+
 handleSnapshots = (info) ->

-  leakedHeapSnapshot = { content: v8profiler.takeSnapshot(), date: Date.now() }
-  console.log 'Memory leakage detected ', info
-  console.log 'Snapshot headers: Leaked snapshot:', leakedHeapSnapshot.content.getHeader(), 'Latest snapshot before the leak:', latestHeapSnapshot.content.getHeader()
-  console.log 'Leaked snapshot compared to the latest snapshot: ', latestHeapSnapshot.content.compare leakedHeapSnapshot.content
+  leakedHeapSnapshot = { content: v8profiler.takeSnapshot() }
+  log 'Memory leakage detected ', info
+  log 'Snapshot headers: Leaked snapshot:', leakedHeapSnapshot.content.getHeader(), 'Latest snapshot before the leak:', latestHeapSnapshot.content.getHeader()

-  leakedHeapSnapshot.key = "leakedHeapSnapshot.#{leakedHeapSnapshot.date}.heapsnapshot"
-  latestHeapSnapshot.key = "latestHeapSnapshot.#{latestHeapSnapshot.date}.heapsnapshot"
+  date                   = Date.now()
+  leakedHeapSnapshot.key = "leakedHeapSnapshot.#{date}.heapsnapshot"
+  latestHeapSnapshot.key = "latestHeapSnapshot.#{date}.heapsnapshot"

   s3Upload [leakedHeapSnapshot, latestHeapSnapshot]

@@ -29,27 +32,28 @@
       { key, content } = snapshot

       content.export (err, result) ->
-        return console.log err  if err
+        return log err  if err
         params = { Key: key, Body: result }

+        log "Started uploading #{key} to s3 bucket #{bucket}"
         s3Bucket.upload params, (err) ->
-          return console.log err  if err
-          console.log "#{key} uploaded to s3 bucket #{bucket}"
+          return log err  if err
+          log "Successfully uploaded #{key}to s3 bucket #{bucket}"

 start = ->

   state = on

   # take an initial snapshot
-  latestHeapSnapshot = { content: v8profiler.takeSnapshot(), date: Date.now() }
+  latestHeapSnapshot = { content: v8profiler.takeSnapshot() }

   # then take a heap snapshot every minute
   snapshotInterval = setInterval ->
-    latestHeapSnapshot = { content: v8profiler.takeSnapshot(), date: Date.now() }
+    latestHeapSnapshot = { content: v8profiler.takeSnapshot() }
   , 60000

   memwatch.on 'leak', handleSnapshots
-  console.log "Started watching possible memory leakage for the process with id #{process.pid}"
+  log "Started watching possible memory leakage for the process with id #{process.pid}"

 stop = ->
@@ -60,14 +64,18 @@
     clearInterval snapshotInterval

   memwatch.removeListener 'leak', handleSnapshots
-  console.log "Stopped watching possible memory leakage for the process with id #{process.pid}"
+  log "Stopped watching possible memory leakage for the process with id #{process.pid}"

 setup = (options = {}) ->

-  console.log "To start/stop watching memory profiling send SIGUSR2 to PID #{process.pid}"
+  log "To start/stop watching memory profiling send SIGUSR2 to PID #{process.pid}"

   { AWS_KEY, AWS_SECRET } = options
+
+  unless AWS_KEY or AWS_SECRET
+    return log 'Missing S3 credentials'
+
   AWS.config.update { accessKeyId: AWS_KEY, secretAccessKey: AWS_SECRET }

   process.on 'SIGUSR2', ->


git-go

git-go

--- test/test_literals.coffee
+++ test/test_literals.coffee
@@ -151,4 +151,35 @@
     null

 ok obj.options.value is yes
-ok obj.fn() is null+ok obj.fn() is null
+
+
+# Implicit objects with wacky indentation:
+obj =
+  reverse: (obj) ->
+    Array.prototype.reverse.call obj
+  abc: ->
+    @reverse(
+      @reverse @reverse ['a', 'b', 'c'].reverse()
+    )
+  one: [1, 2,
+    a: 'b'
+  3, 4]
+  red:
+    orange:
+          yellow:
+                  green: 'blue'
+    indigo: 'violet'
+  misdent: [[],
+  [],
+                  [],
+      []]
+
+ok obj.abc().join(' ') is 'a b c'
+ok obj.one.length is 5
+ok obj.one[4] is 4
+ok obj.one[2].a is 'b'
+ok (key for key of obj.red).length is 2
+ok obj.red.orange.yellow.green is 'blue'
+ok obj.red.indigo is 'violet'
+ok obj.misdent.toString() is ',,,'


only catch errors in dev mode

only catch errors in dev mode

--- lib/main.coffee
+++ lib/main.coffee
@@ -61,14 +61,18 @@
             OpenedFiles ?= require "./#{pkgName}"
             @openedFiles = new OpenedFiles(logger)
           catch e
-            log "loading core failed"
-            log e.message if e?.message?
+            if atom.inDevMode()
+              log "loading core failed"
+              log e.message if e?.message?
+            else
+              throw e
           if @openedFiles?.comps?.app?
             @openedFiles.comps.app.colorPicker = @colorPicker
             @openedFiles.comps.app.changeColor = @changeColor
             @cbHandler = @colorChangeCb? @openedFiles.comps.app.colorChangeCb
         if @compiling?
           @compiling.then load
+          .catch throw
         else
           load()
       if atom.packages.isPackageActive("tree-view")


:art: Disable deactivated packages Using the code @AsaAyers suggested

--- lib/main.coffee
+++ lib/main.coffee
@@ -1,3 +1,4 @@
+{Disposable} = require('atom')
 module.exports =
   instance: null
   config:
@@ -22,12 +23,15 @@
         linter = legacy(require "#{atomPackage.path}/lib/#{implementation}")
         @consumeLinter(linter)

-  consumeLinter: (linter) ->
-    if linter instanceof Array
-      for singleLinter in linter
-        @_consumeLinter(singleLinter)
-    else
-      @_consumeLinter(linter)
+  consumeLinter: (linters) ->
+    unless linters instanceof Array
+      linters = [ linters ]
+    for linter in linters
+      if @_validateLinter(linter)
+        @instance.linters.push linter
+    new Disposable =>
+      @instance.linters = @instance.linters.filter (item) ->
+        linters.indexOf(item) is -1

   consumeStatusBar: (statusBar) ->
     @instance.views.attachBottom(statusBar)
@@ -38,9 +42,10 @@
   deactivate: ->
     @instance?.deactivate()

-  _consumeLinter: (linter) ->
+  _validateLinter: (linter) ->
     if linter.grammarScopes instanceof Array and typeof linter.lint is 'function'
-      @instance.linters.push linter
+      true
     else
       err = new Error("Invalid Linter Provided")
-      atom.notifications.addError err.message, {detail: err.stack}+      atom.notifications.addError err.message, {detail: err.stack}
+      false


:new: Add underlineIssues config

:new: Add underlineIssues config

--- lib/main.coffee
+++ lib/main.coffee
@@ -8,12 +8,16 @@
       type: 'boolean'
       default: true
     showErrorPanel:
-      title: "Show Error Panel at the bottom"
+      title: 'Show Error Panel at the bottom'
       type: 'boolean'
       default: true
     showErrorInline:
-      title: "Show Inline Tooltips"
-      descriptions: "Show inline tooltips for errors"
+      title: 'Show Inline Tooltips'
+      descriptions: 'Show inline tooltips for errors'
+      type: 'boolean'
+      default: true
+    underlineIssues:
+      title: 'Underline Issues'
       type: 'boolean'
       default: true


improve reliability

improve reliability

--- lib/main.coffee
+++ lib/main.coffee
@@ -47,19 +47,22 @@

   delaySettings: (onSettingsOpen) =>
     settingsTab = document.querySelector('.tab-bar [data-type="SettingsView"]')
-    settingsEnabled = settingsTab.className.includes 'active'
+    settingsEnabled = settingsTab.className.includes 'active' if settingsTab
     return unless settingsTab && settingsEnabled
     try
       # Tab title
       settingsTab.querySelector('.title').textContent = "設定"

-      # on Open
-      applyOnPanel()
+      # Load all settings panels
+      lastMenu = document.querySelector('.panels-menu .active a')
+      panelMenus = document.querySelectorAll('.settings-view .panels-menu li a')
+      for panelMenu in panelMenus
+        panelMenu.click()
+      # Restore last active menu
+      lastMenu.click() if lastMenu

-      # Add a event on menus which works when clicked
-      panelMenus = document.querySelectorAll('.settings-view .panels-menu li')
-      for panelMenu in panelMenus
-        panelMenu.addEventListener("click", applyOnPanel, false)
+      # on Init
+      applyToPanel()

       # Left-side menu
       menu = document.querySelector('.settings-view .panels-menu')
@@ -75,22 +78,13 @@
     catch e
       console.error "日本語化に失敗しました。", e

-  applyOnPanel = (e) ->
-    activePanelName
-    if e
-      activePanelName = e.target.title
-    else
-      activePanel = document.querySelector('.panels-menu .active')
-      activePanelName = activePanel.getAttribute('name')
-
-    if activePanelName == "Settings"
-      # Settings panel
-      sp = document.querySelector('.settings-panel')
-      for d in window.JapaneseMenu.defS.Settings.settings
-        applyTextContentBySettingsId(d)
+  applyToPanel = (e) ->
+    # Settings panel
+    for d in window.JapaneseMenu.defS.Settings.settings
+      applyTextContentBySettingsId(d)

     # Every panel
-    panel = document.querySelector('.panels>[style="display: block;"]')
+    #TODO

   applyTextContentBySettingsId = (data) ->
     el = document.querySelector("[id='#{data.id}']")


Remove semicolon

Remove semicolon

--- lib/main.coffee
+++ lib/main.coffee
@@ -54,4 +54,4 @@
     unless @goBackView?
       GoBackView = require './go-back-view'
       @goBackView = new GoBackView(@stack)
-    @goBackView;
+    @goBackView


The second argument isn't used

The second argument isn't used

wouldn't have this problem if it used TypeScript

--- lib/main.coffee
+++ lib/main.coffee
@@ -10,4 +10,4 @@

 module.exports =
   activate: (state) ->
-    atom.syntax.addGrammar(new JavaScriptSemanticGrammar(atom.syntax, {}))
+    atom.syntax.addGrammar(new JavaScriptSemanticGrammar(atom.syntax))


:new: Add statusIconPosition config

:new: Add statusIconPosition config

--- lib/main.coffee
+++ lib/main.coffee
@@ -20,6 +20,12 @@
       title: 'Underline Issues'
       type: 'boolean'
       default: true
+    statusIconPosition:
+      title: 'Position of Status Icon on Bottom Bar'
+      description: 'Requires a reload/restart to update'
+      enum: ['Left', 'Right']
+      type: 'string'
+      default: 'Left'

   activate: ->
     LinterPlus = require('./linter-plus.coffee')


linter fixes

linter fixes

--- lib/main.coffee
+++ lib/main.coffee
@@ -15,12 +15,16 @@
     keyBindSource = 'tabs package'
     configKey = 'tabs.enableMruTabSwitching'

-    @updateTraversalKeybinds = =>
+    @updateTraversalKeybinds = ->
       # We don't modify keybindings based on our setting if the user has already tweaked them.
-      bindings = atom.keymaps.findKeyBindings(target:document.body, keystrokes:'ctrl-tab')
-      return if bindings.length > 1 and bindings[0].source != keyBindSource
-      bindings = atom.keymaps.findKeyBindings(target:document.body, keystrokes:'ctrl-shift-tab')
-      return if bindings.length > 1 and bindings[0].source != keyBindSource
+      bindings = atom.keymaps.findKeyBindings(
+        target: document.body,
+        keystrokes: 'ctrl-tab')
+      return if bindings.length > 1 and bindings[0].source isnt keyBindSource
+      bindings = atom.keymaps.findKeyBindings(
+        target: document.body,
+        keystrokes: 'ctrl-shift-tab')
+      return if bindings.length > 1 and bindings[0].source isnt keyBindSource

       if atom.config.get(configKey)
         atom.keymaps.removeBindingsFromSource(keyBindSource)


:gift: better vmp integration, auto-move-to row 0 when enter insert-mode

--- lib/main.coffee
+++ lib/main.coffee
@@ -63,13 +63,23 @@
     search: @search.bind(this)
     lines: @lines.bind(this)

-  consumeVim: ({getEditorState, onDidAddVimState}) ->
-    startInInsertModeForUI = (vimState) ->
-      if not vimState.isMode('insert') and vimState.editorElement.classList.contains('narrow')
+  consumeVim: ({getEditorState, observeVimStates}) ->
+    isNarrowEditor = (vimState) ->
+      vimState.editorElement.classList.contains('narrow')
+
+    subscribe = @subscribe.bind(this)
+
+    @subscribe observeVimStates (vimState) ->
+      return unless isNarrowEditor(vimState)
+
+      if settings.get('vmpStartInInsertModeForUI') and not vimState.isMode('insert')
         vimState.activate('insert')

-    @subscribe onDidAddVimState (vimState) ->
-      startInInsertModeForUI(vimState) if settings.get('vmpStartInInsertModeForUI')
+      do (vimState) ->
+        editor = vimState.editor
+        subscribe vimState.modeManager.onDidActivateMode ({mode, submode}) ->
+          if mode is 'insert' and editor.getCursorBufferPosition().row isnt 0
+            editor.setCursorBufferPosition([0, Infinity])

     confirmSearch = -> # return search text
       editor = atom.workspace.getActiveTextEditor()


Don't overwrite the file-scoped path variable

--- src/editor.coffee
+++ src/editor.coffee
@@ -410,13 +410,13 @@
   saveAs: (filePath) -> @buffer.saveAs(filePath)

   checkoutHead: ->
-    if path = @getPath()
-      atom.project.getRepo()?.checkoutHead(path)
+    if filePath = @getPath()
+      atom.project.getRepo()?.checkoutHead(filePath)

   # Copies the current file path to the native clipboard.
   copyPathToClipboard: ->
-    path = @getPath()
-    atom.clipboard.write(path) if path?
+    if filePath = @getPath()
+      atom.clipboard.write(filePath)

   # Public: Returns the {String} path of this editor's text buffer.
   getPath: -> @buffer.getPath()


moving clear outside of camera transform

--- src/engine.coffee
+++ src/engine.coffee
@@ -135,12 +135,12 @@
       self.trigger "afterUpdate"

     draw = ->
+      if I.clear
+        canvas.clear()
+      else if I.backgroundColor
+        canvas.fill(I.backgroundColor)
+
       I.canvas.withTransform I.cameraTransform, (canvas) ->
-        if I.clear
-          canvas.clear()
-        else if I.backgroundColor
-          canvas.fill(I.backgroundColor)
-
         self.trigger "beforeDraw", canvas

         if I.zSort


Use new fail() args in Entity#setAnimation

--- src/entity.coffee
+++ src/entity.coffee
@@ -397,7 +397,7 @@

   setAnimation: (name) ->
-    @currentAnimation = @animations[name] or fail("Couldn't find animation with name #{name}")
+    @currentAnimation = @animations[name] or fail("Entity#setAnimation", "Couldn't find animation with name #{name}")

   clearAnimation: ->


Clear the pool when font or lineHeight change

--- src/text-editor-component.coffee
+++ src/text-editor-component.coffee
@@ -653,6 +653,7 @@
     {@fontSize, @fontFamily, @lineHeight} = getComputedStyle(@getTopmostDOMNode())

     if @fontSize isnt oldFontSize or @fontFamily isnt oldFontFamily or @lineHeight isnt oldLineHeight
+      @domElementPool.clear()
       @measureLineHeightAndDefaultCharWidth()

     if (@fontSize isnt oldFontSize or @fontFamily isnt oldFontFamily) and @performedInitialMeasurement