Teamwork: Implemented preview auto refresh feature.

--- client/landing/app/Applications/Teamwork.kdapplication/Views/teamworkworkspace.coffee
+++ client/landing/app/Applications/Teamwork.kdapplication/Views/teamworkworkspace.coffee
@@ -29,12 +29,28 @@
       if @amIHost()
         # currently only for host.
         # bc of clients need to know host's vmName and active pane's file data etc.
-        {viewerHeader} = @getActivePanel().paneLauncher.previewPane.previewer
+        activePanel    = @getActivePanel()
+        {previewPane}  = activePanel.paneLauncher
+        {viewerHeader} = previewPane.previewer
         viewerHeader.addSubView new KDButtonView
-          cssClass : "clean-gray workspace-previewer-button"
+          cssClass : "clean-gray tw-previewer-button"
           title    : "View active file"
           callback : =>
             @previewFile()
+
+        viewerHeader.addSubView @autoRefreshSwitch = new KDOnOffSwitch
+          defaultValue : off
+          cssClass     : "tw-live-update"
+          title        : "Auto Refresh: "
+          size         : "tiny"
+          tooltip      :
+            title      : "If it's on, preview will be refreshed when you save a file."
+            placement  : "bottom"
+          callback     : (state) =>
+            @autoRefresh previewPane  if state
+
+        activePanel.getPaneByName("editor").on "EditorDidSave", =>
+          @autoRefresh previewPane  if @autoRefreshSwitch.getValue()

   createLoader: ->
     @container.addSubView @loader = new KDCustomHTMLView
@@ -80,6 +96,9 @@
       teamwork                   = new teamworkClass teamworkOptions
       @getDelegate().teamwork    = teamwork
       @addSubView teamwork
+
+  autoRefresh: (previewPane) ->
+    previewPane.previewer.emit "ViewerRefreshed"

   createRunButton: (panel) ->
     # panel.headerButtons.Environments.hide()


update api

update api

--- lib/watcher.coffee
+++ lib/watcher.coffee
@@ -120,7 +120,8 @@
       marker = @editor.markBufferRange range
       d 'marker', range, marker
       @editor.decorateMarker marker, type: 'highlight', class: 'refactor-error'
-      @editor.decorateMarker marker, type: 'gutter', class: 'refactor-error'
+      @editor.decorateMarker marker, type: 'line-number', class: 'refactor-error'
+      # TODO: show error message
       marker

   destroyReferences: ->


Adds responsive plugin by default to tables with responsive class

--- coffee/responsiveTables.coffee
+++ coffee/responsiveTables.coffee
@@ -85,4 +85,7 @@
         plugin[_].apply plugin, args

   Furatto.ResponsiveTables.version = "1.0.0"
+
+  $(document).ready ->
+    $('table.responsive').responsiveTables()
 ) $, window, document


Bug fix

Bug fix

--- lib/relations/one.coffee
+++ lib/relations/one.coffee
@@ -97,7 +97,7 @@

   has: (model, key, item) ->
     current_related_model = model.attributes[@key]
-    return !item if not current_related_model
+    return item is current_related_model if not current_related_model

     # compare ids
     current_id = current_related_model.get('id')


Fix poor regexp performance with long upper case constant name

--- lib/robe-provider.coffee
+++ lib/robe-provider.coffee
@@ -16,7 +16,7 @@
     wholePrefix = editor.getTextInBufferRange([startPosition, bufferPosition])
     # resolve [] unless match?
     {moduleName, isInstanceMethod} = RobeEditorUtil.currentContext(editor, bufferPosition)
-    constantPrefix = /(?:^|\s+)((?:[A-Z][A-Za-z0-9_]*|::)+)$/.exec(wholePrefix)?[1]
+    constantPrefix = /(?:^|\s+)([A-Z][A-Za-z0-9_]*(?:::[A-Z][A-Za-z0-9_]*)*)$/.exec(wholePrefix)?[1]
     if constantPrefix
       @clientFactory.retrieve().then (client) ->
         client?.completeConst(constantPrefix, moduleName).then (constants) ->


:clock7::flower_playing_cards: Updated in browser at strd6.github.io/editor

--- pixie.cson
+++ pixie.cson
@@ -1,4 +1,4 @@
-version: "0.4.0-pre.3"
+version: "0.4.0"
 entryPoint: "main"
 remoteDependencies: [
   "https://code.jquery.com/jquery-1.11.0.min.js"


refactored the undefined if to more cofee style

--- lib/assets/javascript/init.js.coffee
+++ lib/assets/javascript/init.js.coffee
@@ -31,7 +31,7 @@
 jQuery ->
   window.Initjs.initialize()

-  unless typeof Turbolinks == 'undefined'
+  unless window.Turbolinks is undefined
     document.addEventListener "page:change", ->
       window.Initjs.initialize()


Add tests for CRLF (#26)

Add tests for CRLF (#26)

--- spec/markdown-table-formatter-spec.coffee
+++ spec/markdown-table-formatter-spec.coffee
@@ -57,16 +57,21 @@
   runEditorTests = (grammar, fixture, scope, opts = {}) ->
     opts.addGrammar ?= true
     opts.selectBeforeTest ?= false
+    opts.crlf ?= '\n'

-    describe "editor tests for #{grammar}", ->
+    describe "editor tests for #{grammar} with #{JSON.stringify(opts.crlf)}", ->
       editor = null

-      test = testFormat editorFormat = (input) ->
+      editorFormat = (input) ->
         editor.setText input
         if opts.selectBeforeTest
           editor.setSelectedBufferRange(editor.getBuffer().getRange())
         atom.commands.dispatch atom.views.getView(editor), 'markdown-table-formatter:format'
         editor.getText()
+
+      modCrLf = (x) -> x.replace(/\n/g, opts.crlf)
+
+      test = testFormat editorFormat, modCrLf

       beforeEach ->
         grammarPromise = atom.packages.activatePackage(grammar)
@@ -78,6 +83,7 @@
             atom.workspace.open("#{__dirname}#{sep}fixtures#{sep}#{fixture}")
           .then (ed) ->
             editor = ed
+            editor.getBuffer().setPreferredLineEnding(opts.crlf)
           .then ->
             if opts.addGrammar
               atom.commands.dispatch atom.views.getView(editor), 'markdown-table-formatter:enable-for-current-scope'
@@ -96,12 +102,12 @@

       describe 'Tables at the end of document', ->
         modTest = testFormat editorFormat, (text, rand) ->
-          nonTables[Math.floor(rand * nonTables.length)] + '\n' + text
+          modCrLf nonTables[Math.floor(rand * nonTables.length)] + opts.crlf + text
         testSuite modTest

       describe 'Tables at the beginning of document', ->
         modTest = testFormat editorFormat, (text, rand) ->
-          text + '\n' + nonTables[Math.floor(rand * nonTables.length)]
+          modCrLf text + opts.crlf + nonTables[Math.floor(rand * nonTables.length)]
         testSuite modTest

       it 'should properly format large text', ->
@@ -109,12 +115,16 @@
         expected = ''
         for table in testTables
           text = nonTables[Math.floor(Math.random() * nonTables.length)]
-          edtext += '\n' + text + '\n' + table.test
-          expected += '\n' + text + '\n' + table.expected
+          edtext += opts.crlf + text + opts.crlf + table.test
+          expected += opts.crlf + text + opts.crlf + table.expected
         test edtext, expected

   runEditorTests 'language-gfm', 'empty.md', 'source.gfm'
   runEditorTests 'language-text', 'empty.text', 'text.plain.null-grammar'
+  runEditorTests 'language-gfm', 'empty.md', 'source.gfm',
+    crlf: '\r\n'
+  runEditorTests 'language-text', 'empty.text', 'text.plain.null-grammar',
+    crlf: '\r\n'
   runEditorTests 'language-text', 'empty.text', 'text.plain.null-grammar',
     addGrammar: false
     selectBeforeTest: true


handle leading space

handle leading space

--- lib/style-heading.coffee
+++ lib/style-heading.coffee
@@ -49,8 +49,8 @@
     @getStylePattern().test(text)

   addStyle: (text) ->
-    text = /// ^ #{@style.before[0]}* \s? (.*) $ ///.exec(text)[1]
-    "#{@style.before}#{text}#{@style.after}"
+    match = /// ^ (\s*) #{@style.before[0]}* \s? (.*) $ ///.exec(text)
+    "#{match[1]}#{@style.before}#{match[2]}#{@style.after}"

   removeStyle: (text) ->
     matches = @getStylePattern().exec(text)
@@ -59,4 +59,4 @@
   getStylePattern: ->
     before = utils.regexpEscape(@style.before)
     after = utils.regexpEscape(@style.after)
-    /// ^#{before} (.*?) #{after}$ ///
+    /// ^ (\s*) #{before} (.*?) #{after}$ ///


only scroll if more than 1

only scroll if more than 1

--- app/assets/javascripts/app/controllers/messages/article.js.coffee
+++ app/assets/javascripts/app/controllers/messages/article.js.coffee
@@ -127,5 +127,6 @@

   scrollToBottom: ->
     @delay ->
-      last = @items.find('.item:last')
+      return if @items.children('.item').length is 1
+      last = @items.children('.item:last')
       @items.scrollTop(last.offset().top - @items.offset().top - 25)


refactor scroll logic

refactor scroll logic

--- app/assets/javascripts/darkswarm/directives/body_scroll.js.coffee
+++ app/assets/javascripts/darkswarm/directives/body_scroll.js.coffee
@@ -9,9 +9,9 @@
         document.body.style.overflowY = 'scroll'
         document.body.style.width = '100%'
       else
-        scrollY = parseInt(document.body.style.top)
+        scrollY = parseInt(document.body.style.top) * -1
         document.body.style.position = ''
         document.body.style.top = ''
         document.body.style.overflowY = ''
         document.body.style.width = ''
-        window.scrollTo(0, scrollY * -1) if scrollY
+        window.scrollTo(0, scrollY) if scrollY


test #13

test #13

--- lib/touch-gesture.coffee
+++ lib/touch-gesture.coffee
@@ -19,7 +19,9 @@
     @threeActive=on if e.touches.length==3

   touchMove: (e) ->
-    console.log "jo" if @threeActive
+    if @threeActive
+      console.log "jo"
+      console.log e.diff.left
   touchEnd: (e) ->
     @threeActive=off

@@ -27,8 +29,18 @@
   addEvent: (name,callBack) ->
     @element.addEventListener name, (e) => @fortifyEvent e,callBack

-  start: null
+  startTouches: null
   fortifyEvent: (event,callFunction) ->
     console.log event
-    # if event.
+    if event.type=='touchstart'
+      @startTouches = event.touches
+    else if event.type =='touchend'
+      @startTouches = null
+    currentTouch = event.touches[0]
+    event.left = currentTouch.clientX
+    event.top = currentTouch.clientY
+
+    event.diff = {} =
+      left: @startTouches[0].clientX-event.left
+      top: @startTouches[0].clientY-event.right
     callFunction event


Fix settings toggles

Fix settings toggles

--- client/app/Applications/Account.kdapplication/account/accSettingsPersEmailNotifications.coffee
+++ client/app/Applications/Account.kdapplication/account/accSettingsPersEmailNotifications.coffee
@@ -34,21 +34,20 @@
         labels        : labels
         defaultValue  : field.current
         callback      : (state)->
-          flag  = @getData()
           prefs = {}

-          prefs[flag] = state
-          fields[flag].loader.show()
+          prefs[@getData()] = state
+          fields[@getData()].loader.show()

           account.setEmailPreferences prefs, (err)=>
-            fields[flag].loader.hide()
+            fields[@getData()].loader.hide()
             if err
               @fallBackToOldState()
               new KDNotificationView
                 duration : 2000
                 title    : 'Failed to change state'
             else
-              if flag is 'global'
+              if @getData() is 'global'
                 @emit 'StateChanged', state
         , flag


Request access to previously pushed release via `balena deploy`

This access is used to cross mount the old layers, rather than
reuploading the layers each time.

Connects-to: #1045
Change-type: minor
Signed-off-by: Matthew McGinn <125a166ae9e1092ea5e1e8c8df08f62c507f7f6b@gmail.com>

--- lib/utils/compose.coffee
+++ lib/utils/compose.coffee
@@ -349,13 +349,54 @@
 			logs: d.logs
 			props: d.props

-authorizePush = (tokenAuthEndpoint, registry, images) ->
+
+getPreviousRepos = (sdk, docker, logger, appID) ->
+	sdk.pine.get(
+		resource: 'release'
+		options:
+			$filter:
+				belongs_to__application: appID
+				status: 'success'
+			$select:
+				[ 'id' ]
+			$expand:
+				contains__image:
+					$expand: 'image'
+			$orderby: 'id desc'
+			$top: 1
+	)
+	.then (release) ->
+		# grab all images from the latest release, return all image locations in the registry
+		if release?.length > 0
+			images = release[0].contains__image
+			Promise.map images, (d) ->
+				imageName = d.image[0].is_stored_at__image_location
+				docker.getRegistryAndName(imageName)
+				.then ( registry ) ->
+					logger.logDebug("Requesting access to previously pushed image repo (#{registry.imageName})")
+					return registry.imageName
+	.catch (e) ->
+		logger.logDebug("Failed to access previously pushed image repo: #{e}")
+
+authorizePush = (sdk, logger, tokenAuthEndpoint, registry, images, previousRepos) ->
 	_ = require('lodash')
-	sdk = require('balena-sdk').fromSharedOptions()
+
+	# TODO: https://github.com/balena-io/balena-cli/issues/1070
+	maxRepos = 20

 	if not _.isArray(images)
 		images = [ images ]

+	if images.length > maxRepos
+		throw new Error (
+			"More than #{maxRepos} containers is currently not supported, see " +
+			'https://github.com/balena-io/balena-cli/issues/1070 for more information'
+		)
+	images.push previousRepos...
+	if images.length + previousRepos?.length > maxRepos
+		logger.logDebug("Truncating requested repositories to #{maxRepos} by limiting previously pushed repo access")
+		# at this point, we know we're only truncating access to previously pushed repos
+		images = images[0...maxRepos]
 	sdk.request.send
 		baseUrl: tokenAuthEndpoint
 		url: '/auth/v1/token'
@@ -423,7 +464,10 @@
 		tagServiceImages(docker, images, serviceImages)
 		.tap (images) ->
 			logger.logDebug('Authorizing push...')
-			authorizePush(apiEndpoint, images[0].registry, _.map(images, 'repo'))
+			sdk = require('balena-sdk').fromSharedOptions()
+			getPreviousRepos(sdk, docker, logger, appId)
+			.then (previousRepos) ->
+				authorizePush(sdk, logger, apiEndpoint, images[0].registry, _.map(images, 'repo'), previousRepos)
 			.then (token) ->
 				logger.logInfo('Pushing images to registry...')
 				pushAndUpdateServiceImages docker, token, images, (serviceImage) ->


fix button issue

fix button issue

--- lib/package/package-info-view.coffee
+++ lib/package/package-info-view.coffee
@@ -45,6 +45,7 @@

     # observe identifier change
     @name.getEditor().onDidChange =>
+      return unless @name.getText()? # reject null only
       # check for available
       fs.exists path.resolve(atom.project.path, @name.getText()), (exists)=>
         if exists


Do not proceed screenshots when not initialized

--- lib/testing/screenshot/server.coffee
+++ lib/testing/screenshot/server.coffee
@@ -129,6 +129,9 @@
     opts.diff = getPathWithBasename opts.path, "#{path.name}_diff"
     opts.rect = rects[opts.clientUid]

+    unless opts.rect
+        throw new Error "Screenshots are not initialized"
+
     takeScreenshot opts
     if testScreenshot(opts)
         fs.unlinkSync opts.diff


GitBridge.add expects a repo, not a path.

--- lib/view/merge-conflicts-view.coffee
+++ lib/view/merge-conflicts-view.coffee
@@ -144,7 +144,7 @@
     for e in atom.workspace.getTextEditors()
       e.save() if e.getPath() is filePath

-    GitBridge.add repoPath, (err) =>
+    GitBridge.add @state.repo, repoPath, (err) =>
       return if handleErr(err)

       @pkg.didStageFile file: filePath


Show and hide the footer bar as well.

Show and hide the footer bar as well.

--- lib/view/merge-conflicts-view.coffee
+++ lib/view/merge-conflicts-view.coffee
@@ -20,18 +20,19 @@
         @text 'Conflicts'
         @span class: 'pull-right icon icon-fold', click: 'minimize', 'Hide'
         @span class: 'pull-right icon icon-unfold', click: 'restore', 'Show'
-      @div class: 'conflict-list', outlet: 'body', =>
-        @ul class: 'block list-group', outlet: 'pathList', =>
-          for {path: p, message} in state.conflicts
-            @li click: 'navigate', "data-path": p, class: 'list-item navigate', =>
-              @span class: 'inline-block icon icon-diff-modified status-modified path', p
-              @div class: 'pull-right', =>
-                @button click: 'stageFile', class: 'btn btn-xs btn-success inline-block-tight stage-ready', style: 'display: none', 'Stage'
-                @span class: 'inline-block text-subtle', message
-                @progress class: 'inline-block', max: 100, value: 0
-                @span class: 'inline-block icon icon-dash staged'
-      @div class: 'footer block pull-right', =>
-        @button class: 'btn btn-sm', click: 'quit', 'Quit'
+      @div outlet: 'body', =>
+        @div class: 'conflict-list', =>
+          @ul class: 'block list-group', outlet: 'pathList', =>
+            for {path: p, message} in state.conflicts
+              @li click: 'navigate', "data-path": p, class: 'list-item navigate', =>
+                @span class: 'inline-block icon icon-diff-modified status-modified path', p
+                @div class: 'pull-right', =>
+                  @button click: 'stageFile', class: 'btn btn-xs btn-success inline-block-tight stage-ready', style: 'display: none', 'Stage'
+                  @span class: 'inline-block text-subtle', message
+                  @progress class: 'inline-block', max: 100, value: 0
+                  @span class: 'inline-block icon icon-dash staged'
+        @div class: 'footer block pull-right', =>
+          @button class: 'btn btn-sm', click: 'quit', 'Quit'

   initialize: (@state, @pkg) ->
     @subs = new CompositeDisposable


always use verbose output

always use verbose output

--- app/templates/gulpfile.coffee
+++ app/templates/gulpfile.coffee
@@ -275,6 +275,7 @@
     ssh: true
     recursive: true
     compareMode: "checksum"
+    args: ["--verbose"]
   }, rsyncOpts

   rsync rsyncOpts, (error, stdout, stderr, cmd) ->


[client][mainApp][avatararea] removed avatar image from groups list

--- client/app/MainApp/avatararea/avatarareagroupswitcherpopup.coffee
+++ client/app/MainApp/avatararea/avatarareagroupswitcherpopup.coffee
@@ -123,14 +123,6 @@

     roleClasses = roles.map((role)-> "role-#{role}").join ' '
     @setClass "role #{roleClasses}"
-
-    @avatar = new KDCustomHTMLView
-      tagName    : 'img'
-      cssClass   : 'avatar-image'
-      attributes :
-        src      : avatar or "http://lorempixel.com/20/20?#{@utils.getRandomNumber()}"
-        width    : 20
-        height   : 20

     @switchLink = new CustomLinkView
       title       : title
@@ -148,7 +140,6 @@
   pistachio: ->
     {roles} = @getData()
     """
-    {{> @avatar}}

       {{> @switchLink}}
       #{roles.join ', '}


Get rid of grunt config for nonexistent test

--- config/grunt/protractor.coffee
+++ config/grunt/protractor.coffee
@@ -36,14 +36,7 @@
         configFile: 'config/protractor.coverage.js'
         args:
           baseUrl: baseUrl
-          exclude: ['test/wd/e2e.coffee']
-          specs: ['test/wd/*.coffee']
-    test:
-      options:
-        args:
-          baseUrl: baseUrl
-          exclude: ['test/wd/e2e.coffee']
-          specs: ['test/wd/*.coffee']
+          specs: ['test/wd/e2e.coffee']
     e2e:
       options:
         args:


Fix WebTerm to work correctly with multiple VMs

--- client/app/Applications/WebTerm.kdapplication/AppController.coffee
+++ client/app/Applications/WebTerm.kdapplication/AppController.coffee
@@ -8,14 +8,15 @@
     behavior     : "application"
     preCondition :
       condition  : (options, cb)->
-        KD.singletons.vmController.info (err, vm, info)=>
+        {params} = options
+        vmName   = params?.vmName or "koding~#{KD.nick()}~0"
+        KD.singletons.vmController.info vmName, (err, vm, info)=>
           cb  if info?.state is 'RUNNING' then yes else no
       failure    : (options, cb)->
         KD.singletons.vmController.askToTurnOn 'Terminal', cb

   constructor:(options = {}, data)->
-    vmName          = options.params?.vmName or \
-                      KD.singletons.vmController.getDefaultVmName()
+    vmName          = options.params?.vmName or "koding~#{KD.nick()}~0"
     options.view    = new WebTermAppView {vmName}
     options.appInfo =
       title         : "Terminal on #{vmName}"


Cleanup messed code

Cleanup messed code

--- client/landing/app/CommonViews/comments/commentlistitemview.coffee
+++ client/landing/app/CommonViews/comments/commentlistitemview.coffee
@@ -12,36 +12,22 @@
     originType  = data.getAt('originType')
     deleterId   = data.getAt('deletedBy')?.getId?()

-    origin = {
+    origin =
       constructorName  : originType
       id               : originId
-    }
+
     @avatar = new AvatarView {
-      size    : {width: 30, height: 30}
-      origin
-    }
-    @author = new ProfileLinkView {
+      size     :
+        width  : 30
+        height : 30
       origin
     }

+    @author = new ProfileLinkView { origin }
+
     if deleterId? and deleterId isnt originId
       @deleter = new ProfileLinkView {}, data.getAt('deletedBy')
-    # if data.originId is KD.whoami().getId()
-    #   @settingsButton = new KDButtonViewWithMenu
-    #     style       : 'transparent activity-settings-context'
-    #     cssClass    : 'activity-settings-menu'
-    #     title       : ''
-    #     icon        : yes
-    #     delegate    : @
-    #     iconClass   : "cog"
-    #     menu        : [
-    #       type      : "contextmenu"
-    #       items     : [
-    #         { title : 'Delete', id : 2,  parentId : null, callback : => data.delete (err)=> @propagateEvent KDEventType: 'CommentIsDeleted' }
-    #       ]
-    #     ]
-    #     callback    : (event)=> @settingsButton.contextMenu event
-    # else
+
     @deleteLink = new KDCustomHTMLView
       tagName     : 'a'
       attributes  :


app: Add annotations layer.

app: Add annotations layer.

--- Oghma/app.coffee
+++ Oghma/app.coffee
@@ -63,7 +63,7 @@
   keymap: null

   # Order of layers
-  layer_order: [ 'avatars', 'dice' ]
+  layer_order: [ 'annotations', 'avatars', 'dice' ]

   # Layers
   layer: {}


Remove controller#title, adjust style slightly.

--- src/chaplin/controllers/controller.coffee
+++ src/chaplin/controllers/controller.coffee
@@ -19,15 +19,13 @@
   # was called in the current action
   redirected: false

-  # You should set a `title` property on the derived controller. Like this:
-  # title: 'foo'
-
   constructor: ->
     @initialize arguments...

   initialize: ->
     # Empty per default

+  # Change document title.
   adjustTitle: (subtitle) ->
     @publishEvent '!adjustTitle', subtitle

@@ -57,11 +55,9 @@
     return if @disposed

     # Dispose and delete all members which are disposable
-    for own prop of this
-      obj = this[prop]
-      if obj and typeof obj.dispose is 'function'
-        obj.dispose()
-        delete this[prop]
+    for own prop, obj of this when obj and typeof obj.dispose is 'function'
+      obj.dispose()
+      delete this[prop]

     # Unbind handlers of global events
     @unsubscribeAllEvents()


Add test for Backbone.WebSQL.promiseType

--- test/src/backbone.websql.deferred-test.coffee
+++ test/src/backbone.websql.deferred-test.coffee
@@ -344,3 +344,35 @@
         expect(Backbone.WebSQL.sync.called).to.be.false
         expect(Backbone.WebSQL.ajaxSync.calledOnce).to.be.true
         done()
+
+  describe 'Backbone.WebSQL.promiseType', ->
+    context 'Backbone.WebSQL.promiseType is set to use Q', ->
+      beforeEach (done) ->
+        Backbone.WebSQL.promiseType = 'q'
+
+        @user = new User
+          name: 'foobar'
+
+        @user.save().done do (done) =>
+          done()
+
+      afterEach ->
+        @user.destroy()
+
+      it 'should create a user and can fetch', (done) ->
+        fetchedUser = new User id: @user.id
+        fetchedUser.fetch().done =>
+          expect(fetchedUser.get 'id').to.eq @user.get 'id'
+          expect(fetchedUser.get 'name').to.eq @user.get 'name'
+          done()
+
+    context 'Backbone.WebSQL.promiseType is set unknown type', ->
+      beforeEach ->
+        Backbone.WebSQL.promiseType = 'foo'
+
+        @user = new User
+          name: 'foobar'
+
+      it 'should throw an error', ->
+        fn = => @user.save().done
+        expect(fn).to.throw('Unsupported Backbone.WebSQL.promiseType: foo')


Disable atom auto-close with last tab

Disable atom auto-close with last tab

--- .config/atom/config.cson
+++ .config/atom/config.cson
@@ -1,6 +1,7 @@
 "*":
   core:
     audioBeep: false
+    closeEmptyWindows: false
     disabledPackages: [
       "language-clojure"
       "language-go"


Change js around

Change js around

--- common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee
+++ common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee
@@ -1,9 +1,11 @@
 class @Rubric
   constructor: () ->

-  @initialize: (location) ->
-    $('.rubric').data("location", location)
-    $('input[class="score-selection"]').change @tracking_callback
+  @initialize: (location,el) ->
+    @el = el
+    @$el = $(el)
+    @$('.rubric').data("location", location)
+    @$('input[class="score-selection"]').change @tracking_callback
     # set up the hotkeys
     $(window).unbind('keydown', @keypress_callback)
     $(window).keydown @keypress_callback
@@ -12,8 +14,11 @@
     @category = $(@categories.first())
     @category.prepend('> ')
     @category_index = 0
-
-
+
+  # locally scoped jquery.
+  $: (selector) ->
+    $(selector, @el)
+
   @keypress_callback: (event) =>
     # don't try to do this when user is typing in a text input
     if $(event.target).is('input, textarea')
@@ -86,34 +91,48 @@
     return true

 class @CombinedOpenEnded
-  constructor: (element) ->
-    @element=element
-    @reinitialize(element)
+  constructor: (el) ->
+    @el=el
+    @$el = $(el)
+    @reinitialize(el)
     $(window).keydown @keydown_handler
     $(window).keyup @keyup_handler

+  # locally scoped jquery.
+  $: (selector) ->
+    $(selector, @el)
+
   reinitialize: (element) ->
-    @wrapper=$(element).find('section.xmodule_CombinedOpenEndedModule')
-    @el = $(element).find('section.combined-open-ended')
-    @combined_open_ended=$(element).find('section.combined-open-ended')
-    @id = @el.data('id')
-    @ajax_url = @el.data('ajax-url')
-    @state = @el.data('state')
-    @task_count = @el.data('task-count')
-    @task_number = @el.data('task-number')
-    @accept_file_upload = @el.data('accept-file-upload')
-    @location = @el.data('location')
+    @wrapper=@$('section.xmodule_CombinedOpenEndedModule')
+    @coe = @$('section.combined-open-ended')
+
+    #Get data from combinedopenended
+    @allow_reset = @coe.data('allow_reset')
+    @id = @coe.data('id')
+    @ajax_url = @coe.data('ajax-url')
+    @state = @coe.data('state')
+    @task_count = @coe.data('task-count')
+    @task_number = @coe.data('task-number')
+    @accept_file_upload = @coe.data('accept-file-upload')
+    @location = @coe.data('location')
+    @child_state = @coe.data('state')
+    @child_type = @coe.data('child-type')
+
     # set up handlers for click tracking
     Rubric.initialize(@location)
     @is_ctrl = false

-    @allow_reset = @el.data('allow_reset')
+    #Setup reset
     @reset_button = @$('.reset-button')
     @reset_button.click @reset
+
+    #Setup next problem
     @next_problem_button = @$('.next-step-button')
     @next_problem_button.click @next_problem
+
     @status_container = @$('.status-elements')

+    #setup show results
     @show_results_button=@$('.show-results-button')
     @show_results_button.click @show_results

@@ -122,33 +141,32 @@

     # valid states: 'initial', 'assessing', 'post_assessment', 'done'
     Collapsible.setCollapsibles(@el)
-    @submit_evaluation_button = $('.submit-evaluation-button')
+    @submit_evaluation_button = @$('.submit-evaluation-button')
     @submit_evaluation_button.click @message_post

-    @results_container = $('.result-container')
-    @combined_rubric_container = $('.combined-rubric-container')
-
-    @legend_container= $('.legend-container')
+    @results_container = @$('.result-container')
+    @combined_rubric_container = @$('.combined-rubric-container')
+
+    @legend_container= @$('.legend-container')
     @show_legend_current()

     # Where to put the rubric once we load it
-    @el = $(element).find('section.open-ended-child')
-    @errors_area = @$('.error')
-    @answer_area = @$('textarea.answer')
-    @prompt_container = @$('.prompt')
-    @rubric_wrapper = @$('.rubric-wrapper')
-    @hint_wrapper = @$('.hint-wrapper')
-    @message_wrapper = @$('.message-wrapper')
-    @submit_button = @$('.submit-button')
-    @child_state = @el.data('state')
-    @child_type = @el.data('child-type')
+
+    @oe = @$('section.open-ended-child')
+    @errors_area = @$(@oe).find('.error')
+    @answer_area = @$(@oe).find('textarea.answer')
+    @prompt_container = @$(@oe).find('.prompt')
+    @rubric_wrapper = @$(@oe).find('.rubric-wrapper')
+    @hint_wrapper = @$(@oe).find('.hint-wrapper')
+    @message_wrapper = @$(@oe).find('.message-wrapper')
+    @submit_button = @$(@oe).find('.submit-button')
     if @child_type=="openended"
-      @skip_button = @$('.skip-button')
+      @skip_button = @$(@oe).find('.skip-button')
       @skip_button.click @skip_post_assessment

-    @file_upload_area = @$('.file-upload')
+    @file_upload_area = $(@oe).find('.file-upload')
     @can_upload_files = false
-    @open_ended_child= @$('.open-ended-child')
+    @open_ended_child= $(@oe).find('.open-ended-child')

     @out_of_sync_message = 'The problem state got out of sync.  Try reloading the page.'

@@ -165,10 +183,6 @@
     if @task_number>1
       @show_combined_rubric_current()
       @show_results_current()
-
-  # locally scoped jquery.
-  $: (selector) ->
-    $(selector, @el)

   show_results_current: () =>
     data = {'task_number' : @task_number-1}
@@ -223,10 +237,10 @@
     evaluation_scoring = $(event.target).parent()

     fd = new FormData()
-    feedback = evaluation_scoring.find('textarea.feedback-on-feedback')[0].value
-    submission_id = external_grader_message.find('input.submission_id')[0].value
-    grader_id = external_grader_message.find('input.grader_id')[0].value
-    score = evaluation_scoring.find("input:radio[name='evaluation-score']:checked").val()
+    feedback = @$(evaluation_scoring).find('textarea.feedback-on-feedback')[0].value
+    submission_id = @$(external_grader_message).find('input.submission_id')[0].value
+    grader_id = @$(external_grader_message).find('input.grader_id')[0].value
+    score = @$(evaluation_scoring).find("input:radio[name='evaluation-score']:checked").val()

     fd.append('feedback', feedback)
     fd.append('submission_id', submission_id)
@@ -474,11 +488,11 @@
       @errors_area.html(@out_of_sync_message)

   gentle_alert: (msg) =>
-    if @el.find('.open-ended-alert').length
-      @el.find('.open-ended-alert').remove()
+    if @$el.find('.open-ended-alert').length
+      @$el.find('.open-ended-alert').remove()
     alert_elem = "
" + msg + "
"
-    @el.find('.open-ended-action').after(alert_elem)
-    @el.find('.open-ended-alert').css(opacity: 0).animate(opacity: 1, 700)
+    @$el.find('.open-ended-action').after(alert_elem)
+    @$el.find('.open-ended-alert').css(opacity: 0).animate(opacity: 1, 700)

   queueing: =>
     if @child_state=="assessing" and @child_type=="openended"


lod_and_effect: Start with random chromosome highlighted

--- lod_and_effect/lod_and_effect.coffee
+++ lod_and_effect/lod_and_effect.coffee
@@ -129,14 +129,18 @@
           .x((d) -> botLxScale[j](d))
           .y((d,i) -> yScale(data.lod[j].lod[i]))

+  randomChr = data.chr[Math.floor(Math.random()*data.chr.length)]
+
   botsvg.append("g").append("path")
-       .attr("d", botlodcurve("2")(data.lod["2"].pos))
+       .attr("d", botlodcurve(randomChr)(data.lod[randomChr].pos))
        .attr("class", "thickline")
        .attr("id", "detailedLod")
        .attr("stroke", "blue")

+
   # select of chromosome for lower LOD detailed curve
-  lastChr = 0
+  lastChr = randomChr
+  topsvg.select("#rect#{randomChr}").attr("fill", "#E9CFEC")
   chrRect.on("click", (d) ->
              d3.select(this).attr("fill", "#E9CFEC")
              topsvg.select("#rect#{lastChr}").attr("fill", chrColor[lastChr]) if lastChr != 0


fix/refactor

fix/refactor

--- bin/cli.coffee
+++ bin/cli.coffee
@@ -1,61 +1,46 @@
-path      = require 'path'
-min       = require 'minimist'
-gentry    = require 'gentry'
-runner    = require 'gentry-runner-cli'
+min = require 'minimist'
+gentry = require 'gentry'
+runner = require 'gentry-runner-cli'

-#questions = require '../app/questions'
-#actions   = require '../app/actions'
-#templateDir = path.resolve './app/templates/'
-
-# usage message
 usage = ->
-  console.log "USAGE:"
-  console.log "# question user and just run actions"
-  console.log "$ gen <generator>"
-  console.log "# question user, run actions & auto-scaffold project w/ boba"
-  console.log "$ gen <generator> scaffold"
+  console.log """
+  USAGE:
+  # question user and just run actions
+  $ gen <generator>
+  # question user, run actions & auto-scaffold project with boba
+  $ gen <generator> scaffold
+  """

 argv = min process.argv.slice(2)
+generatorName = argv._[0]

 # no args = print usage
 if argv._.length is 0 then return usage()

-else
+try
+  generator = require "gentry-#{generatorName}"
+  questions = generator.questions

-  try
+  runner questions, (answers) ->
+    # just actions
+    if argv._[1] is '-actions'
+      gentry.runActions questions, answers, ->
+        process.exit()

-    generator = require "gentry-#{argv._[0]}"
-    questions = generator.questions
+    # full scaffold
+    dest = process.cwd() + "/#{answers.package.name}"

-    runner questions, (answers) ->
+    console.log "auto scaffold -"
+    console.log dest
+    gentry.autoScaffold
+      answers: answers
+      templateDir: generator.templateDir
+      dest: dest
+    , ->
+      process.exit()

-      # scaffold
-      if !argv._[1]?
-
-        dest = process.cwd() + "/#{answers.package.name}"
-
-        console.log "auto scaffold -"
-        console.log dest
-        gentry.autoScaffold
-          answers: answers
-          templateDir: generator.templateDir
-          dest: dest
-        , ->
-          process.exit()
-
-
-      # just actions
-      else if argv._[1] is '-actions'
-
-        gentry.runActions questions, answers, ->
-          process.exit()
-
-      # no args = usage
-      else
-        usage()
-
-  catch e
-    console.log e
-    console.log "Generator '#{argv._[0]}' not installed, try:"
-    console.log "$ npm install -g gentry-#{argv._[0]}"
-    process.exit()
+catch e
+  console.log e
+  console.log "Generator '#{generatorName}' not installed, try:"
+  console.log "$ npm install -g gentry-#{generatorName}"
+  process.exit()


Dispatch mouse events when simulating selection changes

--- test/src/test_helpers.coffee
+++ test/src/test_helpers.coffee
@@ -69,10 +69,13 @@
   event

 @moveCursor = (direction, callback) ->
-  selection = window.getSelection()
-  selection.modify("move", direction, "character")
+  document.activeElement.dispatchEvent(createEvent("mousedown"))
+  window.getSelection().modify("move", direction, "character")
+  document.activeElement.dispatchEvent(createEvent("mouseup"))
   defer(callback)

 @selectAll = (callback) ->
+  document.activeElement.dispatchEvent(createEvent("mousedown"))
   window.getSelection().selectAllChildren(document.activeElement)
+  document.activeElement.dispatchEvent(createEvent("mouseup"))
   defer(callback)


Set moment locale

Set moment locale

--- packages/rocketchat-channel-settings-mail-messages/server/methods/mailMessages.coffee
+++ packages/rocketchat-channel-settings-mail-messages/server/methods/mailMessages.coffee
@@ -19,7 +19,7 @@
 		name = user.name
 		email = user.emails?[0]?.address

-		moment(data.language)
+		moment.locale(data.language)

 		html = ""
 		RocketChat.models.Messages.findByRoomIdAndMessageIds(data.rid, data.messages, { sort: { ts: 1 } }).forEach (message) ->


Tidy up file

Tidy up file

--- app/assets/javascripts/species/views/search_form/taxon_concept_auto_complete_suggestion_view.js.coffee
+++ app/assets/javascripts/species/views/search_form/taxon_concept_auto_complete_suggestion_view.js.coffee
@@ -12,7 +12,10 @@
   ).property('taxonConceptQuery')
   template: ( ->
     if @get('taxonConcept.rankName') == 'SPECIES'
-      Ember.Handlebars.compile('{{#linkTo "taxon_concept.legal" view.taxonConcept}}{{highlight view.taxonConcept.autoCompleteSuggestion controller.taxonConceptQuery}}{{/linkTo}}')
+      Ember.Handlebars.compile(
+        '{{#linkTo "taxon_concept.legal" view.taxonConcept}}
+          {{highlight view.taxonConcept.autoCompleteSuggestion controller.taxonConceptQuery}}
+        {{/linkTo}}')
     else
       Ember.Handlebars.compile('
         {{#linkTo "search" view.setSearchParams }}


real json

real json

--- scripts/eslogger_watcher.coffee
+++ scripts/eslogger_watcher.coffee
@@ -52,11 +52,17 @@
       eslogger.logMessageES log, res.message.room, res

     robot.enter (res) ->
-      msg = res.message
-      line = "#{msg.user.name} has joined #{msg.room} (#{msg.text})"
-      eslogger.logMessageES line, msg.room, res
+      log = {
+        room: res.message.room
+        nick: ''
+        message: "#{res.message.user.name} has joined #{res.message.room} (#{res.message.text})"
+      }
+      eslogger.logMessageES log, msg.room, res

     robot.leave (res) ->
-      msg = res.message
-      line = "#{msg.user.name} has quit #{msg.room}"
-      eslogger.logMessageES line, msg.room, res
+      log = {
+        room: res.message.room
+        nick: ''
+        message: "#{res.message.user.name} has quit #{res.message.room}"
+      }
+      eslogger.logMessageES log, msg.room, res


prepare logging of join/part

prepare logging of join/part

--- scripts/eslogger_watcher.coffee
+++ scripts/eslogger_watcher.coffee
@@ -31,3 +31,9 @@
       eslogger.logMessageES log, room, robot

     robot.logMessageFromRobot = logMessageFromRobot
+
+    robot.enter (res) ->
+      console.log res
+
+    robot.leave (res) ->
+      console.log res


Teamwork: Saving app info to Firebase when started a new Facebook session.

--- client/landing/app/Applications/Teamwork.kdapplication/Views/facebookteamwork.coffee
+++ client/landing/app/Applications/Teamwork.kdapplication/Views/facebookteamwork.coffee
@@ -120,6 +120,20 @@
   runOnFB: ->
     KD.utils.createExternalLink "http://apps.facebook.com/#{@appNamespace}"

+  setAppInfoToCloud: ->
+    @workspaceRef.child("FacebookAppInfo").set { @appId, @appNamespace, @appCanvasUrl }
+
+  getAppInforFromCloud: (callback = noop) ->
+    @workspaceRef.once "value", (snapshot) =>
+      facebookAppInfo = snapshot.val().FacebookAppInfo
+      return unless facebookAppInfo
+
+      @appId          = facebookAppInfo.appId
+      @appNamespace   = facebookAppInfo.appNamespace
+      @appCanvasUrl   = facebookAppInfo.appCanvasUrl
+
+      callback()
+
   createIndexFile: ->
     markup = ""


Allow to implement multiple models in one coffee file

--- bokehjs/src/coffee/common/base.coffee
+++ bokehjs/src/coffee/common/base.coffee
@@ -341,6 +341,11 @@

     modulename = locations[typename]

+    if _.isArray(modulename)
+      [modulename, submodulename] = modulename
+    else
+      submodulename = null
+
     if not mod_cache[modulename]?
       mod = require(modulename)

@@ -349,7 +354,12 @@
       else
           throw Error("improperly implemented collection: #{modulename}")

-    return mod_cache[modulename].Collection
+    mod = mod_cache[modulename]
+
+    if submodulename?
+      mod = mod[submodulename]
+
+    return mod.Collection

   Collections.register = (name, collection) ->
     collection_overrides[name] = collection
@@ -362,5 +372,5 @@
     "locations": locations
     "index": index
     "Collections": Collections
-    "Config" : Config
+    "Config": Config
   }


POST data as JSON

POST data as JSON

--- build/tasks/publish-build-task.coffee
+++ build/tasks/publish-build-task.coffee
@@ -150,8 +150,7 @@
     uri: 'https://api.github.com/repos/atom/atom/releases'
     method: 'POST'
     headers: defaultHeaders
-    json: true
-    qs:
+    json:
       tag_name: "v#{version}"
       name: version
       draft: true


increase printing timeout

increase printing timeout

--- salvus/client.coffee
+++ salvus/client.coffee
@@ -1972,6 +1972,7 @@
         opts = defaults opts,
             project_id  : required
             path        : required
+            timeout     : 90          # some things can take a long time to print!
             options     : undefined   # optional options that get passed to the specific backend for this file type
             cb          : undefined   # cp(err, relative path in project to printed file)
         @call_local_hub
@@ -1979,6 +1980,7 @@
             message    : message.print_to_pdf
                 path    : opts.path
                 options : opts.options
+            timeout    : opts.timeout
             cb         : (err, resp) =>
                 if err
                     opts.cb?(err)


don't break zip if a photo hasn't binary, just skip it.

--- server/controllers/album.coffee
+++ server/controllers/album.coffee
@@ -106,8 +106,10 @@
                 # TODOS : Remove _attachment for photos
                 if photo.binary?
                     path = "/data/#{photo.id}/binaries/raw"
+                else if photo._attachments
+                    path = "/data/#{photo.id}/attachments/raw"
                 else
-                    path = "/data/#{photo.id}/attachments/raw"
+                    return cb()

                 name = photo.title or "#{photo.id}.jpg"
                 request = downloader.download path, (stream) ->
@@ -124,13 +126,16 @@
                 req.on 'close', ->
                     archive.abort()

+                res.on 'close', ->
+                    archive.abort()
+
                 # Set headers describing the final zip file.
                 disposition = "attachment; filename=\"#{zipName}.zip\""
                 res.setHeader 'Content-Disposition', disposition
                 res.setHeader 'Content-Type', 'application/zip'

                 async.eachSeries photos, addToArchive, (err) ->
-                    if err then res.error 500, 'An error occured', err
+                    if err then log.error "An error occured : #{err}"
                     else
                         archive.finalize (err, bytes) ->
                             if err then next err


Update Chronograph plugin

Update Chronograph plugin

--- app/assets/javascripts/chronograph.coffee
+++ app/assets/javascripts/chronograph.coffee
@@ -9,17 +9,6 @@
     @minute = 0
     @init()

-  ###
-    @second: 0 ~ 360
-  ###
-  setSecond: (second) ->
-    @second = second
-
-  ###
-    @minute: 0 ~ 360
-  ###
-  setMinute: (minute) ->
-    @minute = minute

   init: ->
     @canvas = document.createElement('canvas');
@@ -29,11 +18,13 @@
     @element.appendChild(@canvas);
     @dashboard()

+
   ###
     @deg: 0 ~ 360
   ###
   deg: (deg) =>
     (Math.PI/180) * deg - (Math.PI/180)*90
+

   dashboard: =>
     ctx = @canvas.getContext("2d")
@@ -41,20 +32,22 @@

     # minute
     ctx.beginPath()
-    ctx.arc(@settings.size/2, @settings.size/2, @settings.size/2 - 12, 0, Math.PI*2, true)
+    ctx.arc(@settings.size/2, @settings.size/2, @settings.size/2 - 12, 0, Math.PI*2)
     ctx.lineWidth = 22
     ctx.strokeStyle = "#000"
     ctx.globalAlpha = 0.325
+    ctx.shadowBlur  = 0
     ctx.stroke()
     ctx.closePath()

     (=>
-      degs = @minute / 180 * 360
+      degs = @minute
       ctx.beginPath()
       ctx.arc(@settings.size/2, @settings.size/2, @settings.size/2 - 12, @deg(0), @deg(degs))
       ctx.lineWidth = 22
       ctx.strokeStyle = "#FF00FF"
       ctx.globalAlpha = 0.4
+      ctx.shadowBlur  = 0
       ctx.stroke()
       ctx.closePath();
     )()
@@ -62,20 +55,22 @@

     # second
     ctx.beginPath()
-    ctx.arc(@settings.size/2, @settings.size/2, @settings.size/2 - 38.5, 0, Math.PI*2, true)
+    ctx.arc(@settings.size/2, @settings.size/2, @settings.size/2 - 38.5, 0, Math.PI*2)
     ctx.lineWidth = 22
     ctx.strokeStyle = "#000"
     ctx.globalAlpha = 0.225
+    ctx.shadowBlur  = 0
     ctx.stroke()
     ctx.closePath()

     (=>
-      degs = @second / 180 * 360
+      degs = @second
       ctx.beginPath()
       ctx.arc(@settings.size/2, @settings.size/2, @settings.size/2 - 38.5, @deg(0), @deg(degs))
       ctx.lineWidth = 22
       ctx.strokeStyle = "#0FF"
       ctx.globalAlpha = 0.4
+      ctx.shadowBlur  = 0
       ctx.stroke()
       ctx.closePath();
     )()
@@ -83,29 +78,38 @@

     # millisecond
     ctx.beginPath()
-    ctx.arc(@settings.size/2, @settings.size/2, @settings.size/2 - 55, 0, Math.PI*2, true)
+    ctx.arc(@settings.size/2, @settings.size/2, @settings.size/2 - 55, 0, Math.PI*2)
     ctx.lineWidth = 2
     ctx.strokeStyle = "#000"
     ctx.globalAlpha = 0.225
+    ctx.shadowBlur  = 0
     ctx.stroke()
     ctx.closePath()

     ctx.beginPath()
-    degs = @millisecond / 180 * 360
+    degs = @millisecond
     ctx.arc(@settings.size/2, @settings.size/2, @settings.size/2 - 55, @deg(0), @deg(degs))
     ctx.lineWidth = 2
     ctx.strokeStyle = "#FFF"
     ctx.globalAlpha = 0.155
+
+    ctx.shadowBlur    = 10
+    ctx.shadowOffsetX = 0
+    ctx.shadowOffsetY = 0
+    ctx.shadowColor   = '#ffdc50'
+
     ctx.stroke()
     ctx.closePath();

+
   run: ->
+    i = 0
     timer = setInterval((=>
-      @millisecond = 0 if @millisecond == 180
-      @millisecond++
+      @millisecond = 0 if @millisecond >= 360
+      @millisecond = @millisecond + 1000 / Math.PI * 2 / 360
       @dashboard()
     ), 1)

+
 window.Chronograph = Chronograph

-


hiding buttons beforehand

hiding buttons beforehand

--- app/assets/javascripts/is_building.coffee
+++ app/assets/javascripts/is_building.coffee
@@ -8,6 +8,7 @@
 	verticalOffset: 100 # pixels to offset the polygon from the center

 	constructor: () ->
+		$("#buttons").hide()
 		@map = L.mapbox.map('map', 'nypllabs.singlesheet-fix',
 			zoomControl: false
 			, animate: true
@@ -66,12 +67,12 @@
 		$.get("/fixer/flag",
 			i: @currentPolygon.id
 			f: type
-			# , () ->
-			# 	fixer.showNextPolygon()
-		).done( () ->
-			fixer.showNextPolygon()
-		).fail( () ->
-			fixer.showNextPolygon()
+			, () ->
+				fixer.showNextPolygon()
+		# ).done( () ->
+		# 	fixer.showNextPolygon()
+		# ).fail( () ->
+		# 	fixer.showNextPolygon()
 		)

 	showNextPolygon: () =>


Fixed: the public name emptiness condition was wrong

--- server/controllers/mails.coffee
+++ server/controllers/mails.coffee
@@ -123,7 +123,7 @@

                 # retrieves and slugifies the username if it exists
                 if users?[0]?.value.public_name? and
-                    users?[0]?.value.public_name? isnt ''
+                    users?[0]?.value.public_name isnt ''
                         displayName = users[0].value.public_name
                         displayName = displayName.toLowerCase()
                                                  .replace ' ', '-'


Added attribute of relation which can't be done a lookup.

--- app/assets/javascripts/app/models/_application_model.js.coffee
+++ app/assets/javascripts/app/models/_application_model.js.coffee
@@ -301,7 +301,7 @@
                   item = App[ attribute.relation ]._fillUp(item)
                   data[ withoutId ] = item
                 else
-                  console.log("ERROR, cant find App.#{ attribute.relation }.find(#{ data[attribute.name] }) for '#{ data.constructor.className }' #{ data.displayName() }")
+                  console.log("ERROR, cant find #{ attribute.name } App.#{ attribute.relation }.find(#{ data[attribute.name] }) for '#{ data.constructor.className }' #{ data.displayName() }")
     data

   @search: (params) ->


Remove logs

Remove logs

--- server/publications/room.coffee
+++ server/publications/room.coffee
@@ -28,12 +28,10 @@
 			usernames:
 				$all: [user.username, name]

-
 	# Change to validate access manualy
 	# if not Meteor.call 'canAccessRoom', rid, this.userId
 	# 	return this.ready()

-	console.log query
 	ChatRoom.find query,
 		fields:
 			name: 1


this approach should be faster

this approach should be faster

--- app/assets/javascripts/views/base/model_on_list_view.js.coffee
+++ app/assets/javascripts/views/base/model_on_list_view.js.coffee
@@ -5,7 +5,7 @@
   classNames: ["model_on_list"]

   href: (->
-    "#/#{@get("baseRoute")}/#{@get("model").get("id")}"
+    "#/#{@get("baseRoute")}/#{@get("model.id")}"
   ).property()

   baseRoute: (->


Add httpd to list of log file types.

Add httpd to list of log file types.

--- src/ext/components/log-file-output.coffee
+++ src/ext/components/log-file-output.coffee
@@ -4,7 +4,7 @@
   _contents = signal ''
   _nodes = signal []
   _activeNode = signal null
-  _fileTypes = signal ['trace', 'debug', 'info', 'warn', 'error', 'fatal', 'stdout', 'stderr']
+  _fileTypes = signal ['trace', 'debug', 'info', 'warn', 'error', 'fatal', 'httpd', 'stdout', 'stderr']
   _activeFileType = signal null

   createNode = (node, index) ->


add checkBongoConnectivity helper function

--- servers/testhelper/index.coffee
+++ servers/testhelper/index.coffee
@@ -4,8 +4,23 @@
 request     = require 'request'
 querystring = require 'querystring'

+{ argv  }   = require 'optimist'
+KONFIG      = require('koding-config-manager').load("main.#{argv.c}")
+mongo       = KONFIG.mongoReplSet or "mongodb://#{ KONFIG.mongo }"
 { daisy }   = Bongo
 { expect }  = require 'chai'
+
+
+checkBongoConnectivity = (callback) ->
+
+  bongo = new Bongo
+    root   : __dirname
+    mongo  : mongo
+    models : ''
+
+  bongo.once 'dbClientReady', ->
+    callback()
+

 # returns 20 characters by default
 generateRandomString = (length = 20) -> hat().slice(32 - length)
@@ -127,5 +142,6 @@
   generateRandomEmail
   generateRandomString
   generateRandomUsername
+  checkBongoConnectivity
   generateDefaultRequestParams
 }


converted link by buttons to checkboxes

converted link by buttons to checkboxes

--- app/coffee/main.coffee
+++ app/coffee/main.coffee
@@ -411,22 +411,21 @@
     d.name

   draw_link_by_buttons = (links) ->
-    return if ScholarMapViz.$similarity_types.data('links-for') == @type
+    return if ScholarMapViz.$similarity_types.data('links-for') == data_type
+    ScholarMapViz.$similarity_types.data 'links-for', data_type

     ScholarMapViz.$similarity_types.css 'display', 'none'
-
-    similarity_types = []
-    for link in links
-      for similarity in link.similarities
-        similarity_types.push(similarity.type) unless similarity_types.indexOf(similarity.type) > -1
-
     ScholarMapViz.$similarity_types.html ''

-    for type in similarity_types
+    for type in similarity_types()
       formatted_type = type[0].toUpperCase() + type[1..-1]
-      ScholarMapViz.$similarity_types.data 'links-for', type
       ScholarMapViz.$similarity_types.append """
-        <button class="btn btn-default btn-block active" data-similarity-type="#{type}">#{formatted_type}</button>
+        <div class="checkbox">
+          <label>
+            <input type="checkbox" data-similarity-type="#{type}" value="#{type}" checked>
+            #{formatted_type}
+          </label>
+        </div>
       """

     ScholarMapViz.$similarity_types.fadeIn 500
@@ -475,8 +474,8 @@
     _.compact _.flatten(links)

   similarity_exclusions = ->
-    $.makeArray( ScholarMapViz.$similarity_types.find('button:not(.active)') ).map (type) ->
-      $(type).data 'similarity-type'
+    $.makeArray( ScholarMapViz.$similarity_types.find('input[type="checkbox"]:not(:checked)') ).map (type) ->
+      type.value

   active_similarity_types = ->
     similarity_types().filter (type) ->
@@ -537,9 +536,11 @@
 class ScholarMapViz.LinkTypeToggles

   constructor: ->
-    ScholarMapViz.$similarity_types.on 'click', 'button', ->
-      unless ScholarMapViz.$similarity_types.find('button.active').length == 1 && $(@).hasClass('active')
-        $(@).toggleClass 'active'
+    ScholarMapViz.$similarity_types.on 'click', 'input[type="checkbox"]', ->
+      $this = $(@)
+      if ScholarMapViz.$similarity_types.find('input:checked').length == 1 && $this.prop('checked')
+        $this.prop 'checked', !$this.prop('checked')
+      else
         ScholarMapViz.current_map.link_weight_cache = {}
         ScholarMapViz.current_map.draw()


fix so that you could see main bg over navbar when you clicked about us link

--- app/assets/javascripts/static_page.coffee
+++ app/assets/javascripts/static_page.coffee
@@ -21,7 +21,7 @@
     $target = $($(this).attr('href'))

     $('html, body').stop().animate {
-      'scrollTop': $target.offset().top - $('#navigation').height()
+      'scrollTop': $target.offset().top - $('#navigation').height() + 1
     }, 500, 'swing'

 $(window).on 'load', ->


Update comment.

Update comment.

--- app/client/js/react/pages/notfound.coffee
+++ app/client/js/react/pages/notfound.coffee
@@ -5,11 +5,10 @@
   ###*
     @param {app.Routes} routes
     @param {este.react.Element} element
-    @param {este.react.Link} link
     @constructor
   ###
-  constructor: (routes, element, link) ->
-    {div, h1, p} = element
+  constructor: (routes, element) ->
+    {div, h1, p, Link} = element

     @component = React.createFactory React.createClass

@@ -17,7 +16,7 @@
         div className: 'page',
           h1 {}, NotFound.MSG_H1
           p {}, NotFound.MSG_P
-          link.to route: routes.home, NotFound.MSG_LINK
+          Link route: routes.home, NotFound.MSG_LINK

   @MSG_H1: goog.getMsg "This page isn't available"
   @MSG_P: goog.getMsg 'The link may be broken, or the page may have been removed.'


Fix weird problem interpolating option element content

--- app/components/workflow-tasks-editor.cjsx
+++ app/components/workflow-tasks-editor.cjsx
@@ -22,6 +22,7 @@
     delete data[path[0]]
   else
     data[path[0]] = value
+
   @emit 'change'

 AnswerEditor = React.createClass
@@ -45,10 +46,10 @@
           <label>
             {nextValue = @props.answer.next ? JSON.stringify(@props.answer.next) ? 'undefined'; null}
             Next task <select name="tasks.#{@props.taskKey}.answers.#{@props.answerIndex}.next" value={nextValue} onChange={handleInputChange.bind @props.workflow}>
-              <option value="undefined" data-delete-value>(Default)</option>
+              <option value="undefined" data-delete-value>(Next in line)</option>
               {for key, task of @props.workflow.tasks
-                <option key={key} value={key}>{task.question ? task.instruction} ({key})</option>}
-              <option value="null" data-json-value>End classification</option>
+                <option key={key} value={key}>{"#{task.question ? task.instruction} (#{key})"}</option>}
+              <option value="null" data-json-value>(End classification)</option>
             </select>
           </label>
         </span>
@@ -203,7 +204,7 @@
     willTransitionFrom: (transition, component) ->
       if component.props.workflow.hasUnsavedChanges()
         transition.abort()
-        if confirm 'You have unsaved changes that **will be lost**. Do you really want to leave the workflow editor?'
+        if confirm 'You have unsaved changes that will be lost forever. Do you really want to leave the workflow editor?'
           transition.retry()

   getDefaultProps: ->
@@ -216,7 +217,6 @@
           type: 'question'
           question: 'Cool?'
           answers: [
-            value: true
             label: 'Yep'
           ]
       first_task: 'cool'


added method __process

added method __process

--- meta-engine.coffee
+++ meta-engine.coffee
@@ -360,6 +360,32 @@

     return content

+  __process: (resourcePath, isolated = false, trim = true, cbfn)->
+
+    @contentProvider.getContent resourcePath, (err, content)=>
+
+      return cbfn err if err
+
+      @__processIncludeTab resourcePath, content, (err, content)=>
+
+        return cbfn err if err
+
+        if isolated
+
+          try
+
+            [ regionMap, content ] = @__processRegionTag resourcePath, content
+
+            content = @__processUseTag resourcePath, content, regionMap
+
+            content = @__trim content if trim
+
+          catch ex
+
+            return cbfn ex
+
+        return cbfn null, content
+

   processSync: (resourcePath)->


Fixed the eval builtin

Fixed the eval builtin

--- builtin/eval.coffee
+++ builtin/eval.coffee
@@ -13,21 +13,14 @@
 class EvalExp extends nodeio.JobClass
     init: ->
         if @options.args.length is 0
-            @exit 'Please enter an expression, e.g. `node.io eval "input.length"`'
+            @exit 'Please enter an expression, e.g. `node.io eval "input.length"`'
         if @options.args[0] is 'help'
-            @exit 'node.io eval [EXPRESSION]'
-
+            @status usage
+            @exit
+
     run: (input) ->
         result = eval @options.args[0]
         if result? then @emit result else @skip

-class UsageDetails extends nodeio.JobClass
-    input: ->
-        @status usage
-        @exit()
-
 @class = EvalExp
-@job = {
-    eval: new EvalExp()
-    help: new UsageDetails()
-}+@job = new EvalExp()


Fix CSV export course ordering

Fix CSV export course ordering

--- app/views/courses/TeacherClassView.coffee
+++ app/views/courses/TeacherClassView.coffee
@@ -383,7 +383,9 @@
       for course in @sortedCourses
         courseCountsMap[course._id] ?= {levels: 0, playtime: 0}
       courseCounts = []
-      for courseID, data of courseCountsMap
+      for course in @sortedCourses
+        courseID = course._id
+        data = courseCountsMap[courseID]
         courseCounts.push
           id: courseID
           levels: data.levels


fixed flow page: now other media or organisations are also shown if only one (media or organisation) is selected

--- packages/custom/transparency/coffee/server/controllers/transparencyServerCtrl.coffee
+++ packages/custom/transparency/coffee/server/controllers/transparencyServerCtrl.coffee
@@ -324,7 +324,7 @@
     filteredflows: (req, res) ->
         getOtherMedia = (organisations, media, period, paymentTypes, federalState) ->
             result = []
-            if (organisations and organisations.length > 1) and (media and media.length > 1)
+            if (organisations and organisations.length > 0) and (media and media.length > 0)
                 qry = {}
                 (qry.transferType = $in: paymentTypes.map (e)->
                     parseInt(e)) if paymentTypes.length > 0
@@ -358,7 +358,7 @@
                     resolve result
         getOtherOrganisations = (organisations, media, period, paymentTypes, federalState) ->
             result = []
-            if (media and media.length > 1) and (organisations and organisations.length > 1)
+            if (media and media.length > 0) and (organisations and organisations.length > 0)
                 qry = {}
                 (qry.transferType = $in: paymentTypes.map (e)->
                     parseInt(e)) if paymentTypes.length > 0


:white_check_mark: test vB in hardTab editor

--- spec/visual-blockwise-spec.coffee
+++ spec/visual-blockwise-spec.coffee
@@ -508,6 +508,108 @@
           ensure "k", selectedTextOrdered: ["", "567890"], cursor: [[1, 15], [2, 15]]
           ensure "k", selectedTextOrdered: ["0123", "", "0123"], cursor: [[0, 24], [1, 15], [2, 24]]

+  describe "In hardTab text", ->
+    beforeEach ->
+      jasmine.attachToDOM(atom.workspace.getElement())
+
+      waitsForPromise ->
+        atom.packages.activatePackage('language-go')
+
+      runs ->
+        set
+          grammar: 'source.go'
+          textC: """
+          package main
+
+          import "fmt"
+
+          func| main() {
+          \tif 7%2 == 0 {
+          \t\tfmt.Println("7 is even")
+          \t} else {
+          \t\tfmt.Println("7 is odd")
+          \t}
+          }\n
+          """
+
+    it "[tabLength = 2] select blockwise", ->
+      editor.update(tabLength: 2)
+
+      blockwiseEnsureOptions =
+        mode: ['visual', 'blockwise']
+        selectedTextOrdered: [
+          " main"
+          " 7%2 "
+          "fmt.P"
+          "else "
+          "fmt.P"
+        ]
+
+      ensure "v 4 j 4 l"
+      ensure "ctrl-v", blockwiseEnsureOptions
+      ensure "v ctrl-v", blockwiseEnsureOptions
+
+      ensure 'o'; ensureBlockwiseSelection head: 'top', tail: 'bottom', headReversed: true
+      ensure "v ctrl-v", blockwiseEnsureOptions
+      ensure 'O'; ensureBlockwiseSelection head: 'top', tail: 'bottom', headReversed: false
+      ensure "v ctrl-v", blockwiseEnsureOptions
+      ensure 'o'; ensureBlockwiseSelection head: 'bottom', tail: 'top', headReversed: true
+      ensure "v ctrl-v", blockwiseEnsureOptions
+      ensure 'O'; ensureBlockwiseSelection head: 'bottom', tail: 'top', headReversed: false
+      ensure "v ctrl-v", blockwiseEnsureOptions
+
+    it "[tabLength = 4] move up/down bufferRow wise with aware of tabLength", ->
+      editor.update(tabLength: 4)
+
+      blockwiseEnsureOptions =
+        mode: ['visual', 'blockwise']
+        selectedTextOrdered: [
+          " main"
+          "if 7%"
+          "\tf"
+          "} els"
+          "\tf"
+        ]
+
+      ensure "v 4 j l"
+      ensure "ctrl-v", blockwiseEnsureOptions
+      ensure "v ctrl-v", blockwiseEnsureOptions
+
+      ensure 'o'; ensureBlockwiseSelection head: 'top', tail: 'bottom', headReversed: true
+      ensure "v ctrl-v", blockwiseEnsureOptions
+      ensure 'O'; ensureBlockwiseSelection head: 'top', tail: 'bottom', headReversed: false
+      ensure "v ctrl-v", blockwiseEnsureOptions
+      ensure 'o'; ensureBlockwiseSelection head: 'bottom', tail: 'top', headReversed: true
+      ensure "v ctrl-v", blockwiseEnsureOptions
+      ensure 'O'; ensureBlockwiseSelection head: 'bottom', tail: 'top', headReversed: false
+      ensure "v ctrl-v", blockwiseEnsureOptions
+
+    it "[tabLength = 8] move up/down bufferRow wise with aware of tabLength", ->
+      editor.update(tabLength: 8)
+      set cursor: [5, 1]
+
+      blockwiseEnsureOptions =
+        mode: ['visual', 'blockwise']
+        selectedTextOrdered: [
+          "if 7%2 == 0 {"
+          "\tfmt.P"
+          "} else {"
+          "\tfmt.P"
+        ]
+
+      ensure "v 3 j 5 l"
+      ensure "ctrl-v", blockwiseEnsureOptions
+      ensure "v ctrl-v", blockwiseEnsureOptions
+
+      ensure 'o'; ensureBlockwiseSelection head: 'top', tail: 'bottom', headReversed: true
+      ensure "v ctrl-v", blockwiseEnsureOptions
+      ensure 'O'; ensureBlockwiseSelection head: 'top', tail: 'bottom', headReversed: false
+      ensure "v ctrl-v", blockwiseEnsureOptions
+      ensure 'o'; ensureBlockwiseSelection head: 'bottom', tail: 'top', headReversed: true
+      ensure "v ctrl-v", blockwiseEnsureOptions
+      ensure 'O'; ensureBlockwiseSelection head: 'bottom', tail: 'top', headReversed: false
+      ensure "v ctrl-v", blockwiseEnsureOptions
+
   # [FIXME] not appropriate put here, re-consider all spec file layout later.
   describe "gv feature", ->
     preserveSelection = ->


fix: clear all the object stores

fix: clear all the object stores

In the previous commit, the screenshots and userActions objects stores
where not emptied when clearing the database.

--- src.coffee/storageExecutor.coffee
+++ src.coffee/storageExecutor.coffee
@@ -15,6 +15,8 @@
     @clearStore "parameters"
     @clearStore "labels"
     @clearStore "userStudySessions"
+    @clearStore "screenshots"
+    @clearStore "userActions"
     sendResponse {message:{title:"message from networkManager", content:"Database cleared", level:"success"}}

   #Clears all the data from a specific store and send an ack to the popup


Add route for waterpoint creation form

Add route for waterpoint creation form

--- app/scripts/app.coffee
+++ app/scripts/app.coffee
@@ -15,6 +15,9 @@
       .when '/waterpoints/edit/:id',
         templateUrl: 'views/edit.html'
         controller: 'WaterpointEditCtrl'
+      .when '/waterpoints/new',
+        templateUrl: 'views/edit.html'
+        controller: 'WaterpointCreateCtrl'
       .otherwise
         redirectTo: '/'


allow in global scope; clean up

allow in global scope; clean up

--- minimongoid.coffee
+++ minimongoid.coffee
@@ -1,4 +1,4 @@
-class Minimongoid
+class @Minimongoid
   id: undefined
   attributes: {}

@@ -15,15 +15,15 @@

   save: ->
     return false unless @isValid()
-
+
     attributes = @mongoize(@attributes)
     attributes['_type'] = @constructor._type if @constructor._type?
-
+
     if @isPersisted()
       @constructor._collection.update @id, { $set: attributes }
     else
       @id = @constructor._collection.insert attributes
-
+
     this

   update: (@attributes) ->
@@ -51,9 +51,6 @@
   @_collection: undefined
   @_type: undefined

-  # Rubists will love me, everyone else will burn me!
-  #
-  # Allows calls like User.new firstname: 'John'
   @new: (attributes) ->
     new @(attributes)

@@ -68,7 +65,6 @@

   @toArray: (selector = {}, options = {}) ->
     for attributes in @where(selector, options).fetch()
-      # eval is ok, because _type is never entered by user
       new(eval(attributes._type) ? @)(attributes)

   @count: (selector = {}, options = {}) ->


Fix context menu

Fix context menu

--- vendor/assets/javascripts/context.menu.coffee
+++ vendor/assets/javascripts/context.menu.coffee
@@ -14,9 +14,7 @@
         data: '_'
         type: 'POST'
         beforeSend: (jqXHR, settings) ->
-          rsp = $(element).trigger('before:context:ajax', [jqXHR, settings, from_mouse])
-          console.log(settings)
-          rsp
+          $(element).trigger('before:context:ajax', [jqXHR, settings, from_mouse])
         success: (data) ->
           $(data)
             .openContextMenu(x, y, from_mouse)
@@ -25,11 +23,6 @@
         .find('[data-context-target]')
         .clone()
         .openContextMenu(x, y, from_mouse)
-
-        # .first()
-        # .addClass('from-mouse')
-        # .css(top: y, left: x)
-        # .show()

   constructor: ->
     $.fn.openContextMenu = (x, y, from_mouse = true) ->
@@ -51,7 +44,6 @@
       event.stopPropagation();

     $(document).on 'click', (event) =>
-      # return if $(event.target).closest('[data-context-button]').length > 0
       @close_all_context_menus()

     $(window).on 'blur', =>


Resources

Resources

--- views/components/main/parts/resource-panel.cjsx
+++ views/components/main/parts/resource-panel.cjsx
@@ -2,47 +2,31 @@
 {ROOT, layout, _, $, $$, React, ReactBootstrap, toggleModal} = window
 {log, warn, error} = window
 {Panel, Grid, Col} = ReactBootstrap
+classNames = require 'classnames'
+{connect} = require 'react-redux'
 __ = i18n.main.__.bind(i18n.main)
 __n = i18n.main.__n.bind(i18n.main)
-order = [1, 3, 2, 4, 5, 7, 6, 8]
+order = [0, 2, 1, 3, 4, 6, 5, 7]
 {MaterialIcon} = require '../../etc/icon'

-ResourcePanel = React.createClass
-
-  getInitialState: ->
-    material: ['??', '??', '??', '??', '??', '??', '??', '??', '??']
-    limit: 30750   # material limit of level 120
-    show: true
-  shouldComponentUpdate: (nextProps, nextState) ->
-    nextState.show
-  handleVisibleResponse: (e) ->
-    {visible} = e.detail
-    @setState
-      show: visible
-  handleResponse: (e) ->
-    {method, path, body, postBody} = e.detail
-    switch path
-      when '/kcsapi/api_port/port'
-        {limit} = @state
-        level = parseInt(body.api_basic.api_level)
-        if level > 0
-          limit = 750 + level * 250
-        @setState
-          limit: limit
-  componentDidMount: ->
-    window.addEventListener 'game.response', @handleResponse
-    window.addEventListener 'view.main.visible', @handleVisibleResponse
-  componentWillUnmount: ->
-    window.removeEventListener 'game.response', @handleResponse
-    window.removeEventListener 'view.main.visible', @handleVisibleResponse
+ResourcePanel = connect(
+  (state) ->
+    resources: state.info?.resources
+    admiralLv: state.info?.basic?.api_level || 0
+) React.createClass
   render: ->
+    valid = !!@props.admiralLv
+    limit = 750 + @props.admiralLv * 250
     <Panel bsStyle="default">
       <Grid>
       {
         for i in order
+          className = classNames 'material-icon',
+            'glow': valid && i < 4 && @props.resources[i] < limit
+          amount = if valid then @props.resources[i] else '??'
           <Col key={i} xs={6} style={marginBottom: 2, marginTop: 2}>
-            <MaterialIcon materialId={i} className="material-icon #{if i <= 4 and @state.material[i] < @state.limit then 'glow' else ''}" />
-            {@state.material[i]}
+            <MaterialIcon materialId={i+1} className={className} />
+            {amount}
           </Col>
       }
       </Grid>


Correct image version

Correct image version

--- models/icon.coffee
+++ models/icon.coffee
@@ -21,8 +21,8 @@
   #   - render a default icon if there is none instead of "missing_image"
   #   - display an unprocessed original version if the image is waiting on a delayed job
   imageUrl: ->
-    if @hasImage 'square_140'
-      @sslUrl @get('image_url').replace(':version', 'square_140').replace('.jpg', '.png')
+    if @hasImage 'square140'
+      @sslUrl @get('image_url').replace(':version', 'square140').replace('.jpg', '.png')
     else if @hasImage 'square'
       @sslUrl @get('image_url').replace(':version', 'square').replace('.jpg', '.png')
     else if @has('image_filename') and _.isNull(@get('versions'))


user-friendly messages in the discussion widget

--- client/landing/app/Applications/Activity.kdapplication/widgets/widgetcontroller.coffee
+++ client/landing/app/Applications/Activity.kdapplication/widgets/widgetcontroller.coffee
@@ -58,7 +58,7 @@
         delegate  : mainView
         callback  : (data)=>
           msg = new KDNotificationView
-            title : "You kinda submitted"
+            title : "You started a discussion!"
           if submissionStopped
             return notifySubmissionStopped()
           else
@@ -158,7 +158,7 @@
       activity.modify data, (err, res)=>
         callback? err, res
         unless err
-          new KDNotificationView type : "mini", title : "Updated successfully"
+          new KDNotificationView type : "mini", title : "Updated the discussion successfully"
         else
           new KDNotificationView type : "mini", title : err.message
     else


made browser.coffee override Node dependent exported methods

--- browser.coffee
+++ browser.coffee
@@ -1,26 +1,38 @@
+# Override exported methods for non-Node.js engines.
+
+CoffeeScript = require './coffee-script'
+
+CoffeeScript.eval = (code, options) ->
+  eval CoffeeScript.compile code, options
+
+unless window?
+  CoffeeScript.run = (code, options) ->
+    (Function CoffeeScript.compile code, options)()
+  return
+
+CoffeeScript.run  = (code, options) ->
+  setTimeout CoffeeScript.compile code, options
+
+CoffeeScript.load = (url, options) ->
+  xhr = new (window.ActiveXObject or XMLHttpRequest)('Microsoft.XMLHTTP')
+  xhr.open 'GET', url, true
+  xhr.overrideMimeType 'text/plain' if 'overrideMimeType' of xhr
+  xhr.onreadystatechange = ->
+    CoffeeScript.run xhr.responseText, options if xhr.readyState is 4
+  xhr.send null
+
 # Activate CoffeeScript in the browser by having it compile and evaluate
 # all script tags with a content-type of `text/coffeescript`.
 # This happens on page load.
-if window?
-  CoffeeScript = require './coffee-script'
-  grind = (coffee) ->
-    setTimeout CoffeeScript.compile coffee
-  grindRemote = (url) ->
-    xhr = new (window.ActiveXObject or XMLHttpRequest)('Microsoft.XMLHTTP')
-    xhr.open 'GET', url, true
-    xhr.overrideMimeType 'text/plain' if 'overrideMimeType' of xhr
-    xhr.onreadystatechange = ->
-      grind xhr.responseText if xhr.readyState is 4
-    xhr.send null
-  processScripts = ->
-    for script in document.getElementsByTagName 'script'
-      if script.type is 'text/coffeescript'
-        if script.src
-          grindRemote script.src
-        else
-          grind script.innerHTML
-    null
-  if window.addEventListener
-    addEventListener 'DOMContentLoaded', processScripts, false
-  else
-    attachEvent 'onload', processScripts+processScripts = ->
+  for script in document.getElementsByTagName 'script'
+    if script.type is 'text/coffeescript'
+      if script.src
+        CoffeeScript.load script.src
+      else
+        CoffeeScript.run script.innerHTML
+  null
+if window.addEventListener
+  addEventListener 'DOMContentLoaded', processScripts, false
+else
+  attachEvent 'onload', processScripts


make thumbnail creation cancellable; remove cancelled thumbnails

--- scripts/media.coffee
+++ scripts/media.coffee
@@ -21,14 +21,21 @@
   constructor: (container, link, @uri) ->
     @container = $(container)
     @source    = $(link)
+
+    @isCancelled = false

   scrape: ->
     try
       @container.append @createDefaultThumbnail()
       @loadThumbnail()
-      true
     catch ex
-      false
+      @cancel()
+
+    not @isCancelled
+
+  cancel: ->
+    @thumbnail.remove()
+    @isCancelled = true

   loadThumbnail: ->
     @loadThumbnailTitle()


comments in client/

comments in client/

--- client/index.coffee
+++ client/index.coffee
@@ -57,6 +57,9 @@
      responseTimes: "/streams/metrics/histograms/responseTimes"
   events:
     # Rx.Subjects to decouple UI from event sources
+    # Think of these as the ports on a switchboard
+    # http://makemusicals.com/wp-content/uploads/2011/11/switchboard1.jpg
+    # They are a layer of indirection between the source of events and the consumers.
     timer: new Subject()
     timerSwitch: new Subject()
     mouseDrags: new Subject()
@@ -208,7 +211,7 @@
       R.br {},
       'count: ' + @state.count,
       R.br {},
-      'mean: ' + @state.mean, ''
+      'mean: ' + @state.mean, '' # trailing space required for some reason

 MouseDragWidget = React.createClass
   mixins: [ReactRxMixin]


Added globals to the REPL that are (for a reason currently unknown to me) not provided by `vm.Script.createContext`

--- repl.coffee
+++ repl.coffee
@@ -33,6 +33,8 @@

 # The REPL context; must be visible outside `run` to allow for tab completion
 sandbox = Script.createContext()
+nonContextGlobals = ['clearInterval', 'Buffer', 'setTimeout', 'console', 'process', 'clearTimeout', 'setInterval']
+sandbox[g] = global[g] for g in nonContextGlobals
 sandbox.global = sandbox.root = sandbox.GLOBAL = sandbox

 # The main REPL function. **run** is called every time a line of code is entered.


improved fix for #1266

improved fix for #1266

--- repl.coffee
+++ repl.coffee
@@ -14,8 +14,8 @@

 # Config
 enableColours = no
-if process.platform isnt 'win32'
-  enableColours = !process.env.NODE_DISABLE_COLORS
+unless process.platform is 'win32'
+  enableColours = not process.env.NODE_DISABLE_COLORS

 # Start by opening up `stdin` and `stdout`.
 stdin = process.openStdin()
@@ -38,7 +38,8 @@
   backlog = ''
   try
     val = CoffeeScript.eval code, bare: on, globals: on, filename: 'repl'
-    process.stdout.write inspect(val, no, 2, enableColours) + '\n' if val isnt undefined
+    unless val is undefined
+      process.stdout.write inspect(val, no, 2, enableColours) + '\n'
   catch err
     error err
   repl.prompt()


Content Disposition header for uploading from client (#157)

* add ability to set Content Disposition header

* re-spelled disposition, and added default for backward compatibility

* added defaults to client as well, not just policy in server signing

--- client/functions.coffee
+++ client/functions.coffee
@@ -59,6 +59,8 @@
 			uploader:"default"
 			unique_name:true
 			connection:Meteor
+			server_side_encryption:false
+			content_disposition:"inline"

 		if ops.file
 			uploadFile(ops.file, ops, callback)


counter: bugfix botched logic

counter: bugfix botched logic

--- node_modules_koding/koding-counter/lib/koding-counter/index.coffee
+++ node_modules_koding/koding-counter/lib/koding-counter/index.coffee
@@ -9,7 +9,7 @@
     @collectionName = collectionName ? 'counters'
     @offset         = offset ? 0

-  wrap =(callback)-> (err, doc)-> console.log {err, doc}; callback err, doc?.seq
+  wrap =(callback)-> (err, doc)-> callback err, doc?.seq

   update:->
     switch arguments.length
@@ -36,10 +36,10 @@
     return @update { $set: seq: @offset }  if isHard

     collection  = @db.collection @collectionName
-    selector    = { seq: $lte: @offset }
+    selector    = { seq: $gt: @offset }

     collection.count selector, (err, count)=>
       if err then callback err
       else if count is 0
         @update selector, { $set: seq: @offset }, wrap callback
-      else callback null, @offset+      else callback null


JAccount::likeUser method added for admins.

--- workers/social/lib/social/models/account.coffee
+++ workers/social/lib/social/models/account.coffee
@@ -257,6 +257,8 @@
           (signature String, Function)
         updateCountAndCheckBadge:
           (signature Object, Function)
+        likeUser:
+          (signature String, Function)
     schema                  :
       skillTags             : [String]
       locationTags          : [String]
@@ -946,6 +948,34 @@
     else
       callback new KodingError 'Access denied'

+  likeUser: permit 'assign badge',
+    success: (client, userId, callback)->
+      JAccount.one { '_id' : userId }, (err, account)=>
+        return new KodingError 'Account not found' if err or not account
+        Relationship.count
+          as         : 'like'
+          targetId   : account.getId()
+          sourceId   : @getId()
+        , (err, count)=>
+          return new KodingError err if err
+          unless count > 0
+            rel = new Relationship
+              targetId    : account.getId()
+              targetName  : 'JAccount'
+              sourceId    : @getId()
+              sourceName  : 'JAccount'
+              as          : 'like'
+            rel.save (err)=>
+              return new KodingError err if err
+              Relationship.count
+                as         : 'like'
+                targetName : 'JAccount'
+                sourceName : 'JAccount'
+                targetId   : @getId()
+              , (err, count)=>
+                @update ($set: 'counts.staffLikes': count), (err)->
+                  return new KodingError err if err
+
   fetchUserByAccountIdOrNickname:(accountIdOrNickname, callback)->

     kallback= (err, account)->


filling in tests for getUriFromResultDataWithMaxSize

--- test/giphy.spec.coffee
+++ test/giphy.spec.coffee
@@ -535,11 +535,56 @@
         callback.should.not.have.been.called

     describe '.getUriFromResultDataWithMaxSize', ->
-      it 'ignores calls with invalid or empty images'
-      it 'ignores calls with size <= 0'
-      it 'returns the largest allowed image in strict mode'
-      it 'returns the smallest image when all images are too large in loose mode'
-      it 'returns nothing when all images are too large in strict mode'
+      it 'ignores calls with invalid or empty images', ->
+        result = @giphy.getUriFromResultDataWithMaxSize()
+        should.not.exist result
+        result = @giphy.getUriFromResultDataWithMaxSize null
+        should.not.exist result
+        result = @giphy.getUriFromResultDataWithMaxSize { }
+        should.not.exist result
+
+      it 'ignores calls without size or with size <= 0', ->
+        result = @giphy.getUriFromResultDataWithMaxSize { img: null }
+        should.not.exist result
+        result = @giphy.getUriFromResultDataWithMaxSize { img: null }, 0
+        should.not.exist result
+        result = @giphy.getUriFromResultDataWithMaxSize { img: null }, -1
+        should.not.exist result
+
+      it 'returns the largest allowed image in strict mode', ->
+        images = {
+          medium: { size: '500' },
+          small: { size: '100' },
+          large: { size: '1000' },
+        }
+        result = @giphy.getUriFromResultDataWithMaxSize images, 123, false
+        should.exist result
+        result.should.eql images.small
+        result = @giphy.getUriFromResultDataWithMaxSize images, 500, false
+        should.exist result
+        result.should.eql images.medium
+        result = @giphy.getUriFromResultDataWithMaxSize images, 999, false
+        should.exist result
+        result.should.eql images.medium
+
+      it 'returns the smallest image when all images are too large in loose mode', ->
+        images = {
+          medium: { size: '500' },
+          small: { size: '100' },
+          large: { size: '1000' },
+        }
+        result = @giphy.getUriFromResultDataWithMaxSize images, 1, true
+        should.exist result
+        result.should.eql images.small
+
+      it 'returns nothing when all images are too large in strict mode', ->
+        images = {
+          medium: { size: '500' },
+          small: { size: '100' },
+          large: { size: '1000' },
+        }
+        result = @giphy.getUriFromResultDataWithMaxSize images, 1, false
+        should.not.exist result

     describe '.getUriFromResultData', ->
       it 'returns .images.original.url', ->


Fix typo in mycha_spec

Fix typo in mycha_spec

--- test/mycha_spec.coffee
+++ test/mycha_spec.coffee
@@ -105,7 +105,7 @@
             done()

       it 'calls mocha once', ->
-        expect(@mycha_call_stub).to.have.been.calledOnce
+        expect(@mocha_call_stub).to.have.been.calledOnce

       it 'uses the spec reporter', ->
         expect(@mocha_argument).to.contain_consecutive_elements '--reporter', 'spec'
@@ -141,7 +141,7 @@
             done()

       it 'calls mocha once', ->
-        expect(@mycha_call_stub).to.have.been.calledOnce
+        expect(@mocha_call_stub).to.have.been.calledOnce

       it 'uses the foo reporter', ->
         expect(@mocha_argument).to.contain_consecutive_elements '--reporter', 'foo'


Ignore bundle testla from get-parameter-names

This should have been a dev dependency

--- build/tasks/build-task.coffee
+++ build/tasks/build-task.coffee
@@ -63,6 +63,8 @@
       path.join('npm', 'node_modules', '.bin', 'clear')
       path.join('npm', 'node_modules', '.bin', 'starwars')
       path.join('pegjs', 'examples')
+      path.join('get-parameter-names', 'node_modules', 'testla')
+      path.join('get-parameter-names', '.bin', 'testla')
       path.join('jasmine-reporters', 'ext')
       path.join('jasmine-node', 'node_modules', 'gaze')
       path.join('jasmine-node', 'spec')


New tests

New tests

--- test/rules_test.coffee
+++ test/rules_test.coffee
@@ -5,6 +5,10 @@

 tests = [
   'helper: the rules'
+  'helper: the laws'
+  'helper: what are the laws?'
+  'helper: what are the three laws of robotics?'
+  'helper: what are the 3 laws?'
 ]

 until tests.length == 0


corrected bitcoin cash chain selection

corrected bitcoin cash chain selection

--- app/src/controllers/onboarding/device/onboarding_device_chains_view_controller.coffee
+++ app/src/controllers/onboarding/device/onboarding_device_chains_view_controller.coffee
@@ -37,9 +37,9 @@
   bitcoinCashSelected: (e) ->
     dialog = new OnboardingDeviceChainsMessageDialogViewController()
     dialog.once 'click:split', =>
-      @chainChoosen(@networks[3])
+      @chainChoosen(@networks[parseInt(e.target.attributes.value.value,10)+1])
     dialog.once 'click:un_split', =>
-      @chainChoosen(@networks[2])
+      @chainChoosen(@networks[e.target.attributes.value.value])
     dialog.show()

   chooseSegwit: (e) ->


don't show test login at /turkserver

don't show test login at /turkserver

--- client/login.coffee
+++ client/login.coffee
@@ -88,7 +88,7 @@
   # Don't try logging in if we are logged in or already have parameters
   return if Meteor.userId() or Session.get("_loginParams")
   # Don't show this if we are trying to get at the admin interface
-  return if Router.current()?.path?.indexOf("/turkserver") is 0
+  return if Router.current()?.url?.indexOf("/turkserver") is 0

   str = Random.id()
   data =


Add `nodejsHttpXAgent` member per class in addition to per instance

- before: `nodejsHttpXAgent` defined per `XMLHttpRequest` instance, making
  impossible to _stub_ HTTP proxies underneath an existing implementation
  (like `dropbox-je`)
- Now, per class `XMLHttpRequest.nodejsHttpXAgent`.  Per-instance value
  take the per-class by default & default to the `httpX.globalAgent` if
  the per-class value is not defined.
- I am new to coffee-script, so I have no idea on how to systematically
  test this.  It works here.

--- src/001-xml_http_request.coffee
+++ src/001-xml_http_request.coffee
@@ -287,14 +287,14 @@
   # NOTE: this is not in the XMLHttpRequest API, and will not work in
   # browsers.  It is a stable node-xhr2 API that is useful for testing
   # & going through web-proxies.
-  nodejsHttpAgent: http.globalAgent
+  nodejsHttpAgent: XMLHttpRequest.nodejsHttpAgent

   # @property {https.Agent} the agent option passed to HTTPS requests
   #
   # NOTE: this is not in the XMLHttpRequest API, and will not work in
   # browsers.  It is a stable node-xhr2 API that is useful for testing
   # & going through web-proxies.
-  nodejsHttpsAgent: https.globalAgent
+  nodejsHttpsAgent: XMLHttpRequest.nodejsHttpsAgent

   # HTTP methods that are disallowed in the XHR spec.
   #
@@ -387,10 +387,10 @@

     if @_url.protocol is 'http:'
       hxxp = http
-      agent = @nodejsHttpAgent || http.globalAgent
+      agent = @nodejsHttpAgent || XMLHttpRequest.nodejsHttpAgent || http.globalAgent
     else
       hxxp = https
-      agent = @nodejsHttpsAgent || https.globalAgent
+      agent = @nodejsHttpsAgent || XMLHttpRequest.nodejsHttpsAgent || https.globalAgent

     request = hxxp.request
         hostname: @_url.hostname, port: @_url.port, path: @_url.path,


Moved mongoize and demongoize to instance methods.

--- minimongoid.coffee
+++ minimongoid.coffee
@@ -3,8 +3,11 @@
   attributes: {}

   constructor: (attributes = {}) ->
-    @attributes = attributes
-    @id = attributes._id
+    if attributes._id
+      @attributes = @demongoize(attributes)
+      @id = attributes._id
+    else
+      @attributes = attributes

   isPersisted: -> @id?

@@ -12,13 +15,14 @@

   save: ->
     return false unless @isValid()
-
-    attributes = @constructor.mongoize(@attributes)
+
+    attributes = @mongoize(@attributes)
+
     if @isPersisted()
       @constructor._collection.update @id, { $set: attributes }
     else
       @id = @constructor._collection.insert attributes
-
+
     this

   update: (@attributes) ->
@@ -29,15 +33,19 @@
       @constructor._collection.remove @id
       @id = null

-  # Dont allow direct access to _id and _type.
-  @mongoize: (attributes) ->
+  mongoize: (attributes) ->
+    taken = {}
     for name, value of attributes
-      delete attributes[name] if name in ['_id', '_type']
-
-    attributes
+      continue if name.match(/^_/)
+      taken[name] = value
+    taken

-  # Demongoize is just an alias for mongoize.
-  @demongoize: @mongoize
+  demongoize: (attributes) ->
+    taken = {}
+    for name, value of attributes
+      continue if name.match(/^_/)
+      taken[name] = value
+    taken

   @_collection: undefined


Updated purchase email.

Updated purchase email.

--- packages/retronator-store/transactions/transaction/methods-server/emailcustomer.coffee
+++ packages/retronator-store/transactions/transaction/methods-server/emailcustomer.coffee
@@ -44,7 +44,14 @@
                       to join, just reply and let me know the email you use for Facebook
                       and I'll send you an invite. That way you can follow the development
                       as we go along. Hope to see you there."
-
+
+  email.addParagraph "p.p.s. There is a lo-fi prototype of the drawing activities available
+                      in the form of articles in Retronator Magazine. Currently it's a hidden
+                      draft, but it already has a ton of knowledge and 50 tasks to complete.
+                      You can start learning at:"
+
+  email.addLinkParagraph 'https://medium.com/retronator-magazine/pixel-art-academy-study-guide-3ae5f772a83a', "Pixel Art Academy Study Guide"
+
   email.end()

   Email.send


Fix tests description

Fix tests description

--- tests/test_formatters.coffee
+++ tests/test_formatters.coffee
@@ -106,10 +106,10 @@
       f("Test <&\"'>").should.equal("Test <html><body><&"'></body></html>")

     describe "Italic", ->
-      it "should f simple italic", ->
+      it "should parse simple italic", ->
         f("//Nyan nyan//").should.equal("Nyan nyan")

-      it "should f italic on multiple lines", ->
+      it "should parse italic on multiple lines", ->
         input = """
         Test desu!
         //Nyan nyan// nyan
@@ -118,7 +118,7 @@
         output = "Test desu!
Nyan nyan nyan
 <- look at this!
No at this: desuu"
         f(input).should.equal(output)

-      it "should f multiline italic", ->
+      it "should parse multiline italic", ->
         input = """
         //line 1 text
         line2 text
@@ -129,15 +129,15 @@
         f(input).should.equal(output)

     describe "Bold", ->
-      it "should f simple bold", ->
+      it "should parse simple bold", ->
         f("**Nya nya**").should.equal("Nya nya")

-      it "should f bold on multiple lines", ->
+      it "should parse bold on multiple lines", ->
         input = "Test\n**bold bold** test\n"
         output = "Test
bold bold test
"
         f(input).should.equal(output)

-      it "should f multiline bold", ->
+      it "should parse multiline bold", ->
         input = """Test
         **Аниме в Ультра HD на японском ТВ “к 2014 году”
         Аниме в разрешении 3840×2160 “2160p” UHDTV должно быть доступно к лету 2014 года, так как японское правительство настаивает на развертывании вещания в формате 4K UHDTV на два года раньше чем это запланировано в попытке спасти телевизионную индустрию от краха.** [[http://www.animetank.ru/anime-v-ultra-hd-na-yaponskom-tv-k-2014-godu/ |❤❤❤]]


Enable invalid email test

Enable invalid email test

--- servers/lib/server/handlers/validateemail.test.coffee
+++ servers/lib/server/handlers/validateemail.test.coffee
@@ -66,7 +66,7 @@

   # returns 200 with invalid email address, needs to be investigated
-  it.skip 'should send HTTP 400 if email is not valid', (done) ->
+  it 'should send HTTP 400 if email is not valid', (done) ->

     validateEmailRequestParams = generateValidateEmailRequestParams
       body    :


refactor

refactor

--- app/assets/javascripts/ample_assets/classes/ample_assets.js.coffee
+++ app/assets/javascripts/ample_assets/classes/ample_assets.js.coffee
@@ -156,26 +156,24 @@
       @goto(@current)

   get_pages: (tpl = 'page') ->
-    ref = this
     html = ''
-    $.each @options.pages, (idx,el) ->
-      html += Mustache.to_html ref.tpl(tpl), el
+    $.each @options.pages, (idx,el) =>
+      html += Mustache.to_html @tpl(tpl), el
     html

   toggle: ->
-    ref = this
     el = $("##{@options.id}")
     if @options.expanded
       @options.expanded = false
-      el.animate {height: @options.collapsed_height}, "fast", ->
-        ref.collapse()
-        ref.options.onCollapse()
+      el.animate {height: @options.collapsed_height}, "fast", =>
+        @collapse()
+        @options.onCollapse()
     else
       @options.expanded = true
-      el.animate {height: @options.expanded_height}, "fast", ->
-        ref.expand()
-        ref.options.onExpand()
-        ref.goto(0)
+      el.animate {height: @options.expanded_height}, "fast", =>
+        @expand()
+        @options.onExpand()
+        @goto(0)

   load: (i) ->
     ref = this
@@ -223,12 +221,11 @@

   load_results: (response) ->
     @log "load_results()"
-    ref = this
-    $.each response, (j,el) ->
-      link = ref.build(el)
+    $.each response, (j,el) =>
+      link = @build(el)
       li = $('

').append(link)
       $("#asset-results ul").amplePanels('append', li)
-      ref.load_img(link, el.sizes.tn)
+      @load_img(link, el.sizes.tn)

   build: (el) ->
     ref = this
@@ -310,15 +307,15 @@
   events: ->
     @modal_events()
     @field_events()
-    ref = this
-    $("a.asset-remove").live 'click', ->
-      ref.remove(this)
+    $("a.asset-remove").live 'click', =>
+      @remove(this)
       false
-    $("##{@options.id}-handle").live 'click', ->
-      ref.toggle()
+    $("##{@options.id}-handle").live 'click', =>
+      @toggle()
       false
     @key_down()
     tabs = $("##{@options.id} a.tab")
+    ref = this
     $.each tabs, (idx, el) ->
       $(this).addClass('on') if idx == 0
       $(el).click ->


remove step one altogether

remove step one altogether

--- app/assets/javascripts/backbone/routers/campaigns_router.js.coffee
+++ app/assets/javascripts/backbone/routers/campaigns_router.js.coffee
@@ -6,7 +6,7 @@
     "step_one" : "stepOne"
     "step_two" : "stepTwo"
     "step_three": "stepThree"
-    ".*"        : "stepOne"
+    ".*"        : "stepTwo"

   stepOne: ->
     @view = new Chimpenstein.Views.Campaigns.StepOneView()
@@ -15,11 +15,9 @@
   stepTwo: ->
     campaigns = @campaigns = new Chimpenstein.Collections.CampaignsCollection()
     Chimpenstein.config = @config = new Chimpenstein.Models.Config()
-    #TODO: show loading
     Mailchimp.API.call 'campaigns', {filters: {status: "save,paused", type: 'regular'},limit: 100}, (data) ->
       campaigns.reset data.data
       $('#spin').data('spinner').spin()
-      #TODO: hide loading
     @view = new Chimpenstein.Views.Campaigns.StepTwoView(campaigns: @campaigns, config: @config)
     $('#campaigns').html(@view.render().el)


supporting alreadyPersisted orderItems

supporting alreadyPersisted orderItems

--- app/assets/javascripts/views/order_items/order_item_view.js.coffee
+++ app/assets/javascripts/views/order_items/order_item_view.js.coffee
@@ -3,8 +3,11 @@
   classNames: ["order_item_view"]
   tagName: "tr"

+  alreadyPersisted: (->
+    Ember.isEmpty @get("cachedItemNombre")
+  ).property("cachedItemNombre")
+
   removeOrderItem: ->
-    # @get("controller.model").get("orderItems")
     @get("model").deleteRecord()

   didInsertElement: ->


CustomPartials: Reload views after add new form cancelled.

--- client/Dashboard/views/homepages/homepagesdashboardview.coffee
+++ client/Dashboard/views/homepages/homepagesdashboardview.coffee
@@ -126,7 +126,7 @@
         toggleState()

     @on "AddingNewHomePageCancelled", =>
-      page.show() for page in @homePages
+      @reloadViews()
       @addNewButton.show()

   addNew: (data) ->


Workspace: Added p element to join modal content.

--- client/app/CommonViews/workspace/collaborativeworkspace.coffee
+++ client/app/CommonViews/workspace/collaborativeworkspace.coffee
@@ -193,7 +193,7 @@
     options        = @getOptions()
     modal          = new KDModalView
       title        : options.joinModalTitle   or "Join New Session"
-      content      : options.joinModalContent or "This is your session key, you can share this key with your friends to work together."
+      content      : options.joinModalContent or "
This is your session key, you can share this key with your friends to work together.
"
       overlay      : yes
       cssClass     : "workspace-modal join-modal"
       width        : 500


テストコード追加

テストコード追加

--- test/contexts/startup.coffee
+++ test/contexts/startup.coffee
@@ -7,11 +7,27 @@

 describe 'MaruBatsuRule test', () ->
-  describe 'default state', () ->
-    context = new StartupContext(null, {})
-    it 'test right', () ->
-      state_ = context.initState(context.props)
+  describe '#initState', () ->
+    context = new StartupContext
+    it 'test defaults', () ->
+      state_ = context.initState({})
       assert.equal 12, state_.maxPlayers
       assert.equal 12, state_.playerNames.length
       assert.equal '', state_.playerNames[0]
       assert.equal '', state_.playerNames[11]
+
+    it 'test overwrite', () ->
+      state_ = context.initState({maxPlayers: 3})
+      assert.equal 3, state_.maxPlayers
+      assert.equal 3, state_.playerNames.length
+      assert.equal '', state_.playerNames[0]
+      assert.equal '', state_.playerNames[1]
+
+  describe '#expandComponentProps', () ->
+    context = new StartupContext
+
+    it 'test to inherit player names', () ->
+      state_ = context.initState({})
+      state_.playerNames[1] = 'test'
+      props_ = context.expandComponentProps({}, state_)
+      assert.equal 'test', props_.playerNames[1]


rtmp: Show clientid on socket error

rtmp: Show clientid on socket error

--- rtmp.coffee
+++ rtmp.coffee
@@ -1988,7 +1988,7 @@
           sessionsCount--
         @dumpSessions()
       c.on 'error', (err) ->
-        logger.error "[rtmp] socket error: #{err}"
+        logger.error "[rtmp:#{sess.clientid}] socket error: #{err}"
         c.destroy()
       c.on 'data', (data) =>
         c.rtmpSession.handleData data, (err, output) ->


Complain if there is no web app root.

Complain if there is no web app root.

--- test/e2e/helpers/seed.coffee
+++ test/e2e/helpers/seed.coffee
@@ -12,11 +12,13 @@
     throw "Test preparation seed script unsuccessful: #{ error }"

 # Link test data to _public/data.
+# The source image download root directory.
 src = process.env.QIPROFILE_DATA
-if src
-  # The web app root.
-  root = "#{ __dirname }/../../../_public"
+# The web app root.
+root = "#{ __dirname }/../../../_public"
+# The link destination.
+dest = "#{ root }/data"
+if src and not fs.exists(dest)
   if not fs.exists(root)
-    fs.mkdir(root)
-  dest = "#{ root }/data"
+    throw "Web app root not found: #{ root }"
   fs.symlink(src, dest)


Recipe.rimeUserDir, Recipe.rimeSharedDir

--- rime/recipe.coffee
+++ rime/recipe.coffee
@@ -33,21 +33,25 @@

 exports.Recipe = class Recipe

+  @rimeUserDir: '.'
+
+  @rimeSharedDir: '.'
+
   constructor: (@props) ->
     @validate()

   validate: ->
     # name and version are required
-    throw Error('missing recipe name.') unless @props.name
-    throw Error('missing recipe version.') unless @props.version
+    throw Error 'missing recipe name.' unless @props.name
+    throw Error 'missing recipe version.' unless @props.version
     unless /^[_0-9A-Za-z]+$/.test @props.name
-      throw Error('recipe name should be alpha_numeric.')
+      throw Error 'recipe name should be alpha_numeric.'
     unless typeof @props.version is 'string'
-      throw Error('recipe version should be string type.')
-    @rimeUserDir = @props.rimeUserDir ? '.'
+      throw Error 'recipe version should be string type.'
+    @rimeUserDir = @props.rimeUserDir ? Recipe.rimeUserDir
     unless @rimeUserDir and fs.existsSync @rimeUserDir
-      throw Error('Rime directory not accessible.')
-    @rimeSharedDir = @props.rimeSharedDir ? '.'
+      throw Error 'Rime user directory not accessible.'
+    @rimeSharedDir = @props.rimeSharedDir ? Recipe.rimeSharedDir

   collectParams: (ingredients) ->
     @params ?= {}


Delete token from headers

Delete token from headers

--- public/init.coffee
+++ public/init.coffee
@@ -2,7 +2,6 @@

 # TODO remove this before going live
 $.ajaxSettings.headers =
-  "Authorization": "Bearer 1"
   "Accept": "application/vnd.epages.v1+json"

 App.start()