Add TODO for image download alert box test.

--- test/e2e/sessionDetailSpec.coffee
+++ test/e2e/sessionDetailSpec.coffee
@@ -135,19 +135,17 @@
           expect(open.isDisplayed(), 'The open button is hidden after download')
             .to.eventually.be.true

-    it 'should display an alert box', ->
-      # Find the download/display button pair, then...
-      #
-      # Work around the anomaly described in the scanImageButtons function.
-      #page.scanImageButtons(MISSING_TEST_VOL_NBR).then (btnGroup) ->
-        btnGroup = page.scanImageButtons(MISSING_TEST_VOL_NBR)
-        download = btnGroup.download
-
-        # Click the download button, wait for the image to load, then...
-        page.loadScanImage(download).then ->
-          # The download button should now be hidden.
-          expect(download.isDisplayed(), 'The download button is displayed after download')
-            .to.eventually.be.false
-          # The unclickable exclamation sign button should be displayed after load failure.
-          expect(failed.isDisplayed(), 'The exclamation sign icon is hidden after download failed')
-            .to.eventually.be.true
+    # TODO - Add a test to verify that an alert box is opened when a link for
+    #   a missing image is clicked. Currently the opening of an alert box stops
+    #   Protractor, with no apparent easy workaround.
+    #
+    # it 'should display an alert box', ->
+    #   page.scanImageButtons(MISSING_TEST_VOL_NBR).then (btnGroup) ->
+    #     btnGroup = page.scanImageButtons(MISSING_TEST_VOL_NBR)
+    #     download = btnGroup.download
+    #
+    #     page.loadScanImage(download).then ->
+    #       expect(download.isDisplayed(), 'The download button is displayed after download')
+    #         .to.eventually.be.false
+    #       expect(failed.isDisplayed(), 'The exclamation sign icon is hidden after download failed')
+    #         .to.eventually.be.true


Utils: splitQuery: work with empty queries

--- client/Main/utils.coffee
+++ client/Main/utils.coffee
@@ -644,7 +644,7 @@
   #
   # Structure taken from github.com/koding/kite/protocol/protocol.go

-  splitKiteQuery: (query)->
+  splitKiteQuery: (query = "")->

     keys = [ "username", "environment", "name",
              "version", "region", "hostname", "id" ]


try the links or images first before the less-likely artwork images

--- api/lib/migrate.coffee
+++ api/lib/migrate.coffee
@@ -104,6 +104,34 @@
     return callback() unless profiles.length
     callback null, profiles

+bestThumbnail = (attachments, artworks) ->
+  return null unless attachments?.length
+  artworkUrls = _.compact(
+    for attachment in _.where(attachments, _type: 'PostArtwork')
+      artwork = _.select(artworks, (artwork) ->
+        attachment.artwork_id.toString() is artwork._id.toString()
+      )[0]
+      img = artwork?.additional_images?[0]
+      _.compact([
+        img?.image_urls?.large
+        _.sample(img?.image_urls ? [])
+        artwork?.image_urls?.large
+        _.sample(artwork?.image_urls ? [])
+        (("http://static.artsy.net/additional_images/#{img._id}/" +
+         "#{if v = img.image_version then v + '/' else ''}" +
+         "large.jpg") if img)
+      ])[0]
+  )
+  imageUrls = _.compact(
+    for attachment in _.where(attachments, _type: 'PostImage')
+      "#{GRAVITY_CLOUDFRONT_URL}/post_images/#{attachment._id}/large.jpg"
+  )
+  linkUrls = _.compact(
+    for attachment in _.where(attachments, _type: 'PostLink')
+      attachment.oembed_json?.thumbnail_url or attachment.oembed_json?.url
+  )
+  imageUrls[0] or linkUrls[0] or artworkUrls[0] or null
+
 postsToArticles = (posts, callback) ->
   return callback() unless posts.length
   debug "Migrating #{posts.length} posts...."
@@ -145,64 +173,37 @@
       # Map Gravity data into a Positron schema
       data =
         _id: post._id
-        slugs: (post._slugs or []).concat([post._id.toString()])
+        slugs: [post._id.toString()].concat(post._slugs)
         author_id: ObjectId(post.author_id)
         author: User.denormalizedForArticle(author) if author
         thumbnail_title: post.title
         thumbnail_teaser: $?('p')?.first()?.text()
-        thumbnail_image: _.compact(
-          for attachment, i in (post.attachments ? [])
-            switch attachment._type
-              when 'PostArtwork'
-                artwork = _.select(artworks, (artwork) ->
-                  attachment.artwork_id.toString() is artwork._id.toString()
-                )[0]
-                img = artwork?.additional_images?[0]
-                _.compact([
-                  img?.image_urls?.large
-                  _.sample(img?.image_urls ? [])
-                  artwork?.image_urls?.large
-                  _.sample(artwork?.image_urls ? [])
-                  (("http://static.artsy.net/additional_images/#{img._id}/" +
-                   "#{if v = img.image_version then v + '/' else ''}" +
-                   "large.jpg") if img)
-                ])[0]
-              when 'PostImage'
-                "#{GRAVITY_CLOUDFRONT_URL}/post_images/" +
-                "#{post.attachments?[i]?._id}/large.jpg"
-              when 'PostLink'
-                (
-                  post.attachments?[i]?.oembed_json?.thumbnail_url or
-                  post.attachments?[i]?.oembed_json?.url
-                )
-              else
-                null
-        )[0]
+        thumbnail_image: bestThumbnail(post.attachments, artworks)
         title: post.title
         published: post.published
         published_at: moment(post.published_at).toDate()
         updated_at: moment(post.updated_at).toDate()
         sections: (
           slideshowItems = _.compact(for attachment in (post.attachments or [])
-            switch attachment?._type
+            switch attachment._type
               when 'PostArtwork'
                 {
                   type: 'artwork'
-                  id: attachment?.artwork_id
+                  id: attachment.artwork_id
                 }
               when 'PostImage'
                 {
                   type: 'image'
                   url: "#{GRAVITY_CLOUDFRONT_URL}/post_images/" +
-                    "#{attachment?._id}/larger.jpg"
+                    "#{attachment._id}/larger.jpg"
                 }
               when 'PostLink'
-                if attachment?.url?.match /youtube|vimeo/
+                if attachment.url?.match /youtube|vimeo/
                   {
                     type: 'video'
                     url: attachment.url
                   }
-                else if attachment?.url?.match /jpeg|jpg|png|gif/
+                else if attachment.url?.match /jpeg|jpg|png|gif/
                   {
                     type: 'image'
                     url: attachment.url


Fixed typo

Fixed typo

--- bullet.coffee
+++ bullet.coffee
@@ -23,7 +23,7 @@

     options = _.merge(defaultOptions, options)

-    @maxTravelDistance = options.maxTravelDistance
+    @timeSinceStart = options.timeSinceStart
     @position = options.position
     @shipId = options.shipId
     @maxLifeTime = options.maxLifeTime
@@ -48,7 +48,7 @@
     !@active

   runFrame: (timeDiff)->
-    timeSinceStart += timeDiff
+    @timeSinceStart += timeDiff
     velocityWithTime = helper.multVector(@velocity, timeDiff)
     @position = helper.addVectors(@position, velocityWithTime)

@@ -59,7 +59,7 @@

     @velocity[0] = Math.max(-1, Math.min(@velocity[0], 1));
     @velocity[1] = Math.max(-1, Math.min(@velocity[1], 1));
-    needToRemove = timeSinceStart > @maxLifeTime
+    needToRemove = @timeSinceStart > @maxLifeTime
     console.log("Need to remove bullet") if needToRemove
     @markForDeletion() if needToRemove
     console.log("bullet moved to #{@position} with velocity #{@velocity}")


New changes!

New changes!

--- button.coffee
+++ button.coffee
@@ -16,12 +16,14 @@
         if err
           console.log 'Error turning pin on', err

-  gpio.setup(buttonPin, gpio.DIR_IN, ->);
-
   gpio.write ledPin, true, (err)->
     console.log 'On'
     if err
       console.log 'Error turning pin on', err
+
+gpio.setup buttonPin, gpio.DIR_IN, (err)->
+  if err
+    console.log "Error setting up IN on button", err

 process.on 'SIGINT', ->


added mockup for querying the first node with packages info

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

--- test/spec/controllers/main.coffee
+++ test/spec/controllers/main.coffee
@@ -24,7 +24,12 @@
       { 'id': 'my2.full.fqdn' }
     ]

+    @node1 = {
+      'packages': []
+    }
+
     $httpBackend.whenGET("/nodes").respond(@nodes);
+    $httpBackend.whenGET("/node/"+@nodes[0]['id']).respond(@node1);

     MainCtrl = $controller('MainCtrl', {
       $scope: scope,


default -> initial

default -> initial

--- test/tests/LayerStatesTest.coffee
+++ test/tests/LayerStatesTest.coffee
@@ -107,7 +107,7 @@
 			layer.animateTo 'stateA', instant: true
 			layer.html.should.equal "aaa"

-		it "should not change style when going back to default", ->
+		it "should not change style when going back to initial", ->
 			layer = new Layer
 			layer.style.fontFamily = "Arial"
 			layer.style.fontFamily.should.equal "Arial"
@@ -126,7 +126,7 @@

 	describe "Properties", ->

-		it.skip "should bring back the 'default' state values when using 'next'", (done) ->
+		it "should bring back the 'initial' state values when using 'animateToNext'", (done) ->

 			layer = new Layer
 			layer.states.add
@@ -204,7 +204,7 @@

 			layer.animateTo "stateA", {curve:"linear", time:0.1}

-		it "should restore the default state when using non exportable properties", ->
+		it "should restore the initial state when using non exportable properties", ->

 			layer = new Layer
 			layer.states =


fix(notifications): > 5 should be >= 5

fix(notifications): > 5 should be >= 5

--- internal_packages/unread-notifications/lib/main.coffee
+++ internal_packages/unread-notifications/lib/main.coffee
@@ -40,7 +40,7 @@
       Actions.setFocus(collection: 'thread', item: thread)

   _notifyMessages: ->
-    if @stack.length > 5
+    if @stack.length >= 5
       @_notifyAll()
     else if @stack.length > 0
       @_notifyOne(@stack.pop())


compile fontnicks

compile fontnicks


Add file-icons

Add file-icons

--- Atom/config.cson
+++ Atom/config.cson
@@ -27,3 +27,5 @@
     markerType: "dot"
   "open-recent":
     maxRecentFiles: 10
+  "file-icons":
+    onChanges: true


Simplify gestures.

Simplify gestures.

--- _includes/coffeescript/nav-gestures.coffee
+++ _includes/coffeescript/nav-gestures.coffee
@@ -1,11 +1,8 @@
 $ ->
   $swipeable = $('#content').hammer()

-  $previous = $('[data-previous-guest]')
-  $next = $('[data-next-guest]')
-
-  $previousUrl = $previous.attr 'href'
-  $nextUrl = $next.attr 'href'
+  $previousUrl = $('[data-previous-guest]').attr 'href'
+  $nextUrl = $('[data-next-guest]').attr 'href'

   $swipeable.on "swipeleft", (e) ->
     unless $previousUrl is undefined


Fixed an incorrect error class.

Fixed an incorrect error class.

--- cms/System/Kernel.coffee
+++ cms/System/Kernel.coffee
@@ -14,7 +14,7 @@
   hooks = {}

   constructor: () ->
-    throw new NotImplementedError "Attempt to construct a singleton class!" if kernelCreated
+    throw new Error "Attempt to construct a singleton class!" if kernelCreated

   createHookable: (name) ->
     @hooks[name] = {}


allow local blob url for cors

allow local blob url for cors

--- config/http.coffee
+++ config/http.coffee
@@ -13,6 +13,7 @@
 					"data:"
 					"http://#{host}"
 					"https://#{host}"
+					"blob:"
 				]
 				ret = csp
 					directives:


social: fix participant added/removed events are not forwarded bug

--- client/Main/socialapicontroller.coffee
+++ client/Main/socialapicontroller.coffee
@@ -197,7 +197,11 @@

         data = mapperFn data

-        return  unless validatorFn?(data)
+        if validatorFn
+          if typeof validatorFn isnt "function"
+            return warn "validator function is not valid"
+
+          return  unless validatorFn(data)

         target.emit event, data, rest...


Sidebar: Add message item to include file

--- client/Social/Activity/includes.coffee
+++ client/Social/Activity/includes.coffee
@@ -13,6 +13,7 @@
   "sidebar/activitysidebar.coffee"
   "sidebar/activitysideview.coffee"
   "sidebar/sidebaritem.coffee"
+  "sidebar/messageitem.coffee"
   "sidebar/memberitem.coffee"
   "sidebar/messageitemtext.coffee"
   "sidebar/messageitemicon.coffee"


Do not get selected message when getting nextCOnversation

--- client/app/stores/message_store.coffee
+++ client/app/stores/message_store.coffee
@@ -590,9 +590,6 @@
         messageID = param.messageID or @getCurrentID()
         conversationID = param.conversationID

-        # FIXME : FIX de la Inbox :
-        # lorsque l'on change de vues
-        # la sélection doit être supprimée
         messages = _messagesWithInFlights()

         # Remove selected messages


another fake change to test sprint.ly integration, fixes #16, #34

--- common.coffee
+++ common.coffee
@@ -1,5 +1,4 @@
 # collections
-
 Schemas = {}

 Schemas.User = new SimpleSchema({


:es::white_check_mark: Updated at https://danielx.net/editor/

--- tools.coffee
+++ tools.coffee
@@ -38,7 +38,7 @@
   cut: do ->
     path = []
     active = false
-    targetSet = null
+    targetMap = null

     document.addEventListener 'mouseup', () ->
       return unless active
@@ -52,15 +52,28 @@
       # Find the first two intersections of the path and the image boundary
       # Create a path and a complement path
       # split into two
+
+      canvas = document.querySelector('canvas')
+      context = canvas.getContext('2d')

-      targetSet.forEach (target) ->
-
+      targetMap.forEach (intersections, target) ->
+        if intersections.length >= 2
+          beginPathIndex = intersections.unshift()
+          endPathIndex = intersections.unshift()
+
+          i = beginPathIndex
+          while i < endPathIndex
+            line = Line
+              start: path[i]
+              end: path[i+1]
+            drawLine(context, line, "#0F0")
+            i += 1

     name: "Cut"
     mousedown: (e) ->
       active = true
-      targetSet = new Set
-
+      targetMap = new Map
+
       path = [Point localPosition(e, false)]

     mousemove: (e) ->
@@ -72,8 +85,8 @@
       if active
         # TODO: Should add all targets underneath this point, not just the top
         if target != e.currentTarget
-          unless targetSet.has target
-            targetSet.add target
+          unless targetMap.has target
+            targetMap.set target, []

             drawRect(context, target)

@@ -86,12 +99,15 @@

         drawLine context, line, "purple"

-        targetSet.forEach (target) ->
+        targetMap.forEach (intersections, target) ->
           rectLines(target).forEach (targetLine) ->
             intersection = line.intersects(targetLine)

             if intersection
               drawCircle context, intersection
+
+              intersections.push path.length
+              path.push intersection

         path.push current


Skip mobile browsers for now, but add Opera

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -99,10 +99,10 @@
             browserName: 'firefox'
           ,
             browserName: 'safari'
+            platform: 'OS X 10.8'
+            version: '6'
           ,
-            browserName: 'ipad'
-          ,
-            browserName: 'android'
+            browserName: 'opera'
           ,
             browserName: 'internet explorer'
             platform: 'WIN8'


Fixed sass task.

Fixed sass task.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -65,7 +65,7 @@
         options:
           sourcemap: 'auto'
         expand: true
-        cwd: ['examples']
+        cwd: 'examples'
         src: ['*.scss', '!_*.scss']
         dest: '.'
         ext: '.css'


Use camelCase options for jsbeautifier

Use camelCase options for jsbeautifier

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -71,9 +71,9 @@
       options:
         mode: "VERIFY_ONLY"
         js:
-          space_after_anon_function: true
+          spaceAfterAnonFunction: true
           wrapLineLength: 120
-          indent_size: 2
+          indentSize: 2

     # CoffeeLint
     coffeelint:


Upload to s3 task

Upload to s3 task

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -54,10 +54,10 @@

       upload: [
         src: "src/bugsnag.js",
-        dest: "bugsnag-<%= process.env.RELEASE_VERSION %>.js"
+        dest: "bugsnag-<%= pkg.version %>.js"
       ,
         src: "dist/bugsnag.min.js",
-        dest: "bugsnag-<%= process.env.RELEASE_VERSION %>.min.js"
+        dest: "bugsnag-<%= pkg.version %>.min.js"
       ]

     # Version bumping


Removed faulty bundle rule

Removed faulty bundle rule

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -60,7 +60,6 @@
             'bower_components/backbone/backbone.js'
             'bower_components/backbone-deferred/backbone-deferred-q.js'
             'bower_components/rivets/dist/rivets.js'
-            'bower_components/jasmine-ajax/lib/mock-ajax.js'
             'dist/tails-only.js'
           ]


feat(grunt): simplify grunt buid task

feat(grunt): simplify grunt buid task

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -376,40 +376,26 @@

   grunt.registerTask "test", "Build test task", [
     "build"
-    "lesslint"
     "validation"
   ]

   grunt.registerTask "build", "Build site with `jekyll`, use `--busy` to set availability to false", (target) ->
     grunt.config.set "replace.availability.replacements.0.to", "$1 false" if grunt.option("busy")
-    if grunt.option("fast")
-      grunt.task.run [
-        "replace"
-        "clean"
-        "coffeelint"
-        "uglify:dist"
-        "less:dist"
-        "autoprefixer:dist"
-        "csscomb"
-        "jekyll:dist"
-        "usebanner"
-        "reset"
-      ]
-    else
-      grunt.task.run [
-        "replace"
-        "clean"
-        "coffeelint"
-        "uglify:dist"
-        "less:dist"
-        "autoprefixer:dist"
-        "csscomb"
-        "jekyll:dist"
-        "concurrent:dist"
-        "smoosher"
-        "usebanner"
-        "reset"
-      ]
+    grunt.task.run [
+      "replace"
+      "clean"
+      "coffeelint"
+      "uglify:dist"
+      "lesslint"
+      "less:dist"
+      "autoprefixer:dist"
+      "csscomb"
+      "jekyll:dist"
+      "concurrent:dist"
+      "smoosher"
+      "usebanner"
+      "reset"
+    ]

   grunt.registerTask "sync", "Build site + rsync static files to remote server", [
     "build"


fix grunt

fix grunt

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -52,15 +52,6 @@
     qunit:
       files: ["test/**/*.html"]

-    watch:
-      gruntfile:
-        files: "<%= jshint.gruntfile.src %>"
-        tasks: ["jshint:gruntfile"]
-
-      lib_test:
-        files: "<%= jshint.lib_test.src %>"
-        tasks: ["jshint:lib_test", "qunit"]
-
     jst:
       options:
         processName: (name) ->
@@ -100,6 +91,7 @@
   grunt.loadNpmTasks "grunt-contrib-stylus"
   grunt.loadNpmTasks "grunt-contrib-watch"
   grunt.loadNpmTasks "grunt-contrib-jst"
+  grunt.loadNpmTasks "grunt-contrib-connect"

   # Default task.
   grunt.registerTask "default", ["jst", "stylus"]


Deleted the test unit on the controller helper

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -17,7 +17,6 @@
                     'tests/units/lib/bootstrapper.js': 'dev/tests/units/lib/bootstrapper.coffee'
                     'tests/units/lib/app.js': 'dev/tests/units/lib/app.coffee'
                     'tests/units/lib/helper.js': 'dev/tests/units/lib/helper.coffee'
-                    'tests/units/helpers/controller.js': 'dev/tests/units/helpers/controller.coffee'
         copy:
             dist:
                 files: [


Set max cyclomatic complexity in cs to 14

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -97,7 +97,7 @@
           value: 120
         cyclomatic_complexity:
           level: 'error'
-          value: 12
+          value: 14

       source: ['src/**/*.coffee']
       grunt: 'Gruntfile.coffee'


Chai is already added as a framework.

Chai is already added as a framework.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -60,7 +60,6 @@
         options:
           separator: ';'
         src: [
-          'node_modules/chai/chai.js'
           'bower_components/angular-mocks/angular-mocks.js'
           'node_modules/chai-as-promised/lib/chai-as-promised.js'
           'node_modules/ng-midway-tester/src/ngMidwayTester.js'


Watch Gruntfile for changes

Watch Gruntfile for changes

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -89,6 +89,7 @@
     # Watch for changes to source and vendor files
     watch: {
       files: [
+        'Gruntfile.coffee',
         'src/**/*.coffee',
         'vendor/**/*.js'
       ],


Grunt: Use coffescript transform instead of manual build

Fixes build because our dependencies, like Fletcher, are also
using CoffeeScript

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -23,49 +23,26 @@
           'level': 'ignore'

     # Building for browser
-    coffee:
-      helloworld:
-        options:
-          bare: true
-        expand: true
-        cwd: 'examples/helloworld'
-        src: ['**/*.coffee']
-        dest: 'browser/examples/helloworld/'
-        ext: '.js'
-      lib:
-        options:
-          bare: true
-        expand: true
-        cwd: 'lib'
-        src: ['**/*.coffee', '../index.coffee']
-        dest: 'browser/lib/'
-        ext: '.js'
-      spec:
-        options:
-          bare: true
-        expand: true
-        cwd: 'spec'
-        src: ['**/*.coffee']
-        dest: 'browser/spec/'
-        ext: '.js'
-
     browserify:
       helloworld:
         src: [ 'browser/examples/helloworld/index.js' ],
         dest: './browser/dist/helloworld.js',
         options:
+          transform: ['coffeeify']
           browserifyOptions:
             standalone: 'helloworld'
       lib:
         src: [ 'browser/index.js' ],
         dest: './browser/dist/flowerflip.js',
         options:
+          transform: ['coffeeify']
           browserifyOptions:
             standalone: 'flowerflip'
       spec:
         src: [ 'browser/spec/*.js' ],
         dest: './browser/dist/spec.js',
         options:
+          transform: ['coffeeify']
           browserifyOptions:
             standalone: 'spec'

@@ -83,11 +60,10 @@
   @loadNpmTasks 'grunt-mocha-phantomjs'

   # Grunt plugins for deploying layout filter
-  @loadNpmTasks 'grunt-contrib-coffee'
   @loadNpmTasks 'grunt-browserify'

-  @registerTask 'build-helloworld', ['coffee:helloworld', 'browserify:helloworld']
-  @registerTask 'build-lib', ['coffee:lib', 'browserify:lib']
+  @registerTask 'build-helloworld', ['browserify:helloworld']
+  @registerTask 'build-lib', ['browserify:lib']

   @registerTask 'build', ['build-lib', 'build-helloworld']

@@ -95,7 +71,6 @@
     'coffeelint'
     'mochaTest'
     'build'
-    'coffee:spec'
     'browserify:spec'
     'mocha_phantomjs'
   ]


fixes issue #96

fixes issue #96

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -114,7 +114,7 @@
     exec:
       bundler: cmd: 'bundle install --quiet'
       remove: cmd: 'rm -rf _site/*'
-      jekyll: cmd: 'jekyll build --quiet'
+      jekyll: cmd: 'bundle exec jekyll build --quiet'
       new_post: cmd: 'bash _helper/new-post.sh'
       status: cmd: "clear && echo the site is now accessible at http://localhost:<%= connect.server.options.port %>"


Remove grunt-watchify from Gruntfile.

Remove grunt-watchify from Gruntfile.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -128,7 +128,6 @@
   @loadNpmTasks 'grunt-contrib-clean'
   @loadNpmTasks 'grunt-docco'
   @loadNpmTasks 'grunt-banner'
-  @loadNpmTasks 'grunt-watchify'

   # Grunt plugins used for testing
   @loadNpmTasks 'grunt-coffeelint'


Fix running specs on Windows.

Fix running specs on Windows.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -40,7 +40,7 @@
           failOnError: true

       test:
-        command: 'jasmine-focused --captureExceptions --coffee spec/'
+        command: 'node node_modules/jasmine-focused/bin/jasmine-focused --captureExceptions --coffee spec/'
         options:
           stdout: true
           stderr: true


Don't use abbr(eviations).

Don't use abbr(eviations).

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -42,7 +42,7 @@
         outputStyle: 'expanded'
         noLineComments: false
       dev: {}
-      prod:
+      production:
         options:
           outputStyle: 'compressed'
           noLineComments: true
@@ -111,7 +111,7 @@

     # Imagemin configuration
     imagemin:
-      prod:
+      production:
         files: [
           expand: true
           cwd: '<%= webAssetsPath %>/images/'
@@ -300,7 +300,7 @@

   # Generate the css-files for live
   grunt.registerTask 'generateCssForProduction', [
-    'compass:prod'
+    'compass:production'
     'autoprefixer'
     'clean:afterGenerateCssForProduction'
   ]


Only include the minified version of React

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -67,7 +67,7 @@
                 {
                   name: 'React',
                   location: 'vendor/react',
-                  main: 'react'
+                  main: 'react.min'
                 }
               ]
             });
@@ -81,7 +81,7 @@
         files: [
           expand: true
           cwd: 'node_modules/react/dist'
-          src: '*.js'
+          src: 'react.min.js'
           dest: 'browser/vendor/react'
           filter: 'isFile'
         ,


Add non-test coffee compile task

Add non-test coffee compile task

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -71,6 +71,9 @@
       jsTest:
         files: '<%= jshint.test.src %>'
         tasks: ['jshint:test', 'simplemocha']
+      coffee:
+        files: '<%= coffeelint.lib.src %>'
+        tasks: ['coffeelint:lib','coffee:lib']
       cofffeLib:
         files: '<%= coffeelint.lib.src %>'
         tasks: ['coffeelint:lib','coffee:lib', 'simplemocha']


OAUTH_GATE: remove trailing slash

OAUTH_GATE: remove trailing slash

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -91,7 +91,7 @@
             replacement: process.env.NOFLO_OAUTH_PROVIDER or 'https://passport.thegrid.io'
           ,
             pattern: /\$NOFLO_OAUTH_GATE/ig
-            replacement: process.env.NOFLO_OAUTH_GATE or 'https://noflo-gate.herokuapp.com/'
+            replacement: process.env.NOFLO_OAUTH_GATE or 'https://noflo-gate.herokuapp.com'
           ,
             pattern: /\$NOFLO_OAUTH_SERVICE_USER/ig
             replacement: process.env.NOFLO_OAUTH_SERVICE_USER or 'https://api.flowhub.io'


Added setup task to Grunt for compiling lib JS.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -61,5 +61,6 @@
       src: ['**']
   }

+  grunt.registerTask 'setup', ['uglify', 'connect', 'watch']
   grunt.registerTask 'default', ['connect', 'watch']
   grunt.registerTask 'deploy', ['gh-pages']


gruntfile

gruntfile

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -1,4 +1,4 @@
-ALL_TASKS = ['jst', 'coffee', 'concat', 'sass']
+ALL_TASKS = ['jst:all', 'coffee:all', 'concat:all', 'sass:all']

 module.exports = (grunt) ->

@@ -45,4 +45,4 @@
         files: ['./coffee/**/*.coffee', 'templates/**/*.html', './sass/**/*.sass']
         tasks: ALL_TASKS

-  grunt.registerTask 'default', ALL_TASKS+  grunt.registerTask 'default', ALL_TASKS


Custom scroll: Propagate ineffective function key event

--- lib/components/scrollview/customscrollviewinner.coffee
+++ lib/components/scrollview/customscrollviewinner.coffee
@@ -169,8 +169,10 @@
     if event.which is SPACEBAR and event.shiftKey
       @pageUp()
     else if event.metaKey or event.ctrlKey
-      @scrollTo top : 0  if event.which is UPARROW
-      @scrollToBottom()  if event.which is DOWNARROW
+      switch event.which
+        when UPARROW   then @scrollTo top: 0
+        when DOWNARROW then @scrollToBottom()
+        else shouldPropagate = yes
     else
       switch event.which
         when PAGEUP then @pageUp()


fix a bug package 删除view问题

fix a bug
package 删除view问题

--- lib/gitCreatePackage/gitCreatePackage-info-view.coffee
+++ lib/gitCreatePackage/gitCreatePackage-info-view.coffee
@@ -123,6 +123,8 @@
       wizardView.disableNext()

   destroy: ->
+    unless @loginView.isHidden()
+      @loginView.hide()
     @remove()

   onNext: (wizard) ->
@@ -135,8 +137,6 @@
     unless @loginView.isHidden()
       return

-    console.log "loginView"
-
     url = "#{server}/api/packages/findOne/#{@packageName.getText()}?access_token=#{access_token}"
     Q.Promise (resolve, reject, notify) =>
       request url, (error, response, body) ->


switching to staging server for api calls

--- app/scripts/app.coffee
+++ app/scripts/app.coffee
@@ -22,8 +22,8 @@
   #$httpProvider.defaults.headers.post["Content-Type"] = "application/x-www-form-urlencoded"
   #$sceDelegateProvider.resourceUrlWhitelist(['self', 'http://localhost:9000/**', 'http://localhost:9292/**', 'http://127.0.0.1:9292/**', 'http://cobudget.enspiral.info/**'])
 ])
-#.constant("API_PREFIX", "http://api.cobudget.enspiral.info/cobudget")
-.constant("API_PREFIX", "http://localhost:9292/cobudget")
+.constant("API_PREFIX", "http://api.cobudget.enspiral.info/cobudget")
+#.constant("API_PREFIX", "http://localhost:9292/cobudget")
 .run(["$rootScope", "API_PREFIX", ($rootScope, API_PREFIX) ->
   $rootScope.$debugMode = "on"
   $rootScope.admin = false


minor fixes; check API key on DELETE (unused)

--- code/serv.coffee
+++ code/serv.coffee
@@ -36,9 +36,8 @@
   res.header('Content-Type', 'application/json')
   res.render('index', {rooturl:'example.com'})

-# Check API key for all POSTs
 # TODO: should this be middleware?
-app.post /.*/, (req, res, next) ->
+check_api_key = (req, res, next) ->
   res.header('Content-Type', 'application/json')
   if req.body.apikey?
     url = "https://scraperwiki.com/froth/check_key/#{req.body.apikey}"
@@ -50,11 +49,15 @@
   else
     res.send {error: "No API key supplied"}, 403

+# Check API key for all POSTs
+app.post /.*/, check_api_key
+app.delete /.*/, check_api_key
+
 # Documentation for SSHing to a box.
 app.get "/:box_name$", (req, res) ->
   res.header('Content-Type', 'application/json')
   Box.findOne {name: req.params.box_name}, (err, box) ->
-    return res.send { error: "Box not found" }, 404 unless box?
+    return (res.send { error: "Box not found" }, 404) unless box?
     res.render('box', {
       box_name: req.params.box_name,
       rooturl:'example.com'


Fix bug in quickjump.js

Fix bug in quickjump.js

--- app/assets/javascripts/quickjump.js.coffee
+++ app/assets/javascripts/quickjump.js.coffee
@@ -91,7 +91,7 @@
         else
           url = $(event.target).data('url')
           data = {}
-          data[$(event.target).attr("name")] = query
+          data[$(event.target).attr("name")] = @dictionary.getKey(query)
           self = this
           OUT.setLazyTimer "quickjump_request", @DELAY_BEFORE_SERVER_CALL, ->
             self.requestResults(query, url, data)


no numbers in tutorial

no numbers in tutorial

--- app/assets/javascripts/tag_building.coffee
+++ app/assets/javascripts/tag_building.coffee
@@ -177,6 +177,7 @@
 			@intro.setOptions(
 				skipLabel: "Exit tutorial"
 				tooltipClass: "tutorial"
+				showStepNumbers: false
 				steps: [
 						{
 							element: "#map-highlight"
@@ -239,8 +240,8 @@
 							position: "top"
 						}
 						{
-							element: "#fix-button"
-							intro: "Press FIX."
+							element: "#yes-button"
+							intro: "Press YES. That's a LARGE building!"
 							position: "top"
 						}
 						{
@@ -314,9 +315,9 @@
 		@removeButtonListeners()

 		switch @intro._currentStep
-			when 1, 2, 23
+			when 1, 2
 				$(".introjs-helperLayer").addClass("noMap yesNext")
-			when 4, 6, 8, 10, 12, 14, 16, 18, 18, 20
+			when 4, 6, 8, 10, 12, 14, 16, 18, 20, 22
 				$(".introjs-helperLayer").addClass("noMap")
 				@addButtonListeners()
 		@


fixed bug in `component install --link`

fixed bug in `component install --link`

there was a problem where dead symlinks woule return 'false' to 'exists()'
calls (b/c the link exists but the target doesnt), and then it would choke when
it tried to create the link.  this tries to remove the link just in case

--- component.coffee
+++ component.coffee
@@ -56,6 +56,10 @@
       log "link-installing #{name} to #{tgtDir}"
       orig = resolve process.cwd()
       process.chdir tgtDir
+      # there's a chance that 'csi' is a dead link (so exists() returns false,
+      # but there's actually a link in the directory).  remove that here.
+      try
+        fs.unlinkSync name
       fs.symlinkSync join(orig, src), name, "dir"
       process.chdir orig
     else


Unlock dongle with pin before flashing

Unlock dongle with pin before flashing

--- app/src/fup/firmware_update_request.coffee
+++ app/src/fup/firmware_update_request.coffee
@@ -28,6 +28,7 @@
   ErrorDongleMayHaveASeed: ledger.errors.ErrorDongleMayHaveASeed
   ErrorDueToCardPersonalization: ledger.errors.ErrorDueToCardPersonalization
   HigherVersion: ledger.errors.HigherVersion
+  WrongPinCode: ledger.errors.WrongPinCode

 ExchangeTimeout = 200

@@ -296,18 +297,25 @@
     .done()

   _processUnlocking: ->
-    @_waitForUserApproval('unlock')
+    @_waitForUserApproval('pincode')
     .then =>
-      unless @_stateCache.pincode?
-        getRandomChar = -> "0123456789".charAt(_.random(10))
-        @_stateCache.pincode = getRandomChar() + getRandomChar()
-      pincode = @_stateCache.pincode
-      @_dongle.unlockWithPinCode pincode, (isUnlocked, error) =>
-        @emit "erasureStep", if error?.retryCount? then error.retryCount else 3
-        @_waitForPowerCycle()
-      return
+      if @_forceDongleErasure
+        @_setCurrentState(States.Erasing)
+      else
+        if @_pinCode.length is 0
+          @_setCurrentState(States.ReloadingBootloaderFromOs)
+          @_handleCurrentState()
+          return
+        pin = new ByteString(@_pinCode, ASCII)
+        @_getCard().exchange_async(new ByteString("E0220000" + Convert.toHexByte(pin.length), HEX).concat(pin)).then (result) =>
+          if @_getCard().SW os 0x900
+            @_setCurrentState(States.ReloadingBootloaderFromOs)
+            @_handleCurrentState()
+            return
+          else
+            throw Errors.WrongPinCode
     .fail =>
-      @_failure(Errors.CommunicationError)
+      @_failure(Errors.WrongPinCode)
     .done()

   _processInitOs: ->


Fix endless while if one image

Fix endless while if one image

fix issue that makes browser freeze up if the film roll is loaded with
only one image and moveToIndex is called twice

--- coffee/jquery.film_roll.coffee
+++ coffee/jquery.film_roll.coffee
@@ -275,7 +275,7 @@
     jQuery(@pager_links[index]).addClass 'active'
     # if shuttle width is wider than the wrapper, we need to rotate
     wrapper_width = @wrapper.width()
-    if wrapper_width < @real_width # rotate if the children are wider than the container
+    if wrapper_width < @real_width && @children.length > 1 # rotate if the children are wider than the container
       # first, where is this photo?
       # what should show on either side of this child
       visible_margin = (wrapper_width - @child_widths[index])/2


Spliced: bugfix toggle control on end.

Spliced: bugfix toggle control on end.

--- coffee/src/shells/spliced.shell.coffee
+++ coffee/src/shells/spliced.shell.coffee
@@ -127,6 +127,11 @@
     @controlsView.$('.control-view.pause').addClass 'hidden'

+  onMediaDidEnd: =>
+    @controlsView.$('.control-view.play').removeClass 'hidden'
+    @controlsView.$('.control-view.pause').addClass 'hidden'
+
+

 class SplicedShell.RemixView extends CollectionShell.RemixView


youtube: fixed custom offset loadVideoById bug

--- coffee/src/shells/youtube.shell.coffee
+++ coffee/src/shells/youtube.shell.coffee
@@ -286,9 +286,12 @@
         # this *should* initialize the playback at the correct point but
         # doesn't. Need a more robust solution (tick)
         start = parseInt(@model.timeStart() ? 0, 10)
-        @player.loadVideoById(@model.youtubeId(), start)
+        # This function is broken. It is sufficient to initialize the player
+        # using the playerID above.
+        # @player.loadVideoById(@model.youtubeId(), start)
         @player.playVideo()
         @player.pauseVideo()
+        @player.seekTo(start, true)

         # playing still needs buffering sometimes. hack: play then pause
         @setMediaState 'ready'


simplified drawCommentChain #347

simplified drawCommentChain #347

--- app/assets/javascripts/oxalis/view/skeletontracing/abstract_tree_renderer.coffee
+++ app/assets/javascripts/oxalis/view/skeletontracing/abstract_tree_renderer.coffee
@@ -191,21 +191,9 @@

   drawCommentChain : (decision, left, right, top, mode) ->

-    middle = @calculateMiddle(left, right)
-    topChild = @calculateChildTreeTop(mode, decision.chainCount, top)
-    childNode = decision.node.children[0]
-    nodeHasComment = @nodeHasComment(decision.node.id)
-
-    if nodeHasComment
-      childNode = decision.node
-      topChild -= @nodeDistance
-
-    childTree = @drawTreeWithWidths(childNode, left, right, topChild, mode)
-
-    if !nodeHasComment
-      @drawEdge(middle, topChild - @nodeDistance, childTree.middle, childTree.top)
-
-    return middle
+    topChild = @calculateChildTreeTop(mode, decision.chainCount, top) - @nodeDistance
+    extent = @drawTreeWithWidths(decision.node, left, right, topChild, mode)
+    return extent.middle

   drawBranch : (decision, left, right, top, mode) ->


next decision point inlcudes comments as well. refactored getNextDecisionPoint to getNextDecision

--- app/assets/javascripts/oxalis/view/skeletontracing/abstract_tree_renderer.coffee
+++ app/assets/javascripts/oxalis/view/skeletontracing/abstract_tree_renderer.coffee
@@ -83,37 +83,33 @@
     @recordWidths(root)
     @drawTreeWithWidths(root, @NODE_RADIUS, @canvas.width() - @NODE_RADIUS, @nodeDistance, mode)

+
   drawTreeWithWidths : (tree, left, right, top, mode) ->

     # get the decision point
-    decisionPoint = @getNextDecisionPoint(tree)
-
-    # TODO: algorithm expects all decision points to be branch points
-    # though a normal point with comment only has one child --> refactor this
-
-    middle = @calculateTreeMiddle(decisionPoint, left, right)
-    chainCount = @calculateChainCount(tree)
+    decision = @getNextDecision(tree)
+    middle = @calculateTreeMiddle(decision.node, left, right)
     rootX = middle # if decisionPoint is leaf, there's not much to do

     # if the decision point has 2 children, draw them and remember their position
-    if decisionPoint.children.length > 1
-
-      topChildren = @calculateChildTreeTop(mode, chainCount, top)
-      c1 = @drawTreeWithWidths(decisionPoint.children[0], left,  middle, topChildren, mode)
-      c2 = @drawTreeWithWidths(decisionPoint.children[1], middle, right, topChildren, mode)
+    if decision.isBranch
+
+      topChildren = @calculateChildTreeTop(mode, decision.chainCount, top)
+      leftTree = @drawTreeWithWidths(decision.node.children[0], left,  middle, topChildren, mode)
+      rightTree = @drawTreeWithWidths(decision.node.children[1], middle, right, topChildren, mode)

       # set the root's x coordinate to be in between the decisionPoint's children
-      rootX = (c1.rootX + c2.rootX) / 2
+      rootX = (leftTree.rootX + rightTree.rootX) / 2

       # draw edges from last node in 'chain' (or root, if chain empty)
       # and the decisionPoint's children
-      @drawEdge(rootX, topChildren - @nodeDistance, c1.rootX, c1.top)
-      @drawEdge(rootX, topChildren - @nodeDistance, c2.rootX, c2.top)
-
-    if mode == @MODE_NORMAL or chainCount < 3
+      @drawEdge(rootX, topChildren - @nodeDistance, leftTree.rootX, leftTree.top)
+      @drawEdge(rootX, topChildren - @nodeDistance, rightTree.rootX, rightTree.top)
+
+    if mode == @MODE_NORMAL or decision.chainCount < 3
       # Draw the chain and the root, connect them.
       node = tree
-      for i in [0..chainCount]
+      for i in [0..decision.chainCount]
         @drawNode(rootX, top + i * @nodeDistance, node.id)
         node = node.children[0]
         if i != 0
@@ -122,8 +118,10 @@
     else if mode == @MODE_NOCHAIN

       # Find out, if the chain contains an active node
-      node = tree.children[0]; hasActiveNode = false
-      for i in [0...(chainCount - 1)]
+      node = tree.children[0]
+      hasActiveNode = false
+
+      for i in [0...(decision.chainCount - 1)]
         hasActiveNode |= node.id == @activeNodeId
         node = node.children[0]

@@ -132,7 +130,7 @@
       @drawEdge(rootX, top, rootX, top + 0.5 * @nodeDistance)
       @drawChainIndicator(rootX, top + 0.5 * @nodeDistance, top + 1.5 * @nodeDistance, hasActiveNode)
       @drawEdge(rootX, top + 1.5 * @nodeDistance, rootX, top + 2 * @nodeDistance)
-      @drawNode(rootX, top + 2 * @nodeDistance, decisionPoint.id)
+      @drawNode(rootX, top + 2 * @nodeDistance, decision.node.id)

     return { rootX, top }

@@ -152,17 +150,6 @@
       child2 = decisionPoint.children[1]
       return (right - left) * child1.width / (child1.width + child2.width) + left

-  # Calculate the length of the 'chain' of nodes with one child,
-  # because they all share the root's x coordinate
-  calculateChainCount : (tree) ->
-
-    chainCount = 0
-    subTree = tree
-    while subTree.children.length == 1
-      subTree = subTree.children[0]
-      chainCount++
-
-    return chainCount

   # Calculate the top of the children
   calculateChildTreeTop : (mode, chainCount, top) ->
@@ -232,12 +219,23 @@
     @ctx.lineWidth = 1

-  getNextDecisionPoint : (tree) ->
+  getNextDecision : (tree) ->

     # Decision point is any point with point.children.length != 1
-    while tree.children.length == 1
+    # or any point that has a comment.
+    # Decision points will definitely be drawn.
+
+    chainCount = 0
+    while !@nodeHasComment(tree.id) and tree.children.length == 1
       tree = tree.children[0]
-    return tree
+      chainCount++
+
+    return {
+      node: tree,
+      chainCount,
+      isBranch: tree.children.length > 1
+      isLeaf: tree.children.length == 0
+    }

   recordWidths : (tree) ->
@@ -245,22 +243,28 @@
     # Because any node with children.length == 1 has
     # the same width as its child, we can skip those.

+    decision = @getNextDecision(tree)
+
     # Leaves just have a width of one
-    decisionPoint = @getNextDecisionPoint(tree)
-    if decisionPoint.children.length == 0
-      decisionPoint.width = 1
+    if decision.isLeaf
+      decision.node.width = 1
       return 1
+
+    decisionPoint = decision.node
+
+    # edge case: system is made for binary trees only
+    # allow a maximum of two
+    if decision.node.children.length > 2
+      decision.node.children = decision.node.children[0...2]

     # Branchpoints are as wide as its children combined.
     # But actually, we need the width of the children
-    result = 0
-    if decisionPoint.children.length > 2
-      decisionPoint.children = decisionPoint.children[0...2]
-
-    for child in decisionPoint.children
+    width = 0
+    for child in decision.node.children
       child.width = @recordWidths(child)
-      result += child.width
-    return result
+      width += child.width
+
+    return width

   getMaxTreeDepth : (tree, mode = @MODE_NORMAL, count = 0) ->
@@ -268,22 +272,28 @@
     unless tree
       return count

-    # One decision point
+    # current tree is a decision point
     count++

-    # Count non decision points
-    chainCount = 0
-    while tree.children.length == 1
-      tree = tree.children[0]
-      chainCount++
+    # find next decision point
+    decision = @getNextDecision(tree)
     if mode == @MODE_NOCHAIN
-      chainCount = Math.min(chainCount, 2)
-    count += chainCount
-
-    if tree.children.length == 0
+      decision.chainCount = Math.min(decision.chainCount, 2)
+    count += decision.chainCount
+
+    # bottom reached, done.
+    if decision.isLeaf
       return count
-    return Math.max(@getMaxTreeDepth(tree.children[0], mode, count),
-              @getMaxTreeDepth(tree.children[1], mode, count))
+
+    # traverse further and compare left & right subtree
+    if decision.isBranch
+      return Math.max(
+              @getMaxTreeDepth(decision.node.children[0], mode, count),
+              @getMaxTreeDepth(decision.node.children[1], mode, count)
+             )
+
+    # current decision point is a comment, follow the current chain
+    return @getMaxTreeDepth(decision.node.children[0], mode, count)

   getIdFromPos : (x, y) =>


fixed z layering #347

fixed z layering #347

--- app/assets/javascripts/oxalis/view/skeletontracing/abstract_tree_renderer.coffee
+++ app/assets/javascripts/oxalis/view/skeletontracing/abstract_tree_renderer.coffee
@@ -83,6 +83,9 @@
     @recordWidths(root)
     @drawTreeWithWidths(root, @NODE_RADIUS, @canvas.width() - @NODE_RADIUS, @nodeDistance, mode)

+    # because of z layering all nodes have to be drawn last
+    @drawAllNodes()
+

   drawTreeWithWidths : (tree, left, right, top, mode) ->

@@ -160,9 +163,6 @@

     @ctx.arc(x, y, radius, 0, 2 * Math.PI)
     @ctx.fill()
-
-    # put it in nodeList
-    @nodeList.push({x : x, y : y, id : id})

   drawEdge : (x1, y1, x2, y2) ->
@@ -231,7 +231,7 @@
     # Draw the chain and the root, connect them.
     node = root
     for i in [0..decision.chainCount]
-      @drawNode(left, top + i * @nodeDistance, node.id)
+      @addNode(left, top + i * @nodeDistance, node.id)
       node = node.children[0]
       if i != 0
         @drawEdge(left, top + (i - 1) * @nodeDistance, left, top + i * @nodeDistance)
@@ -242,11 +242,23 @@
     hasActiveNode = @chainContainsActiveNode(tree, decision.node)

     # Draw root, chain indicator and decision point
-    @drawNode(middle, top, tree.id)
+    @addNode(middle, top, tree.id)
     @drawEdge(middle, top, middle, top + 0.5 * @nodeDistance)
     @drawChainIndicator(middle, top + 0.5 * @nodeDistance, top + 1.5 * @nodeDistance, hasActiveNode)
     @drawEdge(middle, top + 1.5 * @nodeDistance, middle, top + 2 * @nodeDistance)
-    @drawNode(middle, top + 2 * @nodeDistance, decision.node.id)
+    @addNode(middle, top + 2 * @nodeDistance, decision.node.id)
+
+
+  addNode : (x, y, id) ->
+
+    # put it in nodeList
+    @nodeList.push({x : x, y : y, id : id})
+
+
+  drawAllNodes : ->
+
+    for {x, y, id} in @nodeList
+      @drawNode(x, y, id)

   getNextDecision : (tree) ->


change Config's registration to app

change Config's registration to app

--- app/templates/app/src/Config/Config.coffee
+++ app/templates/app/src/Config/Config.coffee
@@ -71,4 +71,4 @@

             settings = settings[prop]

-module.exports = (app)-> app.factory 'Config', -> config+module.exports = (app)-> app.factory 'Config', -> new Config


Render index view by default.

Render index view by default.

--- app/templates/src/controllers/index.coffee
+++ app/templates/src/controllers/index.coffee
@@ -1,3 +1,3 @@
 module.exports =
 	index: (req, res)->
-		res.send('You are here.')
+		res.render 'index'


:runner::clock630: Updated in browser at strd6.github.io/editor

--- pixie.cson
+++ pixie.cson
@@ -7,9 +7,8 @@
   "http://strd6.github.io/tempest/javascripts/envweb-v0.4.7.js"
 ]
 dependencies:
-  appcache: "STRd6/appcache:v0.1.0"
-  compression: "STRd6/lz-string:v1.3.3"
-  priority_queue: "STRd6/priority_queue:v2.0.0-pre"
-  runtime: "STRd6/runtime:v0.1.1"
-  sprite: "distri/sprite:v0.1.1"
-  "touch-canvas": "STRd6/touch-canvas:v0.2.0"
+  appcache: "distri/appcache:v0.2.0"
+  priority_queue: "STRd6/priority_queue:v2.0.0"
+  runtime: "STRd6/runtime:v0.2.0"
+  sprite: "distri/sprite:v0.2.0"
+  "touch-canvas": "distri/touch-canvas:v0.3.0"


change getBounds

change getBounds

--- syslibs/library.coffee
+++ syslibs/library.coffee
@@ -86,7 +86,7 @@

 # get browser size(include scrolling bar)
 getBounds=->
-    width = document.documentElement.clientWidth - 2
+    width = document.documentElement.clientWidth - 18
     height = document.documentElement.clientHeight - 2
     frame = JSRectMake(0, 0, width, height)
     return frame


change getBounds

change getBounds

--- syslibs/library.coffee
+++ syslibs/library.coffee
@@ -86,8 +86,8 @@

 # get browser size(include scrolling bar)
 getBounds=->
-    width = document.documentElement.clientWidth - 18
-    height = document.documentElement.clientHeight - 18
+    width = document.documentElement.clientWidth - 2
+    height = document.documentElement.clientHeight - 2
     frame = JSRectMake(0, 0, width, height)
     return frame


freezing screen when asking permission

freezing screen when asking permission

--- client/app/Applications/Members.kdapplication/ContentDisplays/profileview.coffee
+++ client/app/Applications/Members.kdapplication/ContentDisplays/profileview.coffee
@@ -115,6 +115,8 @@
         color      : "#ffffff"
         shape      : "spiral"

+    @cancelPhoto = @getCancelView()
+
     @headers =
       actions     : new AvatarChangeHeaderView
         buttons   : [@photoButton, @uploadButton, @gravatarButton]
@@ -125,7 +127,7 @@

       photo       : new AvatarChangeHeaderView
         title     : "Take Photo"
-        buttons   : [@getCancelView()]
+        buttons   : [@cancelPhoto]

       upload      : new AvatarChangeHeaderView
         title     : "Upload Image"
@@ -182,6 +184,13 @@
     @avatar.hide()
     @avatarHolder.addSubView @webcamTip
     @setWide()
+    @cancelPhoto.disable()
+    @getDelegate().avatarMenu.changeStickyState on
+
+    release = =>
+      @cancelPhoto.enable()
+      @getDelegate().avatarMenu.changeStickyState off
+
     @avatarHolder.addSubView @webcamView = new KDWebcamView
       hideControls  : yes
       countdown     : 3
@@ -196,10 +205,12 @@
     @webcamView.addSubView @takePhotoButton
     @webcamView.on "snap", (data)=> @avatarData = data
     @webcamView.on "allowed", =>
+      release()
       @webcamTip.destroy()
       @takePhotoButton.show()

     @webcamView.on "forbidden", =>
+      release()
       @webcamTip.updatePartial """
       <cite>
         You disabled the camera for Koding.
@@ -387,7 +398,7 @@
             delegate : @avatar
             x        : @avatar.getX() + 96
             y        : @avatar.getY() - 7
-          , customView: @avatarChange = new AvatarChangeView {}, @memberData
+          , customView: @avatarChange = new AvatarChangeView delegate: this, @memberData

           @avatarChange.on "UseGravatar", =>
             @avatarSetGravatar()


href support

href support

--- jquery-select7.coffee
+++ jquery-select7.coffee
@@ -1,9 +1,9 @@
 ###
 @name jquery-select7
-@version 0.2.12
+@version 0.2.13
 @author Se7enSky studio <info@se7ensky.com>
 ###
-###! jquery-select7 0.2.12 http://github.com/Se7enSky/jquery-select7 ###
+###! jquery-select7 0.2.13 http://github.com/Se7enSky/jquery-select7 ###

 plugin = ($) ->

@@ -102,7 +102,7 @@
 			for option, i in @options
 				continue if option.isPlaceholder
 				continue if i is @selectedIndex
-				$option = $ """

"""
+				$option = $ """

"""
 				$option.text option.title
 				$option.addClass "select7__option_disabled" if option.disabled
 				$option.prepend """""" if option.icon
@@ -112,6 +112,9 @@
 				{i} = $el.data()
 				option = @options[i]
 				return if option.disabled
+				if option.href
+					window.location.href = option.href
+					return
 				@$el.val(option.value).trigger("change")
 				@close()
 			@$select7.append @$drop


run it last since it screws with me js

run it last since it screws with me js

--- app/assets/javascripts/application.coffee
+++ app/assets/javascripts/application.coffee
@@ -33,7 +33,7 @@
 #= require transliteration/lib/browser/transliteration
 #
 #= require js-cookie/src/js.cookie
+#
+#= require_tree .

 Turbolinks.enableProgressBar()
-
-#= require_tree .


Add missing data for truncation.

Add missing data for truncation.

--- app/assets/javascripts/calendar.js.coffee
+++ app/assets/javascripts/calendar.js.coffee
@@ -73,7 +73,7 @@
           item.attr("data-original-title","")
     $(".bar-warning").attr("data-action",null)
     $(".tab-pane").attr("style",null)
-    $.event.trigger({type: "eventsTruncated", time: new Date()})
+    $.event.trigger({type: "eventsTruncated", time: new Date(), day: moment("#{year}-#{month}-#{day}","YYYY-MM-DD")})
     return
   refresh_view: ->
     if @datepicker?


Empty accordion structure

Empty accordion structure

--- src/javascript/accordion.coffee
+++ src/javascript/accordion.coffee
@@ -3,14 +3,17 @@
 # 手風琴

 ts.fn.accordion = value: ->
+
+    # ------------------------------------------------------------------------
+    # 變數與常數設置
+    # ------------------------------------------------------------------------
+
     # 模組名稱。
-    Name       = 'accordion'
+    NAME             = 'accordion'
     # 模組事件鍵名。
-    EventKey   = ".#{Name}"
-    # 設定鍵名。
-    SettingKey = "#{Name}"
-    #
-    ModuleNamespace = "module-#{Name}"
+    EVENT_NAMESPACE  = ".#{Name}"
+    # 模組命名空間。
+    MODULE_NAMESPACE = "module-#{Name}"

     # 模組設定。
     Settings =
@@ -37,12 +40,12 @@

     # 事件名稱。
     Event =
-        OPENING: "opening#{EventKey}"
-        OPEN   : "open#{EventKey}"
-        CLOSING: "closing#{EventKey}"
-        CLOSE  : "close#{EventKey}"
-        CHANGE : "change#{EventKey}"
-        CLICK  : "click#{EventKey}"
+        OPENING: "opening#{EVENT_NAMESPACE}"
+        OPEN   : "open#{EVENT_NAMESPACE}"
+        CLOSING: "closing#{EVENT_NAMESPACE}"
+        CLOSE  : "close#{EVENT_NAMESPACE}"
+        CHANGE : "change#{EVENT_NAMESPACE}"
+        CLICK  : "click#{EVENT_NAMESPACE}"

     # 樣式名稱。
     ClassName =
@@ -57,29 +60,12 @@
         ACTIVE_CONTENT: '.active.content'
         ACTIVE        : '.active'

-    # 資料。
-    Data =
-        $MODULES       : ts @
-        ARGUMENTS      : arguments
-        QUERY          : arguments[0]
-        QUERY_ARGUMENTS: [].slice.call arguments, 1
-        VALUE          : undefined
+    ts(@).each ->

-    # 每個模組
-    Data.$MODULES.each ->
-        # 本元素。
-        $this = ts @
-        element = @
-        #
-        $title   = $this.find Selector.TITLE
-        $content = $this.find Selector.CONTENT
-        #
-        methodInvoked = typeof Data.QUERY is 'string'
-        settings = {}
-        #
-        instance = undefined
+        # ------------------------------------------------------------------------
+        # 模組定義
+        # ------------------------------------------------------------------------

-        # 模組主體。
         module =

             # Initialize
@@ -87,13 +73,64 @@
             # 初始化

             initialize: =>
-                module.debug '初始化手風琴', $this

-                module.bind.events()
-                if settings.observeChanges
-                    module.observeChanges()
+            # Instantiate
+            #
+            # 實例化

-                module.instantiate()
+            instantiate: =>
+
+            # Observe Changes
+            #
+            # 結構異動觀察者
+
+            observeChanges: =>
+
+            # Refresh
+            #
+            # 更新資料
+
+            refresh: =>
+
+            # Destroy
+            #
+            # 摧毀
+
+            destroy: ->
+
+            # ------------------------------------------------------------------------
+            # 自訂函式
+            # ------------------------------------------------------------------------
+
+            # Open
+            #
+            # 展開
+
+            open: (index) ->
+
+            # Close
+            #
+            # 閉合
+
+            close: (index) ->
+
+            # Close Others
+            #
+            # 閉合指定以外
+
+            closeOthers: (index) ->
+
+            # Close All
+            #
+            # 閉合所有
+
+            closeAll: ->
+
+            # Toggle
+            #
+            # 切換開合
+
+            toggle: (index) ->

             # Bind
             #
@@ -105,225 +142,8 @@
                 #
                 # 事件

-                events:
-                    $this.on Event.CLICK, Selector.TITLE, ->
-                        module.toggle $title.indexOf @
-                    $this.on Event.OPENING, (event, context) ->
-                        settings.onOpening.call context, event
-                    $this.on Event.OPEN, (event, context) ->
-                        settings.onOpen.call context, event
-                    $this.on Event.CLOSING, (event, context) ->
-                        settings.onClosing.call context, event
-                    $this.on Event.CLOSE, (event, context) ->
-                        settings.onClose.call context, event
-                    $this.on Event.CHANGE, (event, context) ->
-                        settings.onChange.call context, event
+                events: =>

-            # Instantiate
-            #
-            # 實例化
-
-            instantiate: =>
-                module.debug '實例化手風琴', $this
-
-                instance = module
-                $this.data ModuleNamespace, module
-
-            # Observe Changes
-            #
-            # 結構異動觀察者
-
-            observeChanges: =>
-                if not 'MutationObserver' in window
-                    return
-                observer = new MutationObserver (mutations) ->
-                    module.debug "DOM 樹狀結構已變更，更新快取資料"
-                    module.refresh()
-                observer.observe element,
-                    childList : true
-                    subtree   : true
-                module.debug "已設置 DOM 樹狀結構異動觀察者", observer
-
-            # Refresh
-            #
-            # 更新資料
-
-            refresh: =>
-                $title   = $this.find Selector.TITLE
-                $content = $this.find Selector.CONTENT
-
-            # Destroy
-            #
-            # 摧毀
-
-            destroy: ->
-                module.debug '摧毀手風琴', $this
-
-                $this.removeData ModuleNamespace
-                     .off        EventKey
-
-            # Open
-            #
-            # 展開
-
-            open: (index) ->
-                module.debug '開啟手風琴分頁', index, $this
-
-                $t = $title.eq   index
-                $c = $content.eq index
-
-                if $t.hasClass ClassName.ACTIVE
-                    return
-
-                if settings.exclusive
-                    module.debug '由於手風琴分頁同時間僅能有一個打開，因此關閉其他分頁', index, $this
-                    module.closeAll()
-
-                $this.trigger Event.OPENING, $c.get()
-                $this.trigger Event.OPEN   , $c.get()
-                $this.trigger Event.CHANGE , $c.get()
-
-                $t.addClass ClassName.ACTIVE
-                $c.addClass ClassName.ACTIVE
-
-            # Close
-            #
-            # 閉合
-
-            close: (index) ->
-                module.debug '關閉手風琴分頁', index, $this
-
-                $t = $title.eq   index
-                $c = $content.eq index
-
-                if not $t.hasClass ClassName.ACTIVE
-                    return
-
-                $this.trigger Event.CLOSING, $c.get()
-                $this.trigger Event.CLOSE  , $c.get()
-                $this.trigger Event.CHANGE , $c.get()
-
-                $t.removeClass ClassName.ACTIVE
-                $c.removeClass ClassName.ACTIVE
-
-                # 如果需要的話，一同關閉子手風琴。
-                if settings.closeNested
-                    module.debug '關閉子手風琴因應設定', index, $this
-                    ts($this.find(Selector.ACCORDION)).accordion "close all"
-
-            # Close Others
-            #
-            # 閉合指定以外
-
-            closeOthers: (index) ->
-                module.debug '關閉指定手風琴分頁以外的其他分頁', index, $this
-
-                module.closeAll()
-                module.open index
-
-            # Close All
-            #
-            # 閉合所有
-
-            closeAll: ->
-                module.debug '關閉所有手風琴分頁', $this
-                module.close i for i in [0..$this.find(Selector.TITLE).length-1]
-
-            # Toggle
-            #
-            # 切換開合
-
-            toggle: (index) ->
-                module.debug '切換手風琴分頁', index, $this
-
-                $t = $title.eq index
-
-                if $t.hasClass ClassName.ACTIVE
-                    if not settings.collapsible
-                        module.debug '手風琴不允許閉合唯一分頁，略過切換步驟', index, $this
-                        return
-                    module.close index
-                else
-                    module.open index
-
-            # Debug
-            #
-            # 除錯訊息
-
-            debug: ->
-                return if not settings.debug or settings.silent
-
-                module.debug = Function.prototype.bind.call console.info, console, "#{Name}:"
-                module.debug.apply console, arguments
-
-            # Error
-            #
-            # 錯誤訊息
-
-            error: ->
-                return if settings.silent
-
-                module.error = Function.prototype.bind.call console.error, console, "#{Name}:"
-                module.error.apply console, arguments
-
-            # Invoke
-            #
-            # 模組呼叫點
-
-            invoke: (query, passedArguments, context) =>
-                if methodInvoked
-                    if instance is undefined
-                        module.initialize()
-                    module.invoke Data.QUERY
-                else
-                    if instance isnt undefined
-                        instance.invoke 'destroy'
-                    module.initialize()
-
-                switch Data.query
-
-                    # Open
-                    #
-                    # 開啟指定索引的手風琴內容。
-
-                    when 'open'
-                        module.open Data.QUERY_ARGUMENTS[0]
-
-                    # Close
-                    #
-                    # 關閉指定索引的手風琴內容。
-
-                    when 'close'
-                        module.close Data.QUERY_ARGUMENTS[0]
-
-                    # Close Others
-                    #
-                    # 關閉指定索引手風琴以外的其他索引。
-
-                    when 'close others'
-                        module.closeOthers Data.QUERY_ARGUMENTS[0]
-
-                    # Close All
-                    #
-                    # 關閉所有手風琴的頁籤。
-
-                    when 'close all'
-                        module.closeAll()
-
-                    # Toggle
-                    #
-                    # 切換指定索引的手風琴內容，如果是開啟的則關閉，相反之。
-
-                    when 'toggle'
-                        module.toggle Data.QUERY_ARGUMENTS[0]
-
-        settings = $this.data(SettingKey)
-
-        module.initialize()
-        module.invoke()
-
-    return if Data.VALUE isnt undefined then Data.VALUE else @
-
-
-
-
+        # ------------------------------------------------------------------------
+        # Tocas 核心安插
+        # ------------------------------------------------------------------------


Pull initial selected collection from hash parameter.

--- src/js/collection-editor.coffee
+++ src/js/collection-editor.coffee
@@ -325,6 +325,10 @@
       $(select).attr('id','collection_select')
       $(select).attr('style','width:100%')
       $('.container').append select
+
+      if parse_query_string()['collection']?
+        $("option[value=#{parse_query_string()['collection']}]").attr('selected','selected')
+
       $('#collection_select').chosen()
       $('#collection_select').bind 'change', (event) =>
         $('#collection_form').remove()


Hide dropdown menu of filters if clicking elsewhere.

--- app/assets/javascripts/postings.js.coffee
+++ app/assets/javascripts/postings.js.coffee
@@ -85,9 +85,14 @@
           console.log "fail"

-    # Drop-down filters
+    # Show drop-down filters
     $(".filter-buttons button").click (event) ->
       $(".filter-buttons ul").slideToggle(400)
+    # Close drop-down when clicking outside the menu
+    $(document).on "click", (event) ->
+      if !$(event.target).closest(".filter-buttons").length
+        if $(".filter-buttons ul").is(":visible")
+          $(".filter-buttons ul").slideToggle(400)

     # Show and hide postings according to user's filter
     $(".filter-buttons a").click (event) ->


Refactor MediaGrid module.

Refactor MediaGrid module.

--- app/assets/javascripts/undersky.js.coffee
+++ app/assets/javascripts/undersky.js.coffee
@@ -52,6 +52,9 @@

   class MediaGrid
     self = this
+    column_offset = 140
+    panel_offset = 75
+    scroll_amount = 200

     @columns: ->
       $('.media-grid.photos a')
@@ -99,7 +102,7 @@
         column = column.parent().prev().find('a').first()
       if column.size()
         self.toggle.call(column, e)
-        $w.scrollTop(column.offset().top - 65)
+        $w.scrollTop(column.offset().top - column_offset)

     @next: (e) ->
       columns = self.columns()
@@ -110,26 +113,26 @@
         column = columns.first()
       if column.size()
         self.toggle.call(column, e)
-        $w.scrollTop(column.offset().top - 65)
+        $w.scrollTop(column.offset().top - column_offset)

     @up: (e) ->
       e && e.preventDefault()
       panel = $('.modal.media-panel.show')
-      panel.scrollTop(panel.scrollTop() - 100)
+      panel.scrollTop(panel.scrollTop() - scroll_amount)

     @down: (e) ->
       e && e.preventDefault()
       panel = $('.modal.media-panel.show')
-      panel.scrollTop(panel.scrollTop() + 100)
+      panel.scrollTop(panel.scrollTop() + scroll_amount)

     @resize: ->
-      $('.modal.media-panel.show').css('height', $w.height() - 75)
-
-    do ->
+      $('.modal.media-panel.show').css('height', $w.height() - panel_offset)
+
+    do ->
+      $d.on 'click', '.media-grid.photos a', self.toggle
+      $d.on 'click', '.modal.media-panel .close', self.close
+      $d.keydown self.action
       $w.resize self.resize
-      $d.delegate '.media-grid.photos a', 'click', self.toggle
-      $d.delegate '.modal.media-panel .close', 'click', self.close
-      $d.keydown self.action

   class Likes
     self = this


EDSC-154: Fix issues with saving workspaces on page load

--- app/assets/javascripts/util/url.js.coffee
+++ app/assets/javascripts/util/url.js.coffee
@@ -212,10 +212,11 @@
     paramStr = '?' + paramStr if paramStr.length > 0

     path = path + paramStr
-    if workspaceName || (path != savedPath && path.length > config.urlLimit)
-      # assign a guid
-      shortenPath(path, state, workspaceName)
-      return
+    if workspaceName || path.length > config.urlLimit
+      if path != savedPath || (workspaceName && savedName != workspaceName)
+        # assign a guid
+        shortenPath(path, state, workspaceName)
+        return

     if cleanPath() && cleanPath() != path
       savedPath = path


Make minical not be read by NVDA after opening calendar feeds

Essentially this is a bug in the way that NVDA and/or Firefox
works. While the aria-hidden is true on the minical and should
always be respected, a bug (https://github.com/nvaccess/nvda/issues/6053)
causes changes to the parents aria-hidden to unhide the aria-hidden
stuff in its children :(

There are no tests associated with this commit because there is no
real way to test specific screenreader behavior.

closes CNVS-30258

Test Plan:
  - Using NVDA/Firefox go to the Calendar (/calendar)
  - Navigate to the calendar feed button (the minical shouldn't have been read.)
  - Click the button, then close the modal
  - Navigate back through the page the minical should still not be read.

Change-Id: I48f8698c41b81c8b3b8b128be8b7b19fb710454a
Reviewed-on: https://gerrit.instructure.com/84093
Tested-by: Jenkins
Reviewed-by: Steven Burnett <a0b450117c5d20a22d5c4d52bb8ae13adee003d8@instructure.com>
QA-Review: Jahnavi Yetukuri <ed2b2672ccc2e59e56c0615df7f01b579202e014@instructure.com>
Product-Review: Clay Diffrient <9dff2e5c98626d20d2848250c411e8009465bb87@instructure.com>

--- app/coffeescripts/calendar/sidebar.coffee
+++ app/coffeescripts/calendar/sidebar.coffee
@@ -77,10 +77,29 @@

       userSettings.set('checked_calendar_codes', @contexts)

+  setupCalendarFeedsWithSpecialAccessibilityConsiderationsForNVDA = ->
+    $calendarFeedModalContent = $('#calendar_feed_box')
+    $calendarFeedModalOpener = $('.dialog_opener[aria-controls="calendar_feed_box"]')
+    # We need to get the modal initialized early rather than wait for
+    # .dialog_opener to open it so we can attach the event to it the first
+    # time.  We extend so that we still get all the magic that .dialog_opener
+    # should give us.
+    $calendarFeedModalContent.dialog($.extend({
+      autoOpen: false,
+      modal: true
+    }, $calendarFeedModalOpener.data('dialogOpts')))
+
+    $calendarFeedModalContent.on('dialogclose', ->
+      forceScreenreaderToReparse($('#application')[0])
+    )
+
+
   return sidebar = (contexts, selectedContexts, dataSource) ->
     $holder   = $('#context-list-holder')
     $skipLink = $('.skip-to-calendar')
     $colorPickerBtn = $('.ContextList__MoreBtn')
+
+    setupCalendarFeedsWithSpecialAccessibilityConsiderationsForNVDA()

     $holder.html contextListTemplate(contexts: contexts)


fix: validate namespace and track after validation

--- app/components/namespace-component.coffee
+++ app/components/namespace-component.coffee
@@ -17,10 +17,13 @@

   actions:
     addNamespace: ->
-      analytics.feature(ENV.csb.feature.namespaceAdded, ENV.csb.module.security, ENV.csb.product.appknox)
       tRequestToAddNamespace = @get "tRequestToAddNamespace"
+      namespace = @get "namespace"
+      if !namespace
+        return @get("notify").error "Please enter any namespace", ENV.notifications
       data =
-        namespace: @get "namespace"
+        namespace: namespace
+      analytics.feature(ENV.csb.feature.namespaceAdded, ENV.csb.module.security, ENV.csb.product.appknox)
       that = @
       @set "isAddingNamespace", true
       @get("ajax").post ENV.endpoints.namespaceAdd, data: data


enhance: only set the values

enhance: only set the values

--- app/components/project-preferences.coffee
+++ app/components/project-preferences.coffee
@@ -41,10 +41,10 @@
   actions:

     selectDeviceType: ->
-      selectedDeviceType = @set "selectedDeviceType", parseInt @$('#project-device-preference').val()
+      @set "selectedDeviceType", parseInt @$('#project-device-preference').val()

     selectVersion: ->
-      selectVersion = @set "selectVersion", parseInt @$('#project-version-preference').val()
+      @set "selectVersion", parseInt @$('#project-version-preference').val()

     versionSelected: ->
       selectedDeviceType = @get "selectedDeviceType"


Track when people see the first green build invitations prompt.

Fixes #1889.

--- js/inner/build.coffee
+++ js/inner/build.coffee
@@ -373,6 +373,9 @@
       deferEvaluation: true
       read: =>
         if VM.project().github_users_not_following()
+          mixpanel.track "Saw invitations prompt",
+            first_green_build: true
+            project: VM.project().project_name()
           new CI.inner.Invitations VM.project().github_users_not_following(), (sending, users) =>
             node = $ ".first-green"
             node.addClass "animation-fadeout-collapse"
@@ -380,6 +383,7 @@
               node.addClass "success"
               for user in users
                 mixpanel.track "Sent invitation",
+                  first_green_build: true
                   project: VM.project().project_name()
                   login: user.login()
                   id: user.id()


adapt scrollbar range

adapt scrollbar range

--- js/msa/zoombar.coffee
+++ js/msa/zoombar.coffee
@@ -8,7 +8,7 @@
       zoomSlider.name = "points"

       min = 1 unless min?
-      max = 30 unless max?
+      max = Math.max 30,@msa.zoomer.level unless max?

       zoomSlider.min = min
       zoomSlider.max = max


Fix test for latest mocha

Fix test for latest mocha

--- test/conditions.coffee
+++ test/conditions.coffee
@@ -57,7 +57,7 @@
       conditions.if_exists(
         if_exists: __dirname
         () -> should.be.ok false
-        next
+        -> next()
       )
     it 'should skip if file does not exists', (next) ->
       conditions.if_exists(
@@ -69,7 +69,7 @@
       conditions.if_exists(
         if_exists: [__dirname, __filename]
         () -> should.be.ok false
-        next
+        -> next()
       )
     it 'should skip if at least one file exists', (next) ->
       conditions.if_exists(
@@ -96,13 +96,13 @@
       conditions.not_if_exists(
         not_if_exists: './oh_no'
         () -> should.be.ok false
-        next
+        -> next()
       )
     it 'should succeed if no file exists', (next) ->
       conditions.not_if_exists(
         not_if_exists: ['./oh_no', './eh_no']
         () -> should.be.ok false
-        next
+        -> next()
       )
     it 'should skip if at least one file exists', (next) ->
       conditions.not_if_exists(
@@ -116,7 +116,7 @@
       conditions.should_exist(
         should_exist: __filename
         () -> should.be.ok false
-        next
+        -> next()
       )
     it 'should failed if file does not exist', (next) ->
       conditions.should_exist(


Test test 1

Test test 1

--- test/js/totodoo.coffee
+++ test/js/totodoo.coffee
@@ -34,4 +34,4 @@
 				text = drvr.findElement(id: 'listName').getText()
 				expect(text).to.eventually.contain
 				'Todos'
-				done()
+			@timeout 4000, done()


Testing serializer pipelines after a skip.

--- test/serializer.coffee
+++ test/serializer.coffee
@@ -154,6 +154,13 @@
       @deepEqual toArray(buffer), [ 0x41, 0x42, 0x43, 0x00 ]
       done 2

+  'test: write a zero terminated UTF-8 string after skipping': (done) ->
+    serializer = new Serializer()
+    serializer.buffer "x16{0}, b8z|utf8()", "ABC", (buffer) =>
+      @equal serializer.getBytesWritten(), 6
+      @deepEqual toArray(buffer), [ 0x00, 0x00, 0x41, 0x42, 0x43, 0x00 ]
+      done 2
+
   'test: write a zero terminated 8 byte padded UTF-8 string': (done) ->
     serializer = new Serializer()
     serializer.buffer "b8[8]{1}z|utf8()", "ABC", (buffer) =>


update namespaces

update namespaces

--- src/Main.coffee
+++ src/Main.coffee
@@ -2,4 +2,5 @@
     TextureCache: {}
     MapCache: {}
     Loaders: {}
-    Drawable: {}
+    Drawables: {}
+    Renders: {}


Updating to better response handling

Updating to better response handling

--- scripts/crashlytics-notifications.coffee
+++ scripts/crashlytics-notifications.coffee
@@ -27,19 +27,17 @@
     @failing ||= []
     query = querystring.parse(url.parse(req.url).query)

-    res.end('')
-
     try
       data = req.body

       if data.event == 'verification'
         console.log "verified with Crashlytics"
-        res.writeHead 204, { 'Content-Length': 0}
-        res.end()

       if data.event == 'issue_impact_change'
         robot.messageRoom '#publishmobileautomati', 'Stuffs broke!'
+        res.writeHead 204, { 'Content-Length': 0}

+      res.end()
     catch error
       console.log "jenkins-notify error: #{error}. Data: #{req.body}"
       console.log error.stack


KDView: code style

KDView: code style

--- core/KDView.coffee
+++ core/KDView.coffee
@@ -379,7 +379,7 @@
 # ADD/DESTROY VIEW INSTANCES
 # #

-  destroy:->
+  destroy: ->
     # instance destroys own subviews
     @destroySubViews()  if @getSubViews().length > 0

@@ -398,7 +398,7 @@
     # and delete instance from KD.instances registry
     super

-  destroySubViews:()->
+  destroySubViews: ->
     # (subView.destroy() for subView in @getSubViews())

     for subView in @getSubViews().slice()


Reading state twice is not needed.

Reading state twice is not needed.


fix - minimum limit so last column still recognized


Fix #3193: show completed levels again in campaign selector

--- server/campaigns/campaign_handler.coffee
+++ server/campaigns/campaign_handler.coffee
@@ -64,7 +64,7 @@
         obj = @formatEntity(req, doc)
         obj.adjacentCampaigns = _.mapValues(obj.adjacentCampaigns, (a) -> _.pick(a, ['showIfUnlocked', 'color', 'name', 'description' ]))
         for original, level of obj.levels
-          obj.levels[original] = _.pick level, ['locked', 'disabled', 'original', 'rewards']
+          obj.levels[original] = _.pick level, ['locked', 'disabled', 'original', 'rewards', 'slug']
         obj
       documents = (formatCampaign(doc) for doc in documents)
       @sendSuccess(res, documents)


app: Add controls layer.

app: Add controls layer.

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

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

   # Layers
   layer: {}


fix bouygues telecom konnector

fix bouygues telecom konnector

--- server/konnectors/bouyguestelecom.coffee
+++ server/konnectors/bouyguestelecom.coffee
@@ -165,9 +165,9 @@
             # when the link is clicked.
             dataArray = urlData.split ','
             params =
-                id: dataArray[4].replace /[\/']/g, ''
-                date: dataArray[5].replace /[\/']/g, ''
-                type: dataArray[6].replace /[\/']/g, ''
+                id: dataArray[4].replace /[\/'\s]/g, ''
+                date: dataArray[5].replace /[\/'\s]/g, ''
+                type: dataArray[6].replace /[\/'\s]/g, ''
                 no_reference:dataArray[7].replace /[\/)']/g, ''
             url = "#{baseDlUrl}?#{qs.stringify params}"


Better format document json output.

Better format document json output.

--- app/assets/javascripts/app/filters.js.coffee
+++ app/assets/javascripts/app/filters.js.coffee
@@ -16,7 +16,9 @@
       cls = "number"
       if /^"/.test(match)
         if /:$/.test(match)
+          # Remove quotes from a key
           match = match.replace(/^"/, "").replace(/":$/, ":")
+
           cls = "key"
         else
           cls = "string"


servers: remove odesk handler

servers: remove odesk handler

--- servers/lib/server/odesk_callback.coffee
+++ servers/lib/server/odesk_callback.coffee
@@ -1,73 +0,0 @@
-provider             = 'odesk'
-http                 = require 'https'
-koding               = require './bongo'
-{ parseString }      = require 'xml2js'
-{ OAuth }            = require 'oauth'
-
-{
-  redirectOauth
-  saveOauthToSession
-}                    = require './helpers'
-
-{
-  key
-  secret
-  request_url
-  access_url
-  version
-  redirect_uri
-  signature
-}                    = KONFIG[provider]
-
-module.exports = (req, res) ->
-  { query, cookies }              = req
-  { oauth_token, oauth_verifier } = query
-  { clientId }                    = cookies
-  { JSession }                    = koding.models
-
-  JSession.one { clientId }, (err, session) ->
-    if err or not session
-      redirectOauth err, req, res, { provider }
-      return
-
-    { foreignAuth }        = session
-    { username }           = session.data
-    { requestTokenSecret } = foreignAuth[provider]
-
-    customHeaders =
-      'Accept'     : 'application/json',
-      'Connection' : 'close',
-      'User-Agent' : 'Koding'
-
-    client = new OAuth request_url, access_url, key, secret, version, redirect_uri,
-      signature, 0 , customHeaders
-
-    # coffeelint: disable=indentation
-    client.getOAuthAccessToken oauth_token, requestTokenSecret, oauth_verifier, \
-      (err, accessToken, accessTokenSecret) ->
-        if err
-          redirectOauth err, req, res, { provider }
-          return
-
-        client.get 'https://www.upwork.com/api/auth/v1/info',
-          accessToken, accessTokenSecret, (err, data) ->
-            try
-              response = JSON.parse data
-            catch e
-              redirectOauth 'Error parsing user info', req, res, { provider }
-              return
-
-            odesk                   = session.foreignAuth.odesk
-            odesk.token             = accessToken
-            odesk.accessTokenSecret = accessTokenSecret
-            odesk.foreignId         = response.auth_user.uid
-            odesk.profileUrl        = response.info.profile_url
-            odesk.profile           = response
-
-            saveOauthToSession odesk, clientId, provider, (err) ->
-              if err
-                redirectOauth err, req, res, { provider }
-                return
-
-              redirectOauth null, req, res, { provider }
-    # coffeelint: enable=indentation


Reorder js plugins position in application.coffee file

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -1,50 +1,68 @@
 #= require vendor/modernizr
+
 #= require jquery
 #= require jquery_ujs
 #= require jquery-ui/autocomplete
+
 #= require foundation/foundation
 #= require foundation
 #= require plugins/foundation_init
+
+#= require globals/_functions
+#= require base/flash
+
 #= require turbolinks
 #= require nprogress
 #= require nprogress-turbolinks
+#= require plugins/nprogress
+
 #= require rails.validations
 #= require rails.validations.simple_form
 #= require rails.validations.simple_form.fix
+
 #= require i18n
 #= require i18n/translations
-#= require js.cookie
-#= require form_backup
-#= require modules/scroll_infinite
+
+#= require magnific-popup
+#= require modules/magnific_popup
+
 #= require vex.combined.min.js
 #= require plugins/vex_config
+
+#= require js.cookie
+#= require plugins/cookie_ie
+#= require plugins/cookie_adult_validation
+#= require plugins/cookie_cnil
+
+#= require form_backup
+#= require jquery.autosize
+#= require jquery.autosize.initializer
+
 #= require plugins/override_rails_confirm
 #= require classes/social_share_class
+
 #= require mapbox
 #= require plugins/mapbox
-#= require magnific-popup
+
 #= require fotorama
+#= require plugins/fotorama
+
 #= require moment
 #= require fullcalendar
 #= require fullcalendar/lang/fr
-#= require jquery.autosize
-#= require jquery.autosize.initializer
-#= require globals/_functions
+#= require plugins/fullcalendar
+
 #= require modules/responsive_menu
 #= require modules/autocomplete_search
-#= require modules/magnific_popup
-#= require plugins/nprogress
-#= require plugins/cookie_ie
-#= require plugins/cookie_adult_validation
-#= require plugins/fotorama
-#= require plugins/fullcalendar
-#= require base/flash
+
 #= require outdatedbrowser/outdatedBrowser
 #= require outdated_browser
+
 #= require mediaelement_rails
 #= require mediaelement/mejs-feature-logo.min
 #= require plugins/mediaelement
-#= require plugins/cookie_cnil
+
+#= require modules/scroll_infinite

 $(document).on 'ready page:load page:restore', ->
   # Save form inputs in LocalStorage


Shorten JS line length

Shorten JS line length

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -409,20 +409,20 @@
         # Are there add limits
         add_limit_attr = question.find(".list-add").attr("data-add-limit")

-        list_excl_egs = question.find(".list-add > li").not(".js-add-example").size()
-        list_defaults = question.find(".list-add > li.js-add-example.js-add-default").size()
-        list_size = list_excl_egs + list_defaults
+        li_excl_eg = question.find(".list-add > li").not(".js-add-example").size()
+        li_default = question.find(".list-add > li.js-add-example.js-add-default").size()
+        li_size = li_excl_eg + li_default

         if ((typeof(add_limit_attr) != typeof(undefined)) && add_limit_attr != false)

-          if list_size >= add_limit_attr
+          if li_size >= add_limit_attr
             can_add = false

-          if list_size + 1 >= add_limit_attr
+          if li_size + 1 >= add_limit_attr
             question.find(".js-button-add").addClass("visuallyhidden")

         if can_add
-          add_eg = add_eg.replace(/(form\[(\w+|_)\]\[)(\d+)\]/g, "$1#{list_size+1}]")
+          add_eg = add_eg.replace(/(form\[(\w+|_)\]\[)(\d+)\]/g, "$1#{li_size+1}]")
           question.find(".list-add").append("<li>#{add_eg}</li>")

           clear_example = question.find(".list-add").attr("data-need-to-clear-example")
@@ -438,7 +438,9 @@
   $(document).on "click", ".question-group .list-add .js-remove-link", (e) ->
     e.preventDefault()
     if !$(this).hasClass("read-only")
-      $(this).closest(".question-group").find(".js-button-add").removeClass("visuallyhidden")
+      $(this).closest(".question-group")
+             .find(".js-button-add")
+             .removeClass("visuallyhidden")
       $(this).closest("li").remove()

   # Disable copy/pasting in confirmation fields


closing tour dismisses it permenantly

closing tour dismisses it permenantly

--- app/assets/javascripts/bootstrap-tour.coffee
+++ app/assets/javascripts/bootstrap-tour.coffee
@@ -41,7 +41,6 @@
       cookieDomain: false                  # Will this cookie be attached to a domain, ie. '.mydomain.com'
       postRideCallback: $.noop             # A method to call once the tour closes
       postStepCallback: $.noop             # A method to call after each step
-      nextOnClose: false                   # If cookies are enabled, increment the current step on close
       debug: false
     # Merge default settings with options.
     settings = $.extend settings, options
@@ -108,7 +107,7 @@
       $('a.tour-tip-close').live 'click', ->
         current_step = $(@).data('touridx')
         $(settings.tipContent).first().find("li:nth-child(#{current_step})").data('target').popover('hide')
-        setCookieStep(current_step + 1) if settings.nextOnClose
+        setCookieStep($tips.length + 1)

       # handle the next and done buttons
       $('a.tour-tip-next').live 'click', ->


ensures that markers still show even if sites exist without a CMS page

--- app/assets/javascripts/modules/map.js.coffee
+++ app/assets/javascripts/modules/map.js.coffee
@@ -42,9 +42,10 @@

     for index, site of sites
       marker = L.marker([site.lat, site.lon])
-      marker.bindPopup("""
-        #{site.name}
-      """)
+      if site.pages.length != 0
+        marker.bindPopup("""
+          #{site.name}
+        """)
       markersGroup.addLayer(marker)
     markersGroup.addTo(window.map)
     window.map.fitBounds(markersGroup.getBounds())


changed text

changed text

--- app/assets/pg/intro/intro.constant.js.coffee
+++ app/assets/pg/intro/intro.constant.js.coffee
@@ -3,33 +3,72 @@
     slides: [
       {
         type: 'text',
-        body: 'Welcome to jQuest Season 1:
Diversity in Parliament'
-      }
-      {
-        type: 'text'
-        body: 'Together with 123 students from across Europe, you’ll do an exclusive data-driven investigation.'
+        body: 'Welcome to this season of jQuest:
Diversity in Parliament'
       }
       {
         type: 'question'
         body: 'Do you think this person is member of Parliament?'
-        image: 'http://placehold.it/200x300/6C99C0/fff'
+        image: '//static.jquestapp.com/images/LyndaGiddens.jpg'
       }
       {
         type: 'question'
         body: 'Do you think this person is member of Parliament?'
-        image: 'http://placehold.it/200x300/e34d4d/fff'
+        image: '//static.jquestapp.com/images/tomorrowscheese.jpg'
       }
       {
         type: 'question'
         body: 'Do you think this person is member of Parliament?'
-        image: 'http://placehold.it/200x300/6C99C0/fff'
+        image: '//static.jquestapp.com/images/accoyer.jpg'
+      }
+      {
+        type: 'question'
+        body: 'Do you think this person is member of Parliament?'
+        image: '//static.jquestapp.com/images/Arndt_Michael_Dr_Bild_w500.jpg'
+      }
+      {
+        type: 'question'
+        body: 'Do you think this person is member of Parliament?'
+        image: '//static.jquestapp.com/images/pumpkincat210.jpg'
+      }
+      {
+        type: 'question'
+        body: 'Do you think this person is member of Parliament?'
+        image: '//static.jquestapp.com/images/tabacci.jpg'
       }
       {
         type: 'text'
-        body: 'It was easy, right?
Because our parliaments, in Europe, are not as diverse as our society.'
+        body: 'It was easy, right?
Because our parliaments are not as diverse as our societies.'
       }
       {
         type: 'text'
         body: 'How much so? No one knows.
And that\'s what you\'re going to find out.'
       }
-    ]
+      {
+        type: 'text'
+        body: 'Together with hundreds of journalism students from across Europe,
you\'ll be part of an exclusive investigation.'
+      }
+      {
+        type: 'text'
+        body: 'You\'ll measure how representative our elected officials actually are.
And learn datajournalism in the process.'
+      }
+      {
+        type: 'text'
+        body: 'We\'ll give you politicians to research.
It\'ll be easy at first but get harder as you progress.'
+      }
+      {
+        type: 'text'
+        body: 'If you\'re ever stuck, we\'ll be available for you on a dedicated Slack channel,
where you can also chat and connect with fellow students from across Europe.'
+      }
+      {
+        type: 'text'
+        body: 'Who\'s we?
We\'re Journalism++, a small agency for datajournalism. We were awarded the Data Journalism Award in 2014 and the European Press Prize in 2015 and we work with the largest news organizations on this continent.'
+      }
+      {
+        type: 'text'
+        body: 'No investigation that large has ever been tried before.'
+      }
+      {
+        type: 'text'
+        body: 'Together, let\'s make journalism history.'
+      }
+]


Dependency //= require statements were broken

--- app/assets/utensils/carousel/carousel.coffee
+++ app/assets/utensils/carousel/carousel.coffee
@@ -1,6 +1,6 @@
-#= utensils/utensils
-#= utensils/bindable
-#= utensils/detect
+#= require utensils/utensils
+#= require utensils/bindable
+#= require utensils/detect

 class utensils.Carousel
   constructor:(el) ->


LIG-1543 Set optional headers

LIG-1543 Set optional headers

--- src/auth.coffee
+++ src/auth.coffee
@@ -77,7 +77,7 @@

 # Return an authed SDK object for use with a single authorized user
-TeamSnap::auth = (token, optionalRequestHeaders) ->
+TeamSnap::auth = (token, options) ->
   if typeof token is 'function'
     callback = token
     token = null
@@ -104,11 +104,10 @@

   @request.hook (xhr) ->
     xhr.setRequestHeader 'Authorization', 'Bearer ' + token
-
-  if options and options.length
-    for option in optionalRequestHeaders
-      @request.hook (xhr) ->
-        xhr.setRequestHeader option.name, option.value
+
+    if options.optionalRequestHeaders and options.optionalRequestHeaders.length
+      for option in options.optionalRequestHeaders
+        xhr.setRequestHeader option.headerName, option.headerValue
   this


Change to do explicit casting

Change to do explicit casting

--- src/cell.coffee
+++ src/cell.coffee
@@ -64,6 +64,11 @@
         switch @_.type
           when "s"
             val = @workbook._.sst.get(@_.value)
+          when "str"
+            val = String(val)
+          else
+            if Utils.isNumber(val)
+              val = Number(val)
         if @_.styleMixin.style.numberFormat.isDate()
           val = Utils.offsetToDate(val)
         val


Reducing duplication for all commands

Reducing duplication for all commands

Most of the scripts were just going straight to `exec`. This just
brings most of that down to a single helper method to simplify everything a bit.

Also, removing `spawn` since it was never used.

--- src/chef.coffee
+++ src/chef.coffee
@@ -16,112 +16,69 @@
 #   hubot knife client show <name> - chef: Display client configurations et al
 #   hubot node list - chef: Lists all nodes on chef server
 #   hubot node status - chef: Get knife status of all nodes
+#   hubot node show <node> - chef: Get knife status of all nodes
 #   hubot uptime <server> - chef: Prints uptime per node
 #
 # Author:
 #   jjasghar
+#   mattdbridges
+#

+exec  = require('child_process').exec
+
+execCommand = (msg, cmd) ->
+  exec cmd, (error, stdout, stderr) ->
+    msg.send error
+    msg.send stdout
+    msg.send stderr

 module.exports = (robot) ->
-  robot.respond /node list$/i, (msg) ->
-    spawn = require('child_process').spawn

-    server = msg.match[1]
-    command = "knife node list"
+  robot.respond /node (list|status)$/i, (msg) ->
+    subcmd = msg.match[1]
+    command = "knife node #{subcmd}"

     msg.send "Listing nodes..."
+    execCommand msg, command

-    @exec = require('child_process').exec
+  robot.respond /node show (.*)$/i, (msg) ->
+    nodeName = msg.match[1]
+    command = "knife node show #{nodeName}"

-    @exec command, (error, stdout, stderr) ->
-      msg.send error
-      msg.send stdout
-      msg.send stderr
-
-  robot.respond /node status$/i, (msg) ->
-    spawn = require('child_process').spawn
-
-    server = msg.match[1]
-    command = "knife status"
-
-    msg.send "Getting statuses of all nodes..."
-
-    @exec = require('child_process').exec
-
-    @exec command, (error, stdout, stderr) ->
-      msg.send error
-      msg.send stdout
-      msg.send stderr
+    msg.send "Showing node for #{nodeName}..."
+    execCommand msg, command

   robot.respond /environment list$/i, (msg) ->
-    spawn = require('child_process').spawn
-
-    server = msg.match[1]
     command = "knife environment list"

     msg.send "Listing environments..."
-
-    @exec = require('child_process').exec
-
-    @exec command, (error, stdout, stderr) ->
-      msg.send error
-      msg.send stdout
-      msg.send stderr
-
+    execCommand msg, command

   robot.respond /knife (node|role|client) show (.*)$/i, (msg) ->
-    spawn = require('child_process').spawn
-
     subcmd = msg.match[1]
     name = msg.match[2]
     command = "knife #{subcmd} show #{name}"

     msg.send "Running: #{command}"
-
-    @exec = require('child_process').exec
-
-    @exec command, (error, stdout, stderr) ->
-      msg.send error
-      msg.send stdout
-      msg.send stderr
+    execCommand msg, command

   robot.respond /uptime (.*)$/i, (msg) ->
-    spawn = require('child_process').spawn
     server = msg.match[1]
-
     command = "knife ssh name:#{server} 'uptime'"

     msg.send "Checking #{server} for uptime..."
-
-    @exec = require('child_process').exec
-
-    @exec command, (error, stdout, stderr) ->
-      msg.send error
-      msg.send stdout
-      msg.send stderr
+    execCommand msg, command

   robot.respond /converge (.*)$/i, (msg) ->
     server = msg.match[1]
-
-    @exec = require('child_process').exec
-
     command = "knife ssh --attribute ipaddress --no-color name:#{server} 'sudo chef-client'"

     msg.send "Converging #{server}."
-
-    @exec command, (error, stdout, stderr) ->
-      msg.send error
-      msg.send stdout
-      msg.send stderr
+    execCommand msg, command

   robot.respond /converge-environment (.*)$/i, (msg) ->
-    # environment = msg.match[1]
-    # @exec = require('child_process').exec
-    # command = "knife ssh --no-color --attribute ipaddress chef_environment:#{environment} 'sudo chef-client'"
+    environment = msg.match[1]
+    command = "knife ssh --no-color --attribute ipaddress chef_environment:#{environment} 'sudo chef-client'"

     msg.send "Configuring #{environment}....nope just kidding man, you have balls..."
-
-    @exec command, (error, stdout, stderr) ->
-      msg.send error
-      msg.send stdout
-      msg.send stderr
+    # execCommand msg, command


move lifetime access to  stripe checkout (#6435)

--- app/views/core/SubscribeModal.coco.coffee
+++ app/views/core/SubscribeModal.coco.coffee
@@ -229,6 +229,12 @@
     if me.get('anonymous')
       application.tracker?.trackEvent 'Started Signup from buy lifetime', {service: 'stripe'}
       return @openModalView new CreateAccountModal({startOnPath: 'individual', subModalContinue: 'lifetime'})
+    @startStripeSubscription(@lifetimeProduct)
+      .catch (err) =>
+        console.error 'stripe lifetime handle failed', err
+        @oldStripeLifetimeHandle()
+
+  oldStripeLifetimeHandle: ->
     startEvent = 'Start Lifetime Purchase'
     finishEvent = 'Finish Lifetime Purchase'
     descriptionTranslationKey = 'subscribe.lifetime'


Enable the fair setup and pass in the fair to the layered search view

--- apps/artwork/client/below-the-fold.coffee
+++ apps/artwork/client/below-the-fold.coffee
@@ -12,15 +12,16 @@

     $.when.apply(null, @artwork.fetchRelatedCollections()).then =>
       # Find the first related collection that has any results
-      availableFeature = _.find @artwork.relatedCollections, (xs) -> xs.length
-      if availableFeature
-        @["setup#{availableFeature.kind}"]()
+      @availableFeature = _.find @artwork.relatedCollections, (xs) -> xs.length
+      if @availableFeature
+        @["setup#{@availableFeature.kind}"]()
       else
         # Always fallback to layered search if there are no features available
         @setupLayeredSearch()

-  setupLayeredSearch: ->
-    new LayeredSearchView el: @$el, artwork: @artwork
+  setupLayeredSearch: (options = {}) ->
+    { @fair } = options
+    new LayeredSearchView el: @$el, artwork: @artwork, fair: @fair
     @done()

   setupSales: ->
@@ -28,7 +29,8 @@
     @done()

   setupFairs: ->
-    throw 'I don\'t know how to do this yet!'
+    fair = @availableFeature.first()
+    @setupLayeredSearch fair: fair

   setupShows: ->
     throw 'I don\'t know how to do this yet!'


convert ipv4 and ipv6 ip addresses to ipv6.v6.Address

--- anon.coffee
+++ anon.coffee
@@ -15,6 +15,9 @@
     i = new ipv6.v6.Address(ip)
   else
     i = new ipv6.v4.Address(ip)
+    subnetMask = 96 + i.subnetMask
+    ip = '::ffff:' + i.toV6Group() + "/" + subnetMask
+    i = new ipv6.v6.Address(ip)

 ipToInt = (ip) ->
   i = address(ip)
@@ -35,12 +38,6 @@
   else
     a = address(ip)
     b = address(block)
-    # if we are comparing a v6 address to an v4 cidr range we need to
-    # convert the v4 cidr to a v6 cidr
-    if not a.v4 and b.v4
-      subnetMask = 96 + b.subnetMask
-      block = '::' + b.toV6Group() + "/" + subnetMask
-      b = new ipv6.v6.Address(block)
     a.isInSubnet(b)

 isIpInAnyRange = (ip, blocks) ->
@@ -61,7 +58,6 @@
   require path

 getStatusLength = (edit, name, template) ->
-  # returns length of the tweet based on shortened url
   # https://support.twitter.com/articles/78124-posting-links-in-a-tweet
   fakeUrl = 'http://t.co/BzHLWr31Ce'
   status = Mustache.render template, name: name, url: fakeUrl, page: edit.page
@@ -118,7 +114,8 @@
 if require.main == module
   main()

-# export these for testing
+# for testing
+exports.address = address
 exports.compareIps = compareIps
 exports.isIpInRange = isIpInRange
 exports.isIpInAnyRange = isIpInAnyRange


set document.title to docname

set document.title to docname

--- assets/js/index.coffee
+++ assets/js/index.coffee
@@ -35,6 +35,8 @@
 $settingsDiv = $('#settings')
 $modeDiv = $('#mode')

+docname = window.location.pathname.split('/')[1]
+
 changeStyle = (theme) ->
   $('body').attr('id', theme)

@@ -129,6 +131,7 @@

   # render when ready
   $(document).ready ->
+    document.title = "#{docname} - Vimflowy" unless docname is ''
     View.renderSession session

   key_handler = new KeyHandler session, key_bindings
@@ -284,7 +287,6 @@
     ), 5000

 else if localStorage?
-  docname = window.location.pathname.split('/')[1]
   datastore = new DataStore.LocalStorageLazy docname
   doc = new Document datastore