fix for firefox & safari support on chart tooltip mouseleave events

--- src/services/chart-formatter/chart-formatter.svc.coffee
+++ src/services/chart-formatter/chart-formatter.svc.coffee
@@ -51,7 +51,7 @@
     @customTooltip = (tooltip) ->
       # Find the tooltip
       tooltipEl = angular.element('#chartjs-tooltip')
-      if (!tooltipEl[0])
+      if (!tooltipEl[0])
         angular.element('body').append('

')
         tooltipEl = angular.element('#chartjs-tooltip')

@@ -83,17 +83,17 @@

       # Avoid mouse glitches
       canvasEl.bind 'mouseleave', (event) ->
-        tooltipEl.remove() unless event.toElement.id == 'chartjs-tooltip'
+        tooltipEl.remove() unless event.relatedTarget.id == 'chartjs-tooltip'

       tooltipEl.bind 'mouseleave', (event) ->
-        tooltipEl.remove() unless event.toElement.tagName == 'CANVAS'
+        tooltipEl.remove() unless event.relatedTarget.tagName == 'CANVAS'

       # Display, position, and set styles for font
       tooltipEl.css({
         'background-color': '#17262d'
         color: 'white'
-
+
         opacity: 1
         transition: 'opacity 0.5s, top 0.5s, left 0.5s'

@@ -116,7 +116,7 @@
         titleFontFamily: "Lato, 'Helvetica Neue', Helvetica, Arial, sans-serif"
         bodyFontFamily: "Lato, 'Helvetica Neue', Helvetica, Arial, sans-serif"
         footerFontFamily: "Lato, 'Helvetica Neue', Helvetica, Arial, sans-serif"
-
+
         enabled: false
         custom: _self.customTooltip
       }
@@ -235,7 +235,7 @@
     # positivesOnly: if true (default), negative bars will be turned into their opposite
     @barChart = (inputData, opts={}, positivesOnly=true) ->
       _self.setTooltipsTextLayout(opts)
-
+
       index = 0
       colors = []
       for value in inputData.values
@@ -265,7 +265,7 @@
     # positivesOnly: if true (default), negative bars will be turned into their opposite
     @combinedBarChart = (inputData, opts={}, positivesOnly=true) ->
       _self.setTooltipsTextLayout(opts, true)
-
+
       index = 0
       result = {
         type: 'bar',


No need to create a cursor, just used the one already bound to the document

--- lib/written/app/assets/javascripts/written/core/content.coffee
+++ lib/written/app/assets/javascripts/written/core/content.coffee
@@ -113,7 +113,6 @@

     if document = @history.previous()
       @history.current = document
-      cursor = new Written.Cursor(@element(), window.getSelection())
       @update(@history.current)

   redo: (e) =>


Minor fixes in user app

Minor fixes in user app

--- user.coffee
+++ user.coffee
@@ -69,7 +69,6 @@
   @get '/page/:page', user_list

   format_user = ({ _id, pubkey, content, sig }, is_html) ->
-    console.log { _id, pubkey }
     user =
       username: _id
       pubkey: pubkey.toString 'hex'
@@ -87,10 +86,10 @@
     rating = new Rating
       _user: req.user._id, _rater: req.auth._id
       rating: req.body.rating, content: req.body.content
-    rating.save iferr next, -> req.redirect 201, "#{req.user._id}/rating/#{rating._id}"
+    rating.save iferr next, -> req.redirect 303, "#{req.user._id}/rating/#{rating._id}"

   # Rating
-  @get '/rating/:rating_id', (req, res, next) ->
+  @get '/:user/rating/:rating_id', (req, res, next) ->
     Rating.find(req.params.rating_id).populate('_user _rater').exec iferr next, (rating) ->
       return res.send 404 unless rating? and rating.user is req.user._id
       res.format


EnvironmentMachineItem: Update Run init script childs

--- client/Environments/views/scene/environmentmachineitem.coffee
+++ client/Environments/views/scene/environmentmachineitem.coffee
@@ -169,10 +169,10 @@

     if running
       items['Run build script'].children =
-        'In Terminal'   :
-          callback      : @lazyBound "runBuildScript", inTerminal = yes
-        'In Background' :
-          callback      : @lazyBound "runBuildScript", inTerminal = no
+        'Inside a terminal' :
+          callback        : @lazyBound "runBuildScript", inTerminal = yes
+        'As background process' :
+          callback        : @lazyBound "runBuildScript", inTerminal = no

     return items


Don't use jQuery's hasClass method

Don't use jQuery's hasClass method

--- client/account/lib/test/accountlinkedaccountslist.test.coffee
+++ client/account/lib/test/accountlinkedaccountslist.test.coffee
@@ -23,6 +23,6 @@
     it 'should use custom css class', ->

       listView  = new AccountLinkedAccountsList
-      hasClass  = listView.getDomElement().hasClass 'AppModal--account-switchList'
+      hasClass  = listView.hasClass 'AppModal--account-switchList'

       expect(hasClass).toBeTruthy()


Client: Activity: Feed: Passed popularChannels as a prop to PublicFeedPane

--- client/activity/lib/components/channelthreadpane/index.coffee
+++ client/activity/lib/components/channelthreadpane/index.coffee
@@ -30,9 +30,9 @@
     super props

     @state =
-      showDropTarget        : no
-      channelThread         : immutable.Map()
-      channelParticipants   : immutable.List()
+      showDropTarget      : no
+      channelThread       : immutable.Map()
+      channelParticipants : immutable.List()

   channel: (args...) -> @state.channelThread.getIn ['channel'].concat args
@@ -80,7 +80,8 @@

         <PublicFeedPane
           ref='pane'
-          thread={thread} />
+          thread={thread}
+          popularChannels={@state.popularChannels}/>

     else


Fetch AppStorage before refreshApps

Fetch AppStorage before refreshApps

--- client/app/Applications/StartTab.kdapplication/AppView.coffee
+++ client/app/Applications/StartTab.kdapplication/AppView.coffee
@@ -12,7 +12,8 @@
     mainView       = @getSingleton('mainView')
     appsController = @getSingleton("kodingAppsController")
     appsController.on "AppsRefreshed", (apps)=>
-      @decorateApps apps
+      @getSingleton("kodingAppsController").appStorage.fetchStorage (storage)=>
+        @decorateApps apps

     # mainView.sidebar.finderResizeHandle.on "DragInAction", =>
     #   log "DragInAction", mainView.contentPanel.getWidth()


J.util.isPlainObject bugfix

J.util.isPlainObject bugfix

--- util.coffee
+++ util.coffee
@@ -219,7 +219,6 @@
         return false unless obj?
         return false if typeof obj isnt 'object'
         return false if obj is obj.window
-        return false if obj.nodeType
         return false if obj.constructor and not ({}).hasOwnProperty.call(obj.constructor.prototype, 'isPrototypeOf')
         return false if obj._isReactElement
         true


About: photos fixed

About: photos fixed

--- client/landing/site.landing/coffee/about/aboutlistitem.coffee
+++ client/landing/site.landing/coffee/about/aboutlistitem.coffee
@@ -12,12 +12,19 @@

     super options, data

-    {username} = @getData()
+    {username, prefix} = @getData()
+    dpr  = window.devicePixelRatio ? 1
+    size = 160 * dpr
+    hash = KD.utils.md5 "#{prefix or username}@koding.com"
+
+    defaultAvatarUri = "https://koding-cdn.s3.amazonaws.com/square-avatars/default.avatar.#{size}.png"
+    src =  "//gravatar.com/avatar/#{hash}?size=#{size}&d=#{defaultAvatarUri}&r=g"
+
     @avatar    = new KDCustomHTMLView
-      tagName  : 'img'
-      cssClass : 'avatarview'
-      origin   : username
-      size     : width : 160
+      tagName    : 'img'
+      cssClass   : 'avatarview'
+      attributes : {src}
+      size       : width : 160

     @link      = new CustomLinkView
       cssClass : 'profile'


Disable mobile input mode during tests

Disable mobile input mode during tests

--- test/src/test_helpers.coffee
+++ test/src/test_helpers.coffee
@@ -1,3 +1,5 @@
+Trix.config.useMobileInputMode = -> false
+
 keyCodes =
   left: 37
   right: 39


skips array copy-paste fix

skips array copy-paste fix

--- gulp-nav.coffee
+++ gulp-nav.coffee
@@ -39,7 +39,7 @@
     targets = [ targets ] unless Array.isArray targets
     titles  = [ titles ]  unless Array.isArray titles
     orders  = [ orders ]  unless Array.isArray orders
-    skips   = [ skips ]   unless Array.isArray orders
+    skips   = [ skips ]   unless Array.isArray skips

     # scaffolding for crawling the directory structure
     files = []


replace es6 coffee with regular coffee

replace es6 coffee with regular coffee

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -1,5 +1,5 @@
 gulp       = require 'gulp'
-coffee     = require 'gulp-coffee-es6'
+coffee     = require 'gulp-coffee'

 gulp.task 'compile', ->
   gulp.src './src/**/*.coffee'


fix(#36): do not remove IE conditional comments

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -1113,6 +1113,7 @@
 gulp.task 'spa', ['scripts', 'styles'].concat(if isProd then 'templateCache' else 'views'), ->
 	options =
 		minifyHtml:
+			conditionals: true
 			empty: true
 			quotes: true
 		template: JSON.parse JSON.stringify templateOptions


Remove plumber

Remove plumber

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -7,7 +7,6 @@
 rename = require("gulp-rename")
 template = require("gulp-template")
 gutil = require("gulp-util")
-plumber = require("gulp-plumber")
 {exec} = require("child_process")

 command = (cmd, cb) ->
@@ -57,7 +56,6 @@
 		debug: true

 	return gulp.src(config.entry)
-		.pipe(plumber())
 		.pipe(gulpWebpack(config))
 		.pipe(gulp.dest("build/"))

@@ -68,7 +66,6 @@
 		output: {filename: "tests.js"}

 	return gulp.src(config.entry)
-		.pipe(plumber())
 		.pipe(gulpWebpack(config))
 		.pipe(gulp.dest("test/phantomjs/"))

@@ -81,8 +78,7 @@
 		}}))

 gulp.task "watch", ->
-	gulp.run "test", ->
-		gulp.watch(["./*.coffee", "framer/**", "test/tests/**", "!Version.coffee"], ["test"])
+	gulp.watch(["./*.coffee", "framer/**", "test/tests/**", "!Version.coffee"], ["test"])

 gulp.task "watcher", ["version"], ->


Added a tag to t' footer showing git build status

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -12,6 +12,7 @@
 gulpInject =       require 'gulp-inject'
 gulpMocha =        require 'gulp-mocha'
 gulpRename =       require 'gulp-rename'
+gulpReplace =      require 'gulp-replace'
 gulpHtmlmin =      require 'gulp-htmlmin'
 gulpServe =        require 'gulp-serve'
 gulpNodemon =      require 'gulp-nodemon'
@@ -33,8 +34,13 @@
 mainBowerFiles =   require 'main-bower-files'
 lazypipe =         require 'lazypipe'
 es =               require 'event-stream'
+exec =             require('child_process').exec

 bower =            require './bower'
+
+execute = (command, callback) ->
+  exec command, (error, stdout) ->
+    callback(stdout)

 htmlminOpts =
   removeComments: true
@@ -108,11 +114,17 @@
     .pipe(gulpFilter('**/*.js'))
     .pipe(gulp.dest('./target/client/tmp/client/statics/vendors/js'))

-  gulp.src('./src/main/client/index.html')
-    .pipe(gulpInject(gulp.src('./target/client/tmp/client/statics/vendors/css/bootstrap.css'), {ignorePath: ['target/client/tmp/client'], starttag: ''}))
-    .pipe(gulpInject(es.merge(jsVendorFiles, cssVendorFiles), {ignorePath: ['target/client/tmp/client'], starttag: ''}))
-    .pipe(gulpInject(es.merge(appFiles(), cssFiles()), {ignorePath: ['target/client/tmp/client', 'src/main/client']}))
-    .pipe(gulp.dest('./target/client/tmp/client/'))
+  execute 'git log -n 1 --format="%h"', (commitId) ->
+    execute 'git diff --shortstat', (shortStat) ->
+      shortStat = shortStat.toString().trim()
+      commitId = commitId.toString().trim()
+      tag = 'Tag: ' + commitId + (if shortStat then (' with: ' + shortStat) else '')
+      gulp.src('./src/main/client/index.html')
+        .pipe(gulpReplace(/[^<]*?<\/span>/m, '' + tag + ''))
+        .pipe(gulpInject(gulp.src('./target/client/tmp/client/statics/vendors/css/bootstrap.css'), {ignorePath: ['target/client/tmp/client'], starttag: ''}))
+        .pipe(gulpInject(es.merge(jsVendorFiles, cssVendorFiles), {ignorePath: ['target/client/tmp/client'], starttag: ''}))
+        .pipe(gulpInject(es.merge(appFiles(), cssFiles()), {ignorePath: ['target/client/tmp/client', 'src/main/client']}))
+        .pipe(gulp.dest('./target/client/tmp/client/'))

 gulp.task 'templates', () ->


fix path while parsing script

fix path while parsing script

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -237,15 +237,19 @@
       .pipe $.debug()
       .pipe gulp.dest paths.resources.external.contents.copyDest(category)

-gulp.task 'compile-story', (cb) ->
-  # compile story data files like characters.cson
+gulp.task 'handle-story', [ 'compile-story-characters', 'parse-story-script' ]
+
+gulp.task 'compile-story-characters', ->
   gulp
     .src paths.story.characters.meta.toCompile
     .pipe $.debug()
     .pipe $.cson().on('error', gutil.log)
     .pipe gulp.dest paths.story.characters.meta.compileDest

-  exec "coffee tools/script_parser/parser.coffee --input-file #{paths.story.mainScript.toParse} --output-file #{paths.story.mainScript.parseDestFile}", (err, stdout, stderr) ->
+gulp.task 'parse-story-script', (cb) ->
+  path = require 'path'
+
+  exec "coffee #{path.join __dirname, 'tools/script_parser/parser.coffee'} --input-file #{path.join __dirname, paths.story.mainScript.toParse[0]} --output-file #{path.join __dirname, paths.story.mainScript.parseDestFile}", (err, stdout, stderr) ->
     gutil.log stdout
     gutil.log stderr
     cb err
@@ -279,7 +283,7 @@
       'handle-internal-elements'
       'compile-external-config'
       'handle-external-resources'
-      'compile-story'
+      'handle-story'
     ]
     cb
   )


change paperdark's url

change paperdark's url

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -35,7 +35,7 @@
     superhero: 'https://bootswatch.com/superhero/bootstrap.css'
     united: 'https://bootswatch.com/united/bootstrap.css'
     lumendark: 'https://raw.githubusercontent.com/PHELiOX/poi-theme-lumendark/master/lumendark.css'
-    paperdark: 'https://raw.githubusercontent.com/ruiii/poi_theme_paper_dark/master/paper_dark.css'
+    paperdark: 'https://raw.githubusercontent.com/ruiii/poi_theme_paper_dark/master/paperdark.css'
   for theme, url of themes
     dir = path.join(__dirname, 'assets', 'themes', theme, 'css')
     fs.ensureDirSync dir


updates to `gulp newPost --title "some title" --date "2016/05/02"`

can now specify the date from the `date` flag. If no date supplied, then current date is used.

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -47,16 +47,16 @@
   .pipe livereload()

 gulp.task "newPost", ->
-  date = new Date()
-  y = date.getFullYear()
-  m = date.getMonth()+1 # yes...js months are 0 based
-  folderDate =  y + "-" + ("0" + m).slice(-2)
   knownOpts =
-    string: 'title'
-    defaults: folderDate
+    string: ['title','date']
+    defaults: {date: folderDate, title: 'untitled'}
   opts = if process.argv.length > 1 then minimist process.argv.slice(2), knownOpts else {'title':null}
   fileTitle = if opts.title then "_"+opts.title.replace(/\s+/g, '-').toLowerCase() else ""
   humanTitle = opts.title
+  date = if not isNaN(Date.parse(opts.date)) then new Date(opts.date) else new Date()
+  y = date.getFullYear()
+  m = date.getMonth()+1 # yes...js months are 0 based
+  folderDate =  y + "-" + ("0" + m).slice(-2)
   postTitle = folderDate + fileTitle

   postDir = "contents/articles/" + "#{ postTitle }"
@@ -71,6 +71,7 @@
                 title: #{ humanTitle }\n
                 date: #{ date }\n
                 author: Pupil Dev Team\n
+                subtitle: \n
                 ---"
   fs.writeFile postDir+"/index.md", postHeader
   gutil.log gutil.colors.white.bgBlue("Success! "), "New post created at", gutil.colors.white.bgBlue("#{ postDir }")


Changed gulpfile

Changed gulpfile

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -1,10 +1,9 @@
-gulp = require 'gulp'
-coffee = require 'gulp-coffee'
-gutil = require 'gulp-util'
-concat = require 'gulp-concat'
+gulp = require('gulp')
+coffee = require('gulp-coffee')
+concat = require('gulp-concat')
 uglify = require 'gulp-uglify'
-sourcemaps = require 'gulp-sourcemaps'
-browserify = require 'gulp-browserify'
+sourcemaps = require('gulp-sourcemaps')
+browserify = require('gulp-browserify')
 rename = require 'gulp-rename'
 rimraf = require 'gulp-rimraf'
 gulpif = require 'gulp-if'
@@ -80,15 +79,10 @@
 gulp.task 'watch', ['build'], ->
   gulp.watch files.all, ['build']

-gulp.task 'coffee',->
-  gulp.src files.lib
-    # .pipe sourcemaps.init()
-    .pipe coffee {bare: true}
-      .on('error', gutil.log)
-    # .pipe concat()
-    # .pipe sourcemaps.write()
-    .pipe gulp.dest('./lib/')
-
+gulp.task 'mocha', ->
+  gulp.src files.test, { read: false }
+    .pipe mocha {reporter : 'list'}
+    .pipe exit()

 gulp.task 'lint', ->
   gulp.src files.all
@@ -104,7 +98,7 @@

 gulp.task 'literate', ->
   gulp.src files.examples
-    .pipe ljs { code : trguue }
+    .pipe ljs { code : true }
     .pipe rename
       basename : "README"
       extname : ".md"
@@ -112,7 +106,7 @@
     .pipe gulpif '!**/', git.add({args : "-A"})

 gulp.task 'codo', [], ()->
-  command = 'codo -o "./doc" --name "yjs" --private true --title "yjs rte type API" ./lib'
+  command = './node_modules/codo/bin/codo -o "./doc" --name "yjs" --readme "README.md" --undocumented false --private true --title "yjs API" ./lib - LICENSE.txt '
   run(command).exec()

 gulp.task 'phantom_test', ['build_browser'], ()->
@@ -120,7 +114,7 @@
     .pipe mochaPhantomJS()

 gulp.task 'clean', ->
-  gulp.src ['./build/{browser,test,node}/**/*.{js,map}','./doc/','./lib/*.js'], { read: false }
+  gulp.src ['./build/{browser,test,node}/**/*.{js,map}','./doc/'], { read: false }
     .pipe rimraf()

 gulp.task 'mocha', [], ->


Simplified build

Simplified build

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -20,13 +20,10 @@
   gulp.watch './src/**/*.coffee', -> gulp.run 'build'

 gulp.task 'build_client', ->
-  es.merge(
-    gulp.src(['src/**/*.coffee', '!src/node/*.coffee'])
-    gulp.src(['client/node-dependencies/**/*.js'])
-  )
+  gulp.src(['src/**/*.coffee', '!src/node/*.coffee', 'client/node-dependencies/**/*.js'])
     .pipe(es.map (file, callback) -> file.path = file.path.replace("#{path.resolve(dir)}/", '') for dir in ['./src', './client/node-dependencies']; callback(null, file))
     .pipe(compile({coffee: {bare: true}}))
-    .pipe(modules({type: 'local-shim', file_name: 'backbone-orm.js', umd: {symbol: 'BackboneORM', dependencies: ['underscore', 'backbone', 'moment', 'inflection'], bottom: true}}))
+    .pipe(modules({type: 'local-shim', file_name: 'backbone-orm.js', umd: {symbol: 'BackboneORM', dependencies: ['underscore', 'backbone', 'moment', 'inflection']}}))
     .pipe(gulp.dest('./'))

 gulp.task 'minify_client', ['build_client'], ->


build default on watch

build default on watch

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -95,7 +95,7 @@

 gulp.task 'default', ['package', 'coffee', 'html', 'less']

-gulp.task 'watch', ['reloader', 'html'], ->
+gulp.task 'watch', ['default', 'reloader', 'html'], ->
   # watch to rebuild
   sources = (v for k, v of paths)
   gulp.watch sources, ['default']


restructured to follow gulp examples

restructured to follow gulp examples

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -15,9 +15,6 @@
 outapp = './Yakayak.app/Contents/Resources/app'
 outui  = outapp + '/ui'

-gulp.task 'clean', (cb) ->
-  rimraf outbin, cb
-
 copyPrebuilt = ->
   # XXX this stinks, but gulp can't deal with
   # the symlinks in the binary.
@@ -27,33 +24,47 @@
 gulp.task 'pre', ->
   copyPrebuilt() unless fs.existsSync outbin

-gulp.task 'default', ['pre'], ->
+paths =
+  README:  './README.md'
+  package: './package.json'
+  coffee:  './src/**/*.coffee'
+  html:    './src/**/*.html'
+  less:    './src/**/*.less'

-  gulp.src './README.md'
+
+# setup package stuff (README, package.json)
+gulp.task 'package', ['pre'], ->
+  gulp.src paths.README
     .pipe changed outapp
     .pipe gulp.dest outapp

   # install runtime deps
-  gulp.src './package.json'
+  gulp.src paths.package
     .pipe changed outapp
     .pipe gulp.dest outapp
     .pipe install(production:true)

-  # compile coffeescript
-  gulp.src './src/**/*.coffee'
+
+# compile coffeescript
+gulp.task 'coffee', ->
+  gulp.src paths.coffee
     .pipe sourcemaps.init()
     .pipe coffee().on 'error', gutil.log
     .pipe sourcemaps.write()
     .pipe changed outapp
     .pipe gulp.dest outapp

-  # move .html-files
-  gulp.src './src/**/*.html'
+
+# copy .html-files
+gulp.task 'html', ->
+  gulp.src paths.html
     .pipe changed outapp
     .pipe gulp.dest outapp

-  # compile less
-  gulp.src './src/**/*.less'
+
+# compile less
+gulp.task 'less', ->
+  gulp.src paths.less
     .pipe sourcemaps.init()
     .pipe less().on 'error', gutil.log
     .pipe concat('ui/app.css')
@@ -62,8 +73,7 @@
     .pipe gulp.dest outapp

-gulp.task 'watch', ['default'], ->
-
+gulp.task 'reloader', ->
   # create an auto reload server instance
   reloader = autoReload()

@@ -71,8 +81,16 @@
   reloader.script()
     .pipe gulp.dest outui

-  # watch to rebuild
-  gulp.watch './src/**/*', ['default']
-
   # watch rebuilt stuff
   gulp.watch "#{outui}/**/*", reloader.onChange
+
+
+gulp.task 'clean', (cb) ->
+  rimraf outbin, cb
+
+gulp.task 'default', ['package', 'coffee', 'html', 'less']
+
+gulp.task 'watch', ['reloader', 'default'], ->
+  # watch to rebuild
+  sources = (v for k, v of paths)
+  gulp.watch sources, ['default']


Standardise gulpfile

Standardise gulpfile


re-instantiate if called with `new`

re-instantiate if called with `new`

--- hexaflip.coffee
+++ hexaflip.coffee
@@ -36,6 +36,7 @@

   constructor: (@el, @sets, @options = {}) ->
     return unless css.transform and @el
+    return new HexaFlip arguments... unless @ instanceof HexaFlip
     @[option] = @options[option] ? defaults[option] for option, value of defaults
     @fontSize += 'px' if typeof @fontSize is 'number'


Update URL

Update URL

--- app/js/app.coffee
+++ app/js/app.coffee
@@ -35,7 +35,7 @@

   $('#license-spinner').change ->
     selected = $("select option:selected")
-    $('#license-selection').html "Great! You selected #{selected.text()}!"
+    $('#license-selection').html "Great! You selected #{selected.text()}!"

   validator = new FormValidator("license-form", [
     name: "license"


Add table function.

Add table function.

--- test/e2e/helpers/page.coffee
+++ test/e2e/helpers/page.coffee
@@ -1,6 +1,4 @@
 _ = require 'lodash'
-
-_.str = require 'underscore.string'

 expect = require('./expect')()

@@ -25,85 +23,223 @@
 #   login = LoginPage('/login')
 #   expect(login.billboard).to.evantually.equal('Login')
 class Page
+  # @param selector the search condition
+  # @returns the By locator object
+  locatorFor = (selector) ->
+    # If the selector is a string, then make a By locator.
+    # Otherwise, the selector is assumed to already be a
+    # By locator.
+    if _.isString(selector)
+      # An xpath selector string has a slash or equals.
+      # An id selector starts with # and doesn't have a space.
+      # Anything else is assumed to be CSS.
+      if selector is '..' or '/' in selector
+        By.xpath(selector)
+      else if selector[0] is '#' and not ' ' in selector
+        By.id(selector[1..-1])
+      else
+        By.css(selector)
+    else
+      selector
+
+  # @param from the WebElement to search from, or this Page
+  #   to search from the document root
+  # @param selectors the search criteria
+  # @returns a promise which resolves to the target
+  #   ElementFinder, or null if no such element exists
+  chainedSelect = (from, selectors...) ->
+    # @param parent the ElementFinder to search from, or this
+    #   Page to search from the document root
+    # @param locator the protractor By object
+    # @returns the result of calling element on the from
+    #   parent with the given locator
+    next = (parent, locator) ->
+      parent.element(locator)
+
+    # Convert the selectors to locators.
+    locators = ((locatorFor(selector) for selector in selectors))
+    # Chain the selects.
+    target = _.reduce(locators, next, from)
+
+    # Return a promise which guards the result with an existence
+    # check.
+    target.isPresent().then (exists) ->
+      if exists then target else null
+
+  # @param from the ElementArrayFinder to search from, or
+  #   this Page to search from the document root
+  # @param selectors the search criteria
+  # @returns a promise which resolves to the target
+  #   ElementFinder array
+  chainedAll = (from, selectors...) ->
+    # @param parent the ElementArrayFinder to search from, or
+    #   this Page to search from the document root
+    # @param locator the protractor By object
+    # @returns the search result WebElement array
+    next = (parent, locator) ->
+      parent.all(locator)
+
+    # Convert the selectors to locators.
+    locators = ((locatorFor(selector) for selector in selectors))
+    # Chain the search.
+    _.reduce(locators, next, from)
+
+  ## The Page functions. ##
+
   # @param url the page request URL
   constructor: (url) ->
     # Fire off the browser request.
     browser.get(url)
-
-    # The title is resolved when the page is loaded.
-    @title = browser.getTitle()
-
-    # The billboard text.
-    @billboard = @text('.qi-billboard', 'h3')
-
+
+  # Search from the document root.
+  element: element
+
+  # Search from the document root.
+  all: element.all
+
   # Finds the element with the given search selection chain.
-  # Each selector is a By locator or search string. The search
-  # string can be CSS or xpath. If the string contains a '/' or
-  # equals '..', then the string is assumed to be an xpath,
-  # otherwise, it is assumed to be CSS.
+  # Each selector is either a Protractor By object or a search
+  # condition string. The search condition string can be CSS,
+  # an id or an xpath, determined as follows:
+  #
+  # * If the string contains a '/' or equals '..', then the
+  #   string is assumed to be an xpath.
+  #
+  # * Otherwise, if the string starts with '#' the search
+  #   is on the id following the hash.
+  #
+  # * Otherwise, the search argument is assumed to be CSS.
   #
   # For example,
   #
-  #     page.select('.qi-billboard', 'h3', '..')
+  #     page.find('.qi-billboard', 'h3', '..')
   #
   # returns the parent of the h3 element within the .qi-billboard
   # element on the page.
   #
-  # @param selectors the selectors
+  # The ElementFinder result is extended with a find function
+  # for chaining, so the above example is equivalent to:
+  #
+  #     page.find('.qi-billboard').find('h3').find('..')
+  #
+  # @param selectors the search conditions
   # @returns a promise which resolves to the target
   #   ElementFinder, or null if no such element exists
-  select: (selectors...) ->
-    # @param selectors the search chain conditions
-    # @returns the By locator object
-    locator = (selector) ->
-      # If the selector is a string, then make a By locator.
-      # Otherwise, the selector is assumed to already be a
-      # By locator.
-      if _.isString(selector)
-        # An xpath selector string has a slash or equals.
-        # An id selector starts with # and doesn't have a space.
-        # Anything else is assumed to be CSS.
-        if selector is '..' or '/' in selector
-          By.xpath(selector)
-        else if selector[0] is '#' and not ' ' in selector
-          By.id(selector[1..-1])
-        else
-          By.css(selector)
-      else
-        selector
-
-    # @param parent the ElementFinder wrapper to search, or
-    #   null if no parent
-    # @param locator the protractor By object
-    # @returns the result of calling element on the locator
-    #   relative to the given parent finder
-    find = (parent, locator) ->
-      parent.element(locator)
-
-    # Convert the selectors to locators.
-    locators = ((locator(selector) for selector in selectors))
-    # The initial locator.
-    locator = locators.shift()
-    # The initial ElementFinder.
-    finder = element(locator)
-    # Chain the selects.
-    target = _.reduce(locators, find, finder)
-
-    # Return a promise which guards the result with an
-    # existence check.
-    target.isPresent().then (exists) ->
-      if exists then target else null
-
-  # @param selectors the target search selectors
+  find: (selectors...) ->
+    # Chain the selects...
+    chainedSelect(this, selectors...).then (target) ->
+      # ...and add the search functions.
+      if target?
+        target.find = _.partial(chainedSelect, target)
+        target.findAll = _.partial(chainedAll, target)
+      # Return the result.
+      target
+
+  # Finds the elements with the given search selection.
+  # The selectors are described in the find function.
+  # Unlike the find function, findAll only accepts one
+  # selector argument.
+  #
+  # Each ElementFinder in the array result is extended
+  # with a find function for chaining.
+  #
+  # @param selector the search condition
+  # @returns a promise which resolves to the target
+  #   ElementFinder array
+  findAll: (selectors...) ->
+    # Chain the selects...
+    chainedAll(this, selectors...).then (targets) ->
+      # ...and add the search functions.
+      for target in targets
+        target.find = _.partial(chainedSelect, target)
+        target.findAll = _.partial(chainedAll, target
+        )
+      # Return the result.
+      targets
+
+  # @param selectors the target search conditions
   # @returns a promise which resolves to the element's visible
   #   text, or null if the search result does not exist
   text: (selectors...) ->
     # Find the element.
-    @select(selectors...).then (target) ->
+    @find(selectors...).then (target) ->
       if target? then target.getText() else null
+
+  # Finds the table WebElements for the given Page.findAll()
+  # selector. The return value is an array of table promises.
+  # Each table promise resolves to an object {header, body},
+  # where:
+  # * header is a promise which resolves to an array of table
+  #   heading text value promises
+  # * body is a promise which resolves to an array of row promises
+  #
+  # Each row promise resolves to an array of column text promises.
+  #
+  # If the function argument is provided, then it is applied to
+  # each table cell text value promise.
+  #
+  # @param selector the Protractor CSS locator argument
+  # @returns the table promises
+  findTables: (selectors...) ->
+    headings = (table) ->
+      table.all(By.tagName('th'))
+
+    rows = (table) ->
+      body = table.element(By.tagName('tbody'))
+      body.all(By.tagName('tr'))
+
+    columns = (row) ->
+      row.all(By.tagName('td'))
+
+    text = (col) ->
+      col.getText().then (text) ->
+        text
+
+    mapTables = (tables) ->
+      mapTable = (table) ->
+        mapHeader = ->
+          # Return the [heading values] promise.
+          hdgs = headings(table)
+          hdgs.map(text)
+
+        mapBody = ->
+          mapRow = (row) ->
+            # Return the [column values] promise.
+            columns(row).map(text)
+
+          # Return the [rows] promise.
+          rows(table).map(mapRow)
+
+        # Return the {header, body}
+        header: mapHeader()
+        body: mapBody()
+
+      # Return the [{header, body}, ...] promise.
+      tables.map(mapTable)
+
+    # Return the [tables] promise.
+    @findAll(selectors...).then(mapTables)

 # Add the virtual properties.
 Object.defineProperties Page.prototype,
+  # @returns the title text
+  title:
+    get: ->
+      browser.getTitle()
+
+  # @returns the billboard text
+  billboard:
+    get: ->
+      @text('.qi-billboard', 'h3')
+
+  # Find the partial content. The page is loaded if and and only if
+  # the return value is not null.
+  #
+  # @returns the qi-content WebElement holding the partial content
+  content:
+    get: ->
+      @find('.qi-content')
+
   # Navigate to the home page by clicking the home button.
   #
   # @returns the home URL
@@ -111,17 +247,17 @@
   home:
     get: ->
       # Finds the home button.
-      find = =>
+      findButton = =>
         # The home button is the parent of the home icon.
-        @select('button .glyphicon-home', '..')
-
+        @find('button .glyphicon-home', '..')
+
       # Validates the home button.
       #
       # @throws expectation error if the button is missing
       validate = (button) ->
         expect(button, 'The home button is missing').to.exist
         button
-
+
       # Clicks the given button.
       click = (button) ->
         button.click()
@@ -136,36 +272,37 @@
         browser.navigate().back().then ->
           # Resolve to the URL.
           home_url
-
-      find()
+
+      findButton()
         .then(validate)
         .then(click)
         .then(pop)
-
+
   # @returns the help content
   help:
     get: ->
-      @select('.qi-help').then (block) =>
+      @find('.qi-help').then (elt) =>
         # Help is initially hidden.
-        expect(block.isDisplayed(), 'The help element is not initially hidden')
+        expect(elt, 'The help element is missing').to.exist
+        expect(elt.isDisplayed(), 'The help element is not initially hidden')
           .to.eventually.be.false
         # The help button is the parent of the question mark icon.
-        @select('button .glyphicon-question-sign', '..').then (button) =>
+        @find('button .glyphicon-question-sign', '..').then (button) ->
           expect(button, 'The help button is missing').to.exist
           # Open the help view...
           button.click().then =>
             # ...verify that the help is displayed...
-            expect(block.isDisplayed(), 'The help element is hidden after click')
+            expect(elt.isDisplayed(), 'The help element is hidden after click')
               .to.eventually.be.true
-            # ...find the element...
-            @select('.qi-main .qi-help-text').then (content) =>
+            # ...find the help text element...
+            elt.find('.qi-help-text').then (content) ->
               # ...resolve to the formatted help content.
-              content.getInnerHtml()
-
-    # @returns the contact information contact
-    contact: () ->
-      @select('.qi-main .qi-contact-info').then (content) =>
-        # ...resolve to the formatted contact information content.
-        content.getInnerHtml()
+              content.getInnerHtml() if content?
+
+  # @returns the contact information
+  contactInfo:
+    get: ->
+      @find('.qi-contact-info').then (content) ->
+        content.getInnerHtml() if content?

 module.exports = Page


Add webmaster setting

Add webmaster setting

--- src/scripts/settings.coffee
+++ src/scripts/settings.coffee
@@ -24,4 +24,7 @@
     #   Example: 'http:' + '//' + 'cnx.org' + '/' + 'content'
     # Do not include the protocol or a trailing slash
     legacy: 'cnx.org'
+
+    # Webmaster E-mail address
+    webmaster: 'cnx@cnx.org'
   }


Clean up docs

Clean up docs

--- src/select-list-view.coffee
+++ src/select-list-view.coffee
@@ -71,10 +71,10 @@

   # Public: Set the array of items to display in the list.
   #
-  # This should be model items not actual views.  `viewForItem(item)` will be
+  # This should be model items not actual views. {.viewForItem} will be
   # called to render the item when it is being appended to the list view.
   #
-  # items - The {Array} of model items to display in the list.
+  # items - The {Array} of model items to display in the list (default: []).
   setItems: (@items=[]) ->
     @populateList()
     @setLoading()
@@ -112,8 +112,10 @@
   getFilterQuery: ->
     @editorView.getEditor().getText()

-  # Public: Build the DOM elements using the array from the last call to
-  # {.setItems}.
+  # Public: Populate the list view with the model items previously set by
+  # calling {.setItems}.
+  #
+  # Subclasses may override this method but should always call `super`.
   populateList: ->
     return unless @items?

@@ -143,6 +145,8 @@
   #
   # itemCount - The {Number} of items in the array specified to {.setItems}
   # filteredItemCount - The {Number} of items that pass the fuzzy filter test.
+  #
+  # Returns a {String} message (default: 'No matches found').
   getEmptyMessage: (itemCount, filteredItemCount) -> 'No matches found'

   # Public: Set the maximum numbers of items to display in the list.


Added placeholder for gD

Added placeholder for gD

--- keymaps/vim-mode.cson
+++ keymaps/vim-mode.cson
@@ -117,6 +117,7 @@
   '`': 'vim-mode:move-to-mark-literal'
   '\'': 'vim-mode:move-to-mark'
   'g d': 'vim-mode:move-to-definition'
+  'g D': 'vim-mode:move-to-definition'

   'f': 'vim-mode:find'
   'F': 'vim-mode:find-backwards'


Fix empty mini pie charts

Fix empty mini pie charts

--- lineman/app/js/controllers/mini_proposal_pie_chart_controller.coffee
+++ lineman/app/js/controllers/mini_proposal_pie_chart_controller.coffee
@@ -4,7 +4,8 @@
     { value : 0, color : "#90D490" },
     { value : 0, color : "#F0BB67" },
     { value : 0, color : "#D49090" },
-    { value : 0, color : "#dd0000"}
+    { value : 0, color : "#dd0000" },
+    { value : 0, color : "#cccccc" }
   ]

   $scope.pieChartOptions =
@@ -15,11 +16,12 @@
   refreshPieChartData = ->
     return unless $scope.proposal
     counts = $scope.proposal.voteCounts
-    # yeah - this is done to preseve the view binding on the pieChartData
+    hasAnyVotes = counts.yes + counts.abstain + counts.no + counts.block > 0
     $scope.pieChartData[0].value = counts.yes
     $scope.pieChartData[1].value = counts.abstain
     $scope.pieChartData[2].value = counts.no
     $scope.pieChartData[3].value = counts.block
+    $scope.pieChartData[4].value = if hasAnyVotes then 0 else 1

   $scope.$watch 'proposal.voteCounts', ->
     refreshPieChartData()


:bug: Open Terms of Use on click

:bug: Open Terms of Use on click

--- lib/about-view.coffee
+++ lib/about-view.coffee
@@ -1,5 +1,6 @@
 {ScrollView} = require 'atom-space-pen-views'
 {Disposable} = require 'atom'
+shell = require 'shell'

 module.exports =
 class AboutView extends ScrollView
@@ -59,8 +60,12 @@
     @copyAtomVersion.on 'click', =>
       atom.clipboard.write(@atomVersion.text())

-    @viewLicense.on 'click', =>
+    @viewLicense.on 'click', ->
       atom.commands.dispatch(atom.views.getView(atom.workspace), 'application:open-license')
+
+    @viewTerms.on 'click', ->
+      # TODO: De-dupe this and use `application:open-terms-of-use`
+      shell.openExternal 'https://help.github.com/articles/github-terms-of-service'

   serialize: ->
     deserializer: @constructor.name


actually working implementation plus better errors

--- lib/connection.coffee
+++ lib/connection.coffee
@@ -11,7 +11,7 @@

     @client.handle 'error', (options) =>
       if atom.config.get("julia-client.errorsToConsole")
-        process.emitter.emit 'stderr', options.msg
+        @process.emitter.emit 'stderr', options.msg + '\n' + options.detail
       else
         atom.notifications.addError options.msg, options


Working jumps across views.

Working jumps across views.

- Fixes #3.
- Fixes #9.

--- lib/jumpy-view.coffee
+++ lib/jumpy-view.coffee
@@ -51,10 +51,13 @@
       if location == null
           console.log "Jumpy canceled jump.  No location found."
           return
-      editor = _.find atom.workspace.getEditors(), (editor) ->
-          editor.id == location.editor
-      editor.setCursorBufferPosition(location.position)
-      console.log "Jumpy jumped to: #{@firstChar}#{@secondChar} at (#{location.position.row},#{location.position.column})"
+      atom.workspaceView.eachEditorView (editorView) ->
+          currentEditor = editorView.getEditor()
+          if currentEditor.id == location.editor
+              pane = editorView.getPane()
+              pane.activate()
+              currentEditor.setCursorBufferPosition(location.position)
+              console.log "Jumpy jumped to: #{@firstChar}#{@secondChar} at (#{location.position.row},#{location.position.column})"

   findLocation: ->
       label = "#{@firstChar}#{@secondChar}"


Use methods, not internal function functions (for WIP only)

--- lib/y-richtext.coffee
+++ lib/y-richtext.coffee
@@ -142,7 +142,9 @@
   # @param position [Integer] start position for the delta, default: 0
   #
   # @return [Integer] the position of the cursor after parsing the delta
-  deltaHelper = (delta, position =0) ->
+  deltaHelper: (delta, position = 0) ->
+    val = (position) =>
+      (@_get "characters").val(position)

     if delta?
       noneIsNull = (array)->
@@ -162,8 +164,8 @@

       if delta.insert?
         @insertHelper position, delta.insert
-        from = @val position
-        to = @val (position + delta.insert.length)
+        from = val position
+        to = val (position + delta.insert.length)
         operation.call selections, from, to, delta.attributes
         return position + delta.insert.length

@@ -173,13 +175,13 @@

       else if delta.retain?
         retain = parseInt delta.retain
-        from = @val position
-        to = @val (position + retain)
+        from = val position
+        to = val (position + retain)

         operation.call selections, from, to, delta.attributes
         return position + retain

-  insertHelper = (position, content) ->
+  insertHelper: (position, content) ->
     pusher = (position, char) =>
       if @_model?
         (@_get "characters").insert position, char
@@ -192,18 +194,20 @@
         charObj = @createChar char
         pusher (position + offset), charObj

-  deleteHelper = (position, length = 1) ->
+  deleteHelper: (position, length = 1) ->
     (@_get "characters").delete position, length

-  createChar = (char, left=[], right=[]) ->
-    return new Y.Object char: char
+  createChar: (char, left=[], right=[]) ->
+    return new Y.Object {
+      char: char
       left: left
       right: right
+    }

   # return the index of a character
   # @param character [Y.Object] the character to look for
   #TODO: check that it works
-  indexOf = (character) ->
+  indexOf: (character) ->
     (@_get "characters").val().indexOf(character)

   # pass deltas to the character instance


Inherit from BaseClass

Inherit from BaseClass

--- lib/y-richtext.coffee
+++ lib/y-richtext.coffee
@@ -1,3 +1,5 @@
+BaseClass = (require "./misc.coffee").BaseClass
+
 # All dependencies (like Y.Selections) to other types (that have its own
 # repository) should  be included by the user (in order to reduce the amount of
 # downloaded content).
@@ -5,7 +7,7 @@
 # script tags this is the best solution that came to my mind.

 # A class holding the information about rich text
-class RichText
+class RichText extends BaseClass
   # @param content [String] an initial string
   # @param editor [Editor] an editor instance
   # @param author [String] the name of the local author


Add _unique method

Add _unique method

--- ioserver.coffee
+++ ioserver.coffee
@@ -117,9 +117,22 @@
 			@method_list[name] = @method_list[name].concat(names)
 			k = Object.getPrototypeOf(k)
 			break if not Object.getPrototypeOf(k) # avoid listing Object properties
-		@method_list[name].unique().sort()
+		@method_list[name] = @_unique(@method_list[name]).sort()

+	_unique: (arr) ->
+		hash = {}
+		result = []

+		i = 0
+		l = arr.length
+		while i < l
+			unless hash.hasOwnProperty(arr[i])
+				hash[arr[i]] = true
+				result.push arr[i]
+			++i
+
+		return result
+
 	_findClientsSocket: ({service, room, cb}={}) ->
 		res = []
 		ns = @io.of(service ||"/")


adding a test for expressions in range comprehensions

--- test/fixtures/execution/test_range_comprehension.coffee
+++ test/fixtures/execution/test_range_comprehension.coffee
@@ -1,6 +1,6 @@
 nums: i * 3 for i in [1..3]

-negs: x for x in [-20..-10]
+negs: x for x in [-20..-5*2]
 negs: negs[0..2]

 result: nums.concat(negs).join(', ')


Cancel and resume methods

Cancel and resume methods

--- workers/social/lib/social/models/recurly/subscription.coffee
+++ workers/social/lib/social/models/recurly/subscription.coffee
@@ -18,7 +18,9 @@
         'all', 'one', 'some',
         'getSubscriptions'
       ]
-      instance     : []
+      instance     : [
+        'cancel', 'resume'
+      ]
     schema         :
       uuid         : String
       planCode     : String
@@ -96,4 +98,34 @@

         async = require 'async'
         async.parallel stack, (err, results)->
-          callback()+          callback()
+
+  cancel: secure (client, callback)->
+    {delegate} = client.connection
+    userCode = "user_#{delegate._id}"
+    payment.cancelUserSubscription userCode,
+      uuid: @uuid
+    , (err, sub)=>
+      @status   = sub.status
+      @datetime = sub.datetime
+      @expires  = sub.expires
+      @plan     = sub.plan
+      @quantity = sub.quantity
+      @renew    = sub.renew
+      @save =>
+        callback no, @
+
+  resume: secure (client, callback)->
+    {delegate} = client.connection
+    userCode = "user_#{delegate._id}"
+    payment.reactivateUserSubscription userCode,
+      uuid: @uuid
+    , (err, sub)=>
+      @status   = sub.status
+      @datetime = sub.datetime
+      @expires  = sub.expires
+      @plan     = sub.plan
+      @quantity = sub.quantity
+      @renew    = sub.renew
+      @save =>
+        callback no, @


Use more descriptive variable name

Use more descriptive variable name

--- js-library/app/js/jail_iframe/classes/loading_promises.coffee
+++ js-library/app/js/jail_iframe/classes/loading_promises.coffee
@@ -1,11 +1,11 @@
 setTimeout ->
-  ok =
+  isDOMContentLoaded =
     loaded: true
     interactive: !(document.documentMode < 11)
     complete: true

-  if ok[document.readyState]
+  if isDOMContentLoaded[document.readyState]
     FactlinkJailRoot.host_ready_promise.resolve()
   else
     document.addEventListener('DOMContentLoaded', -> FactlinkJailRoot.host_ready_promise.resolve())


fix remove area

fix remove area

--- BackofficeBundle/Resources/public/coffee/page/areaView.coffee
+++ BackofficeBundle/Resources/public/coffee/page/areaView.coffee
@@ -34,9 +34,8 @@
     @removeArea event  if confirm("Vous êtes sur le point de supprimer une zone. Souhaitez-vous poursuivre cette action ?")

   removeArea: (event) ->
-    ul = @el.parent()
-    $(@el).remove()
-    refreshUl ul
+    $(event.target).closest('li').remove()
+    refreshUl $(@el)
     @sendRemoveArea()
     return


Fix navigate not reloading current route

--- BackofficeBundle/Resources/public/coffee/page/nodeView.coffee
+++ BackofficeBundle/Resources/public/coffee/page/nodeView.coffee
@@ -61,7 +61,7 @@
       url: @node.get('links')._self_duplicate
       method: 'POST'
       success: (response) ->
-        Backbone.history.navigate(redirectRoute, true)
+        Backbone.history.loadUrl(redirectRoute)
     return

   renderWidgetStatus: ->


Reorder methods

Reorder methods

Separate state-setting, chainable methods from evaluating, value-returning methods

--- test/helpers/gozer.coffee
+++ test/helpers/gozer.coffee
@@ -9,15 +9,15 @@
       PhantomD.open(page, url)
     @

-  run: (fn, args) ->
-    @page.then (page) ->
-      PhantomD.evaluate(page, fn, args)
-
   resize: (dimensions) ->
     dimensions.height ?= 768
     @page.then (page) ->
       page.set('viewportSize', dimensions)
     @
+
+  run: (fn, args) ->
+    @page.then (page) ->
+      PhantomD.evaluate(page, fn, args)

   getStyle: (selector, property) ->
     fn = (args) ->


add test for callback only once

add test for callback only once

--- test/spec/goldfish.coffee
+++ test/spec/goldfish.coffee
@@ -158,4 +158,17 @@
         count += 1
       cache.clear()
       expect(count).to.equal 20
-      done()+      done()
+  it "should call callback only once", (done)->
+    count = 0
+    clock = sinon.useFakeTimers()
+    populate = (arg, cb)->
+      setTimeout(cb.bind(null, new Error("lulz")), 1000)
+    cache = new Goldfish({populate: populate})
+    setTimeout(done, 2000)
+    cache.get "test", (err, value)->
+      count += 1
+      expect(count).to.equal 1
+      expect(err).to.be.ok
+      clock.tick(1000)
+    clock.tick(1000)


Modify url on expand

Modify url on expand

--- SingularityUI/app/components/aggregateTail/AggregateTail.cjsx
+++ SingularityUI/app/components/aggregateTail/AggregateTail.cjsx
@@ -81,6 +81,7 @@
   showOnlyInstance: (taskId) ->
     @setState
       viewingInstances: [taskId]
+    history.replaceState @state, '', location.href.replace(location.search, "?taskIds=#{taskId}&view=#{@getViewString(@state.splitView)}&grep=#{@state.search}")

   selectTasks: (selectFuncion) ->
     viewing = _.pluck(selectFuncion(_.sortBy(@state.activeTasks, (task) => task.taskId.instanceNo)), 'id')


Fix zeros on the account page

Fix zeros on the account page

--- app/assets/javascripts/modules/dashboard/views/account.coffee
+++ app/assets/javascripts/modules/dashboard/views/account.coffee
@@ -10,7 +10,7 @@

     amountClass: ->
       return unless @model.get('account_balance')
-      modifier = if +@model.get('account_balance') > 0 then "positive" else "negative"
+      modifier = if +@model.get('account_balance') >= 0 then "positive" else "negative"
       "user-box__#{modifier}"

     formattedBalance: ->


DICOM viewing - remote fetch not working

--- app/coffee/modules/search/facetsearch/views/itemDetail.coffee
+++ app/coffee/modules/search/facetsearch/views/itemDetail.coffee
@@ -42,22 +42,9 @@
     #   @trigger 'show:underlay'
     #   @loadCsv(@model.get('views').csv)

-  loadDicom: ->
-    url = @model.get('views').dicom

-    # Fetches file from Background app
-    Backbone.Radio.channel('background').request('file', url).then (dicomBuffer) =>
-      @renderDicom(dicomBuffer)
+  fetchDicom: (url) ->

-  renderDicom: (pixelData) ->
-
-    # Loads dicom image
-    # TODO - use data-attribute element
-    # TODO - this should be abstracted into a separate viewer
-    element = document.getElementById('dicomImage')
-    cornerstone.enable(element)
-
-    # String to ArrayBuffer
     str2ab = (str) ->
       buf = new ArrayBuffer(str.length * 2)
       # 2 bytes for each char
@@ -67,7 +54,42 @@
       while i < strLen
         bufView[i] = str.charCodeAt(i)
         i++
-      buf
+      return buf
+
+    # XHR Configuration
+    # TODO - will jQuery get work just as well here?
+    xhr = new XMLHttpRequest
+    xhr.open('GET', url, true)
+    xhr.responseType = 'text'
+
+    # XHR Callbacks, Backbone flavored events
+    xhr.onload = (e) =>
+      status = if xhr.status == 200 then 'success' else 'error'
+
+      # Renders CSV
+      return @renderDicom(str2ab(xhr.response)) if status == 'success'
+
+    # Sends XHR, triggers request event
+    xhr.send()
+
+  loadDicom: ->
+    url = @model.get('views').dicom
+
+    if _s.startsWith(url, 'file://')
+      # Fetches file from Background app
+      Backbone.Radio.channel('background').request('file', url).then (dicomBuffer) => @renderDicom(dicomBuffer)
+
+    else
+      console.log 'FETCH FROM SERVER'
+      @fetchDicom(encodeURI(url))
+
+  renderDicom: (pixelData) ->
+
+    # Loads dicom image
+    # TODO - use data-attribute element
+    # TODO - this should be abstracted into a separate viewer
+    element = document.getElementById('dicomImage')
+    cornerstone.enable(element)

     # # # # #


small refactorings in Summer::resolve

small refactorings in Summer::resolve

--- src/summer/container.coffee
+++ src/summer/container.coffee
@@ -359,29 +359,39 @@
       ids = id
       result = {}

-      async.forEachSeries ids, (id, callback)=>
+      iterator = (id, callback)=>
         @resolve id, context, (err, res)->
           return callback(err) if err

           result[id] = res
-          callback(null, res)
-      , (err)->
+          callback(null)
+
+      end = (err)->
         return callback(err) if err
         callback(null, result)
+
+      async.forEachSeries(ids, iterator, end)
+
     # resolve an alias to id map
     else if typeof id is "object"
       map = id
+      ids = Object.keys(map)
       result = {}

-      async.forEachSeries Object.keys(map), (alias, callback)=>
+      iterator = (alias, callback)=>
         _id = map[alias]
-        @resolve _id, (err, res)->
-          callback(err) if err
+        @resolve _id, context, (err, res)->
+          return callback(err) if err
+
           result[alias] = res
-          callback()
-      , (err)->
+          callback(null)
+
+      end = (err)->
         return callback(err) if err
         callback(null, result)
+
+      async.forEachSeries(ids, iterator, end)
+
     # resolve a single id
     else
       factory = @getFactory(id)


Don't accidentally find our way back into the wizard

--- js/inner.coffee
+++ js/inner.coffee
@@ -806,7 +806,7 @@
     false

   advanceWizard: (new_step) =>
-    @wizardStep(Math.min(new_step, @wizardStep() + 1))
+    @wizardStep(Math.max(new_step, @wizardStep() + 1))

   loadStripe: () =>


disable the sidebar for now (fixes scrolling bug)

--- js/outer.coffee
+++ js/outer.coffee
@@ -234,7 +234,7 @@
         footerID: "#footer"
         contentID: ".article article"
     highlight()
-    sidebar()
+    # sidebar()

   about.lib = =>
     placeholder()


Simplified hash scrolling

Simplified hash scrolling

--- js/outer.coffee
+++ js/outer.coffee
@@ -13,12 +13,7 @@
       $("body").append HAML['footer'](renderContext)

       # Sammy eats hashes, so we need to reapply it to land at the right anchor on the page
-      hash = window.location.hash
-      if hash != '' and hash != '#'
-        window.location.hash = hash
-        @scroll(hash)
-      else
-        @scroll()
+      @scroll(window.location.hash)

       # Apply polyfill(s) if they exists
       @polyfill() if @polyfill?
@@ -29,10 +24,8 @@
         $ -> self.render()

     scroll: (hash) ->
-      if hash?
-        $('html, body').animate({scrollTop: $(hash).offset().top}, 0);
-      else
-        $('html, body').animate({ scrollTop: 0 }, 0);
+      if hash == '' or hash == '#' then hash = "body"
+      $('html, body').animate({scrollTop: $(hash).offset().top}, 0);

     display: ->
       if HAML? and HAML[@name]? @render() else @load()


solving issue #24

solving issue #24

https://github.com/serpentem/theoricus/issues/24

--- www/src/utils/string_util.coffee
+++ www/src/utils/string_util.coffee
@@ -5,7 +5,7 @@
 	###
 	@ucfirst=( str )->
 		a = str.substr( 0, 1 ).toUpperCase()
-		b = str.substr( 1 ).toLowerCase()
+		b = str.substr( 1 )
 		return a + b

 	###
@@ -21,4 +21,4 @@
 	###
 	@underscore=( str )->
 		str = str.replace( /([A-Z])/g, "_$1" ).toLowerCase()
-		str = if str.substr( 1 ) == "_" then str.substr 1 else str+		str = if str.substr( 1 ) == "_" then str.substr 1 else str


drop VCOM

drop VCOM

--- maps/contracts.coffee
+++ maps/contracts.coffee
@@ -11,8 +11,6 @@
     channel_id: 34959
   PLUN:
     channel_id: 34977
-  VCOM:
-    channel_id: 34983
   SUPR:
     channel_id: 34981
   PMED:


simplify code

simplify code

--- lib/edit-in-new-tab.coffee
+++ lib/edit-in-new-tab.coffee
@@ -111,9 +111,7 @@
           childEditor = atom.workspace.getActiveTextEditor()

           atom.workspace.observeTextEditors (editor) ->
-            return unless editor.id is childEditor.id
-
-            editor.onDidChange ->
+            childEditor.onDidChange ->
               parentSelection.insertText(editor.getText(), { select: true })

       .catch (error) ->


Fix F-Number values

Fix F-Number values


#200 cleanup, want to declarative at least this part

--- lib/operation-stack.coffee
+++ lib/operation-stack.coffee
@@ -88,19 +88,24 @@
       @vimState.activate('reset')
     @finish()

-  finish: (operation) ->
+  ensureAllSelectionsAreEmpty: ->
+    unless @editor.getLastSelection().isEmpty()
+      if settings.get('throwErrorOnNonEmptySelectionInNormalMode')
+        throw new Error("Selection is not empty in normal-mode: #{operation.toString()}")
+      else
+        @editor.clearSelections()
+
+  ensureAllCursorsAreNotAtEndOfLine: ->
+    for cursor in @editor.getCursors() when cursor.isAtEndOfLine()
+      moveCursorLeft(cursor, {preserveGoalColumn: true})
+
+  finish: (operation=null) ->
     @record(operation) if operation?.isRecordable()
     @vimState.emitter.emit 'did-finish-operation'
     if @vimState.isMode('normal')
-      unless @editor.getLastSelection().isEmpty()
-        if settings.get('throwErrorOnNonEmptySelectionInNormalMode')
-          throw new Error("Selection is not empty in normal-mode: #{operation.toString()}")
-        else
-          @editor.clearSelections()
+      @ensureAllSelectionsAreEmpty()
+      @ensureAllCursorsAreNotAtEndOfLine()

-      # Ensure Cursor is NOT at EndOfLine position
-      for cursor in @editor.getCursors() when cursor.isAtEndOfLine()
-        moveCursorLeft(cursor, {preserveGoalColumn: true})
     @vimState.updateCursorsVisibility()
     @vimState.reset()


Include version in error message

Include version in error message

--- lib/package-manager.coffee
+++ lib/package-manager.coffee
@@ -64,7 +64,7 @@
           @emit 'package-installed', pack
       else
         atom.packages.activatePackage(name) if activateOnFailure
-        error = new Error("Installing '#{name}' failed.")
+        error = new Error("Installing '#{name}@#{version}' failed.")
         error.stdout = stdout
         error.stderr = stderr
         if theme


Fix updating using the wrong emitter method

--- lib/package-manager.coffee
+++ lib/package-manager.coffee
@@ -280,7 +280,7 @@
         error.stderr = stderr
         onError(error)

-    @emitter.emit('package-updating', {pack})
+    @emitPackageEvent 'updating', pack
     apmProcess = @runCommand(args, exit)
     handleProcessErrors(apmProcess, errorMessage, onError)


Import statement is now added on the first line following 'from __future__' imports and comments, even when empty lines are used

--- lib/python-debugger.coffee
+++ lib/python-debugger.coffee
@@ -26,24 +26,25 @@
     )

     editor.moveCursorToTop()
+    insert_position = editor.getCursorBufferPosition()
     editor.moveCursorToBeginningOfLine()
     editor.selectToEndOfLine()
     line = editor.getSelectedText()
-    if not line.startsWith "from __future__"
-      console.log "First import is not from __future__"
-      if not IMPORT_STATEMENT.startsWith line
-        editor.moveCursorToBeginningOfLine()
-        editor.insertText(IMPORT_STATEMENT)

-    else
-      console.log "First import is from __future__"
+    # skip comments (and Python headers), "from __future__" imports and empty lines
+    while (line.startsWith "#") or (line.startsWith "from __future__") or (not line)
       editor.moveCursorToBeginningOfLine()
       editor.moveCursorDown()
       editor.selectToEndOfLine()
+      if line
+        insert_position = editor.getCursorBufferPosition()
       line = editor.getSelectedText()
-      if not IMPORT_STATEMENT.startsWith line
-        editor.moveCursorToBeginningOfLine()
-        editor.insertText(IMPORT_STATEMENT)
+
+    editor.setCursorBufferPosition(insert_position)
+
+    if not (IMPORT_STATEMENT.startsWith line)
+      editor.moveCursorToBeginningOfLine()
+      editor.insertText(IMPORT_STATEMENT)

     for cursor, index in cursors
       cursor.setBufferPosition(saved_positions[index])


Fix serialization error.

Fix serialization error.

Fixes #16.

--- lib/python-debugger.coffee
+++ lib/python-debugger.coffee
@@ -1,4 +1,5 @@
 {CompositeDisposable} = require "atom"
+path = require "path"
 Breakpoint = require "./breakpoint"
 BreakpointStore = require "./breakpoint-store"

@@ -41,7 +42,7 @@
     @pythonDebuggerView.destroy()

   serialize: ->
-    pythonDebuggerViewState: @pythonDebuggerView.serialize()
+    pythonDebuggerViewState: @pythonDebuggerView?.serialize()

     activePath = editor?.getPath()
     relative = atom.project.relativizePath(activePath)


Don't use bodyParser as we don't need file uploads, and it's a security risk

--- server/app.coffee
+++ server/app.coffee
@@ -28,8 +28,11 @@
   require('./initializers/handlebars_helpers')

   app.use express.favicon()
-  app.use express.bodyParser()
+
+  app.use express.json()
+  app.use express.urlencoded()
   app.use express.methodOverride()
+
   app.use express.cookieParser("your secret here")
   app.use express.session(
     store: new MongoStore(


Possible fix for bug #23

Possible fix for bug #23

--- lib/table-formatter.coffee
+++ lib/table-formatter.coffee
@@ -72,7 +72,7 @@

     editor.getBuffer().transact =>
       for range in selectionsRanges
-        editor.backwardsScanInBufferRange(@regex, range, myIterator)
+        editor.scanInBufferRange(@regex, range, myIterator)

   formatTable: (text) ->
     padding = (len, str = ' ') -> str.repeat len


Add a config for control character input

--- lib/transient-emacs.coffee
+++ lib/transient-emacs.coffee
@@ -9,6 +9,9 @@
     useLegacySearch:
       type: 'boolean'
       default: true
+    inputControlCharacter:
+      type: 'boolean'
+      default: false
   killring: null
   commands: null
   eventListeners: []
@@ -52,7 +55,8 @@
       addEditorEventListner(event.textEditor)

     @addIsearchCommands()
-    @addInputCtrlsCommands()
+    @addInputCtrlsCommands() if atom.config.get("transient-emacs.inputControlCharacter")
+
     @killring = new KillRing()

   addIsearchCommands: ->


Changing default web page from atom.io to bing.

--- lib/web-editor-view.coffee
+++ lib/web-editor-view.coffee
@@ -5,7 +5,7 @@

         # Create root element
         @element = document.createElement 'iframe'
-        @relocate 'https://atom.io/'
+        @relocate 'https://www.bing.com/'
         @element.setAttribute 'name', 'disable-x-frame-options'

         # It is very important to note that this is for development only...


fixed arguements behaviour

fixed arguements behaviour

--- lib/csss.coffee
+++ lib/csss.coffee
@@ -1,4 +1,6 @@
 throw 'CoffeeScript is needed' unless CoffeeScript?
+
+#TODO: lineBefore

 class DocumentStyle

@@ -61,10 +63,12 @@

   page: (selector, values) ->
     {selector, values} = @_selectorAndValues(selector, values)
-    @addLine("@page #{selector} #{@object_to_css(values)}") if selector
+    # values = if values then values else ''
+    values = @object_to_css(values) if values
+    @addLine("@page #{selector} #{values}") if selector

   _selectorAndValues: (selector,values) ->
-    if typeof selector is 'Object'
+    if typeof selector is 'object'
       { selector: '', values: selector }
     else
       selector ?= ''
@@ -307,10 +311,11 @@
   processStyleText: ->
     styletext = @styletext
     lines = styletext.split('\n')
+    lastSelectorLine = null
     for line, i in lines
       lineBeginsWithAttribute = @doesLineBeginsWithAttribute(line)
       line = @renameHyphenFunctionName(line)
-      lineBefore = lines[i-1]
+      lineBefore = lines[lastSelectorLine]
       # media queries have an exception rule
       if @pattern.isCSSQuery.test(line)
         # @media
@@ -320,7 +325,7 @@
       # attributes
       # color: 'black'
       else if lineBeginsWithAttribute
-        line = @parseAttributeLine(line, { indent: '  ', escape: true }) # we have an escape for e.g. font: 'Lucida', Arial
+        line = @parseAttributeLine(line, { indent: line.match(/^\s+/)[0], escape: true }) # we have an escape for e.g. font: 'Lucida', Arial
       else
         line = @parseInlineArguments(line)
         # functions, like `@pad('5px')`
@@ -329,12 +334,14 @@
         line = line.replace /^(\s*)([a-zA-Z\.\#\&\>\:\*]+((?!\:\s).)*)$/, "\n@add '$2', '$1', "
         # one selector, like `body.imprint`
         line = line.replace /\n(\s*)([a-zA-Z\.\#\&\>\:\*]+((?!\:\s).)*)(\s*)$/, "\n@add '$2', '$1', $4"
+        lastSelectorLine = null
       # check line before called a function/css query like @page
       if lineBefore and lineBeginsWithAttribute and /^\s*(@[a-z\_\-]+)([^\,]\s*)*\s*$/i.test(lineBefore)
         matches = lineBefore.match /^(\s*)(@[a-z\_\-]+)([^a-z\_\,].*)*\s*$/i
         changedLine  = matches[1]+matches[2]
         # TODO: what if @rule :pseudo, :pseudo ?
-        lines[i-1] = changedLine += ( if matches[3] then matches[3]+',' else '' ) + ' __$cssAttributes__ ='
+        lines[lastSelectorLine] = changedLine += ( if matches[3] then matches[3]+',' else '' ) + ' __$cssAttributes__ ='
+      lastSelectorLine = i if /^@[a-zA-Z\_\-]+/.test(line)
       lines[i] = line

     @styletext = lines.join('\n').replace(/\n+/g, "\n")


Use fs.realpathSync to save the correct path for files in symlinked directories

--- lib/file.coffee
+++ lib/file.coffee
@@ -9,7 +9,7 @@
     status: null # Either null, 'added', 'ignored', or 'modified'

   @::accessor 'name', -> @file.getBaseName()
-  @::accessor 'path', -> @file.getPath()
+  @::accessor 'path', -> fs.realpathSync(@file.getPath())
   @::accessor 'symlink', -> @file.symlink
   @::accessor 'type', ->
     extension = path.extname(@path)


remove old code

remove old code

--- lib/init.coffee
+++ lib/init.coffee
@@ -142,10 +142,6 @@
     backgroundColors.push(bck)
     document.getElementById('lupa-structure').style.background = bck;

-document.getElementById('lupa-run').addEventListener 'click', () ->
-    result = require(currentFile)
-    alert(result)
-
 document.getElementById('lupa-index-project').addEventListener('click', () ->
     alert "Click ok to start indexing (it can take a while)."
     try


getFileFor

getFileFor

--- lib/init.coffee
+++ lib/init.coffee
@@ -30,6 +30,9 @@

"

+getFileFor = (name) ->
+    'not defined -' + name
+
 atom.workspace.addLeftPanel(item: el)

@@ -52,7 +55,11 @@
                 '
'
         default: (entry) ->
             if entry.data.length
-                '
' + entry.name + '
' + entry.data.join('
') + '
'
+                '
#{entry.name}
' +
+                    entry.data.map(
+                        (n) -> '
#{n} - #{getFileFor(n)}
'
+                    ).join('
') +
+                    '
'
             else
                 '' #"
no #{entry.name}"


only save if buffer was modified

only save if buffer was modified

--- lib/main.coffee
+++ lib/main.coffee
@@ -24,7 +24,7 @@
     scope  = editor.getGrammar().scopeName

     if script? and scope.startsWith 'source.nsl'
-      editor.save()
+      editor.save() if editor.isModified()

       @getPath (stdout) ->
         nslJar  = atom.config.get('language-nsl.pathToJar')


:art: Sort grammar ids

:art: Sort grammar ids

--- lib/main.coffee
+++ lib/main.coffee
@@ -32,9 +32,9 @@
         'source.gfm'
         'source.litcoffee'
         'text.html.basic'
+        'text.md'
         'text.plain'
         'text.plain.null-grammar'
-        'text.md'
       ]
       description: 'List of scopes for languages for which previewing is enabled. See [this README](https://github.com/atom/spell-check#spell-check-package-) for more information on finding the correct scope for a specific language.'
     useGitHubStyle:


Specify config schema

Specify config schema

--- lib/main.coffee
+++ lib/main.coffee
@@ -1,6 +1,5 @@
 GitDiffView = require './git-diff-view'
 DiffListView = null
-

 diffListView = null
 toggleDiffList = ->
@@ -9,8 +8,10 @@
   diffListView.toggle()

 module.exports =
-  configDefaults:
-    showIconsInEditorGutter: false
+  config:
+    showIconsInEditorGutter:
+      type: 'boolean'
+      default: false

   activate: ->
     atom.workspaceView.eachEditorView (editorView) ->


Revert "lib/site: Parse age field as integer"

This reverts commit b71e56a75a292b9d704936480127afe9f8fbf806.

--- lib/site.coffee
+++ lib/site.coffee
@@ -30,8 +30,6 @@
             new Date val
           when 'cats','pending','permafoster'
             val is 'yes'
-          when 'age'
-            parseInt val, 10
           else
             val
     return info


<string> elements in GetParameterValues and GetRPCMethods should be unqualified

--- lib/soap.coffee
+++ lib/soap.coffee
@@ -107,7 +107,7 @@
   el = xml.node('cwmp:GetParameterValues').node('ParameterNames')
   el.attr({'soap-enc:arrayType' : "xsd:string[#{methodRequest.parameterNames.length}]"})
   for p in methodRequest.parameterNames
-    el.node('xsd:string').text(p)
+    el.node('string').text(p)

 cpeGetParameterValuesResponse = (xml) ->
@@ -210,7 +210,7 @@
   el = xml.node('cwmp:GetRPCMethodsResponse').node('MethodList')
   el.attr({'soap-enc:arrayType' : "xsd:string[#{methodResponse.methodList.length}]"})
   for m in methodResponse.methodList
-    el.node('xsd:string').text(m)
+    el.node('string').text(m)

 acsTransferComplete = (xml) ->


simplify DOM ready

simplify DOM ready

--- cf/spell.coffee
+++ cf/spell.coffee
@@ -1,6 +1,6 @@
-$().ready()
+$ ->

-words = $.getJSON("spellings.json")
-$.when(words).then (()->
-  alert("Hello World")
+  words = $.getJSON("spellings.json")
+  $.when(words).then (()->
+    alert("Hello World")
   )


Fixing variables scope accessor.

Fixing variables scope accessor.

--- www/src/theoricus/mvc/view.coffee
+++ www/src/theoricus/mvc/view.coffee
@@ -32,8 +32,8 @@
   @param [Object] @data   Data to be passed to the template
   @param [Object] @el     Element where the view will be "attached/appended"
   ###
-  _render:( @data = {}, template )=>
-    data =
+  _render:( data = {}, template )=>
+    @data =
       view: @
       params: @process.params
       data: data


removed old components/indicatorator/test/libs folder

--- server/components/indicatorator/test/libs/indicatorator.coffee
+++ server/components/indicatorator/test/libs/indicatorator.coffee
@@ -1,185 +0,0 @@
-assert = require('chai').assert
-_ = require('underscore')
-sinon = require('sinon')
-fs = require 'fs'
-Q = require('q')
-
-GDocGetter = require('../../getters/gdoc')
-Indicator = require('../../../../models/indicator').model
-Indicatorator = require('../../lib/indicatorator')
-StandardIndicatorator = require('../../indicatorators/standard_indicatorator')
-SubIndicatorator = require('../../lib/subindicatorator')
-
-suite('Indicatorator')
-
-test(".getData loads and formats the data based on its source", (done) ->
-  indicator = new Indicator(
-    indicatoration:
-      source: "esri"
-  )
-
-  sandbox = sinon.sandbox.create()
-
-  gotData = {some: 'data'}
-  fetchDataStub = sandbox.stub(Indicatorator, 'fetchData', ->
-    Q.fcall(-> gotData)
-  )
-
-  formattedData = {fancy: 'data'}
-  formatDataStub = sandbox.stub(Indicatorator, 'formatData', -> formattedData)
-
-  applyRangesStub = sandbox.stub(StandardIndicatorator, 'applyRanges', (data) ->
-    data
-  )
-
-  Indicatorator.getData(indicator).then( (data) ->
-    try
-      assert.isTrue(
-        fetchDataStub.calledOnce,
-        "Expected getData to be called"
-      )
-
-      formatDataCallArgs = formatDataStub.getCall(0).args
-
-      assert.isTrue(
-        formatDataStub.calledWith(indicator.indicatoration.source, gotData),
-        "Expected formatData to be called with the indicator source and the fetched data,
-        but was called with #{JSON.stringify formatDataCallArgs}"
-      )
-
-      assert.deepEqual data, formattedData,
-        "Expected the formatted data to be returned"
-
-      done()
-    catch err
-      done(err)
-    finally
-      sandbox.restore()
-  ).fail((err) ->
-    sandbox.restore()
-    done(err)
-  )
-)
-
-test(".getData doesn't apply ranges if the indicator has
-  indicatoration.applyRanges: false", (done)->
-  indicator = new Indicator(
-    indicatoration:
-      applyRanges: false
-  )
-
-  sandbox = sinon.sandbox.create()
-
-  applyRangesStub = sandbox.stub(StandardIndicatorator, 'applyRanges')
-  sandbox.stub(Indicatorator, 'fetchData', ->
-    Q.fcall(->)
-  )
-  sandbox.stub(Indicatorator, 'formatData', ->)
-
-  Indicatorator.getData(indicator).then( (data) ->
-    try
-      assert.strictEqual applyRangesStub.callCount, 0,
-        "Expected StandardIndicatorator.applyRanges not to be called"
-      done()
-    catch err
-      done(err)
-    finally
-      sandbox.restore()
-  ).catch((err)->
-    sandbox.restore()
-    done(err)
-  )
-)
-
-test(".query groups sub indicators if the indicator definition includes
- a reduceField attribute", (done)->
-  indicator = new Indicator(
-    indicatoration:
-      reduceField: 'station'
-  )
-
-  sandbox = sinon.sandbox.create()
-
-  groupStub = sandbox.stub(
-    SubIndicatorator, 'groupSubIndicatorsUnderAverageIndicators'
-  )
-  theData = {id: 5}
-  sandbox.stub(StandardIndicatorator, 'applyRanges', -> theData)
-
-  sandbox.stub(Indicatorator, 'fetchData', ->
-    Q.fcall(->)
-  )
-  sandbox.stub(Indicatorator, 'formatData', ->)
-
-  Indicatorator.getData(indicator).then(->
-    try
-      assert.strictEqual groupStub.callCount, 1,
-        "Expected SubIndicatorator.groupSubIndicatorsUnderAverageIndicators to be called once"
-
-      assert.isTrue groupStub.calledWith(
-        theData, {valueField: 'value', reduceField: indicator.indicatoration.reduceField}
-      ), "Expected groupSubIndicatorsUnderAverageIndicators to be called with the data and the field grouping data"
-
-      done()
-    catch err
-      done(err)
-    finally
-      sandbox.restore()
-  ).catch((err)->
-    sandbox.restore()
-    done(err)
-  )
-)
-
-
-test('.fetchData finds the getter for the indicator.source and calls fetch on it', (done) ->
-  indicator = new Indicator(
-    indicatoration:
-      source: "gdoc"
-  )
-
-  indicatorOfGetter = null
-  getterFetchStub = sinon.stub(GDocGetter::, 'fetch', ->
-    indicatorOfGetter = @indicator
-    Q.fcall(->)
-  )
-
-  Indicatorator.fetchData(indicator).then( (data) ->
-    try
-      assert.isTrue(
-        getterFetchStub.calledOnce,
-        "Expected getter.fetch to be called once, but called #{getterFetchStub.callCount}"
-      )
-
-      assert.deepEqual indicatorOfGetter, indicator,
-        "Expected the getter to refer to the supplied indicator"
-
-      done()
-    catch err
-      done(err)
-    finally
-      getterFetchStub.restore()
-  ).fail((err) ->
-    getterFetchStub.restore()
-    done(err)
-  )
-)
-
-test('.fetchData throws an error if there is no getter for the source', ->
-  indicator = new Indicator(
-    indicatoration:
-      source: "this_source_does_not_exist"
-  )
-
-  assert.throw( (->
-    Indicatorator.fetchData(indicator)
-  ), "No known getter for source 'this_source_does_not_exist'")
-)
-
-
-test('.formatData throws an error if there is no formatter for the source', ->
-
-  assert.throw( (->
-    Indicatorator.formatData("this_source_does_not_exist", [])
-  ), "No known formatter for source 'this_source_does_not_exist'")
-)


- using x-forwarded-host instead of config-option now

--- lib/util.coffee
+++ lib/util.coffee
@@ -13,7 +13,7 @@
   # callback)
   url: (config, req, params)->
     protocol = if config.ssl then "https:" else "http:"
-    host = if config.proxyhost then config.proxyhost else req.headers.host
+    host = if req.headers["x-forwarded-host"] then req.headers["x-forwarded-host"] else req.headers.host
     return URL.format(
       protocol: protocol
       host:     host


:memo: Add a note about how classes are applied to views

--- lib/view.coffee
+++ lib/view.coffee
@@ -3,6 +3,10 @@

 Bubble = require './view-bubble'

+#
+# When applying classes to views, we perform a substr on constructor name of the error,
+# so that LinterError becomes Error then we perform toLowerCase on it.
+#
 class LinterView extends EventEmitter
   constructor: (@Linter) ->
     super()


Remove isInText

Remove isInText

--- BackofficeBundle/Resources/public/coffee/security.coffee
+++ BackofficeBundle/Resources/public/coffee/security.coffee
@@ -4,13 +4,5 @@
 isLoginForm = (xhr) ->
   return xhr.getResponseHeader('X-Form-Type') == 'login'

-isInText = (text, message) ->
-  return false if typeof text == 'object' || ! text
-  nunberOccurence = text.indexOf message
-  if nunberOccurence > 0
-    true
-  else
-    false
-
 redirectToLogin = ->
   window.location.reload()


test that catches array gotcha with removeNode #142

--- spec/Graph.coffee
+++ spec/Graph.coffee
@@ -170,10 +170,31 @@
       }
     },
     {
+      "src": {
+        "process": "Foo",
+        "port": "out2"
+      },
+      "tgt": {
+        "process": "Bar",
+        "port": "in2"
+      },
+      "metadata": {
+        "route": "foo",
+        "hello": "World"
+      }
+    },
+    {
       "data": "Hello, world!",
       "tgt": {
         "process": "Foo",
         "port": "in"
+      }
+    },
+    {
+      "data": "Hello, world, 2!",
+      "tgt": {
+        "process": "Foo",
+        "port": "in2"
       }
     }
   ]
@@ -226,8 +247,8 @@
         done()
       g.setNodeMetadata 'Foo',
         hello: 'World'
-    it 'should contain one connection', ->
-      chai.expect(g.edges.length).to.equal 1
+    it 'should contain two connections', ->
+      chai.expect(g.edges.length).to.equal 2
     it 'the first Edge should have its metadata intact', ->
       edge = g.edges[0]
       chai.expect(edge.metadata).to.be.an 'object'
@@ -241,8 +262,8 @@
         done()
       g.setEdgeMetadata e.from.node, e.from.port, e.to.node, e.to.port,
         hello: 'World'
-    it 'should contain one IIP', ->
-      chai.expect(g.initializers.length).to.equal 1
+    it 'should contain two IIPs', ->
+      chai.expect(g.initializers.length).to.equal 2
     it 'should contain one published inport', ->
       chai.expect(g.inports).to.not.be.empty
     it 'should contain one published outport', ->
@@ -341,18 +362,18 @@
           chai.expect(node.id).to.equal 'Baz'
           done()
         g.removeNode 'Baz'
-      it 'shouldn\'t be have edges left behind', ->
+      it 'shouldn\'t have edges left behind', ->
         connections = 0
         for edge in g.edges
           connections++ if edge.from.node is 'Baz'
           connections++ if edge.to.node is 'Baz'
         chai.expect(connections).to.equal 0
-      it 'shouldn\'t be have IIPs left behind', ->
+      it 'shouldn\'t have IIPs left behind', ->
         connections = 0
         for edge in g.initializers
           connections++ if edge.to.node is 'Baz'
         chai.expect(connections).to.equal 0
-      it 'shouldn\'t be have exports left behind', ->
+      it 'shouldn\'t have exports left behind', ->
         exports = 0
         for exported in g.exports
           [exportedNode, exportedPort] = exported.private.split '.'


Add memo

Add memo

--- resources/assets/coffee/react/beatmap-discussions/main.coffee
+++ resources/assets/coffee/react/beatmap-discussions/main.coffee
@@ -33,6 +33,7 @@
     @xhr = {}
     @state = JSON.parse(props.container.dataset.beatmapsetDiscussionState ? null)
     @restoredState = @state?
+    # FIXME: update url handler to recognize this instead
     @focusNewDiscussion = document.location.hash == '#new'

     if !@restoredState


Make privilege level check in EditorHttpController more explicit

--- services/web/app/coffee/Features/Editor/EditorHttpController.coffee
+++ services/web/app/coffee/Features/Editor/EditorHttpController.coffee
@@ -9,6 +9,7 @@
 ProjectEditorHandler = require('../Project/ProjectEditorHandler')
 Metrics = require('../../infrastructure/Metrics')
 CollaboratorsHandler = require("../Collaborators/CollaboratorsHandler")
+PrivilegeLevels = require "../Authorization/PrivilegeLevels"

 module.exports = EditorHttpController =
 	joinProject: (req, res, next) ->
@@ -36,7 +37,7 @@
 					return callback(error) if error?
 					AuthorizationManager.getPrivilegeLevelForProject user_id, project_id, (error, privilegeLevel) ->
 						return callback(error) if error?
-						if !privilegeLevel
+						if !privilegeLevel? or privilegeLevel == PrivilegeLevels.NONE
 							callback null, null, false
 						else
 							callback(null,


correction

correction

--- src/Server.coffee
+++ src/Server.coffee
@@ -13,7 +13,7 @@
         account: @options.commission.account
         calculate: (params) ->
           amount: params.amount.multiply COMMISSION_RATE
-          refrence: COMMISSION_REFERENCE
+          reference: COMMISSION_REFERENCE
     @ceOperationHub =
       stream: zmq.socket 'sub'
       result: zmq.socket 'push'


fix(vm): always consider memory dynamic max when updating resource set

--- src/api/vm.coffee
+++ src/api/vm.coffee
@@ -72,7 +72,7 @@
   limits = {
     cpus: template.CPUs.number,
     disk: 0,
-    memory: template.memory.size,
+    memory: template.memory.dynamic[1],
     vms: 1
   }
   objectIds = [
@@ -449,7 +449,7 @@

     if resourceSet?
       yield @allocateLimitsInResourceSet({
-        memory: memory - VM.memory.size
+        memory: memory - VM.memory.dynamic[1]
       }, resourceSet)
     yield xapi.call 'VM.set_memory_dynamic_max', ref, "#{memory}"


fix(vm.set): correctly change min dynamic memory if necessary (#317)

Fixes vatesfr/xo-web#970

--- src/api/vm.coffee
+++ src/api/vm.coffee
@@ -426,15 +426,18 @@
         "cannot set memory below the static minimum (#{VM.memory.static[0]})"
       )

-    if ($isVMRunning VM) and memory > VM.memory.static[1]
-      @throw(
-        'INVALID_PARAMS'
-        "cannot set memory above the static maximum (#{VM.memory.static[1]}) "+
-          "for a running VM"
-      )
-
-    if memory > VM.memory.static[1]
+    if memory < VM.memory.dynamic[0]
+      yield xapi.call 'VM.set_memory_dynamic_min', ref, "#{memory}"
+    else if memory > VM.memory.static[1]
+      if $isVmRunning VM
+        @throw(
+          'INVALID_PARAMS'
+          "cannot set memory above the static maximum (#{VM.memory.static[1]}) "+
+            "for a running VM"
+        )
+
       yield xapi.call 'VM.set_memory_static_max', ref, "#{memory}"
+
     if resourceSet?
       yield @allocateLimitsInResourceSet({
         memory: memory - VM.memory.size


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/camera.coffee
+++ src/camera.coffee
@@ -41,7 +41,7 @@
       I.scroll.x = I.scroll.x.approach(centerRect.left, I.maxVelocity.x)

     if objectCenter.y > centerRect.bottom
-      I.scroll.x = I.scroll.x.approach(centerRect.right, I.maxVelocity.x)
+      I.scroll.y = I.scroll.x.approach(centerRect.right, I.maxVelocity.x)
     else if objectCenter.x < centerRect.left
       I.scroll.x = I.scroll.x.approach(centerRect.left, I.maxVelocity.x)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/camera.coffee
+++ src/camera.coffee
@@ -1,3 +1,5 @@
+oldCamera = Camera
+
 Camera = (I={}) ->
   Object.reverseMerge I,
     cameraBounds: Rectangle # World Coordinates
@@ -25,25 +27,21 @@
   objectFilters = []
   transformFilters = []

-  focusOn = (object) ->
-    dt = 1 / 30 # TODO: Use engine FPS
+  focusOn = (object, elapsedTime) ->
+    debugger unless elapsedTime
     dampingFactor = 2

     #TODO: Different t90 value inside deadzone?

-    c = dt * 3.75 / I.t90
+    c = elapsedTime * 3.75 / I.t90
     if c >= 1
       # Spring is configured to be too intense, just snap to target
       self.position(target)
       I.velocity = Point.ZERO
     else
       objectCenter = object.center()
-      objectVelocity = object.I.velocity
-
-      if objectVelocity
-        target = objectCenter.add(objectVelocity.scale(5))
-      else
-        target = objectCenter
+
+      target = objectCenter

       delta = target.subtract(self.position())

@@ -52,25 +50,25 @@
       I.velocity = I.velocity.add(force.scale(c))

   followTypes =
-    centered: (object) ->
+    centered: (object, elapsedTime) ->
       I.deadzone = Point(0, 0)

-      focusOn(object)
+      focusOn(object, elapsedTime)

-    topdown: (object) ->
+    topdown: (object, elapsedTime) ->
       helper = Math.max(I.screen.width, I.screen.height) / 4

       I.deadzone = Point(helper, helper)

-      focusOn(object)
+      focusOn(object, elapsedTime)

-    platformer: (object) ->
+    platformer: (object, elapsedTime) ->
       width = I.screen.width / 8
       height = I.screen.height / 3

       I.deadzone = Point(width, height)

-      focusOn(object)
+      focusOn(object, elapsedTime)

   self = Core(I).extend
     follow: (object, type="centered") ->
@@ -85,9 +83,9 @@

   self.attrAccessor "transform"

-  self.bind "afterUpdate", ->
+  self.bind "afterUpdate", (elapsedTime) ->
     if currentObject
-      followTypes[currentType](currentObject)
+      followTypes[currentType](currentObject, elapsedTime)

     # Hard clamp camera to world bounds
     I.x = I.x.clamp(I.cameraBounds.left + I.screen.width/2, I.cameraBounds.right - I.screen.width/2)
@@ -135,3 +133,5 @@
   "Flash"
   "Fade"
 ]
+
+Object.extend Camera, oldCamera


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/camera.coffee
+++ src/camera.coffee
@@ -25,7 +25,7 @@
   objectFilters = []
   transformFilters = []

-  focusOn = (object) ->
+  focusOn = (object, d) ->
     dt = 1 / 30 # TODO: Use engine FPS
     dampingFactor = 2


Removed unecessary returns values

Removed unecessary returns values

--- src/config.coffee
+++ src/config.coffee
@@ -9,13 +9,11 @@
     platform = if process.platform is 'win32' then 'USERPROFILE' else 'HOME'
     @file = path.join process.env[platform], '.snailer'
     @open()
-    @config

   # Return contents of ~/.snail or return an empty object {}
   open: ->
     @config = JSON.parse try fs.readFileSync @file, 'utf-8' catch err then '{}'
     @save()
-    @config

   # Returns false on fail, or the file contents
   save: ->
@@ -28,7 +26,6 @@
   reset: ->
     @config = {}
     @save()
-    @config

   addHostname: (hostname) ->
     hostname = helpers.getHostname hostname


Allow removal of all tags

Allow removal of all tags

We previously disabled removal of tags that have a key, since those were syste-added tags. But there's no reason for that restriction, and we need to be able to modify the newly-introduced instructor new/returning tags.

--- app/assets/javascripts/components/overview/tag_button.cjsx
+++ app/assets/javascripts/components/overview/tag_button.cjsx
@@ -9,7 +9,7 @@
   props.tags.map (tag) =>
     remove_button = (
       <button className='button border plus' onClick={remove.bind(null, tag.id)}>-</button>
-    ) unless tag.key?
+    )

       {tag.tag}{remove_button}


Fix RoomAddWidget hide on add error

Fix RoomAddWidget hide on add error

--- app/scripts/view/widget/RoomAddWidget/RoomAddWidget.coffee
+++ app/scripts/view/widget/RoomAddWidget/RoomAddWidget.coffee
@@ -42,7 +42,9 @@
         @ui.name.focus()
       , 200

-    onClickAdd: ->
+    onClickAdd: (e)->
+      e.preventDefault()
+      e.stopPropagation()
       name = @viewModel.get 'name'
       return unless /^[a-z0-9_]+$/.test name
       common.api.post_rooms({name}).done =>


Add Tablesorter Filter URL

Add Tablesorter Filter URL

--- server/app/assets/javascript/ship.coffee
+++ server/app/assets/javascript/ship.coffee
@@ -5,6 +5,7 @@
     sortAsc: 'icon-chevron-up glyphicon glyphicon-chevron-up'
     sortDesc: 'icon-chevron-down glyphicon glyphicon-chevron-down'

+  # Restore URL Parameter
   param = fromURLParameter(location.hash.replace(/^\#/, ''))
   param.lv = if param.lv? then decodeURIComponent(param.lv) else ""
   if param.modal?
@@ -13,22 +14,33 @@
     else
       $('#modal').modal({remote: "aship/#{param.id}"})

-  $('#ship_table').tablesorter
+  $('#ship_table').tablesorter(
     sortList: [[3, 1], [4, 1]]
     theme: 'bootstrap'
     headerTemplate: '{content} {icon}'
     widgets: ['uitheme', 'filter']
     widgetOptions:
       filter_hideFilters: true
+  ).bind 'filterEnd', (e, filter) ->
+    filters = $.tablesorter.getFilters($('#ship_table'))
+    location.hash = toURLParameter({filters: JSON.stringify(filters)})
+
   $.tablesorter.setFilters($('#ship_table'), ['', '', '', param.lv], true)
+
+  if param.filters?
+    $.tablesorter.setFilters($('#ship_table'), JSON.parse(param.filters), true)

   $('#clear').click (e) ->
     $.tablesorter.setFilters($('#ship_table'), '' for i in [1..15], true)
+    url = location.href.split('#')[0]
+    history.pushState(null, null, url)

   $('#modal').on 'shown.bs.modal', (e) ->
     $('.ship_hbar').each () ->
+      # setURL
       id = JSON.parse($(this).attr('data-id'))
       location.hash = toURLParameter({modal: true, id: id})
+      # plot
       data = JSON.parse($(this).attr('data-json'))
       $(this).jqplot(data, jqplotOpt)
     $('.fleet_data').each () ->


Improve date format in change list

Improve date format in change list

--- services/web/public/coffee/track-changes/ChangeListView.coffee
+++ services/web/public/coffee/track-changes/ChangeListView.coffee
@@ -3,6 +3,15 @@
 	"libs/mustache"
 	"libs/backbone"
 ], (moment)->
+
+	moment.lang "en", calendar:
+		lastDay : '[Yesterday at] h:mm a'
+		sameDay : '[Today at] h:mm a'
+		nextDay : '[Tomorrow at] h:mm a'
+		lastWeek : "Do MMM YYYY, h:mm a"
+		nextWeek : "Do MMM YYYY, h:mm a"
+		sameElse : 'Do MMM YYYY, h:mm a'
+
 	ChangeListView = Backbone.View.extend
 		template: $("#changeListTemplate").html()


Isolate mutation warning

Isolate mutation warning

--- src/layout.coffee
+++ src/layout.coffee
@@ -37,7 +37,7 @@

   level_dict._CHILDREN = [
     header(level_dict)
-    controls(level_dict)
+    # controls(level_dict)
     grid(level_dict)
     inspector
   ]


Don't learn from actual bot commands.

Don't learn from actual bot commands.

--- src/markov.coffee
+++ src/markov.coffee
@@ -108,8 +108,17 @@

   # The robot hears ALL. You cannot run.
   robot.hear /.+$/, (msg) ->
-    return if /^hubot:?/i.test msg.match[0]
+    # Don't learn from commands sent to the bot directly.
+    name = robot.name.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&')
+    if robot.alias
+      alias = robot.alias.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&')
+      r = new Regexp("^[@]?(?:#{alias}[:,]?|#{name}[:,]?)")
+    else
+      r = new Regexp("^[@]?#{name}[:,]?")
+    return if r.test msg.match[0]
+
     model.learn msg.match[0]

+  # Generate markov chains on demand, optionally seeded by some initial state.
   robot.respond /markov(\s+(.+))?$/i, (msg) ->
     msg.reply model.generate msg.match[2] or '', 10


chore(project): agrego handlebars como dependencia

--- source/coffee/package/lib/jeannie.coffee
+++ source/coffee/package/lib/jeannie.coffee
@@ -9,7 +9,7 @@
 ###

 yaml = require('js-yaml')
-#through = require('through2')
+hbs = require('handlebars')
 #Buffer = require('buffer').Buffer
 #extend = util._extend
 #unescapeHtml = require('unescape-html')


Fix Player physics body size and (dynamic) offset.

--- src/player.coffee
+++ src/player.coffee
@@ -37,13 +37,13 @@
       @physics = @sprite.body
       @velocity = @physics.velocity
       @acceleration = @physics.acceleration
+      @direction = Direction.Right
       @_initPhysics()

       @cursors = game.input.keyboard.createCursorKeys()

       @animation = null
       @state = State.Still
-      @direction = Direction.Right
       @nextAction = Action.None

     inMidAir: -> no
@@ -86,6 +86,10 @@
     _initPhysics: ->
       @physics.collideWorldBounds = on
       @physics.drag.x = 200
+      h = @sprite.height
+      w = @sprite.width
+      @_yOffset = h / 4
+      @physics.setSize (w / 2), (h / 2), @_xOffset(), @_yOffset

     _run: (direction) ->
       factor = 200
@@ -107,6 +111,8 @@
         @animation = @animations.play 'start' unless @inMidAir()
       @direction = direction
       @sprite.scale.x = direction
+      @physics.offset = new Phaser.Point @_xOffset(direction), @_yOffset

+    _xOffset: (direction = @direction) -> direction * 10

   Player


.

Former-commit-id: 95f41200bbf9b3446556bf4699bd1cebbde50ca6 [formerly 0fe7f8ff0d16d3756a609b98534c99d023568069] [formerly dc40382998d3b2a9c7b4e09b6ffbebfd20743ed0 [formerly efac5106815fa6c57b489d874526aecafb1013cb]] [formerly 9e2c9f18963df325a2d3f1fadb7fc02fac31e4d1 [formerly 374993c44fbcce6e4412ea180b8735faaadacca8] [formerly dd8ffe44dd9a174a3eac4518de107bc326142491 [formerly dd8ffe44dd9a174a3eac4518de107bc326142491 [formerly 17c6f3197bec2a59f9e3434ad47507d6ad87cc51]]]]
Former-commit-id: 257ecb110aba0e236ad4db74a15b6d49e37703f7 [formerly 3f5e496e36d6c2bc1efc8c06849d13bf41342215] [formerly c3f97014e6a7f061801e97756c56f1de99c3a800 [formerly aed3f6443f62cdd0c74f950a4afe219f7a0d21f4]]
Former-commit-id: f68f41696f54e4fff87bae9e4d13cfc9d3df6d44 [formerly 89db1b1f6794bf3f6121ba6d158379800e02d2e7]
Former-commit-id: cbd00e46fb1f84a42bfe442676b33a4ac75fee15
Former-commit-id: 25a283ee7c9b1d361d583caae022deb562598f5d

--- @client/edit_proposal.coffee
+++ @client/edit_proposal.coffee
@@ -370,7 +370,7 @@
                   # are set on the proposal

             ProposalRoles
-              key: if @props.fresh then @local else proposal
+              key: if @props.fresh then @local.key else proposal.key

         if @local.errors?.length > 0