unit-test al-repeat transparent assignment

--- test/directive/repeat.coffee
+++ test/directive/repeat.coffee
@@ -429,28 +429,52 @@
         $test.equal getText(), '1x2y3z'
         $test.close()

-###
-Test('al-repeat restrict M #0', 'al-repeat-restrict-m-0').run ($test, alight) ->
-    $test.start 1
-
-    el = document.createElement 'div'
-    el.innerHTML = "

-
-                            {{$index}}
-                            {{item.kind}}
-                            {{item.name}}
-
-
"
-
-    scope = alight.Scope()
-    scope.list = [
-        {kind: 'linux', name: 'Ubuntu 14'}
-        {kind: 'macos', name: 'X'}
-        {kind: 'windows', name: '10'}
-        {kind: 'freebsd', name: '7'}
-    ]
-    alight.applyBindings scope, el
-
-    #$test.equal alight.f$.text(alight.f$.find(el, 'p')[0]).trimLeft(), 'Hello World!'
+
+Test('al-repeat-transparent-assigment-0', 'al-repeat-transparent-assigment-0').run ($test, alight) ->
+    $test.start 8
+
+    childS = null
+    alight.d.al.fire = (cd) ->
+        if cd.scope.child isnt '14'
+            return
+        if cd.scope.box.name isnt 'ubuntu'
+            return
+        childS = cd
+        null
+
+    el = ttDOM """
+

+            box={{box.name}}
+            {{child}}
+

+    """
+
+    scope =
+        list: []
+    scope.list.push
+        name: 'linux'
+        children: ['x86', 'x64']
+
+    cd = alight.ChangeDetector scope
+    alight.bind cd, el
+
+    $test.equal ttGetText(el), 'box=linux x86 x64'
+    $test.equal scope.firstLine, 'linux'
+    $test.equal scope.fromChild, 'x64'
+
+    scope.list.push
+        name: 'ubuntu'
+        children: ['14', '15']
+    cd.scan()
+
+    $test.equal ttGetText(el), 'box=linux x86 x64 box=ubuntu 14 15'
+    $test.equal scope.firstLine, 'ubuntu'
+    $test.equal scope.fromChild, '15'
+
+    childS.scope.child = 'X'
+    childS.scope.box.name = 'debian'
+    childS.scan()
+    $test.equal ttGetText(el), 'box=linux x86 x64 box=debian X 15'
+    $test.equal childS.scope.$root, cd.scope
+
     $test.close()
-###


Find the help button by its controller.

Find the help button by its controller.

--- test/e2e/helpers/page.coffee
+++ test/e2e/helpers/page.coffee
@@ -287,7 +287,7 @@
         expect(elt.isDisplayed(), 'The help element is not initially hidden')
           .to.eventually.be.false
         # The help button is the parent of the question mark icon.
-        @find('button .glyphicon-question-sign', '..').then (button) ->
+        @find("[ng-controller='HelpCtrl']").then (button) ->
           expect(button, 'The help button is missing').to.exist
           # Open the help view...
           button.click().then =>


hide token in logger

hide token in logger

--- app/controllers/applications_controller.coffee
+++ app/controllers/applications_controller.coffee
@@ -14,6 +14,8 @@
         send error: true, msg: "Server error occured", code

+
+
 # Load application corresponding to slug given in params
 before 'load application', ->
     Application.all key: params.slug, (err, apps) =>
@@ -37,7 +39,7 @@
     render title: "Cozy Home"

 action "reset_token", ->
-    app.token = body.token
+    app.token = body.password

 # Return list of applications available on this cozy instance.


Fixed the submitting of scores to only allow logged in users to get on the leaderboard.

--- server/queues/scoring.coffee
+++ server/queues/scoring.coffee
@@ -29,6 +29,7 @@
   throw new Error  "There was an error registering the scoring queue."

 module.exports.createNewTask = (req, res) ->
+  return errors.forbidden res, "You need to be logged in to be added to the leaderboard" if isUserAnonymous req
   return errors.badInput res, "The session ID is invalid" unless typeof req.body.session is "string"
   LevelSession.findOne { "_id": req.body.session}, (err, sessionToScore) ->
     return errors.serverError res, "There was an error finding the given session." if err?
@@ -87,7 +88,7 @@
 getUserIDFromRequest = (req) -> if req.user? then return req.user._id else return null

-isUserAnonymous = (req) -> if req.user? then return req.user.anonymous else return true
+isUserAnonymous = (req) -> if req.user? then return req.user.get('anonymous') else return true

 parseTaskQueueMessage = (req, res, message) ->


Improved validation of accounts fields.

Improved validation of accounts fields.

--- app/client/bootstrap/accounts.coffee
+++ app/client/bootstrap/accounts.coffee
@@ -13,6 +13,8 @@
   minLength: 3
   maxLength: 32
   trim: true
+  re: /^[\w]{3,32}$/
+  errStr: 'Only symbols and digits allowed.'
   # TODO: Uncomment after implement of server-side checker
   # func: (value) ->
   #   if Meteor.isClient
@@ -32,3 +34,7 @@
   type: 'text'
   displayName: 'Fullname'
   required: true
+  minLength: 2
+  maxLength: 140
+  re: /^[\w\s]{2,140}$/
+  errStr: 'Only symbols and digits allowed.'


add previous question button

add previous question button

--- src/coffee/controllers/questions_controller.coffee
+++ src/coffee/controllers/questions_controller.coffee
@@ -34,6 +34,7 @@
     page "/questions/#{@id + 1}"

   _prevQuestion: ->
+    page "/questions/#{@id - 1}"

   _startOver: ->
     SwanKiosk.Store.answers = {}


commit import test

commit import test

--- test/hello-world_test.coffee
+++ test/hello-world_test.coffee
@@ -17,3 +17,7 @@

   it 'registers a hear listener', ->
     expect(@robot.hear).to.have.been.calledWith(/orly/)
+
+describe 'test-require', ->
+  it 'requries hubot-influxdb-alerts', ->
+    require('../src/hubot-influxdb')


[logging] do not overwrite the logger name

Signed-off-by: Jakob Ackermann <caa3e4f01d56b797520cbfed54bdab4c5e3b0ebb@outlook.com>

--- app/coffee/FSPersistorManager.coffee
+++ app/coffee/FSPersistorManager.coffee
@@ -36,7 +36,7 @@
       if err?
         logger.err  location:location, target:target, fsPath:fsPath, err:err, "something went wrong writing stream to disk"
         return callback err
-      @sendFile location, target, fsPath, (err) ->
+      @sendFile location, target, fsPath, (err) ->
         # delete the temporary file created above and return the original error
         LocalFileWriter.deleteFile fsPath, () ->
           callback(err)


include product exclude test data from cart query str

--- assets/coffeescript/pupil_store.coffee
+++ assets/coffeescript/pupil_store.coffee
@@ -676,7 +676,7 @@
         event.preventDefault()
         link = $(event.target)
         data = @_getOrderPermalink()
-        # document.location = "?" + $.param(data) + '&user=uk' + '&test=1'
+        document.location = "?" + $.param(data)

   eventFillOrderFormFromQueryString: ->
     if $(@cartPage).length > 0


Remove unused CompositeDisposable import

--- src/display-marker.coffee
+++ src/display-marker.coffee
@@ -1,4 +1,4 @@
-{Emitter, CompositeDisposable} = require 'event-kit'
+{Emitter} = require 'event-kit'

 # Essential: Represents a buffer annotation that remains logically stationary
 # even as the buffer changes. This is used to represent cursors, folds, snippet


updated configurator as world HR + binocular is not an option that can be purchased due to bandwidth limitations.

--- assets/coffeescript/pupil_store.coffee
+++ assets/coffeescript/pupil_store.coffee
@@ -160,6 +160,9 @@

         if $(@worldConfigActiveClass).attr('id') is "world_none" and $(@eyeConfigActiveClass).attr('id') is "eye_none"
           @addToCartConfig.addClass('Button--state-inactive')
+
+        else if $(@worldConfigActiveClass).attr('id') is "world_hr" and $(@eyeConfigActiveClass).attr('id') is "eye_120hz_binocular"
+          @addToCartConfig.addClass('Button--state-inactive')
         else
           if @addToCartConfig.hasClass('Button--state-inactive')
             @addToCartConfig.removeClass('Button--state-inactive')
@@ -614,11 +617,11 @@
     if activeWorldId is "world_none" and activeEyeId is "eye_none"
       subTotal = "Not for sale"
       weight = ""
+    if activeWorldId is "world_hr" and activeEyeId is "eye_120hz_binocular"
+      subTotal = "Not for sale"
+      weight = ""
     $(@configSubTotalClass).text(subTotal)
     $("#StoreConfig-weight").text(weight)
-
-  _updateConfigWeight: ->
-

   # _uniqueId: (len=6)->
   #   id = ""


Setting level now makes use of setXp

Setting level now makes use of setXp

--- src/entities/creature.coffee
+++ src/entities/creature.coffee
@@ -33,14 +33,13 @@

 		if level? and not @xp?
 			@xp = calc.xpForLevel level
-
+
 		@xp ?= 0

 		Object.defineProperty @, 'level',
 			get: => calc.levelFromXp @xp
 			set: (level) =>
-				@xp = calc.xpForLevel level
-				@recalculateStats()
+				@setXp calc.xpForLevel level

 		@personalities ?= []


[benchmarks] remove max event listener limit for ssh server process.

--- benchmarks/framework/ssh_server.coffee
+++ benchmarks/framework/ssh_server.coffee
@@ -33,6 +33,7 @@
                 buffer = ''
                 @emit('ready')

+        process.setMaxListeners(0)
         process.on 'exit', () =>
             if @server
                 @server.kill()


Attempt to make code editor background image preloading more reliable

https://github.com/codecombat/codecombat/commit/624e4f8b791162ad62d029b1bf4b846ac859bb11
  wasn't fully effective, so this adds some redundancy.

--- app/views/play/level/PlayLevelView.coco.coffee
+++ app/views/play/level/PlayLevelView.coco.coffee
@@ -160,6 +160,9 @@

     if features.china
       @checkTournamentEndInterval = setInterval @checkTournamentEnd.bind(@), 3000
+
+    preloadImages = ['/images/level/code_palette_wood_background.png', '/images/level/code_editor_background_border.png']
+    _.delay (-> $('<img/>')[0].src = img for img in preloadImages), 1000

   getMeta: ->
     link: [


Remove special handling of Shift modifier

atom-keymap now handles this automatically when
normalizing.

--- src/browser/application-menu.coffee
+++ src/browser/application-menu.coffee
@@ -147,7 +147,6 @@
     modifiers = firstKeystroke.split('-')
     key = modifiers.pop()

-    modifiers.push("Shift") if key != key.toLowerCase()
     modifiers = modifiers.map (modifier) ->
       modifier.replace(/shift/ig, "Shift")
               .replace(/cmd/ig, "Command")


Check for updates 10 seconds after atom launches

--- src/browser/atom-application.coffee
+++ src/browser/atom-application.coffee
@@ -139,7 +139,9 @@
       @applicationMenu.showInstallUpdateItem(false)
       @applicationMenu.showCheckForUpdateItem(true)

-    autoUpdater.checkForUpdates()
+    # Check for update after Atom has fully started and the menus are created
+    setTimeout((-> autoUpdater.checkForUpdates()), 10000)
+

   checkForUpdate: ->
     autoUpdater.once 'update-available', ->


Stop auto bracket-matching of <...>

Stop auto bracket-matching of <...>

--- settings/language-stata.cson
+++ settings/language-stata.cson
@@ -8,7 +8,6 @@
       "{}"
       "`'"
       "\"\""
-      "‹›"
     ]
   autocomplete:
     symbols:


Ignore delete events

Ignore delete events

--- src/packages/command-logger/src/command-logger.coffee
+++ src/packages/command-logger/src/command-logger.coffee
@@ -22,6 +22,7 @@
     'core:backspace'
     'core:cancel'
     'core:confirm'
+    'core:delete'
     'editor:newline'
     'tree-view:directory-modified'
   ]


in embed.coffee, actually connect the ClientConnection

--- bokehjs/src/coffee/server/embed.coffee
+++ bokehjs/src/coffee/server/embed.coffee
@@ -6,6 +6,7 @@
 {logger} = require "../common/logging"
 {Document, RootAddedEvent, RootRemovedEvent} = require "../common/document"
 {ClientConnection} = require "../common/client"
+{Promise} = require "es6-promise"

 inject_css = (url) ->
   link = $("<link href='#{url}' rel='stylesheet' type='text/css'>")
@@ -49,15 +50,28 @@

 _connection = null
 _get_connection = () ->
-  if not _connection?
+  if _connection == null
     _connection = new ClientConnection()
-  _connection
+    _connection.connect().then(
+      (whatever) ->
+        _connection
+      (error) ->
+        logger.error("Failed to connect to Bokeh server #{error}")
+        throw error
+    )
+  else
+    Promise.resolve(_connection)

 # map from session id to promise of ClientSession
 _sessions = {}
 _get_session = (session_id) ->
   if session_id not of _sessions
-    _sessions[session_id] = _get_connection().pull_session(session_id)
+    _sessions[session_id] = _get_connection().then(
+      (connection) ->
+        connection.pull_session(session_id)
+      (error) ->
+        throw error
+    )

   _sessions[session_id]


Conserta erro de binding do template-as-modal

--- src/coffee/product-component.coffee
+++ src/coffee/product-component.coffee
@@ -3,13 +3,20 @@

 class ProductComponent
 	bindProductEvent: (name, handler, productIdIndex = 0) =>
-		$(window).on name, (evt, args...) =>
-			productId = args[productIdIndex]
-			return unless @productId is productId
-			handler(evt, args...)
+		bindProductId = (name, handler, productId) =>
+			$(window).on name, (evt, args...) =>
+				evtProductId = args[productIdIndex]
+				return unless productId is evtProductId
+				handler(evt, args...)

-		if EVENT_HISTORY[@productId]?[name]?
-			handler(EVENT_HISTORY[@productId][name]...)
+			if EVENT_HISTORY[productId]?[name]?
+				handler(EVENT_HISTORY[productId][name]...)
+
+		if @productId instaceof Array
+			for productId in @productId
+				bindProductId(name, handler, productId)
+		else
+			bindProductId(name, handler, @productId)

 	triggerProductEvent: (name, args...) =>
 		element = @element or ($window)


pruning chapters without valid sections

pruning chapters without valid sections

--- src/flux/cc-dashboard.coffee
+++ src/flux/cc-dashboard.coffee
@@ -31,7 +31,9 @@
           page.chapter_section[1] or 0
         ).reverse()
         chapter
-      chapters.reverse()
+
+      _.select chapters.reverse(), (chapter) ->
+        chapter.valid_sections?.length

 extendConfig(DashboardConfig, new CrudConfig())


Fix core:cancel's keymap selector

Fix core:cancel's keymap selector

--- keymaps/find-and-replace.cson
+++ keymaps/find-and-replace.cson
@@ -10,9 +10,7 @@

   'meta-e': 'find-and-replace:use-selection-as-find-pattern'

-'.find-and-replace .mini.editor input':
-  'tab': 'find-and-replace:focus-next'
-  'shift-tab': 'find-and-replace:focus-previous'
+'.find-and-replace':
   'enter': 'core:confirm',
   'escape': 'core:cancel'
   'meta-w': 'core:cancel'
@@ -20,6 +18,10 @@
   'alt-meta-c': 'find-and-replace:toggle-case-option'
   'alt-meta-s': 'find-and-replace:toggle-selection-option'

+'.find-and-replace .mini.editor input':
+  'tab': 'find-and-replace:focus-next'
+  'shift-tab': 'find-and-replace:focus-previous'
+
 '.project-find-and-replace .preview-list':
   'enter': 'core:confirm'
   'left': 'find-and-replace:collapse-result'


atom proto-repl settigns

atom proto-repl settigns

--- files/atom/config.cson
+++ files/atom/config.cson
@@ -32,13 +32,6 @@
     disabledPackages: [
       "deprecation-cop"
       "autocomplete-snippets"
-      "proto-repl"
-      "proto-repl-charts"
-      "linter"
-      "linter-ui-default"
-      "linter-write-good"
-      "intentions"
-      "busy-signal"
     ]
     ignoredNames: [
       ".git"
@@ -115,7 +108,11 @@
   "proto-repl":
     autoPrettyPrint: true
     bootPath: "/usr/local/bin/boot"
+    displayExecutedCodeInRepl: false
+    displayHelpText: false
     leinPath: "/usr/local/bin/lein"
+    refreshOnReplStart: false
+    showInlineResults: false
   "terminal-plus":
     core: {}
   "tree-view":


that's the right way!

that's the right way!

--- api/apps/articles/model.coffee
+++ api/apps/articles/model.coffee
@@ -96,21 +96,22 @@
 # Persistence

 @save = (data, callback) ->
-  id = ObjectId(data._id) or new ObjectId()
+  id = ObjectId(data._id)
   whitelisted = _.pick data, _.keys schema
   whitelisted.author_id = whitelisted.author_id?.toString()
   Joi.validate whitelisted, schema, (err, data) ->
     return callback err if err
-    db.articles.findOne { _id: id }, (err, article) ->
-      return callback err if err
-      data = _.extend (article or {}), { slugs: [] }, data
-      data.updated_at = moment().format()
-      data.author_id = ObjectId data.author_id
-      slug = _s.slugify data.title
-      data.slugs = data.slugs.concat [slug] unless slug in data.slugs
-      db.articles.update { _id: id }, data, { upsert: true }, (err, res) ->
-        return callback err if err
-        callback null, _.extend data, _id: id
+    data.updated_at = moment().format()
+    data.author_id = ObjectId(data.author_id)
+    slug = _s.slugify data.title
+    data.slugs ?= []
+    data.slugs.push slug unless slug in data.slugs
+    db.articles.findAndModify {
+      query: { _id: id }
+      update: data
+      upsert: true
+      new: true
+    }, callback

 @syncToPost = (article, accessToken, callback) ->


Updated kmc config

Updated kmc config

--- app/scripts/common/kmc-config.coffee
+++ app/scripts/common/kmc-config.coffee
@@ -4,12 +4,12 @@

 	module.provider 'kmc', ->
 		$get: ->
-			ks = 'NzYxMzJjODgyMzA4NGU5YWExODRmY2E5NmZmNDcxOGQ0NTU2ZTk5ZXw5MzkzNDE7OTM5MzQxOzE0NDcxODU0MjQ7MjsxNDQ3MDk5MDI0LjM1Njg7cmtzaGFyZWRib3hAZ21haWwuY29tO2Rpc2FibGVlbnRpdGxlbWVudDs7'
+			ks = ''
 			for param in (location.search or location.hash)?.split('?')[1]?.split('&') or []
 				parts = param.split '='
 				if parts[0] is 'ks'
 					ks = parts[1]
 					break
-			window.kmc or vars:
+			window.parent.kmc or vars:
 				service_url: 'http://www.kaltura.com'
 				ks: ks


video: helper fix docs

video: helper fix docs

--- client/app/lib/videocollaboration/viewmodel.coffee
+++ client/app/lib/videocollaboration/viewmodel.coffee
@@ -72,13 +72,13 @@

 ###*
-  * This is a workaround.
-  * This is probably wrong but the way OpenTok sessions are working makes all
-  * subscriber videos invisible. This method's pure existence is to get around
-  * it. This probably requires a FIXME tag.
-  * p.s.: 173 is weird.
-  *
-  * @param {ParticipantType.Participant} participant
+ * This is a workaround.
+ * This is probably wrong but the way OpenTok sessions are working makes all
+ * subscriber videos invisible. This method's pure existence is to get around
+ * it. This probably requires a FIXME tag.
+ * p.s.: 173 is weird.
+ *
+ * @param {ParticipantType.Participant} participant
 ###
 fixParticipantVideo = (participant) ->


No longer special case timedout, there's now a style for that

--- js/inner.coffee
+++ js/inner.coffee
@@ -62,7 +62,6 @@

     @action_header_style = @komp =>
       css = @status()
-      css = "failed" if css == "timedout"

       result =
         minimize: @minimize()


Replace pusher lib with our own. Adds test pusher API keys

--- js/inner.coffee
+++ js/inner.coffee
@@ -652,7 +652,11 @@
     @setupPusher()

   setupPusher: () =>
-    @pusher = new Pusher("356b7c379e56e14c261b", { encrypted: true})
+    key = switch renderContext.environment
+      when "production" then "6465e45f8c4a30a2a653"
+      else "3f8cb51e8a23a178f974"
+
+    @pusher = new Pusher(key, { encrypted: true})

     Pusher.channel_auth_endpoint = "/auth/pusher"


improve error handling

improve error handling

--- spike/sign.coffee
+++ spike/sign.coffee
@@ -35,8 +35,12 @@
 verify = (md, signature, publicKey) ->
   try
     verified = publicKey.verify md.digest().bytes(), signature
-    log "verification successful"
-  catch
-    log "verification failed"
+    log "Verification successful"
+  catch e
+    if e.message.match /Encryption block is invalid/
+      log "Verification failed"
+    else
+      console.error e
+      process.exit 1

 main()


whenever a word is choosen, @current is informed.

--- cf/spell.coffee
+++ cf/spell.coffee
@@ -19,6 +19,7 @@

   random_question = (data) ->
     choice = Math.floor(Math.random() * data['words'].length)
+    @current = choice
     return data['words'][choice]

   update_question = (data) ->


display text as markdown

display text as markdown

--- src/frontend/modules/textChat.coffee
+++ src/frontend/modules/textChat.coffee
@@ -77,11 +77,12 @@
         jqNewAuthor   = ($ '<span>', { class : 'fontlightblue' }).text message.from
         jqNewTime     = ($ '<span>', { class : 'time' }).text ' at ' + ((do d.toTimeString).substr 0, 5)
         (jqNewMetadata.append jqNewAuthor).append jqNewTime
-        jqNewMessage  = ($ '<div>', { class : 'chatmessage' }).text message.text
+        jqNewMessage  = ($ '<div>', { class : 'chatmessage' }).html message.text
         (do @scrollPane.getContentPane).append (($ '<li>').append jqNewMetadata).append jqNewMessage

     # Add a new message to the text chat window.
     addMessage      : (message) =>
+        message.text  = @markdown.makeHtml message.text
         jqLastMessage = do (@jqMessageBox.find 'li').last
         if jqLastMessage[0]?
             lastAuthor = do ((jqLastMessage.children '.chatmetadata').children 'span').first


allow stylesheet urls to be passed to config

--- src/renderer.coffee
+++ src/renderer.coffee
@@ -81,6 +81,7 @@
     this.addStyles(DEFAULT_STYLES)
     # Ensure user specified styles are added after modules'
     _.defer(_.bind(this.addStyles, this, @options.styles)) if @options.styles?
+    _.defer(_.bind(this.addStyleSheet, this, @options.stylesheet)) if @options.stylesheet?

   addContainer: (className, before = false) ->
     refNode = if before then @root else null
@@ -103,5 +104,13 @@
       @emitter.emit(@emitter.constructor.events.RENDER_UPDATE, css) if @emitter?
     )

+  addStyleSheet: (url) ->
+    link = @root.ownerDocument.createElement('link')
+    link.type = 'text/css'
+    link.rel = 'stylesheet'
+    link.href = url
+    _.defer( =>
+      @root.ownerDocument.querySelector('head').appendChild(link)
+    )

 module.exports = Renderer


Improved consistency in code workflow.

Improved consistency in code workflow.

--- client.coffee
+++ client.coffee
@@ -121,7 +121,6 @@
       queue = @cryptoWorker.queue
       @cryptoWorker = new FallbackCryptoWorker @, @size, @onProgress, @chunkSize
       @cryptoWorker.addQueue queue
-      @cryptoWorker.nextInQueue()

 class BaseCryptoWorker
   constructor: (@instance, @size, @onProgress, @chunkSize) ->
@@ -164,6 +163,7 @@
   addQueue: (queue) =>
     @totalSizeQueued += item.size or 0 for item in queue
     @queue = @queue.concat queue
+    @nextInQueue()

   nextInQueue: =>
     return if @busy
@@ -186,10 +186,8 @@
       return

     if @current.message is 'ping'
-      @worker.postMessage
-        message: 'ping'
-        data: @current.data
-      return
+      @ping()
+     return

     assert.equal @current.message, 'update'

@@ -217,6 +215,9 @@
   finalize: (params) ->
     throw new Error "Not implemented!"

+  ping: ->
+    throw new Error "Not implemented!"
+
   destroy: ->
     throw new Error "Not implemented!"

@@ -249,6 +250,11 @@
     @worker.postMessage
       message: 'finalize'

+  ping: =>
+    @worker.postMessage
+      message: 'ping'
+      data: @current.data
+
   destroy: =>
     @worker.terminate()
     @instance._destroy()


Call the new Airbrake api properly, avoids a mixed-content warning when there's an enxception in outer.

--- js/outer.coffee
+++ js/outer.coffee
@@ -168,8 +168,8 @@

   # Airbrake
   @bind 'error', (e, data) ->
-    if data? and data.error? and window.Hoptoad?
-      window.Hoptoad.notify data.error
+    if data? and data.error? and window.Airbrake?
+      window.Airbrake.captureException data.error

   # Kissmetrics
   if renderContext.showJoinLink


Correct sort for follow artists

Correct sort for follow artists

--- components/follow_button/artist_suggestions.coffee
+++ components/follow_button/artist_suggestions.coffee
@@ -4,7 +4,7 @@
 { API_URL } = require('sharify').data
 listTemplate = -> require('./artist_suggestions.jade') arguments...
 rowTemplate = -> require('./artist_suggestion.jade') arguments...
-Artists = require '../../collections/artists.coffee'
+Artist = require '../../models/artist.coffee'
 analyticsHooks = require '../../lib/analytics_hooks.coffee'

 module.exports = class ArtistSuggestions extends Backbone.View
@@ -39,7 +39,7 @@
     "a[data-artist-id='#{artistId}']"

   renderSuggestedArtists: ->
-    artists = new Artists
+    artists = new Backbone.Collection [], model: Artist
     artists.url = "#{API_URL}/api/v1/me/suggested/artists"
     artists.fetch
       data:


More improvements to the add projects page. Display gravatars, fixing renaming bugs.

--- js/inner.coffee
+++ js/inner.coffee
@@ -774,9 +774,9 @@
   loadRepos: (org) =>
     @loadingOrganizations(true)
     if org.org
-      url = "/api/v1/user/org/#{org.name}/repos"
+      url = "/api/v1/user/org/#{org.login}/repos"
     else
-      url = "/api/v1/user/user/#{org.name}/repos"
+      url = "/api/v1/user/user/#{org.login}/repos"

     $.getJSON url, (data) =>
       @loadingOrganizations(false)


Really fix realtime updates

Really fix realtime updates

--- js/inner.coffee
+++ js/inner.coffee
@@ -201,15 +201,6 @@
     x = for o in @out()
       "
#{@htmlEscape(o.message)}
"
     x.join ""
-
-  appendLog: (json) =>
-    len = @out.length
-    last = @out[len - 1]
-    payload = json.out
-    if last.type == payload.type
-      last.message += payload.message
-    else
-      @out.push(payload)

   report_build: () =>
     VM.raiseIntercomDialog('I think I found a bug in Circle at ' + window.location + '\n\n')
@@ -463,7 +454,18 @@
   appendAction: (json) =>
     # adds output to the action
     @fillActions(json.step, json.index)
-    @steps()[json.step].actions()[json.index].out.push(json.out)
+
+    # @steps()[json.step].actions()[json.index].out.push(json.out)
+    out = @steps()[json.step].actions()[json.index].out
+    len = out().length
+    last = out()[len - 1]
+    payload = json.out
+    if last? and last.type == payload.type
+      out.valueWillMutate()
+      last.message += payload.message
+      out.valueHasMutated()
+    else
+      out.push(payload)

   # TODO: CSRF protection
   retry_build: (data, event) =>
@@ -969,8 +971,6 @@

       plan.price + (extra_c * 49) + (Math.round(extra_p * 99))

-
-
   selectPlan: (plan) =>
     if plan.price?
       @chosenPlan(plan)


reattached upload dialog

reattached upload dialog

--- app/coffeescripts/menus.coffee
+++ app/coffeescripts/menus.coffee
@@ -254,6 +254,6 @@
         else
           true

-    $("#upload").click (e) ->
+    $("#open-local-network").click (e) ->
       $("#uploadField").click()
       e.preventDefault()


hotfix PUT routing.

hotfix PUT routing.

--- tunemachine/routes/api.coffee
+++ tunemachine/routes/api.coffee
@@ -117,6 +117,3 @@
         else
           res.status 404
           res.end()
-
-    res.status 200
-    res.end()


Removed usage of bindMeteor.

Removed usage of bindMeteor.

--- src/Depends.coffee
+++ src/Depends.coffee
@@ -25,16 +25,16 @@
       deps = []
     isCallback = Types.isFunction(value)
     if isCallback
-      value = bindMeteor(value)
+      value = Meteor.bindEnvironment(value)
     df = @_getDeferred(name)
     if Q.isFulfilled(df.promise)
       throw new Error('Dependency with name "' + name + '" already defined.')
     log('Defined dependency', name, deps)
     @on(deps).then(
-      bindMeteor (depsResult) ->
+      Meteor.bindEnvironment (depsResult) ->
         value = if isCallback then value.apply(null, depsResult) else value
         Q.when(value).then(
-          bindMeteor (valueResult) ->
+          Meteor.bindEnvironment (valueResult) ->
             log('Resolved dependency', name, valueResult)
             df.resolve(valueResult)
           df.reject
@@ -62,11 +62,11 @@
     unless Types.isArray(deps)
       deps = [deps]
     if callback
-      callback = bindMeteor(callback)
+      callback = Meteor.bindEnvironment(callback)
     promises = _.map deps, (name) => @_getDeferred(name).promise
     log('Waiting for dependencies', deps)
     promise = Q.all(promises)
-    promise.then bindMeteor (results) ->
+    promise.then Meteor.bindEnvironment (results) ->
       log('Dependencies resolved', deps, results)
       if callback
         callback.apply(null, results)
@@ -74,4 +74,3 @@
     promise

 log = -> console.log.apply(console, arguments) if Depends.debug
-bindMeteor = -> Meteor.bindEnvironment.apply(Meteor, arguments)


Edited gameState.coffee via GitHub

Edited gameState.coffee via GitHub

--- gameState.coffee
+++ gameState.coffee
@@ -317,6 +317,7 @@
     @supply = this.makeSupply(tableau)
     @prizes = [c["Bag of Gold"], c.Diadem, c.Followers, c.Princess, c["Trusty Steed"]]
     @tradeRouteMat = []
+    @tradeRouteValue = 0

     @bridges = 0
     @quarries = 0
@@ -424,13 +425,6 @@
     if @supply['Colony']?
       minimum = Math.min(minimum, @supply['Colony'])
     minimum
-
-  # 'tradeRouteValue()' returns the current value of the Trade Route by
-  # counting the number of cards in the @tradeRouteMat pool.
-  tradeRouteValue: () ->
-    value = @tradeRouteMat.length
-    this.log("Trade Route value = $#{value}.")
-    value

   #### Playing a turn
   #
@@ -650,6 +644,7 @@
         @supply[card] -= 1
       if @supply["Trade Route"]? and card.isVictory and not in @tradeRouteMat
         @tradeRouteMat.push(card)
+        @tradeRouteValue = +1
     else
       this.log("There is no #{card} to gain.")
     # TODO: handle gain reactions


Date time range grammar: Fixed another problem of the timezone. See #62161

--- src/elements/DateTime/DateTimeRangeGrammar.coffee
+++ src/elements/DateTime/DateTimeRangeGrammar.coffee
@@ -626,6 +626,9 @@
 			return

 		if inputString.match(CUI.DateTimeRangeGrammar.REGEXP_YEAR)
+			if not inputString.startsWith(CUI.DateTimeRangeGrammar.DASH)
+				momentInput.add(1, "day")
+				momentInput.parseZone()
 			from = to = CUI.DateTimeRangeGrammar.format(momentInput)
 		else if inputString.match(CUI.DateTimeRangeGrammar.REGEXP_MONTH)
 			from = CUI.DateTimeRangeGrammar.format(momentInput, false)


program labels, not yet meaningful

program labels, not yet meaningful

--- graphics.coffee
+++ graphics.coffee
@@ -39,6 +39,7 @@
     @message_bg_fill_style = '#000000'

     @computeOutlines()
+    @computeProgramLabels()

     @sprites = []
     @anim_complete_callbacks = []
@@ -63,6 +64,8 @@
     g: '#00c000'  # green
     b: '#0000c0'  # blue
     p: '#c000c0'  # pink/purple/magenta
+
+  label_letters: ['a','b','c','d','e','f']

   computeOutlines: () ->
     @outline_links = []
@@ -97,6 +100,29 @@

         was_in = is_in

+  computeProgramLabels: () ->
+    assigned = {}
+    @program_labels = []
+
+    d = Math.max(@game_state.Hi, @game_state.Wi)
+
+    for i in [0..2*d]
+      for j in [0..i]
+        x = i-j
+        y = j
+
+        if @game_state.level[x]? and  @game_state.level[x][y]?
+          b = @game_state.level[x][y]
+        else
+          continue
+
+        if b.type == 'program' and not assigned[b.id]
+          @program_labels.push(
+            x: x*@block, y: y*@block, id: b.id
+            letter: @label_letters[@program_labels.length]
+          )
+          assigned[b.id] = true
+
 ################

   render: (t) ->
@@ -118,6 +144,7 @@
     @ctx.fillRect(0, 0, @cnv.width, @cnv.height)

     @renderGrid()
+    @renderLabels()

     @ctx.restore()

@@ -416,6 +443,19 @@
       @clues = [ @game_state.programs[@game_state.level[xi][yi].id] ]
     else
       @clues = []
+
+  renderLabels: ->
+    @ctx.save()
+
+    @ctx.translate(@board_dims.x+.5+@block/16, @board_dims.y+.5+@block/16)
+    @ctx.strokeStyle = 'white'
+    @ctx.lineWidth = 1
+
+    for label in @program_labels
+      @ctx.save()
+      @ctx.translate(label.x, label.y)
+      @renderLetter(label.letter)
+      @ctx.restore()

   renderClues: ->
     if @clues.length == 0
@@ -625,4 +665,26 @@

     @ctx.restore()

+  letterGraphics: {
+    a: [ [[0,1],[1.5,1],[1.5,3],[0,3],[0,2],[1.5,2]] ]
+    b: [ [[0,0],[0,3],[1.5,3],[1.5,1.5],[0,1.5]] ]
+    c: [ [[1.5,1],[0,1],[0,3],[1.5,3]] ]
+    d: [ [[2,0],[2,3],[.5,3],[.5,1.5],[2,1.5]] ]
+    e: [ [[0,2],[1.5,2],[1.5,1],[0,1],[0,3],[1.5,3]] ]
+    f: [ [[.5,1.5],[1.5,1.5]], [[2,0],[1,0],[1,3]] ]
+  }
+
+  renderLetter: (l, scale = @block/2) ->
+
+    g = @letterGraphics[l]
+    if not g?
+      return
+
+    @ctx.beginPath()
+    for line in g
+      @ctx.moveTo(line[0][0]/4*scale, line[0][1]/4*scale)
+      for point in line[1..]
+        @ctx.lineTo(point[0]/4*scale, point[1]/4*scale)
+    @ctx.stroke()
+
 window.DwimGraphics = DwimGraphics


Made setCoordinates private

Made setCoordinates private

--- js-library/app/js/jail_iframe/classes/buttons.coffee
+++ js-library/app/js/jail_iframe/classes/buttons.coffee
@@ -15,7 +15,7 @@
     @$el.addClass stateClass
     @frame.sizeFrameToFitContent()

-  setCoordinates: (top, left) =>
+  _setCoordinates: (top, left) =>
     return if @_visible

     @_top = top
@@ -61,7 +61,7 @@

   placeNearElement: (el) ->
     offset = $(el).offset()
-    @setCoordinates offset.top, offset.left
+    @_setCoordinates offset.top, offset.left
     @show()

@@ -101,4 +101,4 @@
     else
       left = selectionLeft + selectionBox.width/2

-    @setCoordinates selectionTop-2, left
+    @_setCoordinates selectionTop-2, left


Added prompt and build internally

Added prompt and build internally

--- lib/frame.coffee
+++ lib/frame.coffee
@@ -133,6 +133,20 @@
     @color 'none', 'none'
     return @

+  prompt: ->
+    @pos @stdout.rows, 0
+    @color 'black', 'white'
+    @fillLine()
+    @pos @stdout.rows+1, 0
+    @write '> '
+
+  build: ->
+    @save 'screen'
+    @format 'none'
+    @erase()
+    @background 'black'
+    @prompt()
+
   # ~~~
   # Custom eventing
   on: (name, func) ->


tweak style

tweak style

--- lib/carte/client/views/list.cjsx
+++ lib/carte/client/views/list.cjsx
@@ -76,7 +76,7 @@

-
{@props.card.get('title')}

+
{@props.card.get('title')}


MachineStateModal: do not ask for info of NotInitialized machines

--- client/Main/providers/machinestatemodal.coffee
+++ client/Main/providers/machinestatemodal.coffee
@@ -125,8 +125,15 @@
     @createFooter()

     if @getOption 'initial'
+
+      currentState = @machine.status.state
+
+      if currentState is NotInitialized
+        @buildViews State: currentState
+        return
+
       KD.getSingleton 'computeController'
-        .kloud.info { @machineId, currentState: @machine.status.state }
+        .kloud.info { @machineId, currentState }
         .then (response)=>

           info "Initial info result:", response


video: update control state change methods(cam/mic)

--- client/app/lib/videocollaboration/model.coffee
+++ client/app/lib/videocollaboration/model.coffee
@@ -328,8 +328,10 @@
    * @param {boolean} state
   ###
   setAudioState: (state) ->
-    @ensurePublishing { publishAudio: state }, =>
-      @publisher.videoData.publishAudio state
+
+    @publisher.videoData.publishAudio state
+    @setState { audio: state }
+    @emit 'AudioPublishStateChanged', state

   ###*
@@ -338,8 +340,10 @@
    * @param {boolean} state
   ###
   setVideoState: (state) ->
-    @ensurePublishing { publishVideo: state }, =>
-      @publisher.videoData.publishVideo state
+
+    @publisher.videoData.publishVideo state
+    @setState { video: state }
+    @emit 'VideoPublishStateChanged', state

   ###*


Add increasing timeout on consequent request failures.

--- frontend/script.coffee
+++ frontend/script.coffee
@@ -9,6 +9,24 @@
 PLAYBACK_TYPE = null
 CURRENT_SONG = null
 nextSong = null
+
+class RetryTimeouter
+        constructor: (@minTimeout=1000, @maxTimeout=30000) ->
+                @reset()
+
+        increaseTimeout: =>
+                timeout = @nextTimeout
+                @nextTimeout = timeout + @lastTimeout
+                if @nextTimeout > @maxTimeout
+                        @nextTimeout = @maxTimeout
+                @lastTimeout = timeout
+                return timeout
+
+        reset: =>
+                @nextTimeout = @minTimeout
+                @lastTimeout = @minTimeout
+
+RETRY_TIMEOUTER = new RetryTimeouter()

 playHashSong = ->
         if document.location.hash.length <= 1
@@ -30,25 +48,18 @@
     nextStatus.text(nextSong)
     encodedPath = encodeURIComponent(nextSong)
     songPath = "/file/#{encodedPath}?type=#{PLAYBACK_TYPE}"
+
     request = $.get(songPath)
     errorCallback = =>
-        setTimeout (-> preloadSong(successCallback)), 1000
+        setTimeout (-> preloadSong(successCallback)), RETRY_TIMEOUTER.increaseTimeout()
     request.error errorCallback
     request.success =>
+        RETRY_TIMEOUTER.reset()
         if (successCallback)
             successCallback()
         else
             nextStatus.append(" ✓")

-    # // var player = new Audio();
-    # // var playerContainer = $("#player-preloaded");
-    # // playerContainer.empty();
-    # // playerContainer.append(player);
-    # // var playerJquery = $(player);
-    # // var encodedPath = encodeURIComponent(nextSong);
-    # // playerJquery.append("<source src='/file/" + encodedPath + "?type=ogg' type='audio/ogg' />");
-    # // playerJquery.append("<source src='/file/" + encodedPath + "?type=mp3' type='audio/mpeg' />");
-    # // playerJquery.attr("preload", "auto");

 playRandomSong = ->
     song = nextSong


fixed hound lint check and file path

fixed hound lint check and file path

--- src/hubot-influxdb.coffee
+++ src/hubot-influxdb.coffee
@@ -3,7 +3,7 @@

 cwd = process.cwd()

-DEFAULTS_FILE = "#{cwd}/scripts/data/defaults.json"
+DEFAULTS_FILE = "#{__dirname}/data/defaults.json"
 CONFIG_FILE = "#{cwd}/config/hubot-influx-config.json"

 a = require(DEFAULTS_FILE)
@@ -144,8 +144,8 @@
   #Client for each database
   influx_connect_config = nconf.get("connection")

-  queries = nconf.get("queries")
-  if queries
+  queries = nconf.get("queries")
+  if queries
     for database in Object.keys(nconf.get("queries"))
       if (!influx_clients[database])
         influx_connect_config['database'] = database
@@ -154,8 +154,8 @@
 print_queries = (msg) ->
   query_config = nconf.get("queries")
   if !query_config
-      msg.send "No queries configured"
-      return
+    msg.send "No queries configured"
+    return

   buf = ""
   for database in Object.keys(query_config)


Tests: AppManager: Clear spies.

Tests: AppManager: Clear spies.

--- client/app/test/applicationmanager.test.coffee
+++ client/app/test/applicationmanager.test.coffee
@@ -6,6 +6,7 @@
 ApplicationManager   = require '../lib/applicationmanager'
 KodingAppsController = require '../lib/kodingappscontroller'

+spy        = null
 appManager = null
 createApps = (appNames = [], shouldShow) ->

@@ -19,7 +20,18 @@
 describe 'kd.singletons.appManager', ->

-  beforeEach -> appManager = new ApplicationManager
+  beforeEach ->
+
+    spy        = null
+    appManager = new ApplicationManager
+
+
+  afterEach ->
+
+    if spy
+      spy.restore()
+      expect.restoreSpies()
+      spy = null

   describe 'constructor', ->
@@ -33,7 +45,7 @@

       isRegistered = no
       appInstance  = new AppController name: 'FooApp'
-      expect.spyOn appManager, 'setListeners'
+      spy = expect.spyOn appManager, 'setListeners'

       appManager.on 'AppRegistered', -> isRegistered = yes
       appManager.register appInstance
@@ -123,10 +135,9 @@

     it 'should warn if there is no name', ->

-      expect.spyOn kd, 'warn'
+      spy = expect.spyOn kd, 'warn'

       appManager.tell()
-
       expect(kd.warn).toHaveBeenCalled()

@@ -213,7 +224,7 @@
     it 'should load app', (done) ->

       globals.config.apps.InternalApp = name: 'InternalApp'
-      expect.spyOn KodingAppsController, 'loadInternalApp'
+      spy = expect.spyOn KodingAppsController, 'loadInternalApp'

       appManager.create 'InternalApp', {}

@@ -233,7 +244,7 @@
       createApps [ 'FooApp', 'BarApp' ]

       appManager.on 'AppIsBeingShown', -> isAppShown = yes
-      expect.spyOn appManager, 'setLastActiveIndex'
+      spy = expect.spyOn appManager, 'setLastActiveIndex'

       appManager.appControllers.FooApp.instances.first.appIsShown = ->
         isAppIsShownCallbackExecuted = yes
@@ -264,7 +275,7 @@

       createApps [ 'App1', 'App2', 'App3' ], yes
       appManager.on 'AppIsBeingShown', -> isEventEmitted = yes
-      expect.spyOn appManager, 'setLastActiveIndex'
+      spy = expect.spyOn appManager, 'setLastActiveIndex'

       expect(appManager.getFrontApp().options.name).toBe 'App3'


remove useless parenthesis

remove useless parenthesis

--- client/app/views/mailboxes_list.coffee
+++ client/app/views/mailboxes_list.coffee
@@ -11,8 +11,8 @@
 class exports.MailboxesList extends ViewCollection

     id: "mailboxes"
-    itemView: require('views/mailboxes_list_element')
-    template: require('templates/_mailbox/list')
+    itemView: require 'views/mailboxes_list_element'
+    template: require 'templates/_mailbox/list'

     checkIfEmpty: ->
         super


disable /Environments

disable /Environments

--- client/landing/app/MainApp/kodingrouter.coffee
+++ client/landing/app/MainApp/kodingrouter.coffee
@@ -230,7 +230,7 @@
       '/:name?/Account'                 : createSectionHandler 'Account'
       '/:name?/Demos'                   : createSectionHandler 'Demos'
       '/:name?/Dashboard'               : createSectionHandler 'Dashboard'
-      '/:name?/Environments'            : createSectionHandler 'Environments'
+      #'/:name?/Environments'            : createSectionHandler 'Environments'

       # group dashboard
       # '/:name?/Dashboard'               : (routeInfo, state, route)->


(feature) Finish processing even if an error occurs.

--- src/jailed_sandbox.coffee
+++ src/jailed_sandbox.coffee
@@ -29,7 +29,7 @@
       var start = function(app){ this.application=null;#{remotes};#{snippets};runTests(#{apiArgs});app.remote.__finished__()};
       start(application);
     """
-
+
     if esprima?
       try
         esprima.parse code
@@ -44,12 +44,13 @@
       connected = false
       customApi.remote?.finished?()
       disconnect()
-
+
     runner = new jailed.DynamicPlugin code, customApi.remote
     disconnect = runner.disconnect.bind(runner)
-
+
     setTimeout (() ->
       if connected
+        customApi.remote?.finished?()
         console.error "Sandbox timed out after #{config.timeout}ms!"
         runner.disconnect()
       ), config.timeout


fix a bug where the hint does not appears

--- cf/spell.coffee
+++ cf/spell.coffee
@@ -26,7 +26,7 @@
   update_question = (data) ->
     word = random_question(data)
     update_keyHandler()
-    $("p#hint_define").empty().append(word['hint_define'])
+    $("p#hint_define").empty().append(word['hint'])

   update_keyHandler = () ->


Don't use deprecated APIs

Don't use deprecated APIs

--- spec/github-file-spec.coffee
+++ spec/github-file-spec.coffee
@@ -4,9 +4,9 @@
 os = require 'os'

 describe "GitHubFile", ->
+  [githubFile, editor] = []
+
   describe "commands", ->
-    githubFile = null
-    editor = null
     workingDirPath = path.join(os.tmpdir(), 'open-on-github-working-dir')
     filePathRelativeToWorkingDir = 'some-dir/some-file.md'

@@ -25,8 +25,12 @@

     setupGithubFile = ->
       atom.project.setPath(workingDirPath)
-      editor = atom.project.openSync(filePathRelativeToWorkingDir)
-      githubFile = GitHubFile.fromPath(editor.getPath())
+      waitsForPromise ->
+         atom.workspace.open(filePathRelativeToWorkingDir)
+
+      runs ->
+        editor = atom.workspace.getActiveEditor()
+        githubFile = GitHubFile.fromPath(editor.getPath())

     teardownWorkingDirAndRestoreFixture = (fixtureName) ->
       success = null
@@ -55,7 +59,7 @@

         beforeEach ->
           setupWorkingDir(fixtureName)
-          githubFile = setupGithubFile()
+          setupGithubFile()

         afterEach ->
           teardownWorkingDirAndRestoreFixture(fixtureName)
@@ -76,19 +80,23 @@

         describe "when the file has a '#' in its name", ->
           it "opens the GitHub.com blob URL for the file", ->
-            editor = atom.project.openSync('a/b#/test#hash.md')
-            githubFile = GitHubFile.fromPath(editor.getPath())
-            spyOn(githubFile, 'openUrlInBrowser')
-            githubFile.open()
-            expect(githubFile.openUrlInBrowser).toHaveBeenCalledWith \
-              'https://github.com/some-user/some-repo/blob/master/a/b%23/test%23hash.md'
+            waitsForPromise ->
+              atom.workspace.open('a/b#/test#hash.md')
+
+            runs ->
+              editor = atom.workspace.getActiveEditor()
+              githubFile = GitHubFile.fromPath(editor.getPath())
+              spyOn(githubFile, 'openUrlInBrowser')
+              githubFile.open()
+              expect(githubFile.openUrlInBrowser).toHaveBeenCalledWith \
+                'https://github.com/some-user/some-repo/blob/master/a/b%23/test%23hash.md'

       describe "when the branch has a '/' in its name", ->
         fixtureName = 'branch-with-slash-in-name'

         beforeEach ->
           setupWorkingDir(fixtureName)
-          githubFile = setupGithubFile()
+          setupGithubFile()

         afterEach ->
           teardownWorkingDirAndRestoreFixture(fixtureName)
@@ -104,7 +112,7 @@

         beforeEach ->
           setupWorkingDir(fixtureName)
-          githubFile = setupGithubFile()
+          setupGithubFile()

         afterEach ->
           teardownWorkingDirAndRestoreFixture(fixtureName)
@@ -120,7 +128,7 @@

         beforeEach ->
           setupWorkingDir(fixtureName)
-          githubFile = setupGithubFile()
+          setupGithubFile()

         afterEach ->
           teardownWorkingDirAndRestoreFixture(fixtureName)
@@ -136,7 +144,7 @@

         beforeEach ->
           setupWorkingDir(fixtureName)
-          githubFile = setupGithubFile()
+          setupGithubFile()

         afterEach ->
           teardownWorkingDirAndRestoreFixture(fixtureName)
@@ -152,7 +160,7 @@

         beforeEach ->
           setupWorkingDir(fixtureName)
-          githubFile = setupGithubFile()
+          setupGithubFile()

         afterEach ->
           teardownWorkingDirAndRestoreFixture(fixtureName)
@@ -186,7 +194,7 @@

         beforeEach ->
           setupWorkingDir(fixtureName)
-          githubFile = setupGithubFile()
+          setupGithubFile()

         afterEach ->
           teardownWorkingDirAndRestoreFixture(fixtureName)
@@ -211,7 +219,7 @@

         beforeEach ->
           setupWorkingDir(fixtureName)
-          githubFile = setupGithubFile()
+          setupGithubFile()

         afterEach ->
           teardownWorkingDirAndRestoreFixture(fixtureName)
@@ -228,7 +236,7 @@

         beforeEach ->
           setupWorkingDir(fixtureName)
-          githubFile = setupGithubFile()
+          setupGithubFile()

         afterEach ->
           teardownWorkingDirAndRestoreFixture(fixtureName)
@@ -245,7 +253,7 @@
       beforeEach ->
         setupWorkingDir(fixtureName)
         atom.config.set('open-on-github.includeLineNumbersInUrls', true)
-        githubFile = setupGithubFile()
+        setupGithubFile()

       afterEach ->
         teardownWorkingDirAndRestoreFixture(fixtureName)
@@ -261,8 +269,6 @@
           expect(atom.clipboard.read()).toBe 'https://github.com/some-user/some-repo/blob/master/some-dir/some-file.md#L3'

   describe "githubRepoUrl", ->
-    githubFile = null
-
     beforeEach ->
       githubFile = new GitHubFile()


[skip] Added overridable context method to model editor (2m)

--- vendor/assets/javascripts/neat/model_editor.coffee
+++ vendor/assets/javascripts/neat/model_editor.coffee
@@ -31,8 +31,7 @@
       @edit() if @canEdit() and !$(e.target).isIn('input, button, a, label')

   render: ->
-    json = _.extend(@model.toJSON(), {options: @templateOptions})
-    @$el.html @template()(json)
+    @$el.html @template()(@context())
     @$el.attr('id', "#{@resource}_#{@model.get('id')}") # e.g. "calendar_5"

     @afterRender()
@@ -94,6 +93,9 @@

   okToSave: (attributes)->
     true
+
+  context: ->
+    _.extend(@model.toJSON(), {options: @templateOptions})

   attributesFromForm: ($el) ->
     attrs = {}


Listen for both load and change

Listen for both load and change

--- client/lanes/components/grid/Grid.cjsx
+++ client/lanes/components/grid/Grid.cjsx
@@ -13,7 +13,10 @@
         query: 'props'

     bindDataEvents:
-        query: 'load'
+        query: 'load change'
+
+    getInitialState: ->
+        selectedIndex: null, toolbar: !!@props.allowCreate

     setDataState: (newstate) ->
         @refs.grid?.reload() if newstate.query
@@ -37,9 +40,6 @@

     getDefaultProps: ->
         headerHeight: 40, rowHeight: 30, editorProps: {}, autoLoadQuery: true
-
-    getInitialState: ->
-        toolbar: !!@props.allowCreate

     componentWillMount: -> @query.results.ensureLoaded(0) if @props.autoLoadQuery


Tests: Removed wrong assertion added pause.

--- client/test/lib/helpers/helpers.coffee
+++ client/test/lib/helpers/helpers.coffee
@@ -168,7 +168,7 @@
     browser
       .pause                    2500 # while typing something steals activity input focus
       .click                    '[testpath="public-feed-link/Activity/Topic/public"]'
-      .waitForElementVisible    '.topic-public [testpath=activity-list] [testpath=ActivityListItemView]', 20000
+      .pause                    3000 # for page load
       .waitForElementVisible    '[testpath=ActivityInputView]', 30000
       .click                    '[testpath="ActivityTabHandle-/Activity/Public/Recent"] a'
       .waitForElementVisible    '.most-recent [testpath=activity-list]', 30000


Add fail load

Add fail load

--- views/components/settings/parts/navigator-bar.cjsx
+++ views/components/settings/parts/navigator-bar.cjsx
@@ -3,6 +3,8 @@
 webview = $('kan-game webview')
 getIcon = (status) ->
   switch status
+    when -2
+      <FontAwesome name='times' />
     when -1
       <FontAwesome name='arrow-right' />
     when 0
@@ -28,6 +30,9 @@
     @setState
       navigateStatus: 0
       navigateUrl: webview.getUrl()
+  handleFailLoad: ->
+    @setState
+      navigateStatus: -2
   handleNavigate: ->
     webview.src = @state.navigateUrl
   handleRefresh: ->
@@ -35,9 +40,11 @@
   componentDidMount: ->
     webview.addEventListener 'did-start-loading', @handleStartLoading
     webview.addEventListener 'did-stop-loading', @handleStopLoading
+    webview.addEventListener 'did-fail-load', @handleFailLoad
   componentWillUmount: ->
     webview.removeEventListener 'did-start-loading', @handleStartLoading
     webview.removeEventListener 'did-stop-loading', @handleStopLoading
+    webview.removeEventListener 'did-fail-load', @handleFailLoad
   render: ->
     <Grid>
       <Col xs={8}>


Fixed submitItem keybinding

Fixed submitItem keybinding

--- src/controllers/expensesCtrl.coffee
+++ src/controllers/expensesCtrl.coffee
@@ -76,8 +76,8 @@
   )
   clearAddItemDialog()

-$('#inputDes, #inputPrice, #inputDate').bind 'keydown', 'return', ->
-  submitItem()
+$('#inputDes, #inputPrice, #inputDate').bind 'keydown', (e) ->
+  submitItem() if e.which == 13 # Enter

 $('.expenses tbody').on 'click', '.delete', ->
   id = $(this).parent().data 'id'


appears to no longer be necessary

appears to no longer be necessary

--- lib/debugger/stepper-view.coffee
+++ lib/debugger/stepper-view.coffee
@@ -63,9 +63,6 @@
       type: 'overlay'
       item: @view
       stable: true
-    # Work around markers being destroyed when closing a split editor
-    @mListener?.dispose()
-    @mListener = @marker.onDidDestroy => @attach() unless @destroyed

   constructor: (@editor, @line) ->
     @createView()
@@ -93,7 +90,6 @@
   destroy: ->
     @destroyed = true
     @disposables.dispose()
-    @mListener.dispose()
     @rmClass @editor
     @fadeOut =>
       @marker.destroy()


Address @janpaul123's concerns

Address @janpaul123's concerns

--- app/js/initializers/xdm.coffee
+++ app/js/initializers/xdm.coffee
@@ -1,6 +1,5 @@
-# Initiate the easyXDM object
-if(this.Factlink)
-  this.Factlink.easyXDM =easyXDM.noConflict("FACTLINK")
+# Initiate the easyXDM object
+Factlink.easyXDM = easyXDM.noConflict("FACTLINK")

 Factlink.remote = new Factlink.easyXDM.Rpc {
   # The URL to load


koding router: bugfix: forgot to update the name handler to our new, more readable style.

--- client/app/MainApp/kodingrouter.coffee
+++ client/app/MainApp/kodingrouter.coffee
@@ -54,7 +54,7 @@
     @getSingleton('groupsController').changeGroup group, (err)=>
       if err then new KDNotificationView title: err.message
       else
-        appManager = KD.getSingleton("appManager")
+        appManager = KD.getSingleton "appManager"
         appManager.open app
         appManager.tell app, 'handleQuery', query

@@ -145,8 +145,8 @@
         unless KD.isLoggedIn() then __utils.defer fn
         else clear()

-    createSectionHandler = (sec)->
-      ({params:{name}, query})-> @openSection sec, name, query
+    createSectionHandler = (sec)=>
+      ({params:{name}, query})=> @openSection sec, name, query

     createContentHandler = @bound 'createContentDisplayHandler'

@@ -271,23 +271,23 @@

       # top level names
       '/:name':do->
-        open =(routeInfo, model, status_404)->
+        open =(routeInfo, model)->
           switch model?.bongo_?.constructorName
-            when 'JAccount' then content.Members routeInfo, model
-            when 'JGroup'   then content.Groups  routeInfo, model
-            else status_404()
+            when 'JAccount'
+              (createContentHandler 'Members') routeInfo, model
+            when 'JGroup'
+              (createSectionHandler 'Activity') routeInfo, model
+            else
+              @handleNotFound routeInfo.params.name

         nameHandler =(routeInfo, state, route)->
           return  if @landingPageLoading

-          {params} = routeInfo
-          status_404 = @handleNotFound.bind this, params.name
-
           if state?
-            open routeInfo, state, status_404
+            open.call this, routeInfo, state

           else
-            KD.remote.cacheable params.name, (err, [model], name)->
-              open routeInfo, model, status_404
+            KD.remote.cacheable routeInfo.params.name, (err, [model], name)->
+              open.call this, routeInfo, model

     routes


import as functions

import as functions

--- resources/assets/lib/beatmapsets-show/main.coffee
+++ resources/assets/lib/beatmapsets-show/main.coffee
@@ -12,7 +12,7 @@
 import * as React from 'react'
 import { div } from 'react-dom-factories'
 import * as BeatmapHelper from 'utils/beatmap-helper'
-import * as BeatmapsetPageHash from 'utils/beatmapset-page-hash'
+import { generate, parse, setHash } from 'utils/beatmapset-page-hash'
 import { nextVal } from 'utils/seq'
 import { currentUrl } from 'utils/turbolinks'
 import { Header } from './header'
@@ -35,7 +35,7 @@
     if @restoredState
       @state.beatmaps = new Map(@state.beatmapsArray)
     else
-      optionsHash = BeatmapsetPageHash.parse currentUrl().hash
+      optionsHash = parse currentUrl().hash

       beatmaps = _.concat props.beatmapset.beatmaps, props.beatmapset.converts
       beatmaps = BeatmapHelper.group beatmaps
@@ -268,9 +268,9 @@

   setHash: =>
-    BeatmapsetPageHash.setHash BeatmapsetPageHash.generate
+    setHash generate
       beatmap: @state.currentBeatmap

   tabHrefFunc: (mode) ->
-    BeatmapsetPageHash.generate mode: mode
+    generate mode: mode


Remove redundant ::setTextViaDiff specs

Remove redundant ::setTextViaDiff specs

--- spec/text-buffer-spec.coffee
+++ spec/text-buffer-spec.coffee
@@ -170,81 +170,6 @@
       buffer.undo()
       expect(buffer.getText()).toBe "hello\nworld\r\nyou are cool"

-  describe "::setTextViaDiff(text)", ->
-    describe "when the buffer contains no newlines", ->
-      beforeEach ->
-        buffer = new TextBuffer('original content')
-
-      it "can change the contents of the buffer", ->
-        newText = 'new text'
-        buffer.setTextViaDiff(newText)
-        expect(buffer.getText()).toBe newText
-
-    describe "when the buffer contains standard newlines", ->
-      beforeEach ->
-        buffer = new TextBuffer(SampleText)
-
-      it "can replace the contents of the buffer with text that doesn't end in a newline", ->
-        newText = "I know you are.\nBut what am I?"
-        buffer.setTextViaDiff(newText)
-        expect(buffer.getText()).toBe newText
-
-      it "can replace the contents of the buffer with text that ends in a newline", ->
-        newText = "I know you are.\nBut what am I?\n"
-        buffer.setTextViaDiff(newText)
-        expect(buffer.getText()).toBe newText
-
-      it "can change a few lines at the beginning in the buffer", ->
-        newText = buffer.getText().replace(/function/g, 'omgwow')
-        buffer.setTextViaDiff(newText)
-        expect(buffer.getText()).toBe newText
-
-      it "can change a few lines in the middle of the buffer", ->
-        newText = buffer.getText().replace(/shift/g, 'omgwow')
-        buffer.setTextViaDiff(newText)
-        expect(buffer.getText()).toBe newText
-
-      it "can add a newline to the end of the buffer", ->
-        newText = buffer.getText() + '\n'
-        buffer.setTextViaDiff(newText)
-        expect(buffer.getText()).toBe newText
-
-    describe "when the buffer contains windows newlines", ->
-      beforeEach ->
-        buffer = new TextBuffer(SampleText.replace(/\n/g, '\r\n'))
-
-      it "can replace the contents of the buffer with shorter text that doesn't end in a newline", ->
-        newText = "I know you are.\r\nBut what am I?"
-        buffer.setTextViaDiff(newText)
-        expect(buffer.getText()).toBe newText
-
-      it "can replace the contents of the buffer with shorter text that doesn't end in a newline", ->
-        newText = "I know you are.\r\nBut what am I?\r\n"
-        buffer.setTextViaDiff(newText)
-        expect(buffer.getText()).toBe newText
-
-      it "can change a few lines at the beginning in the buffer", ->
-        newText = buffer.getText().replace(/function/g, 'omgwow')
-        buffer.setTextViaDiff(newText)
-        expect(buffer.getText()).toBe newText
-
-      it "can change a few lines in the middle of the buffer", ->
-        newText = buffer.getText().replace(/shift/g, 'omgwow')
-        buffer.setTextViaDiff(newText)
-        expect(buffer.getText()).toBe newText
-
-      it "can add a newline at the end of the buffer", ->
-        newText = buffer.getText() + '\r\n'
-        buffer.setTextViaDiff(newText)
-        expect(buffer.getText()).toBe newText
-
-    describe "when the buffer contains carriage returns for newlines", ->
-      fit "can replace the contents of the buffer", ->
-        buffer = new TextBuffer("first\rsecond\rlast")
-        newText = "new first\rnew last"
-        buffer.setTextViaDiff(newText)
-        expect(buffer.getText()).toBe newText
-
   describe "::insert(position, text, normalizeNewlinesn)", ->
     it "inserts text at the given position", ->
       buffer = new TextBuffer("hello world")
@@ -1251,6 +1176,13 @@
         buffer.setTextViaDiff(newText)
         expect(buffer.getText()).toBe newText

+    describe "when the buffer contains carriage returns for newlines", ->
+      it "can replace the contents of the buffer", ->
+        buffer = new TextBuffer("first\rsecond\rlast")
+        newText = "new first\rnew last"
+        buffer.setTextViaDiff(newText)
+        expect(buffer.getText()).toBe newText
+
   describe "::save()", ->
     saveBuffer = null


Fix typo in scan's spec.

Fix typo in scan's spec.

--- spec/text-buffer-spec.coffee
+++ spec/text-buffer-spec.coffee
@@ -1407,7 +1407,7 @@
       waitsFor ->
         buffer.loaded

-    it "retunrns lineText and lineTextOffset", ->
+    it "returns lineText and lineTextOffset", ->
       matches = []
       buffer.scan /current/, (match) ->
         matches.push(match)


Fix bug where using up and down arrows would sometimes jump sections prematurely.

--- src/Editor.coffee
+++ src/Editor.coffee
@@ -59,7 +59,7 @@

   handleUpPress: (event, caretOffset, textNode) =>
     isAtStart = caretOffset == 0
-    hasPreviousSibling = textNode.previousSibling
+    hasPreviousSibling = !allAncestorsUntil(textNode, event.target, (node) -> !node.previousSibling)
     isEmptyEditor = event.target.textContent == "" &&
       event.target.previousSibling == textNode.previousSibling

@@ -72,14 +72,24 @@

   handleDownPress: (event, caretOffset, textNode) =>
     isAtEnd = caretOffset == textNode.textContent.length
-    hasNextSibling = textNode.nextSibling
+    hasNextSibling = !allAncestorsUntil(textNode, event.target, (node) -> !node.nextSibling)
     isEmptyEditor = event.target.textContent == "" &&
       event.target.nextSibling == textNode.nextSibling

-    # TODO somtimes detects incorrectly...might be we can't trust event.target,
-    # and might instead need to look at the textNode's first contentEditable ancestor.
     if isAtEnd && (!hasNextSibling || isEmptyEditor)
       event.target.nextSibling?.focus()
+
+  # Returns true iff the predicate function returns true for the current source
+  # and target, as well as every ancestor node of the source, until the target
+  # ancestor has been reached.
+  allAncestorsUntil = (source, target, predicate) ->
+    if source == target
+      true
+    else if !source
+      throw new Error "allAncestorsUntil() was called with a source that did not have the target for an ancestor!"
+    else
+      predicate(source) &&
+        allAncestorsUntil(source.parentNode, target, predicate)

   applySmartQuote: (event, caretOffset, textNode) =>
     event.preventDefault()


Move anims: Implement status moves

Move anims: Implement status moves

--- client/app/js/views/battle_view.coffee
+++ client/app/js/views/battle_view.coffee
@@ -288,14 +288,14 @@
       done()
       return

+    [targetPlayer, targetSlot] = targetSlots[0]
+    $attacker = @$sprite(player, slot)
+    $defender = @$sprite(targetPlayer, targetSlot)
+    [a, d] = [$attacker.position(), $defender.position()]
+    scale = (if a.top < d.top then 1.3 else 1/1.3)
     if 'contact' in moveData.flags
       # Simple attack animation
       # Tackling the opponent
-      [targetPlayer, targetSlot] = targetSlots[0]
-      $attacker = @$sprite(player, slot)
-      $defender = @$sprite(targetPlayer, targetSlot)
-      [a, d] = [$attacker.position(), $defender.position()]
-      scale = (if a.top < d.top then 1.3 else 1/1.3)
       $attacker
         .transition(x: d.left - a.left, y: d.top - a.top, scale: scale, 250, 'in')
         .transition(x: 0, y: 0, scale: 1, 250, 'out')
@@ -306,10 +306,6 @@
     else if moveData['power'] > 0
       # Non-contact attacking move
       # Projectile
-      [targetPlayer, targetSlot] = targetSlots[0]
-      $attacker = @$sprite(player, slot)
-      $defender = @$sprite(targetPlayer, targetSlot)
-      [a, d] = [$attacker.position(), $defender.position()]
       $projectile = $('<div/>').addClass('projectile')
       $projectile.addClass(moveData['type'].toLowerCase())
       $projectile.appendTo(@$(".battle_pane"))
@@ -326,6 +322,22 @@
         .transition(x: -4, 0, 'linear').delay(50)
         .transition(x: 4, 0, 'linear').delay(50)
         .transition(x: 0, 0, 'linear', done)
+    else if player != targetPlayer || slot != targetSlot
+      # This is a non-attacking move that affects another pokemon
+      # S-shaped movement
+      $projectile = $('<div/>').addClass('projectile')
+      $projectile.addClass(moveData['type'].toLowerCase())
+      $projectile.appendTo(@$(".battle_pane"))
+      $projectile.css(
+        top: a.top + (($attacker.height() - $projectile.height()) >> 1)
+        left: a.left + (($attacker.width() - $projectile.width()) >> 1)
+      )
+      [transX, transY] = [(d.left - a.left), (d.top - a.top)]
+      $projectile
+        .transition(x: transX * 2 / 3, y: transY / 3, 150, 'easeInOutSine')
+        .transition(x: transX / 3, y: transY * 2 / 3, 100, 'easeInOutSine')
+        .transition(x: transX, y: transY, 150, 'easeInOutSine')
+        .transition(opacity: 0, 100, 'easeInOutSine', done)
     else
       # Side-to-side movement
       $attacker = @$sprite(player, slot)


Kloud: increase ping timeout for managed vms

--- client/app/lib/kite/kites/kloud.coffee
+++ client/app/lib/kite/kites/kloud.coffee
@@ -123,7 +123,7 @@
           computeController.invalidateCache machineId
           callback null

-      .timeout 5000
+      .timeout if managed then 10000 else 5000

       .catch (err)->


FSHelper: Add machine uid to file paths.

--- client/app/lib/util/fs/fshelper.coffee
+++ client/app/lib/util/fs/fshelper.coffee
@@ -14,7 +14,11 @@
     else if not options?.path?
       return kd.warn "pass a path and type to create a file instance"

-    unless options.machine?
+    if options.machine
+      unless options.path.indexOf('localfile:/') is 0
+        # make sure that we machine and it's uid in path
+        options.path = "[#{options.machine.uid}]#{@plainPath options.path}"
+    else
       kd.warn "No machine instance passed, creating dummy file instance"
       options.machine = new DummyMachine


fixed install w/o previously existing .test dir

--- component.coffee
+++ component.coffee
@@ -10,6 +10,7 @@
 read = (fname, encoding="utf8") -> fs.readFileSync(fname, encoding)
 exists = path.existsSync
 join = path.join
+pathSep = path.normalize("/")
 argv = null

 usage = """node $0 [-l] install
@@ -63,6 +64,7 @@
     if link
       orig = path.resolve process.cwd()
       process.chdir tgtDir
+      console.log "IN THERE"
       fs.symlinkSync join(orig, src), name, "dir"
       process.chdir orig
     else
@@ -93,9 +95,10 @@

 provide = (pth) ->
   if not exists pth
-    _.reduce [""].concat(pth.split("/")), (soFar, dir) ->
-      fs.mkdirSync join(soFar, dir) if not exists(join(soFar, dir))
-      soFar += (if soFar then "/" else "") + dir
+    _.reduce [""].concat(pth.split(pathSep)), (soFar, dir) ->
+      if not exists(join(soFar, dir))
+        fs.mkdirSync join(soFar, dir)
+      soFar += (if soFar then pathSep else "") + dir

 discoverTests = (dir) ->
   dir ||= if isComponent() then join(testDirectory(), "static") else "static"
@@ -184,7 +187,7 @@
       commands.install()
     if not exists join(staticDirName, "components", componentName())
       commands.install()
-      installTo join(staticDirName, "components"), true
+      installTo join(staticDirName, "components"), argv.link
     tests = discoverTests staticDirName
     extraHtml = getTestTemplate() + "\n" + stringBundlesAsRequirejsModule()
     testServer


don't AutoRegister a second semester course

Otherwise they won't see the info messages

--- coach/src/course/new-registration.cjsx
+++ coach/src/course/new-registration.cjsx
@@ -41,7 +41,8 @@
     if User.isLoggedIn() and
       @props.enrollmentCode and
       not course.isPending() and
-      not course.isRegistered()
+      not course.isRegistered() and
+      not course.isSecondSemester()
         course.register(@props.enrollmentCode, User)
         @setState(isAutoRegistering: true)


Optionally fire show and hide events in tip. :satellite:

--- src/js/jquery.hlf.tip.coffee
+++ src/js/jquery.hlf.tip.coffee
@@ -83,6 +83,10 @@
       #   direction. Note that the direction data structure must be an array of
       #   `components`, and conventionally with top/bottom first.
       defaultDirection: ['bottom', 'right']
+      # - `fireEvents` can be enabled to allow `show.hlf.tip` and `shown.hlf.tip`,
+      #   `hide.hlf.tip` and `hidden.hlf.tip` events to be triggered from the
+      #   trigger elements. This is off by default to improve performance.
+      fireEvents: on
       # - `tipTemplate` should return interpolated html when given the
       #   additional container class list. Its context is the plugin instance.
       tipTemplate: (containerClass) ->
@@ -266,10 +270,17 @@
       @_state = state
       @debugLog @_state
       switch state
-        when 'asleep' then @afterHide.apply @, data
-        when 'awake' then @afterShow.apply @, data
-        when 'sleeping' then clearTimeout @_wakeCountdown
+        when 'asleep'
+          @_$currentTrigger?.trigger @evt('hidden') if @fireEvents is on
+          @afterHide.apply @, data
+        when 'awake'
+          @_$currentTrigger?.trigger @evt('shown') if @fireEvents is on
+          @afterShow.apply @, data
+        when 'sleeping'
+          @_$currentTrigger?.trigger @evt('hide') if @fireEvents is on
+          clearTimeout @_wakeCountdown
         when 'waking'
+          @_$currentTrigger?.trigger @evt('show') if @fireEvents is on
           clearTimeout @_sleepCountdown
           @_triggerChanged = yes

@@ -332,11 +343,12 @@
       deferred = $.Deferred()
       promise = deferred.promise()
       @_setCurrentTrigger $trigger
-
-      # - Go directly to the position updating if no toggling is needed.
+      #- Abstract into subroutine.
       updateBeforeWake = =>
         @_positionToTrigger $trigger, event
         @onShow event # Hook in custom logic.
+
+      # - Go directly to the position updating if no toggling is needed.
       if @_state is 'awake'
         @debugLog 'quick update'
         updateBeforeWake()


fixed loading timing error

fixed loading timing error

Also fixes the broken-slier bug. @tenedor

--- coffee/src/shells/youtube.shell.coffee
+++ coffee/src/shells/youtube.shell.coffee
@@ -152,7 +152,8 @@

   isPlaying: =>
-    @player?.getPlayerState() == YT.PlayerState.PLAYING
+    if @player? and YT?
+      @player.getPlayerState() == YT.PlayerState.PLAYING

   seekOffset: =>


クラス名を修正

クラス名を修正

--- lib/index.coffee
+++ lib/index.coffee
@@ -1,3 +1,3 @@

-module.exports = class WebMercator
+module.exports = class Index
   # TODO:


WIP ~ Keyboard navigation by using up-down arrow keys

--- core/KDView.coffee
+++ core/KDView.coffee
@@ -222,7 +222,7 @@
   getElement:-> @getDomElement()[0]

   # shortcut method for @getDomElement()
-
+
   $ :(selector)->
     if selector?
       @getDomElement().find(selector)
@@ -300,6 +300,10 @@

   toggleClass:(cssClass)->
     @$().toggleClass cssClass
+    @
+
+  hasClass:(cssClass)->
+    @$().hasClass cssClass
     @

   getBounds:()->


Revert "Move aloha cleanup code to onClose method."

--- scripts/views/workspace/content/aloha-edit.coffee
+++ scripts/views/workspace/content/aloha-edit.coffee
@@ -63,11 +63,7 @@
       $(Aloha, 'body').off 'aloha-smart-content-changed.updatemodel'

       # Kill old editable, otherwise they accumulate on Aloha.editables.
-      @$el.mahalo?()
-
-      # reset some of the state of our root element so aloha doesn't freak out
-      @$el.removeClass('aloha-block-blocklevel-sortable')
-      @$el.removeData()
+      Aloha.getEditableById(@$el.attr('id')).destroy()

     onRender: () ->
       # update model after the user has stopped making changes
@@ -85,8 +81,16 @@

         # Once Aloha has finished loading enable
         @$el.addClass('disabled')
+
         Aloha.ready =>
-          @$el.addClass('aloha-root-editable').aloha()
+          @$el.addClass('aloha-root-editable')
+          @$el.mahalo?()
+
+          # resset some of the state of our root element so aloha doesn't freak out
+          @$el.removeClass('aloha-block-blocklevel-sortable')
+          @$el.removeData()
+
+          @$el.aloha()

           Aloha.bind 'aloha-smart-content-changed.updatemodel', (evt, d) =>
             updateModel() if d.editable.obj.is(@$el) or $.contains @$el[0], d.editable.obj[0]


social: update documentation

social: update documentation

--- workers/social/lib/social/models/invitation.coffee
+++ workers/social/lib/social/models/invitation.coffee
@@ -23,7 +23,10 @@
       'send invitations' : ['moderator', 'admin']
     indexes         :
       code          : 'unique'
-      # TODO create a compound index on groupName and
+      # email         : 'ascending'
+      # groupName     : 'ascending'
+      #
+      # TODO(cihagir) create a compound unique index on groupName and email
     sharedMethods   :

       instance:
@@ -77,19 +80,18 @@
         type        : Date
         default     : -> new Date

-  # remove deleted invitation from database
+  accept$: secure (client, callback) ->
+    { delegate } = client.connection
+    @accept delegate, callback
+
+  accept: (account, callback) ->
+    operation = $set : { status: 'accepted' }
+    @update operation, callback
+
+  # remove deletes an invitation from database
   remove$: permit 'send invitations',
     success: (client, callback) ->
       @remove callback
-
-  accept$: secure (client, callback) ->
-    { delegate } = client.connection
-    @accept delegate, callback
-
-  accept: (account, callback) ->
-    operation = $set : { status: 'accepted' }
-    @update operation, callback
-

   # some selects result set for invitations, it adds group name automatically
   @some$:  permit 'send invitations',
@@ -109,6 +111,8 @@
       console.log selector, options
       JInvitation.some selector, options, callback

+  # search searches database with given query string, adds `starting
+  # with regex` around query param
   @search$:  permit 'send invitations',
     success: (client, query, options, callback) ->
       selector = { $or : [ {'name' : ///^#{query}/// }, {'email' : ///^#{query}/// } ]}


Use argument.isNew for ArgumentVotes#isNew

--- core/app/backbone/models/argument_votes.coffee
+++ core/app/backbone/models/argument_votes.coffee
@@ -3,7 +3,7 @@
     believes: 0
     disbelieves: 0

-  isNew: -> false
+  isNew: -> @_argument.isNew()

   initialize: (attributes, options) ->
     @_argument = options.argument


Removed redundant returns

Removed redundant returns

--- coffee/mylibs/mandelbrot-worker.coffee
+++ coffee/mylibs/mandelbrot-worker.coffee
@@ -19,14 +19,11 @@
             real = interpolate column, box.left, box.right, 0, width
             imaginary = interpolate row, box.top, box.bottom, 0, height
             rowTimes.push (getEscapeTimeAtPoint real, imaginary, maxIterations)
-            return 0
         escapeTimes.push rowTimes
         message = JSON.stringify
           message:"progress"
           value:100*(row/height)
         self.postMessage message
-        return 0
-
   return escapeTimes

 interpolate = (x, y0, y1, x0, x1) -> y0 + (x - x0)*((y1 - y0)/(x1-x0))


fewer help tests

fewer help tests

--- test/mocha/cli.spec.coffee
+++ test/mocha/cli.spec.coffee
@@ -27,13 +27,9 @@

   describe "?`, short for help", ->
     it "is same as `man precious-readme`", ->
-      (proExec "#{precious} ?").then (result) ->
-        assertSame [result, matches.man.readme7]
-
-  describe "help`", ->
-    it "is same as `man precious-readme`", ->
-      (proExec "#{precious} help").then (result) ->
-        assertSame [result, matches.man.readme7]
+      (proExec "#{precious} ?").then (shorthand) ->
+        (proExec "#{precious} help").then (help) ->
+          assertSame [shorthand, help, matches.man.readme7]

   describe "help json`", ->
     it "is `man precious-json`", ->


fix: don't delete reservations you don't have

--- lib/index.coffee
+++ lib/index.coffee
@@ -20,6 +20,7 @@

 module.exports = class ReserveResource
   constructor: (@by, @host, @port, @heartbeat_interval, @lock_ttl, @log=console.log) ->
+    @_lost_reservation = false
     return

   lock: (resource, cb) ->
@@ -64,6 +65,7 @@

   release: (cb) ->
     return setImmediate cb unless @_reserve_key?  # nothing reserved here
+    return setImmediate cb if @_lost_reservation

     clearInterval @_heartbeat if @_heartbeat? # we're done here, no more heartbeats
     @_init_redis()
@@ -83,8 +85,10 @@
       (cb_wf) => @_redis.get @_reserve_key, cb_wf
       (reserved_by, cb_wf) =>
         unless reserved_by?  # we losts it
+          @_lost_reservation = true
           throw new Error "Worker #{@_reserve_val} lost reservation for #{@_reserve_key}"
         unless reserved_by is @_reserve_val  # they stole the precious!!!
+          @_lost_reservation = true
           throw new Error "Worker #{@_reserve_val} lost #{@_reserve_key} to #{reserved_by}"
         @_set_expiration cb_wf
     ], (err, expire_state) =>


remove button instead of hiding it

remove button instead of hiding it

--- app/assets/javascripts/comfy/admin/cms/resources.js.coffee
+++ app/assets/javascripts/comfy/admin/cms/resources.js.coffee
@@ -23,7 +23,7 @@

           childInputs.forEach((node) => node.remove() )

-          $(this).hide()
+          $(this).remove()
       )
     )

@@ -52,7 +52,7 @@

           childInputs.forEach((node) => node.remove() )

-          $(this).hide()
+          $(this).remove()
       )
   )
 )


move fetchSession logic in juser.login

move fetchSession logic in juser.login

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -381,20 +381,14 @@
           queue.next()

       ->
-        # fetch session of the current requester
-        JSession.fetchSession clientId, (err, { session: fetchedSession }) ->
-          return callback err  if err
-
-          session = fetchedSession
-          unless session
-            console.error "login: session not found #{username}"
-            return callback new KodingError 'Couldn\'t restore your session!'
+        fetchSession {
+          clientId, username
+        }, queue, callback, (session_) ->
+          session = session_

           bruteForceControlData =
             ip        : session.clientIP
             username  : username
-
-          queue.next()

       ->
         # todo add alert support(mail, log etc)
@@ -543,6 +537,23 @@

       user.verifyByPin options, callback

+
+  fetchSession = (options, queue, callback, fetchData) ->
+
+    { clientId, username } = options
+
+    # fetch session of the current requester
+    JSession.fetchSession clientId, (err, { session: fetchedSession }) ->
+      return callback err  if err
+
+      session = fetchedSession
+      unless session
+        console.error "login: session not found #{username}"
+        return callback new KodingError 'Couldn\'t restore your session!'
+
+      fetchData session
+
+      queue.next()

   fetchInvitationByCode = (invitationToken, queue, callback, fetchData) ->


allow passing options to glob, properly handle cwd

--- lib/index.coffee
+++ lib/index.coffee
@@ -26,19 +26,25 @@
     setOutput: (path) ->
       zipPath = path

-    add: (path, callback) ->
+    add: (path, options={}, callback=->) ->
       if not initialized
         initialize()

-      glob path, (err, files) ->
+      if typeof options is 'function'
+        callback = options
+        options = {}
+
+      cwd = options.cwd || ''
+
+      glob path, options, (err, files) ->
         callback err if err
         files.forEach (file, i) ->
-          fs.stat file, (err, stats) ->
+          fs.stat "#{cwd}/#{file}", (err, stats) ->
             if stats.isFile()
               do (file) ->
                 fileList.push file
                 fileStack.push (cb) ->
-                  zip.append(fs.createReadStream(file), name: file, cb)
+                  zip.append(fs.createReadStream("#{cwd}/#{file}"), name: file, cb)

             if i is files.length-1
               callback()
@@ -46,7 +52,7 @@
     getFiles: ->
       return fileList

-    compress: (callback) ->
+    compress: (callback=->) ->
       if not initialized
         initialize()


map magic

map magic

--- app/assets/javascripts/directives/accounts/new_list.coffee
+++ app/assets/javascripts/directives/accounts/new_list.coffee
@@ -20,13 +20,12 @@

     scope.form = [
       'name',
-      { key: 'color', type: 'radiobuttons', titleMap: [
-          { name: "", value: "red" }
-          { name: "", value: "green" }
-          { name: "", value: "blue" }
-          { name: "", value: "gray" }
-          { name: "", value: "orange" }
-        ], style: { selected: 'btn-primary' }
+      {
+        key: 'color',
+        type: 'radiobuttons',
+        titleMap: ['red', 'green', 'blue', 'gray', 'orange'].map (color) ->
+          { name: "", value: color }
+        style: { selected: 'btn-primary' }
       }
       { type: 'submit', title: 'Create List', style: 'btn btn-primary' }
     ]


use an internal EventEmitter with listeners for addons

--- lib/index.coffee
+++ lib/index.coffee
@@ -1,81 +1,84 @@
-
 net = require 'net'
 tls = require 'tls'
-# TODO:
-# reconnect = require 'reconnect-net'

-# i dislike having to read all these when they may not be used even once...
-# but, it's expected to require early on to discover if they're missing and
-# so they're near the top (easy to find)
-addListeners = require './add-listeners'
-readCerts    = require './read-certs'
-relistener = require './relistener'
+class Cio

-module.exports = (builderOptions) ->
+  constructor: (@_options) ->
+    @_readCerts @_options
+    @_events = new require('events').EventEmitter

-  # TODO: builderOptions...
+  _readCerts: require './read-certs'
+  _relistener: require './relistener'

-  # build socket builder function
-  builder = (isServer, options) ->
+  _build: (isServer, options = {}) ->

-    # if there are cert file paths specified, read the files now
-    readCerts options
+    # 1. combine options
+    if @_options?
+      options[key] ?= value for key,value of @_options

-    # TODO: when `defaultOptions` actually has some values, we'll want to
-    # combine options and defaultOptions now...
+    # 2. secure if some options related to a secure connection are specified
+    if options.rejectUnauthorized or options.key? or options.private? or options.requestCert
+      isSecure = true
+      # copy aliases and delete them
+      for alias,key of private:'key', public:'cert', root:'ca'
+        options[key] = options[alias]
+        delete options[alias]

-    # 1. secure if some options related to a secure connection are specified
-    if options.rejectUnauthorized or options.key? or options.private? or options.requestCert
-      secure = true
-      # convert aliases
-      if options.private? then options.key = options.private
-      if options.public? then options.cert = options.public
-      if options.root? then options.ca = options.root
+    # 3. if there are cert file paths specified, read the files now
+    @_readCerts options

-
-    # 2. choose creator and use its function to build the socket
-    socket = do (isServer, secure) ->
+    # 4. choose creator and use its function to build the socket
+    socket = do (isServer, isSecure) ->
       functionName = if isServer then 'createServer' else 'connect'
-      creator = if secure then tls else net
-      # TODO: use `reconnect-net` for client
+      creator = if isSecure then tls else net
       creator[functionName] options

-    # always add our own listener first so we can do some work for them
-    # TODO: unless directed not to...
+    # 5. call listeners
+    if isServer # emit the server socket, and when clients connect to it
+      @_events.emit 'ns', socket, options, builderOptions
+      which = if isSecure then 'secureConnection' else 'connection'
+      events = @_events
+      socket.on which, (conn) -> events.emit 'nsc', conn, options, builderOptions

-    # 3. decide which connection listeners to add
-    whichEvents = []
-    # it's complicated for a server because it can be secured or not...
-    # and, it's possible to listen to *both* unsecured and secured
-    if isServer
-      # if secure, then we always add to 'secureConnection'
-      if secure then whichEvents.push 'secureConnection'
-      # if not secure then we always add to 'connection'
-      # also, if it's secure, but they specify an `onConnect`, then we do 'connection'
-      if not secure or options.onConnect? then whichEvents.push 'connection'
+    # otherwise, tell them it's a client socket
+    else @_events.emit 'nc', socket, options, builderOptions

-    # client is simple... it's always 'connect'
-    else whichEvents.push 'connect'
+    # 6. add their listeners, if they exist
+    if options.onSecureConnect?
+      socket.on 'secureConnection', options.onSecureConnect

-    # 4. add listeners
-    addListeners options, socket, whichEvents
-
-    # 5. add their listeners, if they exist
-    # TODO: wrap this first one with `if isServer` ?
-    if options.onSecureConnect? then socket.on 'secureConnection', options.onSecureConnect
     if options.onConnect?
       socket.on (if isServer then 'connection' else 'connect'), options.onConnect

-    # 6. unless 'address-in-use' helper is specified to *not* be used, then add it
+    # 7. unless 'address-in-use' helper is specified to *not* be used, then add it
     # for 'listening' to retry listen() the config'd number of times with config'd delay
-    if isServer and not options.noRelisten then relistener server:socket
+    if isServer and not options.noRelisten then @_relistener server:socket

-    # 7. all done
+    # all done
     return socket

-  return fns =
-    # could use bind() here, but, I find it harder to read...
-    # client: builder.bind builder, false # isServer = false
-    # server: builder.bind builder, true  # isServer = true
-    client: (options) -> builder false, options # isServer = false
-    server: (options) -> builder true, options  # isServer = true
+  # load a string via require(), return a function, and return an error otherwise
+  _load: (arg) ->
+    switch typeof arg
+      when 'string' then @_load require arg
+      when 'function' then arg
+      else error:'must be requireable string or function',arg:arg
+
+  # look thru args and load stuff
+  _fromArgs: (event, args) ->
+    if Array.isArray args[0] then args = args[0]
+
+    for arg in args
+      fn = @_load arg
+      if fn.error? then return fn
+      @_events.on event, fn
+
+    client: (options) -> @_build false, options # isServer = false
+    server: (options) -> @_build true, options  # isServer = true
+
+    onClient      : (args...) -> @_fromArgs 'nc', args  # 'nc'  = 'new client'
+    onServer      : (args...) -> @_fromArgs 'ns', args  # 'ns'  = 'new server'
+    onServerClient: (args...) -> @_fromArgs 'nsc', args # 'nsc' = 'new server client'
+
+
+module.exports = (options) -> new Cio options


trigger onchange event after clearing answers, this is needed for embedded sub surveys in the conditional logic

--- app/assets/javascripts/hanuman/conditional-logic.js.coffee
+++ app/assets/javascripts/hanuman/conditional-logic.js.coffee
@@ -120,6 +120,8 @@
     radiobuttons = container.find(":radio")
     radiobuttons.each ->
       $(this).prop('checked', false)
+    # trigger onchange event which is needed for embedded conditional logic
+    container.find('.form-control').trigger('change')

   #evaluate conditional logic rules
   evaluateCondition: (operator, answer, value) ->


clean up code

clean up code

--- app/assets/javascripts/hanuman/conditional-logic.js.coffee
+++ app/assets/javascripts/hanuman/conditional-logic.js.coffee
@@ -10,9 +10,6 @@
         self.setDefaultState(rule.question_id, this.question_id, this.operator, this.answer)
         self.bindConditions(rule.question_id, this.question_id, this.operator, this.answer)

-  #TODO: need to write default state check for edit
-  #could we actually do this by evaluating the question itself, if it has an answer show, if not hide?
-  #is there some other way?
   # set the default hide show conditions
   setDefaultState: (ancestor_id, question_id, operator, answer) ->
     self.hideShowQuestions(self.evaluateRules(operator, answer, $("[data-question-id=" + question_id + "]").val()), ancestor_id)


Fix bug when 'left' or 'top' is not defined in CSS (i.e. value "auto")

--- jquery.transformTo.js.coffee
+++ jquery.transformTo.js.coffee
@@ -27,6 +27,8 @@
       $this = $(this)
       $width = $this.width()
       $height = $this.height()
+      currentLeft = if isNaN($this.css('left')) then 0 else $this.css('left')
+      currentTop = if isNaN($this.css('top')) then 0 else $this.css('top')

       currently = $this.data('transformTo') || {}
       transforms = {scaleX: 1, scaleY: 1}
@@ -41,11 +43,11 @@
       # set translate
       if settings.x || (transforms.scaleX != 1)
         scaleExtraLeft = ($width/2)*(1-transforms.scaleX)
-        transforms.translateX = (settings.x - ($this.css("left") + scaleExtraLeft))/transforms.scaleX
+        transforms.translateX = (settings.x - (currentLeft + scaleExtraLeft))/transforms.scaleX

       if settings.y || (transforms.scaleY != 1)
         scaleExtraTop = ($height/2)*(1-transforms.scaleY)
-        transforms.translateY = (settings.y - ($this.css("top") + scaleExtraTop))/transforms.scaleY
+        transforms.translateY = (settings.y - (currentTop + scaleExtraTop))/transforms.scaleY

       # save transforms
       $this.data('transformTo', transforms)


show xhr error message if it's not undefined #65

--- app/assets/javascripts/oxalis/view/merge_modal_view.coffee
+++ app/assets/javascripts/oxalis/view/merge_modal_view.coffee
@@ -213,7 +213,10 @@
       app.router.loadURL(url)
       Toast.message(annotation.messages)
     ).fail( (xhr) ->
-      Toast.error("Error. Please try again.")
+      if xhr
+        Toast.error(xhr.responseJSON.messages[0].error)
+      else
+        Toast.error("Error. Please try again.")
     ).always( =>
       @toggleIcon()
     )


:arrow_up: add link to github repo

:arrow_up: add link to github repo

--- src/renderer/components/doc-title-component.coffee
+++ src/renderer/components/doc-title-component.coffee
@@ -1,5 +1,6 @@
 React = require 'react'
 Radium = require 'radium'
+Link = require './link-component'
 colors = require './colors/color-definition'

 class DocTitleComponent extends React.Component
@@ -61,7 +62,10 @@

       {
         unless @props.collapsed
-
{"#{@props.current.kindString} in #{@props.from.name}"}

+

+            {"#{@props.current.kindString} in "}
+            {"#{@props.from.name.replace(/"/g, '').replace(/$/, '.ts')}"}
+

       }

@@ -97,6 +101,9 @@
     fontSize: 15
     color: colors.general.r.light

+  link:
+    color: colors.general.r.light
+
 DocTitleComponent.contextTypes =
   ctx: React.PropTypes.any


feat(LMS-1451):LMS-1451 - Users not able to edit discussions that include due dates

--- app/coffeescripts/views/assignments/DueDateOverride.coffee
+++ app/coffeescripts/views/assignments/DueDateOverride.coffee
@@ -53,13 +53,14 @@
       validRowKeys = _.pluck(data.assignment_overrides, "rowKey")
       blankOverrideMsg = I18n.t('blank_override', 'You must have a student or section seleted')
       for row in $('.Container__DueDateRow-item')
-        rowKey = "#{$(row).data('row-key')}"
-        continue if _.contains(validRowKeys, rowKey)
-        identifier = 'tokenInputFor' + rowKey
-        $inputWrapper = $('[data-row-identifier="'+identifier+'"]')[0]
-        $nameInput = $($inputWrapper).find("input")
-        errors = _.extend(errors, { blankOverrides: [message: blankOverrideMsg] })
-        $nameInput.errorBox(blankOverrideMsg).css("z-index", "20")
+        unless $(row).is(':hidden')
+          rowKey = "#{$(row).data('row-key')}"
+          continue if _.contains(validRowKeys, rowKey)
+          identifier = 'tokenInputFor' + rowKey
+          $inputWrapper = $('[data-row-identifier="'+identifier+'"]')[0]
+          $nameInput = $($inputWrapper).find("input")
+          errors = _.extend(errors, { blankOverrides: [message: blankOverrideMsg] })
+          $nameInput.errorBox(blankOverrideMsg).css("z-index", "20")
       errors

     # ==============================


Implement a scrollbar width detector

Implement a scrollbar width detector

--- app/js/jail_iframe/initializers/scrollbarSuppressor.coffee
+++ app/js/jail_iframe/initializers/scrollbarSuppressor.coffee
@@ -1,3 +1,21 @@
+scrollbarWidth = 0
+FactlinkJailRoot.loaded_promise.then ->
+  # Create the measurement nod; see http://davidwalsh.name/detect-scrollbar-width
+  scrollDiv = document.createElement("div");
+  $(scrollDiv).css(
+    width: "100px"
+    height: "100px"
+    overflow: "scroll"
+    position: "absolute"
+    top: "-9999px"
+  )
+  document.body.appendChild(scrollDiv)
+
+  scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth
+
+  document.body.removeChild(scrollDiv)
+  console.log 'scrollbar width: ', scrollbarWidth
+
 FactlinkJailRoot.on 'modalOpened', ->
   document.documentElement.setAttribute('data-factlink-suppress-scrolling', '')
 FactlinkJailRoot.on 'modalClosed', -> document.documentElement.removeAttribute('data-factlink-suppress-scrolling')


Added dependencies to build output.

Added dependencies to build output.

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -7,8 +7,8 @@
 gzip = require 'gulp-gzip'
 fs = require 'fs'

-aws = fs.readFileSync("aws.json")
-
+aws = JSON.parse fs.readFileSync("aws.json").toString()
+
 s3options =
   "gzippedOnly": true
   headers: {'Cache-Control': 'max-age=172800, no-transform, public'}
@@ -33,7 +33,15 @@
   gulp.src "src/**/*.png"
   .pipe gulp.dest 'dist/'

-gulp.task 'build', ['js','css','html','assets'], ->
+gulp.task 'dependencies', ->
+  gulp.src "node_modules/angular2/bundles/**/*"
+  .pipe gulp.dest 'dist/node_modules/angular2/bundles'
+  gulp.src 'dependencies/ace/src-min-noconflict/**/*'
+  .pipe gulp.dest 'dist/dependencies/ace/src-min-noconflict'
+  gulp.src 'node_modules/systemjs/dist/**/*'
+  .pipe gulp.dest 'dist/node_modules/systemjs/dist'
+
+gulp.task 'build', ['js','css','html','assets', 'dependencies'], ->

 gulp.task 's3', ->
   gulp.src 'dist/**/*'


Explicitly look for output.pdf file, not any pdf

--- services/clsi/app/coffee/CompileController.coffee
+++ services/clsi/app/coffee/CompileController.coffee
@@ -24,7 +24,7 @@
 					else
 						status = "failure"
 						for file in outputFiles
-							if file.type == "pdf"
+							if file.path?.match(/output\.pdf$/)
 								status = "success"

 					timer.done()


Remove duplicate code in coffee script

Remove duplicate code in coffee script

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -9,26 +9,6 @@
 gulp.task 'clean', ->
   shelljs.rm '-rf', './build'
   shelljs.rm '-rf', './dist'
-
-# Build for each platform; on OSX/Linux, you need Wine installed to build win32 (or remove winIco below)
-['win32', 'osx64', 'linux32', 'linux64'].forEach (platform) ->
-  gulp.task 'build:' + platform, ->
-    if process.argv.indexOf('--toolbar') > 0
-      shelljs.sed '-i', '"toolbar": false', '"toolbar": true', './src/package.json'
-
-    gulp.src './src/**'
-      .pipe $.nwBuilder
-        platforms: [platform]
-        version: '0.12.2'
-        winIco: if process.argv.indexOf('--noicon') > 0 then undefined else './assets-windows/icon.ico'
-        macIcns: './assets-osx/icon.icns'
-        macZip: true
-        macPlist:
-          NSHumanReadableCopyright: 'aluxian.com'
-          CFBundleIdentifier: 'com.aluxian.messengerfordesktop'
-      .on 'end', ->
-        if process.argv.indexOf('--toolbar') > 0
-          shelljs.sed '-i', '"toolbar": true', '"toolbar": false', './src/package.json'

 # Build for each platform; on OSX/Linux, you need Wine installed to build win32 (or remove winIco below)
 ['win32', 'osx64', 'linux32', 'linux64'].forEach (platform) ->


Save changes to a collection's toc when a node's title changes

--- src/scripts/helpers/backbone/views/editable.coffee
+++ src/scripts/helpers/backbone/views/editable.coffee
@@ -58,6 +58,10 @@
             if /^currentPage\./.test(value)
               model.set('currentPage.changed', true)
               model.set('childChanged', true)
+
+              # Changing a module's title also change's a book's ToC
+              if /^currentPage.title$/.test(value)
+                model.set('changed', true)
             else
               model.set('changed', true)


Refactoring script

Refactoring script

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -1,12 +1,12 @@
-gulp = require('gulp')
-concat = require('gulp-concat')
-coffee = require('gulp-coffee')
-ngAnnotate = require('gulp-ng-annotate')
-uglify = require('gulp-uglify')
-less = require('gulp-less')
-nodemon = require('gulp-nodemon')
-del = require('del')
-bower = require('bower')
+gulp        = require 'gulp'
+concat      = require 'gulp-concat'
+coffee      = require 'gulp-coffee'
+ngAnnotate  = require 'gulp-ng-annotate'
+uglify      = require 'gulp-uglify'
+less        = require 'gulp-less'
+nodemon     = require 'gulp-nodemon'
+del         = require 'del'
+bower       = require 'bower'

 sources =
   bower:  'bower.json'
@@ -31,36 +31,36 @@
         'bower_components/ngstorage/ngStorage.min.js'
         'bower_components/ng-file-upload/angular-file-upload.min.js'
       ])
-      .pipe(concat('lib.js'))
-      .pipe(gulp.dest(targets.gulp))
-      .pipe(gulp.dest(targets.appengineDevServer))
+      .pipe concat 'lib.js'
+      .pipe gulp.dest targets.gulp
+      .pipe gulp.dest targets.appengineDevServer
     gulp.src([
         'bower_components/bootstrap/dist/**/*'
         'bower_components/angular-loading-bar/build/loading-bar.min.css'
       ])
-      .pipe(gulp.dest(targets.gulp))
-      .pipe(gulp.dest(targets.appengineDevServer))
+      .pipe gulp.dest targets.gulp
+      .pipe gulp.dest targets.appengineDevServer

 gulp.task 'coffee', ->
-  gulp.src(sources.coffee)
-    .pipe(coffee())
-    .pipe(ngAnnotate())
-    .pipe(uglify())
-    .pipe(concat('app.js'))
-    .pipe(gulp.dest(targets.gulp))
-    .pipe(gulp.dest(targets.appengineDevServer))
+  gulp.src sources.coffee
+    .pipe coffee()
+    .pipe ngAnnotate()
+    .pipe uglify()
+    .pipe concat 'app.js'
+    .pipe gulp.dest targets.gulp
+    .pipe gulp.dest targets.appengineDevServer

 gulp.task 'less', ->
-  gulp.src(sources.less)
-    .pipe(less())
-    .pipe(concat('app.css'))
-    .pipe(gulp.dest(targets.gulp))
-    .pipe(gulp.dest(targets.appengineDevServer))
+  gulp.src sources.less
+    .pipe less()
+    .pipe concat 'app.css'
+    .pipe gulp.dest targets.gulp
+    .pipe gulp.dest targets.appengineDevServer

 gulp.task 'static', ->
-  gulp.src(sources.static)
-    .pipe(gulp.dest(targets.gulp))
-    .pipe(gulp.dest(targets.appengineDevServer))
+  gulp.src sources.static
+    .pipe gulp.dest targets.gulp
+    .pipe gulp.dest targets.appengineDevServer

 gulp.task 'clean', (cb) -> del targets.gulp, cb

@@ -74,9 +74,9 @@
   gulp.watch sources.static, ['static']

 gulp.task 'coffee-server', ->
-  gulp.src(sources.mockserverCoffee)
-    .pipe(coffee())
-    .pipe(gulp.dest(targets.mockserver))
+  gulp.src sources.mockserverCoffee
+    .pipe coffee()
+    .pipe gulp.dest targets.mockserver

 gulp.task 'watch-coffee-server', ['coffee-server'], ->
   gulp.watch sources.mockserverCoffee, ['coffee-server']