use _.every

use _.every

--- bokehjs/src/coffee/api/figure.coffee
+++ bokehjs/src/coffee/api/figure.coffee
@@ -11,9 +11,6 @@
   Collections = base.Collections
   logger = Logging.logger

-  if not Array.prototype.every
-    Array.prototype.every = (f) -> (x for x in @ when f(x)).length == @length
-
   _get_num_minor_ticks = (axis_type, num_minor_ticks) ->
     if not num_minor_ticks?
         return 0
@@ -64,10 +61,10 @@
     #   return range

     if _.isArray(range)
-      if range.every(_.isString)
+      if _.every(range, _.isString)
         return Collections("FactorRange").create(factors: range)

-      if range.length == 2 and range.every(_.isNumber)
+      if range.length == 2 and _.every(range, _.isNumber)
         return Collections("Range1d").create({start: range[0], end: range[1]})

     logger.error("Unrecognized range input: #{range.toJSON}")


#90 fixed problem on chrome dev build where wrong window is used to populate template data

--- src/lib/background.coffee
+++ src/lib/background.coffee
@@ -366,13 +366,14 @@
   shortenMap = {}
   tab        = null
   template   = null
-  windowId   = null
+  windowId   = chrome.windows.WINDOW_ID_CURRENT
   # Create a runner to manage this asynchronous mess.
   runner = new utils.Runner()
-  runner.push chrome.windows, 'getCurrent', (win) ->
-    log.info 'Retrieved the following window...', win
-    windowId = win.id
-    runner.next()
+  unless windowId?
+    runner.push chrome.windows, 'getCurrent', (win) ->
+      log.info 'Retrieved the following window...', win
+      windowId = win.id
+      runner.next()
   runner.pushPacked chrome.tabs, 'query', ->
     [active: yes, windowId: windowId, (tabs) ->
       log.info 'Retrieved the following tabs...', tabs
@@ -518,13 +519,14 @@
 selectOrCreateTab = (url, callback) ->
   log.trace()
   tab      = null
-  windowId = null
+  windowId = chrome.windows.WINDOW_ID_CURRENT
   # Create a runner to mange the asynchronous pattern.
   runner = new utils.Runner()
-  runner.push chrome.windows, 'getCurrent', (win) ->
-    log.debug 'Retrieved the following window...', win
-    windowId = win.id
-    runner.next()
+  unless windowId?
+    runner.push chrome.windows, 'getCurrent', (win) ->
+      log.debug 'Retrieved the following window...', win
+      windowId = win.id
+      runner.next()
   runner.pushPacked chrome.tabs, 'query', ->
     [windowId: windowId, (tabs) ->
       log.debug 'Retrieved the following tabs...', tabs


Update locales.test.coffee

Update locales.test.coffee

Hebrew Locale Tests

--- tests/locales.test.coffee
+++ tests/locales.test.coffee
@@ -233,3 +233,16 @@

     it "shows the correct CONFIRM translation", ->
       expect(@labels.confirm).to.equal "Onayla"
+
+  describe "Hebrew", ->
+    beforeEach ->
+      @setLocale "he"
+
+    it "shows the correct OK translation", ->
+      expect(@labels.ok).to.equal "אישור"
+
+    it "shows the correct CANCEL translation", ->
+      expect(@labels.cancel).to.equal "ביטול"
+
+    it "shows the correct CONFIRM translation", ->
+      expect(@labels.confirm).to.equal "אישור"


test for detectives

test for detectives

--- tests/test_builder.coffee
+++ tests/test_builder.coffee
@@ -14,6 +14,42 @@
 afterEach ->
     mock.restore()
     process.chdir(@old_cwd)
+
+describe 'Testing detectives', ->
+    beforeEach ->
+        mock(yaml.safeLoad("""
+            testimonial:
+                a.js: |
+                    define({
+                        a: 1
+                        });
+                b.js: |
+                    define(function(require) {
+                        return require("./a")
+                        });
+                c.js: |
+                    define(function(require, exports, module) {
+                        module.exports = require("./b");
+                    });
+                d.js: |
+                    define(["./c"], function(c) {
+                        return c;
+                        });
+                e.js: |
+                    define("e", ["./d"], function(d) {
+                        return d;
+                        });
+            """))
+
+    detectiveCJS = require('detective')
+    detectiveAMD = require('detective-amd')
+
+    it 'should find dependency', ->
+        expect(detectiveAMD(fs.readFileSync("/testimonial/a.js", encoding: "utf8"))).to.deep.equal([])
+        expect(detectiveAMD(fs.readFileSync("/testimonial/b.js", encoding: "utf8"))).to.deep.equal(['./a'])
+        expect(detectiveAMD(fs.readFileSync("/testimonial/c.js", encoding: "utf8"))).to.deep.equal(['./b'])
+        expect(detectiveAMD(fs.readFileSync("/testimonial/d.js", encoding: "utf8"))).to.deep.equal(['./c'])
+        expect(detectiveAMD(fs.readFileSync("/testimonial/e.js", encoding: "utf8"))).to.deep.equal(['./d'])

 describe 'Building module with unknown dependency', ->

@@ -1552,7 +1588,7 @@
                     id: -> @that.equals("h")
                     deps: -> @that.deep.equals({})
                     type: -> @that.equals("amd")
-                    amdtype: -> @that.equals("XXX")
+                    amdtype: -> @that.equals(null)
                 2: -> @that.has.properties
                     id: -> @that.equals("b")
                     deps: -> @that.deep.equals


Unload greet listeners

Unload greet listeners

--- modules/greet.coffee
+++ modules/greet.coffee
@@ -2,24 +2,33 @@
   init: =>
     { Dispatcher } = @engine.bot

-    Dispatcher.on 'GUILD_MEMBER_ADD', (e)=>
-      { data } = await Core.guilds.getGuild e.guild
-      if data.greet and data.greet isnt 'off'
-        e.guild.generalChannel.sendMessage(
-          data.greet
-          .replace /\{mention\}/g, e.member.mention
-          .replace /\{name\}/g, e.member.username
-          .replace /\{server\}/g, e.guild.name
-        )
+    Dispatcher.on 'GUILD_MEMBER_ADD', @greetHandler
+    Dispatcher.on 'GUILD_MEMBER_REMOVE', @farewellHandler

-    Dispatcher.on 'GUILD_MEMBER_REMOVE', (e)=>
-      { data } = await Core.guilds.getGuild e.guild
-      if data.farewell and data.farewell isnt 'off'
-        e.guild.generalChannel.sendMessage(
-          data.farewell
-          .replace /\{mention\}/g, e.user.mention
-          .replace /\{name\}/g, e.user.username
-          .replace /\{server\}/g, e.guild.name
-        )
+  greetHandler: (e)=>
+    { data } = await Core.guilds.getGuild e.guild
+    if data.greet and data.greet isnt 'off'
+      e.guild.generalChannel.sendMessage(
+        data.greet
+        .replace /\{mention\}/g, e.member.mention
+        .replace /\{name\}/g, e.member.username
+        .replace /\{server\}/g, e.guild.name
+      )
+
+  farewellHandler: (e)=>
+    { data } = await Core.guilds.getGuild e.guild
+    if data.farewell and data.farewell isnt 'off'
+      e.guild.generalChannel.sendMessage(
+        data.farewell
+        .replace /\{mention\}/g, e.user.mention
+        .replace /\{name\}/g, e.user.username
+        .replace /\{server\}/g, e.guild.name
+      )
+
+  unload: =>
+    { Dispatcher } = @engine.bot
+    Dispatcher.removeListener 'GUILD_MEMBER_ADD', @greetHandler
+    Dispatcher.removeListener 'GUILD_MEMBER_REMOVE', @farewellHandler
+

 module.exports = GreetModule


Remove template helpers from attribution.coffee

--- src/scripts/modules/media/footer/attribution/attribution.coffee
+++ src/scripts/modules/media/footer/attribution/attribution.coffee
@@ -5,7 +5,3 @@

   return class AttributionView extends FooterTabView
     template: template
-    templateHelpers: () ->
-      model = super()
-      model.id = @model.getVersionedId() # Ensure the id has the version attached
-      return model


fixing issue where group button text is changed

--- jsapp/xlform_model_view/view.surveyApp.templates.coffee
+++ jsapp/xlform_model_view/view.surveyApp.templates.coffee
@@ -27,7 +27,7 @@
             <button class="btn btn--utility" id="save"> Save #{type_name}</button>
             <button class="btn btn--utility" id="xlf-preview"> Preview #{type_name}</button>
             <button class="btn btn--utility survey-editor__action--multiquestion js-expand-multioptions--all" > Expand All Questions</button>
-            <button class="btn btn--utility survey-editor__action--multiquestion btn--group-questions btn--disabled js-group-rows js-expand-multioptions--all">Group Questions</button>
+            <button class="btn btn--utility survey-editor__action--multiquestion btn--group-questions btn--disabled js-group-rows">Group Questions</button>
           <button class="btn btn--utility pull-right survey-editor__action--multiquestion rowselector_toggle-library" id="question-library"> Question Library</button>


manually specify enctype

manually specify enctype

--- lib/assets/javascripts/easiest_ever_ajax_uploads.coffee
+++ lib/assets/javascripts/easiest_ever_ajax_uploads.coffee
@@ -38,6 +38,7 @@
         data: fd
         processData: false
         # contentType: file.type
+        enctype: 'multipart/form-data'
         crossDomain: true
         success: (data, textStatus, jqXHR) ->
           location = jqXHR.getResponseHeader("Location")


TAP5-2338: Work around some IE8 wierdness

The T5 floating console was always visible since there was an error trying to write to the native console, which simply does not exist in IE8
unless the developer tools are enabled. This fix drops attempts to write to the native console when it is missing. It is then possible to dismiss the
floating console normally.

--- tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/console.coffee
+++ tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/console.coffee
@@ -156,10 +156,14 @@
         if _.isFunction consolefn
           # Use the available native console, calling it like an instance method
           consolefn.call console, message
-        else
-          # And IE just has to be different. The properties of console are callable, like functions,
-          # but aren't proper functions that work with `call()` either.
-          consolefn message
+          return
+
+        # On IE8, the console object appears empty unless debugging tools are enabled.
+        return unless consolefn
+
+        # And IE just has to be different. The properties of console are callable, like functions,
+        # but aren't proper functions that work with `call()` either.
+        consolefn message

         return


新增 todo 。

新增 todo 。

--- src/buttons/image.coffee
+++ src/buttons/image.coffee
@@ -240,6 +240,7 @@
       if @editor.body.find('img.uploading').length < 1
         @editor.uploader.trigger 'uploadready', [file, result]

+#todo: 检测文件格式加自定义格式。
   parseUpyunResponse: (responseText) ->
     result = $.parseJSON responseText
     success: result.code is 200


On completion-engines page, put example first.

On that page, we have regular expressions (which are not new-user
friendly) and an example (which is new-user friendly).  So the example
should come before the regular expressions.

--- pages/completion_engines.coffee
+++ pages/completion_engines.coffee
@@ -13,13 +13,6 @@
     html.push "
"
     if engine.example.explanation
       html.push "
#{engine.example.explanation}
"
-    if engine.regexps
-      html.push "
"
-      html.push "Regular expression#{if 1 < engine.regexps.length then 's' else ''}:"
-      html.push "
"
-      html.push "#{cleanUpRegexp re}\n" for re in engine.regexps
-      html.push "
"
-      html.push "
"
     if engine.example.searchUrl and engine.example.keyword
       engine.example.description ||= engine.constructor.name
       html.push "
"
@@ -28,6 +21,13 @@
       html.push "#{engine.example.keyword}: #{engine.example.searchUrl} #{engine.example.description}"
       html.push "
"
       html.push "
"
+    if engine.regexps
+      html.push "
"
+      html.push "Regular expression#{if 1 < engine.regexps.length then 's' else ''}:"
+      html.push "
"
+      html.push "#{cleanUpRegexp re}\n" for re in engine.regexps
+      html.push "
"
+      html.push "
"
     html.push "
"

   document.getElementById("engineList").innerHTML = html.join ""


Clean up getJobs(), getJob() output handling

--- src/ext/modules/routines.coffee
+++ src/ext/modules/routines.coffee
@@ -539,6 +539,11 @@
   extendJob = (job) ->
     render_ job, -> H2O.JobOutput _, job

+  extendJobs = (jobs) ->
+    for job in jobs
+      extendJob job
+    render_ jobs, -> H2O.JobsOutput _, jobs
+
   extendModel = (model) ->
     switch model.algo
       when 'kmeans'
@@ -1057,15 +1062,27 @@
       else
         assist getModel

+  requestJob = (key, go) ->
+    _.requestJobByDestinationKey key, (error, job) ->
+      if error
+        go error
+      else
+        go null, extendJob job
+
+  requestJobs = (go) ->
+    _.requestJobs (error, jobs) ->
+      if error
+        go error
+      else
+        go null, extendJobs jobs
+
   getJobs = ->
-    renderable _.requestJobs, (jobs, go) ->
-      go null, H2O.JobsOutput _, jobs
+    _fork requestJobs

   getJob = (arg) ->
     switch typeOf arg
       when 'String'
-        renderable _.requestJobByDestinationKey, arg, (job, go) ->
-          go null, H2O.JobOutput _, job
+        _fork requestJob, arg
       when 'Object'
         if arg.key?
           getJob arg.key


PUBDEV-2339 PUBDEV-2337 Switch rapids expression to use assign instead of tmp=

--- src/ext/modules/routines.coffee
+++ src/ext/modules/routines.coffee
@@ -1018,7 +1018,7 @@
     splits

   requestBindFrames = (key, sourceKeys, go) ->
-    _.requestExec "(tmp= #{key} (cbind #{sourceKeys.join ' '}))", (error, result) ->
+    _.requestExec "(assign #{key} (cbind #{sourceKeys.join ' '}))", (error, result) ->
       if error
         go error
       else
@@ -1046,7 +1046,7 @@
         else
           g

-        push statements, "(tmp= #{part.key} (rows #{frameKey} #{sliceExpr}))"
+        push statements, "(assign #{part.key} (rows #{frameKey} #{sliceExpr}))"

       push statements, "(rm #{randomVecKey})"

@@ -1238,7 +1238,7 @@
         else
           "[]"

-        _.requestExec "(tmp= #{frame} (h2o.impute #{frame} #{columnIndex} #{JSON.stringify method} #{JSON.stringify combineMethod} #{groupByArg}))", (error, result) ->
+        _.requestExec "(assign #{frame} (h2o.impute #{frame} #{columnIndex} #{JSON.stringify method} #{JSON.stringify combineMethod} #{groupByArg}))", (error, result) ->
           if error
             go error
           else
@@ -1255,7 +1255,7 @@
         catch columnKeyError
           return go columnKeyError

-        _.requestExec "(tmp= #{frame} (:= #{frame} (#{method} (cols #{frame} #{columnIndex})) #{columnIndex} [0:#{result.rows}]))", (error, result) ->
+        _.requestExec "(:= #{frame} (#{method} (cols #{frame} #{columnIndex})) #{columnIndex} [0:#{result.rows}])", (error, result) ->
           if error
             go error
           else


updating error handling for Base64 paths

--- lib/Base64.coffee
+++ lib/Base64.coffee
@@ -3,6 +3,7 @@
 mime = require('mime')
 async = require('async')
 through = require('through')
+_console = require('PrettyConsole')

 escapeRegExp = (str)->
@@ -27,8 +28,11 @@

     for own k,v of matchHash
       do (k,v)->
-        matchHash[k] = (cb2)->
+        matchHash[k] = (cb2)->
           fs.readFile(v, (err, data)->
+            if err
+              _console.error(err, v)
+              return cb2(err, false)
             cb2(err, "data:#{mime.lookup(v)};base64,#{new Buffer(data).toString('base64')}")
           )


test(e2e): fix e2e test

test(e2e): fix e2e test

--- e2e/reportCard/reportCard.spec.coffee
+++ e2e/reportCard/reportCard.spec.coffee
@@ -34,4 +34,4 @@
 	it 'should get the username', ->
 		@page.fillUsername 'yokun'
 		@page.search()
-		expect(@page.getFullName()).toEqual('yokun')+		expect(@page.getFullName()).toEqual('Yevgeniy Okun')


Add another TODO.

Add another TODO.

--- src/coffee/import.coffee
+++ src/coffee/import.coffee
@@ -83,6 +83,7 @@
       matchFn = @initMatcher @matchBy, existingProducts
       productsToUpdate =
       if @validator.updateVariantsOnly
+        # TODO: reactive this - currently broken!
         @mapVariantsBasedOnSKUs existingProducts, products
       else
         products


Updated cohtml-parser

Updated cohtml-parser

--- src/cohtml-parser.coffee
+++ src/cohtml-parser.coffee
@@ -38,7 +38,7 @@
     offset = location.lastIndexOf '\n'
     index = inputString.indexOf '\n', (offset+1)
     line = (@inputArray[offset+1...index]).join ''
-    line = line.replace /\s/g, '~'
+    # line = line.replace /\s/g, '~'

     charNumber = @head - offset - 1

@@ -46,7 +46,7 @@

     text = """
            CohtmlParserError: #{message}
-           head: #{@head}
+           head: #{@head} (#{@current()})
            line: #{lineNumber}
            char: #{charNumber}
            near: <#{line}>
@@ -59,6 +59,8 @@
     Utilities
   ###

+
+
   countNewlines: (string)->
     lineCount = 0
     offset = 0
@@ -69,6 +71,10 @@

   ignoreWhitespace: ->
     @ignore @charset['ws']
+
+
+
+

   getIndentLevel: ->
     @backUp()
@@ -87,13 +93,16 @@
     return scope

   extractStatement: (indentLevel, parentNode)->
+
+
+
     foundIndentLevel = @getIndentLevel()

     if foundIndentLevel is indentLevel
       @takeAll @indentCharacter
       if node = @extractNode indentLevel, parentNode
         return node
-      else if node = @extractTextNode parentNode
+      else if node = @extractTextNode indentLevel, parentNode
         return node
       else
         if @isEof()
@@ -103,7 +112,26 @@
     else
       return false

-  extractTextNode: (parentNode)->
+  extractTextNode: (indentLevel, parentNode)->
+    if @take @charset['backtick']
+      innerText = @takeAllUntil @charset['backtick']
+      @take @charset['backtick']
+      @ignoreWhitespace()
+
+      node = new CohtmlTextNode parentNode, innerText
+
+      @take @charset['newline']
+
+      @backUp()
+      childrenList = @extractScope (indentLevel + 1), node
+      if childrenList.length isnt 0
+        @rollback()
+        return @throwError 'CohtmlTextNode can not contain children'
+      @commit()
+
+      return node
+    else
+      return false

   extractNode: (indentLevel, parentNode)->
     tag = @takeAll @charset['tag']
@@ -158,7 +186,7 @@

     @take @charset['newline']

-    node.childrenList = @extractScope (indentLevel + 1), node
+    node.childrenList = @extractScope (indentLevel + 1), node

     return node


Make command final changes

Make command final changes

--- src/commands/make.coffee
+++ src/commands/make.coffee
@@ -20,12 +20,12 @@
   del.sync([config.imagePath])
   fs.mkdirsSync(config.imagePath)

-  images.create(config.pumlPath, config.imagePath ,() ->
-    imageMetaData = images.metaData(config.imagePath)
-
+  images.create(config.pumlPath, config.imagePath ,(err, paths) ->
+    imageMetaData = images.metaData(paths)
+    allconfig = _.merge({images: imageMetaData}, config)
     readme.create(
       config.readmeTemplatePath
       config.readmeOutputPath
-      _.merge(imageMetaData, config)
+      allconfig
     )
   )


Add support for obtaining sync tokens

Add support for obtaining sync tokens

--- lib/clever.coffee
+++ lib/clever.coffee
@@ -20,7 +20,7 @@
     http_opts.headers ?= {}
     _(http_opts.headers).extend Authorization: "Bearer #{auth.token}"

-module.exports = (auth, url_base='https://api.clever.com', options={}) ->
+Clever = module.exports = (auth, url_base='https://api.clever.com', options={}) ->
   throw new Error 'Must provide auth' if not auth
   auth = {api_key: auth} if _.isString auth
   clever =
@@ -264,3 +264,37 @@
     Query    : Query

 module.exports.handle_errors = handle_errors
+Clever.handle_errors = handle_errors
+
+Clever.Token = class Token
+  @url_base: 'https://clever.com'
+  @api_base: 'https://api.clever.com'
+  @path: '/oauth/tokens'
+
+  @find: (client_id, client_secret, owner_type..., cb) ->
+    owner_type = owner_type[0]
+    owner_type ?= 'district'
+    opts =
+      method: 'get'
+      ca: certs
+      json: true
+      auth: "#{client_id}:#{client_secret}"
+      uri: "#{@url_base}#{@path}?owner_type=#{owner_type}"
+    quest opts, (err, resp, body) ->
+      handle_errors resp, body, (err, resp, body) ->
+        return cb?(err) if err
+        cb?(err, body?.data)
+
+  @details: (token, cb) ->
+    auth = {token: token} if _.isString(token)
+    auth ?= {token: token.access_token}
+    opts =
+      method: 'get'
+      ca: certs
+      json: true
+      uri: "#{@api_base}/me"
+    apply_auth auth, opts
+    quest opts, (err, resp, body) ->
+      handle_errors resp, body, (err, resp, body) ->
+        return cb?(err) if err
+        cb?(err, body?.data)


:memo: Update comments

:memo: Update comments

--- src/browser/application.coffee
+++ src/browser/application.coffee
@@ -53,7 +53,7 @@
       win = BrowserWindow.fromWebContents(event.sender)
       @applicationMenu.update(win, template, keystrokesByCommand)

-  # Public: Removes the {AtomWindow} from the global window list.
+  # Public: Removes the {ApplicationWindow} from the global window list.
   removeWindow: (window) ->
     if @windows.length is 1
       @applicationMenu?.enableWindowSpecificItems(false)
@@ -62,7 +62,7 @@
         return
     @windows.splice(@windows.indexOf(window), 1)

-  # Public: Adds the {AtomWindow} to the global window list.
+  # Public: Adds the {ApplicationWindow} to the global window list.
   addWindow: (window) ->
     @windows.push window
     @applicationMenu?.addWindow(window.browserWindow)


:memo: Remember why we added backgroundColor

--- src/browser/atom-window.coffee
+++ src/browser/atom-window.coffee
@@ -24,6 +24,10 @@
     options =
       show: false
       title: 'Atom'
+      # Add an opaque backgroundColor (instead of keeping the default
+      # transparent one) to prevent subpixel anti-aliasing from being disabled.
+      # We believe this is a regression introduced with Electron 0.37.3, and
+      # thus we should remove this as soon as a fix gets released.
       backgroundColor: "#fff"
       webPreferences:
         backgroundThrottling: not @isSpec


Update translations.coffee

Update translations.coffee

Japanese translation added

--- src/coffee/translations.coffee
+++ src/coffee/translations.coffee
@@ -88,6 +88,27 @@
         try_again: 'Try again'
         install: "Install %{client} from App Store"
         register_an_account: 'Register an account with '
-        please_copy_url: 'Please copy the URL below and add it to your Podcast- or RSS-Client.'
+          please_copy_url: 'Please copy the URL below and add it to your Podcast- or RSS-Client.'

-module.exports = Translations
+    ja:
+      button: '登録する'
+      panels:
+        title: '登録する'
+      podcast_panel:
+        choose_client: 'クライアントを選ぶ'
+      help_panel:
+        title: '登録がよろしいですか？'
+        paragraph1: '今新しいポットキャストを登録しています。それでポットキャストクライアントアプリケーションで新しいエピソードを自動でダウンロードできる、またはポットキャストアーカイブで過去のエピソードを探せます。'
+        paragraph2: 'ポットラブ登録ボタンは登録を支援します。気に入り、使っているポットキャストクライアントがポットキャストクラウドサービスを使用可能なもののリストを選んで下さい。'
+        paragraph3: 'スタートアップでポットキャストクライアントがポットキャストを登録はずです。アプリがまだインストールしなかったら、ダウンロードリンクをインストールために使って下さい。'
+      clients_panel:
+        app: 'アプリ'
+        cloud: 'クラウド'
+        other_client: '他のクライアント'
+      finish_panel:
+        handing_over_to: 'に渡す'
+        something_went_wrong: '何が失敗しましたか？'
+        try_again: 'もう一度試してください'
+        install: 'App Storeからインストールする'
+        register_an_account: 'にアカウントを登録する'
+        please_copy_url: 'URLをコピーして、ポットキャストがRSSクライアントに貼り付けて下さい。'


Fix second level sorting

Fix second level sorting

--- lib/groups.coffee
+++ lib/groups.coffee
@@ -11,7 +11,7 @@
 titleDigits = 10
 @titleSort = (title) ->
   title = title.title if title.title?
-  title.toLowerCase().replace /\d+/, (n) -> s.lpad n, titleDigits, '0'
+  title.toLowerCase().replace /\d+/g, (n) -> s.lpad n, titleDigits, '0'

 @Groups = new Mongo.Collection 'groups'


Updates the interface snippet

Updates the interface snippet

--- snippets/interface.cson
+++ snippets/interface.cson
@@ -1,4 +1,4 @@
 '.source.inspec':
   'Describe interface':
     'prefix': 'interface'
-    'body': "# http://inspec.io/docs/reference/resources/interface/\ndescribe interface('${1:name}') do\n  it { should${2:_not} ${3:exist} }${4}\nend"
+    'body': "# http://inspec.io/docs/reference/resources/interface/\ndescribe interface do\n  it { should${1:_not} be_up }\n  its('${2:property}') { should${3:_not} eq '${4:value}' }${5}\nend"


fixed view button

fixed view button

--- src/components/life_app.coffee
+++ src/components/life_app.coffee
@@ -162,7 +162,6 @@
   getAllTimelineObjects: (events, headers, view_time_range) ->
     if not view_time_range?
       view_time_range = @getViewTimeRange @state.view_type
-    debugger
     # Reads the events and headers off of state, orders them, and returns them
     objects = []
     i = 0
@@ -211,9 +210,11 @@
       else if object.event
         timeline_list.push React.createElement(EventTile, object)

-    return React.createElement("div", {className: "col-sm-offset-2 col-sm-8"},
-      React.createElement(AppNavigation, {switchView: @switchView})
-      React.createElement("div", null,
+    return React.createElement("div", null
+      React.createElement("div", {className: "col-sm-offset-2 col-sm-8"},
+        React.createElement(AppNavigation, {switchView: @switchView})
+      )
+      React.createElement("div", {className: "col-sm-offset-2 col-sm-8"},
         React.createElement(TimelineBar, {y: @state.timeline_hover_y})
         React.createElement("div", null, timeline_list)
       )


Raise default logger og level to 6

Raise default logger og level to 6

--- lib/logger.coffee
+++ lib/logger.coffee
@@ -1,6 +1,6 @@
 Logger = require "devnull"

-logLevel = process.env.LOG_LEVEL ? 3
+logLevel = process.env.LOG_LEVEL ? 6
 logger = new Logger timestamp: false, level: logLevel

 logger.http = (req, res, next) ->


Started adding fractions to number.coffee

--- lib/number.coffee
+++ lib/number.coffee
@@ -123,18 +123,28 @@

     out =
       switch op1.constructor
+        when CheckedInt
+          switch op2.constructor
+            when CheckedInt
+              [op1, op2]
+            when LongInt
+              [LongInt.fromNative(op1.val), op2]
+            when Fraction
+              [new Fraction(op1,1), op2]
         when LongInt
           switch op2.constructor
             when CheckedInt
               [op1, LongInt.fromNative(op2.val)]
             when LongInt
               [op1, op2]
-        when CheckedInt
+            when Fraction
+              [new Fraction(op1,1), op2]
+        when Fraction
           switch op2.constructor
-            when CheckedInt
+            when CheckedInt, LongInt
+              [op1, new Fraction op2, 1]
+            when Fraction
               [op1, op2]
-            when LongInt
-              [LongInt.fromNative(op1.val), op2]

     if out
       out
@@ -148,6 +158,8 @@
         new CheckedInt x.digits.first() * x.sign
       else
         new CheckedInt 0
+    else if x instanceof Fraction and x.den.eq(1)
+      x.num
     else
       x

@@ -155,12 +167,14 @@
     num[name]          = (args...) -> f.call num, args...
     NumberBase::[name] = (args...) -> num[name] this, args...

-  for name in ['cmp', 'plus', 'minus', 'times', 'div', 'mod', 'gcd']
+  for name in ['cmp', 'plus', 'minus', 'times', 'by', 'div', 'mod', 'gcd']
     do (name) ->
       namex = "#{name}__"
       operator name, (a, b) ->
         [x, y] = upcast a, b
         downcast x[namex] y
+
+  by__: (other) -> Fraction.normalized @this, other

   gcd__: (other) ->
     step = (a, b) -> if b.isPos() then -> step b, a.mod(b) else a
@@ -203,20 +217,15 @@
   constructor: (@val = 0) ->

   neg__: -> new CheckedInt -@val
-
   abs__: -> new CheckedInt Math.abs @val
-
   sgn__: -> if @val < 0 then -1 else if @val > 0 then 1 else 0

-  isPos__: -> @val > 0
-
-  isNeg__: -> @val < 0
-
+  isPos__:  -> @val > 0
+  isNeg__:  -> @val < 0
   isZero__: -> @val == 0

   isEven__: -> @val % 2 == 0
-
-  isOdd__: -> @val % 2 != 0
+  isOdd__:  -> @val % 2 != 0

   sqrt__: -> new CheckedInt Math.floor Math.sqrt @val

@@ -250,20 +259,15 @@
     @first = @digits?.first() or 0

   neg__: -> new LongInt -@sign, @digits
-
   abs__: -> new LongInt 1, @digits
-
   sgn__: -> @sign

-  isPos__: -> @sign > 0
-
-  isNeg__: -> @sign < 0
-
+  isPos__:  -> @sign > 0
+  isNeg__:  -> @sign < 0
   isZero__: -> @sign == 0

   isEven__: -> @first % 2 == 0
-
-  isOdd__: -> @first % 2 != 0
+  isOdd__:  -> @first % 2 != 0

   zeroes = BASE.toString()[1..]

@@ -435,6 +439,55 @@
       new LongInt 1, makeDigits n
     else
       new LongInt 0, null
+
+# ----
+
+# The 'Fraction' class implements rational numbers.
+
+class Fraction
+  constructor: (@num, @den) ->
+
+  @normalized = (num, den) ->
+    if den.eq 0
+      throw new Error "expected a non-zero denominator, got #{den}"
+    else if den.lt 0
+      Fraction.normalized num.neg(), den.neg()
+    else
+      a = num.gcd den
+      new Fraction num.div(a), den.div(a)
+
+  neg__: -> new Fraction @num.neg(), @den
+  abs__: -> new Fraction @num.abs(), @den
+  sgn__: -> @num.sgn()
+  inv__: -> normalized @den, @num
+
+  isPos__: -> @num.isPos()
+  isNeg__: -> @num.isNeg()
+  isZero__: -> @num.isZero()
+
+  isEven__: -> @den.eq(1) and @num.isEven()
+  isOdd__: -> @den.eq(1) and @num.isOdd()
+
+  cmp__: (x) -> @minus__(x).num.cmp 0
+
+  plus__: (x) ->
+    a = num.gcd @den, x.den
+    s = num.div x.den, a
+    t = num.div @den, a
+    Fraction.normalized s.times(@num).plus(t.times(x.num)), s.times @den
+
+  minus__: (x) -> @plus__ x.neg__()
+
+  times__: (x) ->
+    a = num.gcd @num, x.den
+    b = num.gcd @den, x.num
+    n = @num.div(a).times(x.num.div(b))
+    d = @den.div(b).times(x.den.div(a))
+    Fraction.normalized n, d
+
+  by__: (x) -> @times__ x.inv__()
+
+  toString: -> if @den.eq 1 then "#{@num}" else "#{@num}/#{@den}"

 # ----
 # Exporting.
@@ -522,3 +575,6 @@
   show -> num.gt 65535, num.pow 2, 16
   show -> num.gt 65536, num.pow 2, 16
   show -> num.gt 65537, num.pow 2, 16
+
+  log ''
+  show -> num.by 2, 3


Preliminary `arara` log parsing support

Preliminary `arara` log parsing support

--- lib/parser.coffee
+++ lib/parser.coffee
@@ -12,6 +12,8 @@
 latexmkPatternNoGM = /^Latexmk:\sapplying\srule/
 latexmkUpToDate = /^Latexmk: All targets \(.*\) are up-to-date/

+araraPattern = /Running\s(?:[a-zA-Z]*)\.\.\./g
+araraFailurePattern = /(FAILURE)/g
 module.exports =
 class Parser extends Disposable
   constructor: (latex) ->
@@ -22,7 +24,9 @@
     @isLatexmkSkipped = false
     if log.match(latexmkPattern)
       log = @trimLatexmk log
-    if log.match(latexPattern) or log.match(latexFatalPattern)
+    if log.match(araraPattern)
+      log = @trimArara log
+    if log.match(latexPattern) or log.match(latexFatalPattern) or log.match(araraFailurePattern)
       @parseLatex log
     else if @latexmkSkipped(log)
       @latex.package.status.view.status = 'skipped'
@@ -47,6 +51,17 @@
     if lines[0].match(latexmkUpToDate)
       return true
     return false
+
+  trimArara: (log) ->
+    araraRunIdx = []
+    lines = log.replace(/(\r\n)|\r/g, '\n').split('\n')
+    for index of lines
+      line = lines[index]
+      result = line.match(/Running\s(?:[a-zA-Z]*)\.\.\./)
+      if result
+        araraRunIdx = araraRunIdx.concat index
+    # Return only last arara run
+    return lines.slice(araraRunIdx.slice(-1)[0]).join('\n')

   parseLatex: (log) ->
     log = log.replace(/(.{78}(\w|\s|\d|\\|\/))(\r\n|\n)/g, '$1')


No need to consume whitespace myself, it's sanatized in the line right before the specials.  -.-

--- lib/parser.coffee
+++ lib/parser.coffee
@@ -51,9 +51,9 @@
 sanitize = (text) -> text.trim().replace /\s+/g, ' '

 specials = [
-  { pattern: /^\/(?!\/)(\s*)/, replacement: 'say ' },
-  { pattern: /^\/\/(\s*)/, replacement: 'emote ' },
-  { pattern: /^\?(\s*)/, replacement: 'eval ' },
+  { pattern: /^\/(?!\/)/, replacement: 'say ' },
+  { pattern: /^\/\//, replacement: 'emote ' },
+  { pattern: /^\?/, replacement: 'eval ' },
   { pattern: /\b(ass|tit|fag|vag)\b/g, replacement: '***' },
   { pattern: /\b(shit|fuck|tits|cock|dick|boob|fags|cunt)\b/g, replacement: '****' },
   { pattern: /\b(shits|fucks|bitch|penis|cocks|dicks|boobs|cunts)\b/g, replacement: '*****' },


Add Ruby support

Add Ruby support

--- lib/script.coffee
+++ lib/script.coffee
@@ -13,6 +13,9 @@
     makeargs: (code) -> ['-c', code]
   JavaScript:
     interpreter: "node"
+    makeargs: (code) -> ['-e', code]
+  Ruby:
+    interpreter: "ruby"
     makeargs: (code) -> ['-e', code]

 module.exports =


add /logout/

add /logout/

--- lib/server.coffee
+++ lib/server.coffee
@@ -8,12 +8,20 @@

 server.get('/', (req, resp) -> resp.redirect("/slideshow/"))

+
 server.get('/login/',
   (req, resp) ->
     if req.user?
       resp.redirect("/slideshow/")
     else
       resp.sendfile("assets/login.html", {}, (err) -> console.log(err)))
+
+server.get('/logout/',
+  (req, resp) ->
+    if req.user?
+      req.session.auth = null
+      req.session.destroy(->)
+    resp.redirect("/login/")

 server.get('/slideshow/',
   (req, resp) ->


no need to send a taken response if we send a job taken list, job search was not working clientside since we have a two element array no (revision and id)

--- lib/server.coffee
+++ lib/server.coffee
@@ -57,15 +57,12 @@
 			jumpon = 0
 			findinque = (id)->
 				for obj,ix in workque
-					if obj._id is id
-						return workque[ix]
+					return workque[ix] if obj._id is id
 				return false
 			findplace = (id)->
-				found = -1
 				for obj,i in workque
-					if obj._id is id
-						found = i
-				return found
+					return i if obj._id is id
+				return -1
 			dirtyqueue = 0
 			async.forever((next)->
 				unless r.ready
@@ -242,15 +239,11 @@
 							hostJobs[c.node.name] = lastseen: ~~((new Date).getTime() / 1000)
 						else
 							hostJobs[c.node.name].lastseen = ~~((new Date).getTime() / 1000)
-						syncJobs c.node.name, c.jobIds
+						syncJobs c.node.name, c.jobIds if c.jobIds?
 						datalog.gauge 'task_processing', c.jobIds.length, [ 'worker:' + c.node.name ] if c.jobIds? and Array.isArray(c.jobIds)
 					if c.id and findinque(c.id)?
 						jobItem = findinque(c.id)
 						switch c.status
-							when 2 # job taken
-								jobItem._status = "working"
-								jobItem._takenby = c.node.name if c.node?.name?
-								jobItem._lastchange = ~~((new Date).getTime() / 1000)
 							when 3 # job finished
 								if c.error
 									# error on the job
@@ -282,8 +275,8 @@
 								if act._status is "working" and act._takenby is c.startup
 									act._status = "tosend"
 									act._lastchange = ~~((new Date).getTime() / 1000)
-						else
-							console.log 'Not sure what to do with ', c
+						#else
+							#console.log 'Not sure what to do with ', c
 					#datalog.gauge 'task_errors', Object.keys(couch_errors).length
 					return
 				r.ready = true
@@ -533,6 +526,10 @@
 			if r.listening
 				return
 			jobs = []
+			jobIndex = (id)->
+				for item,ix in jobs
+					return ix if item[0] is id
+				return -1
 			s_wk_client = zmq.socket('pull')
 			#socket Work Que Client;
 			#setup the work returns
@@ -546,13 +543,11 @@
 				#one more job
 				c = if options.jsonpack then jsonpack.unpack(String(msg)) else JSON.parse(msg)
 				args = ''
-				if !c.exec or jobs.indexOf(c._id) > -1
+				if !c.exec? or jobIndex(c._id) > -1
 					#tell mothership we already had this ..
-					if jobs.indexOf(c._id) > -1
+					if jobIndex(c._id) isnt -1
 						console.log 'already had ', c._id, ' :|'
 						s_wc.send JSON.stringify(
-							id: c._id
-							status: 2
 							node:
 								name: os.hostname()
 								loadavg: os.loadavg()
@@ -570,8 +565,6 @@
 				console.log 'got job ', (c.exec.split("/").pop()).split(" ")[0], c._id, 'currently running', jobs.length
 				#sent back teh status job
 				s_wc.send JSON.stringify(
-					id: c._id
-					status: 2
 					node:
 						name: os.hostname()
 						loadavg: os.loadavg()
@@ -639,7 +632,6 @@
 							loadavg: os.loadavg()
 							freemem: os.freemem()
 							totamem: os.totalmem()
-							jobs: String(jobs.length - 1)
 						file: file_ret
 						_rev: c._rev
 						jobIds: jobs)


修复首次创建编辑服务器重复编译 markdown 错误

修复首次创建编辑服务器重复编译 markdown 错误

--- lib/server.coffee
+++ lib/server.coffee
@@ -20,7 +20,7 @@
   )
 )

-gulp.task('watch', ->
+gulp.task('watch', ['build'], ->
   gulp.watch([
     '*/*.{md,markdown}'
     'README.{md,markdown}'


fix multiple .exports.page for partial stylus loads.  basically anytime you used media queries

--- lib/server.coffee
+++ lib/server.coffee
@@ -604,7 +604,7 @@
                       ['.exports.region',      "#{p}.region"]
                     ]
                     for sub in subs
-                      source = source.replace sub[0], sub[1]
+                      source = source.replace new RegExp(sub[0], 'g'), sub[1]

                     # add variables code
                     return variables_code + source


Fixed a bug in sitemap pagination.

Fixed a bug in sitemap pagination.

--- workers/sitemapgenerator/lib/sitemapgenerator/sitemapgeneratorworker.coffee
+++ workers/sitemapgenerator/lib/sitemapgenerator/sitemapgeneratorworker.coffee
@@ -66,7 +66,7 @@
       numberOfNamePages = Math.ceil(count / NAMEPERPAGE)

       queue = [1..numberOfNamePages].map (pageNumber)=>=>
-        queue.sitemapNames = []
+        queue.sitemapNames or= []
         skip = (pageNumber - 1) * NAMEPERPAGE
         option = {
           limit : NAMEPERPAGE,
@@ -79,7 +79,6 @@
             content = @generateSitemapString urls
             @saveSitemap sitemapName, content

-            queue.sitemapNames or= []
             queue.sitemapNames.push sitemapName
           queue.next()
       queue.push => generateSitemapIndex(queue.sitemapNames)


Add todo about the app-root-path dep.

Add todo about the app-root-path dep.

--- index.coffee
+++ index.coffee
@@ -1,51 +1,27 @@
-###
-# Wire-context-helper
-###
 _ = require 'lodash'
+
+# TODO We want to use root folder only at the direct parent not the absolute root. So we should not use the app-root-path dep.
 root = require 'app-root-path'

 LIB_PREFIX = process.env['LIB_PREFIX'] ? 'lib'

 class WireContextHelper
-  ########################################
-
   lib: (tail) ->
-    ###
-    Library folder. It depends on the LIB_PREFIX
-    environment variable. It's 'lib' by default.
-    ###
     root.resolve "#{LIB_PREFIX}/#{tail}"

   domain: (tail) ->
-    ###
-    Domain root folder.
-    ###
     WireContextHelper.lib "domain/#{tail}"

   model: (name) ->
-    ###
-    Folder for domain models (entities/value objects).
-    ###
     WireContextHelper.domain "models/#{name}"

   Model: (name) ->
-    ###
-    Same as the 'model', but append 'Model' suffix
-    to the model name.
-    ###
     WireContextHelper.model "#{name}Model"

   repository: (name) ->
-    ###
-    Folder for repositors.
-    ###
     WireContextHelper.domain "repositories/#{name}"

   Repository: (name) ->
-    ###
-    Same as the 'repository', but append 'Repository'
-    suffix to the repository name automatically.
-    ###
     WireContextHelper.repository "#{name}Repository"

   service: (name) ->


Make sure aheadBehind returned an object before we reference its keys

--- index.coffee
+++ index.coffee
@@ -68,12 +68,12 @@
   # Get the number of commits you're ahead of the remote
   @register 'ahead', (done) ->
     @get '_aheadBehind', (err, aheadBehind) ->
-      done err, aheadBehind.ahead
+      done err, aheadBehind?.ahead

   # Get the number of commits you're behind the remote
   @register 'behind', (done) ->
     @get '_aheadBehind', (err, aheadBehind) ->
-      done err, aheadBehind.behind
+      done err, aheadBehind?.behind

   # Returns an array of objects with 'path', 'code', 'staged', 'desc'
   #


adds route for logout hook

adds route for logout hook

--- index.coffee
+++ index.coffee
@@ -57,6 +57,8 @@
 				v0.responses.respond res, data.replace "{{host}}", configs.host
 	else if urlParts.pathname == "/clefCallback"
 		handleClefCallback req, res
+	else if urlParts.pathname == "/clefLogout"
+		handleClefLogout req, res
 	else if urlParts.pathname == "/logout"
 		handleBrowserLogout req, res
 	else if urlParts.pathname == "/check"


Only instantiate servos if we have pins

Only instantiate servos if we have pins

--- index.coffee
+++ index.coffee
@@ -20,9 +20,11 @@
     leftServoPin:
       type: 'integer'
       required: true
+      default: 6
     rightServoPin:
       type: 'integer'
       required: true
+      default: 9

 class Plugin extends EventEmitter
   constructor: ->
@@ -42,10 +44,12 @@

   setOptions: (options={}) =>
     @options = options
-    @servos.left  = new Servo options.leftServoPin
-    @servos.left.enable()
-    @servos.right = new Servo options.rightServoPin
-    @servos.right.enable()
+    if options.leftServoPin?
+      @servos.left  = new Servo options.leftServoPin
+      @servos.left.enable()
+    if options.rightServoPin?
+      @servos.right = new Servo options.rightServoPin
+      @servos.right.enable()

 module.exports =
   messageSchema: MESSAGE_SCHEMA


I am filled with infinite shame

I am filled with infinite shame

--- index.coffee
+++ index.coffee
@@ -20,15 +20,15 @@
   grunt.registerMultiTask 'create-windows-installer', 'Create the Windows installer', ->
     @requiresConfig("#{@name}.#{@target}.appDirectory")

-    console.log "Starting!"
-
     useMono = false
     [monoExe, wineExe] = _.map(['mono', 'wine'], locateExecutableInPath)

     unless process.platform is 'win32'
       useMono = true
-      console.log "wine: #{wineExe}, mono: #{monoExe}"
       throw new Error("You must install both Mono and Wine on non-Windows") unless wineExe and monoExe
+
+      grunt.verbose.ok "Using Mono: '#{monoExe}'"
+      grunt.verbose.ok "Using Wine: '#{wineExe}'"

     done = @async()

@@ -47,7 +47,6 @@

     {certificateFile, certificatePassword, remoteReleases, signWithParams} = config

-    console.log "ASARing!"
     asarFile = path.join(appDirectory, 'resources', 'app.asar')
     if fs.existsSync(asarFile)
       appMetadata = JSON.parse(asar.extractFile(asarFile, 'package.json'))


moved to CABOOSE_ENV

moved to CABOOSE_ENV

--- index.coffee
+++ index.coffee
@@ -4,7 +4,7 @@
 if not global.Caboose?
   global.Caboose = {
     root: process.cwd()
-    env: process.env.caboose_env ? 'development'
+    env: process.env.CABOOSE_ENV ? 'development'
   }
   global.Caboose.path = {
     app: path.join global.Caboose.root, 'app'


Revert to old style jumping algorithm

Revert to old style jumping algorithm

Requires deathcap/voxel-control@1de90a89347d9667ce4f9689a0a101b9d8704020

To be revisited. Reopens GH-5

--- index.coffee
+++ index.coffee
@@ -43,7 +43,7 @@
     controls:
       discreteFire: false
       fireRate: 100 # ms between firing
-      jumpSpeed: 0.001
+      jumpTimer: 25
     }


Removed old term.js hack

Removed old term.js hack

--- index.coffee
+++ index.coffee
@@ -159,13 +159,6 @@
     focusNextTick = (activeItem) ->
       process.nextTick ->
         termView.focus()
-        # HACK!
-        # so, term.js allows for a special _textarea because of iframe shenanigans,
-        # but, it is the constructor instead of the instance!!!1 - probably to avoid having to bind this as a premature
-        # optimization.
-        atomPane = activeItem.parentsUntil("atom-pane").parent()[0]
-        if termView.term
-          termView.term.constructor._textarea = atomPane

     subscriptions.add pane.onDidActivate ->
       activeItem = pane.getActiveItem()


try that

try that

--- index.coffee
+++ index.coffee
@@ -16,7 +16,6 @@

 # Helper to cache an endpoint
 fetchAndCache = (key, url, callback) ->
-  console.log 'FETCH', key
   request
     .get(ARTSY_URL + url)
     .set('x-xapp-token': xappToken)


jquery phantomjs script now takes a URL argument.

--- bin/jquery.coffee
+++ bin/jquery.coffee
@@ -4,6 +4,8 @@
     if /Error/.test(msg)
         console.log(msg)

-page.open "http://localhost:3000", (status) ->
+url = phantom.args[0] || "http://localhost:3000"
+
+page.open url, (status) ->
     if status != 'success'
         console.log("Failed to load page.")


Turning off logs because they destroy the UI

--- admin/static/coffee/cluster.coffee
+++ admin/static/coffee/cluster.coffee
@@ -709,7 +709,7 @@
         $.getJSON('/ajax/progress', set_progress)
         $.getJSON('/ajax/directory', set_directory)
         $.getJSON('/ajax/last_seen', set_last_seen)
-        $.getJSON('/ajax/log/_?max_length=10', set_log_entries)
+        #$.getJSON('/ajax/log/_?max_length=10', set_log_entries)

     # Override the default Backbone.sync behavior to allow reading diffs
     legacy_sync = Backbone.sync


remove unusable argument (gearman protocol doesn't allow for messages in WORK_FAIL)

--- lib/worker.coffee
+++ lib/worker.coffee
@@ -16,7 +16,7 @@
     delete @gearman.currentWorkers[@handle]
     @gearman.sendCommand "GRAB_JOB"

-  error: (error) ->
+  error: () ->
     return if @finished
     @finished = true
     @gearman.sendCommand "WORK_FAIL", @handle


Add tests for only sending components-changed when definition changes

--- spec/coordinator.coffee
+++ spec/coordinator.coffee
@@ -47,6 +47,7 @@
       describe 'receiving same discovery message multiple times', ->
         added = []
         updated = []
+        libraryChanged = []
         definition =
           id: 'multiple-discovery-11'
           role: 'multiple-discovery'
@@ -54,6 +55,7 @@
           inports: []
           outports: []
         eventListener = null
+        libraryListener = null

         before (done) ->
           eventListener = (event, def) ->
@@ -65,6 +67,9 @@
             else
               return done new Error "Unexpected event #{event}"
           coordinator.on 'participant', eventListener
+          libraryListener = (names) ->
+            libraryChanged.push names
+          coordinator.library.on 'components-changed', libraryListener

           # send two times
           client.registerParticipant definition, (err) ->
@@ -76,6 +81,7 @@

         after (done) ->
           coordinator.removeListener 'participant', eventListener
+          coordinator.library.removeListener 'components-changed', libraryListener
           return done null

         it 'should emit participant-added only once', () ->
@@ -90,7 +96,10 @@
           chai.expect(d.id).to.equal definition.id
           diff = d.extra.lastSeen.getTime() - added[0].extra.lastSeen.getTime()
           chai.expect(diff, 'time difference').to.be.above 0
-
+        it 'should emit library components-changed only once', ->
+          chai.expect(libraryChanged).to.eql [
+            ['MultipleDiscovery']
+          ]

       describe.skip 'discovery for multiple participants with same role', ->
         # it 'should not be a new FBP node' # XXX: should be tested in ./protocol.coffee instead?
@@ -98,6 +107,7 @@
       describe 'discovery message changes component data', ->
         added = []
         updated = []
+        libraryChanged = []
         definition =
           id: 'multiple-discovery-11'
           role: 'multiple-discovery'
@@ -109,6 +119,7 @@
           ]
           outports: []
         eventListener = null
+        libraryListener = null
         before (done) ->
           eventListener = (event, def) ->
             if event == 'added'
@@ -119,6 +130,9 @@
             else
               return done new Error "Unexpected event #{event}"
           coordinator.on 'participant', eventListener
+          libraryListener = (names) ->
+            libraryChanged.push names
+          coordinator.library.on 'components-changed', libraryListener

           # send new definition
           client.registerParticipant definition, (err) ->
@@ -126,6 +140,7 @@

         after (done) ->
           coordinator.removeListener 'participant', eventListener
+          coordinator.library.removeListener 'components-changed', libraryListener
           return done null
         it 'should send component-changed', ->
           chai.expect(updated).to.have.length 1
@@ -133,7 +148,10 @@
           chai.expect(d.id).to.equal definition.id
         it 'should not send new participant', ->
           chai.expect(added).to.have.length 0
-
+        it 'should emit library components-changed', ->
+          chai.expect(libraryChanged).to.eql [
+            ['MultipleDiscovery']
+          ]
       describe 'receiving discovery message for pending connection', ->
         it 'should setup the connection'


Add some sweet spec helpers

Add some sweet spec helpers

--- spec/spec-helper.coffee
+++ spec/spec-helper.coffee
@@ -1,13 +1,33 @@
 require '../src/ext/svg-circle'
 require '../src/ext/svg-draggable'
 require '../src/ext/svg-export'
+
+util = require 'util'

 beforeEach ->
   jasmine.addMatchers
     toShow: ->
       compare: (actual) ->
-        {passed: actual.css('display') != 'none'}
+        pass = getComputedStyle(actual)['display'] isnt 'none'
+        {pass}

     toHide: ->
       compare: (actual) ->
-        {passed: actual.css('display') != 'none'}
+        pass = getComputedStyle(actual)['display'] is 'none'
+        {pass}
+
+    toHaveLength: ->
+      compare: (actual, expectedValue) ->
+        actualValue = actual.length
+        pass = actualValue is expectedValue
+        notStr = if pass then ' not' else ''
+        message = "Expected array with length #{actualValue} to#{notStr} have length #{expectedValue}"
+        {pass, message}
+
+    toHaveAttr: ->
+      compare: (actual, attr, expectedValue) ->
+        actualValue = actual.getAttribute(attr)
+        pass = actualValue is expectedValue
+        notStr = if pass then ' not' else ''
+        message = "Expected attr '#{attr}' to#{notStr} be #{JSON.stringify(expectedValue)} but it was #{JSON.stringify(actualValue)}"
+        {pass, message}


Fix spec_helper.coffee

Fix spec_helper.coffee

--- spec/spec_helper.coffee
+++ spec/spec_helper.coffee
@@ -11,23 +11,23 @@
   nock: require "nock"

 requirejs.config(
-  baseUrl: "/home/dev/workspace/github-issues-label-manager/lib/server/static"
+  baseUrl: "/home/dev/workspace/github-issues-label-manager"
   paths:
     # app
-    "app/main": "lib/app/js/main"
-    "app/application": "lib/app/js/application"
-    "app/config": "lib/app/js/config"
-    "app/common": "lib/app/js/common"
-    "app/repository": "lib/app/js/repository"
-    "app/repositories": "lib/app/js/repositories"
-    "app/page": "lib/app/js/page"
-    "app/labels": "lib/app/js/labels"
-    "app/label": "lib/app/js/label"
+    "app/main": "tmp/lib/app/js/main"
+    "app/application": "tmp/lib/app/js/application"
+    "app/config": "tmp/lib/app/js/config"
+    "app/common": "tmp/lib/app/js/common"
+    "app/repository": "tmp/lib/app/js/repository"
+    "app/repositories": "tmp/lib/app/js/repositories"
+    "app/page": "tmp/lib/app/js/page"
+    "app/labels": "tmp/lib/app/js/labels"
+    "app/label": "tmp/lib/app/js/label"
     # components
-    "com/jquery/jquery.pjax":             "lib/com/jquery-pjax/js/jquery.pjax"
-    "com/bootstrap/bootstrap":            "lib/com/bootstrap/js/bootstrap"
-    "com/backbone/backbone-fetch-cache":  "lib/com/backbone-fetch-cache/js/backbone.fetch-cache"
-    "com/sprintf/sprintf":                "lib/com/sprintf/js/sprintf"
+    "com/jquery/jquery.pjax":             "lib/server/static/lib/com/jquery-pjax/js/jquery.pjax"
+    "com/bootstrap/bootstrap":            "lib/server/static/lib/com/bootstrap/js/bootstrap"
+    "com/backbone/backbone-fetch-cache":  "lib/server/static/lib/com/backbone-fetch-cache/js/backbone.fetch-cache"
+    "com/sprintf/sprintf":                "lib/server/static/lib/com/sprintf/js/sprintf"
   shim:
     "com/jquery/jquery.pjax":
       exports: "jQuery.fn.pjax"


Send-after-connect only once

Send-after-connect only once

--- lib/Port.coffee
+++ lib/Port.coffee
@@ -31,7 +31,8 @@

     send: (data) ->
         return @socket.send data if @isConnected()
-        @socket.on "connect", =>
+
+        @socket.once "connect", =>
             @socket.send data
         @socket.connect()


String interpolation support for some global objects added

--- input.coffee
+++ input.coffee
@@ -22,6 +22,14 @@
 	CONSTANT_VARS = @CONSTANT_VARS = ['undefined', 'false', 'true', 'null', 'this', 'JSON']

 	cache = {}
+
+	GLOBAL =
+		Math: Math
+		Array: Array
+		Object: Object
+		Number: Number
+		RegExp: RegExp
+		String: String

 	@getVal = do ->
 		getFromElement = (elem, prop) ->
@@ -58,6 +66,8 @@
 				v = getFromObject file.storage, prop
 			if v is undefined
 				v = getElement file, prop
+			if v is undefined
+				v = GLOBAL[prop]

 			v

@@ -296,7 +306,7 @@
 			try
 				callFunc.call @
 			catch err
-				log.warn "Interpolated string error in '#{@text}';\n#{err}"
+				log.warn "Interpolated string error in '#{@text}';\n#{err.stack || err}"

 	clone: (original, self) ->
 		node = original.node.getCopiedElement @node, self.node


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/function_extensions.coffee
+++ src/function_extensions.coffee
@@ -3,6 +3,7 @@
   memo = undefined

   return ->
+    if

   _.once = function(func) {
     var ran = false, memo;


Added call to greyatom-tree-view package for connecting to remote-file-server after login.

--- lib/auth.coffee
+++ lib/auth.coffee
@@ -100,9 +100,11 @@
         token = url.parse(tokenUrl, true).query.ide_token
         if token?.length
           confirmOauthToken(token).then (res) ->
-           return unless res
-           _token.set(token)
-           resolve()
+            return unless res
+            _token.set(token)
+            if atom.project and atom.project.remoteftp
+              atom.project.remoteftp.connectToStudentFTP()
+            resolve()
         # console.log('TOKEN:', token)
         # _token.set(token)
         # resolve()
@@ -130,7 +132,7 @@
         githubLogin().then(resolve)

     #if not win.loadURL('https://learn.co/ide/sign_in?ide_onboard=true')
-    if not win.loadURL('http://35.154.96.42:3000/signin/')
+    if not win.loadURL('http://35.154.96.42:7000/signin/')
       win.destroy()
       #githubLogin.then(resolve)


Add type:pr to restrict search results to only pull requests

--- src/github/pullrequests.coffee
+++ src/github/pullrequests.coffee
@@ -14,7 +14,7 @@

   @fromKey: (key) ->
     octo.search.issues.fetch
-      q: "#{key} @#{Config.github.organization} state:open"
+      q: "#{key} @#{Config.github.organization} state:open type:pr"
     .then (json) ->
       return Promise.all json.items.map (issue) ->
         octo.fromUrl(issue.pullRequest.url).fetch() if issue.pullRequest?.url


move pan & zoom buttons

move pan & zoom buttons

--- app/components/frame-viewer.cjsx
+++ app/components/frame-viewer.cjsx
@@ -48,6 +48,7 @@
     subject = @props.subject
     frame = @props.frame
     {type, format, src} = getSubjectLocation @props.subject, @props.frame
+    console.log "TYPE", type
     FrameWrapper = @props.frameWrapper
     frameDisplay = switch type
       when 'image'
@@ -58,12 +59,6 @@

               <LoadingIndicator />

}
-

-            <button className={if @state.panEnabled then "toggle fa fa-arrows active" else "toggle fa fa-arrows"} title={"pan"} onClick={@togglePan} ></button>
-            <button className="zoom-out fa fa-minus" onClick={ @zoom.bind(this, 1.1 ) } />
-            <button className="zoom-in fa fa-plus" onClick={ @zoom.bind(this, .9) } />
-            <button className="reset" onClick={ this.zoomReset } >Reset</button>
-

       when 'video'

@@ -103,9 +98,19 @@

     if FrameWrapper
-      <FrameWrapper frame={frame} naturalWidth={@state.frameDimensions?.width or 0} naturalHeight={@state.frameDimensions?.height or 0} panByDrag={@panByDrag} viewBoxDimensions={@state.viewBoxDimensions or "0 0 0 0"} workflow={@props.workflow} subject={@props.subject} classification={@props.classification} annotation={@props.annotation} loading={@state.loading} onChange={@props.onChange} panEnabled={@state.panEnabled} >
-        {frameDisplay}
-      </FrameWrapper>
+

+        <FrameWrapper frame={frame} naturalWidth={@state.frameDimensions?.width or 0} naturalHeight={@state.frameDimensions?.height or 0} panByDrag={@panByDrag} viewBoxDimensions={@state.viewBoxDimensions or "0 0 0 0"} workflow={@props.workflow} subject={@props.subject} classification={@props.classification} annotation={@props.annotation} loading={@state.loading} onChange={@props.onChange} panEnabled={@state.panEnabled} >
+          {frameDisplay}
+        </FrameWrapper>
+

+          <button className={if @state.panEnabled then "toggle fa fa-arrows active" else "toggle fa fa-arrows"} title={"pan"} onClick={@togglePan} ></button>
+          <button className="zoom-out fa fa-minus" onClick={ @zoom.bind(this, 1.1 ) } />
+          <button className="zoom-in fa fa-plus" onClick={ @zoom.bind(this, .9) } />
+          <button className="reset" onClick={ this.zoomReset } >Reset</button>
+

+

+
+
     else
       frameDisplay


refactor: try using only `didInsertElement`

--- app/components/vnc-viewer.coffee
+++ app/components/vnc-viewer.coffee
@@ -52,10 +52,6 @@
     if @get 'file.isReady'
       @send("connect")

-
-  didUpdate: ->
-    @setupRFB()
-
   didInsertElement: ->
     @setupRFB()


prevent unnecessary propagation of event.

I at last understand how atom commands are implemented by following DOM
event propagation.
And noticed I don’t need this propagation to parent Node for
vim-mode-plus’s command.
Preventing propagation skip unnecessary command search by each node
stepping up to parent node.

--- lib/base.coffee
+++ lib/base.coffee
@@ -264,5 +264,6 @@
       vimState.domEvent = event
       # Reason: https://github.com/t9md/atom-vim-mode-plus/issues/85
       vimState.operationStack.run(this)
+    event.stopPropagation()

 module.exports = Base


Include language with classifications

Include language with classifications

--- app/models/classification.coffee
+++ app/models/classification.coffee
@@ -40,6 +40,8 @@
       annotation[key] = value
       annotation

+    metaAnnotations.push language: $(document.body.parentNode).attr 'data-language'
+
     classification:
       subject_ids: [@subject.id]
       annotations: @annotations.concat metaAnnotations


scrollComponent

scrollComponent

--- framer/Components/NavComponent.coffee
+++ framer/Components/NavComponent.coffee
@@ -51,7 +51,7 @@
 			@showNext(layer)
 		else
 			@_tempScroll = new ScrollComponent
-				name: "scroll"
+				name: "scrollComponent"
 				parent: @
 				width: @width
 				height: @height
@@ -81,6 +81,8 @@
 	@define "previous",
 		get: -> return @_stack[@_stack.length - 2]?.layer

+	@define "scrollComponent",
+		get: -> return @_stack[@_stack.length - 1]?

 	##############################################################
 	# Header and footer
@@ -243,7 +245,7 @@

 		if not scroll
 			scroll = new ScrollComponent
-			scroll.name = "scroll"
+			scroll.name = "scrollComponent"
 			scroll.backgroundColor = @backgroundColor
 			layer[NavComponentLayerScrollKey] = scroll
 			layer.parent = scroll.content


comments

comments

--- lib/ccss.coffee
+++ lib/ccss.coffee
@@ -4,10 +4,10 @@
   css = ''

   for selector, pairs of rules
-    # a pair is either a css declaration, or a nested rule
     declarations = ''
     nested = {}

+    #add mixins to the current level
     if {mixin} = pairs
       delete pairs.mixin
       unless mixin instanceof Array
@@ -16,6 +16,7 @@
         for key, value of mix
           pairs[key] = value

+    #a pair is either a css declaration, or a nested rule
     for key, value of pairs
       if typeof value is 'object'
         nested["#{selector} #{key}"] = value


Don’t use state. Modify the dom directly

--- src/highlight-component.coffee
+++ src/highlight-component.coffee
@@ -8,14 +8,9 @@

   render: ->
     {startPixelPosition, endPixelPosition, decoration} = @props
-    {flash} = @state

     className = 'highlight'
     className += " #{decoration.class}" if decoration.class?
-
-    if flash?
-      className += " #{flash.class}"
-      @flashTimeout = setTimeout(@turnOffFlash, flash.duration ? 500)

     div {className},
       if endPixelPosition.top is startPixelPosition.top
@@ -23,26 +18,23 @@
       else
         @renderMultiLineRegions()

-  componentWillMount: ->
-    @state.flash = @props.decoration.flash
+  componentDidMount: ->
+    @startFlashAnimation() if @props.decoration.flash?

-  componentWillUpdate: (newProps) ->
-    if newProps.decoration.flash?
-      if @flashTimeout?
-        # This happens when re-rendered before the flash finishes. We need to
-        # render _without_ the flash class first, then re-render with the
-        # flash class. Otherwise there will be no flash.
-        clearTimeout(@flashTimeout)
-        setImmediate => @setState(flash: newProps.decoration.flash)
-        @flashTimeout = null
-        @state.flash = null
-      else
-        @state.flash = newProps.decoration.flash
+  componentDidUpdate: ->
+    @startFlashAnimation() if @props.decoration.flash?

-  turnOffFlash: ->
-    clearTimeout(@flashTimeout)
-    @flashTimeout = null
-    @setState(flash: null)
+  startFlashAnimation: ->
+    {flash} = @props.decoration
+
+    node = @getDOMNode()
+    node.classList.remove(flash.class)
+
+    requestAnimationFrame =>
+      node.classList.add(flash.class)
+      clearTimeout(@flashTimeoutId)
+      removeFlashClass = -> node.classList.remove(flash.class)
+      @flashTimeoutId = setTimeout(removeFlashClass, flash.duration ? 500)

   renderSingleLineRegions: ->
     {startPixelPosition, endPixelPosition, lineHeightInPixels} = @props
@@ -91,4 +83,4 @@
     regions

   shouldComponentUpdate: (newProps, newState) ->
-    newState.flash isnt @state.flash or not isEqualForProperties(newProps, @props, 'startPixelPosition', 'endPixelPosition', 'lineHeightInPixels')
+    not isEqualForProperties(newProps, @props, 'startPixelPosition', 'endPixelPosition', 'lineHeightInPixels')


using cwd as a base to create data folder

--- src/lib/runPhantomStats.coffee
+++ src/lib/runPhantomStats.coffee
@@ -35,7 +35,7 @@
     result[stat] = value

   file = "./data/#{pageName}.json"
-  ensureDirectoryExists('./data', ->
+  ensureDirectoryExists(process.cwd() + '/data', ->
     fs.unlink(file, (err) -> fs.writeFile(file, JSON.stringify(result), (err) -> callback(err is null, err)))
   )


re-order the menu

re-order the menu

--- menus/jekyll.cson
+++ menus/jekyll.cson
@@ -5,12 +5,12 @@
     'submenu': [
       'label': 'Jekyll'
       'submenu': [
+        { 'label': 'Manage Jekyll', 'command': 'jekyll:manage'},
         { 'label': 'New Post', 'command': 'jekyll:new-post' },
         { 'label': 'Open Config', 'command': 'jekyll:open-config' },
         { 'label': 'Open Layout', 'command': 'jekyll:open-layout' },
         { 'label': 'Open Include', 'command': 'jekyll:open-include' },
-        { 'label': 'Open Data File', 'command': 'jekyll:open-data'},
-        { 'label': 'Manage Jekyll', 'command': 'jekyll:manage'}
+        { 'label': 'Open Data File', 'command': 'jekyll:open-data'}
       ]
     ]
   }


:art: Avoid two co-current lints

:art: Avoid two co-current lints

--- lib/plus.coffee
+++ lib/plus.coffee
@@ -26,12 +26,12 @@
       return unless atom.workspace.getActiveTextEditor()
       @lint()
     @Subscriptions.add atom.workspace.observeTextEditors (editor)=>
-      return if @InProgress
       return unless editor.getPath()
       editor.onDidSave(@lint.bind(@))
       @Subscriptions.add editor.onDidChangeCursorPosition ({newBufferPosition})=>
         @View.updateBubble(newBufferPosition)
   lint:->
+    return if @InProgress
     @InProgress = true

     ActiveEditor = atom.workspace.getActiveTextEditor()


make that obvious

make that obvious

--- spec/javascripts/ajax_tokenizer_spec.js.coffee
+++ spec/javascripts/ajax_tokenizer_spec.js.coffee
@@ -2,7 +2,7 @@
   describe "ajaxTokenizer", ->
     httpBackend = null
     ajaxTokenizer = null
-    CARDCONNECT_AJAX_URL = 'fts.prinpay.com/cardsecure'
+    CARDCONNECT_AJAX_URL = 'some.url'

     beforeEach module("cardConnect")


Fetching bugfix

Fetching bugfix

--- fetching.coffee
+++ fetching.coffee
@@ -101,7 +101,7 @@

                 # There may be changes to @_requestersByQs that we couldn't act on
                 # until this request was done.
-                Tracker.afterFlush @remergeQueries, 0.8
+                Tracker.afterFlush (=> @remergeQueries()), 0.8

     requestQuery: (querySpec) ->
@@ -125,7 +125,7 @@
                         if _.isEmpty @_requestersByQs[qsString]
                             delete @_requestersByQs[qsString]
                     @_requestsChanged = true
-                    Tracker.afterFlush @remergeQueries, 0.8
+                    Tracker.afterFlush (=> @remergeQueries()), 0.8

         if @isQueryReady querySpec
             modelClass = J.models[querySpec.modelName]
@@ -136,7 +136,7 @@

         if Tracker.active
             @_requestsChanged = true
-            Tracker.afterFlush @remergeQueries, 0.8
+            Tracker.afterFlush (=> @remergeQueries()), 0.8
             throw J.makeValueNotReadyObject()
         else
             undefined


Fix Promise.resolve

Fix Promise.resolve

--- lib/util.coffee
+++ lib/util.coffee
@@ -132,12 +132,12 @@
       if atom.config.get('haskell-ghc-mod.cabalSandbox')
         Util.getCabalSandbox(rootPath)
       else
-        Promise.resolve # undefined
+        Promise.resolve() # undefined
     stackSandbox =
       if atom.config.get('haskell-ghc-mod.stackSandbox')
         Util.getStackSandbox(rootPath, apd, objclone(env))
       else
-        Promise.resolve # undefined
+        Promise.resolve() # undefined
     res =
       Promise.all([cabalSandbox, stackSandbox])
       .then ([cabalSandboxDir, stackSandboxDirs]) ->


Removed more logging.

Removed more logging.

--- web/coffee/management.coffee
+++ web/coffee/management.coffee
@@ -25,7 +25,6 @@
     #get_all_problems
     apiCall "GET", "/api/admin/problems/submissions"
     .done ((api) ->
-      console.log api.data
       @setState React.addons.update @state,
         submissions: $set: api.data
     ).bind this


Update tags metadescription

Update tags metadescription

--- models/tag.coffee
+++ models/tag.coffee
@@ -26,4 +26,4 @@
     if @get('description')
       _s.clean(@mdToHtmlToText('description'))
     else
-      "Artworks with the #{@get('name')} tag on Artsy"
+      "Browse artworks with the #{@get('name')} tag on Artsy. Artsy has the largest collection of art on the Web; browse art by subject matter, medium, size and price."


set desiredState per service

set desiredState per service

--- imports/server/stateReconciler.coffee
+++ imports/server/stateReconciler.coffee
@@ -60,7 +60,7 @@
 f = true

 setServiceField = (fieldName, fieldValue, labels) ->
-  if (name = labels['bigboat.instance.name']) and (service = labels['bigboat.service.name']) and labels['bigboat.service.type'] is 'service'
+  if (name = labels['bigboat.instance.name']) and (service = labels['bigboat.service.name']) and labels['bigboat.service.type'] in ['service', 'oneoff']
     Instances.upsert {name: name}, $set: {"services.#{service}.#{fieldName}": fieldValue}

 labelsToObject = (labels) ->
@@ -97,7 +97,7 @@
     unless mappedState in SERVICE_STATES
       throw "Service state '#{mappedState}' is not supported."
     if name = labels['bigboat.instance.name']
-      if (type = labels['bigboat.service.type']) is 'service'
+      if (type = labels['bigboat.service.type']) in ['service', 'oneoff']
         properties = labelsToObject labels
         updateDoc =
           name: name
@@ -111,6 +111,9 @@
         service = labels['bigboat.service.name']

         updateDoc["services.#{service}.state"] = mappedState
+        updateDoc["services.#{service}.desiredState"] = switch type
+          when 'service' then ['running']
+          when 'oneoff' then ['running', 'stopped']
         updateDoc["services.#{service}.properties"] = properties
         updateDoc.status = "Starting #{service}" if mappedState is 'starting'
         updateDoc.status = "Stopping #{service}" if mappedState is 'stopping'


lets just not worry about Foxie._beenAppended for now


Sort tooltip chart by listens

Sort tooltip chart by listens

--- elaboratecharts/coffee/index.coffee
+++ elaboratecharts/coffee/index.coffee
@@ -27,12 +27,13 @@
           \
             #{ Highcharts.dateFormat('%A, %b %e, %Y', @x) }\
           """
-        $.each @points.reverse(), ->
-          if @y > 0
+        points = _.sortBy(@points, (point) -> point.y)
+        for point in points.reverse()
+          if point.y > 0
             s += """\

\
-              ● \
-              #{ @series.name }: #{ @y }"""
+              ● \
+              #{ point.series.name }: #{ point.y }"""
         return s
     plotOptions:
       area:


Fix bad connection debugging parameters

Fix bad connection debugging parameters

--- services/web/public/coffee/ide/editor/ShareJsDoc.coffee
+++ services/web/public/coffee/ide/editor/ShareJsDoc.coffee
@@ -31,7 +31,7 @@
 					if window.disconnectOnUpdate? and Math.random() < window.disconnectOnUpdate
 						console.log "Disconnecting on update", update
 						window._ide.socket.socket.disconnect()
-					if window.dropUpdates? and Math.random() < window.dropAcks
+					if window.dropUpdates? and Math.random() < window.dropUpdates
 						console.log "Simulating a lost update", update
 						return
 					@socket.emit "applyOtUpdate", @doc_id, update


Can now delete a password entry.

Can now delete a password entry.

--- app/assets/javascripts/main.coffee
+++ app/assets/javascripts/main.coffee
@@ -443,6 +443,12 @@

     pwgAjax.post url, data, onSuccess

+
+  deleteEntry = (pw) ->
+    $scope.confirm("Really delete #{pw.name}?", "Confirm deletion").then (result) ->
+      url = $("#config").data("delete-pwentry-url").replace("0", pw.id)
+      pwgAjax.delete url, ->
+        $scope.showAll()

   cancelEdit = (pw) ->
     _.extend pw, originalEntries[pw.id]


nitrogen6x.coffee: Update getting started links

Signed-off-by: Zachary Walchuk <9de8c4480303b5335cd2a33eefe814615ba3612a@resin.io>

--- nitrogen6x.coffee
+++ nitrogen6x.coffee
@@ -11,9 +11,9 @@

 	instructions: commonImg.instructions
 	gettingStartedLink:
-		windows: 'http://docs.resin.io/#/pages/installing/gettingStarted-Nitrogen6x.md#windows'
-		osx: 'http://docs.resin.io/#/pages/installing/gettingStarted-Nitrogen6x.md#on-mac-and-linux'
-		linux: 'http://docs.resin.io/#/pages/installing/gettingStarted-Nitrogen6x.md#on-mac-and-linux'
+		windows: 'http://docs.resin.io/nitrogen6x/nodejs/getting-started/#adding-your-first-device'
+		osx: 'http://docs.resin.io/nitrogen6x/nodejs/getting-started/#adding-your-first-device'
+		linux: 'http://docs.resin.io/nitrogen6x/nodejs/getting-started/#adding-your-first-device'
 	supportsBlink: false

 	yocto:


fix build-html task bug

fix build-html task bug

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -32,6 +32,7 @@
       minutes: entry.minutes
       pubdate: entry.pubdate
       tags: entry.tags
+      title: entry.title
       titleKey: titleKey
     paths: [
       dir + '/' + titleKey + '/index.html'


Fix packager ignore config

Fix packager ignore config

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -126,7 +126,7 @@
   arch: "x64"
   version: "0.34.0"
   prune: true
-  ignore: new RegExp("(^\/node_modules\/(?!#{INCLUDE_MODULES.join("|")}).*|gulpfile\.coffee|^dist/.*|^src/.*|^sass/.*|.*\.js\.map$)")
+  ignore: new RegExp("(^\/node_modules\/(?!#{INCLUDE_MODULES.join("|")}).*|gulpfile\.coffee|^/dist/.*|^/src/.*|^/sass/.*|.*\.js\.map$)")
   asar: true
   overwrite: true
   "app-version": VERSION


mocha: Reduce `slow` option value to 1 second

Signed-off-by: Thodoris Greasidis <5791bdc4541e79bd43d586e3c3eff1e39c16665f@balena.io>

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -41,7 +41,7 @@
 			require: ['ts-node/register'],
 			compilers: 'coffee:coffeescript/register',
 			timeout: 5 * 60 * 1000,
-			slow: 10 * 1000
+			slow: 1000
 		}))

 gulp.task 'build', (callback) ->


Use subject image's natural dimensions to determine scale

--- app/classifier/subject-viewer.cjsx
+++ app/classifier/subject-viewer.cjsx
@@ -12,16 +12,16 @@
   displayName: 'SubjectViewer'

   getInitialState: ->
-    width: 0
-    height: 0
+    naturalWidth: 0
+    naturalHeight: 0
     selectedMark: null

   getScale: ->
     ALMOST_ZERO = 0.01 # Prevent divide-by-zero errors when there is no image.
     rect = @refs.subjectContainer?.getDOMNode().getBoundingClientRect()
     rect ?= width: 0, height: 0
-    horizontal = rect.width / @state.width || ALMOST_ZERO
-    vertical = rect.height / @state.height || ALMOST_ZERO
+    horizontal = rect.width / @state.naturalWidth || ALMOST_ZERO
+    vertical = rect.height / @state.naturalHeight || ALMOST_ZERO
     {horizontal, vertical}

   getEventOffset: (e) ->
@@ -62,12 +62,12 @@
     <img className="subject-image" src={knownGood} onLoad={@handleImageLoad} style={display: 'block'} />

   renderMarkingSVG: ->
-    viewBox = [0, 0, @state.width, @state.height]
+    viewBox = [0, 0, @state.naturalWidth, @state.naturalHeight]
     svgStyle = height: '100%', left: 0, position: 'absolute', top: 0, width: '100%'

     <svg className="subject-viewer-svg" viewBox={viewBox} preserveAspectRatio="none" data-tool={@props.selectedDrawingTool?.type} style={svgStyle}>
       <Draggable onStart={@handleInitStart} onDrag={@handleInitDrag} onEnd={@handleInitRelease}>
-        <rect width={@state.width} height={@state.height} fill="transparent" stroke="none" />
+        <rect width={@state.naturalWidth} height={@state.naturalHeight} fill="transparent" stroke="none" />
       </Draggable>

       <g className="subject-viewer-tools">{@renderTools()}</g>
@@ -96,9 +96,9 @@
         <Tool key={mark._id} {...toolProps} {...toolFunctions} />

   handleImageLoad: (e) ->
-    {width, height} = e.target
-    unless @state.width is width and @state.height is height
-      @setState {width, height}
+    {naturalWidth, naturalHeight} = e.target
+    unless @state.naturalWidth is naturalWidth and @state.naturalHeight is naturalHeight
+      @setState {naturalWidth, naturalHeight}

   handleFrameChange: (index) ->
     @props.classification.update _frame: index


Correct Visible spelling

Correct Visible spelling

--- app/coffeescripts/menu-data.coffee
+++ app/coffeescripts/menu-data.coffee
@@ -10,7 +10,7 @@
 $a.nav_bar_menu_items = {
     'File': {
               'New' : (() -> alert('Not Configured'))
-              'Open Local Network' : (() -> $a.broker.trigger("app:open_scenario"))
+              'Open Local Network' : (() -> $a.broker.trigger("app:open_scenario"))
               'Save Local Network' : (() ->  $a.broker.trigger("app:save_scenario"))
               'Close Local Network' : (() -> $a.broker.trigger('map:clear_map'))
               'Import Local Network' : (() -> alert('Not Configured'))
@@ -69,27 +69,27 @@

 # Layers Menu
 $a.layers_node_type_list = [
-       { label: 'Freeway', event: 'toggleVisabilty', triggerShow: "map:nodes:show_freeway", triggerHide: "map:nodes:hide_freeway" }
-       { label: 'Highway',   event: 'toggleVisabilty', triggerShow: "map:nodes:show_highway", triggerHide: "map:nodes:hide_higway"  }
-       { label: 'Signalized Intersections', event: 'toggleVisabilty', triggerShow: "map:nodes:show_signalized_intersection", triggerHide: "map:nodes:hide_signalized_intersection"  }
-       { label: 'Simple', event: 'toggleVisabilty', triggerShow: "map:nodes:show_simple", triggerHide: "map:nodes:hide_simple" }
-       { label: 'Stop Intersections',   event: 'toggleVisabilty', triggerShow: "map:nodes:show_stop_intersection", triggerHide: "map:nodes:hide_stop_intersection"  }
-       { label: 'Terminals', event: 'toggleVisabilty', triggerShow: "map:nodes:show_terminal", triggerHide: "map:nodes:hide_terminal"  }
-       { label: 'Other', event: 'toggleVisabilty', triggerShow: "map:nodes:show_other", triggerHide: "map:nodes:hide_other"  }
+       { label: 'Freeway', event: 'toggleVisible', triggerShow: "map:nodes:show_freeway", triggerHide: "map:nodes:hide_freeway" }
+       { label: 'Highway',   event: 'toggleVisible', triggerShow: "map:nodes:show_highway", triggerHide: "map:nodes:hide_higway"  }
+       { label: 'Signalized Intersections', event: 'toggleVisible', triggerShow: "map:nodes:show_signalized_intersection", triggerHide: "map:nodes:hide_signalized_intersection"  }
+       { label: 'Simple', event: 'toggleVisible', triggerShow: "map:nodes:show_simple", triggerHide: "map:nodes:hide_simple" }
+       { label: 'Stop Intersections',   event: 'toggleVisible', triggerShow: "map:nodes:show_stop_intersection", triggerHide: "map:nodes:hide_stop_intersection"  }
+       { label: 'Terminals', event: 'toggleVisible', triggerShow: "map:nodes:show_terminal", triggerHide: "map:nodes:hide_terminal"  }
+       { label: 'Other', event: 'toggleVisible', triggerShow: "map:nodes:show_other", triggerHide: "map:nodes:hide_other"  }
      ]

 $a.layers_link_type_list = [
-      { label: 'Elec. toll coll. lanes', event: 'toggleVisabilty', triggerShow: "map:links:show_electric_toll", triggerHide: "map:links:hide_electric_toll" }
-      { label: 'Freeway mainlines', event: 'toggleVisabilty', triggerShow: "map:links:show_freeway", triggerHide: "map:links:hide_freeway"  }
-      { label: 'Highway mainlines', event: 'toggleVisabilty', triggerShow: "map:links:show_highway", triggerHide: "map:links:hide_highway"  }
-      { label: 'Heavy vehicle lanes', event: 'toggleVisabilty', triggerShow: "map:links:show_heavy_vehicle", triggerHide: "map:links:hide_heavy_vehicle" }
-      { label: 'HOV lanes', event: 'toggleVisabilty', triggerShow: "map:links:show_hov", triggerHide: "map:links:hide_hov"  }
-      { label: 'HOT lanes', event: 'toggleVisabilty', triggerShow: "map:links:show_hot", triggerHide: "map:links:hide_hot"  }
-      { label: 'Interconnects', event: 'toggleVisabilty', triggerShow: "map:links:show_freeway_connector", triggerHide: "map:links:hide_freeway_connector" }
-      { label: 'Intersection Approach', event: 'toggleVisabilty', triggerShow: "map:links:show_intersection_approach", triggerHide: "map:links:hide_intersection_approach" }
-      { label: 'On-ramps', event: 'toggleVisabilty', triggerShow: "map:links:show_onramp", triggerHide: "map:links:hide_onramp"  }
-      { label: 'Off-ramps', event: 'toggleVisabilty', triggerShow: "map:links:show_offramp", triggerHide: "map:links:hide_offramp"  }
-      { label: 'Streets', event: 'toggleVisabilty', triggerShow: "map:links:show_street", triggerHide: "map:links:hide_street"  }
+      { label: 'Elec. toll coll. lanes', event: 'toggleVisible', triggerShow: "map:links:show_electric_toll", triggerHide: "map:links:hide_electric_toll" }
+      { label: 'Freeway mainlines', event: 'toggleVisible', triggerShow: "map:links:show_freeway", triggerHide: "map:links:hide_freeway"  }
+      { label: 'Highway mainlines', event: 'toggleVisible', triggerShow: "map:links:show_highway", triggerHide: "map:links:hide_highway"  }
+      { label: 'Heavy vehicle lanes', event: 'toggleVisible', triggerShow: "map:links:show_heavy_vehicle", triggerHide: "map:links:hide_heavy_vehicle" }
+      { label: 'HOV lanes', event: 'toggleVisible', triggerShow: "map:links:show_hov", triggerHide: "map:links:hide_hov"  }
+      { label: 'HOT lanes', event: 'toggleVisible', triggerShow: "map:links:show_hot", triggerHide: "map:links:hide_hot"  }
+      { label: 'Interconnects', event: 'toggleVisible', triggerShow: "map:links:show_freeway_connector", triggerHide: "map:links:hide_freeway_connector" }
+      { label: 'Intersection Approach', event: 'toggleVisible', triggerShow: "map:links:show_intersection_approach", triggerHide: "map:links:hide_intersection_approach" }
+      { label: 'On-ramps', event: 'toggleVisible', triggerShow: "map:links:show_onramp", triggerHide: "map:links:hide_onramp"  }
+      { label: 'Off-ramps', event: 'toggleVisible', triggerShow: "map:links:show_offramp", triggerHide: "map:links:hide_offramp"  }
+      { label: 'Streets', event: 'toggleVisible', triggerShow: "map:links:show_street", triggerHide: "map:links:hide_street"  }
     ]

 $a.layers_menu = [
@@ -101,8 +101,9 @@
   { label: 'Hide all links', event: ((e) -> $a.broker.trigger('map:hide_link_layer')) }
   { label: 'Links', className: 'dropdown submenu', href: '#linkTypeList', link: 'linkTypeList', items: $a.layers_link_type_list }
   { className: 'divider' }
-  { label: 'Events', event: 'toggleVisabilty', triggerShow: "map:show_event_layer", triggerHide: "map:hide_event_layer" }
-  { label: 'Controllers',  event: 'toggleVisabilty', triggerShow: "map:show_controller_layer", triggerHide: "map:hide_controller_layer" }
-  { label: 'Sensors', event: 'toggleVisabilty', triggerShow: "map:show_sensor_layer", triggerHide: "map:hide_sensor_layer" }
+  { label: 'Events', event: 'toggleVisible', triggerShow: "map:show_event_layer", triggerHide: "map:hide_event_layer" }
+  { label: 'Controllers',  event: 'toggleVisible', triggerShow: "map:show_controller_layer", triggerHide: "map:hide_controller_layer" }
+  { label: 'Sensors', event: 'toggleVisible', triggerShow: "map:show_sensor_layer", triggerHide: "map:hide_sensor_layer" }
+  { label: 'Demands', event: 'toggleVisible', triggerShow: "map:show_demand_layer", triggerHide: "map:hide_demand_layer" }
 ]


fix test, wait for animation

fix test, wait for animation

--- test/integration/dataset.coffee
+++ test/integration/dataset.coffee
@@ -88,6 +88,10 @@
         before (done) ->
           browser.get "#{url}/", done

+        # wait for animation :(
+        before (done) ->
+          setTimeout done, 500
+
         it 'should display the home page', (done) ->
           browser.url (err, url) ->
             url.should.match /\/$/


Move categories spec to bottom.

Move categories spec to bottom.

--- spec/javascripts/application/seeds_store_spec.js.coffee
+++ spec/javascripts/application/seeds_store_spec.js.coffee
@@ -69,17 +69,6 @@
       p = @subject.readPod('c').then(@normalizePods)
       expect(p).eventually
         .eql([@listByName()[2]]).notify(done)
-    describe 'updateCategories()', ->
-      beforeEach (done) ->
-        expect(@subject.updateCategories())
-          .eventually.notify(done)
-      it 'categories()', (done) ->
-        expect(@subject.categories()).eventually
-          .eql([
-            {name: 'a', podsCount: 1},
-            {name: 'b', podsCount: 1},
-            {name: 'c', podsCount: 4}
-          ]).notify(done)
     expectReadAll = () ->
       describe 'readFromAll()', ->
         it 'from the beginning', (done) ->
@@ -152,3 +141,14 @@
           @category = 'c'
         expectReadAll()
         expectReadCategory()
+    describe 'updateCategories()', ->
+      beforeEach (done) ->
+        expect(@subject.updateCategories())
+          .eventually.notify(done)
+      it 'categories()', (done) ->
+        expect(@subject.categories()).eventually
+          .eql([
+            {name: 'a', podsCount: 1},
+            {name: 'b', podsCount: 1},
+            {name: 'c', podsCount: 4}
+          ]).notify(done)


removed extra lines

removed extra lines

--- test/unit/api/input_test.coffee
+++ test/unit/api/input_test.coffee
@@ -70,5 +70,3 @@
     it 'succeeds if all conditions are met', ->
       input.clearAndType(selector, keys)

-
-


Updates calculator error message to "Error" [#3650608]

--- javascripts/widgets/calculator.coffee
+++ javascripts/widgets/calculator.coffee
@@ -192,7 +192,7 @@
     @display()

   errorMsg: ->
-    "This is an error."
+    "Error"

   reset: ->
     @current_expression.reset()


Fixed checkbox unit test

Fixed checkbox unit test

--- test/unit/specs/checkbox.coffee
+++ test/unit/specs/checkbox.coffee
@@ -1,4 +1,3 @@
-Vue = require 'vue'
 utils = require '../utils.coffee'

 describe 'Checkbox', ->
@@ -7,13 +6,13 @@
   checkLabel = null
   before (done) ->
     app.currentComponent = 'checkbox'
-    Vue.nextTick ->
-      document.utils = utils
-      check = $('#check')
-      checkLabel = $('label[for=check]')
+    utils.nextTick()
+    .then ->
+      check = $ '#check'
+      checkLabel = $ 'label[for=check]'
       done()
   it 'exists', ->
-    check.should.exists
+    check.should.exist
     check.should.have.prop 'type', 'checkbox'
   it 'is updraded', ->
     span = $ 'label[for=check] span:nth-child(2)'
@@ -22,7 +21,7 @@
     .and.have.text 'Check me'
   it 'is checked', ->
     check.should.be.checked
-    #check.should.have.class 'is-checked'
+    checkLabel.should.have.class 'is-checked'
   it 'can be unchecked', (done) ->
     vm.check = false
     utils.nextTick()
@@ -60,4 +59,46 @@
     $ 'label[for=check-dyn]'
     .should.have.class 'added-class'
   it 'can use an array', ->
-    debugger
+    $ '#id-0'
+    .should.not.be.checked
+    $ '#id-1'
+    .should.not.be.checked
+    $ '#id-2'
+    .should.not.be.checked
+    vm.checks = ['id-0']
+    utils.nextTick()
+    .then ->
+      $ '#id-0'
+      .should.be.checked
+      $ '#id-1'
+      .should.not.be.checked
+      $ '#id-2'
+      .should.not.be.checked
+      vm.checks = ['id-0', 'id-1']
+      utils.nextTick()
+    .then ->
+      $ '#id-0'
+      .should.be.checked
+      $ '#id-1'
+      .should.be.checked
+      $ '#id-2'
+      .should.not.be.checked
+      vm.checks = ['id-1']
+      utils.nextTick()
+    .then ->
+      $ '#id-0'
+      .should.not.be.checked
+      $ '#id-1'
+      .should.be.checked
+      $ '#id-2'
+      .should.not.be.checked
+      vm.checks = ['id-1', 'id-0']
+      utils.nextTick()
+    .then ->
+      $ '#id-0'
+      .should.be.checked
+      $ '#id-1'
+      .should.be.checked
+      $ '#id-2'
+      .should.not.be.checked
+


Correction du mode pause bloqué par le changement d'onglet

--- js/game/core/Game.coffee
+++ js/game/core/Game.coffee
@@ -91,7 +91,7 @@

         # Lors changement de visibilité de la fenêtre
         handleVisibilityChange: (visible) ->
-            @pause = yes if not visible
+            @pause = not visible

         # Progression du chargement
         handleLoadProgress: (e) ->


Unused sharedDataService from top controller

--- app/scripts/controllers/top.coffee
+++ app/scripts/controllers/top.coffee
@@ -13,7 +13,6 @@
     '$q'
     'GithubUserApiService'
     'GithubGistsApiService'
-    'sharedDataService'
     'authorName'
     'injectGithubApiKey'
     (
@@ -21,7 +20,6 @@
       $q
       GithubUserApiService
       GithubGistsApiService
-      sharedDataService
       authorName
       injectGithubApiKey
     ) ->
@@ -30,30 +28,15 @@
       $scope.error = undefined
       $scope.injectGithubApiKey = injectGithubApiKey

-      $scope.$watch(
-        () ->
-          return sharedDataService.author
-        (newVal, oldVal, scope) ->
-          scope.articles[0] = newVal
-      )
-
-      $scope.$watchCollection(
-        () ->
-          return sharedDataService.gists
-        (newCollection, oldCollection, scope) ->
-          scope.articles = scope.articles.concat newCollection
-      )
-
       githubPromises = $q.all [
         GithubUserApiService(authorName)
-          .then (res) ->
-            sharedDataService.author = res.data
-
         GithubGistsApiService()
-          .then (res) ->
-            sharedDataService.gists = sharedDataService.gists.concat res.data
       ]
       githubPromises
+        .then (results) ->
+          $scope.articles[0] = results[0].data
+          $scope.articles = $scope.articles.concat results[1].data
+
         .catch (err) ->
           $scope.error = err


The -> This

The -> This

--- src/browser/main.coffee
+++ src/browser/main.coffee
@@ -123,7 +123,7 @@
   options.string('socket-path')

   # Deprecated 1.0 API preview flag
-  options.alias('1', 'one').boolean('1').describe('1', 'The option is no longer supported. Atom now defaults to launching with the 1.0 API. Use --include-deprecated-apis to run Atom with deprecated APIs.')
+  options.alias('1', 'one').boolean('1').describe('1', 'This option is no longer supported. Atom now defaults to launching with the 1.0 API. Use --include-deprecated-apis to run Atom with deprecated APIs.')

   args = options.argv


Log uncaught exceptions in browser process

--- src/browser/main.coffee
+++ src/browser/main.coffee
@@ -13,6 +13,10 @@

 console.log = (args...) ->
   nslog(args.map((arg) -> JSON.stringify(arg)).join(" "))
+
+process.on 'uncaughtException', (error={}) ->
+  nslog(error.message) if error.message?
+  nslog(error.stack) if error.stack?

 delegate.browserMainParts.preMainMessageLoopRun = ->
   args = parseCommandLine()


Update addBlacklist.coffee

Update addBlacklist.coffee

--- src/routes/addBlacklist.coffee
+++ src/routes/addBlacklist.coffee
@@ -9,7 +9,7 @@
         false

     # Create blacklist.list if not exists
-    fs.closeSync fs.openSync path, 'w'
+    fs.closeSync fs.openSync path, 'a'

     fs.readFile path, encoding: 'utf8', (error, data) ->
         if error


Convert point-like arguments to Point objects

--- src/display-layer.coffee
+++ src/display-layer.coffee
@@ -62,6 +62,8 @@
     text

   translateBufferPosition: (bufferPosition, options) ->
+    bufferPosition = Point.fromObject(bufferPosition)
+
     @patchIterator.seekToInputPosition(bufferPosition)
     if @patchIterator.inChange()
       if options?.clipDirection is 'forward'
@@ -72,6 +74,8 @@
       @patchIterator.translateInputPosition(@buffer.clipPosition(bufferPosition, options))

   translateScreenPosition: (screenPosition, options) ->
+    screenPosition = Point.fromObject(screenPosition)
+
     @patchIterator.seekToOutputPosition(screenPosition)
     if @patchIterator.inChange()
       if options?.clipDirection is 'forward'
@@ -82,6 +86,8 @@
       @buffer.clipPosition(@patchIterator.translateOutputPosition(screenPosition), options)

   clipScreenPosition: (screenPosition, options) ->
+    screenPosition = Point.fromObject(screenPosition)
+
     @patchIterator.seekToOutputPosition(screenPosition)
     if @patchIterator.inChange()
       if options?.clipDirection is 'forward'


Parameterize fold character

Parameterize fold character

…so that we can use a zero-width nbsp in Atom.

--- src/display-layer.coffee
+++ src/display-layer.coffee
@@ -84,6 +84,7 @@
       showIndentGuides: settings.showIndentGuides ? false,
       ratioForCharacter: settings.ratioForCharacter ? -> 1.0,
       isWrapBoundary: settings.isWrapBoundary ? isWordStart
+      foldCharacter: settings.foldCharacter ? '⋯'
     })

   serialize: ->
@@ -102,7 +103,7 @@
     for id, displayMarkerLayer of @displayMarkerLayersById
       displayMarkerLayer.destroy()

-  reset: ({@tabLength, @invisibles, @showIndentGuides, @softWrapColumn, @softWrapHangingIndent, @ratioForCharacter, @isWrapBoundary}) ->
+  reset: ({@tabLength, @invisibles, @showIndentGuides, @softWrapColumn, @softWrapHangingIndent, @ratioForCharacter, @isWrapBoundary, @foldCharacter}) ->
     @eolInvisibles = {
       "\r": @invisibles.cr
       "\n": @invisibles.eol
@@ -328,7 +329,7 @@
         if not character?
           characterWidth = 0
         else if foldEndBufferPosition?
-          characterWidth = @ratioForCharacter('⋯')
+          characterWidth = @ratioForCharacter(@foldCharacter)
         else if character is '\t'
           distanceToNextTabStop = @tabLength - (screenColumn % @tabLength)
           characterWidth = @ratioForCharacter(' ') * distanceToNextTabStop
@@ -532,7 +533,7 @@
           bufferLineLength = bufferLine.length
           isEmptyLine &&= (bufferLineLength is 0)
           screenColumn += 1
-          screenLineWidth += @ratioForCharacter('⋯')
+          screenLineWidth += @ratioForCharacter(@foldCharacter)
           tokensScreenExtent = screenColumn
           wrapBoundaryBufferColumn = bufferColumn
           wrapBoundaryScreenColumn = screenColumn
@@ -906,7 +907,7 @@
     else if ((metadata & LEADING_WHITESPACE) or (metadata & TRAILING_WHITESPACE)) and @invisibles.space?
       @invisibles.space.repeat(screenExtent)
     else if metadata & FOLD
-      '⋯'
+      @foldCharacter
     else if metadata & VOID
       if metadata & LINE_ENDING
         if (metadata & CR) and (metadata & LF)


Helper method to extract pool types of an exercise

Ignores all_exercises since they all have that

--- src/flux/exercise.coffee
+++ src/flux/exercise.coffee
@@ -99,6 +99,9 @@
         section.toString() is topic_chapter_section.toString()
       )

+    poolTypes: (exercise) ->
+      _.without( exercise.pool_types, 'all_exercises')
+
     allForPage: (pageId) ->
       @_exercises[pageId] or []


only load from disk in project if file changed at least 7s after doing a save to disk.

--- src/smc-util/syncstring.coffee
+++ src/smc-util/syncstring.coffee
@@ -1636,8 +1636,8 @@
                     if @_closed
                         @_file_watcher.close()
                         return
-                    if ctime - (@_save_to_disk_start_ctime ? 0) >= 15*1000
-                        # last attempt to save was at least 15s ago, so definitely
+                    if ctime - (@_save_to_disk_start_ctime ? 0) >= 7*1000
+                        # last attempt to save was at least 7s ago, so definitely
                         # this change event was not caused by it.
                         dbg("_load_from_disk: no recent save")
                         @_load_from_disk()
@@ -1649,12 +1649,11 @@
                         return
                     if @_save_to_disk_start_ctime <= ctime and ctime <= @_save_to_disk_end_ctime
                         # changed triggered during the save
-                        dbg("_load_from_disk: change happened during @_save_to_disk , so ignoring file change")
+                        dbg("_load_from_disk: change happened during @_save_to_disk, so ignoring file change")
                         return
                     # Changed happened near to when there was a save, but not in window, so
                     # we do it..
-                    dbg("_load_from_disk: despite a recent save")
-                    @_load_from_disk()
+                    dbg("_load_from_disk: happened to close to recent save, so ignoring")
                     return

                 @_file_watcher.on 'delete', =>


Run tasks in order

Run tasks in order

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -51,10 +51,10 @@
 gulp.task "default", ["develop"]

 gulp.task "develop", ->
-  runSequence ["watch", "browser-sync"]
+  runSequence "watch", "browser-sync"

 gulp.task "swift", ->
-  runSequence ["generate-swift", "run-swift"]
+  runSequence "generate-swift", "run-swift"

 gulp.task "build", ->
   runSequence ["sass", "coffee", "vendor-js"], "lint-scss", "jekyll-build", "swift"


add clean task & clean before build for production

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -149,7 +149,10 @@

 gulp.task 'revision', ['revision-files', 'replace-references']

-buildTasks = ['coffee', 'jade', 'stylus', 'assets', 'content']
+gulp.task 'clean', (cb) ->
+  rimraf.sync './public', cb
+
+buildTasks = ['clean', 'coffee', 'jade', 'stylus', 'assets', 'content']
 buildTasks = buildTasks.concat ['compress', 'revision'] if production

 gulp.task 'build', buildTasks


added a comment about exorcist

added a comment about exorcist

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -14,7 +14,7 @@
 source      = require 'vinyl-source-stream'
 transform   = require 'vinyl-transform'
 buffer      = require 'vinyl-buffer'
-exorcist    = require 'exorcist'
+exorcist    = require 'exorcist'      # moves inline source maps to external .js.map file
 glob        = require 'glob'
 coffeelint  = require 'browserify-coffeelint'
 global.argv = subarg(process.argv.slice(2)) # used for tunnel


mixpanel: log signups directly to debug missing entries in mixpanel

--- client/Login/AppView.coffee
+++ client/Login/AppView.coffee
@@ -338,6 +338,11 @@
         KD.mixpanel.alias account.profile.nickname
         KD.mixpanel "Signup, success"
         _gaq.push ['_trackEvent', 'Sign-up']
+
+        try
+          mixpanel.track "Alternate Signup, success"
+        catch
+          KD.logToExternal "mixpanel doesn't exist"

         $.cookie 'newRegister', yes
         $.cookie 'clientId', replacementToken


Resurrection: Save layout size when snapshot is updated and split views are resized

--- client/ide/lib/index.coffee
+++ client/ide/lib/index.coffee
@@ -130,6 +130,8 @@
       [ideView] = @ideViews.filter (ideView) -> ideView.isFullScreen
       ideView.emit 'CloseFullScreen'  if ideView

+    @on 'SnapshotUpdated', @bound 'saveLayoutSize'
+

   prepareIDE: (withFakeViews = no) ->

@@ -299,6 +301,7 @@
     @setActiveTabView newIdeView.tabView

     splitView.on 'ResizeDidStop', kd.utils.throttle 500, @bound 'doResize'
+    splitView.on 'ResizeDidStop', kd.utils.debounce 750, @bound 'saveLayoutSize'

     if not silent
       newIdeView.emit 'NewSplitViewCreated',