ComputeController: add ::start method

ComputeController: add ::start method

--- client/Main/providers/computecontroller.coffee
+++ client/Main/providers/computecontroller.coffee
@@ -101,8 +101,12 @@

   info: (machine)->
+  start: (machine)->

     log "[info]", machine
+    @eventListener.triggerState machine,
+      status      : Machine.State.Starting
+      percentage  : 0

     # If machine state is NotInitialized or Terminated
     # we don't need to ask info to kloud kite.
@@ -110,10 +114,20 @@
     if machine.status.state in [ NotInitialized, Terminated ]
       @emit "public-#{machine._id}", { status: machine.status.state }
       return Promise.resolve()
+    @kloud.start { machineId: machine._id }

     if machine.status.state is Building
       log "state is building, start polling"
       @eventListener.addListener 'build', machine._id
+    .then (res)=>
+
+      @eventListener.addListener 'start', machine._id
+      log "start res:", res
+
+    .catch (err)=>
+
+      warn "start err:", err
+
       return Promise.resolve()


sidebar: change state key for all templates

Signed-off-by: Umut Sirin <4055abbc0e40a1254e715b952ccd414607e0b365@gmail.com>

--- client/app/lib/components/sidebar/index.coffee
+++ client/app/lib/components/sidebar/index.coffee
@@ -48,7 +48,7 @@
       activeLeavingSharedMachineId : EnvironmentFlux.getters.activeLeavingSharedMachineId
       requiredInvitationMachine    : EnvironmentFlux.getters.requiredInvitationMachine
       differentStackResourcesStore : EnvironmentFlux.getters.differentStackResourcesStore
-      allStackTemplates            : EnvironmentFlux.getters.allStackTemplates
+      templates                    : EnvironmentFlux.getters.allStackTemplates
       team                         : TeamFlux.getters.team
       selectedTemplateId           : EnvironmentFlux.getters.selectedTemplateId
     }
@@ -190,7 +190,7 @@

   renderStack: (stack) ->

-    template = @state.allStackTemplates.get stack.get 'baseStackId'
+    template = @state.templates.get stack.get 'baseStackId'

     <SidebarStackSection
       key={stack.get '_id'}


environment/flux: add remove template action

--- client/app/lib/flux/environment/actions.coffee
+++ client/app/lib/flux/environment/actions.coffee
@@ -17,6 +17,7 @@
 providersParser = require 'stacks/views/stacks/providersparser'
 requirementsParser = require 'stacks/views/stacks/requirementsparser'
 { jsonToYaml } = require 'stacks/views/stacks/yamlutils'
+Tracker = require 'app/util/tracker'

 _eventsCache = { machine: {}, stack: no }

@@ -608,6 +609,29 @@

       reactor.dispatch actions.GENERATE_STACK_SUCCESS, { template, stack }
       resolve { template, stack }
+
+
+removeStackTemplate = (template) ->
+
+  { reactor, groupsController } = kd.singletons
+
+  currentGroup = groupsController.getCurrentGroup()
+
+  return new Promise (resolve, reject) ->
+    reactor.dispatch actions.REMOVE_STACK_TEMPLATE_BEGIN, { template }
+    template.delete (err) ->
+      if err
+        reactor.dispatch actions.REMOVE_STACK_TEMPLATE_FAIL, { template, err }
+        reject err
+        return
+
+      if template.accessLevel is 'group'
+        currentGroup.sendNotification 'GroupStackTemplateRemoved', template._id
+
+      reactor.dispatch actions.REMOVE_STACK_TEMPLATE_SUCCESS, { template }
+      resolve()
+
+      Tracker.track Tracker.STACKS_DELETE_TEMPLATE

 deleteStack = (stackTemplateId) ->
@@ -717,6 +741,7 @@
   updateStackTemplate
   generateStack
   generateStackFromTemplate
+  removeStackTemplate
   deleteStack
   changeTemplateTitle
   loadMachineSharedUsers


ComputeStorage: make sure all the machines in stack document are valid

--- client/app/lib/providers/computestorage.coffee
+++ client/app/lib/providers/computestorage.coffee
@@ -47,11 +47,12 @@
           stack = remote.revive stack  unless stack.ಠ_ಠ
           stack.title = Encoder.htmlDecode stack.title
           stack.machines = stack.machines
+            .filter Boolean
             .map (machine) =>
               machine = if machine.bongo_
               then machine
               else @get 'machines', '_id', machine
-              machine._stackId = stack._id
+              machine._stackId = stack._id  if machine
               return machine

             .filter (machine) ->


ComputeStorage: generic deleteInstance handler for all, fixes #10884

--- client/app/lib/providers/computestorage.coffee
+++ client/app/lib/providers/computestorage.coffee
@@ -156,6 +156,10 @@
     return null

+  _deleteHandler: (type, value) ->
+    => @pop type, value._id
+
+
   push: (type, value) ->

     debug 'push', type, value
@@ -183,6 +187,8 @@
       value = cached
     else
       @storage[type].push value
+      value.off? 'deleteInstance', @_deleteHandler type, value
+      value.on?  'deleteInstance', @_deleteHandler type, value

     postPush?.call this, value


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/function_extensions.coffee
+++ src/function_extensions.coffee
@@ -6,7 +6,7 @@
     return memo if ran
     ran = true

-    return memo = this.apply(arguments)
+    return memo = @apply(arguments)

 Function::withBefore = (interception) ->
   method = this


add medley levelup count

add medley levelup count

--- src/hubot-scfes-command.coffee
+++ src/hubot-scfes-command.coffee
@@ -55,3 +55,14 @@
     difficulty = msg.match[3]

     msg.reply "next levelup is " + scfes.getNextLevelupCount(now_exp, difficulty)
+
+  robot.respond /scfes levelup count medley( expup)? (\d+)( (\w+))?( (\d+))?/, (msg) ->
+    arrange = false
+    arrange = true if msg.match[1]
+
+    now_exp = parseInt msg.match[2]
+    difficulty = msg.match[4]
+    times =  parseInt msg.match[6]
+    times = 3 unless times
+
+    msg.reply "next levelup is " + scfes.getNextLevelupCount(now_exp, difficulty, times, arrange)


Remove those, we are using backoff

Remove those, we are using backoff

--- client/landing/app/MainApp/kite/newkite.coffee
+++ client/landing/app/MainApp/kite/newkite.coffee
@@ -140,9 +140,6 @@
       callback scrubbed

   tell:(options, callback) ->
-    if not @addr
-      @getKiteAddr()
-
     @ready =>
       # token is needed to initiate a valid session
       # TODO: invalidate token when something goes wrong, or if we got a new token from kontrol


Landing: disable single-sign-on to gitlab feature on production

--- client/landing/site.landing/coffee/main.coffee
+++ client/landing/site.landing/coffee/main.coffee
@@ -30,7 +30,7 @@
       kd.config.group = group
       # add Gitlab Config into kd.config if it's enabled for this team
       gitlab = window._runtimeOptions.gitlab ? {}
-      if gitlab.team is group.slug
+      if gitlab.team is group.slug and kd.config.environment isnt 'production'
         kd.config.gitlab = gitlab

     # BIG BANG


ignore errors from our shitty unsupported experimental router

--- client/shared/helpers/exception_handler.coffee
+++ client/shared/helpers/exception_handler.coffee
@@ -21,6 +21,7 @@
       'Script error.'
       'miscellaneous_bindings'
       'canonicalUrl'
+      'TypeError: s.outlets[Object.keys(...)[0]]'
     ]
     ignoreUrls: [
       /graph\.facebook\.com/i


uhhh i have added Things to atom

uhhh i have added Things to atom

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -1,4 +1,21 @@
 "*":
+  "atom-beautify":
+    crystal:
+      beautify_on_save: true
+    css:
+      beautify_on_save: true
+    html:
+      beautify_on_save: true
+    js:
+      disabled: true
+    jsx:
+      disabled: true
+    markdown:
+      beautify_on_save: true
+    nginx:
+      beautify_on_save: true
+    ruby:
+      default_beautifier: "Ruby Beautify"
   core:
     disabledPackages: [
       "background-tips"
@@ -38,6 +55,8 @@
       "text.git-commit"
       "text.html.basic"
     ]
+  stylefmt:
+    formatOnSave: true
   welcome:
     showOnStartup: false
 ".js.jsx.react.source":


ComponentLoader: Don't fail on missing getIcon function

This can happen when loaded component is using an older version of NoFlo
which does not define the getIcon method.

--- src/lib/ComponentLoader.coffee
+++ src/lib/ComponentLoader.coffee
@@ -121,7 +121,7 @@

   setIcon: (name, instance) ->
     # See if component has an icon
-    return if instance.getIcon()
+    return if not instance.getIcon or instance.getIcon()

     # See if library has an icon
     [library, componentName] = name.split '/'


Code refactor

Code refactor

--- tooltips.coffee
+++ tooltips.coffee
@@ -33,8 +33,41 @@
 hideTooltip = ->
 	setTooltip false

-_showTooltip = (e, $el) ->
-	$el = $el or $(@)
+toggleTooltip = ->
+	if getTooltip().text then hideTooltip() else showTooltip null, $(this)
+
+positionTooltip = ($el) ->
+	direction = $el.data('tooltip-direction') or 'n'
+	$tooltip = $(".tooltip")
+
+	position = $el.offset()
+	offLeft = $el.data('tooltip-left')
+	offTop = $el.data('tooltip-top')
+
+	if _.isUndefined(offLeft)
+		offLeft = 0
+	else
+		hasOffsetLeft = true
+
+	if _.isUndefined(offTop)
+		offTop = 0
+	else
+		hasOffsetTop = true
+
+	position.top = switch direction
+		when 'w', 'e' then (center vertically $tooltip, $el) + offTop
+		when 'n' then position.top - $tooltip.outerHeight() - (if hasOffsetTop then offTop else offset[1])
+		when 's' then position.top + $el.outerHeight() + (if hasOffsetTop then offTop else offset[1])
+
+	position.left = switch direction
+		when 'n', 's' then (center horizontally $tooltip, $el) + offLeft
+		when 'w' then position.left - $tooltip.outerWidth() - (if hasOffsetLeft then offLeft else offset[0])
+		when 'e' then position.left + $el.outerWidth() + (if hasOffsetLeft then offLeft else offset[0])
+
+	setPosition(position, direction)
+
+showTooltip = (evt, $el) ->
+	$el = $el or $(this)
 	viewport = $el.data 'tooltip-disable'

 	if viewport and _.isString(viewport)
@@ -47,45 +80,11 @@
 	else
 		$el.data('tooltip')

-	setTooltip content
+	setTooltip(content)
 	setPosition(top: 0, left: 0)

-	Tracker.afterFlush ->
-		direction = $el.data('tooltip-direction') or 'n'
-		$tooltip = $(".tooltip")
+	Tracker.afterFlush -> positionTooltip($el)

-		position = $el.offset()
-		offLeft = $el.data('tooltip-left')
-		offTop = $el.data('tooltip-top')
-
-		if _.isUndefined(offLeft)
-			offLeft = 0
-		else
-			hasOffsetLeft = true
-
-		if _.isUndefined(offTop)
-			offTop = 0
-		else
-			hasOffsetTop = true
-
-		position.top = switch direction
-			when 'w', 'e' then (center vertically $tooltip, $el) + offTop
-			when 'n' then position.top - $tooltip.outerHeight() - (if hasOffsetTop then offTop else offset[1])
-			when 's' then position.top + $el.outerHeight() + (if hasOffsetTop then offTop else offset[1])
-
-		position.left = switch direction
-			when 'n', 's' then (center horizontally $tooltip, $el) + offLeft
-			when 'w' then position.left - $tooltip.outerWidth() - (if hasOffsetLeft then offLeft else offset[0])
-			when 'e' then position.left + $el.outerWidth() + (if hasOffsetLeft then offLeft else offset[0])
-
-		setPosition(position, direction)
-
-
-_toggleTooltip = ->
-	if getTooltip().text
-		hideTooltip()
-	else
-		_showTooltip null, $(@)

 # Positioning

@@ -160,11 +159,13 @@

 Meteor.startup ->

-	$(document).on 'mouseover', '[data-tooltip]:not([data-tooltip-trigger]), [data-tooltip-element]:not([data-tooltip-trigger]), [data-tooltip-trigger="hover"]', _showTooltip
+	$(document).on 'mouseover', '[data-tooltip]:not([data-tooltip-trigger]), [data-tooltip-element]:not([data-tooltip-trigger]), [data-tooltip-trigger="hover"]', showTooltip
+
 	$(document).on 'mouseout', '[data-tooltip]:not([data-tooltip-trigger]), [data-tooltip-element]:not([data-tooltip-trigger]), [data-tooltip-trigger="hover"]', hideTooltip
-	$(document).on 'click', '[data-tooltip-trigger="click"]', _toggleTooltip
-	$(document).on 'focus', '[data-tooltip-trigger="focus"]', _showTooltip
+
+	$(document).on 'click', '[data-tooltip-trigger="click"]', toggleTooltip
+	$(document).on 'focus', '[data-tooltip-trigger="focus"]', showTooltip
 	$(document).on 'blur', '[data-tooltip-trigger="focus"]', hideTooltip
-	$(document).on 'tooltips:show', '[data-tooltip-trigger="manual"]', _showTooltip
+	$(document).on 'tooltips:show', '[data-tooltip-trigger="manual"]', showTooltip
 	$(document).on 'tooltips:hide', '[data-tooltip-trigger="manual"]', hideTooltip
-	$(document).on 'tooltips:toggle', '[data-tooltip-trigger="manual"]', _toggleTooltip
+	$(document).on 'tooltips:toggle', '[data-tooltip-trigger="manual"]', toggleTooltip


Add tests for remote posts with invalid dates

--- test/post.coffee
+++ test/post.coffee
@@ -418,7 +418,15 @@
                     .c("items", node: "/user/sisko@ds9.sf/posts")
                     .c("item", id: "test-C-8")
                     .cnode(server.makeAtom(content: "Test post C8", author_uri: "acct:sisko@ds9.sf", id: "test-C-8")
-                        .remove("id"))
+                        .remove("id")),
+                server.makePubsubEventMessage("buddycloud.ds9.sf", "buddycloud.example.org")
+                    .c("items", node: "/user/sisko@ds9.sf/posts")
+                    .c("item", id: "test-C-9")
+                    .cnode(server.makeAtom(content: "Test post C9", author_uri: "acct:sisko@ds9.sf", id: "test-C-9", published: "not a date")),
+                server.makePubsubEventMessage("buddycloud.ds9.sf", "buddycloud.example.org")
+                    .c("items", node: "/user/sisko@ds9.sf/posts")
+                    .c("item", id: "test-C-10")
+                    .cnode(server.makeAtom(content: "Test post C10", author_uri: "acct:sisko@ds9.sf", id: "test-C-10", updated: "not a date")),
             ]
             async.series [(cb) ->
                 for msgEl in msgs
@@ -426,7 +434,7 @@
                 setTimeout cb, 500

             , (cb) ->
-                async.forEach [4..8], (i, cb2) ->
+                async.forEach [4..10], (i, cb2) ->
                     iq = server.makePubsubGetIq("picard@enterprise.sf", "buddycloud.example.org", "retrieve-C-#{i}")
                         .c("items", node: "/user/sisko@ds9.sf/posts")
                         .c("item", id: "test-C-#{i}")


Remove the loading overlay from video

Remove the loading overlay from video

--- app/components/frame-viewer.cjsx
+++ app/components/frame-viewer.cjsx
@@ -44,10 +44,6 @@
         </div>
       when 'video'
         <VideoPlayer src={src} type={type} format={format} frame={@props.frame} onLoad={@handleLoad}>
-        {if @state.loading
-          <div className="loading-cover" style={@constructor.overlayStyle}>
-            <LoadingIndicator />
-          </div>}
         </VideoPlayer>

     if FrameWrapper


Link directly to external projects from stats page

--- app/pages/profile/stats.cjsx
+++ app/pages/profile/stats.cjsx
@@ -33,16 +33,20 @@
         @setState {avatar}

   render: ->
-    [owner, name] = @props.project.slug.split('/')
-    <Link to="project-home" params={owner: owner, name: name} className="stats-project-icon">
-
-

+    content = [
+
+

         {@state.owner?.display_name}

         {@props.project.display_name}

-      {if @props.badge
-
{@props.badge}
}
-    </Link>
+
{@props.badge}
 if @props.badge
+    ]
+
+    if !!@props.project.redirect
+      {content}
+    else
+      [owner, name] = @props.project.slug.split '/'
+      <Link to="project-home" params={owner: owner, name: name} className="stats-project-icon">{content}</Link>

 module.exports = React.createClass
   getDefaultProps: ->


bugfix where options were not correctly calculated

--- wrapper.coffee
+++ wrapper.coffee
@@ -8,7 +8,7 @@
 minimist = require 'minimist'
 bundle   = require './bundle'

-options = extend {}, {
+options = extend true, {
   name:
     infix: '-'
     extension: '.js'
@@ -34,7 +34,7 @@
       .pipe(plumber())
   dest: (params...) ->
     if params.length is 0 or typeof params[0] isnt 'string'
-      params.unshift @dirs.build
+      params.unshift @dirs.build + '/'
     gulp.dest.call @, params...
   env: minimist process.argv.slice 2
 }


tests for new project command looking good

--- test/test.coffee
+++ test/test.coffee
@@ -1,6 +1,7 @@
 should = require 'should'
 path = require 'path'
 fs = require 'fs'
+rimraf = require 'rimraf'
 colors = require 'colors'
 run = require('child_process').exec
 root = path.join process.cwd(), 'test'
@@ -15,7 +16,7 @@
   describe 'compile', ->

     before (done) ->
-      run "cd #{cli_root}; roots compile", (a,b,c) -> done()
+      run "cd #{cli_root}; ../../bin/roots compile", (a,b,c) -> done()

     it 'should compile files to /public', ->
       fs.readdirSync(path.join(cli_root, 'public')).should.have.lengthOf(5)
@@ -25,14 +26,56 @@
       js_content.should.not.match /\n/

     it 'should compile all files to public', ->
-      css_content = fs.readFileSync(path.join(cli_root, 'public/css/example.css'), 'utf8')
+      css_content = fs.readFileSync path.join(cli_root, 'public/css/example.css'), 'utf8'
       css_content.should.not.match /\n/

   describe 'new', ->
-    it 'should create a new folder in the current directory with the specified name'
-    it 'should use express template if the --express flag is present'
-    it 'should use basic template if the --basic flag is present'
-    it 'should create all the necessary files'
+    it 'should create a new folder in the current directory with the right name and files', (done) ->
+      run "cd #{root}; ../bin/roots new testproj", ->
+        test_path = path.join(root, 'testproj')
+        fs.existsSync(test_path).should.be.ok
+        fs.existsSync(path.join(test_path, 'app.coffee')).should.be.ok
+        fs.existsSync(path.join(test_path, 'readme.md')).should.be.ok
+        fs.existsSync(path.join(test_path, 'views')).should.be.ok
+        fs.existsSync(path.join(test_path, 'views/index.jade')).should.be.ok
+        fs.existsSync(path.join(test_path, 'views/layout.jade')).should.be.ok
+        fs.existsSync(path.join(test_path, 'views/index.jade')).should.be.ok
+        fs.existsSync(path.join(test_path, 'assets')).should.be.ok
+        fs.existsSync(path.join(test_path, 'assets/favicon.ico')).should.be.ok
+        fs.existsSync(path.join(test_path, 'assets/css')).should.be.ok
+        fs.existsSync(path.join(test_path, 'assets/css/_settings.styl')).should.be.ok
+        fs.existsSync(path.join(test_path, 'assets/css/example.styl')).should.be.ok
+        fs.existsSync(path.join(test_path, 'assets/js')).should.be.ok
+        fs.existsSync(path.join(test_path, 'assets/js/main.coffee')).should.be.ok
+        fs.existsSync(path.join(test_path, 'assets/js/_helper.coffee')).should.be.ok
+        fs.existsSync(path.join(test_path, 'assets/js/pie.htc')).should.be.ok
+        fs.existsSync(path.join(test_path, 'assets/js/require.js')).should.be.ok
+        fs.existsSync(path.join(test_path, 'assets/img')).should.be.ok
+        fs.existsSync(path.join(test_path, 'assets/img/noise.png')).should.be.ok
+        rimraf.sync path.join(root, 'testproj')
+        done()
+
+    it 'should use express template if the --express flag is present', (done) ->
+      run "cd #{root}; ../bin/roots new testproj --express", ->
+        test_path = path.join(root, 'testproj')
+        fs.existsSync(test_path).should.be.ok
+        fs.existsSync(path.join(test_path, 'app.js')).should.be.ok
+        fs.existsSync(path.join(test_path, 'routes')).should.be.ok
+        fs.existsSync(path.join(test_path, 'assets')).should.be.ok
+        fs.existsSync(path.join(test_path, 'views')).should.be.ok
+        fs.existsSync(path.join(test_path, 'public')).should.be.ok
+        rimraf.sync path.join(root, 'testproj')
+        done()
+
+    it 'should use basic template if the --basic flag is present', (done) ->
+      run "cd #{root}; ../bin/roots new testproj --basic", ->
+        test_path = path.join(root, 'testproj')
+        fs.existsSync(test_path).should.be.ok
+        fs.existsSync(path.join(test_path, 'views/index.html')).should.be.ok
+        fs.existsSync(path.join(test_path, 'assets/js/main.js')).should.be.ok
+        fs.existsSync(path.join(test_path, 'assets/css/example.css')).should.be.ok
+        rimraf.sync path.join(root, 'testproj')
+        done()

   describe 'watch', ->
     it 'should compile the project to /public'


Add test for findTitleFromLex

Add test for findTitleFromLex

--- test/test.coffee
+++ test/test.coffee
@@ -2,6 +2,7 @@
 assert     = require 'assert'
 request    = require 'request'
 FeedParser = require 'feedparser'
+marked     = require 'marked'

 basedir = "#{__dirname}/notes"

@@ -51,6 +52,16 @@
     it 'should return expected result', ->
       assert.strictEqual bitterServer.obfuscateEmail('test@example.com'),
         'test@example.com'
+
+  describe 'findTitleFromLex', ->
+    it 'should return expected result', ->
+      markdown = """
+      # Hello Markdown
+
+      Test
+      """
+      title = bitterServer.findTitleFromLex marked.Lexer.lex markdown
+      assert.strictEqual title, 'Hello Markdown'

 describe 'server', ->
   before (done) ->


tests for deployer

tests for deployer

--- test/test.coffee
+++ test/test.coffee
@@ -90,20 +90,20 @@

     it 'should install a plugin from github if install is called'

-#   describe 'version', -> # ---------------------------------------------------------------------
+  describe 'version', -> # ---------------------------------------------------------------------

-#     it 'should output the correct version number for roots', (done) ->
-#       version = JSON.parse(fs.readFileSync('package.json')).version
-#       run './bin/roots version', (err,out) ->
-#         out.replace(/\n/, '').should.eql(version)
-#         done()
+    it 'should output the correct version number for roots', (done) ->
+      version = JSON.parse(fs.readFileSync('package.json')).version
+      run './bin/roots version', (err,out) ->
+        out.replace(/\n/, '').should.eql(version)
+        done()

-#   describe 'js', -> # --------------------------------------------------------------------------
+  describe 'js', -> # --------------------------------------------------------------------------

-#     it 'should expose bower\'s interface', (done) ->
-#       run "cd #{basic_root}; ../../bin/roots js", (err,out, stdout) ->
-#         out.should.match /bower/
-#         done()
+    it 'should expose bower\'s interface', (done) ->
+      run "cd #{basic_root}; ../../bin/roots js", (err,out, stdout) ->
+        out.should.match /bower/
+        done()

 #
 # adapters
@@ -162,6 +162,15 @@
     deployer = new Deployer(test_adapter, '')
     deployer.add_shell_method('test');

-  it 'works correctly', ->
+  it 'handles adapters correctly', ->
     deployer.test(true).should.be.ok
-    deployer.test(false).should.not.be.ok+    deployer.test(false).should.not.be.ok
+
+  it 'has all the required shell methods', ->
+    deployer.check_install_status.should.exist
+    deployer.check_credentials.should.exist
+    deployer.compile_project.should.exist
+    deployer.add_config_files.should.exist
+    deployer.commit_files.should.exist
+    deployer.create_project.should.exist
+    deployer.push_code.should.exist


Modify tests.

Modify tests.

--- test/test.coffee
+++ test/test.coffee
@@ -125,7 +125,7 @@
                 @value = {foo: "foo"}
                 service.set("testkey1", @value)
                 @return = service.get("testkey1", "fun hater")
-                @return.should.equal(@value)
+                @return.should.deep.equal(@value)
             it "on an invalid key... should return default value", ->
                 @return = service.get("testkeyINVALID", "fun hater")
                 @return.should.equal("fun hater")
@@ -359,7 +359,7 @@
                 @result.should.have.property("key")
                 @result.key.should.equal("testkey1")
                 @result.should.have.property("value")
-                @result.value.should.equal(@value)
+                @result.value.should.deep.equal(@value)
                 @result.should.have.property("action")
                 @result.action.should.equal(StoreIt.Action.removed)


Error codes

Error codes

--- rest_controller.coffee
+++ rest_controller.coffee
@@ -115,11 +115,11 @@
       # TODO: is there a way to do this without the model? eg. transaction only (with confirmation of existence) - HEAD?
       if req.params.id
         @model_type.find req.params.id, (err, model) =>
-          return res.status(404).send(error: err.toString()) if err
-          return res.status(404).send("Model not found with id: #{req.params.id}") unless model
+          return res.status(500).send(error: err.toString()) if err
+          return res.status(404).send(error: "Model not found with id: #{req.params.id}") unless model
           model.destroy {
-            success: -> res.send(200)
-            error: -> res.send(404)
+            success: -> res.status(200).send()
+            error: -> res.status(500).send(error: "Model not deleted with id: #{req.params.id}")
           }
     catch err
       res.status(500).send(error: err.toString())


remove skipped tests

remove skipped tests

--- src/desktop/components/auth_modal/test/view.coffee
+++ src/desktop/components/auth_modal/test/view.coffee
@@ -139,26 +139,3 @@
     it 'does not submit form if the the mode is password reset', ->
       @view.onSubmitSuccess @view.user, { success: 200 }
       @submitSpy.should.be.calledOnce
-
-  describe 'GDPR compliance', ->
-    beforeEach ->
-      @view.state = new Backbone.Model mode: 'register'
-      @view.user = new LoggedOutUser
-      @view.$el.html $ "<form>" + render('register')(
-        copy: new Backbone.Model
-        sd: CSRF_TOKEN: 'csrfoo', AP: loginPagePath: 'foo'
-      ) + "</form>"
-      # @view.renderInner()
-
-
-
-    xit 'requires user accept terms for registration via email', ->
-      @view.$('input[name="name"]').val('Foo Bar')
-      @view.$('input[name="email"]').val('foo@bar.com')
-      @view.$('input[name="password"]').val('password123!')
-      console.log(typeof @view.$('form').get(0).checkValidity)
-      @view.$('form').checkValidity()
-      console.log(@view.$('.tos-error').html())
-      @view.$('.tos-error').text().should.equal 'html5 checkValidity not firing in node'
-    xit 'requires user accept terms for registration via social auth'
-


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/text.coffee
+++ test/text.coffee
@@ -17,6 +17,6 @@
 test "text fades out", ->
   text = Text()

-
+  text.

 module()


removed cruft

removed cruft

--- core/app/backbone/views/activities_view.coffee
+++ core/app/backbone/views/activities_view.coffee
@@ -7,31 +7,31 @@
   template: 'activities/list'

   initialize: (opts) ->
-    this.collection.on('reset', this.reset, this);
-    this.collection.on('add', this.add, this);
-    this.itemView = ActivityItemView;
+    @collection.on('reset', this.reset, this)
+    @collection.on('add', this.add, this)
+    @itemView = ActivityItemView

-  render: () ->
+  render: ->
     super arguments
     this.renderChildren()

-  renderChildren: () ->
+  renderChildren: ->
     this.$('.list').html('')
     for childView in @childViews
       this.appendHtml(this, childView)

-  reset: () ->
+  reset: ->
     this.collection.each( this.add, this );
-    this.renderChildren();
+    this.renderChildren()

   add: (model) ->
     last = _.last(this.childViews);

     if (last && (last.appendable(model)))
-      appendTo = last;
+      appendTo = last
     else
-      appendTo = this.newChildView(model);
-      this.childViews.push(appendTo);
+      appendTo = this.newChildView(model)
+      this.childViews.push(appendTo)
       this.appendHtml(this, appendTo)

     appendTo.collection.add(model);
@@ -41,11 +41,11 @@
       childView.close

   appendHtml: (collectionView, childView) ->
-    childView.render();
-    this.$(".list").append(childView.$el);
+    childView.render()
+    this.$(".list").append(childView.$el)

   newChildView: (model) ->
-    ch = this.collection.channel;
+    ch = this.collection.channel
     new UserActivitiesView
       model: model.getActivity(),
       collection: new ChannelActivities([], {channel: ch})


refactored scroll to top

refactored scroll to top

--- _assets/js/_scroll.coffee
+++ _assets/js/_scroll.coffee
@@ -5,9 +5,9 @@
 revealScrollToTop = ->
   $('#page-content-wrapper').scroll ->
     if $('#page-content-wrapper').scrollTop() > 300
-      $('.scrollToTop').css 'display', 'initial'
+      $('.scrollToTop').css 'display', 'block'
     else
-      $('.scrollToTop').css 'display', 'none'
+      $('.scrollToTop').css 'display', ''
     return
   #Click event to scroll to top
   $('.scrollToTop').click ->


Write naive get/set tests.

Write naive get/set tests.

--- test/view.coffee
+++ test/view.coffee
@@ -178,3 +178,22 @@

     it 'should set textarea\'s value correctly', ->
       html.should.contain '<textarea id="textarea-value">value</textarea>'
+
+  describe '#get', ->
+    view = null
+    before ->
+      class View extends mvstar.View
+      view = new View state:
+        a: 'a'
+        b: 'b'
+
+    it 'should get state correctly', ->
+      view.get('a').should.eq 'a'
+      view.get('b').should.eq 'b'
+
+    it 'should set state correctly', ->
+      view.set 'a', 'z'
+      view.get('a').should.eq 'z'
+
+      view.set 'b', 'x'
+      view.get('b').should.eq 'x'


Comment definition

Comment definition

--- grammars/mathematica.cson
+++ grammars/mathematica.cson
@@ -25,4 +25,14 @@
       'name': 'constant.character'
     }
   }
+  {
+    'comment': 'Comments'
+    'name': 'comment'
+    'begin': '\\(\\*'
+    'end': '\\*\\)'
+    'patterns': {
+      'match': '\\.'
+      'name': 'comment'
+    }
+  }
 ]


Fixed doubled search event on mouse click

--- _src/js/views/main.coffee
+++ _src/js/views/main.coffee
@@ -267,9 +267,11 @@
 		return

 	_onSearch: ( evnt )=>
-		evnt.stopPropagation()
-		@exit()
-		@trigger( "searchbutton", @searchButton.event )
+		if ( evnt.type is "click" and evnt.detail is 0 ) or evnt.type is "mousedown"
+			evnt?.preventDefault()
+			evnt.stopPropagation()
+			@exit()
+			@trigger( "searchbutton", @searchButton.event )
 		return

 	_onFocusSearch: ( evnt )=>


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/bounded.coffee
+++ test/bounded.coffee
@@ -7,8 +7,9 @@

   obj.include(Bounded)

-  equals obj.bounds().x, 5, "returns correct x, y, width, height"
+  equals obj.bounds().x, 5
   equals obj.bounds().y, 10
   equals obj.bounds().width, 50
   equals obj.bounds().height, 100

+


actually fixes issue

actually fixes issue

--- api/v0/models/keys.coffee
+++ api/v0/models/keys.coffee
@@ -13,7 +13,7 @@

 keysSchema.static "getUserKeyWithIdentifier", (user, ident, cb) ->
 	this.find
-		user: user.id
+		user: mongoose.Types.ObjectId(user.id)
 		identifier: ident
 	, (err, doc) ->
 			if err


Remove uber-suck session calculation code from legislation api code.

--- api/v1/legislation.coffee
+++ api/v1/legislation.coffee
@@ -9,9 +9,8 @@
   api.v1.getLegislationById = (req, res) -> res.jsonp req.legislation

   api.v1.getLegislation = (req, res) ->
-    db.collection("sessions").findOne {current: true}, (err, currentSession) ->
-      unless currentSession?
-        err = "Could not find current session."
+    db.collection("legislation").find({sessionId: req.apiRequestSessionId}).sort({number: 1}).toArray (err, results) ->
+      if err
         errorId = Math.random().toString(36).substring(7)
         console.error("Error " + errorId + ": " + err)

@@ -22,31 +21,7 @@

         return

-      selectedSessionIdStr = req.query.sessionId || currentSession._id.toString()
-
-      try
-        selectedSessionId = new ObjectId(selectedSessionIdStr)
-      catch err
-        res.jsonp
-          error: "Invalid session ID."
-        , 500
-
-        res.end
-        return
-
-      db.collection("legislation").find({sessionId: selectedSessionId}).sort({number: 1}).toArray (err, results) ->
-        if err
-          errorId = Math.random().toString(36).substring(7)
-          console.error("Error " + errorId + ": " + err)
-
-          res.jsonp
-            id: errorId,
-            error: err
-          , 500
-
-          return
-
-        res.jsonp(results)
+      res.jsonp(results)

   api.v1.getLegislationByTypeAndNumber = (req, res) ->
     type = req.params.type.toUpperCase()
@@ -61,9 +36,8 @@

     fullCode = type + " " + req.params.number

-    db.collection("sessions").findOne {current: true}, (err, currentSession) ->
-      unless currentSession?
-        err = "Could not find current session."
+    db.collection("legislation").findOne {sessionId: req.apiRequestSessionId, code: fullCode}, (err, legislation) ->
+      if err
         errorId = Math.random().toString(36).substring(7)
         console.error("Error " + errorId + ": " + err)

@@ -74,22 +48,7 @@

         return

-      selectedSessionIdStr = req.query.sessionId || currentSession._id.toString()
-      selectedSessionId = new ObjectId(selectedSessionIdStr)
-
-      db.collection("legislation").findOne {sessionId: selectedSessionId, code: fullCode}, (err, legislation) ->
-        if err
-          errorId = Math.random().toString(36).substring(7)
-          console.error("Error " + errorId + ": " + err)
-
-          res.jsonp
-            id: errorId,
-            error: err
-          , 500
-
-          return
-
-        res.jsonp(legislation)
+      res.jsonp(legislation)

   api


Clear doc

Clear doc

--- routes/itemdata.coffee
+++ routes/itemdata.coffee
@@ -1,7 +1,6 @@
 express = require 'express'
 router = express.Router()
 database = require '../routes/database'
-item = require '../routes/item'

 convertToArrayIfNot = (field) ->
   if typeof(field) != 'object' then return [field] else return field
@@ -9,192 +8,6 @@
 getDelta = (arr1, arr2) ->
   res = arr1.filter((x) => arr2.indexOf(x) == -1)
   if !(res[0]) then return null else return res
-
-UpdateAdjacent = (incomingCollection, incomingId, incomingParam, remoteCollection, remoteParam, updateParam) ->
-  db = database.getDb()
-  db.collection(incomingCollection).find({'_id': database.getId(incomingId)}).toArray (err, result) ->
-    if (getDelta(incomingParam, result[0][remoteParam]))
-      console.log 'adding', getDelta(incomingParam, result[0][remoteParam])
-      if getDelta(incomingParam, result[0][remoteParam]).length >= 1
-        itemDiff = getDelta incomingParam, result[0][remoteParam]
-        for itemId in itemDiff
-          query = {
-                    '_id': database.getId itemId
-                  }
-          queryParam = {
-            $set:{}
-          }
-          queryParam['$set'][updateParam] = incomingId
-          db.collection(remoteCollection).findOneAndUpdate query, queryParam
-          console.log 'adding', query, queryParam
-    if (getDelta(result[0][remoteParam], incomingParam))
-      console.log 'removing', getDelta(result[0][remoteParam], incomingParam)
-      if getDelta(result[0][remoteParam], incomingParam).length >= 1
-        itemDiff = getDelta result[0].hasItems, incomingParam
-        for itemId in itemDiff
-          query = {
-                    '_id': database.getId itemId
-                  }
-          queryParam = {
-            $set:{}
-          }
-          queryParam['$set'][updateParam] = ''
-          db.collection(remoteCollection).findOneAndUpdate query, queryParam
-          console.log 'removing', query, queryParam
-router.post '/newItem', (req, res, next) ->
-  db = database.getDb()
-  newItem = new item.item database.getId(), req.body.name, req.body.status, req.body.type, req.body.link, req.body.description, req.body.date, req.body.technologies, req.body.images
-  db.collection('collection').find({'_id':database.getId(req.body.collection)}).toArray (err, result) ->
-    currentCollection = new item.collection result[0]._id, result[0].name, result[0].status, result[0].type, result[0].link, result[0].description, result[0].image, result[0].hasItems
-    newItem.setCollection currentCollection
-    db.collection('collection').update {'_id':database.getId(req.body.collection)}, currentCollection
-    db.collection('item').insert newItem, ->
-      res.redirect 'back'
-
-router.post '/newCollection', (req, res, next) ->
-  db = database.getDb()
-  newCollection = new item.collection database.getId(), req.body.name, req.body.status, req.body.type, req.body.link, req.body.description, req.body.image, req.body.items, req.body.showcase
-  db.collection('collection').insert newCollection, ->
-    res.redirect 'back'
-
-router.post '/newTeam', (req, res, next) ->
-  db = database.getDb()
-  newTeam = new item.team database.getId(), req.body.name, req.body.logo, req.body.link, req.body.description, req.body.showcase, req.body.members
-  if req.body.hasCollections
-    req.body.hasCollections = convertToArrayIfNot(req.body.hasCollections)
-    UpdateAdjacent 'team', teamId, req.body.hasCollections, 'collection', 'hasCollections', 'partOfTeam'
-  if req.body.hasItems
-    req.body.hasItems = convertToArrayIfNot(req.body.hasItems)
-    UpdateAdjacent 'team', teamId, req.body.hasItems, 'item', 'hasItems', 'partOfTeam'
-  db.collection('team').insert newTeam, ->
-    res.redirect 'back'
-
-router.post '/update/:collection/:id', (req, res, next) ->
-  db = database.getDb()
-  collection = db.collection(req.params.collection)
-
-  updateItem = (itemId, data) ->
-    return
-
-  updateCollection = (collectionId, data) ->
-    return
-
-  updateTeam = (teamId, data) ->
-    collection.find({'_id': database.getId(teamId)}).toArray (err, result) ->
-      data.hasItems = convertToArrayIfNot(data.hasItems)
-      UpdateAdjacent 'team', teamId, data.hasItems, 'item', result[0].hasItems, 'partOfTeam'
-      data.hasCollections = convertToArrayIfNot(data.hasCollections)
-      UpdateAdjacent 'team', teamId, data.hasCollections, 'collection', 'hasCollections', 'partOfTeam'
-      collection.findOneAndUpdate(
-        {
-          '_id': database.getId(teamId)
-        },
-        {
-          $set: data
-        }
-      )
-
-    # collection.find({'_id': database.getId(teamId)}).toArray (err, result) ->
-    #   if getDelta(data.hasItems, result[0].hasItems).length >= 1
-    #     itemDiff = getDelta(data.hasItems, result[0].hasItems)
-    #     for itemId in itemDiff
-    #       db.collection('item').findOneAndUpdate(
-    #         {
-    #           '_id': database.getId(itemId)
-    #         },
-    #         {
-    #           $set: {
-    #             'partOfTeam': req.params.id
-    #           }
-    #         }
-    #       )
-    #   else if getDelta(result[0].hasItems, data.hasItems).length >= 1
-    #     itemDiff = getDelta(result[0].hasItems, data.hasItems)
-    #     for itemId in itemDiff
-    #       db.collection('item').findOneAndUpdate(
-    #         {
-    #           '_id': database.getId(itemId)
-    #         },
-    #         {
-    #           $set: {
-    #             'partOfTeam': ''
-    #           }
-    #         }
-    #       )
-    #   collection.findOneAndUpdate(
-    #     {
-    #       '_id': database.getId(req.params.id)
-    #     },
-    #     {
-    #       $set: data
-    #     }
-    #   )
-
-  switch req.params.collection
-    when 'item' then updateItem(req.params.id, req.body)
-    when 'collection' then updateCollection(req.params.id, req.body)
-    when 'team' then updateTeam(req.params.id, req.body)
-    else
-      throw new ReferenceError()
-
-  res.redirect 'back'
-
-router.post '/delete/:collection', (req, res, next) ->
-  database.getDb().collection(req.params.collection).findOneAndDelete({'_id': database.getId(req.body.id)})
-  res.redirect 'back'
-
-router.post '/getData/:collection', (req, res, next) ->
-  db = database.getDb()
-  db.collection(req.params.collection).find({'_id':database.getId(req.body.id)}).toArray (err, result) ->
-    res.send(result[0])
-
-router.post '/updateItem/:id', (req, res, next) ->
-  db = database.getDb()
-  db.collection('item').find({'_id':database.getId(req.params.id)}).toArray (err, result) ->
-    editedItem = new item.item database.getId(req.params.id), req.body.name, req.body.status, req.body.type, req.body.link, req.body.description, req.body.date, req.body.technologies, req.body.images
-    if req.body.collection
-      db.collection('collection').find({'_id':database.getId(req.body.collection)}).toArray (err, result) ->
-        currentCollection = new item.collection result[0]._id, result[0].name, result[0].status, result[0].type, result[0].link, result[0].description, result[0].image, result[0].hasItems
-        editedItem.setCollection currentCollection
-        db.collection('collection').update {'_id':database.getId(req.body.collection)}, currentCollection, ->
-          db.collection('item').update {'_id':database.getId(req.params.id)}, editedItem, ->
-            res.redirect 'back'
-    else
-      db.collection('item').update {'_id':database.getId(req.params.id)}, editedItem, ->
-        res.redirect 'back'
-
-router.post '/deleteItem', (req, res, next) ->
-  db = database.getDb()
-  db.collection('item').find({'_id':database.getId(req.body.id)}).toArray (err, result) ->
-    if result[0].collection[0]
-
-      itemId = req.body.id
-      db.collection('collection').find({'_id':database.getId(result[0].collection[0])}).toArray (err, result) ->
-        updatedItemList = []
-        for id in result[0].hasItems
-          if itemId != String(id)
-            updatedItemList.push(id)
-        currentCollection = new item.collection result[0]._id, result[0].name, result[0].status, result[0].type, result[0].link, result[0].description, result[0].image, updatedItemList
-        db.collection('collection').update {'_id':database.getId(result[0]._id)}, currentCollection, ->
-          db.collection('item').remove {'_id':database.getId(req.body.id)}, ->
-            res.redirect('back')
-    else
-      db.collection('item').remove {'_id':database.getId(req.body.id)}, ->
-        res.redirect 'back'
-
-router.post '/deleteCollection', (req, res, next) ->
-  db = database.getDb()
-  db.collection('collection').find({'_id':database.getId(req.body.id)}).toArray (err, result) ->
-    if result[0].hasItems.length > 0
-      for itemIds in result[0].hasItems
-        db.collection('item').find({'_id':database.getId(itemIds)}).toArray (err, result) ->
-          currentItem = new item.item result[0]._id, result[0].name, result[0].status, result[0].type, result[0].link, result[0].description, result[0].date, result[0].technologies, result[0].images
-          db.collection('item').update {'_id':database.getId(result[0]._id)}, currentItem
-      db.collection('collection').remove {'_id':database.getId(req.body.id)}, ->
-        res.redirect 'back'
-    else
-      db.collection('collection').remove {'_id':database.getId(req.body.id)}, ->
-        res.redirect 'back'

 router.get '/', (req, res, next) ->
   database.getDb().collection('item').find({}).toArray (err, items) ->


This error complains about the method now, not the file

Still kind of fragile.  Should probably use some shared enum-ish thing.

This fixes failing tests.

--- test/prompts.coffee
+++ test/prompts.coffee
@@ -18,4 +18,4 @@
     tests = promptTests 'load-error',
       expectedError:
         name: 'runtime error'
-        text: 'Your prompt file triggered a JavaScript error.'
+        text: 'Your prompt method triggered a JavaScript error.'


Add special symbol for the main comment

Add special symbol for the main comment

--- text-fade-in.coffee
+++ text-fade-in.coffee
@@ -1,4 +1,4 @@
-###
+###!
 TextFadeIn v 1.0.0
 https://github.com/mdesantis/TextFadeIn


Replace "Regeneration" with "Regen" to fix overlapping text (#6029)

* Replace "Regeneration" with "Regen"

* Removed extra line

--- app/locale/en.coco.coffee
+++ app/locale/en.coco.coffee
@@ -1029,7 +1029,7 @@
     attack: "Damage"  # Can also translate as "Attack"
     health: "Health"
     speed: "Speed"
-    regeneration: "Regeneration"
+    regeneration: "Regen"
     range: "Range"  # As in "attack or visual range"
     blocks: "Blocks"  # As in "this shield blocks this much damage"
     backstab: "Backstab"  # As in "this dagger does this much backstab damage"
@@ -3194,4 +3194,4 @@

   admin:
     license_type_full: 'Full Courses'
-    license_type_customize: 'Customize Courses'+    license_type_customize: 'Customize Courses'


update zh-HANS (#475)

update zh-HANS (#475)

Co-authored-by: LanceWu <1dae8a04fca62b004aa5f8aa877f722314e5b9e7@wjllance-mbp.local>

--- app/locale/zh-HANS.coffee
+++ app/locale/zh-HANS.coffee
@@ -307,7 +307,7 @@
     back_to_dashboard: "返回我的课程"
     back_to_ozaria: "返回奥佳睿首页"
     customize_hero: "英雄设置"
-#    account_settings: "Account Settings"
+    account_settings: "账户设置"

   code:
     if: "如果" # Keywords--these translations show up on hover, so please translate them all, even if it's kind of long. (In the code editor, they will still be in English.)
@@ -1486,7 +1486,7 @@

   cinematic:
     click_anywhere_continue: "点击任意区域继续" # {change}
-#    instructional_tooltip: "Click these arrow buttons to go backwards and forwards. You can also use the arrow keys on your keyboard to do the same."
+    instructional_tooltip: "点击这些方向键来前进或后退，你也可以使用键盘上的方向键来实现同样的效果。"

   community:
     main_title: "CodeCombat社区"


debug mailbox deletion

debug mailbox deletion

--- app/models/mailbox.coffee
+++ app/models/mailbox.coffee
@@ -17,15 +17,15 @@
 # accounts...
 Mailbox::remove = (callback) ->
     @log "destroying box..."
-    box.destroyMails (err) =>
+    @destroyMails (err) =>
         @log err if err
-        box.destroyAttachments (err) =>
+        @destroyAttachments (err) =>
             @log err if err
-            box.destroyMailsToBe (err) =>
+            @destroyMailsToBe (err) =>
                 @log err if err
-                box.destroyAccount (err) =>
+                @destroyAccount (err) =>
                     @log err if err
-                    box.destroy (err) =>
+                    @destroy (err) =>
                         @log err if err
                         @log "destroying finished..."
                         callback()


Only use Sloan single band

Only use Sloan single band

--- app/models/subject.coffee
+++ app/models/subject.coffee
@@ -47,6 +47,7 @@
   @url: (params) -> @withParams "/projects/galaxy_zoo/groups/#{ params.surveyId }/subjects", limit: params.limit

   @randomSurveyId: ->
+    return @::surveys.sloan_singleband.id
     n = Math.random()
     if n <= 0.5
       @::surveys.candels_2epoch.id


invitation fix

invitation fix

--- client/app/MainApp/routes.coffee
+++ client/app/MainApp/routes.coffee
@@ -34,7 +34,7 @@
         new KDNotificationView
           title: 'Could not redeem invitation because you are already logged in.'
       else bongo.api.JInvitation.byCode inviteToken, (err, invite)->
-        if err or !invite? or invite.status isnt 'active'
+        if err or !invite? or invite.status isnt 'active' or invite.status isnt 'sent'
           new KDNotificationView
             title: 'Invalid invitation code!'
         else


team: update username

team: update username

--- client/landing/About/team.coffee
+++ client/landing/About/team.coffee
@@ -43,7 +43,7 @@
         username  : 'burakcan'
         title     : 'CSS Wizard'
       ,
-        username  : 'cenk6'
+        username  : 'cenk'
         title     : 'Software Engineer'
       ,
         username  : 'emred'


isPresent helper for assets that are non image

--- client/lanes/models/Asset.coffee
+++ client/lanes/models/Asset.coffee
@@ -31,6 +31,10 @@
             deps: ['data', 'file_data'], fn: ->
                 if @data then @data else @urlFor('thumbnail')

+        isPresent:
+            deps: ['data', 'file_data'], fn: ->
+                @data || !_.isEmpty(@file_data)
+
         hasImage:
             deps: ['data', 'file_data'], fn: ->
                 (@data && IS_IMAGE(@metadata.content_type)) ||
@@ -55,14 +59,11 @@

     onBlobChange: ->
         if @blob
-            @metadata = {
-                content_type: @blob.type
-            }
+            @metadata = { content_type: @blob.type }
             reader = new FileReader()
             reader.onloadend = (ev) =>
                 @data = reader.result if ev.type is 'loadend'
             reader.readAsDataURL(@blob)
-
         else
             @data = null
             @mdatadata?.content_type = undefined


Add placeholder for shard state

Add placeholder for shard state

--- components/CalculateState.coffee
+++ components/CalculateState.coffee
@@ -36,6 +36,12 @@
       guid: newState.guid
       team: newState.team
       state: 'stable'
+
+    if newState.shards
+      # TODO: We don't get Shard info from the API yet, this is merely a placeholder
+      console.log newState.updated, "Portal #{newState.title} has shards"
+      state.state = 'disco'
+      return state

     previous = previousStates[newState.guid]
     unless previous


Only send the runtime once it has been connected

--- components/ConnectRuntime.coffee
+++ components/ConnectRuntime.coffee
@@ -49,18 +49,20 @@
       @outPorts.error.disconnect()
       return

-    rt = new Runtime definition
-    rt.setParentElement @element if @element
-    try
-      rt.connect()
-    catch e
+    onError = (e) =>
       @outPorts.error.send e
       @outPorts.error.disconnect()
       return

-    @outPorts.runtime.beginGroup definition.id
-    @outPorts.runtime.send rt
-    @outPorts.runtime.endGroup()
-    @outPorts.runtime.disconnect()
+    rt = new Runtime definition
+    rt.setParentElement @element if @element
+    rt.once 'connected', =>
+      rt.removeListener 'error', onError
+      @outPorts.runtime.beginGroup definition.id
+      @outPorts.runtime.send rt
+      @outPorts.runtime.endGroup()
+      @outPorts.runtime.disconnect()
+    rt.once 'error', onError
+    rt.connect()

 exports.getComponent = -> new ConnectRuntime


one more team

one more team

--- models/team.coffee
+++ models/team.coffee
@@ -43,7 +43,7 @@
 TeamSchema.static 'canRegister', (next) ->
   Team.count {}, (err, count) ->
     return next err if err
-    max = 312 + 1 # +1 because team fortnight labs doesn't count
+    max = 313 + 1 # +1 because team fortnight labs doesn't count
     next null, count < max, max - count

 TeamSchema.static 'uniqueName', (name, next) ->


only publish to one redis client in WebsocketLoadBalancer

but listen to all of them

--- services/real-time/app/coffee/WebsocketLoadBalancer.coffee
+++ services/real-time/app/coffee/WebsocketLoadBalancer.coffee
@@ -6,7 +6,7 @@
 HealthCheckManager = require "./HealthCheckManager"

 module.exports = WebsocketLoadBalancer =
-	rclientPubList: RedisClientManager.createClientList(Settings.redis.pubsub, Settings.redis.unusedpubsub)
+	rclientPubList: RedisClientManager.createClientList(Settings.redis.pubsub)
 	rclientSubList: RedisClientManager.createClientList(Settings.redis.pubsub, Settings.redis.unusedpubsub)

 	emitToRoom: (room_id, message, payload...) ->


Run renderer process specs first

Run renderer process specs first

--- build/tasks/spec-task.coffee
+++ build/tasks/spec-task.coffee
@@ -136,13 +136,13 @@
       else
         async.parallel

-    specs = [runMainProcessSpecs, runRendererProcessSpecs, runPackageSpecs]
+    specs = [runRendererProcessSpecs, runMainProcessSpecs, runPackageSpecs]

     method specs, (error, results) ->
       failedPackages = []
       coreSpecFailed = null

-      [mainProcessSpecsFailed, rendererProcessSpecsFailed, failedPackages] = results
+      [rendererProcessSpecsFailed, mainProcessSpecsFailed, failedPackages] = results

       elapsedTime = Math.round((Date.now() - startTime) / 100) / 10
       grunt.log.ok("Total spec time: #{elapsedTime}s using #{concurrency} cores")


:art:

:art:

--- src/event-manager.coffee
+++ src/event-manager.coffee
@@ -55,7 +55,7 @@
       signature: req.get('X-Github-Signature')
       type: req.get('X-Github-Event')

-    @robot.logger.debug "Received event: #{JSON.stringify(event)}"
+    @robot.logger.info "Received event: #{util.inspect(event)}"
     emit(event)

   # Private: Gets the list of event formatters.


Remove dead uniqueHashcode function

Remove dead uniqueHashcode function

--- src/extended-ajax.coffee
+++ src/extended-ajax.coffee
@@ -1,11 +1,3 @@
-uniqueHashcode = (str) =>
-	hash = 0
-	for char in str
-		charcode = char.charCodeAt(0)
-		hash = ((hash << 5) - hash) + charcode
-		hash = hash & hash # Convert to 32bit integer
-	hash.toString()
-
 AjaxQueue = (ajax) ->
 	theQueue = $({})


Ext/EditObject: Add support for blank strings.

--- Oghma/Ext/EditObject.coffee
+++ Oghma/Ext/EditObject.coffee
@@ -31,6 +31,7 @@
   # Supported types are:
   # - color
   # - string
+  # - string-blank
   # - auto
   types: {}

@@ -88,7 +89,7 @@
         when 'color'
           field = Ext.create( 'Oghma.Ext.ColorField' )
         else
-          if type != 'auto' && type != 'string'
+          if type != 'auto' && type != 'string' && type != 'string-blank'
             throw "Unknown type: #{type}"
           else
             field = Ext.create( 'Ext.form.field.Text' )
@@ -97,7 +98,7 @@
         fieldLabel: k
         labelStyle: 'text-transform: capitalize'
         name:       k
-        allowBlank: false
+        allowBlank: type == 'string-blank'
       )
       field.setValue( v )


Update zh-HANS.coffee

Update zh-HANS.coffee

--- app/locale/zh-HANS.coffee
+++ app/locale/zh-HANS.coffee
@@ -2429,7 +2429,7 @@

   esper:
     line_no: "第$1行: "
-#    uncaught: "Uncaught $1" # $1 will be an error type, eg "Uncaught SyntaxError"
+    uncaught: "未捕获的错误$1" # $1 will be an error type, eg "Uncaught SyntaxError"
     reference_error: "参考错误："
     argument_error: "参数错误："
     type_error: "拼写错误："
@@ -2495,7 +2495,7 @@
     there_is_no_function: "没有`$1`函数，但是`$2`有`$3`函数。"
 #    attacks_argument_enemy_has: "`$1`'s argument `$2` has a problem."
 #    is_there_an_enemy: "Is there an enemy within your line-of-sight yet?"
-#    target_is_null_is: "目标$1出现了问题（可能是null）。 Is there always a target to attack? (Use $2?)"
+    target_is_null_is: "目标$1出现了问题（可能是null）。这里总是有攻击目标吗？"
     hero_has_no_method: "`$1` 没有 `$2` 函数。"
     there_is_a_problem: "你的代码有问题。"
 #    did_you_mean: "Did you mean $1? You do not have an item equipped with that skill."
@@ -2503,5 +2503,5 @@
 #    missing_var_use_var: "Missing `$1`. Use `$2` to make a new variable."
 #    you_do_not_have: "You do not have an item equipped with the $1 skill."
 #    put_each_command_on: "Put each command on a separate line"
-#    are_you_missing_a: "Are you missing a '$1' after '$2'? "
+    are_you_missing_a: "在`$2`后面有没有`$1`？"
 #    your_parentheses_must_match: "Your parentheses must match."


reform paste html

reform paste html

--- app/scripts/editor.coffee
+++ app/scripts/editor.coffee
@@ -223,13 +223,16 @@
 			raw = e.clipboardData.getData('text/html') || e.clipboardData.getData('text')
 			pasteElement = document.createElement 'p'
 			pasteElement.innerHTML = raw
-			data = $(pasteElement)
-			# children = data.children()
-			# children.each ->
-			# 	$(this).removeAttr('style').find('*').removeAttr('style')
-			result = data.html()
-				.replace( new RegExp('h[0-9]', 'ig'), 'h1' )
-				# .clearTagsUnless(['p', 'h1', 'a', 'pre'])
+			$(pasteElement).find('*').each ->
+				attributes = $.map(@attributes, (item)->
+					item.name
+				)
+				el = $(@)
+				$.each attributes, (i, key)->
+					el.removeAttr(key)
+			result = $(pasteElement).html()
+				.replace( new RegExp('h[3-9]', 'ig'), 'h2' )
+				.removeTagsExcept(['p', 'h1', 'h2', 'a', 'br', 'pre', 'code'])
 			document.execCommand 'insertHtml', false, result
 		.on 'askid', =>
 			if @askid


selection handling after change tags

selection handling after change tags

--- app/scripts/editor.coffee
+++ app/scripts/editor.coffee
@@ -10,21 +10,36 @@
 	saveSelection: ->
 		sel = @selection()
 		if sel.getRangeAt && sel.rangeCount
-			@lastRange = sel.getRangeAt 0
-			$('.selection').text sel.getRangeAt(0).toString()
-		else if document.selection && document.selection.createRange
-			@lastRange = document.selection.createRange()
+			range = sel.getRangeAt 0
+			$('.selection').text range.toString()
+
+			cursorStart = document.createElement 'span'
+			cursorStart.id = 'cursorStart'
+			range.insertNode cursorStart
+			if !range.collapsed
+				cursorEnd = document.createElement 'span'
+				cursorEnd.id = 'cursorEnd'
+				range.collapse()
+				range.insertNode cursorEnd
 	restoreSelection: (range)->
-		if range
-			if window.getSelection
-				sel = @selection()
-				sel.removeAllRanges()
-				sel.addRange range
-			else if document.selection && range.select
-				range.select()
-	# getSelectedElement: ->
-	# 	el = $(@selection().getRangeAt(0).commonAncestorContainer)
-	# 	return if el[0].nodeType == 3 then el.parent() else el
+		cursorStart = document.getElementById 'cursorStart'
+		cursorEnd = document.getElementById 'cursorEnd'
+		range = document.createRange()
+		if cursorStart
+			sel = @selection()
+			if cursorEnd
+				range.setStartAfter cursorStart
+				range.setEndBefore cursorEnd
+				cursorStart.parentNode.removeChild cursorStart
+				cursorEnd.parentNode.removeChild cursorEnd
+			else
+				range.selectNode cursorStart
+			# select range
+			sel.removeAllRanges()
+			sel.addRange range
+	getSelectedElement: ->
+		el = $(@selection().getRangeAt(0).commonAncestorContainer)
+		return if el[0].nodeType == 3 then el.parent() else el
 	clearStatus: ->
 		$('.debug-status').addClass 'hidden'
 		@status.cmd = @status.ctrl = @status.alt = @status.shift = @status.empty = false
@@ -38,18 +53,20 @@
 			$(@).attr 'name', self.rand()
 		$('#debug').text $('#editor').html()
 	toggleFormatBlock: (tag)->
-
+		el = @getSelectedElement()
 		if el.attr 'name'
 			# save last ragne
-				@getSelection
+				range = @saveSelection()
 			if el.is tag
 				newEl = $('<p>').html el.html()
 				el.after(newEl)
 				el.remove()
+				@restoreSelection range, newEl[0]
 			else
 				newEl = $("<#{tag}>").html el.html()
 				el.after(newEl)
 				el.remove()
+				@restoreSelection range, newEl[0]
 	handleKeyDown: (e)->
 		# debug
 		$('#debug-keydown').text e.keyCode


Use `onDidCompleteDownloadingUpdate` in `listenForUpdates`

This gets a bit confusing… It was formerly
`@applicationDelegate.onUpdateAvailable`, but `::onUpdateAvailable`
listens for the `did-begin-downloading-update` event and
`::onDidCompleteDownloadingUpdate` listens for the `update-available`
event. Note that ‘available’ here means successfully downloaded and
ready to be used and NOT available to be downloaded.

--- src/atom-environment.coffee
+++ src/atom-environment.coffee
@@ -891,7 +891,8 @@
     @emitter.emit 'update-available', details

   listenForUpdates: ->
-    @disposables.add(@applicationDelegate.onUpdateAvailable(@updateAvailable.bind(this)))
+    # listen for updates available locally (that have been successfully downloaded)
+    @disposables.add(@applicationDelegate.onDidCompleteDownloadingUpdate(@updateAvailable.bind(this)))

   setBodyPlatformClass: ->
     @document.body.classList.add("platform-#{process.platform}")


Do not ignore whitespace on current line in Atom

--- atom/config.cson
+++ atom/config.cson
@@ -39,7 +39,8 @@
       space: false
       tab: "⇥"
     softTabs: true
-  whitespace: {}
+  whitespace:
+    ignoreWhitespaceOnCurrentLine: false
   minimap:
     plugins:
       "git-diff": true


Add Atom keybinding for showing Elixir docs

--- atom/keymap.cson
+++ atom/keymap.cson
@@ -46,3 +46,6 @@
   'alt-ctrl-f': 'multi-cursor-plus:move-right'
   'alt-ctrl-a': 'multi-cursor-plus:move-to-first-character-of-line'
   'alt-ctrl-e': 'multi-cursor-plus:move-to-end-of-line'
+
+'atom-text-editor:not(mini)[data-grammar^="source elixir"]':
+  'ctrl-shift-d': 'atom-elixir:show-elixir-docs'


Fixing indentation.

Fixing indentation.

--- src/polvo/core/tentacle.coffee
+++ src/polvo/core/tentacle.coffee
@@ -56,14 +56,14 @@

     # simple static server with 'connect'
     @conn = connect()
-              .use( connect.static root )
-              .use( (req, res)->
-                if ~(req.url.indexOf '.')
-                  res.statusCode = 404
-                  res.end 'File not found: ' + req.url
-                else
-                  res.end (fs.readFileSync index, 'utf-8')
-              ).listen port
+      .use( connect.static root )
+      .use( (req, res)->
+        if ~(req.url.indexOf '.')
+          res.statusCode = 404
+          res.end 'File not found: ' + req.url
+        else
+          res.end (fs.readFileSync index, 'utf-8')
+      ).listen port

     # plugging socket io (only for development mode - excludes -r option)
     unless @cli.argv.r


View: Put some example export statements.

--- src/coffee/core/View.coffee
+++ src/coffee/core/View.coffee
@@ -111,3 +111,8 @@
   ###
   setTemplate: (template) ->
     @getElement().innerHTML = template
+
+goog.exportSymbol 'View', spark.core.View
+goog.exportSymbol 'View.prototype.setTemplate',  spark.core.View.prototype.setTemplate
+goog.exportSymbol 'View.prototype.removeClass',  spark.core.View.prototype.removeClass
+goog.exportSymbol 'View.prototype.getClassList', spark.core.View.prototype.getClassList


load modules - end logic

load modules - end logic

--- src/coffee/core/brex.coffee
+++ src/coffee/core/brex.coffee
@@ -13,6 +13,7 @@
     @modules = ctor.object()
     @browser = ctor.object require("./browser.coffee")
     @talker = new Talker @pid
+    @talker.addListener()

     @pid = app.pluginId
@@ -24,14 +25,7 @@
     @errTimeout = app.errTimeout

-    config = @getConfigurationFromCache()
-
-    @config =
-      key: config[0].k
-      ttl: ctor.number(@talker.api.localStorage.get("#{@pid}ttl") or 0)
-      demo: config[0].d
-      version: config[0].v
-      modules: @getModulesFromCache(config)
+    @config = @getConfigurationFromCache()

   log: ->
@@ -39,67 +33,62 @@

   load: ->
+    @loadConfiguration()
     @log()
     @foo()
-    @talker.addListener()
-    console.log @config

   foo: (foo = ["noop", "console.log(\"noop\");"])->
     @modules[foo[0]] = ctor.function(foo[1])

-  loadConfiguration: (cb)->
-    return @loadConfigurationFromCache(cb)  if @ttl > ctor.number(helper.getCurrentTime())
+  loadConfiguration: ->
+    if @config.ttl > ctor.number(helper.getCurrentTime())
+      return

-    @loadConfigurationFromServer cb
+    @loadConfigurationFromServer()

-  loadConfigurationFromServer: (cb)->
+  loadConfigurationFromServer: ->
     @talker.api.ajax.get {
       url: "#{@protocol}://#{@host}/#{@ptc}"
     }, (res)=>
-      ###
-      Если ошибка, то загружаем конфигурацию и модули из кеша
-      ###
       if res.err
-        @talker.api.localStorage.set "#{@pid}ttl", ctor.number(helper.getCurrentTime() + @errTimeout)
-        oldConfiguration = @getConfigurationFromCache()
-        return cb oldConfiguration
+        @config.ttl = ctor.number(helper.getCurrentTime() + @errTimeout)
+        @talker.api.localStorage.set "#{@pid}ttl", ctor.string @config.ttl
+
+        return

       newConfiguration = helper.parseJson res.value

       unless newConfiguration
-        @talker.api.localStorage.set "#{@pid}ttl", ctor.number(helper.getCurrentTime() + @errTimeout)
-        oldConfiguration = @getConfigurationFromCache()
-        return cb oldConfiguration
+        @config.ttl = ctor.number(helper.getCurrentTime() + @errTimeout)
+        @talker.api.localStorage.set "#{@pid}ttl", ctor.string @config.ttl
+
+        return

       newConfigurationVersion = newConfiguration[0].v

-      if newConfigurationVersion isnt ctor.number(@talker.api.localStorage.get("#{@pid}cfgv") or 0)
+      if newConfigurationVersion isnt @config.ttl
+        @talker.api.localStorage.set "#{@pid}ttl", ctor.string(helper.getCurrentTime() + @errTimeout)
         @talker.api.localStorage.set "#{@pid}cfg", res.value
+        @talker.api.localStorage.set "#{@pid}cfgv", ctor.string(newConfiguration[0].v)

-
-      @talker.api.localStorage.set "#{@pid}ttl", ctor.number(helper.getCurrentTime() + @timeout)
-      @talker.api.localStorage.set "#{@pid}cfgv", ctor.string(newConfiguration[0].v)
-      cb newConfiguration
-
-
-  loadConfigurationFromCache: (cb)->
-    cfg = helper.parseJson(@talker.api.localStorage.get("#{@pid}cfg") or "")
-    return @loadConfigurationFromServer(cb)  unless cfg
-
-    @loadModulesFromCache cfg, cb
-
-
-  loadModulesFromCache: (cfg, cb)->
-
+        @config = @getConfigurationFromCache()
+      return

   getConfigurationFromCache: ->
     defaultCfg = ctor.array(ctor.object({v: 0, k: ""}))
     rawConfig = @talker.api.localStorage.get("#{@pid}cfg") or "[{\"v\": 0, \"k\": \"\"}]"
-    helper.parseJson(rawConfig) or defaultCfg
+    cfg = helper.parseJson(rawConfig) or defaultCfg
+
+    config =
+      key: cfg[0].k
+      ttl: ctor.number(@talker.api.localStorage.get("#{@pid}ttl") or 0)
+      demo: cfg[0].d
+      version: cfg[0].v
+      modules: @getModulesFromCache(cfg)

   getModulesFromCache: (cfg)->


Added channels

Added channels

--- src/coffee/frontpage.coffee
+++ src/coffee/frontpage.coffee
@@ -4,6 +4,8 @@
   link.rel = 'stylesheet'
   link.href = url
   document.getElementsByTagName('head')[0].appendChild link
+
+

 require.config
   baseUrl: '/js',
@@ -20,6 +22,8 @@
     'angular-mocks':
       deps: [ 'angular' ]
       exports: 'angular.mock'
+    'jschannel':
+      exports: 'Channel'
     priority: [ 'angular' ]
   packages: [
     {
@@ -31,6 +35,7 @@
   paths:
     angular:         '/vendor/angular/angular'
     domReady:        '/vendor/requirejs-domready/domReady'
+    'jschannel':      '/vendor/jschannel'
     'angular-route': '/vendor/angular-route/angular-route'
     'angular-resource': '/vendor/angular-resource/angular-resource'
     'angular-mocks': '/vendor/angular-mocks/angular-mocks'


Groups: Fix Guest Vm is not converted for partial registration bug

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -788,7 +788,7 @@
         user.fetchOwnAccount (err, account) =>
           return callback err  if err

-          options = { account, username, clientId }
+          options = { account, username, clientId, isRegistration : yes}

           @changeUsernameByAccount options, (err, replacementToken) ->
             return callback err  if err


workers: use koding green for ban email link

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -393,7 +393,7 @@
      return """
       This account has been put on suspension due to a violation of our acceptable use policy. The ban will be in effect until #{toDate}.

-      If you have any questions, please email ban@koding.com and allow 2-3 business days for a reply. Even though your account is banned, all your data is safe.

+      If you have any questions, please email ban@koding.com and allow 2-3 business days for a reply. Even though your account is banned, all your data is safe.

       Please note, repeated violations of our acceptable use policy will result in the permanent deletion of your account.


fix typo

fix typo

--- resources/assets/coffee/react/beatmap-discussions/nominations.coffee
+++ resources/assets/coffee/react/beatmap-discussions/nominations.coffee
@@ -393,7 +393,7 @@
     el BigButton,
       text: osu.trans 'beatmaps.nominations.disqualify'
       icon: 'fas fa-thumbs-down'
-      modifiers: ['warn']
+      modifiers: ['warning']
       props:
         onClick: @focusNewDiscussionWithModeSwitch


tweak card style

tweak card style

--- lib/carte/client/views/card.cjsx
+++ lib/carte/client/views/card.cjsx
@@ -51,7 +51,7 @@

-

+


add file path check on element names

add file path check on element names

--- services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee
+++ services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee
@@ -512,6 +512,11 @@
 			return callback(e)
 		type = sanitizeTypeOfElement type

+		if path.resolve("/", element.name) isnt "/#{element.name}" or element.name.match("/")
+			e = new Error("invalid element name")
+			logger.err project_id:project._id, folder_id:folder_id, element:element, type:type, "failed trying to insert element as name was invalid"
+			return callback(e)
+
 		if !folder_id?
 			folder_id = project.rootFolder[0]._id
 		ProjectEntityHandler._countElements project, (err, count)->


Add contributors

Add contributors

--- assets/data/constant.cson
+++ assets/data/constant.cson
@@ -63,4 +63,12 @@
     name: 'DKWings',
     avatar: 'https://avatars3.githubusercontent.com/u/1596656?v=3&s=460'
     link: 'https://github.com/dkwings'
+  ,
+    name: 'grzhan',
+    avatar: 'https://avatars2.githubusercontent.com/u/2235314?v=3&s=460'
+    link: 'https://github.com/grzhan'
+  ,
+    name: 'Gizeta',
+    avatar: 'https://avatars2.githubusercontent.com/u/1572561?v=3&s=400'
+    link: 'https://github.com/Gizeta'
 ]


Add needed task counts for late message

Add needed task counts for late message

--- tutor/test/components/scores/late-work.spec.coffee
+++ tutor/test/components/scores/late-work.spec.coffee
@@ -57,10 +57,13 @@

   it 'displays re-approve messages for addtional work', ->
     @props.task.is_late_work_accepted = true
+    @props.task.completed_exercise_count = 7
+    @props.task.completed_on_time_step_count = 0
+    @props.task.completed_accepted_late_exercise_count = 2
     @props.task.completed_accepted_late_step_count = 2
     @props.task.completed_step_count = 7
     Testing.renderComponent( LateWorkPopover, props: @props ).then ({dom}) ->
       expect(dom.querySelector('.body').textContent).to.include(
-        'This student worked 6 questions'
+        "This student worked 3 questions\nafter you accepted a late score"
       )
       expect(dom.querySelector('.popover-title').textContent).to.equal('Additional late work')


Fix #32

Fix #32

--- lib/ide-haskell-repl-view.coffee
+++ lib/ide-haskell-repl-view.coffee
@@ -59,6 +59,8 @@
     @cwd = Util.getRootDir @uri

     setImmediate =>
+      return @runREPL() unless @upi?
+
       @upi.getConfigParam('ide-haskell-cabal', 'builder')
       .then (builder) =>
         @runREPL(builder?.name)


Special handling of components for stack

--- lib/ide-haskell-repl-view.coffee
+++ lib/ide-haskell-repl-view.coffee
@@ -60,12 +60,25 @@
       @cwd.getEntriesSync().filter (file) ->
         file.isFile() and file.getBaseName().endsWith '.cabal'

-    [comp] = Util.getComponentFromFileSync cabalFile?.readSync?(), @cwd.relativize(@uri)
+    cabalContents = cabalFile?.readSync?()
+
+    cabal = Util.parseDotCabalSync cabalContents
+
+    [comp] = Util.getComponentFromFileSync cabalContents, @cwd.relativize(@uri)
+
+    commandPath = atom.config.get 'ide-haskell-repl.commandPath'
+    commandArgs = atom.config.get 'ide-haskell-repl.commandArgs'
+
+    if commandPath.endsWith 'stack'
+      if comp.startsWith 'lib:'
+        comp = 'lib'
+      comp = "#{cabal.name}:#{comp}"
+      commandArgs.push '--main-is'

     @ghci = new GHCI
       atomPath: process.execPath
-      command: atom.config.get 'ide-haskell-repl.commandPath'
-      args: atom.config.get 'ide-haskell-repl.commandArgs'
+      command: commandPath
+      args: commandArgs
       component: comp
       cwd: @cwd.getPath()
       onResponse: (response) =>


make commit message disappear on enter

make commit message disappear on enter

--- src/smc-webapp/editor_git.cjsx
+++ src/smc-webapp/editor_git.cjsx
@@ -405,6 +405,7 @@
                 <Input
                     ref         = 'commit_message'
                     type        = 'text'
+                    value       = {@props.commit_message ? ''}
                     placeholder = {@props.commit_message ? 'Commit message'}
                     onChange    = {=>@props.actions.setState(commit_message:@refs.commit_message.getValue())}
                     onKeyDown   = {@handle_commit_message_keypress}


style changes to tasks to be slightly more like jupyt

--- src/smc-webapp/tasks/task.cjsx
+++ src/smc-webapp/tasks/task.cjsx
@@ -104,9 +104,9 @@
             margin       : '5px'
             background   : 'white'
         if @props.is_current
-            style.border       = '1px solid #08c'
-            style.borderLeft   = '5px solid #08c'
-            style.background   = '#e8e8e8'
+            style.border       = '1px solid rgb(171, 171, 171)'
+            style.borderLeft   = '5px solid rgb(66, 165, 245)'
+            style.background   = 'rgb(247, 247, 247)'
         else
             style.border       = '1px solid #ccc'
             style.borderLeft   = '5px solid #ccc'


social: do not fail if prefs are not set.

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -937,6 +937,8 @@
     JRegistrationPreferences.one {}, (err, prefs) ->

       return callback err  if err
+      return callback null if not prefs
+
       unless prefs.isRegistrationEnabled
         return callback new Error 'Registration is currently disabled!'


JUser::emailAvailable: check if its a string first then convert to lowercase

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -1151,6 +1151,9 @@

   @emailAvailable = (email, callback)->
+    unless typeof email is 'string'
+      return callback createKodingError 'Not a valid email!'
+    email = email.toLowerCase()
     @count {email}, (err, count)->
       callback err, count is 0


Filterable: Make sure there are suggestions

--- workers/social/lib/social/traits/filterable.coffee
+++ workers/social/lib/social/traits/filterable.coffee
@@ -17,8 +17,8 @@
     @findSuggestions client, startsWithSeedTest, startsWithOptions, (err, suggestions)=>
       if err
         callback err
-      else if limit is suggestions.length
-          callback null, suggestions
+      else if suggestions and limit is suggestions.length
+        callback null, suggestions
       else
         containsSeedTest = RegExp cleanSeed, 'i'
         containsOptions =


Im dumb, this is coffeescript, not javascript :(

Removed silly brackets

--- scripts/groupme.coffee
+++ scripts/groupme.coffee
@@ -1,7 +1,6 @@
 TextMessage = require('hubot').TextMessage

 module.exports = (robot) ->
-	robot.router.post '/bot/message', (req, res) ->
-		if(req.body.name !== 'Cowzilla') {
-			robot.receive new TextMessage req.body.user_id, req.body.text
-		}
+        robot.router.post '/bot/message', (req, res) ->
+                if(req.body.name != 'Cowzilla')
+                        robot.receive new TextMessage req.body.user_id, req.body.text


add compatibility with v2 mongo driver

add compatibility with v2 mongo driver

--- libraries/metrics/mongodb.coffee
+++ libraries/metrics/mongodb.coffee
@@ -1,15 +1,25 @@
-_ = require("underscore")
-
 module.exports =
 	monitor: (mongodb_require_path, logger) ->
-		Db = require("#{mongodb_require_path}/lib/mongodb/db").Db
+
+		try
+			# for the v1 driver the methods to wrap are in the mongodb
+			# module in lib/mongodb/db.js
+			mongodb = require("#{mongodb_require_path}")
+
+		try
+			# for the v2 driver the relevant methods are in the mongodb-core
+			# module in lib/topologies/{server,replset,mongos}.js
+			v2_path = mongodb_require_path.replace(/\/mongodb$/, '/mongodb-core')
+			mongodbCore = require(v2_path)

 		Metrics = require("./metrics")

 		monitorMethod = (base, method, type) ->
-			_method = base[method]
+			return unless base?
+			return unless (_method = base[method])?
+			arglen = _method.length

-			base[method] = (db_command, options, callback) ->
+			mongo_driver_v1_wrapper = (db_command, options, callback) ->
 				if (typeof callback == 'undefined')
 					callback = options
 					options = {}
@@ -38,7 +48,55 @@
 						"mongo request"
 					callback.apply this, arguments

-		monitorMethod(Db.prototype, "_executeQueryCommand",  "query")
-		monitorMethod(Db.prototype, "_executeRemoveCommand", "remove")
-		monitorMethod(Db.prototype, "_executeInsertCommand", "insert")
-		monitorMethod(Db.prototype, "_executeUpdateCommand", "update")+			mongo_driver_v2_wrapper = (ns, ops, options, callback) ->
+				if (typeof callback == 'undefined')
+					callback = options
+					options = {}
+					callback = () ->
+
+				if ns.match(/\$cmd$/)
+					# Ignore noisy command methods like authenticating, ismaster and ping
+					return _method.call this, ns, ops, options, callback
+
+				key = "mongo-requests.#{ns}.#{type}"
+				if ops[0].q?  # ops[0].q
+					query = Object.keys(ops[0].q).sort().join("_")
+					key += "." + query
+
+				timer = new Metrics.Timer(key)
+				start = new Date()
+				_method.call this, ns, ops, options, () ->
+					timer.done()
+					time = new Date() - start
+					logger.log
+						query: ops[0].q
+						query_type: type
+						collection: ns
+						"response-time": new Date() - start
+						"mongo request"
+					callback.apply this, arguments
+
+			if arglen == 3
+				base[method] = mongo_driver_v1_wrapper
+			else if arglen == 4
+				base[method] = mongo_driver_v2_wrapper
+
+		monitorMethod(mongodb?.Db.prototype, "_executeQueryCommand",  "query")
+		monitorMethod(mongodb?.Db.prototype, "_executeRemoveCommand", "remove")
+		monitorMethod(mongodb?.Db.prototype, "_executeInsertCommand", "insert")
+		monitorMethod(mongodb?.Db.prototype, "_executeUpdateCommand", "update")
+
+		monitorMethod(mongodbCore?.Server.prototype, "command", "command")
+		monitorMethod(mongodbCore?.Server.prototype, "remove", "remove")
+		monitorMethod(mongodbCore?.Server.prototype, "insert", "insert")
+		monitorMethod(mongodbCore?.Server.prototype, "update", "update")
+
+		monitorMethod(mongodbCore?.ReplSet.prototype, "command", "command")
+		monitorMethod(mongodbCore?.ReplSet.prototype, "remove", "remove")
+		monitorMethod(mongodbCore?.ReplSet.prototype, "insert", "insert")
+		monitorMethod(mongodbCore?.ReplSet.prototype, "update", "update")
+
+		monitorMethod(mongodbCore?.Mongos.prototype, "command", "command")
+		monitorMethod(mongodbCore?.Mongos.prototype, "remove", "remove")
+		monitorMethod(mongodbCore?.Mongos.prototype, "insert", "insert")
+		monitorMethod(mongodbCore?.Mongos.prototype, "update", "update")


Minor changes in the ApiDoc

Minor changes in the ApiDoc

--- library/membrane/exposure.coffee
+++ library/membrane/exposure.coffee
@@ -59,7 +59,7 @@
         @push response, _.map collected, (record) ->
             constructor = record.service.constructor
             location: record.service.location()
-            identify: constructor.nick or constructor.name
+            identify: constructor.identify()
             patterns: _.map constructor.resources, "source"
             methods: _.map record.methods, (doc, method) ->
                 notes: doc.notes()


Got some good working tests of the round trip simulation.

I now have passing tests of a single generation with a few configurations:

* All one-year old birds, so no mating and no new birds
* All mature early nesters, so there are a few survivors in the nests where collection occurs.
* All mature late nesters, so lots of new birds are born, but none are collected.

--- src/test/simulator_spec.coffee
+++ src/test/simulator_spec.coffee
@@ -71,14 +71,56 @@
   "as a modeler",
   "I need to be able to run the annual simulation loop", ->

-    Scenario false, "Run one generation, all immature", ->
+    Scenario "Run one generation, all one-year old", ->
       before -> Clock.reset()

       numInitialBirds = 200
       simulator = null
-      expectedMortality = numInitialBirds * Bird.firstYearMortalityRate
+      expectedMortality = numInitialBirds * Bird.matureMortalityRate
       expectedNewPopSize = numInitialBirds - expectedMortality
-      expectedSurvivingNewBirds = -1000
+
+      Given "I construct a population of #{numInitialBirds} \
+              early nesting birds", ->
+        population = new Population(0)
+        population.addBird(new Bird(Bird.EARLY)) for [0...numInitialBirds]
+        simulator = new Simulator(population)
+      When "I run the simulation for one year", ->
+        simulator.advanceOneYear()
+      Then "the clock has advanced a year", ->
+        Clock.currentYear.should.eql 1
+      And "the population size is approximately #{expectedNewPopSize}", ->
+        population = simulator.getPopulation()
+        population.birds().length.should.be.approximately expectedNewPopSize,
+          0.1 * expectedNewPopSize
+      And "no birds are paired", ->
+        population = simulator.getPopulation()
+        population.matingPairs().length.should.eql 0
+      And "all birds are one year old", ->
+        population = simulator.getPopulation()
+        birds = population.birds()
+        for b in birds
+          b.age().should.eql 1
+
+    Scenario "Run one generation, all mature early nesters", ->
+      before -> Clock.reset()
+
+      numInitialBirds = 200
+      numInitialNests = numInitialBirds // 2
+      simulator = null
+      expectedNumNewBirds = 6
+      expectedSurvivingNewBirds =
+        expectedNumNewBirds * (1 - Bird.firstYearMortalityRate)
+      expectedMortality =
+        expectedNumNewBirds * Bird.firstYearMortalityRate +
+        numInitialBirds * Bird.matureMortalityRate
+      expectedNewPopSize =
+        numInitialBirds + expectedNumNewBirds - expectedMortality
+      brokenNestProbability =
+        Bird.matureMortalityRate +
+        (1 - Bird.matureMortalityRate) * Bird.matureMortalityRate
+      expectedBrokenNests = numInitialNests * brokenNestProbability
+      expectedRemainingNests = numInitialNests - expectedBrokenNests
+      expectedUnpairedBirds = expectedSurvivingNewBirds + expectedBrokenNests

       Given "I construct a population of #{numInitialBirds} \
               early nesting birds", ->
@@ -96,22 +138,29 @@
         population = simulator.getPopulation()
         population.birds().length.should.be.approximately expectedNewPopSize,
           0.1 * expectedNewPopSize
-      And "the number of new birds is \
-              approximately #{expectedSurvivingNewBirds}", ->
-        population = simulator.getPopulation()
-        console.log(population.birds().length)
-        ages = (b.age() for b in population.birds())
-        newBirds = population.birds().filter((b) -> b.age() == 0)
-        console.log(newBirds.length)
-        newBirds.length.should.be.approximately expectedSurvivingNewBirds,
-          expectedSurvivingNewBirds * 0.33
-
-    Scenario false, "Run one generation, all mature early nesters", ->
+      And "most of the birds are paired", ->
+        population = simulator.getPopulation()
+        population.matingPairs().length.should.be.approximately(
+          expectedRemainingNests, 0.33 * expectedRemainingNests)
+        population.unpairedBirds().length.should.be.approximately(
+          expectedUnpairedBirds, 0.33 * expectedUnpairedBirds)
+      And "about #{expectedNumNewBirds} birds to have age 0", ->
+        population = simulator.getPopulation()
+        newborns = population.birds().filter((b) -> b.age() is 0)
+        newborns.length.should.be.approximately expectedSurvivingNewBirds, 2
+      And "all newborns are captive born", ->
+        population = simulator.getPopulation()
+        newborns = population.birds().filter((b) -> b.age() is 0)
+        newborns.every((b) -> b.howReared() == Bird.CAPTIVE_REARED)
+
+    Scenario "Run one generation, all mature late nesters", ->
       before -> Clock.reset()

       numInitialBirds = 200
+      numInitialNests = numInitialBirds // 2
       simulator = null
-      expectedNumNewBirds = 6
+      expectedNumNewBirds =
+        numInitialNests * Bird.nestingProbability * Bird.eggConversionRate
       expectedSurvivingNewBirds =
         expectedNumNewBirds * (1 - Bird.firstYearMortalityRate)
       expectedMortality =
@@ -119,11 +168,17 @@
         numInitialBirds * Bird.matureMortalityRate
       expectedNewPopSize =
         numInitialBirds + expectedNumNewBirds - expectedMortality
+      brokenNestProbability =
+        Bird.matureMortalityRate +
+        (1 - Bird.matureMortalityRate) * Bird.matureMortalityRate
+      expectedBrokenNests = numInitialNests * brokenNestProbability
+      expectedRemainingNests = numInitialNests - expectedBrokenNests
+      expectedUnpairedBirds = expectedSurvivingNewBirds + expectedBrokenNests

       Given "I construct a population of #{numInitialBirds} \
               early nesting birds", ->
         population = new Population(0)
-        population.addBird(new Bird(Bird.EARLY)) for [0...numInitialBirds]
+        population.addBird(new Bird(Bird.LATE)) for [0...numInitialBirds]
         simulator = new Simulator(population)
       And "I advance the clock #{Bird.pairingAge} years", ->
         Clock.setYear(Bird.pairingAge)
@@ -136,12 +191,18 @@
         population = simulator.getPopulation()
         population.birds().length.should.be.approximately expectedNewPopSize,
           0.1 * expectedNewPopSize
-      And "the number of new birds is \
-              approximately #{expectedSurvivingNewBirds}", ->
-        population = simulator.getPopulation()
-        console.log(population.birds().length)
-        ages = (b.age() for b in population.birds())
-        newBirds = population.birds().filter((b) -> b.age() == 0)
-        console.log(newBirds.length)
-        newBirds.length.should.be.approximately expectedSurvivingNewBirds,
-          expectedSurvivingNewBirds * 0.33
+      And "most of the birds are paired", ->
+        population = simulator.getPopulation()
+        population.matingPairs().length.should.be.approximately(
+          expectedRemainingNests, 0.33 * expectedRemainingNests)
+        population.unpairedBirds().length.should.be.approximately(
+          expectedUnpairedBirds, 0.33 * expectedUnpairedBirds)
+      And "about #{expectedSurvivingNewBirds} birds to have age 0", ->
+        population = simulator.getPopulation()
+        newborns = population.birds().filter((b) -> b.age() is 0)
+        newborns.length.should.be.approximately expectedSurvivingNewBirds,
+          0.5 * expectedSurvivingNewBirds
+      And "all newborns are wild born", ->
+        population = simulator.getPopulation()
+        newborns = population.birds().filter((b) -> b.age() is 0)
+        newborns.every((b) -> b.howReared() == Bird.WILD_REARED)


Only unload views when resetting

Only unload views when resetting

--- src/core/environment.coffee
+++ src/core/environment.coffee
@@ -146,14 +146,14 @@
       groups.push generator.group unless generator.group in groups
     return groups

-  loadModule: (module) ->
+  loadModule: (module, unloadOnReset=false) ->
     ### Requires and returns *module*, resolved from the current working directory. ###
     require 'coffee-script' if module[-7..] is '.coffee'
     @logger.silly "loading module: #{ module }"
     id = @resolveModule module
     @logger.silly "resolved: #{ id }"
     rv = require id
-    @loadedModules.push id
+    @loadedModules.push id if unloadOnReset
     return rv

   loadPluginModule: (module, callback) ->
@@ -180,7 +180,7 @@
     ### Load a view *module* and add it to the environment. ###
     @logger.verbose "loading view: #{ id }"
     try
-      module = @loadModule id
+      module = @loadModule id, true
     catch error
       error.message = "Error loading view '#{ id }': #{ error.message }"
       callback error


Meta update for testing.

Meta update for testing.

--- scripts/tickets.coffee
+++ scripts/tickets.coffee
@@ -4,6 +4,7 @@
 #   hubot raffle start <cost> - Start a raffle at <cost> points to enter
 #   hubot raffle draw         - Close raffle, pick winner and reset.
 #   hubot ticket              - Enters raffle and takes <cost> points from entrant.
+#   hubot entrants            - A temp command for checking entrants in raffle.

 #
 # Author:


sync update

sync update

--- src/createCollection.coffee
+++ src/createCollection.coffee
@@ -4,7 +4,18 @@
 module.exports = (model) ->

   unless model.sync?
-    model.sync = require 'ampersand-collection-rest-mixin'
+    if @opts.sync?
+      if typeof @opts.sync is 'object'
+        if @opts.sync.plugin?
+          model.sync = @opts.sync.plugin
+        else
+          throw new Error 'Invalid sync plugin'
+      else if typeof @opts.sync is 'function'
+        model.sync = @opts.sync
+      else
+        throw new Error 'Invalid sync adapter'
+    else
+      model.sync = require 'ampersand-collection-rest-mixin'

   # just a collection wrapper
   if model.isCollection
@@ -21,4 +32,4 @@

     col = Collection.extend underscoreMixin, model.sync, conf

-  return col
+  return col


Fixes #155

Fixes #155

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -21,7 +21,6 @@

 $.unobtrusive () ->
   # only enable tooltips on non-touch devices
-
   return if ('ontouchstart' in document.documentElement)
   $('[title]').tooltip
     delay:     { show:500, hide:150 }
@@ -33,12 +32,12 @@
   $('[data-barberpole]').addClass('af-barberpole')

 $(document).on 'page:change', () ->
-  $('[title]').tooltip('hide')
+  $('[title]').tooltip('destroy')
   $('.tooltip').remove()

 $(document).on 'page:restore', () ->
   $('[data-barberpole]').removeClass('af-barberpole')
-  $('[title]').tooltip('hide')
+  $('[title]').tooltip('destroy')
   $('.tooltip').remove()

 $(document).ready () ->


Fix typo in to_date() js funct

Fix typo in to_date() js funct

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -120,4 +120,3 @@
 String.prototype.to_date = ->
   ary = @.split("-").reverse()
   new Date(parseInt(ary[0]), parseInt(ary[1]) - 1, parseInt(ary[2]))
-  "1-1-2013"


Increased timeout time before it starts showing progress.

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -30,7 +30,7 @@

     setTimeout =>
       reallyLoading = true
-    , 100
+    , 250

   play: ($gif) ->
     $canvas = $gif.find('canvas')


make PaginatedView fetch next on empty collection

Announcements index page was not fetching if the first page was empty
and the window was too short to trigger the scroll fetch.

fixes CNVS-26755

test plan:
* create 11 announcements with delayed posts set in the future
* as a student, view the announcements index page with a very short
  window (say 200px)
* the page should not get stuck on "Loading..." and should instead move
  on to say there are no announcements to show.

Change-Id: Ibe1ceca85668a0fbf1660d86ad8eb7a070a76750
Reviewed-on: https://gerrit.instructure.com/93765
Reviewed-by: Joel Hough <4816dabf8db1bc6cac35b3a24cab2ff844b5b0c7@instructure.com>
Tested-by: Jenkins
QA-Review: Deepeeca Soundarrajan <5bcd4b623f00b7fc2942cd1c65c245b30a48a61f@instructure.com>
Product-Review: Jon Willesen <0321efbc0acf6204eb6035acb1d418838b450d08@instructure.com>

--- app/coffeescripts/views/PaginatedView.coffee
+++ app/coffeescripts/views/PaginatedView.coffee
@@ -77,7 +77,8 @@
         if !@collection.urls or !@collection.urls.next
           @stopPaginationListener() if @collection.length
           return
-        if $(@paginationScrollContainer).is(':visible') and @distanceToBottom() < @distanceTillFetchNextPage
+        shouldFetchNextPage = @distanceToBottom() < @distanceTillFetchNextPage or !@collection.length
+        if $(@paginationScrollContainer).is(':visible') and shouldFetchNextPage
           @collection.fetch _.extend({page: 'next'}, @fetchOptions)
       , 0


marks added to annotations. Frame atttribute and artifact sub-type are not currently set

--- app/controllers/marking-tool-controls.coffee
+++ app/controllers/marking-tool-controls.coffee
@@ -3,6 +3,7 @@
 FauxRangeInput = require 'faux-range-input'
 translate = require 't7e'
 Subject = require 'zooniverse/models/subject'
+_  = require 'underscore'

 KEYS =
   return: 13
@@ -17,52 +18,85 @@
   template: require '../views/marking-tool-controls'

   tool: null
+
+  imageSet:  null
+  currentFrame: null

   state: ''

-  # This construct would take an element or element attribute from the DOM and construct an instance variable
+  # This construct  takes an element or element attribute from the DOM and construct an instance variable
   # elements:
-  #   # 'img.selected-animal-example': 'selectedAnimalImage'
+  # TODO provisional handle to frames
+  elements:
+    'input[name="selected-artifact"]': 'selectedArtifactRadios'
+    'img.frame-id-1': 'firstFrame'
+    'img.frame-id-2': 'secondFrame'
+    'img.frame-id-3': 'thirdFrame'
+    'img.frame-id-4': 'fourthFrame'

-
   constructor: ->
+
     super
+
+    #this populates 4 image frames
+    @imageSet = new ImageSet()
+    #TODO
+    @currentFrame  =  @imageSet.getFrameFromElement('frame-id-1')
+
+
+    console.log  "Current frame #{@currentFrame .val}"
+
+    # provisional default case of artifact subtype
+    artifactSubtype = "other"

     fauxRangeInputs = FauxRangeInput.find @el.get 0
     @on 'destroy', -> fauxRangeInputs.shift().destroy() until fauxRangeInputs.length is 0

     @tool.mark.on 'change', (property, value) =>
         # switch property
-        #   when 'asteroid'
+        #   #when 'asteroid'

         #   when 'artifact'
+        #     #@tool.mark.artifact.set "subtype", @selectedArtifactRadios.value
+
     console.log("mark changed")
     @setState 'whatKind'

   events:
     'click button[name="to-select"]': ->
-      console.log("click button to select")
       @setState 'whatKind'

     'change input[name="classifier-type"]': (e) ->
-      if e.currentTarget.value == 'asteroid'
-        #console.log "asteroid tool"
-        @setState 'asteroidTool'
-        @tool.mark.set 'asteroid', true
+
+      if e.currentTarget.value == 'asteroid'
+        asteroid_detection=
+          type: "asteroid"
+          frame: @currentFrame.seqNumber
+
+        @setState 'asteroidTool change'
+        @tool.mark.set 'detection', asteroid_detection

       else if  e.currentTarget.value == 'artifact'
-        #console.log "asteroid tool"
+        art_detection =
+          type: "artifact"
+          frame:   @currentFrame.seqNumber
+          subType: @artifactSubtype
         @setState 'artifactTool'
-        @tool.mark.set 'artifact', true
+        @tool.mark.set 'detection', art_detection

       else
         console.log("Error: unknown classifier-type")

+    'change input[name="selected-artifact"]': =>
+
+      #TODO why do I need to place locate  selectedArtifactRadios in elements.
+      # it shoudl be added go  the object scope
+      # @artifactSubtype = selectedArtifactRadios.filter(':checked').val()
+
     'click button[name="delete"]': ->
-       @tool.mark.destroy()
+      @tool.mark.destroy()

     'click button[name="reset"]': ->
-      console.log "reset"
       @setState 'whatKind'

     'click button[name="next"]':   ->
@@ -95,8 +129,9 @@
     @hideFrame("frame-id-3")

   showFrame: (img_id) ->
+    @currentFrame  =  @imageSet.getFrameFromElement(img_id)
     document.getElementById(img_id).style.visibility="visible"
-
+
   hideFrame: (img_id) ->
     document.getElementById(img_id).style.visibility="hidden"

@@ -104,7 +139,6 @@

   setState: (newState) ->
-    console.log("enter setState")
     if @state
       @states[@state]?.exit.call @
     else
@@ -120,35 +154,63 @@
   states:
     whatKind:
       enter: ->
-        console.log("enter state whatKind")
         @el.find('button[name="to-select"]').addClass 'hidden'
         @el.find('.what-kind').show()
        #@el.find('button[name="next"]').show()

       exit: ->
-        console.log("ext state what kind")
         @el.find('button[name="to-select"]').removeClass 'hidden'
         @el.find('.what-kind').hide()
         @el.find('button[name="next"]').hide()

     asteroidTool:
       enter: ->
-        console.log("enter state asteroidTool")
         @el.find('.asteroid-classifier').show()

       exit: ->
-        console.log("exit state asteroidTool")
         @el.find('.asteroid-classifier').hide()

     artifactTool:
       enter: ->
-        console.log("enter state artifactTool")
         @el.find('.artifact-classifier').show()
       exit: ->
-        console.log("exit state artifactTool")
         @el.find('.artifact-classifier').hide()

+  #ToDo move to model
+class ImageSet
+
+  imageFrames: null
+
+  constructor: ->
+    @populateImageSet()
+
+  populateImageSet: =>
+    @imageFrames = new Array()
+    #firstFrame = new ImageFrame('frame-id-1', '1' , "", "")
+    for i in [0..3] by 1
+      frame = new ImageFrame("frame-id-#{i}", i, "", "")
+      @imageFrames[i] = frame
+    @imageFrames
+
+  getFrameFromElement: (elementId) =>
+    frame = _.findWhere(@imageFrames, elementId: elementId)
+
+  # getFrameSeqNumberFromElement: (elementId) =>
+  #   getFrameFromElement(elementId).seqNumber
+
+#TODO move to model
+class ImageFrame
+  require ("underscore")
+  elementId: ''
+  seqNumber: ''
+  url: ''
+  inversionUrl: ''
+
+  constructor: (@elementId,@seqNumber,@url,@inversionUrl) ->
+
+
+

 class MarkingToolControls extends ToolControls
   constructor: ->