10245: Tidy regenerable field view

10245: Tidy regenerable field view

--- app/assets/javascripts/views/regenerable_field_view.js.coffee
+++ app/assets/javascripts/views/regenerable_field_view.js.coffee
@@ -1,31 +1,31 @@
 class ELMO.Views.RegenerableFieldView extends ELMO.Views.ApplicationView

   events:
-    'click .regenerate': 'regenerate_field'
+    'click .regenerate': 'regenerateField'

-  regenerate_field: (event) ->
+  regenerateField: (event) ->
     event.preventDefault()

     target = $(event.currentTarget)
     handler = target.data('handler')
-    confirm_msg = target.data('confirm-msg')
+    confirmMsg = target.data('confirm-msg')

     container = target.closest('.regenerable-field')
     displayEl = container.find('span[data-value]')
-    inline_load_ind = container.find('div.inline-load-ind img')
-    success_indicator = container.find('.success')
-    error_indicator = container.find('.failure')
+    inlineLoadInd = container.find('div.inline-load-ind img')
+    successIndicator = container.find('.success')
+    errorIndicator = container.find('.failure')

     # If confirm text is provided and there is a current value,
     # show a confirmation dialog
-    if (confirm_msg && displayEl.data('value') && !window.confirm(confirm_msg))
+    if (confirmMsg && displayEl.data('value') && !window.confirm(confirmMsg))
       return false

     # Disable the button and ensure that only the loading indicator is shown
     target.attr('disabled', 'disabled')
-    success_indicator.hide()
-    error_indicator.hide()
-    inline_load_ind.show()
+    successIndicator.hide()
+    errorIndicator.hide()
+    inlineLoadInd.show()

     $.ajax
       method: 'patch'
@@ -36,10 +36,10 @@
         if (displayEl.length > 0)
           $(displayEl[0]).data(value: data.value)
           $(displayEl[0]).text(data.value)
-        inline_load_ind.hide()
-        success_indicator.show()
+        inlineLoadInd.hide()
+        successIndicator.show()
       error: ->
-        inline_load_ind.hide()
-        error_indicator.show()
+        inlineLoadInd.hide()
+        errorIndicator.show()
       complete: ->
         target.removeAttr('disabled')


update docs

update docs

--- server/jotgit.coffee
+++ server/jotgit.coffee
@@ -124,6 +124,10 @@
   # This is based on "Implementing a Git HTTP Server" by Michael F. Collins, III
   # http://www.michaelfcollins3.me/blog/2012/05/18/implementing-a-git-http-server.html
   #
+  # Some other libraries that may be helpful here:
+  # https://github.com/substack/pushover
+  # https://github.com/schacon/grack
+  #
   Router.map ->
     @route 'projectGitInfoRefs',
       path: '/project.git/info/refs'


Rename in manage

Rename in manage

--- server/manage.coffee
+++ server/manage.coffee
@@ -27,7 +27,7 @@
 randomUser = ->
   user = Random.choice Meteor.users.find().fetch()
   person = Persons.findOne
-    _id: user.person
+    _id: user.person.id

   id: user._id
   username: user.username


Restore cron

Restore cron

--- server/server.coffee
+++ server/server.coffee
@@ -14,7 +14,7 @@
 import configurePassport from './passport'
 import setupApi from './api/setupApi'

-#import jobs from './cron/cron'
+import jobs from './cron/cron'

 process.on 'unhandledRejection', (r) ->
     logger.error "Unhandled rejection "


add basic auth

add basic auth

--- server/server.coffee
+++ server/server.coffee
@@ -14,6 +14,7 @@
 app = express.createServer()

 app.use express.bodyParser()
+app.use express.basicAuth 'koding', '314159'
 app.use express.cookieParser()
 app.use express.session {"secret":"foo"}
 app.use gzippo.staticGzip "#{projectRoot}/website/"


Experimental markdown support.

Experimental markdown support.

--- server/server.coffee
+++ server/server.coffee
@@ -27,8 +27,6 @@
   catch
     console.log "ERROR\n#{module} is not available: sudo npm install -g #{module}"
     process.exit 1
-
-console.log html

 config =
   port: "9293"
@@ -122,7 +120,7 @@
       fs.readFile filename, "utf8", (error, data) ->
         return exit() if error
         console.log "  send:", filename
-        data = parseMarkdown data if args.markdown and request.isContentEditable
+        data = formatParagraphs data if request.isContentEditable
         request.text = data
         ws.send JSON.stringify request
         continuation?()
@@ -143,30 +141,26 @@
       return exit() if error
       sendText exit

-parseMarkdown = (text) ->
+isHTML = (text) ->
+  /^</.test(text) and />$/.test text
+
+formatParagraphs = (text) ->
   paragraphs =
     for paragraph in text.split "\n\n"
       paragraph = paragraph.trim()
-      isHTML = /.*>$/
-      if paragraph.length == 0 or isHTML.test paragraph
+      if paragraph.length == 0 or isHTML paragraph
+        # Leave HTML alone.
         paragraph
-      else
+      else if args.markdown
+        # Parse as Markdown.
         try
           text = html.prettyPrint markdown.markdown.toHTML paragraph
           text.replace(/
/g, "
\n").replace(/<\/p>/g, "\n<\/p>")
         catch
-          console.log "markdown/html processing failed (that's all I know!)."
           paragraph
+      else
+        # Surround the paragraph with

 tags.
+        "
\n#{paragraph}\n
"
+
   paragraphs.join "\n\n"

-wrapParagraphs = (text) ->
-  paragraphs = text.split "\n\n"
-  paragraphs =
-    for paragraph in paragraphs
-      paragraph = paragraph.trim()
-      if paragraph.length == 0 or /^<.*>$/.test paragraph
-        paragraph
-      else
-        "
\n#{paragraph}\n
"
-  paragraphs.join "\n\n"
-


Minor cleanup and fixed some outdated/incorrect todos

--- server/battle.coffee
+++ server/battle.coffee
@@ -91,7 +91,6 @@
       type: 'move'
       name: moveName

-    # TODO: use splice instead of delete
     delete @requests[player.id]

     # Continue the turn if each player has moved.
@@ -106,12 +105,18 @@
   # toPosition - the index of the pokemon to switch to
   #
   makeSwitch: (player, toPosition) =>
+    # TODO: Fail harder if pokemon not in team
+    # TODO: Add more cases of invalid indices (such as fainted poke or activePokemon)
+    pokemon = @getTeam(player.id).at(toPosition)
+    unless pokemon
+      console.log "#{player.username} made an invalid switch to position #{toPosition}."
+      return
+
     # Record the switch
     @playerActions[player.id] =
       type: 'switch'
       to: toPosition

-    # TODO: use splice instead of delete
     delete @requests[player.id]

     # Continue the turn if each player has moved.
@@ -124,11 +129,6 @@
     team = @getTeam(player.id)
     index = team.indexOf(toPokemon)

-    # TODO: Fail harder if pokemon not in team
-    if index == -1
-      console.log "#{player.username} made an invalid switch to #{toPokemon}."
-      return
-
     @makeSwitch(player, index)

   hasWeather: (weatherName) =>
@@ -172,11 +172,7 @@
     for id in @orderIds()
       continue  if id in skipIds

-      action = @getAction(id)
-      player = @getPlayer(id)
-      team = @getTeam(id)
-
-      switch action.type
+      switch @getAction(id).type
         when 'switch' then @performSwitch(id)
         when 'move'   then @performMove(id)

@@ -285,7 +281,6 @@
     action = @getAction(id)
     pokemon = @getTeam(id).at(0)
     defenders = @getOpponentPokemon(id, @numActive)
-    # todo: the move should be cloned and attached to the pokemon
     move = moves[action.name]

     @message "#{player.username}'s #{pokemon.name} used #{move.name}!"


Fixed: add fallback to Atom font

Fixed: add fallback to Atom font

--- dotfiles/.atom/config.cson
+++ dotfiles/.atom/config.cson
@@ -47,7 +47,7 @@
       "duotone-dark-sea-syntax"
     ]
   editor:
-    fontFamily: "Operator Mono"
+    fontFamily: "Operator Mono, Fira Code, Monaco"
     fontSize: 20
     invisibles:
       space: "‧"


System command passes through describeItNest now.

--- cakes.coffee
+++ cakes.coffee
@@ -208,7 +208,7 @@
 				msg = msg.cyan

 		label += ' '+msg
-		mocha[command] label, callback
+		describeItNest command, label, callback, options

 exports.System = createSystem(['label', 'style'])


fixed bugs

fixed bugs

--- core/app/backbone/factlink/tooltip_definition.coffee
+++ core/app/backbone/factlink/tooltip_definition.coffee
@@ -34,7 +34,7 @@

   onClose: ->
     @_removeTooltip()
-    @options.$container.off(".hoverIntent")
+    @options.$target.off(".hoverIntent")

   _openTooltip: ->
     @_$tooltip = @options.makeTooltip @options.$container, @$target
@@ -51,7 +51,7 @@
       @_openTooltip() if @model.hovered()

   _hoverintent: ($element, property)->
-    @_$tooltip.hoverIntent
+    $element.hoverIntent
       timeout: @_closingtimeout
       over: => @model.set property, true
       out:  => @model.set property, false


refactor image uploading

refactor image uploading

--- server/router.coffee
+++ server/router.coffee
@@ -24,7 +24,6 @@
             images[fieldname] =
               filename: filename
               mimetype: mimetype
-              timestamp: timestamp
             req.images = images
           catch error
             res.statusCode = 500
@@ -56,47 +55,42 @@
 Router.route '/image/:roomId/:imageType(presentation|presenter|screen)',
   where: 'server'
 .get ->
-  if @request.cookies.meteor_login_token and Meteor.users.findOne {'services.resume.loginTokens.hashedToken': Accounts._hashLoginToken @request.cookies.meteor_login_token}
+  # get hashed login token from client cookie
+  hashedToken = ''
+  if @request.cookies.meteor_login_token?
+    hashedToken = Accounts._hashLoginToken @request.cookies.meteor_login_token
+
+  user = Meteor.users.findOne
+    'services.resume.loginTokens.hashedToken': hashedToken
+
+  if user and isUserAuthorised user._id, ['admin', 'view', 'control']
     {roomId, imageType} = @params
-
+
     # Find all images that are stored for the room
     room = Rooms.findOne
       _id: roomId
       images:
         $exists: true
+    ,
+      fields:
+        images: 1
+        imageTimestamp: 1

     # Requested image
     reqImage = room?.images["#{imageType}"]
+    imagePath = null
+    if reqImage?
+      imagePath = path.join Meteor.settings.imageDir,
+        roomId, reqImage.filename

-    response = @response
-    redirect = ->
-      response.writeHead 302, 'Location': '/images/no_image_available.png'
-      response.end()
+    if imagePath and fs.existsSync imagePath
+      file = fs.readFileSync imagePath
+      @response.writeHead 200,
+        'Content-Type': reqImage.mimetype
+      @response.write file
+    else # image doesn't exist
+      @response.writeHead 302, 'Location': '/images/no_image_available.png'
+  else # auth failed
+    @response.writeHead 403

-    if reqImage
-      unless reqImage.filename
-        redirect()
-      unless reqImage.mimetype
-        redirect()
-      unless reqImage.timestamp
-        redirect()
-
-      # Check if requested image is stale
-      if room.imageTimestamp > reqImage.timestamp
-        redirect()
-
-      unless @response.finished
-        imagePath = path.join Meteor.settings.imageDir, roomId, reqImage.filename
-        if fs.existsSync imagePath
-          file = fs.readFileSync imagePath
-          @response.writeHead 200,
-            'Content-Type': reqImage.mimetype
-          @response.write file
-          @response.end()
-        else
-          redirect()
-    else
-      redirect()
-  else
-    @response.writeHead 403
-    @response.end()
+  @response.end()


Client: Activity: Fixed purpose special characters problem

--- client/activity/lib/components/channelthreadpane/index.coffee
+++ client/activity/lib/components/channelthreadpane/index.coffee
@@ -17,6 +17,8 @@
 Link                         = require 'app/components/common/link'
 ButtonWithMenu               = require 'app/components/buttonwithmenu'
 KeyboardKeys                 = require 'app/util/keyboardKeys'
+Encoder                      = require 'htmlencode'
+

 module.exports = class ChannelThreadPane extends React.Component

@@ -163,14 +165,15 @@

     return  unless @state.channelThread

-    thread = @state.channelThread
+    purpose = @state.channelThread.getIn ['channel', 'purpose']
+    purpose = Encoder.htmlDecode purpose

     valueLink =
-      value: thread.getIn ['channel', 'purpose']
+      value: purpose
       requestChange: @bound 'handleChange'

-      {thread.getIn ['channel', 'purpose']}
+      {purpose}
       <input ref='purposeInput' type='text' valueLink={valueLink} onKeyDown={@bound 'onKeyDown'} />


Client: Activity: Test: Using Comments.Container instead of Comments in feed item

--- client/activity/lib/components/publicfeedpane/feeditem.coffee
+++ client/activity/lib/components/publicfeedpane/feeditem.coffee
@@ -127,9 +127,9 @@
           <SocialShareLink messageId={message.get('id')} />
         </div>
         <MessageLikeSummary message={message} className="FeedItem-summary" />
-        <Comments
-          ref='Comments'
-          message={ @props.message }
-          channelId={ @props.channelId } />
+        <Comments.Container
+          ref       = 'Comments'
+          message   = { @props.message }
+          channelId = { @props.channelId } />
       </footer>
     </div>


add a space

add a space

--- client/apps/edit/components/content/section_list/index.coffee
+++ client/apps/edit/components/content/section_list/index.coffee
@@ -41,7 +41,7 @@
         (if @props.sections.length then ' esl-children' else '')
       ref: 'sections'
     },
-      SectionTool { sections: @props.sections, index: -1, key: 1}
+      SectionTool { sections: @props.sections, index: -1, key: 1 }
       if @props.sections.length > 0
         DragContainer {
           items: @props.sections.models


playlist: reset time before starting episode from list

--- src/javascripts/extensions/playlist.coffee
+++ src/javascripts/extensions/playlist.coffee
@@ -73,6 +73,7 @@
   playItem: (episode) =>
     @updateEpisodeData(episode)
     @app.player.loadFile()
+    @app.player.setCurrentTime(0)
     @app.player.play()
     @app.initializeExtensions(this)
     @app.extensions.ProgressBar.updateView()


move paths to config object

move paths to config object

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -20,40 +20,58 @@

 production = process.env.NODE_ENV is 'production'

+paths =
+  scripts:
+    source: './src/coffee/main.coffee'
+    destination: './public/js/'
+    filename: 'bundle.js'
+  templates:
+    source: './src/jade/*.jade'
+    watch: './src/jade/*.jade'
+    destination: './public/'
+  styles:
+    source: './src/stylus/style.styl'
+    watch: './src/stylus/*.styl'
+    destination: './public/css/'
+  assets:
+    source: './src/assets/**/*.*'
+    watch: './src/assets/**/*.*'
+    destination: './public/'
+
 gulp.task 'scripts', ->

-  bundle = browserify('./src/coffee/main.coffee')
+  bundle = browserify paths.scripts.source

   build = bundle.bundle(debug: not production)
-    .pipe(source('bundle.js'))
+    .pipe source paths.scripts.filename

   build.pipe(streamify(uglify())) if production

   build
-    .pipe(gulp.dest('./public/js/'))
+    .pipe gulp.dest paths.scripts.destination

 gulp.task 'templates', ->
   gulp
-    .src('src/jade/*.jade')
+    .src paths.templates.source
     .pipe(jade(pretty: not production))
-    .pipe(gulp.dest('public/'))
+    .pipe gulp.dest paths.templates.destination
     .pipe livereload(reloadServer)

 gulp.task 'styles', ->
   styles = gulp
-    .src('src/stylus/style.styl')
+    .src paths.styles.source
     .pipe(stylus({set: ['include css']}))
     .pipe(prefix("last 1 version", "> 1%", "ie 8"))

   styles.pipe(CSSmin()) if production

-  styles.pipe(gulp.dest('public/css/'))
+  styles.pipe gulp.dest paths.styles.destination
     .pipe livereload reloadServer

 gulp.task 'assets', ->
   gulp
-    .src('src/assets/**/*.*')
-    .pipe gulp.dest 'public/'
+    .src paths.assets.source
+    .pipe gulp.dest paths.assets.destination

 gulp.task "server", ->
   staticFiles = new nodeStatic.Server './public'
@@ -66,18 +84,18 @@
 gulp.task "watch", ->
   reloadServer.listen 35729

-  gulp.watch 'src/jade/*.jade', ['templates']
-  gulp.watch 'src/stylus/*.styl', ['styles']
-  gulp.watch 'src/assets/**/*.*', ['assets']
+  gulp.watch paths.templates.watch, ['templates']
+  gulp.watch paths.styles.watch, ['styles']
+  gulp.watch paths.assets.watch, ['assets']

-  bundle = watchify('./src/coffee/main.coffee')
+  bundle = watchify paths.scripts.source

   bundle.on 'update', ->
     build = bundle.bundle(debug: not production)
-      .pipe(source('bundle.js'))
+      .pipe source paths.scripts.filename

     build
-      .pipe(gulp.dest('./public/js/'))
+      .pipe gulp.dest paths.scripts.destination
       .pipe(livereload(reloadServer))

   .emit 'update'


more efficient, targeted compass compilation, with room for further improvement...

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -26,15 +26,19 @@
 gulp.task "compass", ->
   glob o.compass.config, { sync: true, nonull: false }, (e, files) ->
     configs = for file in files then path.resolve(__dirname, file)
-    compile = (config) ->
+    compile = (config, file) ->
       root = path.dirname(config)
-      exec "compass compile -c #{config}"
+      # NOTE: skipped tracking sass import dependencies...
+      # Thus, compile the whole project if a given file is _{underscored} for @import.
+      file = "" if typeof(file) isnt "string" or path.basename(file).indexOf("_") is 0
+      exec "compass compile -c #{config} #{file}"
       , {cwd: root}
       , (error, stdout, stderr) ->
         console.log("[compass] compiling #{root}")
         process.stdout.write(stdout)
         console.log("[compass] error(s) above") if error isnt null

+    # Compile all compass projects just in case something has changed.
     for conf in configs
       gutil.log("Compass #{path.basename(conf)} in #{path.dirname(conf)}")
       compile(conf)
@@ -43,7 +47,7 @@
       unless event.type is "deleted" # if added or changed
         for conf in configs
           if event.path.indexOf(path.dirname(conf)) is 0
-            compile(conf)
+            compile(conf, event.path)
             break
       else console.log("Note: unhandled deletion of #{event.path}")

@@ -51,7 +55,8 @@
 gulp.task "livereload", ->
   reload = livereload()
   gulp.watch o.livereload.watch, (event) ->
-    gutil.log "#{path.basename(event.path)} has been #{event.type}"
+    console.log "[watched] #{path.basename(event.path)} has been #{event.type}"
+    # NOTE: unsure if it makes sense to live-reload on added or deleted files
     reload.changed(event.path)


trivial

trivial

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -42,9 +42,6 @@
         if _.isString config then config = require config
         require(module)(config)

-    npm_resolve: (rest...) ->
-        path.resolve [__dirname, val.npm, rest...]...
-
     prod: !!$.util.env.production
 }

@@ -123,7 +120,6 @@

         not utils.prod and _.map (item) ->
             item.replace '.min.js', '.js'
-

         _.map utils.path.npm
     ]


Remove old task

Remove old task

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -22,10 +22,6 @@
       .pipe sourcemaps.write('.')
       .pipe gulp.dest('./public/assets/dist/')

-gulp.task 'coffee:clean', ->
-  gulp.src ['./app/compiled'], read: false
-      .pipe clean()
-
 gulp.task 'coffee:compile', ['coffee:clean'], ->
   gulp.src ['./public/app/**/*.coffee']
       .pipe plumber()


fix gulpfile.coffee

fix gulpfile.coffee

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -1,9 +1,10 @@
 gulp = require 'gulp'
-require('require-dir')('./gulp')

-gulp.pths =
+gulp.paths =
   dist: 'dist'
   src: 'src'
+
+require('require-dir')('./gulp')

 gulp.task 'src', ['js']


DRYing up gulpfile watch tasks, organizing file

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -22,6 +22,7 @@
 STYLES_PATH = require './src/themes/styl.includes.coffee'
 ENTRY_PATH  = ['./playground/main.coffee']
 COFFEE_PATH = ['./src/components/**/*.coffee','./src/core/**/*.coffee','./src/init.coffee']
+LIBS_PATH   = ['./libs/*.js']
 TEST_PATH   = ['./test/**/*.coffee']
 LIBS        = require './src/lib.includes.coffee'

@@ -31,6 +32,7 @@
 useUglify      = !!argv.uglify
 useMinify      = !!(argv.minify ? yes)

+# Build Tasks

 gulp.task 'styles', ->

@@ -56,7 +58,7 @@
     .pipe rename "kd.libs.#{version}js"
     .pipe gulp.dest "#{buildDir}/js"

-  stream.pipe(livereload())  if useLiveReload
+  stream.pipe livereload()  if useLiveReload

 gulp.task 'coffee', ->
@@ -80,7 +82,8 @@

     stream.pipe livereload()  if useLiveReload

-gulp.task 'coffee-test', ->
+
+gulp.task 'test', ->

   stream = gulp.src './test/test.coffee', { read: false }
     .pipe browserify
@@ -94,47 +97,6 @@
     stream.pipe livereload()
     gulp.src './test/index.html'
       .pipe livereload()
-
-gulp.task 'watch-test', ->
-
-  watcher = gulp.watch TEST_PATH, ['coffee-test']
-
-  watcher.on 'change', (event)->
-    gutil.log gutil.colors.cyan "file #{event.path} was #{event.type}"
-
-gulp.task 'watch-coffee', ->
-
-  watcher = gulp.watch COFFEE_PATH, ['coffee']
-
-  watcher.on 'change', (event)->
-    gutil.log gutil.colors.yellow "file #{event.path} was #{event.type}"
-
-
-gulp.task 'watch-libs', ->
-
-  watcher = gulp.watch './libs/*.js', ['libs']
-
-  watcher.on 'change', (event)->
-    gutil.log gutil.colors.yellow "file #{event.path} was #{event.type}"
-
-
-gulp.task 'watch-styles', ->
-
-  watcher = gulp.watch STYLES_PATH, ['styles']
-
-  watcher.on 'change', (event)->
-    gutil.log gutil.colors.magenta "file #{event.path} was #{event.type}"
-
-
-gulp.task 'watch-playground', ->
-
-  watcher = gulp.watch [
-    './playground/**/*.coffee'
-    './playground/**/*.html'
-  ], ['play']
-
-  watcher.on 'change', (event)->
-    gutil.log gutil.colors.blue "file #{event.path} was #{event.type}"

 gulp.task 'play', ->
@@ -155,15 +117,51 @@

 gulp.task 'live', -> useLiveReload = yes

-gulp.task 'compile', ['styles', 'libs', 'coffee']
+# Watch Tasks
+
+watchLogger = (color, watcher) ->
+  watcher.on 'change', (event) ->
+    gutil.log gutil.colors[color] "file #{event.path} was #{event.type}"

+gulp.task 'watch-test', ->
+  watcher = gulp.watch TEST_PATH, ['test']
+  watchLogger 'cyan', watcher
+
+
+gulp.task 'watch-coffee', ->
+  watcher = gulp.watch COFFEE_PATH, ['coffee']
+  watchLogger 'yellow', watcher
+
+
+gulp.task 'watch-libs', ->
+  watcher = gulp.watch LIBS_PATH, ['libs']
+  watchLogger 'yellow', watcher
+
+
+gulp.task 'watch-styles', ->
+  watcher = gulp.watch STYLES_PATH, ['styles']
+  watchLogger 'yellow', watcher
+
+
+gulp.task 'watch-playground', ->
+  watcher = gulp.watch [
+    './playground/**/*.coffee'
+    './playground/**/*.html'
+  ], ['play']
+
+  watchLogger 'blue', watcher
+
+
+# Aggregate Tasks
+
+gulp.task 'compile', ['styles', 'libs', 'coffee', 'test']
+
 defaultTasks = [
-  'live', 'compile', 'coffee-test', 'play',
+  'live', 'compile', 'play',
   'watch-styles', 'watch-coffee', 'watch-libs',
   'watch-playground', 'watch-test'
 ]
-

 gulp.task 'default', defaultTasks , ->


Bind orbital controls to canvas

Bind orbital controls to canvas

--- src/scripts/wowser/ui/screens/world.coffee
+++ src/scripts/wowser/ui/screens/world.coffee
@@ -8,7 +8,7 @@
     @camera.position.y = 500
     @camera.position.x = 500

-    @controls = new THREE.OrbitControls @camera
+    @controls = new THREE.OrbitControls @camera, @$element[0]
     @controls.noKeys = true
     @controls.minPolarAngle = Math.PI * 0.05
     @controls.maxPolarAngle = Math.PI * 0.45


Make API configuration more configurable

--- app/api/config.coffee
+++ app/api/config.coffee
@@ -10,10 +10,17 @@
   staging: '535759b966935c297be11913acee7a9ca17c025f9f15520e7504728e71110a27'
   cam: '05fd85e729327b2f71cda394d8e87e042e0b77b05e05280e8246e8bdb05d54ed'

-BROWSER_ENV_PARAM = location?.search.match(/\W?env=(\w+)/)?[1]
+hostFromBrowser = location?.search.match(/\W?panoptes-api-host=([^&]+)/)?[1]
+appFromBrowser = location?.search.match(/\W?panoptes-api-application=([^&]+)/)?[1]

-env = BROWSER_ENV_PARAM ? process.env.NODE_ENV ? DEFAULT_ENV
+hostFromShell = process.env.PANOPTES_API_HOST
+appFromShell = process.env.PANOPTES_API_APPLICATION
+
+envFromBrowser = location?.search.match(/\W?env=(\w+)/)?[1]
+envFromShell = process.env.NODE_ENV
+
+env = envFromBrowser ? envFromShell ? DEFAULT_ENV

 module.exports =
-  host: API_HOSTS[env]
-  clientAppID: API_APPLICATION_IDS[env]
+  host: hostFromBrowser ? hostFromShell ? API_HOSTS[env]
+  clientAppID: appFromBrowser ? appFromShell ? API_APPLICATION_IDS[env]


Extend forms

Extend forms

* formToObject handles checkboxes and radio buttons
* applyErrorsToForms handles required fields
* setErrorToField sets helpblock according to new forms best practices: before input and any help blocks

--- app/core/forms.coffee
+++ app/core/forms.coffee
@@ -1,22 +1,35 @@
-module.exports.formToObject = (el) ->
+module.exports.formToObject = ($el, options) ->
+  options = _.extend({ trim: true, ignoreEmptyString: true }, options)
   obj = {}

-  inputs = $('input', el).add('textarea', el)
+  inputs = $('input, textarea, select', $el)
   for input in inputs
     input = $(input)
     continue unless name = input.attr('name')
-    obj[name] = input.val()
-    obj[name] = obj[name].trim() if obj[name]?.trim
-
+    if input.attr('type') is 'checkbox'
+      obj[name] ?= []
+      if input.is(':checked')
+        obj[name].push(input.val())
+    else if input.attr('type') is 'radio'
+      continue unless input.is('checked')
+      obj[name] = input.val()
+    else
+      value = input.val() or ''
+      value = _.string.trim(value) if options.trim
+      if value or (not options.ignoreEmptyString)
+        obj[name] = value
   obj

 module.exports.applyErrorsToForm = (el, errors, warning=false) ->
   errors = [errors] if not $.isArray(errors)
   missingErrors = []
   for error in errors
-    if error.dataPath
+    if error.code is tv4.errorCodes.OBJECT_REQUIRED
+      prop = _.last(_.string.words(error.message)) # hack
+      message = 'Required field'
+
+    else if error.dataPath
       prop = error.dataPath[1..]
-      console.log prop
       message = error.message

     else
@@ -35,8 +48,13 @@
     return console.error el, " did not contain a form group, so couldn't show message:", message

   kind = if warning then 'warning' else 'error'
+  afterEl = $(formGroup.find('.help-block, .form-control, input, select, textarea')[0])
   formGroup.addClass "has-#{kind}"
-  formGroup.append $("#{message}")
+  helpBlock = $("#{message}")
+  if afterEl.length
+    afterEl.before helpBlock
+  else
+    formGroup.append helpBlock

 module.exports.setErrorToProperty = setErrorToProperty = (el, property, message, warning=false) ->
   input = $("[name='#{property}']", el)


Load subcollections of aritrary depth

Load subcollections of aritrary depth

--- src/scripts/models/content.coffee
+++ src/scripts/models/content.coffee
@@ -61,15 +61,19 @@
     # Create a flat collection to store all the pages
     setupToc: () ->
       toc = new Backbone.Collection()
+      index = 0

-      _.each @get('tree').contents, (item, index, contents) ->
-        if item.contents
-          _.each item.contents, (module, ind, subcol) ->
-            module.parent = index # Module parents are numbered by their position in the tree
-            toc.add(module)
-        else
-          toc.add(item)
+      traverse = (o, sub) ->
+        if typeof o isnt 'object' then return
+        for item in o.contents
+          if item.contents
+            traverse(item, true)
+            index++
+          else
+            if sub then item.parent = index # Item parents are numbered by their position in the tree
+            toc.add(item)

+      traverse(@get('tree'))
       @set('toc', toc)
       @set('pages', toc.models.length)


Added smooth scrolling.

Added smooth scrolling.

--- app/lib/routes.coffee
+++ app/lib/routes.coffee
@@ -2,6 +2,15 @@
   layoutTemplate: 'SimpleLayout'
   loadingTemplate: 'Loading'
   notFoundTemplate: 'NotFound'
+
+Router._scrollToHash = (hash) ->
+  hash = 'body' unless hash.length
+  $section = $ hash
+  if $section.length
+    $('html, body').animate
+      scrollTop: $section.offset().top
+    ,
+      'slow'

 Router.route '/',
   name: 'home'


Regex simplification

Regex simplification

--- packages/shrub-grunt/dox/dynamic.coffee
+++ packages/shrub-grunt/dox/dynamic.coffee
@@ -20,7 +20,7 @@

   _transform: (chunk, encoding, done) ->
     line = chunk.toString('utf8')
-    if matches = line.match /^\s*# \#\#\#\# Invoke hook `([^`]+)`/
+    if matches = line.match /^\s*# #### Invoke hook `([^`]+)`/
       @list.push matches[1]

     done()
@@ -36,7 +36,7 @@

   _transform: (chunk, encoding, done) ->
     line = chunk.toString('utf8')
-    if matches = line.match /^\s*# \#\#\#\# Implements hook `([^`]+)`/
+    if matches = line.match /^\s*# #### Implements hook `([^`]+)`/
       @list.push matches[1]

     done()
@@ -55,7 +55,7 @@

   _transform: (chunk, encoding, done) ->
     @lines.push line = chunk.toString('utf8')
-    if matches = line.match /^\s*# \#\#\#\#\#\# TODO/
+    if matches = line.match /^\s*# ###### TODO/
       @todos.push @lines.length - 1

     done()


hotfix- return blank text-string if value is undefined

--- shared/shared.coffee
+++ shared/shared.coffee
@@ -36,6 +36,7 @@
             NestedCollection.insert(newNest)

 share.getPercentOrText = (txt) ->
+    if not txt? then return ""
     if (txt.search(/(\d)+/) >= 0) then txt += "%"
     return txt


modify spec file

modify spec file

--- spec/jade-beautify-specx.coffee
+++ spec/jade-beautify-specx.coffee
@@ -1,62 +0,0 @@
-JadeBeautify = require '../lib/jade-beautify'
-
-# Use the command `window:run-package-specs` (cmd-alt-ctrl-p) to run specs.
-#
-# To run a specific `it` or `describe` block add an `f` to the front (e.g. `fit`
-# or `fdescribe`). Remove the `f` to unfocus the block.
-
-describe "JadeBeautify", ->
-  [workspaceElement, activationPromise] = []
-
-  beforeEach ->
-    workspaceElement = atom.views.getView(atom.workspace)
-    activationPromise = atom.packages.activatePackage('jade-beautify')
-
-  describe "when the jade-beautify:convert event is triggered", ->
-    it "hides and shows the modal panel", ->
-      # Before the activation event the view is not on the DOM, and no panel
-      # has been created
-      expect(workspaceElement.querySelector('.jade-beautify')).not.toExist()
-
-      # This is an activation event, triggering it will cause the package to be
-      # activated.
-      atom.commands.dispatch workspaceElement, 'jade-beautify:convert'
-
-      waitsForPromise ->
-        activationPromise
-
-      runs ->
-        expect(workspaceElement.querySelector('.jade-beautify')).toExist()
-
-        jadeBeautifyElement = workspaceElement.querySelector('.jade-beautify')
-        expect(jadeBeautifyElement).toExist()
-
-        jadeBeautifyPanel = atom.workspace.panelForItem(jadeBeautifyElement)
-        expect(jadeBeautifyPanel.isVisible()).toBe true
-        atom.commands.dispatch workspaceElement, 'jade-beautify:convert'
-        expect(jadeBeautifyPanel.isVisible()).toBe false
-
-    it "hides and shows the view", ->
-      # This test shows you an integration test testing at the view level.
-
-      # Attaching the workspaceElement to the DOM is required to allow the
-      # `toBeVisible()` matchers to work. Anything testing visibility or focus
-      # requires that the workspaceElement is on the DOM. Tests that attach the
-      # workspaceElement to the DOM are generally slower than those off DOM.
-      jasmine.attachToDOM(workspaceElement)
-
-      expect(workspaceElement.querySelector('.jade-beautify')).not.toExist()
-
-      # This is an activation event, triggering it causes the package to be
-      # activated.
-      atom.commands.dispatch workspaceElement, 'jade-beautify:convert'
-
-      waitsForPromise ->
-        activationPromise
-
-      runs ->
-        # Now we can test for view visibility
-        jadeBeautifyElement = workspaceElement.querySelector('.jade-beautify')
-        expect(jadeBeautifyElement).toBeVisible()
-        atom.commands.dispatch workspaceElement, 'jade-beautify:convert'
-        expect(jadeBeautifyElement).not.toBeVisible()


:white_check_mark: by BIG NUMBER(this time use 10)

--- spec/motion-general-spec.coffee
+++ spec/motion-general-spec.coffee
@@ -99,6 +99,28 @@
           ensure 'k', selectedText: text.getLines([0, 1])
           ensure 'j', selectedText: text.getLines([1])
           ensure 'j', selectedText: text.getLines([1, 2])
+
+    describe "with big count was given", ->
+      BIG_NUMBER = null
+      ensureBigCountMotion = (number, keystroke, options) ->
+        count = String(number).split('').join(' ')
+        ensure("#{count} #{keystroke}", options)
+
+      beforeEach ->
+        BIG_NUMBER = Number.MAX_SAFE_INTEGER
+        BIG_NUMBER = 10
+        set
+          text: """
+          0000
+          1111
+          2222
+          """
+          cursor: [1, 2]
+
+      it "by `j`", -> ensureBigCountMotion BIG_NUMBER, 'j', cursor: [2, 2]
+      it "by `k`", -> ensureBigCountMotion BIG_NUMBER, 'k', cursor: [0, 2]
+      it "by `h`", -> ensureBigCountMotion BIG_NUMBER, 'h', cursor: [1, 0]
+      it "by `l`", -> ensureBigCountMotion BIG_NUMBER, 'l', cursor: [1, 3]

     describe "the k keybinding", ->
       beforeEach ->


silence rocketbot on test

silence rocketbot on test

--- packages/rocketchat-hubot/hubot.coffee
+++ packages/rocketchat-hubot/hubot.coffee
@@ -280,8 +280,8 @@
 HubotScripts(RocketBot)
 RocketBot.run()

-RocketBot.hear /^test/i, (res) ->
-	res.send "Test? TESTING? WE DON'T NEED NO TEST, EVERYTHING WORKS!"
+# RocketBot.hear /^test/i, (res) ->
+#	res.send "Test? TESTING? WE DON'T NEED NO TEST, EVERYTHING WORKS!"

 RocketChat.callbacks.add 'afterSaveMessage', RocketBotReceiver, RocketChat.callbacks.priority.LOW


Added instantiate before collectionsAlter, and added collection.destroyAll()

--- packages/shrub-orm/client/index.coffee
+++ packages/shrub-orm/client/index.coffee
@@ -77,26 +77,31 @@
 			collection.connection ?= 'shrub'
 			collection.identity ?= identity
 			collections_[collection.identity] = collection
-
+
+			# Instantiate a model with defaults supplied.
+			collection.instantiate = (values = {}) ->
+
+				for key, value of @attributes
+					continue unless value.defaultsTo?
+
+					values[key] ?= if 'function' is typeof value.defaultsTo
+						value.defaultsTo.call values
+					else
+						JSON.parse JSON.stringify value.defaultsTo
+
+				new @_model @_schema.cleanValues @_transformer.serialize values
+
+			# Destroy all instances of a model.
+			collection.destroyAll = ->
+				@find().then (models) ->
+					Promise.all model.destroy() for model in models
+
 	# Invoke hook `collectionsAlter`.
 	# Allows packages to alter any Waterline collections defined.
 	pkgman.invoke 'collectionsAlter', collections_, waterline

 	# Load the collections into Waterline.
 	for i, collection of collections_
-
-		collection.instantiate = (values = {}) ->
-
-			for key, value of @attributes
-				continue unless value.defaultsTo?
-
-				values[key] ?= if 'function' is typeof value.defaultsTo
-					value.defaultsTo.call values
-				else
-					JSON.parse JSON.stringify value.defaultsTo
-
-			new @_model @_schema.cleanValues @_transformer.serialize values
-
 		Collection = Waterline.Collection.extend collection
 		waterline.loadCollection Collection


removed commetned out code

removed commetned out code

--- src/test/tests/testWikiDay.coffee
+++ src/test/tests/testWikiDay.coffee
@@ -58,73 +58,6 @@

 testDrivers = utils.makeDriverTest(driverFns)

-
-# query = [
-#   { operation: 'split', name: 'Histo', bucket: 'continuous', attribute: 'delta_hist', offset: 0, size: 5 }
-#   { operation: 'apply', name: 'Count', aggregate: 'count' }
-#   { operation: 'combine', sort: { compare: 'natural', prop: 'Count', direction: 'ascending' } }
-# ]
-###
-query = [
-  {
-        "type": "within",
-        "attribute": "time",
-        "range": [
-          "2013-03-30T00:00:00.000Z",
-          "2013-04-06T00:00:00.000Z"
-        ],
-        "operation": "filter"
-      },
-      {
-        "name": "namespace",
-        "attribute": "namespace",
-        "bucket": "identity",
-        "operation": "split"
-      },
-      {
-        "name": "count",
-        "aggregate": "sum",
-        "attribute": "count",
-        "operation": "apply"
-      },
-      {
-        "operation": "combine",
-        "sort": {
-          "compare": "natural",
-          "prop": "count",
-          "direction": "descending"
-        },
-        "limit": 5
-      },
-      {
-        "name": "delta_hist",
-        "attribute": "delta_hist",
-        "bucket": "continuous",
-        "size": 5,
-        "offset": 0,
-        "operation": "split"
-      },
-      {
-        "name": "edits",
-        "aggregate": "count",
-        "operation": "apply"
-      },
-      {
-        "operation": "combine",
-        "sort": {
-          "compare": "natural",
-          "prop": "delta_hist",
-          "direction": "descending"
-        },
-        "limit": 5
-      }
-  ]
-
-driverFns.druid query, (err, data) ->
-  console.log JSON.stringify(err, null, 2), JSON.stringify(data, null, 2)
-  return
-
-###

 exports["apply count"] = testDrivers {
   drivers: ['mySql', 'druid']


debug

debug

--- JSKit/04_JSMenuView.coffee
+++ JSKit/04_JSMenuView.coffee
@@ -46,7 +46,7 @@
                 if (@_userInteractionEnabled == false)
                     return
                 obj = ui.item
-                JSLog(obj.value())
+                JSLog($(obj).value())
                 @selectMenuItem(item)
                 @closeMenu()
             classes:


Cache server list for quick loading

Cache server list for quick loading

--- admin/static/coffee/servers/index.coffee
+++ admin/static/coffee/servers/index.coffee
@@ -40,8 +40,11 @@

 class View extends Backbone.View
     initialize: (options) ->
-        if not options?.model
-            @model = new Model()
+        if not app.view_data_backup.servers_view?
+            @model = app.view_data_backup.servers_view = new Model()
+        else
+            @model = app.view_data_backup.servers_view
+
         @listenTo @model, "change", @render
         @current_vdom_tree = @render_vdom()
         @setElement(createElement(@current_vdom_tree))


Return view from CollectionView#insertView.

No tests, just a simple useful change.

--- src/chaplin/views/collection_view.coffee
+++ src/chaplin/views/collection_view.coffee
@@ -408,7 +408,7 @@
         # Fade the view in if it was made transparent before.
         $viewEl.animate {opacity: 1}, @animationDuration

-    return
+    view

   # Remove the view for an item.
   removeViewForItem: (item) ->


remove css on the plugin

remove css on the plugin

--- src/coffee/responsive.breadcrumbs.coffee
+++ src/coffee/responsive.breadcrumbs.coffee
@@ -124,13 +124,8 @@
                 .each ->
                     current = $ this

-                    # set the elements float left and display inline-block and store the width to the element
-                    current
-                        .css(
-                             float: 'left',
-                             display: 'inline-block'
-                        )
-                        .data "#{ pluginName.toLowerCase() }-width", current.outerWidth(true)
+                    # store the width to the element
+                    current.data "#{ pluginName.toLowerCase() }-width", current.outerWidth(true)
             # set the widths of each elements ::end

             # add the wrapper class to the element
@@ -155,11 +150,6 @@
                 if holder.length is 0
                     holder = ($ "
#{ o.opts.holder.text }
").insertAfter items.first()

-                    # set the holder's css to float left and display inline-block
-                    holder.css
-                        float: 'left',
-                        display: 'inline-block'
-
                 # initialize an array for storage of the hidden items
                 hiddenItems = []

@@ -189,13 +179,6 @@

                     # append the hiddenItems in the holder's child ul
                     dropdownList.append hiddenItems
-
-                    # set the hidden list items to block
-                    dropdownList
-                        .children()
-                        .css
-                            float:   'none'
-                            display: 'block'

                     # trigger the afterCompress callback
                     o.opts.onAfterCompress(_this) if $.isFunction(o.opts.onAfterCompress)
@@ -248,13 +231,6 @@

                             # delete the reference since the holder element have been remove already
                             holder = null
-
-                        # set the css of the children
-                        current
-                            .children()
-                            .css
-                                float:   'left'
-                                display: 'inline-block'

                         # trigger the afterDecompress callback
                         o.opts.onAfterDecompress(_this) if $.isFunction(o.opts.onAfterDecompress)


Initial plugin structure and logic

Initial plugin structure and logic

--- src/coffee/responsive.breadcrumbs.coffee
+++ src/coffee/responsive.breadcrumbs.coffee
@@ -1,15 +1,67 @@
 (($, window, document, undefined_) ->
     "use strict"

-    pluginName = 'responsiveBreacrumbs'
+    pluginName = 'responsiveBreadcrumbs'
     defaults =
-        onLoad: ->
-        onDestroy: ->
+        allowance:              10
+        compressed:
+            wrapperClass: "#{ pluginName.toLowerCase() }-compressed-wrapper"
+            beforeOpen:   (obj) ->
+            open:         (obj) ->
+            afterOpen:    (obj) ->
+            beforeClose:  (obj) ->
+            close:        (obj) ->
+            afterClose:   (obj) ->
+        debounceTime:           200
+        enhanceAnimation:       true
+        exposeItems:            false
+        fixIEResize:            false
+        wrapperClass:           "#{ pluginName.toLowerCase() }-wrapper"
+        wrappedClass:           "#{ pluginName.toLowerCase() }-wrapped"
+        onBeforeCompress:       (obj) ->
+        onCompress:             (obj) ->
+        onAfterCompress:        (obj) ->
+        onBeforeShowCompressed: (obj) ->
+        onShowCompressed:       (obj) ->
+        onAfterShowCompressed:  (obj) ->
+        onBeforeLoad:           (obj) ->
+        onLoad:                 (obj) ->
+        onAfterLoad:            (obj) ->
+        onDestroy:              (obj) ->

-    ResponsiveBreacrumbs = (el, options) ->
+    ResponsiveBreadcrumbs = (el, options) ->
         o        = ($ el).data '_obj', {}
         _this    = this
         metadata = ($ el).data "#{ pluginName.toLowerCase() }-options"
+
+        resize =
+            main: (e) ->
+                current      = $ this
+                optimalWidth = o.el.data "#{ pluginName.toLowerCase() }-optimalwidth"
+
+                crumbHeights = o.el
+                    .children('li')
+                    .map( ->
+                        return ($ this).outerHeight true
+                    )
+                    .get()
+
+                optimalCrumbHeight = _.max crumbHeights
+
+                if typeof optimalWidth is "undefined"
+                    if optimalCrumbHeight isnt o.el.height()
+                        o.el
+                            .data("#{ pluginName.toLowerCase() }-optimalwidth", (current.width() + o.opts.allowance))
+                            .addClass o.opts.wrappedClass
+                else
+                    if current.width() >= optimalWidth
+                        console.log 'remove'
+                        o.el.removeClass o.opts.wrappedClass
+                    else
+                        console.log 'add'
+                        o.el.addClass o.opts.wrappedClass
+
+                return null

         o.debug = ->
             # Skip browsers w/o firebug or console
@@ -22,7 +74,7 @@
                     args.push arguments_[n]
                     args.push " >> "

-                args.push o.el  if args.length is 1
+                args.push(o.el) if args.length is 1
                 args.push "[#{ pluginName }]"

                 console.log.apply console, args
@@ -37,29 +89,57 @@
                 for n of arguments_
                     args.push arguments_[n] + (" >> ")

-                args.push o.el  if args.length is 1
+                args.push(o.el) if args.length is 1
                 args.push "[#{ pluginName }]"

                 console.error.apply console, args

         # Initialize Properties and States Plugin
         o.init = ->
+
+            # check if underscore.js is required.
+            if not window._
+                o.error 'underscore.js is required.'
+
+                return false
+
             # Extend options
             o.opts = $.extend {}, defaults, options, metadata

-            # Initialize
+            # execute custom code before the plugin process the elements.
+            o.opts.onBeforeLoad(_this) if $.isFunction(o.opts.onBeforeLoad)
+
+            # Initialize
             o.el = $(el)
             o.cloneEl = o.el.clone() # Reference to the old untouched element
+
             o.opts.onLoad(_this) if $.isFunction(o.opts.onLoad)
+
+            # add the wrapper class to the element
+            o.el.addClass o.opts.wrapperClass
+
+            if o.opts.fixIEResize
+                _.each resize, (v, i) ->
+                    resize[i] = _.debounce(v, o.opts.debounceTime) if $.isFunction(v)
+
+            ($ window)
+                .on("resize.#{ pluginName }", resize.main)
+                .trigger "resize.#{ pluginName }"
+
+            # execute custom code after the plugin has loaded
+            o.opts.onAfterLoad(_this) if $.isFunction(o.opts.onAfterLoad)

         _this.destroy = ->
             o.opts.onDestroy(_this) if $.isFunction(o.opts.onDestroy)

             # Stop any animation
-            # o.el.stop(true, true);
+            o.el.stop true, true

             # Remove Events attached to the elements
-            # o.el.off('.' + pluginName);
+            o.el.off ".#{ pluginName }"
+
+            # remove any events attached to window object
+            ($ window).off ".#{ pluginName }"

             # Remove Plugin Data
             o.el.removeData pluginName
@@ -77,6 +157,6 @@

     $.fn[pluginName] = (options) ->
         @each ->
-            $.data this, pluginName, new FrozPluginName(this, options) unless $.data(this, pluginName)
+            $.data this, pluginName, new ResponsiveBreadcrumbs(this, options) unless $.data(this, pluginName)

 ) jQuery, window, document


logging

logging

--- coffeeApp.coffee
+++ coffeeApp.coffee
@@ -3,7 +3,7 @@
 newrelic = require 'newrelic'
 http = require 'http'
 path = require 'path'
-# util = require 'util'
+util = require 'util'
 compressor = require 'node-minify'
 grunt = require 'grunt'
 twit = require 'twit'
@@ -83,6 +83,24 @@
     else
       console.log 'placeWithNoBoundingBox' + util.inspect tweet.place

+stream.on 'limit', (limitMessage) ->
+  console.log "mgingras (limit): "
+  console.log limitMessage
+stream.on 'warning', (warning) ->
+  console.log "mgingras (warning): "
+  console.log warning
+stream.on 'disconnect', (disconnectMessage) ->
+  console.log "mgingras (disconnect): "
+  console.log disconnectMessage
+stream.on 'reconnect', (req, res, connectInterval) ->
+  console.log "mgingras (reconnect): "
+  console.log "Reqeuest: "
+  console.log req
+  console.log "Response: "
+  console.log res
+  console.log "Connection Interval: "
+  console.log connectInterval
+
 # Calculate the center of a bounding box for tweet
 centerPoint = (coords, callback) ->
   centerPointX = 0
@@ -113,7 +131,8 @@
   else
     Twitter.post 'statuses/retweet/:id', { id: tweetID }, (err) ->
       if err
-       console.log err
+        console.log "mgingras (Retweet Error): "
+        console.log err
       else
         twitterUsernameArray[screen_name] = screen_name


Back insertOnlyColumns to default after merge()

--- src/access/table-gateway.coffee
+++ src/access/table-gateway.coffee
@@ -206,6 +206,7 @@

         s.ignoreColumnsForMerge.apply(s, @insertOnlyColumns)

+        @insertOnlyColumns = [ ]
         return @db.bindOrCall(s, 'noData', cb)

     bindError: (msg, cb) ->


refactor flatMap_

refactor flatMap_

--- src/Bacon.coffee
+++ src/Bacon.coffee
@@ -512,6 +512,22 @@
 flatMap_ = (root, f, firstOnly) ->
   new EventStream describe(root, "flatMap" + (if firstOnly then "First" else ""), f), (sink) ->
     composite = new CompositeUnsubscribe()
+    queue = []
+    checkQueue = ->
+      child = queue.pop()
+      subscribeChild child
+    subscribeChild = (child) ->
+      composite.add (unsubAll, unsubMe) -> child.subscribeInternal (event) ->
+        if event.isEnd()
+          checkEnd(unsubMe)
+          Bacon.noMore
+        else
+          if event instanceof Initial
+            # To support Property as the spawned stream
+            event = event.toNext()
+          reply = sink event
+          unsubAll() if reply == Bacon.noMore
+          reply
     checkEnd = (unsub) ->
       unsub()
       sink end() if composite.empty()
@@ -525,17 +541,8 @@
       else
         return Bacon.noMore if composite.unsubscribed
         child = makeObservable(f event.value())
-        composite.add (unsubAll, unsubMe) -> child.subscribeInternal (event) ->
-          if event.isEnd()
-            checkEnd(unsubMe)
-            Bacon.noMore
-          else
-            if event instanceof Initial
-              # To support Property as the spawned stream
-              event = event.toNext()
-            reply = sink event
-            unsubAll() if reply == Bacon.noMore
-            reply
+        queue.push(child)
+        checkQueue()
     composite.unsubscribe


Live bar graph of dep vars while simulating.

[#132117775]
https://www.pivotaltracker.com/story/show/132117775

--- src/code/views/node-view.coffee
+++ src/code/views/node-view.coffee
@@ -165,15 +165,18 @@

   renderSliderView: ->
     enabled = not @props.running or @props.data.canEditValueWhileRunning()
+    showHandle = @props.data.canEditInitialValue()
+    value = @props.data.currentValue or @props.data.initialValue
+
     (SliderView
       horizontal: false
       filled: true
       height: 44
       width: 15
-      showHandle: @props.data.canEditInitialValue()
+      showHandle: showHandle
       showLabels: false
       onValueChange: @changeValue
-      value: @props.data.initialValue
+      value: value
       displaySemiQuant: @props.data.valueDefinedSemiQuantitatively
       max: @props.data.max
       min: @props.data.min


fixing tests around console.log spies

fixing tests around console.log spies

--- spec/cli.spec.coffee
+++ spec/cli.spec.coffee
@@ -4,7 +4,7 @@
    beforeEach ->
       sut = require('../src/cli')
       spyOn process, 'exit'
-      spyOn console, 'log'
+      spyOn sut, 'log'

    describe 'getLocation', ->
       it 'should return default if no flag provided', ->
@@ -34,7 +34,7 @@
       it 'should print out help text to console', ->
          sut.version ['-v'], true

-         expect(console.log).toHaveBeenCalled()
+         expect(sut.log).toHaveBeenCalled()

       it "shouldn't exit the process if second parameter is blank", ->
          sut.version ['-v']
@@ -45,7 +45,7 @@
          sut.help []

          expect(process.exit).not.toHaveBeenCalled()
-         expect(console.log).not.toHaveBeenCalled()
+         expect(sut.log).not.toHaveBeenCalled()

    describe 'help (-h)', ->
       it 'should exit the process if second parameter is true', ->
@@ -56,7 +56,7 @@
       it 'should print out help text to console', ->
          sut.help ['-h'], true

-         expect(console.log).toHaveBeenCalled()
+         expect(sut.log).toHaveBeenCalled()

       it "shouldn't exit the process if second parameter is blank", ->
          sut.help ['-h']
@@ -67,7 +67,7 @@
          sut.help []

          expect(process.exit).not.toHaveBeenCalled()
-         expect(console.log).not.toHaveBeenCalled()
+         expect(sut.log).not.toHaveBeenCalled()

    describe 'getData', ->
       expected = [


Set caseInsensitiveFs property on symlinked repo

--- spec/git-spec.coffee
+++ spec/git-spec.coffee
@@ -518,6 +518,7 @@
       fs.symlinkSync(repoDirectory, linkDirectory)

       repo = git.open(linkDirectory)
+      repo.caseInsensitiveFs = true
       expect(repo.relativize(path.join(linkDirectory.toUpperCase(), 'test2'))).toBe 'test2'
       expect(repo.relativize(path.join(linkDirectory.toUpperCase(), 'test2/test3'))).toBe 'test2/test3'


failing test

failing test

--- spec/governor.coffee
+++ spec/governor.coffee
@@ -128,6 +128,52 @@
         async.mapSeries series, iteration, (err) ->
           return done err

+  # Complicated config
+  describe 'complicated config', ->
+    c = \
+    """
+    '*': { app: 'guv-test' }
+    my: { queue: 'myrole.IN', worker: web, minimum: 0, max: 1 }
+    ours: { queue: 'ours.IN', worker: web, minimum: 1, max: 1, app: 'other' }
+    """
+    cfg = guv.config.parse c
+    beforeEach (done) ->
+      governor = new guv.governor.Governor cfg
+      done()
+
+    beforeEach (done) ->
+      governor.stop()
+      done()
+
+    describe 'no messages in queue', ->
+      it 'should scale to minimum', (done) ->
+        mocks.Heroku.setCurrentWorkers 'guv-test', { web: 1 }
+        mocks.Heroku.setCurrentWorkers 'other', { web: 2 }
+        mocks.RabbitMQ.setQueues
+          'myrole.IN':
+            'messages': 0
+          'ours.IN':
+            'messages': 0
+        setWorkers1 = mocks.Heroku.expectWorkers 'guv-test',
+          'web': cfg.my.minimum
+
+        setWorkers2 = mocks.Heroku.expectWorkers 'other',
+          'web': cfg.ours.minimum
+
+        governor.once 'error', (err) ->
+          chai.expect(err).to.not.exist
+
+        governor.once 'state', (state) ->
+          chai.expect(state).to.include.keys 'my'
+          chai.expect(state).to.include.keys 'ours'
+          chai.expect(state.my.current_jobs).to.equal 0
+          chai.expect(state.my.new_workers).to.equal cfg.my.minimum
+          chai.expect(state.ours.new_workers).to.equal cfg.ours.minimum
+          setWorkers1.done()
+          setWorkers2.done()
+          done()
+        governor.start()
+
   # Error cases
   describe 'Errors', ->


tests: Fix wrong chai expect

tests: Fix wrong chai expect

--- spec/governor.coffee
+++ spec/governor.coffee
@@ -13,9 +13,9 @@
     cfg = guv.config.parse ""
     broker = cfg['*'].broker
     # check that required envvars for the tests
-    chai.expect(broker, 'GUV_BROKER envvar not set').to.exist()
+    chai.expect(broker, 'GUV_BROKER envvar not set').to.exist
     chai.expect(broker).to.include 'amqp://'
-    chai.expect(process.env['HEROKU_API_KEY'], 'HEROKU_API_KEY envvar not set').to.exist()
+    chai.expect(process.env['HEROKU_API_KEY'], 'HEROKU_API_KEY envvar not set').to.exist
     mocks.startRecord()

   after () ->


Finding item index bug fixed

Finding item index bug fixed

--- style.coffee
+++ style.coffee
@@ -545,15 +545,23 @@

 		return;

+	findItemWithParent = (item, parent) ->
+		tmp = item
+		while tmp = tmp._parent
+			if tmp is parent
+				return tmp
+		return
+
 	findItemIndex = (node, item, parent) ->
 		tmpIndexNode = node
 		parent = parent.children.target or parent
 		while tmpIndexNode
 			tmpSiblingNode = tmpIndexNode
 			while tmpSiblingNode = tmpSiblingNode._nextSibling
-				if tmpSiblingNode._documentStyle?.item?.parent is parent
-					item.index = tmpSiblingNode._documentStyle.item.index
-					return
+				if tmpSiblingItem = tmpSiblingNode._documentStyle?.item
+					if tmpSiblingTargetItem = findItemWithParent(tmpSiblingItem, parent)
+						item.index = tmpSiblingTargetItem.index
+						return

 			# tmpSiblingNode = tmpIndexNode
 			# while tmpSiblingNode = tmpSiblingNode._previousSibling


set piece in place when it's dragged to correct place

--- views/puzzle.coffee
+++ views/puzzle.coffee
@@ -15,10 +15,13 @@
     @mask = new MissingPiecesMask(this)
     @piece = new PuzzlePiece(this)

-
+    @pz_status = new PuzzleStatus(this)
+
+
+    @mask.draw_mask()
 #    @puzzle_pattern.draw_pattern()
 #    @hex_draw.draw_all_hexes()
-    @hex_draw.fill_all_hexes()
+#    @hex_draw.fill_all_hexes()

     pieces = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p"]
     pc = Math.floor(16*Math.random())
@@ -77,14 +80,13 @@
       y = py-dy

       mouse_hex = @puzzle.grid_model.get_hex(x,y)
-      m_hx_a = mouse_hex[0]
-      m_hx_b = mouse_hex[1]
-      if m_hx_a != 99
-        if ( m_hx_a != @ui_status.active_hex[0] || m_hx_b != @ui_status.active_hex[1] )
-          @ui_status.set_active_hex(m_hx_a,m_hx_b)
-          @puzzle.piece.draw_piece_ab(m_hx_a,m_hx_b)
-
-#      @puzzle.pixel_test.big_dot(x,y)
+      mx_a = mouse_hex[0]
+      mx_b = mouse_hex[1]
+      if mx_a != 99
+        if ( mx_a != @ui_status.active_hex[0] || mx_b != @ui_status.active_hex[1] )
+          @ui_status.set_active_hex(mx_a,mx_b)
+          @puzzle.piece.draw_piece_ab(mx_a,mx_b)
+          @puzzle.pz_status.set_piece() if @puzzle.piece.piece_is_anchored(mx_a,mx_b)

@@ -116,6 +118,25 @@
   terminate_piece_drag: () ->
     @drag_active = false
     console.log("PIECE DRAG: Terminated")
+
+
+
+class PuzzleStatus
+
+  constructor: (puzzle_app) ->
+    @puzzle = puzzle_app
+    @reset_puzzle()
+
+
+  reset_puzzle: () ->
+    @pieces_set = 0
+
+
+  set_piece: () ->
+    @puzzle.ui_status.terminate_piece_drag()
+    console.log("SET PIECE: "+@puzzle.piece.sym)
+    @pieces_set = @pieces_set + 1
+    console.log(@pieces_set+" pieces are set")

@@ -146,6 +167,7 @@
     @canvas = document.getElementById("puzzle-widget")
     @dstring = @canvas.getAttribute("data-puzzle-pattern")

+    @log_hexes = false
     @grid = @get_pattern_grid(@dstring)

@@ -158,8 +180,8 @@
         ch = @dstring[n]
         grid[row][col] = ch
         n = n+1
-    console.log(rrow) for rrow in grid
-    grid
+    console.log(rrow) for rrow in grid if @log_hexes == true
+    return grid

   draw_pattern: () -> # [(temp) - test/diagnostic method]
@@ -214,6 +236,7 @@
     @redraw = new PieceRedrawBuffer(this)
     @hexes = []
     @bounding_box = [0,0,0,0,]
+    @log_hexes = false

   construct_piece: (sym) ->
@@ -226,6 +249,7 @@
     @redraw.reset_size(@width,@height)
     @piece_mask.draw_piece_pattern()
     @cut_piece_from_photo()
+    console.log("piece ID: "+@sym)
     @draw_piece_ab(-7,5)

@@ -263,6 +287,13 @@
     @reset_bounding_box(x,y)

+  piece_is_anchored: (a,b) ->
+    if @hex_box.anchor_hex[0] == a && @hex_box.anchor_hex[1] == b
+      return true
+    else
+      return false
+
+
   reset_bounding_box: (x,y) ->
     @left = x
     @right = x + @width
@@ -280,9 +311,10 @@
     for bb in [1..10]
       for aa in [1..24]
         hexes.push([aa,bb]) if @puzzle.puzzle_pattern.grid[bb][aa] == @sym
-    console.log("PIECE ID: "+@sym)
-    console.log("    "+hx[0]+","+hx[1]) for hx in hexes
-    hexes
+    if @log_hexes == true
+      console.log("PIECE ID: "+@sym)
+      console.log("    "+hx[0]+","+hx[1]) for hx in hexes
+    return hexes

@@ -521,7 +553,7 @@
     @hexes = []
     @box_xy = [null,null]
     @corner_fit = "unknown"
-    @metrics_report = true
+    @metrics_report = false

   set_hex_box: (piece_symbol) ->
@@ -561,6 +593,7 @@
     bb = (@top + @top%2)/2
     bb = bb + 1 if @left%2 == 0 && @corner_fit == "low"
     @anchor_hex = [aa,bb]
+    console.log("anchor hex ="+@anchor_hex)

   get_box_xy: () ->


compile client-side coffee to public/js

compile client-side coffee to public/js

--- server/app.coffee
+++ server/app.coffee
@@ -9,7 +9,8 @@
     basedir = settings.app.root

     PUBLIC  = basedir + "/public"
-    COFFEE  = basedir + "/client"
+    COFFEE  = basedir + "/client/coffee"
+    STYLUS  = basedir + "/client/stylus"

     FORM_OPTIONS =
             keepExtensions : true
@@ -24,12 +25,12 @@
     app.configure ->
         app.use express.compiler
             src: COFFEE
-            dest: PUBLIC
+            dest: PUBLIC + '/js'
             enable: ['coffeescript']

         app.use stylus.middleware {
             debug: true
-            src: basedir + '/client/stylus'
+            src: STYLUS
             dest: PUBLIC
         }

@@ -37,6 +38,7 @@
         app.use express.bodyParser()
         app.use express.session {secret: "TODO", store:store}
         app.use express.static PUBLIC
+        app.use express.static PUBLIC + '/js'

     app.configure "development", ->
         app.use express.logger()
@@ -67,6 +69,7 @@
                 crashobjs[item.id] = item
             return res.send {"ok":"1","crashdata": crashobjs}

+
     app.get "/", (req, res) ->

         crashreports = db.collection('crashreports')


Don't send message if tab id is undefined

--- extension/scripts/events.coffee
+++ extension/scripts/events.coffee
@@ -16,7 +16,8 @@
     @$.trigger eventName, eventData

   sendTrigger: (eventName, eventData) =>
-    chrome.tabs.sendMessage @activeTab?.id, { event: eventName, data: eventData }
+    return unless @activeTab?.id
+    chrome.tabs.sendMessage @activeTab.id, { event: eventName, data: eventData }

   globalTrigger: (eventName, eventData) =>
     @trigger eventName, eventData


Add links to books on category page

Add links to books on category page

--- app/assets/javascripts/categories.coffee
+++ app/assets/javascripts/categories.coffee
@@ -2,7 +2,7 @@
 # All this logic will automatically be available in application.js.
 # You can use CoffeeScript in this file: http://coffeescript.org/

-{renderable, div, text, label, input} = teacup
+{renderable, div, text, label, input, a} = teacup

 @tagSelected = (e) ->
     label = $("label[for='"+$(e).attr('id')+"']");
@@ -40,7 +40,7 @@
             div '.card', ->
                 div '.card-header', ->
                     div '.card-title', ->
-                        text book.name_eng
+                        a book.name_eng, href: '/books/' + book.id
                     div '.card-subtitle', ->
                         text book.categories.join(" ")


added preventDefault() to vote buttons

added preventDefault() to vote buttons

--- app/assets/javascripts/motions.js.coffee
+++ app/assets/javascripts/motions.js.coffee
@@ -106,6 +106,7 @@
       $('#new_vote').preventDefault()
     else
       $('#new_vote').submit()
+      event.preventDefault()
   )

 #** character count for title on discussion:new page **


Demo mode.

Demo mode.

--- app/assets/javascripts/reports.js.coffee
+++ app/assets/javascripts/reports.js.coffee
@@ -248,6 +248,25 @@
                 $("#list_switch_link").attr("href", "#"+@api.compose(list_hidden: (not @api.current_state.list_hidden)))

+class DemoController extends Marionette.Controller
+
+        initialize: (options) ->
+                @api = options.api
+                @listenTo(@api, "navigate", @navigate)
+                @interval = false
+
+        show_next_report: () =>
+                console.log("switching report",window.listController.reports)
+                current_index = window.listController.reports.models.indexOf(window.listController.reports.get(@api.current_state.report or 1))
+                new_index = (current_index + 1) % window.listController.reports.models.length
+                @api.navigate(report: window.listController.reports.models[new_index].id.toString())
+
+        navigate: (old_state,new_state,changed) ->
+                if _.intersection(changed, ["demo"]).length > 0
+                        clearInterval(@interval) if @interval
+                        if new_state.demo
+                                @interval = window.setInterval(@show_next_report,parseInt(new_state.demo)*1000)
+

 #----------------------------------- router

@@ -265,7 +284,8 @@
         current_state: {
                 report: undefined
                 unfolded_groups: []
-                list_hidden: false }
+                list_hidden: false
+                demo: false }

         constructor: () ->
@@ -281,6 +301,8 @@
                         ret += "/groups/"+data.unfolded_groups
                 if data.list_hidden
                         ret += "/list/hide"
+                if data.demo? and data.demo
+                        ret += "/demo/"+data.demo
                 ret.replace(/^\//,"")

@@ -290,7 +312,8 @@
                 new_state = {
                         report: null
                         unfolded_groups: []
-                        list_hidden: false }
+                        list_hidden: false
+                        demo: false }
                 i = 0
                 while i+1 < split.length
                         key = split[i]
@@ -299,6 +322,7 @@
                                 when "report" then new_state.report = value
                                 when "groups" then new_state.unfolded_groups = value.split(",")
                                 when "list" then new_state.list_hidden = (value == "hide")
+                                when "demo" then new_state.demo = value
                                 else
                                         console.log("stupid key in route: ",key)
                         i += 2
@@ -311,7 +335,7 @@
                 @current_state = _.extend({},old_state,inp)
                 application.router.navigate(@compose({}))
                 changed = _.filter(_.keys(@current_state), (key) => ( @current_state[key] != old_state[key]) )
-#                console.log("cc",old_state,@current_state,changed)
+                #console.log("cc",old_state,@current_state,changed)
                 @trigger("navigate",old_state,@current_state,changed)

@@ -371,6 +395,7 @@
         window.listController = new ListController(api: window.API)
         window.reportController = new ReportController(api: window.API)
         window.listFolderController = new ListFolderController(api: window.API)
+        window.demoController = new DemoController(api: window.API)

         application.start({})


Write one se and one en link

Write one se and one en link

Prevents overwriting the other locale

--- app/assets/javascripts/uploads.js.coffee
+++ app/assets/javascripts/uploads.js.coffee
@@ -1,6 +1,9 @@
 image_exts = [".jpg", ".jpeg", ".gif", ".png", ".webp"]
 doc_exts = [".pdf", ".md", ".txt"]
-post_body = '.posts #post_body_en, .posts #post_body_sv, #page_body'
+post_body = '.posts #post_body_en, .posts #post_body_sv, #page_body, #page_body'
+en_post_body = '.posts #post_body_en, #page_body'
+sv_post_body = '.posts #post_body_sv, #page_body'
+
 $ ->

   $(post_body + ', #post_item_upload, #page_item_upload').fileupload
@@ -32,9 +35,11 @@
       src = data.result.source
       extension = src.url.substr(src.url.lastIndexOf('.'), src.url.length)
       if $.inArray(extension, image_exts) > -1
-        insertTextAtCaret($(post_body), image_thumbnail_markdown(data.orig_name, src.url, src.thumb.url) + "\n")
+        insertTextAtCaret($(sv_post_body), image_thumbnail_markdown(data.orig_name, src.url, src.thumb.url) + "\n")
+        insertTextAtCaret($(en_post_body), image_thumbnail_markdown(data.orig_name, src.url, src.thumb.url) + "\n")
       else
-        insertTextAtCaret($(post_body), link_markdown(data.orig_name, src.url) + "\n")
+        insertTextAtCaret($(sv_post_body), link_markdown(data.orig_name, src.url) + "\n")
+        insertTextAtCaret($(en_post_body), link_markdown(data.orig_name, src.url) + "\n")

 handle_file_error = (data) ->
   data.label.text I18n.t('unsupported_file_format')


Clean up javascript

Clean up javascript

--- app/assets/javascripts/warning.js.coffee
+++ app/assets/javascripts/warning.js.coffee
@@ -1,6 +1,6 @@
 jQuery ->
   warned = $.cookie("warned")
-  if(warned==null || warned=="")
+  if(!warned)
     $("#ie-warning").modal()

   $("#ie-warning .btn-primary").click ->


welcome.js.cofee: use 'change' instead of 'click'

--- app/assets/javascripts/welcome.js.coffee
+++ app/assets/javascripts/welcome.js.coffee
@@ -3,7 +3,7 @@
 # You can use CoffeeScript in this file: http://coffeescript.org/

 $ ->
-  $(document).on 'click', '#countries_select', (evt) ->
+  $(document).on 'change', '#countries_select', (evt) ->
     $.ajax 'update_cities',
       type: 'GET'
       dataType: 'script'


Adapt to new network interface

Adapt to new network interface

--- spec/Subgraph.coffee
+++ spec/Subgraph.coffee
@@ -62,7 +62,6 @@

   describe 'with JSON graph definition', ->
     it 'should emit a ready event after network has been loaded', (done) ->
-      @timeout 6000
       c.baseDir = root
       c.once 'ready', ->
         chai.expect(c.network).not.to.be.null
@@ -82,7 +81,6 @@
           Merge:
             component: 'Merge'
     it 'should expose available ports', (done) ->
-      @timeout 6000
       c.baseDir = root
       c.once 'ready', ->
         chai.expect(c.inPorts.ports).to.have.keys [
@@ -113,7 +111,6 @@
         ]
     it 'should update description from the graph', (done) ->
       c.baseDir = root
-      @timeout 6000
       c.once 'ready', ->
         chai.expect(c.network).not.to.be.null
         chai.expect(c.ready).to.be.true
@@ -134,7 +131,6 @@
             component: 'Split'
     it 'should expose only exported ports when they exist', (done) ->
       c.baseDir = root
-      @timeout 6000
       c.once 'ready', ->
         chai.expect(c.inPorts.ports).to.have.keys [
           'graph'
@@ -170,7 +166,6 @@
         ]
     it 'should be able to run the graph', (done) ->
       c.baseDir = root
-      @timeout 6000
       c.once 'ready', ->
         ins = noflo.internalSocket.createSocket()
         out = noflo.internalSocket.createSocket()
@@ -211,15 +206,16 @@
         ]

   describe 'with a Graph instance', ->
-    gr = new noflo.Graph 'Hello, world'
-    gr.baseDir = root
-    gr.addNode 'Split', 'Split'
-    gr.addNode 'Merge', 'Merge'
-    gr.addEdge 'Merge', 'out', 'Split', 'in'
-    gr.addInport 'in', 'Merge', 'in'
-    gr.addOutport 'out', 'Split', 'out'
+    gr = null
+    before ->
+      gr = new noflo.Graph 'Hello, world'
+      gr.baseDir = root
+      gr.addNode 'Split', 'Split'
+      gr.addNode 'Merge', 'Merge'
+      gr.addEdge 'Merge', 'out', 'Split', 'in'
+      gr.addInport 'in', 'Merge', 'in'
+      gr.addOutport 'out', 'Split', 'out'
     it 'should emit a ready event after network has been loaded', (done) ->
-      @timeout 6000
       c.baseDir = root
       c.once 'ready', ->
         chai.expect(c.network).not.to.be.null
@@ -236,7 +232,6 @@
       chai.expect(c.ready).to.be.false
     it 'should expose available ports', (done) ->
       c.baseDir = root
-      @timeout 6000
       c.once 'ready', ->
         chai.expect(c.inPorts.ports).to.have.keys [
           'graph'
@@ -256,16 +251,19 @@
       g.send gr
     it 'should be able to run the graph', (done) ->
       c.baseDir = root
-      @timeout 6000
+      doned = false
       c.once 'ready', ->
         ins = noflo.internalSocket.createSocket()
         out = noflo.internalSocket.createSocket()
         c.inPorts['in'].attach ins
         c.outPorts['out'].attach out
         out.on 'data', (data) ->
-          chai.expect(data).to.equal 'Foo'
-          done()
-        ins.send 'Foo'
+          chai.expect(data).to.equal 'Baz'
+          if doned
+            process.exit 1
+          done()
+          doned = true
+        ins.send 'Baz'
       c.once 'network', ->
         chai.expect(c.ready).to.be.false
         chai.expect(c.network).not.to.be.null
@@ -517,9 +515,12 @@
       before (done) ->
         graph = new noflo.Graph 'main'
         graph.baseDir = root
-        network = new noflo.Network graph
-        network.loader.listComponents (err) ->
-          return done err if err
+        noflo.createNetwork graph,
+          delay: true
+          subscribeGraph: false
+        , (err, nw) ->
+          return done err if err
+          network = nw
           network.loader.components.Split = Split
           network.loader.components.Merge = SubgraphMerge
           sg = new noflo.Graph 'Subgraph'
@@ -532,14 +533,28 @@
             return done err if err
             network.connect done
       it 'should instantiate the subgraph when node is added', (done) ->
-        setTimeout ->
-          chai.expect(network.processes).not.to.be.empty
-          chai.expect(network.processes.Sub).to.exist
-          done()
-        , 10
-        graph.addNode 'Sub', 'foo/AB'
-        graph.addNode 'Split', 'Split'
-        graph.addEdge 'Sub', 'out', 'Split', 'in'
+        network.addNode
+          id: 'Sub'
+          component: 'foo/AB'
+        , (err) ->
+          return done err if err
+          network.addNode
+            id: 'Split'
+            component: 'Split'
+          , (err) ->
+            return done err if err
+            network.addEdge
+              from:
+                node: 'Sub'
+                port: 'out'
+              to:
+                node: 'Split'
+                port: 'in'
+            , (err) ->
+              return done err if err
+              chai.expect(network.processes).not.to.be.empty
+              chai.expect(network.processes.Sub).to.exist
+              done()
       it 'should be possible to start the graph', (done) ->
         network.start done
       it 'should forward IP events', (done) ->
@@ -561,16 +576,26 @@
               chai.expect(ip.data).to.equal 'foo'
               chai.expect(ip.subgraph).to.be.undefined
               done()
-        graph.addInitial 'foo', 'Sub', 'in'
+        network.addInitial
+          from:
+            data: 'foo'
+          to:
+            node: 'Sub'
+            port: 'in'
+        , (err) ->
+          return done err if err
     describe 'with two levels of subgraphs', ->
       graph = null
       network = null
       before (done) ->
         graph = new noflo.Graph 'main'
         graph.baseDir = root
-        network = new noflo.Network graph
-        network.loader.listComponents (err) ->
-          return done err if err
+        noflo.createNetwork graph,
+          delay: true
+          subscribeGraph: false
+        , (err, net) ->
+          return done err if err
+          network = net
           network.loader.components.Split = Split
           network.loader.components.Merge = SubgraphMerge
           sg = new noflo.Graph 'Subgraph'
@@ -591,14 +616,28 @@
               return done err if err
               network.connect done
       it 'should instantiate the subgraphs when node is added', (done) ->
-        setTimeout ->
-          chai.expect(network.processes).not.to.be.empty
-          chai.expect(network.processes.Sub).to.exist
-          done()
-        , 100
-        graph.addNode 'Sub', 'foo/AB2'
-        graph.addNode 'Split', 'Split'
-        graph.addEdge 'Sub', 'out', 'Split', 'in'
+        network.addNode
+          id: 'Sub'
+          component: 'foo/AB2'
+        , (err) ->
+          return done err if err
+          network.addNode
+            id: 'Split'
+            component: 'Split'
+          , (err) ->
+            return done err if err
+            network.addEdge
+              from:
+                node: 'Sub'
+                port: 'out'
+              to:
+                node: 'Split'
+                port: 'in'
+            , (err) ->
+              return done err if err
+              chai.expect(network.processes).not.to.be.empty
+              chai.expect(network.processes.Sub).to.exist
+              done()
       it 'should be possible to start the graph', (done) ->
         network.start done
       it 'should forward IP events', (done) ->
@@ -628,4 +667,11 @@
                 chai.expect(ip.data).to.equal 'foo'
                 chai.expect(ip.subgraph).to.be.undefined
                 done()
-        graph.addInitial 'foo', 'Sub', 'in'
+        network.addInitial
+          from:
+            data: 'foo'
+          to:
+            node: 'Sub'
+            port: 'in'
+        , (err) ->
+          return done err if err


Add a VFL test

Add a VFL test

--- spec/compiler.coffee
+++ spec/compiler.coffee
@@ -23,6 +23,25 @@
         ['eq', ['get', '#button3[width]'], ['get', '#button4[height]']]
       ]
       css: ''
-    it 'should be able to produce correst AST', ->
+    it 'should be able to produce correct AST', ->
       result = parser.compile statement
       chai.expect(result).to.eql ast
+
+  describe 'with a statement containing only VFL', ->
+    statement = "@horizontal [#b1][#b2];"
+    ast =
+      selectors: [
+        "#b1"
+        "#b2"
+      ]
+      vars: [
+        ["get", "#b1[right]", "right", ["$id", "b1"]]
+        ["get", "#b2[left]", "left", ["$id", "b2"]]
+      ]
+      constraints: [
+        ["eq", ["get", "#b1[right]"], ["get", "#b2[left]"]]
+      ]
+    result = null
+    it 'should be able to produce correct AST', ->
+      result = parser.compile statement
+      chai.expect(result).to.eql ast


Remove unneeded fs.mkdirSync call

Remove unneeded fs.mkdirSync call

--- spec/git-spec.coffee
+++ spec/git-spec.coffee
@@ -381,7 +381,6 @@
       newFilePath = path.join(repo.getWorkingDirectory(), 'b.txt')
       fs.writeFileSync(newFilePath, '', 'utf8')

-      fs.mkdirSync(path.join(repo.getWorkingDirectory(), '.git/info'))
       ignoreFile = path.join(repo.getWorkingDirectory(), '.git/info/exclude')
       fs.writeFileSync(ignoreFile, 'c.txt', 'utf8')
       ignoredFilePath = path.join(repo.getWorkingDirectory(), 'c.txt')


add "be the first" when add opinion on proposal with no activity

Former-commit-id: 0d132db78a44707f6d4a7c2fb0859de7c77dbf82 [formerly 761360a0a7b9d6600575172d858583ee1108b432] [formerly 51b888ada32a3d2982ded9e813dad01e4a03ddd8 [formerly 51b888ada32a3d2982ded9e813dad01e4a03ddd8 [formerly 0803e1bb2ede5b3be34df89d13faa2d421166496]]]
Former-commit-id: b27d4a9acfd1343f4be0a965f052b4c4ec771946 [formerly e93655ae396b4680b255fda6ba56a8098dc24c7b]
Former-commit-id: 5f702a675c3e473c973eeb7211cacb11444e9c81
Former-commit-id: b3e9c3ef13eef73537a294dd7ef6d43305f54a20

--- app/assets/javascripts/backbone/apps/franklin/proposal/crafting/crafting_view.coffee
+++ app/assets/javascripts/backbone/apps/franklin/proposal/crafting/crafting_view.coffee
@@ -80,10 +80,18 @@

     serializeData : ->
       current_user = App.request 'user:current'
+
+      if @model && @model.get('published')
+        call = 'Update your opinion'
+      else if @model.getProposal().num_participants() > 0
+        call = 'Add your opinion'
+      else
+        call = 'Be the first to add an opinion'
+
       _.extend {}, @model.getProposal().attributes,
         active : @model.getProposal().get 'active'
         updating : @model && @model.get 'published'
-        call : if @model && @model.get('published') then 'Update your opinion' else 'Add your opinion'
+        call : call

   class Proposal.PositionReasonsLayout extends App.Views.Layout
     template : '#tpl_position_reasons'


a few tweaks

a few tweaks

--- src/Landscape.coffee
+++ src/Landscape.coffee
@@ -42,7 +42,7 @@
     fade.to({ alpha: 0 }, 400000 / SPEED, Phaser.Easing.Linear.None, true)
     fade.start

-    @cloud = @game.add.sprite(1000, 10, 'cloud')
+    @cloud = @game.add.sprite(1000, 40, 'cloud')
     @cloud.alpha = 0.4
     move = @game.add.tween(@cloud)
     move.to({x: -800}, 300000 / SPEED, Phaser.Easing.Linear.None, true, 0, 1)
@@ -53,6 +53,9 @@

     @mountain1a = @game.add.sprite(0, 180, 'mountain1')
     @mountain1b = @game.add.sprite(2000, 180, 'mountain1')
+
+    @guy = @game.add.sprite(600, 330, 'guy')
+    @guy.scale.setTo(0.25, 0.25)

     @mountain2a = @game.add.sprite(0, 180, 'mountain2')
     @mountain2b = @game.add.sprite(2000, 180, 'mountain2')
@@ -80,9 +83,6 @@
     move = @game.add.tween(@tree3)
     move.to({x: -2000}, 3400 / SPEED, Phaser.Easing.Linear.None, true, 17000 / SPEED, 999)
     move.start
-
-    @guy = @game.add.sprite(600, 330, 'guy')
-    @guy.scale.setTo(0.25, 0.25)

     @grass1 = @game.add.sprite(0, 380, 'grass')
     @grass2 = @game.add.sprite(896, 380, 'grass')


added example, assigning a "to" function and then running it from new variable

--- tests.coffee
+++ tests.coffee
@@ -3826,6 +3826,21 @@
   "2[object of class \"Class_of_🚀\"]"

   # ---------------------------------------------------------------------------
+  # assigning a function to a var and then running it from there.
+  # This looks like HOF but it's really an object being assigned.
+
+  """
+  to 🚀:
+  ﹍﹍(withAParameter)
+  ﹍do:
+  ﹍﹍" running with param " + withAParameter
+  myFunc = 🚀
+  console print 🚀 1
+  console print myFunc 2
+  """
+  " running with param 1 running with param 2"
+
+  # ---------------------------------------------------------------------------
   # factorial using FLTo

   """


neo4j: comment out adding grouped activities to overview

--- server/lib/server/graph/decorators/bucket_activity.coffee
+++ server/lib/server/graph/decorators/bucket_activity.coffee
@@ -26,8 +26,9 @@
     # TODO: use anchorId
     id = @extractId datum
     @groups[id].snapshot.group.push @decorateGroupActivity datum[@groupByName]
-    @addActvityToOverview datum[@groupByName].first, anchorId
+    #@addActvityToOverview datum[@groupByName].first, anchorId

+  # not required since for non member buckets, all entries are grouped
   addActvityToOverview:(datum, anchorId)->
     for followers in @groups.overview when followers.ids.first is anchorId
       followers.ids.push datum.id


Look for queued-http in the correct place

--- services/web/public/coffee/main/project-list/index.coffee
+++ services/web/public/coffee/main/project-list/index.coffee
@@ -2,6 +2,6 @@
 	"main/project-list/project-list"
 	"main/project-list/modal-controllers"
 	"main/project-list/tag-controllers"
-	"main/project-list/queued-http"
 	"main/project-list/left-hand-menu-promo-controller"
+	"services/queued-http"
 ], () ->


replace hexagon with diamond, diamond for tables

--- tree2.coffee
+++ tree2.coffee
@@ -1,4 +1,6 @@
 collections = []
+shapes = ['circle','square','hex']
+next_shape = 0

 # keyboard input
 pendingString = ''
@@ -9,8 +11,6 @@

   direct_message = false

-  shapes = ['circle','square','diamond']
-  shape = shapes[collections.length % shapes.length]
   pos = (x: canvas.width/2, y: canvas.height/2 +
               collections.length * default_node_radius * 4)

@@ -25,7 +25,7 @@

     if collections.length < 1 or
        not (collections[collections.length-1] instanceof CodeListCollection)
-      collection = new CodeListCollection(shape)
+      collection = new CodeListCollection('diamond')
       collections.push(collection)
     else
       collection = collections[collections.length-1]
@@ -52,7 +52,8 @@
         else
           letters[c] = 1

-    collection = new NodeCollection(shape)
+    collection = new NodeCollection(shapes[next_shape])
+    next_shape = (next_shape + 1) % shapes.length
     collection.addLeaves( letters, pos )
     collections.push(collection)

@@ -90,6 +91,14 @@
       ctx.lineTo(0,+radius*Math.SQRT2)
       ctx.lineTo(+radius*Math.SQRT2,0)
       ctx.lineTo(0,-radius*Math.SQRT2)
+      ctx.closePath()
+    when 'hex'
+      ctx.moveTo(-radius,0)
+      ctx.lineTo(-radius/2,-radius)
+      ctx.lineTo(+radius/2,-radius)
+      ctx.lineTo(+radius,0)
+      ctx.lineTo(+radius/2,+radius)
+      ctx.lineTo(-radius/2,+radius)
       ctx.closePath()

   return


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/clamp.coffee
+++ src/clamp.coffee
@@ -7,4 +7,4 @@
       property.value = property.value.clamp(property.min, property.max)

   clamp: (properties) ->
-  +


DRYness.

DRYness.

--- services/web/public/coffee/ide/review-panel/controllers/ReviewPanelController.coffee
+++ services/web/public/coffee/ide/review-panel/controllers/ReviewPanelController.coffee
@@ -337,29 +337,25 @@
 				}

 			for id, entry of entries
+				isChangeEntryAndWithinSelection = false
 				if entry.type == "comment" and not $scope.reviewPanel.resolvedThreadIds[entry.thread_id]
 					entry.focused = (entry.offset <= selection_offset_start <= entry.offset + entry.content.length)
 				else if entry.type == "insert"
-					isEntryWithinSelection = entry.offset >= selection_offset_start and entry.offset + entry.content.length <= selection_offset_end
+					isChangeEntryAndWithinSelection = entry.offset >= selection_offset_start and entry.offset + entry.content.length <= selection_offset_end
 					entry.focused = (entry.offset <= selection_offset_start <= entry.offset + entry.content.length)
-					if isEntryWithinSelection
-						$scope.reviewPanel.selectedEntryIds.push id
-						$scope.reviewPanel.nVisibleSelectedChanges++
 				else if entry.type == "delete"
-					isEntryWithinSelection = selection_offset_start <= entry.offset <= selection_offset_end
+					isChangeEntryAndWithinSelection = selection_offset_start <= entry.offset <= selection_offset_end
 					entry.focused = (entry.offset == selection_offset_start)
-					if isEntryWithinSelection
-						$scope.reviewPanel.selectedEntryIds.push id
-						$scope.reviewPanel.nVisibleSelectedChanges++
 				else if entry.type == "agg-change"
-					isEntryWithinSelection = entry.offset >= selection_offset_start and entry.offset + entry.content.length <= selection_offset_end
+					isChangeEntryAndWithinSelection = entry.offset >= selection_offset_start and entry.offset + entry.content.length <= selection_offset_end
 					entry.focused = (entry.offset <= selection_offset_start <= entry.offset + entry.content.length)
-					if isEntryWithinSelection
-						$scope.reviewPanel.selectedEntryIds.push id, entry.metadata.agg_op_id
-						$scope.reviewPanel.nVisibleSelectedChanges++
-
 				else if entry.type in [ "add-comment", "bulk-actions" ] and selection
 					entry.focused = true
+
+				if isChangeEntryAndWithinSelection
+					$scope.reviewPanel.selectedEntryIds.push id
+					$scope.reviewPanel.selectedEntryIds.push entry.metadata.agg_op_id if entry.type == "agg-change"
+					$scope.reviewPanel.nVisibleSelectedChanges++

 			$scope.$broadcast "review-panel:recalculate-screen-positions"
 			$scope.$broadcast "review-panel:layout"
@@ -391,17 +387,19 @@
 		bulkAccept = () ->
 			_doAcceptMultipleChanges $scope.reviewPanel.selectedEntryIds.slice()
 			$scope.reviewPanel.selectedEntryIds = []
+			$scope.reviewPanel.nVisibleSelectedChanges = 0
 			event_tracking.sendMB "rp-bulk-accept", {
 				view: if $scope.ui.reviewPanelOpen then $scope.reviewPanel.subView else 'mini',
-				nEntries: $scope.reviewPanel.selectedEntryIds.length
+				nEntries: $scope.reviewPanel.nVisibleSelectedChanges
 			}

 		bulkReject = () ->
 			_doRejectMultipleChanges $scope.reviewPanel.selectedEntryIds.slice()
 			$scope.reviewPanel.selectedEntryIds = []
+			$scope.reviewPanel.nVisibleSelectedChanges = 0
 			event_tracking.sendMB "rp-bulk-reject", {
 				view: if $scope.ui.reviewPanelOpen then $scope.reviewPanel.subView else 'mini',
-				nEntries: $scope.reviewPanel.selectedEntryIds.length
+				nEntries: $scope.reviewPanel.nVisibleSelectedChanges
 			}

 		$scope.showBulkAcceptDialog = () ->


:new: Add spec for change

:new: Add spec for change

--- spec/helper-spec.coffee
+++ spec/helper-spec.coffee
@@ -152,6 +152,17 @@
       results = helpers.parse(input, regex, {flags: "i"})
       expect(results).toEqual(output)

+      regex = 'type:(?<type>.+) message:(?<message>.+)'
+      input = 'TYPE:type message:message'
+      output = [(
+        type: 'type'
+        text: 'message'
+        filePath: null
+        range: [[0, 0], [0, 0]]
+      )]
+      results = helpers.parse(input, regex, {flags: "gi"})
+      expect(results).toEqual(output)
+
   describe '::find', ->
     it 'cries when no argument is passed', ->
       expect ->


Make syntax more coffee like.

Make syntax more coffee like.

--- spec/spec_helper.coffee
+++ spec/spec_helper.coffee
@@ -13,7 +13,7 @@

 exports.tobi.get = (path, callback) ->
   browser = exports.tobi.createBrowser(app)
-  browser.get(path, { headers: exports.headers.jsonHeaders }, callback)
+  browser.get path, { headers: exports.headers.jsonHeaders }, callback
   return

 exports.headers             = {}


pixie

pixie

--- src/color.coffee
+++ src/color.coffee
@@ -1,5 +1,3 @@
-test = "true"
-
 Color = (I) ->
   I ||= {}


context must be string, not object

context must be string, not object

--- resource/translators/translator.coffee
+++ resource/translators/translator.coffee
@@ -67,7 +67,7 @@
   for own attribute, key of Translator.Context::options
     @config.options[key] = Translator[attribute]

-  @context = new @Context(@config)
+  @context = new @Context(@config).toJSON()
   @log(":::cache context = #{@context}")

   return


error hint

error hint

--- src/atom/pane.coffee
+++ src/atom/pane.coffee
@@ -29,7 +29,7 @@
       when 'right'
         horizontalDiv.append @pane
       else
-        throw "I DON'T KNOW HOW TO DEAL WITH #{@position}"
+        throw "pane position of #{this} can't be `#{@position}`"

   showing: ->
     @pane and not @pane.css('display').match /none/


replace verbose logging by slow query log

--- services/document-updater/app/coffee/LockManager.coffee
+++ services/document-updater/app/coffee/LockManager.coffee
@@ -6,6 +6,8 @@
 logger = require "logger-sharelatex"
 os = require "os"
 crypto = require "crypto"
+
+Profiler = require "./Profiler"

 HOST = os.hostname()
 PID = process.pid
@@ -30,33 +32,36 @@
 	tryLock : (doc_id, callback = (err, isFree)->)->
 		lockValue = LockManager.randomLock()
 		key = keys.blockingKey(doc_id:doc_id)
-		logger.log {doc_id: doc_id, key: key}, "tryLock"
+		profile = new Profiler("tryLock", {doc_id, key, lockValue})
 		rclient.set key, lockValue, "EX", @LOCK_TTL, "NX", (err, gotLock)->
 			return callback(err) if err?
 			if gotLock == "OK"
 				metrics.inc "doc-not-blocking"
-				logger.log {doc_id: doc_id, key: key, lockValue: lockValue}, "got lock"
+				profile.log("got lock").end()
 				callback err, true, lockValue
 			else
 				metrics.inc "doc-blocking"
-				logger.log {doc_id: doc_id, key: key}, "doc is locked"
+				profile.log("doc is locked").end()
 				callback err, false

 	getLock: (doc_id, callback = (error, lockValue) ->) ->
 		startTime = Date.now()
 		testInterval = LockManager.LOCK_TEST_INTERVAL
+		profile = new Profiler("getLock", {doc_id})
 		do attempt = () ->
 			if Date.now() - startTime > LockManager.MAX_LOCK_WAIT_TIME
 				e = new Error("Timeout")
 				e.doc_id = doc_id
+				profile.log("timeout").end()
 				return callback(e)

 			LockManager.tryLock doc_id, (error, gotLock, lockValue) ->
 				return callback(error) if error?
+				profile.log("tryLock")
 				if gotLock
+					profile.end()
 					callback(null, lockValue)
 				else
-					logger.log {doc_id: doc_id, delay: testInterval}, "will retry lock"
 					setTimeout attempt, testInterval
 					# back off when the lock is taken to avoid overloading
 					testInterval = Math.min(testInterval * 2, LockManager.MAX_TEST_INTERVAL)
@@ -75,12 +80,14 @@

 	releaseLock: (doc_id, lockValue, callback)->
 		key = keys.blockingKey(doc_id:doc_id)
+		profile = new Profiler("releaseLock", {doc_id, key, lockValue})
 		rclient.eval LockManager.unlockScript, 1, key, lockValue, (err, result) ->
 			if err?
 				return callback(err)
 			else if result? and result isnt 1 # successful unlock should release exactly one key
+				profile.log("unlockScript:expired-lock").end()
 				logger.error {doc_id:doc_id, key:key, lockValue:lockValue, redis_err:err, redis_result:result}, "unlocking error"
 				return callback(new Error("tried to release timed out lock"))
 			else
-				logger.log {doc_id:doc_id, key:key, lockValue:lockValue}, "released lock"
+				profile.log("unlockScript:ok").end()
 				callback(err,result)


Tests: Better error on missing api key

Tests: Better error on missing api key

--- spec/governor.coffee
+++ spec/governor.coffee
@@ -13,7 +13,7 @@
     # must be set before running tests
     cfg = guv.config.parse ""
     chai.expect(cfg['*'].broker).to.include 'amqp://'
-    chai.expect(process.env['HEROKU_API_KEY']).to.have.length.above 10
+    chai.expect(process.env['HEROKU_API_KEY'], 'HEROKU_API_KEY envvar not set').to.exist
     mocks.startRecord()

   after () ->


first proposal

first proposal

--- spec/protocol.coffee
+++ spec/protocol.coffee
@@ -12,7 +12,7 @@
 #   the specified behavior is independent of how it implemented.
 #                                             ~~ wikipedia  ###

-describe "Game COMMUNICATIONS PROTOCOL Specification v0.2 \n", ->
+describe "Game COMMUNICATIONS PROTOCOL Specification v0.3 \n", ->

   before (test) -> server.start test

@@ -38,11 +38,9 @@
     it "must inform about players that are already online", (done) ->
       (@anotherplayer = server.gcp()).on "connect", () ->
         @emit 'login', "I am Ananda"
-        @on 'players', (msg) ->
-          expect(msg).to.deep.equal ["Anyname", "Ananda"]
-          done()
+        done()

-    it "should notify about players joining the lobby", (done) ->
+    it "should notify about players that log in", (done) ->
       (@lukas = server.gcp()).on "connect", () ->
         @emit 'login', "I am Lukas"
       @anyplayer.on 'login', (msg) => this.happens()
@@ -52,7 +50,11 @@
         expect(@happens.calledTwice).to.be.ok
         done() ), 21 # ms responsiveness !!!

-
+    it "should notify about players that log out", (done) ->
+      @anyplayer.send 'logout'
+      @anotherplayer.on 'logout', (msg) ->
+        expect(msg).to.equal "Anyname"
+        done()

   describe "CHATTING", ->

@@ -75,7 +77,7 @@
   describe "HOSTING GAMES", () ->

     it "should allow any logged in player to host a game", (done) ->
-      @anyplayer.emit 'host', { game: "my.game"}
+      @anyplayer.emit 'host', { game: "my.game", max: 2}
       @lukas.on 'host', (msg) ->
         expect(msg.game).to.equal "my.game"
         expect(msg.host).to.equal "Anyname"
@@ -87,12 +89,19 @@
       setTimeout done, 58

     it "must inform about open hosted games that are already online", (done) ->
-      (@yetanotherplayer = server.gcp()).on "connect", () ->
-        @emit 'login', "I am Yet Another new Player"
-        @on 'games', (msg) ->
-          expect(msg[0].game).to.deep.equal "my.game"
-          expect(msg[0].host).to.deep.equal "Anyname"
-          done()
+      @lukas.send 'state'
+      @lukas.on 'state', (msg) ->
+        expect(msg).to.deep.equal( {
+          players: [  { player:  "Anyname",
+                        game:    "my.game",
+                        joined:  1,
+                        max:     2 },
+                      { player: "Lukas" },
+                      { player: "Ananda"  } ],
+          chat: [ { player: "Lukas", msg: "happy again :-)" } ],
+          games_played: 0,
+          msges_send: 42
+        })

     it "should tell any host what other players want to join", (done) ->
       @anotherplayer.emit 'join', { host: "Anyname", game: "my.game" }


extract background generation to draw_scene

--- views/gorillas.coffee
+++ views/gorillas.coffee
@@ -108,6 +108,12 @@
     @padding = 1
     @buildings = []

+  draw_scene: ->
+    @clear()
+    @draw_the_sun()
+    @draw_buildings()
+    @draw_gorillas()
+
   draw_buildings:()->
     position = 0
     for [1...10]
@@ -128,6 +134,9 @@

   set_color:(color) ->
     @color = color
+
+  clear:() ->
+    @canvas.width = @canvas.width

   draw_gorillas: ->
     building = @buildings[Math.floor(Math.random()*3)]
@@ -150,7 +159,7 @@
   animate_banana:(player) ->
     setTimeout (=>
       this['player_' + player].throw_banana()
-      @draw_buildings()
+      @draw_scene()
       @animate_banana(player)
       ),
       150
@@ -228,10 +237,7 @@
 jQuery = $
 $(document).ready ->
   painter = new Painter
-  painter.draw_the_sun()
-  painter.draw_buildings()
-
-  painter.draw_gorillas()
+  painter.draw_scene()

   $('#player_1_angle').bind "keydown", (event) ->
     if event.keyCode == 13


Show network error log

Show network error log

--- views/services.coffee
+++ views/services.coffee
@@ -84,10 +84,10 @@
     dbg._getLogFunc()(new GameResponse(resPath, body, postBody))
   log("#{__ 'Hit'} #{method} #{resPath}", {dontReserve: true}) if config.get('poi.showNetworkLog', true)
 window.addEventListener 'network.error', ->
-  error(__('Connection failed.'), {dontReserve: true}) if config.get('poi.showNetworkLog', true)
+  error(__('Connection failed.'), {dontReserve: true})
 window.addEventListener 'network.error.retry', (e) ->
   {counter} = e.detail
-  error(__n('Connection failed after %s retry',  counter), {dontReserve: true}) if config.get('poi.showNetworkLog', true)
+  error(__n('Connection failed after %s retry',  counter), {dontReserve: true})
 window.addEventListener 'network.invalid.result', (e) ->
   {code} = e.detail
-  error(__('The server presented you a cat. (Error code: %s)',  code), {dontReserve: true}) if config.get('poi.showNetworkLog', true)
+  error(__('The server presented you a cat. (Error code: %s)',  code), {dontReserve: true})


Don't require to click twice to start an app, refs #731

--- client/app/views/home_application.coffee
+++ client/app/views/home_application.coffee
@@ -127,12 +127,12 @@
             when 'installing'
                 alert t 'state app installing'
             when 'stopped'
-                @startApp()
+                @startApp(event)

     ### Functions ###

-    startApp: =>
+    startApp: (e) =>
         @showSpinner()
         @model.start
             success: =>