test(navbar): add missing tests

test(navbar): add missing tests

--- test/spec/directives/navbar.coffee
+++ test/spec/directives/navbar.coffee
@@ -1,36 +1,131 @@
 describe 'navbarDirective', ->
+  describe 'ios', ->
+    scope   = null
+    state   = null
+    timeout = null
+    element = null
+    title   = null

-  scope   = null
-  element = null
+    beforeEach module 'bp'

-  beforeEach module 'bp'
+    beforeEach module ($stateProvider, $urlRouterProvider) ->
+      $urlRouterProvider.otherwise '/first'
+      $stateProvider
+        .state 'first',
+          url: '/first'
+          data:
+            up: 'second'
+        .state 'second',
+          url: '/second'
+      null

-  beforeEach inject ($rootScope, $compile) ->
-    scope = $rootScope.$new()
-    template = "<bp-navbar bp-navbar-title='Foo'>
-        <bp-action class='one'>Title</bp-action>
-        <bp-action class='two'>Other</bp-action>
-      </bp-navbar>"
-    element = $compile(template) scope
-    scope.$apply()
+    beforeEach inject ($rootScope, $compile, $state, $timeout) ->
+      scope = $rootScope.$new()
+      state = $state
+      timeout = $timeout
+      state.transitionTo 'first'
+      timeout.flush()
+      element = $compile("
+        <bp-navbar>
+          <bp-action>Action</bp-action>
+        </bp-navbar>") scope
+      title = element.find 'bp-navbar-title'
+      scope.$apply()

-  describe 'element', ->
-    it 'should have ARIA role', ->
-      expect(element.attr 'role' ).toBe 'navigation'

-  describe 'title', ->
-    it 'should have ARIA role', ->
-      $text = element.find 'bp-navbar-title'
-      expect($text.attr 'role' ).toBe 'heading'
+    describe 'controller', ->
+      it 'getTitleFromState', inject ($controller) ->
+        childScope = title.scope()
+        stateTitle = childScope.getTitleFromState
+          data: title: 'Foo'
+        expect(stateTitle).toBe 'Foo'
+        stateTitle = childScope.getTitleFromState
+          name: 'Bar'
+        expect(stateTitle).toBe 'Bar'
+        stateTitle = childScope.getTitleFromState
+          name: 'Buz'
+          data: title: 'Baz'
+        expect(stateTitle).toBe 'Baz'

-    it 'should be applied', ->
-      $text = element.find 'bp-navbar-title'
-      expect($text.text()).toBe 'Foo'
-      expect($text.scope().bpNavbarTitle).toBe 'Foo'
+    describe 'element', ->
+      it 'should have ARIA role', ->
+        expect(element.attr 'role').toBe 'navigation'
+        expect(title.attr 'role').toBe 'heading'

-  describe 'actions', ->
-    it 'should have correct type', ->
-      $actions = element.find('bp-action')
-      expect($actions.length).toBe 2
-      expect($actions.eq(0).is '.bp-button.one' ).toBe true
-      expect($actions.eq(1).is '.bp-button.two' ).toBe true
+      it 'should apply title', ->
+        expect(title.text()).toBe 'First'
+
+      it 'should place up button', ->
+        $up = element.find '.bp-action-up'
+        expect($up.text()).toBe 'Second'
+
+      it 'should apply buttons', ->
+        expect(element.find('bp-action').hasClass 'bp-button').toBe true
+        expect(element.children().length).toBe 4
+
+      it 'should calculate center', inject ($window) ->
+        # We have to put the element into the DOM
+        # otherwhise it won't have any width
+        angular.element('body').append(element)
+        timeout.flush()
+        expect(element.children().length).toBe 5
+        $spacer = element.find('div')
+        expect($spacer.attr 'style').toMatch /width/
+        expect($spacer.attr 'style').toMatch /flex/
+        expect(element.children().get 2).toBe $spacer.get 0
+        element.detach()
+
+  describe 'android', ->
+    scope   = null
+    state   = null
+    timeout = null
+    element = null
+    compile = null
+
+    beforeEach module 'bp', (bpConfigProvider) ->
+      bpConfigProvider.setConfig
+        platform: 'android'
+      null
+
+    beforeEach module ($stateProvider, $urlRouterProvider) ->
+      $urlRouterProvider.otherwise '/first'
+      $stateProvider
+        .state 'first',
+          url: '/first'
+          data:
+            up: 'second'
+        .state 'second',
+          url: '/second'
+      null
+
+    beforeEach inject ($rootScope, $compile, $state, $timeout) ->
+      compile = $compile
+      scope = $rootScope.$new()
+      state = $state
+      timeout = $timeout
+      state.transitionTo 'first'
+      timeout.flush()
+      element = $compile("
+        <bp-navbar>
+          <bp-action>Action</bp-action>
+        </bp-navbar>") scope
+      scope.$apply()
+
+    describe 'element', ->
+      it 'should apply icons', ->
+        $icons = element.find '.bp-icon'
+        expect($icons.length).toBe 2
+        expect($icons.eq(1).attr 'aria-label').toBe 'Action'
+        expect(element.find('.bp-action-up').length).toBe 1
+        expect(element.children().length).toBe 3
+        expect(element.find('bp-navbar-icon').length).toBe 1
+
+      it 'should handle navbar icons', ->
+        state.transitionTo 'second'
+        timeout.flush()
+        element2 = compile("
+          <bp-navbar>
+            <bp-action>Action</bp-action>
+          </bp-navbar>") scope
+        expect(element.children().length).toBe 3
+        expect(element.find('bp-navbar-icon').length).toBe 1


removed trash

removed trash

--- make_barrier.coffee
+++ make_barrier.coffee
@@ -1,14 +0,0 @@
-module.exports = (barrier) ->
-  return unless barrier?
-  temp = []
-  if barrier.lines? then for line in barrier.lines
-    if line.by is 'x'
-      for x in [line.range[0]..line.range[1]]
-        temp.push [x, line.y]
-    if line.by is 'y'
-      for y in [line.range[0]..line.range[1]]
-        temp.push [line.x, y]
-
-  if barrier.points? then for point in barrier.points
-    temp.push point
-  return JSON.stringify temp


Quick removal of random line.

Quick removal of random line.

--- start.coffee
+++ start.coffee
@@ -231,7 +231,6 @@

 			_return = [ ]
-			i = 0
 			for doc in valid_docs
 				doc.x = k.Z.elements[i][0]
 				doc.y = k.Z.elements[i][1]


Wait for source before querying stream_key in preroller

--- src/streammachine/preroller.coffee
+++ src/streammachine/preroller.coffee
@@ -17,9 +17,9 @@

         # -- need to look at the stream to get characteristics -- #

-        console.log "calling get_stream_key"
-        @stream.source?.get_stream_key (key) =>
-            @stream_key = key
+        console.log "waiting to call get_stream_key"
+        @stream.once "source", (source) =>
+            @stream_key = source.get_stream_key()
             @stream.log.debug "Stream key is #{@stream_key}"

     #----------


Float the player to the right

Float the player to the right

--- app/assets/javascripts/rglossa/speech/components/centre/results/jplayer.jsx.coffee
+++ app/assets/javascripts/rglossa/speech/components/centre/results/jplayer.jsx.coffee
@@ -66,7 +66,7 @@

     annotations = (@renderAnnotation(annotation[n], n) for n of annotation)

-    `<div><div className="jplayer-text autocue">{annotations}</div></div>`
+    `<div className="jplayer-text autocue">{annotations}</div>`

 window.Jplayer = React.createClass
@@ -189,6 +189,7 @@

   render: ->
     `

+

@@ -231,5 +232,6 @@

+

       <TextBox mediaObj={this.props.mediaObj} startAtLine={this.state.startLine} endAtLine={this.state.endLine} highlightLine={this.state.currentLine} pauseJPlayer={this.pauseJPlayer} />

`


Fix trying to read property statusCode of undefined in pack:win32

--- tasks/pack.coffee
+++ tasks/pack.coffee
@@ -327,9 +327,9 @@
       releasesUrl = remoteReleasesUrl + '/RELEASES'

       request {url: releasesUrl}, (err, res, body) ->
-        if err || res.statusCode < 200 || res.statusCode >= 400
+        if err || !res || res.statusCode < 200 || res.statusCode >= 400
           console.log 'Creating installer without remote releases url', releasesUrl, 'because of',
-            'error', err, 'statusCode', res.statusCode, 'body', res.body
+            'error', err, 'statusCode', res && res.statusCode, 'body', res && res.body
           remoteReleasesUrl = undefined

         winInstaller


Adds back error callback

Adds back error callback

--- api/apps/shows/model.coffee
+++ api/apps/shows/model.coffee
@@ -28,6 +28,7 @@
             return cb null, null if err
             cb null, { id: res.body._id, value: res.body.name }
       , (err, results) ->
+        return callback err if err
         results = _.compact results
         callback null, results


Remove info logging

Remove info logging

--- app/js/services/info.coffee
+++ app/js/services/info.coffee
@@ -53,7 +53,6 @@
         @interval (=>
             if !@is_refreshing
                 @refresh_info()
-                console.log('refresh', @info)
         ), 2500

     constructor: (@q, @log, @location, @growl, @common_api, @blockchain, @blockchain_api, @interval, @utils) ->


Using old temporary variable name generation for single-letter types (e.g., i, j, k, and so on).

--- scope.coffee
+++ scope.coffee
@@ -68,7 +68,10 @@

   # Generate a temporary variable name at the given index.
   temporary: (type, index) ->
-    '_' + type + if index > 1 then index else ''
+    if type.length > 1
+      '_' + type + if index > 1 then index else ''
+    else
+      '_' + (index + parseInt type, 36).toString(36).replace /\d/g, 'a'

   # If we need to store an intermediate result, find an available name for a
   # compiler-generated variable. `_var`, `_var2`, and so on...


Move fetching media into fetcher

Move fetching media into fetcher

--- app/pages/lab/tutorial.cjsx
+++ app/pages/lab/tutorial.cjsx
@@ -97,16 +97,13 @@
   getDefaultProps: ->
     project: null
     tutorial: null
+    media: {}
     delayBeforeSave: 5000

     onDelete: ->
       console.log 'TutorialEditorController onDelete', arguments

-  getInitialState: ->
-    media: {}
-
   componentDidMount: ->
-    @fetchMediaFor @props.tutorial
     @_boundForceUpdate = @forceUpdate.bind this
     @props.tutorial.listen @_boundForceUpdate

@@ -117,26 +114,18 @@
     unless nextProps.tutorial is @props.tutorial
       @fetchMediaFor nextProps.tutorial

-  fetchMediaFor: (tutorial) ->
-    tutorial.get 'attached_images', {}
-      .then (mediaResources) =>
-        mediaByID = {}
-        for mediaResource in mediaResources
-          mediaByID[mediaResource.id] = mediaResource
-        @setState media: mediaByID
-
   render: ->
     <TutorialEditor
       tutorial={@props.tutorial}
-      media={@state.media}
-      onStepAdd={@handleAddStep}
+      media={@props.media}
+      onStepAdd={@handleStepAdd}
       onStepRemove={@handleStepRemove}
       onMediaSelect={@handleStepMediaChange}
       onMediaClear={@handleStepMediaClear}
       onStepChange={@handleStepChange}
     />

-  handleAddStep: ->
+  handleStepAdd: ->
     @props.tutorial.steps.push
       media: ''
       content: ''
@@ -214,7 +203,7 @@

   render: ->

-
This project doesn’t have a tutorial yet.

+
This project doesn’t have a tutorial.

       {if @state.error?

{@state.error.toString()}
}

@@ -245,6 +234,7 @@
     loading: false
     error: null
     tutorial: null
+    media: {}

   componentDidMount: ->
     @_boundForceUpdate = @forceUpdate.bind this
@@ -265,13 +255,22 @@
       loading: true
       error: null
       tutorial: null
-    apiClient.type('tutorials').get project_id: @props.project?.id
+    apiClient.type('tutorials').get project_id: project.id
       .then ([tutorial]) =>
+        @fetchMediaFor tutorial
         @setState {tutorial}
       .catch (error) =>
         @setState {error}
       .then =>
         @setState loading: false
+
+  fetchMediaFor: (tutorial) ->
+    tutorial.get 'attached_images', {} # Prevent caching.
+      .then (mediaResources) =>
+        media = {}
+        for mediaResource in mediaResources
+          media[mediaResource.id] = mediaResource
+        @setState {media}

   render: ->
     if @state.loading
@@ -280,12 +279,16 @@

{@state.error.toString()}

     else if @state.tutorial?
       window?.editingTutorial = @state.tutorial
-      <TutorialEditorController project={@props.project} tutorial={@state.tutorial} onDelete={@handleTutorialCreateOrDelete} />
+      <TutorialEditorController
+        project={@props.project}
+        tutorial={@state.tutorial}
+        media={@state.media}
+        onDelete={@handleTutorialCreateOrDelete}
+      />
     else
       <TutorialCreator project={@props.project} onCreate={@handleTutorialCreateOrDelete} />

   handleTutorialCreateOrDelete: ->
     @fetchTutorialFor @props.project

-
 module.exports = TutorialEditorFetcher


Workaround for null project introduction

--- app/pages/project/home.cjsx
+++ app/pages/project/home.cjsx
@@ -43,5 +43,5 @@

       <hr />

-      <Markdown className="introduction content-container">{@props.project.introduction}</Markdown>
+      <Markdown className="introduction content-container">{@props.project.introduction ? ''}</Markdown>
     </div>


[grunt] streamline declaration of localized nunjucks task configuration

There is no real need to overcomplicate it with Constructor

--- tasks/grunt-nunjucks-2-html.coffee
+++ tasks/grunt-nunjucks-2-html.coffee
@@ -148,20 +148,19 @@
   isoLocale = (locale) ->
     getLangcode(locale) + '_' + getRegioncode(locale).toUpperCase()

-  # ====================
-  # Construct Grunt Task
-  # ====================
-
-  Task = ->
-    # Define targets, with unique options and files, for each locale
-    locales.forEach (currentLocale) =>
-      localeDir     = getLocaleDir(currentLocale)
-      localeProps   = getLocaleProps(currentLocale)
-      localizedData = taskConfig.data(currentLocale)
-
-      @[currentLocale] = {}
-
-      @[currentLocale].options =
+  # =======================
+  # Config l10n of Nunjucks
+  # =======================
+
+  locales.forEach (currentLocale) =>
+    localeDir     = getLocaleDir(currentLocale)
+    localeProps   = getLocaleProps(currentLocale)
+    localizedData = taskConfig.data(currentLocale)
+
+    # Define targets, with unique options and files for each locale
+    @config "nunjucks.#{currentLocale}",
+
+      options:
         paths                : taskConfig.nunjucksEnv
         autoescape           : taskConfig.autoescape
         data                 : localizedData
@@ -437,7 +436,7 @@

           return data

-      @[currentLocale].files =  [
+      files: [
         expand: true
         cwd: taskConfig.files.cwd + '/'
         src: taskConfig.files.src
@@ -446,9 +445,4 @@
         rename: (dest, src) =>
           src = if taskConfig.humanReadableUrls.enabled then humanReadableUrl(src) else src
           path.join(dest, src)
-      ]
-
-    return @
-
-
-  @config 'nunjucks', new Task()+      ]


we'll need to do the base too

we'll need to do the base too


missed a few beginPaths

missed a few beginPaths

--- tree2.coffee
+++ tree2.coffee
@@ -92,6 +92,7 @@
   render: (ctx) ->
     ctx.save()
     setStyle(ctx, link_style)
+    ctx.beginPath()
     ctx.moveTo(@x, @y)
     ctx.lineTo(@child0.x, @child0.y)
     ctx.moveTo(@x, @y)
@@ -151,6 +152,7 @@
     if @merging?
       ctx.save()
       setStyle(ctx, light_link_style)
+      ctx.beginPath()
       ctx.moveTo(@merging.node.x, @merging.node.y)
       ctx.lineTo(@merging.x, @merging.y)
       ctx.stroke()


opt: test

opt: test

--- test/basic.coffee
+++ test/basic.coffee
@@ -109,7 +109,8 @@
 				Yaku.onUnhandledRejection = old
 				r reason

-			Yaku.reject $val
+			Yaku.resolve().then ->
+			    Yaku.reject $val

 .then ->
 	test 'no unhandled rejection', $val, ->


added selectLayer to Context

added selectLayer to Context


dispatcher checks defined handers against interface

--- src/service/simple_dispatcher.coffee
+++ src/service/simple_dispatcher.coffee
@@ -8,8 +8,23 @@
     @http_interface = options.interface
     @map = options.map
     @handlers = options.handlers
+    @verify_handlers()
+
     @classifier = new Classifier(options)
     @error_handler = options.error_handler
+
+  verify_handlers: () ->
+    for resource, definition of @http_interface
+      actions = Object.keys(definition.actions)
+      handler = @handlers[resource]
+      if handler
+        for action in actions
+          if handler[action]
+          else
+            console.error "WARN:", "Missing #{action} handler for #{resource}"
+
+      else
+        console.error "WARN:", "No handler group for resource type: #{resource}"

   dispatch: (request, response) ->


webapp: calculate project uptime based on "status->>time"

--- src/smc-webapp/project_settings.cjsx
+++ src/smc-webapp/project_settings.cjsx
@@ -717,8 +717,8 @@
         </LabeledRow>

     render_uptime: ->
-        # start_ts is e.g. 1508576664416
-        start_ts = @props.project.getIn(['status', 'start_ts'])
+        # time is e.g. 1508576664416, set by kucalc project manager
+        start_ts = @props.project.getIn(['status', 'time'])
         return if not start_ts?
         return if @props.project.getIn(['state', 'state']) != 'running'


compile tests into tests.js to surpress errors

--- config.coffee
+++ config.coffee
@@ -12,6 +12,7 @@
       joinTo:
         'js/app.js': /^app/
         'js/vendor.js': /^(bower_components|vendor)/
+        'js/tests.js': /^test/ # unneccessary, for surpressing errors
       order:
         before: [
           'bower_components/jquery/jquery.js'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/point.coffee
+++ test/point.coffee
@@ -141,7 +141,7 @@

   test "#clamp", ->
     p = Point(10, 10)
-
+    p.

   test ".centroid", ->
     centroid = Point.centroid(


Add a mocha workaround for diffs.

Add a mocha workaround for diffs.

--- test/specs.coffee
+++ test/specs.coffee
@@ -13,5 +13,8 @@
         try
           expect(result).eql(spec.output)
         catch e
+          # this doesn't actually disable diffs, but rather it makes the diff
+          # output look better. see: https://github.com/visionmedia/mocha/issues/1241
+          e.showDiff = false
           e.stack = ''
           throw e


Refactored, added depict, removed 'previous'

--- cakes.coffee
+++ cakes.coffee
@@ -10,30 +10,33 @@
 	describe(message, callback)
 	return

+depict = (label, args)->
+	if args.length == 1
+		args[1] = args[0]
+		label = ''
+
+	describe label.replace('%s', args[0]), args[1]
+
 exports.Background = (action, callback)->
-	describe("\n   Background: #{action}".magenta, callback)
+	depict "\n   Background: #{action}".magenta, arguments

-exports.Scenario = (message, callback)->
-	describe("\n    Scenario: #{message}".green, callback)
+exports.Scenario = ->
+	depict "\n    Scenario: %s".green, arguments

-exports.Given = (context, callback)->
-	if arguments.length == 1
-		callback = context
-		context = 'previous'.grey
+exports.Given = ->
+	depict "Given: %s", arguments

-	describe("Given: #{context}", callback)
+exports.When = ->
+	depict " When: %s", arguments

-exports.When = (action, callback)->
-	describe(" When: #{action}", callback)
+exports.And = ->
+	depict "  and %s", arguments

-exports.And = (more_action_or_outcome, callback)->
-	describe("  and #{more_action_or_outcome}", callback)
+exports.Then = ->
+	depict " Then: %s", arguments

-exports.Then = (outcome, callback)->
-	describe(" Then: #{outcome}", callback)
-
-exports.But = (not_outcome, callback)->
-	describe(" But #{not_outcome}", callback)
+exports.But = ->
+	depict " But %s", arguments

 # Add function names to global scope.
 (global[name] = func for name, func of module.exports)


[misc] tests: fix a static test

[misc] tests: fix a static test

`ProjectHistoryRedisManager.queueOps` is masked and can not push
 changes into redis during the unittest.

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

--- services/document-updater/test/unit/coffee/RedisManager/RedisManagerTests.coffee
+++ services/document-updater/test/unit/coffee/RedisManager/RedisManagerTests.coffee
@@ -445,13 +445,12 @@

 			describe "with project history disabled", ->
 				beforeEach ->
-					@rclient.rpush = sinon.stub()
 					@settings.apis.project_history.enabled = false
 					@RedisManager.getDocVersion.withArgs(@doc_id).yields(null, @version - @ops.length)
 					@RedisManager.updateDocument @project_id, @doc_id, @lines, @version, @ops, @ranges, @updateMeta, @callback

 				it "should not push the updates into the project history ops list", ->
-					@rclient.rpush.called.should.equal false
+					@ProjectHistoryRedisManager.queueOps.called.should.equal false

 				it "should call the callback", ->
 					@callback
@@ -493,7 +492,6 @@

 		describe "with no updates", ->
 			beforeEach ->
-				@rclient.rpush = sinon.stub().callsArgWith(1, null, @project_update_list_length)
 				@RedisManager.getDocVersion.withArgs(@doc_id).yields(null, @version)
 				@RedisManager.updateDocument @project_id, @doc_id, @lines, @version, [], @ranges, @updateMeta, @callback

@@ -503,7 +501,7 @@
 					.should.equal false

 			it "should not try to enqueue project updates", ->
-				@rclient.rpush
+				@ProjectHistoryRedisManager.queueOps
 					.called
 					.should.equal false


Make sure that there's only one form for each type of records.

--- app/clients.coffee
+++ app/clients.coffee
@@ -8,6 +8,7 @@

 @open_clients = ->
   menu_slide ->
+    ($ ".clientform").remove()
     ($ ".groupform").remove()
     ($ ".arena").append form().addClass "clientform"
     ($ ".arena").append form().addClass "groupform"


:statue_of_liberty::penguin: Updated at https://danielx.net/editor/

--- pixel_editor.coffee
+++ pixel_editor.coffee
@@ -8,7 +8,7 @@
     remoteTarget: -> frame.contentWindow

     childLoaded: ->
-      postmaster.invokeRemote "fromBlob", file
+      postmaster.invokeRemote "loadFile", file
       frame.contentWindow.focus()

       return


use the new NODE_JS global instead

use the new NODE_JS global instead

--- test/src/replace/index-test.coffee
+++ test/src/replace/index-test.coffee
@@ -82,7 +82,7 @@

   describe 'Node.js-specific module replacement', ->
-    return unless require('lodash').isFunction(require('quibble'))
+    return unless NODE_JS

     Given -> @passenger = td.replace('../../fixtures/passenger') #<-- a constructor func
     Given -> @honk = td.replace('../../fixtures/honk') #<-- a plain ol' func


Add unit test for /objekttyper listing

Add unit test for /objekttyper listing

--- test/suite.coffee
+++ test/suite.coffee
@@ -31,6 +31,16 @@
         .get('/?api_key=dnt')
         .expect(200, done)

+  describe '/objekttyper', ->
+    it 'should get avaiable object types', (done) ->
+      request(app)
+        .get('/objekttyper?api_key=dnt')
+        .expect(200)
+        .end (err, res) ->
+          throw err if err
+          assert.deepEqual res.body, ['turer', 'steder', 'områder', 'grupper', 'aktiviteter', 'bilder']
+          done()
+
   describe '/:collection', ->
     require './collection-spec.coffee'


adding shift+enter for new line in textarea tool

--- app/assets/javascripts/components/transcribe/tools/text-tool/index.cjsx
+++ app/assets/javascripts/components/transcribe/tools/text-tool/index.cjsx
@@ -132,6 +132,10 @@
     else
       @props.annotation_key

+  getCaret: ()->
+    el = $(@refs.input0?.getDOMNode())
+
+
   updateValue: (val) ->
     console.log "updated val: ", val
     newAnnotation = @state.annotation
@@ -147,11 +151,17 @@
   handleKeyDown: (e) ->
     @handleChange(e) # updates any autocomplete values
     # if [13].indexOf(e.keyCode) >= 0 # ENTER
-    if ! @state.autocompleting && [13].indexOf(e.keyCode) >= 0 # ENTER
+
+    if (! @state.autocompleting && [13].indexOf(e.keyCode) >= 0) && !e.shiftKey# ENTER
       if window.location.hash is '#/transcribe' || @props.task.next_task? # regular transcribe, i.e. no mark transition
         @commitAnnotation()
       else
         @returnToMarking()
+    else if e.keyCode == 13 && e.shiftKey
+      text_area =  $("textarea")
+      the_text = text_area.val()
+      the_text = the_text.concat("/n")
+      text_area.val(the_text)

   handleBadMark: ()->
     newAnnotation = []


Fix tests

Fix tests

--- test/tests.coffee
+++ test/tests.coffee
@@ -8,11 +8,9 @@
 require "./tests/EventEmitterTest"
 require "./tests/UtilsTest"
 require "./tests/BaseClassTest"
-require "./tests/FrameTest"
 require "./tests/LayerTest"
 require "./tests/LayerStatesTest"
 require "./tests/VideoLayerTest"
-require "./tests/CompatTest"
 require "./tests/ImporterTest"
 require "./tests/LayerAnimationTest"
 require "./tests/ContextTest"


Save lines-of-code fairies.

Save lines-of-code fairies.

--- test/types.coffee
+++ test/types.coffee
@@ -33,9 +33,8 @@
       types.quacksLike {}, {}

   test 'quacksLike with function first argument throws', ->
-    functionArgument = ->
-    assert.throws ->
-      types.quacksLike functionArgument, {}
+    assert.throws ->
+      types.quacksLike (->), {}

   test 'quacksLike with null first argument throws', ->
     assert.throws ->
@@ -62,15 +61,10 @@
     assert.isFalse types.quacksLike({ foo: {}, bar: {} }, { foo: {}, baz: {} })

   test 'quacksLike with bar bar second properties returns true', ->
-    firstFunction = ->
-    secondFunction = ->
-    assert.isTrue types.quacksLike({ foo: firstFunction, bar: {} }, { foo: secondFunction, bar: {} })
+    assert.isTrue types.quacksLike({ foo: (->), bar: {} }, { foo: (->), bar: {} })

   test 'quacksLike with differently typed bar bar second properties returns false', ->
-    firstFunction = ->
-    secondFunction = ->
-    thirdFunction = ->
-    assert.isFalse types.quacksLike({ foo: firstFunction, bar: {} }, { foo: secondFunction, bar: thirdFunction })
+    assert.isFalse types.quacksLike({ foo: (->), bar: {} }, { foo: (->), bar: (->) })

   test 'verifyInstance with new Error and Error does not throw', ->
     assert.doesNotThrow ->


removing comment

removing comment

--- services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/PackageManager.coffee
+++ services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/PackageManager.coffee
@@ -36,7 +36,6 @@

 	class PackageManager
 		getCompletions: (editor, session, pos, prefix, callback) ->
-			console.log '>> making it here'
 			callback null, packageSnippets

 	return PackageManager


Fix moment.add javascript error

Fix moment.add javascript error

--- lineman/app/components/thread_page/previous_proposals_card/previous_proposals_card.coffee
+++ lineman/app/components/thread_page/previous_proposals_card/previous_proposals_card.coffee
@@ -20,7 +20,7 @@
     setLastClosedProposal = ->
       return unless $scope.anyProposals() and !$scope.discussion.hasActiveProposal()
       proposal = $scope.discussion.closedProposals()[0]
-      proposal if moment.add(-1, 'month') < proposal.closedAt
+      proposal if moment().add(-1, 'month') < proposal.closedAt

     $scope.$on 'setSelectedProposal', $scope.setSelectedProposal


accepting coffee manifests

accepting coffee manifests

--- client/app/MainApp/kodingappscontroller.coffee
+++ client/app/MainApp/kodingappscontroller.coffee
@@ -83,34 +83,39 @@

     path = "/Users/#{KD.whoami().profile.nickname}/Applications"

-    @kiteController.run "ls #{escapeFilePath path} -lpva", (err, response)=>
-      if err
-        warn err
-        callback err
-      else
-        files = FSHelper.parseLsOutput [path], response
-        apps  = []
-        stack = []
-
-        files.forEach (file)->
-          if /\.kdapp$/.test file.name
-            apps.push file
-
-        apps.forEach (app)->
-          manifest = if app.type is "folder" then FSHelper.createFileFromPath "#{app.path}/.manifest" else app
-          stack.push (cb)->
-            manifest.fetchContents cb
-
-        manifests = @constructor.manifests
-        async.parallel stack, (err, results)->
-          if err
-            warn err
-            callback? err
-          else
-            results.forEach (app)->
-              app = JSON.parse app
-              manifests["#{app.name}"] = app
-            callback? err, manifests
+    require ["coffee-script"], (coffee)=>
+      @kiteController.run "ls #{escapeFilePath path} -lpva", (err, response)=>
+        if err
+          warn err
+          callback err
+        else
+          files = FSHelper.parseLsOutput [path], response
+          apps  = []
+          stack = []
+
+          files.forEach (file)->
+            if /\.kdapp$/.test file.name
+              apps.push file
+
+          apps.forEach (app)->
+            manifestFile = if app.type is "folder" then FSHelper.createFileFromPath "#{app.path}/.manifest" else app
+            stack.push (cb)->
+              manifestFile.fetchContents cb
+
+          manifests = @constructor.manifests
+          async.parallel stack, (err, results)->
+            if err
+              warn err
+              callback? err
+            else
+              results.forEach (rawManifest)->
+                if rawManifest.substr(0,1) is '{'
+                  manifest = JSON.parse rawManifest
+                else
+                  manifest = coffee.compile rawManifest, { bare : yes }
+                  log manifest, ">>>>"
+                manifests["#{manifest.name}"] = manifest
+              callback? err, manifests

   fetchAppsFromDb:(callback)->


Fix fetchCompiledApp and runApp in kodingAppsController for 3rd party Apps

--- client/app/MainApp/kodingappscontroller.coffee
+++ client/app/MainApp/kodingappscontroller.coffee
@@ -6,7 +6,6 @@

   @manifests = {}

-
   @getManifestFromPath = getManifestFromPath = (path)->

     folderName = (p for p in path.split("/") when /\.kdapp/.test p)[0]
@@ -31,7 +30,7 @@

     {profile} = KD.whoami()
     path = if 'string' is typeof manifest then manifest else manifest.path
-    path = if /^~/.test path then "/Users/#{profile.nickname}#{path.substr(1)}" else path
+    path = if /^~/.test path then "/home/#{profile.nickname}#{path.substr(1)}" else path
     return path.replace /(\/+)$/, ""

   # #
@@ -50,8 +49,7 @@
         if err
           @fetchAppsFromFs (err, apps)=>
             if err then callback()
-            else
-              callback null, apps
+            else callback null, apps
         else
           callback? err, apps

@@ -79,7 +77,7 @@
               # to not to break the result of the stack
               cb null, response

-        {manifests} = @constructor
+        manifests = @constructor.manifests
         async.parallel stack, (err, result)=>
           result.forEach (rawManifest)->
             if rawManifest
@@ -117,11 +115,8 @@

   fetchCompiledApp:(manifest, callback)->

-    {name} = manifest
-    appPath = @getAppPath manifest
-    indexJsPath = "#{appPath}/index.js"
-    @kiteController.run "cat #{escapeFilePath indexJsPath}", (err, response)=>
-      callback err, response
+    indexJs = FSHelper.createFileFromPath "#{@getAppPath manifest}/index.js"
+    indexJs.fetchContents (err, response)=> callback null, response

   # #
   # MISC
@@ -191,7 +186,6 @@
       if err then warn err
       else
         if options and options.type is "tab"
-
           KD.getSingleton("appManager").open manifest.name,
             requestedFromAppsController : yes
           , (appInstance)->
@@ -218,7 +212,6 @@
           callback?()
           return null

-        log "App to run:", name
         callback?()

   publishApp:(path, callback)->
@@ -554,39 +547,6 @@
           callback? err
         else
           callback? null
-
-
-  # cloneApp:(path, callback)->
-
-  #   @fetchApps (err, manifests = {})=>
-  #     if err
-  #       warn err
-  #       new KDNotificationView type : "mini", title : "There was an error, please try again later!"
-  #       callback? err
-  #     else
-  #       manifest = getManifestFromPath path
-
-  #       {repo} = manifest
-
-  #       if /^git/.test repo      then repoType = "git"
-  #       else if /^svn/.test repo then repoType = "svn"
-  #       else if /^hg/.test repo  then repoType = "hg"
-  #       else
-  #         err = "Unsupported repository specified, quitting!"
-  #         new KDNotificationView type : "mini", title : err
-  #         callback? err
-  #         return no
-
-  #       appPath = "/home/#{KD.whoami().profile.nickname}/Applications/#{manifest.name}.kdapp"
-  #       appBackupPath = "#{appPath}.old#{@utils.getRandomNumber 9999}"
-
-  #       @kiteController.run "mv #{escapeFilePath appPath} #{escapeFilePath appBackupPath}" , (err, response)->
-  #         if err then warn err
-  #         @kiteController.run "#{forkRepoCommandMap()[repoType]} #{repo} #{escapeFilePath getAppPath manifest}", (err, response)->
-  #           if err then warn err
-  #           else
-  #             log response, "App cloned!"
-  #           callback? err, response

   # #
   # HELPERS


video/model: use request host publish for activating video

--- client/app/lib/videocollaboration/model.coffee
+++ client/app/lib/videocollaboration/model.coffee
@@ -95,11 +95,9 @@
     if helper.isVideoActive @channel
       @setActive()

-      if @isMySession()
-        @enableVideo {},
-          success: =>
-            @setVideoState yes
-            @setAudioState yes
+      return  unless @isMySession()
+
+      @requestVideoPublishForHost()

   ###*
@@ -240,11 +238,10 @@

       @setActive()

-      if @isMySession()
-        @enableVideo {},
-          success: =>
-            @setVideoState yes
-            @setAudioState yes
+      return  unless @isMySession()
+
+      @requestVideoPublishForHost()
+

     # this event only comes to the user who has been muted, so need to make a
     # filtering here.


video/model: add method to request audio change

--- client/app/lib/videocollaboration/model.coffee
+++ client/app/lib/videocollaboration/model.coffee
@@ -103,6 +103,31 @@

   ###*
+   * If it's already being published just sets state's audio to given state.
+   * If not enables the video and then sets the state.
+   *
+   * @param {boolean} audioState
+  ###
+  requestAudioStateChange: (audioState) ->
+
+    options = _.assign getCameraOptionFromState(@state.video),
+      publishAudio : audioState
+
+    oldAudioState = @state.audio
+
+    if @state.publishing
+
+      return  if audioState is oldAudioState
+
+      @setAudioState audioState
+
+    else
+      @enableVideo options,
+        error   : (err) -> console.error err
+        success : @lazyBound 'setAudioState', audioState
+
+
+  ###*
    * Registers callbacks for service events.
    *
    * @param {OT.Session} session


combine feedback and help

combine feedback and help

--- client/lanes/components/shared/FieldMixin.cjsx
+++ client/lanes/components/shared/FieldMixin.cjsx
@@ -70,7 +70,11 @@

     _fieldMixinRenderFormGroup: (child, props, options) ->
         if (invalidMsg = @fieldInvalidValueMessage())
-            msg = <BS.HelpBlock>{invalidMsg}</BS.HelpBlock>
+            feedback = [
+                <BS.FormControl.Feedback key="feedback" />
+                <BS.HelpBlock key="help">{invalidMsg}</BS.HelpBlock>
+            ]
+
         unless @props.unlabeled
             label =
                 <BS.ControlLabel>
@@ -81,8 +85,7 @@
             <BS.FormGroup validationState={options.validationState}>
                 {label}
                 {child}
-                <BS.FormControl.Feedback />
-                {msg}
+                {feedback}
             </BS.FormGroup>
         </BS.Col>

@@ -102,7 +105,9 @@
         </BS.FormControl.Static>

     _fieldMixinRenderNone: (props) ->
-        <span {...props} />
+        clean = LC.Form.FieldMixin.statics.cleanColumnProps(props, @, 'xs', 'sm', 'md', 'lg')
+
+        <span {...clean} />

     renderType: ->
         if @isEditingRecord()
@@ -118,15 +123,19 @@
             else
                 ['none', 'None']
     statics:
-        cleanColumnProps: (props) ->
-            _.omit props,
+        cleanColumnProps: (props, comp, xtra...) ->
+            _.omit( props,  _.keys(comp.constructor.propTypes).concat([
                 'model', 'label', 'name', 'unlabeled', 'fieldOnly', 'placeholder',
                 'commands', 'query', 'editOnly', 'syncOptions', 'labelField', 'type',
-                'selectField', 'idField', 'queryModel', 'choices'
+                'selectField', 'idField', 'queryModel', 'choices', 'align'
+            ], xtra))

         renderEmptyColumn: (props = @props) ->
-            props = @cleanColumnProps(props)
+            props = @cleanColumnProps(props, @)
             <BS.Col {...props} />
+
+        cleanBsSizes: (props) ->
+            _.omit(props, 'lg', 'md', 'sm', 'xs')

     render: ->
         [type, method] = @renderType()
@@ -134,7 +143,7 @@

         hasError = @isFieldValueInvalid()
         options.validationState = 'warning' if hasError
-        props = LC.Form.FieldMixin.statics.cleanColumnProps(@props)
+        props = LC.Form.FieldMixin.statics.cleanColumnProps(@props, @)

         props.className = _.classnames(
             _.result(this, 'fieldClassName'),


NSE: fix cyclomatic complexity

NSE: fix cyclomatic complexity

--- client/new-stack-editor/lib/views/index.coffee
+++ client/new-stack-editor/lib/views/index.coffee
@@ -146,36 +146,38 @@

     { router, computeController } = kd.singletons

-    switch event
-      when Events.Menu.Logs
+    actions =
+      'Menu.Logs': =>
         @logs.unsetClass 'shake'
         unless @logs.isClosed()
           kd.utils.defer => @logs.setClass 'shake'
         @logs.resize { percentage: 40, store: yes }
-      when Events.Menu.Credentials
+      'Menu.Credentials': =>
         @sideView.show 'credentials', { expanded: no }
-      when Events.Menu.MakeTeamDefault
+      'Menu.MakeTeamDefault': =>
         computeController.makeTeamDefault { template: @getData() }
-      when Events.Menu.Initialize
+      'Menu.Initialize': =>
         @emit Events.InitializeRequested, rest...
-      when Events.Menu.Clone
+      'Menu.Clone': =>
         @toolbar.setBanner
           message  : 'Cloning...'
           autohide : 3000
         computeController.cloneTemplate @getData()
-      when Events.ShowSideView
+      'ShowSideView': =>
         @sideView.show rest...
-      when Events.HideSideView
+      'HideSideView': =>
         @sideView.hide internal = yes
-      when Events.ToggleSideView
+      'ToggleSideView': =>
         @sideView.toggle rest...
-      when Events.HideWarning
+      'HideWarning': =>
         @toolbar.banner.emit Events.Banner.Close
-      when Events.TemplateTitleChangeRequested
+      'TemplateTitleChangeRequested': =>
         @controllers.editor.updateTitle rest...
-      when Events.LoadClonedFrom
+      'LoadClonedFrom': =>
         { config: { clonedFrom } } = @getData()
         router.handleRoute "/Stack-Editor/#{clonedFrom}"
+
+    actions[event]?()

   setData: (data, reset = no) ->


Don't spawn asteroids on top of the ship

--- app/js/main.coffee
+++ app/js/main.coffee
@@ -64,12 +64,13 @@
   generateBodies: ->
     @asteroids = []

-    avgSize = sizeDelta = 3
+    avgSize = sizeDelta = 2.5
     deltaVelocity = 2
     deltaTheta = Math.PI

     searchRadius = avgSize - sizeDelta / 4
-    for pos in Utils.distributeRandomPoints [0, 0], [@width, @height], searchRadius
+    for pos, i in Utils.distributeRandomPoints [0, 0], [@width, @height], searchRadius, [@world.center()]
+      continue if i is 0
       size = avgSize + Utils.random(sizeDelta) - sizeDelta/2

       density = Utils.randomInt(0,4)
@@ -87,10 +88,7 @@
         color: "rgba(#{color},#{color},#{color},1)"
         lineColor: "rgba(#{lineColor},#{lineColor},#{lineColor},1)"

-    for a, i in @asteroids
-      a.index = i
-      @world.addBody a
-
+    @world.addBody a for a in @asteroids
     @ship = new Ship 0.3,
       color: "#468"
       lineColor: "#8CF"


fixed syntax highlighting when editing the activity, click interception, css vulnerability

--- client/app/Applications/Activity.kdapplication/ListItems/ActivityListItemDiscussion.coffee
+++ client/app/Applications/Activity.kdapplication/ListItems/ActivityListItemDiscussion.coffee
@@ -47,14 +47,17 @@
     @$("pre").addClass "prettyprint"
     prettyPrint()

+  render:->
+    super()
+    @$("pre").addClass "prettyprint"
+    prettyPrint()

   click:(event)->
-    if $(event.target).is("[data-paths~=title]")
+    if $(event.target).closest("[data-paths~=title], [data-paths~=body]")
       appManager.tell "Activity", "createContentDisplay", @getData()

   applyTextExpansions:(str = "")->
-
-    str = @utils.applyTextExpansions str
+    str = @utils.expandUsernames @utils.applyMarkdown str

     # FIXME: 500 chars is a naive separation, check if it is in a tag (<a> etc) and
     # make the separation after or before the tag in plain text.


Axes are now instance variables of lightcurve.

--- assets/js/lightcurve.coffee
+++ assets/js/lightcurve.coffee
@@ -18,15 +18,25 @@
             @height = @height - @margin.top - @margin.bottom
             @xScale = d3.scale.linear().range([0, @width]).nice()
             @yScale = d3.scale.linear().range([0, @height]).nice()
+            @xAxis = d3.svg.axis().scale(@xScale).orient('bottom').ticks(10).tickSize(-@height, 0, 0)
+            @yAxis = d3.svg.axis().scale(@yScale).orient('left').ticks(10).tickSize(-@width, 0, 0)

         getSvg: (selector) ->
-            d3.select(selector).append('svg')
+            @svg = d3.select(selector).append('svg')
                 .attr
                     width: @width + @margin.left + @margin.right
                     height: @height + @margin.top + @margin.bottom
                 .append('g')
                 .attr
                     transform: "translate(#{@margin.left},#{@margin.top})"
+            @svg.append('g')
+                .attr
+                    class: 'x axis'
+                    transform: "translate(0,#{@height})"
+            @svg.append('g')
+                .attr
+                    class: 'y axis'
+            @svg

         getPhase: (jd) =>
             if @isPeriodic
@@ -38,17 +48,12 @@
         drawData: (data) ->
             @xScale.domain d3.extent data, (d) -> d.jd
             @yScale.domain d3.extent data, (d) -> d.magnitude
-            xAxis = d3.svg.axis().scale(@xScale).orient('bottom').ticks(10).tickSize(-@height, 0, 0)
-            yAxis = d3.svg.axis().scale(@yScale).orient('left').ticks(10).tickSize(-@width, 0, 0)
-            @svg.append('g')
-                .attr
-                    class: 'x axis'
-                    transform: "translate(0,#{@height})"
-                .call(xAxis)
-            @svg.append('g')
-                .attr
-                    class: 'y axis'
-                .call(yAxis)
+            @xAxis.scale(@xScale)
+            @yAxis.scale(@yScale)
+            @svg.select('.x.axis')
+                .call(@xAxis)
+            @svg.select('.y.axis')
+                .call(@yAxis)
             @svg.selectAll('circle')
                 .data(data)
                 .enter()


Fix account hidden not saved

Fix account hidden not saved

--- app/src/controllers/wallet/dialogs/wallet_dialogs_accountsettings_dialog_view_controller.coffee
+++ app/src/controllers/wallet/dialogs/wallet_dialogs_accountsettings_dialog_view_controller.coffee
@@ -39,7 +39,7 @@
     if @_getAccount().isDeletable()
       @_getAccount().delete()
     else
-      @_getAccount().set('hidden', yes)
+      @_getAccount().set('hidden', yes).save()
     @dismiss()
     ledger.app.router.go '/wallet/accounts/index'


instead of just pre-loading the project list, also pre-render it.

--- salvus/page/account.coffee
+++ salvus/page/account.coffee
@@ -975,4 +975,4 @@

 salvus_client.on "signed_in", () ->
     $(".salvus-remember_me-message").hide()
-    require('projects').get_project_list(update:false)
+    require('projects').update_project_list()


make it so jumping to internal links in sagews is more pleasant (scrolls to middle)

--- salvus/page/syncdoc.coffee
+++ salvus/page/syncdoc.coffee
@@ -1556,7 +1556,7 @@
         for x in cm.getAllMarks()
             t = $(x.replacedWith).find(selector)
             if t.length > 0
-                cm.scrollIntoView(x.find().from)
+                cm.scrollIntoView(x.find().from, cm.getScrollInfo().clientHeight/2)
                 return

     process_html_output: (e) =>


Add a test that we can visit the docs page

--- test/integration/free_trial.coffee
+++ test/integration/free_trial.coffee
@@ -74,11 +74,20 @@
           url.should.equal "#{base_url}/pricing/expired"
           done()

+    context 'when I click the docs button', (done) ->
+      before (done) ->
+        wd40.click '.docs a', done
+
+      it 'should be seeing the documentation', (done) ->
+        wd40.trueURL (err, url) ->
+          url.should.equal "#{base_url}/help"
+          done()
+
     context 'when I click the super duper ScraperWiki digger', ->
       before (done) ->
         wd40.click '#logo', done

-      it 'should still be on the pricing expired page', (done) ->
+      it 'should again be on the pricing expired page', (done) ->
         wd40.trueURL (err, url) ->
           url.should.equal "#{base_url}/pricing/expired"
           done()


test(navbar): spawn toolbar

test(navbar): spawn toolbar

--- test/spec/directives/navbar.coffee
+++ test/spec/directives/navbar.coffee
@@ -94,6 +94,16 @@
         expect($icon.hasClass 'bp-icon').toBe true
         expect($icon.attr 'aria-label').toBe 'Action'

+      it 'should spawn toolbar', inject ($compile) ->
+        element3 = $compile("
+          <bp-navbar>
+            <bp-action>First</bp-action>
+            <bp-action>Second</bp-action>
+            <bp-action>Third</bp-action>
+          </bp-navbar>") scope
+        expect(element3.next().is 'bp-toolbar').toBe true
+        expect(element3.next().children().length).toBe 3
+
   describe 'android', ->
     scope   = null
     state   = null
@@ -146,5 +156,15 @@
           <bp-navbar>
             <bp-action>Action</bp-action>
           </bp-navbar>") scope
-        expect(element.children().length).toBe 3
-        expect(element.find('bp-navbar-icon').length).toBe 1
+        expect(element2.children().length).toBe 3
+        expect(element2.find('bp-navbar-icon').length).toBe 1
+
+      it 'should spawn action overflow', ->
+        element3 = compile("
+          <bp-navbar>
+            <bp-action>First</bp-action>
+            <bp-action>Second</bp-action>
+            <bp-action>Third</bp-action>
+          </bp-navbar>") scope
+        expect(element3.children().length).toBe 5
+        expect(element3.find('bp-action-overflow').length).toBe 1


Give default value for redis url

Give default value for redis url

--- scripts/poo-tracker.coffee
+++ scripts/poo-tracker.coffee
@@ -4,7 +4,7 @@
 POO_TRACKER_KEY = "poops"
 POO_LATEST_KEY = "poops:latest_message"

-info = Url.parse process.env.POO_REDIS_URL
+info = Url.parse process.env.POO_REDIS_URL or "redis://localhost:6379/0"
 redis_client = Redis.createClient(info.port, info.hostname)
 redis_clent.auth info.auth.split(":")[1] if info.auth


Remove Atom merge-conflicts package

Remove Atom merge-conflicts package

in favor of the Git integration in Atom 1.18.0.

--- atom/packages.cson
+++ atom/packages.cson
@@ -23,7 +23,6 @@
   "linter"
   "linter-ui-default"
   "linter-write-good"
-  "merge-conflicts"
   "multi-cursor-plus"
   "package-sync"
   "pane-layout-plus"


Atom: Some first snippets

Atom: Some first snippets

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -16,3 +16,24 @@
 # This file uses CoffeeScript Object Notation (CSON).
 # If you are unfamiliar with CSON, you can read more about it here:
 # https://github.com/bevry/cson#what-is-cson
+
+'*':
+  'single arrow':
+    'prefix': '->'
+    'body': '→'
+  'double arrow':
+    'prefix': '=>'
+    'body': '⇒'
+  dots:
+    prefix: '...'
+    body: '…'
+  cop:
+    prefix: 'cop'
+    body: '🤖'
+  shit:
+    prefix: 'shit'
+    body: '💩'
+'.source.ruby':
+  pry:
+    prefix: 'pry'
+    body: "require 'pry'; binding.pry"


Removed some console.debug lines.

Removed some console.debug lines.

--- src/elements/Options/Options.coffee
+++ src/elements/Options/Options.coffee
@@ -205,13 +205,11 @@
 		for value in check
 			for opt in @__options
 				if opt.value == value
-					console.debug "value is ok.", @hasData(), @getData()[@_name]
 					return
 		throw new CUI.CheckValueError("Value is not in the options.")

 	render: ->
 		super()
-		# console.debug "Options.render", @getUniqueId()

 		unsorted_options = @getArrayFromOpt("options")

@@ -247,8 +245,6 @@
 			for a, idx in arr
 				delete(a.___idx)

-			# console.debug "order_value_array:", arr.join(", ")
-
 		order_options_by_value_array = =>
 			if @hasData()
 				arr = @getValue()
@@ -260,8 +256,6 @@
 			# first put the active values
 			for value in arr
 				@__options_order.push(find_value_in_options(value, unsorted_options))
-
-			# console.debug "order in unsorted:", @__options_order.join(", ")

 			missing_opts = []
 			# second put the inactive values
@@ -289,7 +283,6 @@
 		@__options = unsorted_options.slice(0)

 		if @__options_order
-			# console.debug "rendering with order", @__options_order.join(", ")
 			sort_options()

 		@__checkboxes = []
@@ -388,7 +381,6 @@

 		if @hasData()
 			@__setDataOnOptions()
-			# console.debug "setting data on options", @__radio, @getName(), @getData(), CUI.util.dump(@__options_data)

 		if @__checkboxes.length
 			if @_sortable and not CUI.util.isEmpty(@_sortable_hint)
@@ -456,10 +448,7 @@
 					element: sortable_element
 					selector: sortable_selector
 					sorted: (ev, from_idx, to_idx) =>
-						# console.debug "from:", from_idx, "to:", to_idx
-						# console.debug "options order before sort", @__options_order.join(", ")
 						CUI.util.moveInArray(from_idx, to_idx, @__options_order, from_idx < to_idx)
-						# console.debug "options order after sort", @__options_order.join(", ")
 						# re order options
 						sort_options()


can now give an arbatrary number of layers, not recieve though

--- providers/blend.coffee
+++ providers/blend.coffee
@@ -2,7 +2,8 @@
 pngparse = require 'pngparse'
 async = require 'async'
 im = require 'imagemagick'
-
+
+
 parsePng = (buff, cb)->
 	pngparse.parse buff, (err, data) ->
 		if err
@@ -10,17 +11,14 @@
 		else
 			cb null, data.data

-composit = (a,b,cb)->
-	if a and not b
-		cb null, a
+composit = (buffs,cb)->
+	if buffs.length == 1
+		cb null, buffs[0]
 		return
-	else if not a and b
-		cb null, b
-		return
-	else if not a and not b
+	else if buffs.length == 0
 		cb(true)
 		return
-	async.map [a,b], parsePng, (err, tiles)->
+	async.map buffs, parsePng, (err, tiles)->
 		if tiles.length == 0
 			cb true
 			return
@@ -49,8 +47,7 @@
 	jsonp:()->undefined

 Blender = (opt, routes)->
-	@base = opt.layers[0]
-	@overlay = opt.layers[1]
+	@layers = opt.layers
 	@routes = routes
 	@

@@ -59,14 +56,17 @@

 Blender::getTile = (z,x,y, cb) ->
 	#console.log "getting"
-	opts={layer : @base}
+	opts={}
 	opts.zoom = z
 	opts.y = y
 	opts.x = x
 	opts.format = "png"
-	@routes.getTile opts, (err, base)=>
-		#console.log "getting base"
-		opts.layer = @overlay
-		@routes.getTile opts, (err, overlay)=>
-			composit base, overlay, cb
+	mapFunc = (l, clb)=>
+		opts.layer = l
+		@routes.getTile opts, clb
+	filterFunc = (a,c)->
+		c !!a
+	async.map @layers, mapFunc, (err, tilesRaw)->
+		async.filter tilesRaw, filterFunc, (tiles)->
+			composit tiles, cb


Tests: Added showMoreCommentLink test.

Tests: Added showMoreCommentLink test.

--- tests/src/activity/activity.coffee
+++ tests/src/activity/activity.coffee
@@ -138,20 +138,32 @@
       .end()

+  searchActivity: (browser) ->
+
+    post     = helpers.postActivity(browser)
+    selector = '[testpath=activity-list] [testpath=ActivityListItemView]:first-child'
+
     browser
-      .assert.containsText selector + ' .comment-container', comment # Assertion
+      .setValue                 '.kdtabhandlecontainer .search-input', post + '\n'
+      .pause                    5000
+      .assert.containsText      selector , post # Assertion
       .end()

-  # searchActivity: (browser) ->
+  showMoreCommentLink: (browser) ->

-  #   post         = helpers.postActivity(browser)
+    helpers.postComment(browser)

-  #   selector        = '[testpath=activity-list] [testpath=ActivityListItemView]:first-child'
+    for i in [1..5]
+      helpers.postComment(browser, no, no)

-  #   browser
-  #     .setValue                 '.kdtabhandlecontainer .search-input', post + '\n'
-  #     .pause                    5000
-  #     .assert.containsText      selector , post # Assertion
-  #     .end()
+    browser
+      .refresh()
+      .waitForElementVisible  activitySelector + ' .comment-container [testpath=list-previous-link]', 5000
+      .end()

+
+
+
+
+


add db_handler.coffee | postgresql操作のクラスを作成

--- public/js/db_handler.coffee
+++ public/js/db_handler.coffee
@@ -12,38 +12,49 @@

 pg = require 'pg'

-Db_Handler = ->
-  this.db_url = process.env.DATABASE_URL
-  this.db_name = "user_list"
+module.exports = class Db_Handler
+
+  constructor: ->
+    @db_url = process.env.DATABASE_URL
+    @db_name = "user_list"
+    @client = new pg.Client @db_url
+
   # Send query to database
-  this.sendQuery = (query) ->
-    pg.connect this.db_url, (err, client, done) ->
+  sendQuery: (command, callback = ->) ->
+    client = new pg.Client @db_url
+    client.connect (err) ->
       if err
-        return [false, err]
-      client.query query, (err, result) ->
-        done()
+        calback [err, null]
+        return
+      client.query command, (err, result) ->
         if err
-          return [false, err]
-        return [true, result.rows]
+          callback [err, null]
+          return
+        callback [null, result.rows]
+        return

-# Get data from database
-Db_Handler.prototype.getGithubName = (yo_name) ->
-  query = "SELECT github_name FROM #{db_name} WHERE yo_name='#{yo_name}';"
-  return this.sendQuery query
+  # Get data from database
+  getGithubName: (yo_name, callback = ->) ->
+    query = "SELECT github_name FROM #{@db_name} WHERE yo_name='#{yo_name}';"
+    @sendQuery query, (data) ->
+      callback data[0], data[1]
+      return

-# Insert data to database
-# yo_name -> yo_name
-# github_name -> github_name
-Db_Handler.prototype.writeData = (yo_name, github_name) ->
-  query = "INSERT INTO '#{this.db_name}' (yo_name, github_name) values ('#{yo_name}', '#{github_name}');"
+  # Insert data to database
+  # yo_name -> yo_name
+  # github_name -> github_name
+  writeData: (yo_name, github_name, callback = ->) ->
+    query = "INSERT INTO #{@db_name} (yo_name, github_name) values ('#{yo_name}', '#{github_name}');"

-  # TODO: Yoアカウントがあるかどうか検証
-  # TODO: githubアカウントが実在するかどうか検証
-  return this.sendQuery query
+    # TODO: Yoアカウントがあるかどうか検証
+    # TODO: githubアカウントが実在するかどうか検証
+    @sendQuery query, (data) ->
+      callback data[0], data[1]
+      return

-# Delete data in database
-Db_Handler.prototype.deleteData = (yo_name) ->
-  query = "DELETE FROM #{this.db_name} WHERE yo_name='#{yo_name}';"
-  return this.sendQuery query
-
-module.exports = Db_Handler
+  # Delete data in database
+  deleteData: (yo_name, callback = ->) ->
+    query = "DELETE FROM #{@db_name} WHERE yo_name='#{yo_name}';"
+    @sendQuery query, (data) ->
+      callback data[0], data[1]
+      return


Added extra environment for running deployments

--- deploy.coffee
+++ deploy.coffee
@@ -2,9 +2,14 @@
 task    = control.task
 perform = control.perform

-task 'staging', 'config got my server', ->
+task 'socketracer.com', 'config got my server', ->
   config = user: 'root'
   addresses = ['socketracer.com']
+  return control.hosts(config, addresses)
+
+task 'socketracer.co', 'config got my server', ->
+  config = user: 'root'
+  addresses = ['socketracer.co']
   return control.hosts(config, addresses)

 task 'deploy', 'deploy the latest version of the app', (host) ->
@@ -12,7 +17,7 @@
     perform 'restart', host

 task 'update_dependencies', 'upgrade socketstream', (host) ->
-  host.ssh 'cd socketstream/ && git pull origin master && sudo npm link', ->
+  host.ssh 'cd downloads/socketstream/ && git pull origin master && sudo npm link', ->

 task 'restart', 'restart the application', (host) ->
   host.ssh 'sudo sh /etc/init.d/socketracer restart'


Allow manual jobs promotion.

Allow manual jobs promotion.

--- worker.coffee
+++ worker.coffee
@@ -142,3 +142,6 @@
               Log.error "Error running a job queue: #{error.stack or error}"
       finally
         @_jobQueueRunning = false
+
+  @promote: ->
+    @collection._promote_jobs()


Handled moving breakpoints

Handled moving breakpoints

--- lib/atom-gdb.coffee
+++ lib/atom-gdb.coffee
@@ -58,6 +58,22 @@
       marker = editor.markBufferRange(range, {invalidate: 'never'})
       editor.decorateMarker(marker, {type: 'line-number', class: 'breakpoint'})
       @markers[key] = marker
+      marker.key = key
+      marker.filename = filename
+
+      bps = @breakPoints
+
+      marker.onDidChange (event) ->
+        old_line = event.oldHeadBufferPosition.row + 1
+        new_line = event.newHeadBufferPosition.row + 1
+        new_key = marker.filename + ':' + new_line
+        bps.splice(bps.indexOf(marker.key), 1)
+        bps.push new_key
+        marker.key = new_key
+        AtomGdb.updateGdbInit()
+        console.log("Moved breakpoint:", filename, ":", old_line, "to", new_line)
+        return
+
       console.log("Added breakpoint:", filename, ":", row)
     else
       @breakPoints.splice(index, 1)


Add language-erlang Atom package

Add language-erlang Atom package

--- atom/packages.cson
+++ atom/packages.cson
@@ -16,6 +16,7 @@
   "intentions"
   "language-elixir"
   "language-elm"
+  "language-erlang"
   "language-haml"
   "language-pug"
   "language-rspec"


Remove (fe); snippet in favor of general (fe) snippet

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -193,10 +193,6 @@
     'prefix': '(fe)'
     'body': '(function ($1) {\n\t$2\n})$3'

-  'wrapped function expression (w/ semicolon)':
-    'prefix': '(fe);'
-    'body': '(function ($1) {\n\t$2\n});'
-
   'immediately-invoked function expression':
     'prefix': 'iife'
     'body': '(function($1) {\n$3\n}($2));'


oops, @cleanup_alloca -> @currentFunction.cleanup_alloca

--- lib/compiler.coffee
+++ lib/compiler.coffee
@@ -670,7 +670,7 @@
                                 if not @returnValueAlloca?
                                         @returnValueAlloca = @createAlloca @currentFunction, EjsValueType, "returnValue"
                                 irbuilder.createStore rv, @returnValueAlloca
-                                irbuilder.createStore (llvm.Constant.getIntegerValue int32Type, ExitableScope.REASON_RETURN), @cleanup_alloca
+                                irbuilder.createStore (llvm.Constant.getIntegerValue int32Type, ExitableScope.REASON_RETURN), @currentFunction.cleanup_alloca
                                 irbuilder.createBr @finallyStack[0]
                         else
                                 return_alloca = @createAlloca @currentFunction, EjsValueType, "return_alloca"


filter empty values from metadata before passing to book

--- scripts/views/layouts/workspace/bookshelf.coffee
+++ scripts/views/layouts/workspace/bookshelf.coffee
@@ -64,13 +64,13 @@
             rights: rights
             rightsUrl: rightsUrl
             dateModified: "#{now.getFullYear()}-#{now.getMonth()+1}-#{now.getDate()}"
-            subject: modal.find('[name="subject"]').val().split(',')
-            keywords: modal.find('[name="keywords"]').val().split(',')
-            rightsHolders: modal.find('[name="rights-holders"]').val().split(',')
-            authors: modal.find('[name="authors"]').val().split(',')
-            publishers: modal.find('[name="publishers"]').val().split(',')
-            editors: modal.find('[name="editors"]').val().split(',')
-            translators: modal.find('[name="translators"]').val().split(',')
-            illustrators: modal.find('[name="illustrators"]').val().split(',')
+            subject: modal.find('[name="subject"]').val().split(',').filter (i) -> i
+            keywords: modal.find('[name="keywords"]').val().split(',').filter (i) -> i
+            rightsHolders: modal.find('[name="rights-holders"]').val().split(',').filter (i) -> i
+            authors: modal.find('[name="authors"]').val().split(',').filter (i) -> i
+            publishers: modal.find('[name="publishers"]').val().split(',').filter (i) -> i
+            editors: modal.find('[name="editors"]').val().split(',').filter (i) -> i
+            translators: modal.find('[name="translators"]').val().split(',').filter (i) -> i
+            illustrators: modal.find('[name="illustrators"]').val().split(',').filter (i) -> i

           modal.modal('hide')


Use appropriate zIndex

Use appropriate zIndex

--- src/smc-webapp/project_files.cjsx
+++ src/smc-webapp/project_files.cjsx
@@ -2028,7 +2028,7 @@
             />
             {@render_file_creation_error()}
             {@render_help_info()}
-

+

                 {@render_output(@state.error, {color:'darkred', margin:0})}
                 {@render_output(@state.stdout, {margin:0})}


detect platform by forge

detect platform by forge

--- app/config.coffee
+++ app/config.coffee
@@ -7,15 +7,15 @@
     oauthEndpoint: 'http://localhost:3000/oauth/'
     oauthRedirectUri: 'http://localhost:9294'

-  # Development against production
-  Config =
-   env: 'development'
-   host: 'http://www.boorgle.com/api/v1'
-   clientId: 'boorgle-web-mobile-localhost'
-   oauthEndpoint: 'http://www.boorgle.com/oauth/'
-   oauthRedirectUri: 'http://localhost:9294'
+#  # Development against production
+#  Config =
+#   env: 'development'
+#   host: 'http://www.boorgle.com/api/v1'
+#   clientId: 'boorgle-web-mobile-localhost'
+#   oauthEndpoint: 'http://www.boorgle.com/oauth/'
+#   oauthRedirectUri: 'http://localhost:9294'

-else if document.location.href.match(/file:\/\//)
+else if forge?
   # iPhone Native
   Config =
     env: 'ios'


Improve youtube_player_api_loader.coffee

--- app/assets/javascripts/martian_components/components/core/youtube_player_api_loader.coffee
+++ app/assets/javascripts/martian_components/components/core/youtube_player_api_loader.coffee
@@ -13,14 +13,19 @@
     firstScriptTag.parentNode.insertBefore(tag, firstScriptTag)

   onYouTubeIframeAPIReady: ->
+    instance = Components.Core.YoutubePlayerApiLoader.instance
+    $(instance).trigger('apiReady')
+
     $('.embedded-video-player-wrapper').each (i, wrapper) =>
       wrapper = $(wrapper)

       # Skip auto init if inside of a nested componenent
       if !wrapper.parents('[data-nested-level="1"]').length
-        Components.Core.YoutubePlayerApiLoader.instance.setupWrapper(wrapper)
+        instance.setupWrapper(wrapper)

   setupWrapper: (wrapper) ->
+    return if wrapper.data('player')
+
     placeholder = $('.embedded-video-player-placeholder', wrapper)

     options =
@@ -35,4 +40,3 @@
     player = new YT.Player(placeholder.attr('id'), options)

     wrapper.data 'player', player
-


Fixes tests to use keyStr method

Fixes tests to use keyStr method

--- test/unit/lib/xmacroplay.test.coffee
+++ test/unit/lib/xmacroplay.test.coffee
@@ -5,15 +5,15 @@

   describe "when guarding against improper commands", ->
     it "should return null for undefined input", ->
-      result = xmacroplay.create()
+      result = xmacroplay.keyStr()
       expect(result).to.equal null

     it "should return null for null input", ->
-      result = xmacroplay.create null
+      result = xmacroplay.keyStr null
       expect(result).to.equal null

     it "should return null for an empty command", ->
-      result = xmacroplay.create ''
+      result = xmacroplay.keyStr ''
       expect(result).to.equal null

     it "should return null for an invalid string input", ->
@@ -22,11 +22,11 @@

   describe "when creating valid commands", ->
     it "should create a single keypress and keyrelease", ->
-      result = xmacroplay.create 'space'
+      result = xmacroplay.keyStr 'space'
       expect(result).to.equal "printf \"KeyStr space\" | xmacroplay \"$DISPLAY\""

     it "should create a sequence of keystrokes", ->
-      result = xmacroplay.create 'press:alt press:F4 release:F4 release:alt'
+      result = xmacroplay.keyStr 'press:alt press:F4 release:F4 release:alt'
       expect(result).to.equal "printf \"KeyStrPress alt\\nKeyStrPress F4\\nKeyStrRelease F4\\nKeyStrRelease alt\" | xmacroplay \"$DISPLAY\""

     it "should create a valid string command", ->


Fixed path conversion Windows -> Posix

Fixed path conversion Windows -> Posix

Hopefully it's not **TOO** complicated?

--- lib/grammars.coffee
+++ lib/grammars.coffee
@@ -71,7 +71,7 @@
     else if GrammarUtils.OperatingSystem.isWindows() and GrammarUtils.OperatingSystem.release().indexOf("14399") >= 0
       "File Based":
         command: "bash"
-        args: (context) -> ["-c", "g++ -Wall -include stdio.h -include iostream '" + context.filepath + "' -o /tmp/cpp.out && /tmp/cpp.out"]
+        args: (context) -> ["-c", "g++ -Wall -include stdio.h -include iostream '/mnt/" + path.posix.join.apply(path.posix, [].concat([context.filepath.split(path.win32.sep)[0].toLowerCase()], context.filepath.split(path.win32.sep).slice(1))).replace(":", "") + "' -o /tmp/cpp.out && /tmp/cpp.out"]

   Clojure:
     "Selection Based":


Client: Activity: Fixed conflict

Client: Activity: Fixed conflict

--- client/activity/lib/components/channelparticipantsdropdown/index.coffee
+++ client/activity/lib/components/channelparticipantsdropdown/index.coffee
@@ -17,16 +17,6 @@
     visible        : no
     selectedIndex  : 0
     selectedItem   : null
-<<<<<<< HEAD
-
-
-  # this method overrides DropupWrapperMixin-componentDidUpdate handler.
-  # In this component, we use dropdown keyword. In DropupWrapperMixin/componentDidUpdate handler
-  # expects dropup component so it occurs an error. Also this component doesn't need any action when
-  # componentDidUpdate event fired.
-  componentDidUpdate: ->
-=======
->>>>>>> Client: Activity: Deleted unnecessary lines

   # this method overrides DropupWrapperMixin-componentDidUpdate handler.


Specify regex directly as filter

Specify regex directly as filter

--- build/tasks/build-task.coffee
+++ build/tasks/build-task.coffee
@@ -14,9 +14,9 @@
     mkdir path.dirname(buildDir)

     if process.platform is 'darwin'
-      cp 'atom-shell/Atom.app', shellAppDir, filter: -> /default_app/
+      cp 'atom-shell/Atom.app', shellAppDir, filter: /default_app/
     else
-      cp 'atom-shell', shellAppDir, filter: -> /default_app/
+      cp 'atom-shell', shellAppDir, filter: /default_app/

     mkdir appDir


config: remove redis-tools checks

config: remove redis-tools checks

--- config/generateRunFile.coffee
+++ config/generateRunFile.coffee
@@ -313,7 +313,6 @@
       command -v nginx         >/dev/null 2>&1 || { echo >&2 "I require nginx but it's not installed. (brew install nginx maybe?)  Aborting."; exit 1; }
       command -v mongo         >/dev/null 2>&1 || { echo >&2 "I require mongo but it's not installed. (brew install mongo maybe?)  Aborting."; exit 1; }
       command -v pg_isready    >/dev/null 2>&1 || { echo >&2 "I require pg_isready but it's not installed. (brew install postgresql maybe?)  Aborting."; exit 1; }
-      command -v redis-cli     >/dev/null 2>&1 || { echo >&2 "I require redis-cli but it's not installed. (brew install redis maybe?)  Aborting."; exit 1; }
       command -v node          >/dev/null 2>&1 || { echo >&2 "I require node but it's not installed.  Aborting."; exit 1; }
       command -v npm           >/dev/null 2>&1 || { echo >&2 "I require npm but it's not installed.  Aborting."; exit 1; }
       command -v gulp          >/dev/null 2>&1 || { echo >&2 "I require gulp but it's not installed. (npm i gulp -g)  Aborting."; exit 1; }
@@ -426,7 +425,6 @@

     function runRedisDocker () {
         docker run -d -p 6379:6379 --name=redis redis
-        check_connectivity redis
     }

     function runImplyDocker () {


Avoid http and mixed content for tottoz

Avoid http and mixed content for tottoz

--- app/assets/javascripts/chat.coffee
+++ app/assets/javascripts/chat.coffee
@@ -10,7 +10,7 @@
     @board.find(".board-left .norloge").click @norloge
     @board.find("form").submit @postMessage
     @totoz_type = $.cookie("totoz-type")
-    @totoz_url  = $.cookie("totoz-url") or "http://sfw.totoz.eu/gif/"
+    @totoz_url  = $.cookie("totoz-url") or "https://sfw.totoz.eu/gif/"
     @norlogize      right for right in @board.find(".board-right")
     @norlogize_left left  for left  in @board.find(".board-left time").get().reverse()
     @board.on("mouseenter", "time", @highlitizer)


Small bug fixed

Small bug fixed

--- server/src/WaferPie.coffee
+++ server/src/WaferPie.coffee
@@ -190,10 +190,9 @@
     # @param {number} port The listening port of NodeJS http.createServer function
     start: (address, port) ->
         throw new Exceptions.Fatal('Please call configure() before start()!')  unless @_configured
-        $this = this
         @info 'Starting...'
-        http.createServer((request, response) ->
-            requestHandler = new RequestHandler @_configs, @_applications, @_logger
+        http.createServer((request, response) =>
+            requestHandler = new RequestHandler @_applications, @_configs, @_logger
             requestHandler.process new Request(request), new Response(response)
         ).listen port, address
         @info address + ':' + port


copy in @avidyut1's changes here

copy in @avidyut1's changes here

--- app/assets/javascripts/home.coffee
+++ app/assets/javascripts/home.coffee
@@ -12,8 +12,8 @@
 			template = Handlebars.compile(source);
 			for j in response
 				if j.title
-					if(j.title.length > 50)
-						j.short_title = j.title.substring(0, 50)+".."
+					if(j.title.length > 30)
+						j.short_title = j.title.substring(0, 30)+".."
 					else
 						j.short_title = j.title
 				if j.description


Fix issue where one cannot quit out of reporting screen.

--- app/assets/javascripts/main.coffee
+++ app/assets/javascripts/main.coffee
@@ -200,7 +200,7 @@

     reportError: (id) ->
         result = ""
-        while result == ""
+        while result != null and result == ""
             result = window.prompt("Enter reason for reporting this entry:")
         if result != null
             query = $.ajax "/businesses/report/" + id, {type: "POST", data: {


finally figured out workaround for safari issue

--- app/assets/javascripts/main.coffee
+++ app/assets/javascripts/main.coffee
@@ -199,10 +199,10 @@
         this._map.setZoom 15

     reportError: (id) ->
-        result = ""
-        while result? and result == ""
-            result = window.prompt("Enter reason for reporting this entry:")
-        if result?
+        result = " "
+        while result? and result == " "
+            result = window.prompt("Enter reason for reporting this entry:", " ")
+        if result? and (result != "")
             query = $.ajax "/businesses/report/" + id, {type: "POST", data: {
                 reason: result
             }}


BF-147 Implementing scaling of route according to zoom level in XY View

--- app/assets/javascripts/view.coffee
+++ app/assets/javascripts/view.coffee
@@ -292,20 +292,20 @@

   moveActivePlane : (p) ->
     cam2d.moveActivePlane p
-    @updateRoutePosition p
+    @updateRoute()

   #FIXME: why can't I call move() from within this function?
   moveX : (x) ->
     cam2d.moveActivePlane [x, 0, 0]
-    View.updateRoutePosition [x, 0, 0]
+    View.updateRoute()

   moveY : (y) ->
     cam2d.moveActivePlane [0, y, 0]
-    View.updateRoutePosition [0, y, 0]
+    View.updateRoute()

   moveZ : (z) ->
     cam2d.moveActivePlane [0, 0, z]
-    View.updateRoutePosition [0, 0, z]
+    View.updateRoute()

   prevViewportSize : =>
     (View.camera[VIEW_3D].right - View.camera[VIEW_3D].left)         # always quadratic
@@ -353,10 +353,12 @@

   zoomIn : ->
     cam2d.zoomIn(cam2d.getActivePlane())
+    View.updateRoute()

   #todo: validation in Model
   zoomOut : ->
     cam2d.zoomOut(cam2d.getActivePlane())
+    View.updateRoute()

   setActivePlaneXY : ->
     View.setActivePlane PLANE_XY
@@ -379,10 +381,7 @@
     # calculate the global position of the rightclick
     position = [curGlobalPos[0] - 192/curZoomStep + position[0]/curZoomStep, curGlobalPos[1] - 192/curZoomStep + position[1]/curZoomStep, curGlobalPos[2]]
     unless @curIndex
-      @curIndex = 1
-      @route.geometry.vertices[0] = new THREE.Vector3(400, Game.dataSet.upperBoundary[1] - 500, 340)
-      @routeView.geometry.vertices[0] = new THREE.Vector3(400, -340, -500)
-      @particleSystem.geometry.vertices[0] = new THREE.Vector3(400, Game.dataSet.upperBoundary[1] - 500, 340)
+      @curIndex = 0
     # Translating ThreeJS' coordinate system to the preview's one
     if @curIndex < @maxRouteLen
       @route.geometry.vertices[@curIndex] = new THREE.Vector3(position[0], Game.dataSet.upperBoundary[1] - position[2], position[1])
@@ -433,7 +432,10 @@
     @route = route
     @routeView = routeView
     @particleSystem = particleSystem
-    @routeView.position = new THREE.Vector3(-400, 340, 501)
+
+    # Initializing Position
+    gPos = cam2d.getGlobalPos()
+    @routeView.position = new THREE.Vector3(-gPos[0], gPos[1], gPos[2]+1)

     particle = new THREE.Mesh(new THREE.CubeGeometry(300, 300, 300, 10, 10, 10), new THREE.MeshBasicMaterial({color: 0xff0000}))
     particle.position.x = 0
@@ -447,6 +449,12 @@
     @addGeometry VIEW_3D, particleSystem
     @addGeometry PLANE_XY, routeView

-  updateRoutePosition : (p) ->
-    @routeView.position = new THREE.Vector3(@routeView.position.x - p[0], @routeView.position.y + p[1], cam2d.getGlobalPos()[2]+1)
+  updateRoute : ->
+    gPos                = cam2d.getGlobalPos()
+    scale               = cam2d.getPlaneScalingFactor PLANE_XY
+    console.log "scale (alt): " + @routeView.scale
+    console.log "scale: " + scale
+    @routeView.scale    = new THREE.Vector3(1/scale, 1/scale, 1/scale)
+    @routeView.position = new THREE.Vector3(-gPos[0]/scale, gPos[1]/scale, gPos[2]/scale+1)
+    #@routeView.position = new THREE.Vector3(@routeView.position.x - p[0], @routeView.position.y + p[1], cam2d.getGlobalPos()[2]+1)
     @routeView.geometry.verticesNeedUpdate = true


Default duration should be monthly

Default duration should be monthly

--- app/components/pricing-plan.coffee
+++ app/components/pricing-plan.coffee
@@ -6,7 +6,7 @@
 PricingPlanComponent = Ember.Component.extend

   pricing: null
-  paymentDuration: null
+  paymentDuration: ENUMS.PAYMENT_DURATION.MONTHLY
   showPricingModal: false
   classNames: ["column" , "is-one-third"]


Fixed Observer class not being exported.

--- lib/observer.coffee
+++ lib/observer.coffee
@@ -1,12 +1,13 @@
 # include event-kit to make use of CompositeDisposable
 {CompositeDisposable} = require 'event-kit'

+module.exports =
 class Observer
  constructor: () ->
-   @subscriptions = new CompositeDisposable
+   @subscriptions = new CompositeDisposable()

  addSubscription: (disposableSubscription) ->
    @subscriptions.add disposableSubscription

- destroy: ->
+ dispose: ->
    @subscriptions.dispose() # Dispose of all subscriptions at once


Fixes #11 by removing the general settings, and keeping only text.eml.basic

--- settings/language-eml.cson
+++ settings/language-eml.cson
@@ -1,12 +1,5 @@
-# NOTE: RFC2821 says each line MUST < 998 chars, and SHOULD < 78 characters, excluding the CRLF.
-'*':
+'text.eml.basic':
   editor:
-    invisibles: {}
+    # NOTE: RFC2821 line MUST < 998 chars, and SHOULD < 78 characters, excluding CRLF (2)(\.
     preferredLineLength: 76
     showInvisibles: true
-
-'text.eml.basic':
-  editor:
-    invisibles: {}
-    preferredLineLength: 76
-    showInvisibles: true


Fixed Exo font load string.

Fixed Exo font load string.

--- _assets/javascripts/fonts.js.coffee
+++ _assets/javascripts/fonts.js.coffee
@@ -1,5 +1,5 @@
 WebFont.load
   google:
     families: [
-      'Exo+2:700,400&subset=latin,cyrillic' # for Remodal
+      'Exo+2:700,400:latin,cyrillic' # for Remodal
     ]


Remove directives test stub

Remove directives test stub

--- spec/javascripts/unit/order_cycle_spec.js.coffee
+++ spec/javascripts/unit/order_cycle_spec.js.coffee
@@ -249,6 +249,3 @@
           {enterprise_id: "4", active: true}
           {enterprise_id: "6", active: true}
           ]
-
-
-describe 'OrderCycle directives', ->


Resolving typo

Resolving typo

--- auth/setup.coffee
+++ auth/setup.coffee
@@ -7,7 +7,7 @@
 Users = require('../data/Users')

 basicURL = 'http://www.knownodes.com/'
-# basicURL = 'http://localhost:3000/'
+#basicURL = 'http://localhost:3000/'

 findByEmail = (email, fn) ->
@@ -58,7 +58,7 @@

       process.nextTick ->
         profile.identifier = identifier
-        if profile.emails and profilecallback.emails.length > 0
+        if profile.emails and profile.emails.length > 0
           return findByEmail(profile.emails[0].value, (err, user) ->
             if err
               return DB.User.create(


Add support for various manual-page macros

--- grammars/roff.cson
+++ grammars/roff.cson
@@ -923,6 +923,16 @@
 	man:
 		patterns: [{

+			# Various macros that don't need special highlighting
+			name: "meta.function.${2:/downcase}.man.macro.roff"
+			begin: "^([.'])[ \t]*(RE|RS|SM|BT|PT)\\b"
+			end:   "(?<!\\\\)(?=$)|(?=\\\\\")"
+			beginCaptures:
+				1: {name: "punctuation.definition.macro.roff"}
+				2: {name: "entity.function.name.roff"}
+			patterns: [include: "#param-group"]
+		},{
+
 			# .TH: Title
 			name: "markup.heading.title.function.man.macro.roff"
 			patterns: [include: "#param-group"]
@@ -1011,6 +1021,36 @@
 				2: {name: "entity.function.name.roff"}
 				3: {name: "invalid.deprecated.roff"}
 			patterns: [include: "#param-group"]
+		},{
+
+			# .MT/.ME: Hyperlink (GNU extension)
+			name: "meta.function.mailto.hyperlink.man.macro.gnu.roff"
+			begin: "^([.'])[ \t]*(MT)\\b"
+			end:   "^([.'])[ \t]*(ME)\\b(.*)\\s*(\\\\\".*)?$"
+			beginCaptures:
+				1: {name: "punctuation.definition.macro.gnu.roff"}
+				2: {name: "entity.function.name.gnu.roff"}
+			endCaptures:
+				1: {name: "punctuation.definition.macro.gnu.roff"}
+				2: {name: "entity.function.name.gnu.roff"}
+				3: {patterns: [include: "#param-group"]}
+				4: {patterns: [include: "#escapes"]}
+			patterns: [include: "#underline-first"]
+		},{
+
+			# .UR/.UE: URL (GNU extension)
+			name: "meta.function.hyperlink.man.macro.gnu.roff"
+			begin: "^([.'])[ \t]*(UR)\\b"
+			end:   "^([.'])[ \t]*(UE)\\b(.*)\\s*(\\\\\".*)?$"
+			beginCaptures:
+				1: {name: "punctuation.definition.macro.gnu.roff"}
+				2: {name: "entity.function.name.gnu.roff"}
+			endCaptures:
+				1: {name: "punctuation.definition.macro.gnu.roff"}
+				2: {name: "entity.function.name.gnu.roff"}
+				3: {patterns: [include: "#param-group"]}
+				4: {patterns: [include: "#escapes"]}
+			patterns: [include: "#underline-first"]
 		},{

 			# .B/.SB: Bold


beaglebone.coffee: Set yocto version to rocko

Signed-off-by: Florin Sarbu <3a837a37a2ac9072a088641ec330e0e5d5b86f4e@balena.io>

--- beaglebone.coffee
+++ beaglebone.coffee
@@ -39,7 +39,7 @@
 		machine: 'beaglebone'
 		image: 'resin-image-flasher'
 		fstype: 'resinos-img'
-		version: 'yocto-pyro'
+		version: 'yocto-rocko'
 		deployArtifact: 'resin-image-flasher-beaglebone.resinos-img'
 		compressed: true


use standard b64-

use standard b64-

--- src/sct.iced
+++ src/sct.iced
@@ -4,6 +4,11 @@
 constants      = require './constants'
 sc             = require('./status').codes
 mm             = require('./mod').mgr
+
+##=======================================================================
+
+encode = (s) -> s.toString 'base64'
+decode = (b) -> new Buffer b, 'base64'

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

@@ -32,7 +37,7 @@
     err = null
     ret = null
     klass = cfg.klass or SelfCertifiedToken
-    if not (b = utils.base64u.decode(s))? or b.length is 0
+    if not (b = decode(s))? or b.length is 0
       err = "Failed to base64-decode"
     else if not ([e,x] = (utils.katch () -> unpack b))? or e?
       err = "Failed to purepack.unpack"
@@ -91,7 +96,7 @@

   _make_mac : (enc = null) ->
     msg = @_pack true
-    key = utils.base64u.decode @key
+    key = decode @key
     hm = crypto.createHmac 'sha512', key
     hm.update msg
     hm.digest()
@@ -120,12 +125,12 @@
   generate_to_client : (cb) ->
     await @generate defer err, ret
     if not err? and ret?
-      ret = utils.base64u.encode ret
+      ret = encode ret
     cb err, ret

   #-----------------------------------------

-  pack_to_client : () -> utils.base64u.encode @_pack false
+  pack_to_client : () -> encode @_pack false

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


landing: teams: added missing deps for a join form

--- client/landing/site.landing/coffee/team/forms/teamjoinwithinvitedaccountform.coffee
+++ client/landing/site.landing/coffee/team/forms/teamjoinwithinvitedaccountform.coffee
@@ -1,3 +1,5 @@
+kd              = require 'kd.js'
+utils           = require './../../core/utils'
 JView           = require './../../core/jview'
 LoginInputView  = require './../../login/logininputview'
 TeamJoinTabForm = require './../forms/teamjointabform'


add locals middleware; simplify globals

add locals middleware; simplify globals

--- app/express.coffee
+++ app/express.coffee
@@ -11,10 +11,16 @@
 app = module.exports = express()

 # Globals middleware: Extends locals with given properties
-globals = ( extensions, processor ) ->
+globals = (extensions) ->
   globalsFunc = (req, res, next) ->
-    res.locals( extensions )
-    processor( extensions, req, res ) if processor
+    res.locals extensions
+    next()
+
+# Locals middleware: Extends locals dynamically
+locals = (extensions) ->
+  localsFunc = (req, res, next) ->
+    vals = if _.isFunction( extensions ) then extensions(req, res) else extensions
+    res.locals vals
     next()

 # Config
@@ -31,13 +37,12 @@
     app.use express.methodOverride()
     app.use express.cookieSession()
     app.use flash()
-    app.use globals {siteName: config.siteName, title: ''}, (ext, req, res) ->
-      errors = req.flash 'errors'
-      messages = req.flash 'messages'
-      res.locals
-        errors: errors
-        messages: messages
+    app.use globals siteName: config.siteName, title: ''
+    app.use locals (req, res) ->
+      return extensions =
         username: req.session.username or ''
+        errors:   req.flash 'errors'
+        messages: req.flash 'messages'
     app.locals.pretty = true
     app.use middleware.reply
       processor: (opts, req) ->


fix https://github.com/sagemathinc/smc/issues/477 -- password reset error message reporting

--- src/smc-webapp/redux_account.coffee
+++ src/smc-webapp/redux_account.coffee
@@ -88,10 +88,10 @@
         salvus_client.forgot_password
             email_address : email
             cb : (err, mesg) =>
+                if mesg?.error
+                    err = mesg.error
                 if err?
-                    @setState('forgot_password_error': "Error sending password reset message to #{email} (#{err}); write to #{help()} for help.")
-                else if mesg.err
-                    @setState('forgot_password_error': "Error sending password reset message to #{email} (#{err}); write to #{help()} for help.")
+                    @setState('forgot_password_error': "Error sending password reset message to #{email} -- #{err}. Write to #{help()} for help.")
                 else
                     @setState('forgot_password_success': "Password reset message sent to #{email}; if you don't receive it, check your spam folder; if you have further trouble, write to #{help()}.")


Cleaned up error reporting

Cleaned up error reporting

--- lib/ot.coffee
+++ lib/ot.coffee
@@ -12,13 +12,15 @@
 # This is the super apply function that takes in snapshot data (including the type) and edits it in-place.
 # Returns an error string or null for success.
 exports.apply = (data, opData) ->
+  console.log 'apply', data, opData
   return 'Missing data' unless typeof opData is 'object'
   return 'Missing op' unless typeof (opData.op or opData.create) is 'object' or opData.del is true

-  if !data.type
+  if opData.create
+    return 'Document already exists' if data.type
+
     # The document doesn't exist, although it might have once existed. Here we will only allow creates.
     create = opData.create
-    return 'Document does not exist' unless typeof create is 'object'

     type = otTypes[create.type]
     return "Type not found" unless type
@@ -35,6 +37,7 @@
     data.v++

   else
+    return 'Document does not exist' unless data.type
     # Apply the op data
     op = opData.op
     return 'Missing op' unless typeof op is 'object'


improvement: fix double quotes in test according to jshint

Signed-off-by: Dominik Richter <b7323add517e187efe0283b76511b7ed762bfe13@gmail.com>

--- test/spec/controllers/main.coffee
+++ test/spec/controllers/main.coffee
@@ -3,7 +3,7 @@
 describe 'Controller: MainCtrl', ()->

   # load the required module
-  beforeEach(angular.mock.module("restangular"))
+  beforeEach(angular.mock.module('restangular'))

   # load the controller's module
   beforeEach(module('postriderApp'))
@@ -11,8 +11,8 @@
   # Initialize the controller and a mock scope
   beforeEach( inject( ($injector, $controller, $rootScope) ->
     scope = $rootScope.$new()
-    @Restangular = $injector.get("Restangular")
-    @httpBackend = $injector.get("$httpBackend")
+    @Restangular = $injector.get('Restangular')
+    @httpBackend = $injector.get('$httpBackend')

     # reliably determine object types
     # http://stackoverflow.com/questions/7390426/better-way-to-get-type-of-a-javascript-variable
@@ -70,9 +70,9 @@
     @httpBackend.whenGET('/v1/node/'+id).respond({
       'packages':[
         {
-          "id": "poiu",
-          "name": "accountsservice",
-          "summary": "query and manipulate user account information"
+          'id': 'poiu',
+          'name': 'accountsservice',
+          'summary': 'query and manipulate user account information'
         }
       ]
       })
@@ -122,14 +122,14 @@
   it 'should be able to access /package/xyz info (filled one)', () ->
     id = 'xyz'
     r = {
-        "name": "accountsservice",
-        "uri": "http://us.archive.ubuntu.com/ubuntu/pool/main/a/accountsservice/accountsservice_0.6.15-2ubuntu9_amd64.deb",
-        "summary": "query and manipulate user account information",
-        "version": "0.6.15-2ubuntu9",
-        "architecture": "amd64",
-        "provider": "apt",
-        "archive": "precise",
-        "nodes": [
+        'name': 'accountsservice',
+        'uri': 'http://us.archive.ubuntu.com/ubuntu/pool/main/a/accountsservice/accountsservice_0.6.15-2ubuntu9_amd64.deb',
+        'summary': 'query and manipulate user account information',
+        'version': '0.6.15-2ubuntu9',
+        'architecture': 'amd64',
+        'provider': 'apt',
+        'archive': 'precise',
+        'nodes': [
           'my1.full.fqdn'
         ]
       }


update test for new default host configuration

Signed-off-by: Dominik Richter <b7323add517e187efe0283b76511b7ed762bfe13@gmail.com>

--- test/spec/controllers/main.coffee
+++ test/spec/controllers/main.coffee
@@ -81,7 +81,7 @@
   #-----------

   it 'should have no default host configured', () ->
-    expect(scope.ponyExpressHost).toBe(undefined)
+    expect(scope.ponyExpressHost).toBe( window.location.host + "/api" )

   paginateResponse = (httpBackend, baseUrl, response, action, limit=50)->
     # 1. working pagination


fix a static path issue.

fix a static path issue.

--- test/src/process_argv_test.coffee
+++ test/src/process_argv_test.coffee
@@ -1,5 +1,6 @@
 _ = require 'underscore'
 sinon = require 'sinon'
+path = require 'path'

 processArgv = require '../../lib/process.argv.js'

@@ -140,7 +141,7 @@
       '/Users/tw/Projects/viff/lib/index.js',
       '-envs',
       'build=http://localhost:4000,prod=http://ishouldbeageek.me',
-      '/Users/tw/Projects/viff/test/src/test.config.js',
+      path.join(__dirname, '../src/test.config.js'),
       '-paths',
       '/404.html',
       '-selenium-host',


Fixed up some UI methods

Fixed up some UI methods

--- lib/ui.coffee
+++ lib/ui.coffee
@@ -34,16 +34,16 @@

   encode: (input) ->
     bytes = (x) ->
+      output = []
       if typeof x is 'string'
-        x.split('').map (c) =>
+        output = x.split('').map (c) =>
           return c.charCodeAt(0)

       else if Array.isArray(x)
-        output = []
         input.forEach (part) ->
           output.push bytes(part)

-        return output
+      return output

     return new Buffer([0x1b].concat bytes(input))

@@ -65,12 +65,12 @@
     return @

   color: (fore, back) ->
-    @set '[' + @codes.colors[fore].fore + ';' + @codes.colors[back].back + 'm'
+    @set '[' + (if fore? and back? then (@codes.colors[fore].fore + ';' + @codes.colors[back].back) else 0) + 'm'
     return @

   format: (code) ->
     code = @codes.format[code]
-    @set('[' + code + 'm')
+    @set('[' + (if code? then code else 0) + 'm')
     return @

   erase: ->
@@ -95,27 +95,27 @@
   # Cursor actions
   up: (num) ->
     if typeof num is 'number'
-      @set '[' +num+ 'A'
+      @set '[' + (if num? then num else 1) + 'A'
     return @

   down: (num) ->
     if typeof num is 'number'
-      @set '[' +num+ 'B'
+      @set '[' + (if num? then num else 1) + 'B'
     return @

   right: (num) ->
     if typeof num is 'number'
-      @set '[' +num+ 'C'
+      @set '[' + (if num? then num else 1) + 'C'
     return @

   left: (num) ->
     if typeof num is 'number'
-      @set '[' +num+ 'D'
+      @set '[' + (if num? then num else 1) + 'D'
     return @

   pos: (x, y) ->
     if typeof x is 'number' and typeof y is 'number'
-      @set '[' + x + ';' + y + 'H'
+      @set '[' + (if x? then x else 0) + ';' + (if y? then y else 0) + 'H'
     return @

   fillLine: ->


增加__img_demo这个变量

--- honey/index.coffee
+++ honey/index.coffee
@@ -91,6 +91,8 @@
     __pub_tmpl_widget: "#{pubTemplate}widget/"
     #全局组件的css
     __pub_css_comp: if isProduction then pubCssCompProduction else "/imgotv-pub/css/component/"
+    #demo图片的地址
+    __img_demo: if isProduction then "http://image-demo.lab.hunantv.com/#{project_name}/" else "/image-demo/"

   #把变量加到global中去
   jsonData = silky.data.json


Why add another function that doesn't do anything?

--- start.coffee
+++ start.coffee
@@ -204,8 +204,7 @@

 		return cb null

-	, ( cb ) ->
-		update_database cb
+	, update_database

 	, ( cb ) ->
 		log "Starting the web server.."


Simple list to show available domains in the form

--- client/landing/app/Applications/Environments.kdapplication/views/domains/domaincreationform.coffee
+++ client/landing/app/Applications/Environments.kdapplication/views/domains/domaincreationform.coffee
@@ -204,9 +204,33 @@
       placeholder : "Type your awesome domain..."
       createButtonAnimated : yes
     , data
+
+    @domainList = null
+
   viewAppended:->
     tldList = []
     KD.remote.api.JDomain.getTldList (tlds)=>
       for tld in tlds
         tldList.push {title:".#{tld}", value: tld}
-      @inputs.domains.setSelectOptions tldList+      @inputs.domains.setSelectOptions tldList
+
+  setAvailableDomainsData:(domains)->
+    @domainList?.destroy?()
+
+    @domainList = new KDView cssClass:'domain-list'
+    @addSubView @domainList
+
+    for domain in domains
+      @domainList.addSubView new DomainBuyItem {}, domain
+
+class DomainBuyItem extends JView
+
+  constructor:(options={}, data)->
+    options.cssClass = KD.utils.curry "domain-buy-items", options.cssClass
+    super options, data
+
+  pistachio:->
+    """
+      {h1#domain{#(domain)}}
+      {{#(price)}}
+    """


Replace `Spree.url().clone()` by `new URL()`

--- app/assets/javascripts/admin/spree/taxons/taxonomy.js.coffee
+++ app/assets/javascripts/admin/spree/taxons/taxonomy.js.coffee
@@ -8,7 +8,7 @@
   node = data.rslt.o
   new_parent = data.rslt.np

-  url = Spree.url(base_url).clone()
+  url = new URL(base_url)
   url.pathname = url.pathname + '/' + node.attr("id")
   $.ajax
     type: "POST",
@@ -40,7 +40,7 @@
   node = data.rslt.obj
   name = data.rslt.new_name

-  url = Spree.url(base_url).clone()
+  url = new URL(base_url)
   url.pathname = url.pathname + '/' + node.attr("id")

   $.ajax
@@ -53,8 +53,8 @@
 handle_delete = (e, data) ->
   last_rollback = data.rlbk
   node = data.rslt.obj
-  delete_url = base_url.clone()
-  delete_url.setPath delete_url.path() + '/' + node.attr("id")
+  delete_url = new URL(base_url)
+  delete_url.pathname = delete_url.pathname + '/' + node.attr("id")
   if confirm(Spree.translations.are_you_sure_delete)
     $.ajax
       type: "POST",


remove useless toggleFavorite method

remove useless toggleFavorite method

--- app/assets/javascripts/backbone/models/proposition.js.coffee
+++ app/assets/javascripts/backbone/models/proposition.js.coffee
@@ -61,16 +61,6 @@
           embeds = model.get 'embeds'
           model.set embeds: _.reject(embeds, (e) -> e.id == embedId)

-  toggleFavorite: ->
-    isInCurrentUserFavorites = _.find @get('favorite_users').data,((u) -> u.id is app.models.user.id)
-    $.ajax
-      type: if isInCurrentUserFavorites then 'DELETE' else 'POST'
-      url: "#{@url()}/favorite"
-      data:
-        auth_token: app.models.user.token()
-      success: () =>
-        @trigger('change:favorite_users')
-
   toggleUserAction: (action) ->
     console.log @
     alreadyUserActioned = _.find @get("#{action}_users").data,((u) -> u.id is app.models.user.id)


Don't display label: false columns in colvis

--- app/assets/javascripts/effective_datatables/events.js.coffee
+++ app/assets/javascripts/effective_datatables/events.js.coffee
@@ -11,3 +11,10 @@
       $.event.trigger('page:change')
     , 700)
   )
+
+# Remove empty label (bulk actions) from ColVis dropdown
+$(document).on 'click.dtb-collection', (event) ->
+  $colvis = $('.dt-button-collection:not(.initialized)')
+  return if $colvis.length == 0
+
+  $colvis.addClass('initialized').find('li > a:empty').each -> $(this).parent().remove()


run the select2 initializer any time cocoon:after-insert is fired

--- app/assets/javascripts/tenon/features/cocoon_hooks.js.coffee
+++ app/assets/javascripts/tenon/features/cocoon_hooks.js.coffee
@@ -5,7 +5,8 @@
   afterInsert: (e, insertedItem) =>
     @$insertedItem = $(insertedItem)
     @_checkTenonContent()
+    $('select').select2()

   _checkTenonContent: ->
     if @$insertedItem.find('.tn-tc')
-      new Tenon.features.tenonContent.Base+      new Tenon.features.tenonContent.Base


add confirmation if changing bookshelf with unsaved changes

--- scripts/gh-book/auth.coffee
+++ scripts/gh-book/auth.coffee
@@ -26,6 +26,7 @@
       'submit #edit-repo-form': 'editRepo'
       'click [data-select-repo]': 'selectRepo'
       'click [data-create-repo]': 'createRepo'
+      'shown #edit-repo-modal': 'createRepoModal'

     initialize: () ->
       # When a model has changed (triggered `dirty`) update the Save button
@@ -188,6 +189,11 @@
       promise.done () =>
         @isDirty = false
         @render()
+
+    createRepoModal: (e) ->
+      if @isDirty and !confirm 'You have unsaved changes in this bookshelf, are you sure you want to abandon them?'
+        $('#edit-repo-modal').modal 'hide'
+

     createRepo: (e) ->
       e.preventDefault()


add upcoming list of events, add comments about nextplex event script

--- scripts/nextplex-phx.coffee
+++ scripts/nextplex-phx.coffee
@@ -1,14 +1,52 @@
+# Description:
+#   Displays upcoming tech events in Phoenix
+#
+# Dependencies:
+#   "cheerio": "~0.12.0"
+#
+# Commands:
+#   techevents today - Shows list of events today, with links
+#   techevents upcoming <count> - Shows upcoming events (default 8)
+#
+# Author:
+#   @w33ble
+
 cheerio = require 'cheerio'
 site = "http://nextplex.com"

 module.exports = (robot) ->
+
   robot.respond /techevents\stoday/, (msg) ->
     get_events_today msg
+  robot.respond /techevents\supcoming?\s*(\d+)?/, (msg) ->
+    count = msg.match[1] || 8
+    get_events_upcoming msg, count
+
+fetch_site = (msg, cb) ->
+  msg.http("#{site}/phoenix-az/calendar")
+    .get() cb
+
+get_events_upcoming = (msg, count) ->
+  fetch_site msg, (err, res, body) ->
+    evts = []
+    $ = cheerio.load body
+    events = $('#event-listing').find('.date-and-name')
+    if count <= events.length
+      events.length = count
+    events.each (i, el) ->
+      el = $(el)
+      month = el.find('time .day').html()
+      date = el.find('time .date').html()
+      time = $(el).find('.event-name p').html().trim().match( /([0-9\:]+[a|p]m)/i )[0]
+      link = $(el).find('.event-name a')
+      title = link.html()
+      link = "#{site}#{link.attr('href')}"
+      msg.send "#{month} #{date} @ #{time} - #{title} [#{link}]"

 get_events_today = (msg) ->
-  msg.http("#{site}/phoenix-az/calendar")
-    .get() (err, res, body) ->
-      $ = cheerio.load body
-      events = $('.today-cell', '#event_calendar').find('a')
-      events.each (i, el) ->
-        msg.send "#{$(el).html()} [#{site}#{$(el).attr('href')}]"
+  fetch_site msg, (err, res, body) ->
+    evts = []
+    $ = cheerio.load body
+    events = $('.today-cell', '#event_calendar').find('a')
+    events.each (i, el) ->
+      msg.send "#{$(el).html()} [#{site}#{$(el).attr('href')}]"


Default names for template VDIs

Default names for template VDIs

--- app/modules/new-vm/index.coffee
+++ app/modules/new-vm/index.coffee
@@ -304,6 +304,10 @@
           $scope.installation_method = 'network'

       VDIs = $scope.VDIs = cloneDeep template.template_info.disks
+      forEach VDIs, (vdi, index) ->
+        vdi.name_label = $scope.name_label + '_disk' + index
+        vdi.name_description = 'Created by XO'
+
       # if the template has no config disk
       # nor it's Other install media (specific case)
       if VDIs.length is 0 and template.name_label isnt 'Other install media'


Filter the upcoming subject queue for duplicates (#3770)

* Filter the upcoming subject queue for duplicates

* Add a comment explaining the array filter

--- app/pages/project/classify.cjsx
+++ app/pages/project/classify.cjsx
@@ -187,6 +187,9 @@
           filteredSubjects = nonLoadedSubjects.filter((subject) => !subject.already_seen && !subject.retired)
           subjectsToLoad = if filteredSubjects.length > 0 then filteredSubjects else nonLoadedSubjects
           upcomingSubjects.forWorkflow[workflow.id].push subjectsToLoad...
+          # Remove any duplicate subjects from the upcoming queue
+          upcomingSubjects.forWorkflow[workflow.id].filter (subject, i) =>
+            upcomingSubjects.forWorkflow[workflow.id].indexOf subject is i

       # If we're filling this list for the first time, we won't have a subject selected, so try again.
       subject ?= fetchSubjects.then ->