update pageJump offset to account for fixed header

--- resources/assets/coffee/react/profile-page/main.coffee
+++ resources/assets/coffee/react/profile-page/main.coffee
@@ -334,8 +334,9 @@
           # - simple variable in callback
           # Both still change the switch too soon.
           @modeScrollTimeout = Timeout.set 100, => @scrolling = false
-      # count for the tabs height
-      offset: pagesOffset[0].getBoundingClientRect().height * -1
+      # count for the tabs height; assume pageJump always causes the header to be pinned
+      # otherwise the calculation needs another phase and gets a bit messy.
+      offset: (StickyHeader.headerHeight() + pagesOffset[0].getBoundingClientRect().height) * -1

   pageScan: =>


Fix sticky header when force show profile

--- resources/assets/coffee/react/profile-page/main.coffee
+++ resources/assets/coffee/react/profile-page/main.coffee
@@ -135,7 +135,7 @@
     isBlocked = _.find(currentUser.blocks, target_id: @state.user.id)

     div
-      className: 'osu-layout__no-scroll' if isBlocked
+      className: 'osu-layout__no-scroll' if isBlocked && !@state.forceShow
       if isBlocked
         div className: 'osu-page',
           el NotificationBanner,


fixed followers/following/liked subroutes

--- client/app/Applications/Members.kdapplication/AppController.coffee
+++ client/app/Applications/Members.kdapplication/AppController.coffee
@@ -233,7 +233,7 @@
         controller.ready -> controller.handleQuery? query
       callback contentDisplay

-    switch route.split('/')[1]
+    switch route.split('/')[2]
       when 'Followers'
         @createFolloweeContentDisplay model, 'followers', kallback
       when 'Following'


Slider Component: Started from scratch

Slider Component: Started from scratch

There were too much errors in latest commit. Values and intervals corrected. Added snap to step

--- client/landing/app/Applications/Demos.kdapplication/AppView.coffee
+++ client/landing/app/Applications/Demos.kdapplication/AppView.coffee
@@ -2,11 +2,9 @@

   viewAppended:->
     @addSubView new KDSliderBarView
-      minValue  : 0
+      minValue  : 40
       maxValue  : 100
-      step      : 10
-      handles   :
+      interval  : 20
+    , handles   :
         handle1 :
-          value : 0
-        handle2 :
-          value : 50+          value : 40


Capitalize 'follow' in FollowChannelButton

--- core/app/backbone/views/channels/follow_channel_button_view.coffee
+++ core/app/backbone/views/channels/follow_channel_button_view.coffee
@@ -5,9 +5,9 @@
     @bindTo @model, 'change', @updateButton, @

   templateHelpers: =>
-    disabled_label: Factlink.Global.t.follow
-    disable_label:  Factlink.Global.t.unfollow
-    enabled_label:  Factlink.Global.t.following
+    disabled_label: Factlink.Global.t.follow.capitalize()
+    disable_label:  Factlink.Global.t.unfollow.capitalize()
+    enabled_label:  Factlink.Global.t.following.capitalize()

   buttonEnabled: ->
     @model.get('followed?')


prettify geojsons

prettify geojsons

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -81,7 +81,7 @@
 geojson  = require 'gulp-geojson'
 prettify = require 'gulp-jsbeautifier'
 rename   = require 'gulp-rename'
-
+_        = require 'underscore'
 # list of NP
 NPs = require './NPs.json'

@@ -141,5 +141,6 @@
                                 filepath.extname = '.geojson'
                             .pipe gulp.dest base + 'geojson'

+
     for name, url of NPs.urllist
         pushToGulpStream NPs.base + url


Update gulpfile to compile Jade files in subfolders

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -8,6 +8,10 @@
 	.pipe jade()
 	.pipe gulp.dest '.'

+	gulp.src './**/index.jade'
+	.pipe jade()
+	.pipe gulp.dest '.'
+
 	gulp.src './*.styl'
 	.pipe stylus()
 	.pipe gulp.dest '.'


modify: gulpfile.coffee :)

modify: gulpfile.coffee :)

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -35,6 +35,7 @@
 appConfig = require './src/app.config.json' # サイト共通設定
 update = do require './tasks/script/getTime' # 現在日時取得
 timestamp = do require './tasks/script/getTimeStamp' # 現在タイムスタンプ取得
+postCSSSortingConfig = './src/postcss-sorting.json' # PostCSS の ソート設定

 #------------------------------------------------------
 # Development & Production Environment Branch processing
@@ -391,7 +392,7 @@
     )
     require('css-mqpacker')
     require('postcss-sorting')(
-      require './src/postcss-sorting.json' # 並び順の設定ファイル
+      require postCSSSortingConfig # 並び順の設定ファイル
     )
   ]).on('error', $.util.log) # エラーでも止めない
   .pipe $.autoprefixer overrideBrowserslist: ['> 0%']
@@ -512,7 +513,7 @@
     )
     require('css-mqpacker')
     require('postcss-sorting')(
-      require './src/postcss-sorting.json' # 並び順の設定ファイル
+      require postCSSSortingConfig # 並び順の設定ファイル
     )
   ]).on('error', $.util.log) # エラーでも止めない
   .pipe $.autoprefixer overrideBrowserslist: ['> 0%']
@@ -632,7 +633,7 @@
     )
     require('css-mqpacker')
     require('postcss-sorting')(
-      require './src/postcss-sorting.json' # 並び順の設定ファイル
+      require postCSSSortingConfig # 並び順の設定ファイル
     )
   ]).on('error', $.util.log) # エラーでも止めない
   .pipe $.autoprefixer overrideBrowserslist: ['> 0%']
@@ -782,80 +783,9 @@
       console.log '[\u001b[32mAPI Mock Server\u001b[0m] Serving files from: \u001b[35msrc' + paths.api.dest + '\u001b[0m'
     return

-#------------------------------------------------------
-# Monitoring task
-# 監視タスク
-#------------------------------------------------------
-
-# json-server watch & refresh
-api = ->
-  src paths.api.watch
-  .pipe apiServer.pipe()
-
-apiWatch = ->
-  watch paths.api.watch, series api # json ファイルが変更または追加されたらビルド出力
-
-# watch
-watchCommon = ->
-  watch paths.common.import.json, series importData
-  watch [paths.common.js.plugin, paths.common.js.javascript, paths.common.js.coffee], series coffeeCompile
-  watch paths.common.img.src, series imgCheck, imgCompile # img ファイルが変更または追加されたらビルド出力
-
-# watch rp
-watchRP = ->
-  watch [paths.rp.ect.watch, paths.rp.ect.json], series ectRP
-  watch paths.rp.css.watch, series cssRP
-  watch [paths.rp.js.plugin, paths.rp.js.javascript, paths.rp.js.coffee], series coffeeRP
-  watch paths.rp.img.src, series imgCheckRP, imgCompileRP # img ファイルが変更または追加されたらビルド出力
-
-# watch pc
-watchPC = ->
-  watch [paths.pc.ect.watch, paths.pc.ect.json], series ectPC
-  watch paths.pc.css.watch, series cssPC
-  watch [paths.pc.js.plugin, paths.pc.js.javascript, paths.pc.js.coffee], series coffeePC
-  watch paths.pc.img.src, series imgCheckPC, imgCompilePC # img ファイルが変更または追加されたらビルド出力
-
-# watch sp
-watchSP = ->
-  watch [paths.sp.ect.watch, paths.sp.ect.json], series ectSP
-  watch paths.sp.css.watch, series cssSP
-  watch [paths.sp.js.plugin, paths.sp.js.javascript, paths.sp.js.coffee], series coffeeSP
-  watch paths.sp.img.src, series imgCheckSP, imgCompileSP # img ファイルが変更または追加されたらビルド出力
-
-#------------------------------------------------------
-# Declaring Each Task
-# 各タスクの宣言
-#------------------------------------------------------
-
-# Responsive Task
-exports.buildRP = series libCopy, coffeeCompile, imgCheck, imgCompile, coffeeRP, imgCheckRP, imgCompileRP, ectRP, cssRP, removeFiles, importData
-
-# PC Task
-exports.buildPC = series libCopy, coffeeCompile, imgCheck, imgCompile, coffeePC, imgCheckPC, imgCompilePC, ectPC, cssPC, removeFiles, importData
-
-# SP Task
-exports.buildSP = series libCopy, coffeeCompile, imgCheck, imgCompile, coffeeSP, imgCheckSP, imgCompileSP, ectSP, cssSP, removeFiles, importData
-
-# Other Task
-exports.clean = clean
-exports.cleanSP = cleanSP
-exports.cleanArchive = series cleanTemp, cleanArchive
-
-# Differential Task
-exports.import = importData
-exports.export = exportData
-if appConfig.RESPONSIVE_TEMPLATE
-  exports.diff = series clean, cleanTemp, importData, libCopy, coffeeCompile, imgCheck, imgCompile, ectRP, cssRP, coffeeRP, imgCheckRP, imgCompileRP, tempData
-else
-  exports.diff = series clean, cleanTemp, importData, libCopy, coffeeCompile, imgCheck, imgCompile, ectPC, cssPC, coffeePC, imgCheckPC, imgCompilePC, ectSP, cssSP, coffeeSP, imgCheckSP, imgCompileSP, tempData
-
-# Monitoring Task
-exports.watchRP = watchRP
-exports.watchPC = watchPC
-exports.watchSP = watchSP
-
-# Local server Task
-if appConfig.API_SERVER
+# API Server
+apiServer = undefined
+apiServerInit = ->
   apiServer = jsonServer.create {
     port: paths.api.port,
     baseUrl: paths.api.dest,
@@ -866,20 +796,96 @@
     }
   }

-# Default task
+#------------------------------------------------------
+# Monitoring task
+# 監視タスク
+#------------------------------------------------------
+
+# json-server watch & refresh
+api = ->
+  src paths.api.watch
+  .pipe apiServer.pipe()
+
+apiWatch = ->
+  watch paths.api.watch, series api # json ファイルが変更または追加されたらビルド出力
+
+# watch
+watchCommon = ->
+  watch paths.common.import.json, series importData
+  watch [paths.common.js.plugin, paths.common.js.javascript, paths.common.js.coffee], series coffeeCompile
+  watch paths.common.img.src, series imgCheck, imgCompile # img ファイルが変更または追加されたらビルド出力
+
+# watch rp
+watchRP = ->
+  watch [paths.rp.ect.watch, paths.rp.ect.json], series ectRP
+  watch paths.rp.css.watch, series cssRP
+  watch [paths.rp.js.plugin, paths.rp.js.javascript, paths.rp.js.coffee], series coffeeRP
+  watch paths.rp.img.src, series imgCheckRP, imgCompileRP # img ファイルが変更または追加されたらビルド出力
+
+# watch pc
+watchPC = ->
+  watch [paths.pc.ect.watch, paths.pc.ect.json], series ectPC
+  watch paths.pc.css.watch, series cssPC
+  watch [paths.pc.js.plugin, paths.pc.js.javascript, paths.pc.js.coffee], series coffeePC
+  watch paths.pc.img.src, series imgCheckPC, imgCompilePC # img ファイルが変更または追加されたらビルド出力
+
+# watch sp
+watchSP = ->
+  watch [paths.sp.ect.watch, paths.sp.ect.json], series ectSP
+  watch paths.sp.css.watch, series cssSP
+  watch [paths.sp.js.plugin, paths.sp.js.javascript, paths.sp.js.coffee], series coffeeSP
+  watch paths.sp.img.src, series imgCheckSP, imgCompileSP # img ファイルが変更または追加されたらビルド出力
+
+#------------------------------------------------------
+# Declaring Each Task
+# 各タスクの宣言
+#------------------------------------------------------
+
+# Build Task - Responsive
+exports.buildRP = series libCopy, coffeeCompile, imgCheck, imgCompile, coffeeRP, imgCheckRP, imgCompileRP, ectRP, cssRP, removeFiles, importData
+
+# Build Task - PC
+exports.buildPC = series libCopy, coffeeCompile, imgCheck, imgCompile, coffeePC, imgCheckPC, imgCompilePC, ectPC, cssPC, removeFiles, importData
+
+# Build Task - SP
+exports.buildSP = series libCopy, coffeeCompile, imgCheck, imgCompile, coffeeSP, imgCheckSP, imgCompileSP, ectSP, cssSP, removeFiles, importData
+
+# Clean Task
+exports.clean = clean
+exports.cleanSP = cleanSP
+exports.cleanArchive = series cleanTemp, cleanArchive
+
+# Import / Export Task
+exports.import = importData
+exports.export = exportData
+
+# Watch Task
+exports.watchRP = watchRP
+exports.watchPC = watchPC
+exports.watchSP = watchSP
+
+# Static / Responsive Switch
 if appConfig.RESPONSIVE_TEMPLATE
-  # build
+  # All Build Task
   exports.build = series libCopy, coffeeCompile, imgCheck, imgCompile, coffeeRP, imgCheckRP, imgCompileRP, ectRP, cssRP, removeFiles, importData
-  # api
+  # diff Task
+  exports.diff = series clean, cleanTemp, importData, libCopy, coffeeCompile, imgCheck, imgCompile, ectRP, cssRP, coffeeRP, imgCheckRP, imgCompileRP, tempData
+  # Default Task
   if appConfig.API_SERVER
-   exports.default = parallel browserSync, watchRP, watchCommon, api, apiWatch
+    # API Server
+    exports.default = parallel browserSync, watchRP, watchCommon, apiServerInit, api, apiWatch
   else
-   exports.default = parallel browserSync, watchRP, watchCommon
+    # Normal Server
+    exports.default = parallel browserSync, watchRP, watchCommon
 else
-  # build
+  # All Build Task
   exports.build = series libCopy, coffeeCompile, coffeePC, imgCheckPC, imgCompilePC, coffeeSP, imgCheckSP, imgCompileSP, imgCheck, imgCompile, ectPC, cssPC, ectSP, cssSP, removeFiles, importData
-  # api
+  # diff Task
+  exports.diff = series clean, cleanTemp, importData, libCopy, coffeeCompile, imgCheck, imgCompile, ectPC, cssPC, coffeePC, imgCheckPC, imgCompilePC, ectSP, cssSP, coffeeSP, imgCheckSP, imgCompileSP, tempData
+  # Default Task
   if appConfig.API_SERVER
-    exports.default = parallel browserSync, watchPC, watchSP, watchCommon, api, apiWatch
+    # API Server
+    exports.default = parallel browserSync, watchPC, watchSP, watchCommon, apiServerInit, api, apiWatch
   else
+    # Normal Server
     exports.default = parallel browserSync, watchPC, watchSP, watchCommon


Clean up gulpfile.coffee

Clean up gulpfile.coffee

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -9,23 +9,25 @@
 argv = require('yargs').argv

 gulp.task 'default', ['coffee']
+gulp.task 'publish', ['publish:tag', 'publish:git', 'publish:npm']
+gulp.task 'build', ['test', 'coffee']

 gulp.task 'coffee', ->
   gulp.src './src/**/*.coffee'
     .pipe coffee
       bare: true
-    .on('error', gutil.log)
-    .pipe gulp.dest('./build/')
+    .on 'error', gutil.log
+    .pipe gulp.dest './build/'

 gulp.task 'watch', ->
   watch glob: './src/**/*.coffee', ['coffee']

 gulp.task 'test', ->
-   gulp.src(['test/test.coffee', 'test/**/*.coffee'])
-    .pipe(mocha(
+   gulp.src ['test/test.coffee', 'test/**/*.coffee']
+    .pipe mocha
       compilers: 'coffee:coffee-script/register',
       globals: ['chai', 'expect', 'sinon', 'requireFromSrc']
-    )).on('error', gutil.log)
+    .on 'error', gutil.log

 gulp.task 'clean', ->
   gulp.src ['build', 'dist'], read: false
@@ -33,14 +35,14 @@

 gulp.task 'bump', ->
   gulp.src './package.json'
-    .pipe bump type: (argv.type or 'patch')
-    .pipe gulp.dest('./')
+    .pipe bump type:  argv.type or 'patch'
+    .pipe gulp.dest './'

 gulp.task 'version', ->
-  return console.error("You must provide a version with the --to flag") unless argv.to
+  return console.error "You must provide a version with the --to flag" unless argv.to
   gulp.src './package.json'
     .pipe bump version: argv.to
-    .pipe gulp.dest('./')
+    .pipe gulp.dest './'

 gulp.task 'publish:npm', (cb) ->
   exec "npm publish", (err, stdout, stderr) ->
@@ -60,9 +62,3 @@
     console.log stdout
     console.log stderr
     cb err
-
-gulp.task 'publish', ['publish:tag', 'publish:git', 'publish:npm']
-
-gulp.task 'build', ['test', 'coffee']
-
-


credentials at root

credentials at root

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -61,7 +61,7 @@
 		.pipe gulp.dest "./dist/#{version.major}.#{version.minor}"

 gulp.task 'vtex_deploy', (cb) ->
-	credentials = require './credentials.json'
+	credentials = require '/credentials.json'
 	credentials.bucket = 'vtex-io'
 	client = knox.createClient credentials
 	deployer = new Deploy(pkg, client, dryrun: false)


fixed

fixed

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -19,10 +19,11 @@

 banner = [
            "/**"
-           " * generator-git - Create the basic scaffolding to start with a git proyect."
-           " * @version v2.0.2"
-           " * @link    https://github.com/Kikobeats/generator-git"
-           " * @license MIT"
+           " * <%= pkg.name %> - <%= pkg.description %>"
+           " * @version v<%= pkg.version %>"
+           " * @link    <%= pkg.homepage %>"
+           " * @author  <%= pkg.author.name %> (<%= pkg.author.url %>)"
+           " * @license <%= pkg.license %>"
            " */"].join("\n")

 # -- Tasks ---------------------------------------------------------------------


save+find per boundedcontext instead of aggregate

--- src/mongodb_event_store.coffee
+++ src/mongodb_event_store.coffee
@@ -17,7 +17,7 @@

   save: (domainEvent, callback) ->
-    @db.collection domainEvent.aggregate.name, (err, collection) ->
+    @db.collection domainEvent.context, (err, collection) ->
       return callback err, null if err

       collection.insert domainEvent, (err, doc) ->
@@ -26,14 +26,14 @@
         callback null

-  find: ([aggregateName, query, projection]..., callback) ->
+  find: ([contextName, query, projection]..., callback) ->
     if not query
       err = new Error 'Missing query'
       callback err, null
       return
     projection = {} unless projection

-    @db.collection aggregateName, (err, collection) =>
+    @db.collection contextName, (err, collection) =>
       return callback err, null if err

       collection.find query, projection, (err, cursor) =>


Manage the active/non active class toggle ourselves, not bootstrap

--- backbone.tableview.coffee
+++ backbone.tableview.coffee
@@ -329,7 +329,7 @@

 class ButtonFilter extends Filter
     template: _.template """
-        <button type="button" class="filter btn <%= init == on ? "active" : "" %> <%= filterClass %>" data-toggle="button"><%= name %></button>
+        <button type="button" class="filter btn <%= init == on ? "active" : "" %> <%= filterClass %>"><%= name %></button>
     """
     events:
         "click .filter": "update"
@@ -339,7 +339,8 @@
         @values = [@options.off, @options.on]
         @current = if @options.init == @options.off then 0 else 1

-    update: =>
+    update: (e) =>
+        $(e.currentTarget, @$el).toggleClass "active"
         @current = 1 - @current
         @setData @id, @values[@current]


Update app.coffee

Update app.coffee

--- express/sockets/app.coffee
+++ express/sockets/app.coffee
@@ -3,4 +3,4 @@
         control.emit '/app/connection', socket
         socket.on 'initialize', ->
             control.emit '/app/initialize', socket
-            socket.emit 'msg', 'Welcome to Express Brunch with Jade...'+            socket.emit 'msg', 'Welcome to Brunch with Ember and Express...'


Create builder in beforeEach

Create builder in beforeEach

--- spec/builder_spec.coffee
+++ spec/builder_spec.coffee
@@ -1,7 +1,13 @@
 Builder = require '../src/builder'

-describe 'JUnit XML builder', ->
-  builder = new Builder()
+
+describe 'Report builder', ->
+  builder = null
+
+
+  beforeEach ->
+    builder = new Builder()
+

   it 'should should produce an empty test suite when nothing reported', ->
     expect(builder.build()).toBe(


Add breaking test for emoji in JavaScript strings

Test for https://github.com/atom/atom/issues/9153

--- spec/grammar-spec.coffee
+++ spec/grammar-spec.coffee
@@ -800,6 +800,31 @@
           expect(tokens[6].value).toBe '1'
           expect(tokens[6].scopes).toEqual ["source.json", "meta.structure.dictionary.json", "meta.structure.dictionary.value.json", "constant.numeric.json"]

+      describe "when the line contains emoji characters", ->
+
+        it "correctly terminates quotes & parses tokens starting after them", ->
+          grammar = registry.grammarForScopeName('source.js')
+
+          withoutEmoji = grammar.tokenizeLine "var emoji = 'http://a'; var after;"
+          withoutEmojiTokens = registry.decodeTokens(withoutEmoji.line, withoutEmoji.tags)
+
+          withEmoji = grammar.tokenizeLine "var emoji = '💻 http://a'; var after;"
+          withEmojiTokens = registry.decodeTokens(withEmoji.line, withEmoji.tags)
+
+          # ignoring this value (the string containing the emoji), they should be identical
+          withoutEmojiTokens[5].value = 'ABC'
+          withEmojiTokens[5].value = 'ABC'
+
+          expect(withEmojiTokens).toEqual(withoutEmojiTokens)
+
+          expect(withoutEmojiTokens.length).toBe 12
+          expect(withoutEmojiTokens[7].value).toBe ';'
+          expect(withoutEmojiTokens[7].scopes).toEqual [ 'source.js', 'punctuation.terminator.statement.js' ]
+
+          expect(withEmojiTokens.length).toBe 12
+          expect(withEmojiTokens[7].value).toBe ';'
+          expect(withEmojiTokens[7].scopes).toEqual [ 'source.js', 'punctuation.terminator.statement.js' ]
+
     describe "python", ->
       it "parses import blocks correctly", ->
         grammar = registry.grammarForScopeName('source.python')


Strip out additional characters from search query, but replace with spaces to avoid accidentally concatenating separate search terms

--- src/hubot-confluence.coffee
+++ src/hubot-confluence.coffee
@@ -81,7 +81,7 @@
     Authorization: "Basic #{auth}"

 clean_search = (query) ->
-  query = query.replace('?', '')
+  query = query.replace(/[!?,.]/g, ' ')

 make_url = (suffix, api) ->
   host = nconf.get("HUBOT_CONFLUENCE_HOST")


Use CoffeeScript for instead of JS forEach for Property.eachObserver

--- src/observable/property.coffee
+++ src/observable/property.coffee
@@ -71,13 +71,13 @@
     accessor
   eachObserver: (iterator) ->
     key = @key
-    @changeEvent().handlers.slice().forEach(iterator)
+    iterator(object) for object in @changeEvent().handlers.slice()
     if @base.isObservable
       @base._batman.ancestors (ancestor) ->
         if ancestor.isObservable and ancestor.hasProperty(key)
           property = ancestor.property(key)
           handlers = property.changeEvent().handlers
-          handlers.slice().forEach(iterator)
+          iterator(object) for object in handlers.slice()
   observers: ->
     results = []
     @eachObserver (observer) -> results.push(observer)


Add tests directory to root paths

Add tests directory to root paths

Enables tests to require 'test/helpers'

--- test/config/karma.common.coffee
+++ test/config/karma.common.coffee
@@ -19,7 +19,7 @@
   webpack:
     devtool: 'eval-source-map'
     resolve:
-      root: [ path.resolve(__dirname, '../../src') ]
+      root: [ path.resolve(__dirname, '../../src'), path.resolve(__dirname, '../..') ]
       extensions: ['', '.js', '.json', '.coffee', '.cjsx']
     module:
       noParse: [


Reordered code.

Reordered code.

--- server/publication.coffee
+++ server/publication.coffee
@@ -250,78 +250,6 @@
         library:
           _id: publication._id

-Meteor.publish 'publications-by-author-slug', (slug) ->
-  check slug, String
-
-  return unless slug
-
-  author = Persons.findOne
-    slug: slug
-
-  return unless author
-
-  # TODO: Make this reactive
-  person = Persons.findOne
-    _id: @personId
-  ,
-    library: 1
-
-  Publications.find
-    'authors._id': author._id
-    $or: [
-      processed: true
-    ,
-      _id:
-        $in: _.pluck person?.library, '_id'
-    ]
-  ,
-    Publication.PUBLIC_FIELDS()
-
-Meteor.publish 'publications-by-id', (id) ->
-  check id, String
-
-  return unless id
-
-  # TODO: Make this reactive
-  person = Persons.findOne
-    _id: @personId
-  ,
-    library: 1
-
-  Publications.find
-    _id: id
-    $or: [
-      processed: true
-    ,
-      _id:
-        $in: _.pluck person?.library, '_id'
-    ]
-  ,
-    Publication.PUBLIC_FIELDS()
-
-Meteor.publish 'publications-by-ids', (ids) ->
-  check ids, [String]
-
-  return unless ids?.length
-
-  # TODO: Make this reactive
-  person = Persons.findOne
-    _id: @personId
-  ,
-    library: 1
-
-  Publications.find
-    _id:
-      $in: ids
-    $or: [
-      processed: true
-    ,
-      _id:
-        $in: _.pluck person?.library, '_id'
-    ]
-  ,
-    Publication.PUBLIC_FIELDS()
-
 # TODO: Should we use try/except around the code so that if there is any exception we stop handlers?
 publishUsingMyLibrary = (publish, selector, options) ->
   # There are moments when two observes are observing mostly similar list
@@ -405,6 +333,78 @@
     handlePersons.stop() if handlePersons
     handlePublications.stop() if handlePublications

+Meteor.publish 'publications-by-author-slug', (slug) ->
+  check slug, String
+
+  return unless slug
+
+  author = Persons.findOne
+    slug: slug
+
+  return unless author
+
+  # TODO: Make this reactive
+  person = Persons.findOne
+    _id: @personId
+  ,
+    library: 1
+
+  Publications.find
+    'authors._id': author._id
+    $or: [
+      processed: true
+    ,
+      _id:
+        $in: _.pluck person?.library, '_id'
+    ]
+  ,
+    Publication.PUBLIC_FIELDS()
+
+Meteor.publish 'publications-by-id', (id) ->
+  check id, String
+
+  return unless id
+
+  # TODO: Make this reactive
+  person = Persons.findOne
+    _id: @personId
+  ,
+    library: 1
+
+  Publications.find
+    _id: id
+    $or: [
+      processed: true
+    ,
+      _id:
+        $in: _.pluck person?.library, '_id'
+    ]
+  ,
+    Publication.PUBLIC_FIELDS()
+
+Meteor.publish 'publications-by-ids', (ids) ->
+  check ids, [String]
+
+  return unless ids?.length
+
+  # TODO: Make this reactive
+  person = Persons.findOne
+    _id: @personId
+  ,
+    library: 1
+
+  Publications.find
+    _id:
+      $in: ids
+    $or: [
+      processed: true
+    ,
+      _id:
+        $in: _.pluck person?.library, '_id'
+    ]
+  ,
+    Publication.PUBLIC_FIELDS()
+
 Meteor.publish 'my-publications', ->
   publishUsingMyLibrary @, (library) =>
     _id:


Removed unused publish endpoint.

Removed unused publish endpoint.

--- server/publication.coffee
+++ server/publication.coffee
@@ -524,55 +524,6 @@
         inGroups: 1
         library: 1

-Meteor.publish 'publications-by-ids', (ids) ->
-  check ids, [String]
-
-  return unless ids?.length
-
-  @related (person) =>
-    if person?.isAdmin
-      Publication.documents.find
-        _id:
-          $in: ids
-        cached:
-          $exists: true
-      ,
-        Publication.PUBLIC_FIELDS()
-    else
-      Publication.documents.find
-        _id:
-          $in: ids
-        cached:
-          $exists: true
-        $or: [
-          processed:
-            $exists: true
-          $or: [
-            access: Publication.ACCESS.OPEN
-          ,
-            access: Publication.ACCESS.PRIVATE
-            'readUsers._id': @personId
-          ,
-            access: Publication.ACCESS.PRIVATE
-            'readGroups._id':
-              $in: _.pluck person?.inGroups, '_id'
-          ]
-        ,
-          _id:
-            $in: _.pluck person?.library, '_id'
-        ]
-      ,
-        Publication.PUBLIC_FIELDS()
-  ,
-    Person.documents.find
-      _id: @personId
-    ,
-      fields:
-        # _id field is implicitly added
-        isAdmin: 1
-        inGroups: 1
-        library: 1
-
 Meteor.publish 'my-publications', ->
   @related (person) =>
     Publication.documents.find


fix css class for app installation spinner

--- plugins/040-apps/app/assets/javascripts/apps.js.coffee
+++ plugins/040-apps/app/assets/javascripts/apps.js.coffee
@@ -25,7 +25,7 @@

 	toggle_spinner: (finder) ->
 		app = @app(finder)
-		app.find(".spinner-installation").toggle()
+		app.find(".spinner").toggle()

 	progress: (finder) ->
 		@app(finder).find ".progress:first"


add console flash js autohide

add console flash js autohide

--- app/assets/javascripts/folio/console/_flash.coffee
+++ app/assets/javascripts/folio/console/_flash.coffee
@@ -1,6 +1,6 @@
 window.FolioConsole ||= {}

-window.FolioConsole.flash = (msg, type = 'success') ->
+window.FolioConsole.flash = (msg, type = 'success', autohide = false) ->
   if type is 'success'
     className = 'alert-success'
     icon = 'fa fa-mr fa-check-circle'
@@ -8,13 +8,19 @@
     className = 'alert-danger'
     icon = 'fa fa-mr fa-times-circle'

-  $('.f-c-flash-wrap').append("""
+  $flash = $("""

       <button class="close" data-dismiss="alert">×</button>

       #{msg}

   """)
+  $('.f-c-flash-wrap').append $flash
+
+  if autohide
+    setTimeout((->
+      $flash.find('[data-dismiss]').click()
+    ), if typeof autohide is "number" then autohide else 5000)

 window.FolioConsole.clearFlashes = ->
   $('.f-c-flash-wrap').html('')


change the way the data attrs are removed from the snippets

--- app/assets/javascripts/mercury/regions/full.coffee
+++ app/assets/javascripts/mercury/regions/full.coffee
@@ -252,7 +252,10 @@
         element.html("[#{element.data("snippet")}/#{element.data("version")}]")
         element.attr({contenteditable: null, 'data-version': null})
         if saving && Mercury.config.serverParser.radius
-          element.attr({'class': null, 'data-snippet': null})
+          element.attr({'class': null})
+          for attr in element[0].attributes
+            if attr.specified && /^data-/.test(attr.name)
+              element.removeAttr(attr.name)

       # get the html before removing the markers
       content = container.html()


Use the entire user object not userid

Use the entire user object not userid

--- test/routes/time_entries.coffee
+++ test/routes/time_entries.coffee
@@ -15,12 +15,12 @@
 testProject = undefined
 timeEntriesCreated = []

-makeProject = (userId, cb)->
-  ops = options()
+makeProject = (user, cb)->
+  ops = options user
   ops.body =
     name: 'test project'
     createdDate: new Date()
-    userId: userId
+    userId: user.id
   request.post (base '/projects'), ops, (e,r,b)->
     cb b

@@ -29,7 +29,7 @@
     startApp ->
       user_test_helper.makeUser (user)->
         testUser = user
-        makeProject user.id, (project)->
+        makeProject user, (project)->
           testProject = project
           done()

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

     it 'should delete time entries if project is deleted', (done) ->
-      makeProject testUser.id, (p) ->
+      makeProject testUser, (p) ->
         makeTimeEntry = (seed, cb)->
           ops = options()
           ops.body =


Fix undefined error in sign-out route

Fix undefined error in sign-out route

--- router.coffee
+++ router.coffee
@@ -20,7 +20,7 @@
   @route 'entrySignOut',
     path: '/sign-out'
     before: ->
-      if AccountsEntry.config.homeRoute
+      if Session.get('entrySettings').homeRoute
         Meteor.logout()
-        Router.go AccountsEntry.config.homeRoute
+        Router.go Session.get('entrySettings').homeRoute
       @stop()


Fix bug where every blog author was 'Mystery author' on blog index page

--- router.coffee
+++ router.coffee
@@ -6,7 +6,8 @@
       if Blog.settings.blogIndexTemplate
         @template = Blog.settings.blogIndexTemplate
     waitOn: ->
-      Meteor.subscribe 'posts'
+      [ Meteor.subscribe 'posts'
+        Meteor.subscribe 'users' ]
     data: ->
       posts: Post.where published: true

@@ -26,7 +27,8 @@
   @route 'blogAdmin',
     path: '/admin/blog'
     waitOn: ->
-      Meteor.subscribe 'posts'
+      [ Meteor.subscribe 'posts'
+        Meteor.subscribe 'users' ]
     before: ->
       if Meteor.loggingIn()
         return @stop()
@@ -46,7 +48,8 @@
   @route 'blogAdminEdit',
     path: '/admin/blog/edit/:slug'
     waitOn: ->
-      Meteor.subscribe 'posts'
+      [ Meteor.subscribe 'posts'
+        Meteor.subscribe 'users' ]
     data: ->
       Post.first slug: @params.slug
     before: ->


test(tests/unit/add-tests-test.coffee): Add minor checks to last two tests

Last two tests were not verifying callback was called and test was added.

--- test/unit/add-tests-test.coffee
+++ test/unit/add-tests-test.coffee
@@ -488,6 +488,12 @@
       after () ->
         tests = []

+      it 'should run callback', () ->
+        assert.ok callback.called
+
+      it 'should add 1 test', () ->
+        assert.lengthOf tests, 1
+
       it 'should append query parameters with example value', () ->
         assert.equal tests[0].request.query['quux'], 'foo'

@@ -514,6 +520,12 @@
       after () ->
         tests = []

+      it 'should run callback', () ->
+        assert.ok callback.called
+
+      it 'should add 1 test', () ->
+        assert.lengthOf tests, 1
+
       it 'should not append query parameters', () ->
         assert.deepEqual tests[0].request.query, {}


Fix numbers in text.coffee

Fix numbers in text.coffee

--- lib/mixins/text.coffee
+++ lib/mixins/text.coffee
@@ -223,7 +223,7 @@
     @addContent "BT"

     # text position
-    @addContent "1 0 0 1 #{x} #{y} Tm"
+    @addContent "1 0 0 1 #{@number(x)} #{@number(y)} Tm"

     # font and font size
     @addContent "/#{@_font.id} #{@_fontSize} Tf"
@@ -233,7 +233,7 @@
     @addContent "#{mode} Tr" if mode

     # Character spacing
-    @addContent "#{characterSpacing} Tc" if characterSpacing
+    @addContent "#{@number(characterSpacing)} Tc" if characterSpacing

     # Add the actual text
     # If we have a word spacing value, we need to encode each word separately
@@ -266,7 +266,7 @@
       if last < cur
         hex = encoded.slice(last, cur).join ''
         advance = positions[cur - 1].xAdvance - positions[cur - 1].advanceWidth
-        commands.push "<#{hex}> #{-advance}"
+        commands.push "<#{hex}> #{@number(-advance)}"

       last = cur

@@ -286,14 +286,14 @@
         flush i

         # Move the text position and flush just the current character
-        @addContent "1 0 0 1 #{x + pos.xOffset * scale} #{y + pos.yOffset * scale} Tm"
+        @addContent "1 0 0 1 #{@number(x + pos.xOffset * scale)} #{@number(y + pos.yOffset * scale)} Tm"
         flush i + 1

         hadOffset = yes
       else
         # If the last character had an offset, reset the text position
         if hadOffset
-          @addContent "1 0 0 1 #{x} #{y} Tm"
+          @addContent "1 0 0 1 #{@number(x)} #{@number(y)} Tm"
           hadOffset = no

         # Group segments that don't have any advance adjustments


preliminary error handling when FITS does not return 200

--- app/controllers/viewer.coffee
+++ app/controllers/viewer.coffee
@@ -5,19 +5,11 @@

 class Viewer extends Controller
   dimension: 441
-
   bands:  ['g', 'r', 'i']
-  # TODO: Update when FITS images find permanent home
   source: 'http://spacewarps.org.s3.amazonaws.com/subjects/raw/'

-  # Default parameter values
-  defaultAlpha: 0.09
-  defaultQ: 1.7
-  defaultScales: [0.4, 0.6, 1.7]
-
   cache: {}

-  # TODO: Get three presets from science team
   parameters:
     0:
       alpha: 0.09
@@ -87,14 +79,19 @@
     for band in @bands
       @dfs[band] = new $.Deferred()

+    # Create deferred and set up callback for error handling
+    errDfd = new $.Deferred()
+
+    $.when(errDfd)
+      .done(@onError)
+
     # Set callback for when all channels and astrojs libraries received
     dfs = $.map(@dfs, (v, k) -> v)
     $.when.apply(null, dfs)
       .done(@allChannelsReceived)

-    # Check cache for data
-    if prefix of @cache
-      cache = @cache[prefix]
+    if 'prefix' of @cache and Object.keys(@cache.prefix).length > 0
+      cache = @cache['prefix']

       # Load files from cache
       for band, index in @bands
@@ -110,13 +107,18 @@
           @dfs[band].resolve()
     else
       # Request from remote source
-      @cache[prefix] = {}
+      @cache['prefix'] = {}
       for band, index in @bands
         do (band, index) =>
-          @cache[prefix][band] = {}
+
           path = "#{@source}#{prefix}_#{band}.fits.fz"
-
           new astro.FITS.File(path, (fits) =>
+            if fits is null
+              errDfd.resolve()
+              return
+
+            @cache['prefix'][band] = {}
+
             hdu = fits.getHDU()
             header = hdu.header
             dataunit = hdu.data
@@ -131,15 +133,18 @@
             @wfits.loadImage(band, arr, width, height)

             # Cache some data
-            @cache[prefix][band].min = min
-            @cache[prefix][band].max = max
-            @cache[prefix][band].arr = arr
-            @cache[prefix][band].width = width
-            @cache[prefix][band].height = height
-            @cache[prefix][band].calibration = calibration
+            @cache['prefix'][band].min = min
+            @cache['prefix'][band].max = max
+            @cache['prefix'][band].arr = arr
+            @cache['prefix'][band].width = width
+            @cache['prefix'][band].height = height
+            @cache['prefix'][band].calibration = calibration

             @dfs[band].resolve()
           )
+
+  onError: =>
+    @trigger 'close'

   # NOTE: Using exposure time = 1.0
   getCalibration: (header) ->


ext/table: Use native KineticJS code to find mouse position.

--- oghma/ext/table.coffee
+++ oghma/ext/table.coffee
@@ -26,9 +26,6 @@
   # Funcion to apply on next click to stage.
   dropper: null

-  # Last location of mouse in table coordinates.
-  mouse: null
-
   # The underlying keymap for keyboard bindings.  Use {#bind} instead.
   keymap: null

@@ -42,15 +39,17 @@
         @apply_dropper( @tX( e.getX() ), @tY( e.getY() ), e )
         null
       )
-
-      @getEl().on( 'mousemove', ( e ) =>
-        @mouse = [ @tX( e.getX() ), @tY( e.getY() ) ]
-      )
     )

     @keymap = Ext.create( 'Ext.util.KeyMap', target: Ext.getBody() )

     null
+
+  # Mouse position in table coordinates.
+  mouse: ->
+    pos = @stage.getMousePosition()
+    if pos?
+      [ tX( pos.x ), tY( pos.y ) ]

   # Convert client X to table X:
   tX: ( x ) ->
@@ -113,7 +112,7 @@
     bound_handler = binding.handler
     binding.handler = ( args... ) =>
       if document.activeElement == document.body
-        pass = [ @mouse..., args... ]
+        pass = [ @mouse()..., args... ]
         bound_handler.apply( binding.scope, pass )
     @keymap.addBinding( binding )
     this


Preload language-ruby's main module during snapshotting

--- src/initialize-application-window.coffee
+++ src/initialize-application-window.coffee
@@ -38,6 +38,7 @@
   require('keybinding-resolver')
   require('language-html')
   require('language-javascript')
+  require('language-ruby')
   require('line-ending-selector')
   require('link')
   require('markdown-preview')


S3 sync now has a progress indicator.

S3 sync now has a progress indicator.

--- src/publish/bucket/sync.coffee
+++ src/publish/bucket/sync.coffee
@@ -1,7 +1,12 @@
+readline = require "readline"
+rl = readline.createInterface
+  input: process.stdin
+  output: process.stdout
+
 {createReadStream} = require "fs"
 {join} = require "path"

-{async} = require "fairmont"
+{async, cat} = require "fairmont"
 mime = require "mime"

 config = require "../../configuration"
@@ -12,6 +17,28 @@

   async ({dlist, ulist}) ->
     console.log "Syncing S3 bucket."
+    total = (cat dlist, ulist).length
+    currentIndex = 0
+
+    printProgress = ->
+      progress = 100 * currentIndex / total
+      if progress < 1
+        progress = progress.toPrecision(2)
+      else if progress < 10
+        progress = progress.toPrecision(3)
+      else
+        progress = progress.toPrecision(4)
+
+      rl.write null, {ctrl: true, name: 'u'}
+      rl.write progress + "%"
+      currentIndex++
+      if currentIndex == total
+        rl.write null, {ctrl: true, name: 'u'}
+        rl.write "100%"
+        rl.write "\n"
+        rl.close()
+
+
     # Delete Files
     try
       for file in dlist
@@ -20,6 +47,8 @@
           Key: file

         yield s3.deleteObject params
+        printProgress()
+
     catch e
       console.error "Failed to delete object.", e
       throw new Error()
@@ -49,6 +78,7 @@
           Body: createReadStream join config.target, file

         yield s3.putObject params
+        printProgress()
     catch e
       console.error "Failed to upload object.", e
       throw new Error()


Fix up admin toggle

Fix up admin toggle

--- app/partials/main-footer.cjsx
+++ app/partials/main-footer.cjsx
@@ -27,6 +27,33 @@
       daily: 'Daily Zooniverse'
       blog: 'Blog'

+AdminToggle = React.createClass
+  getInitialState: ->
+    checked: false
+
+  componentDidMount: ->
+    apiClient.listen 'change', @handleClientChange
+
+  componentWillUnmount: ->
+    apiClient.stopListening 'change', @handleClientChange
+
+  handleClientChange: ->
+    checked = apiClient.params.admin ? false
+    unless @state.checked is checked
+      @setState {checked}
+
+  render: ->
+
+      <input type="checkbox" checked={@state.checked} onChange={@toggleAdminMode} />{' '}
+      <Translate content="footer.adminMode" />
+
+
+  toggleAdminMode: (e) ->
+    apiClient.update 'params.admin': if e.target.checked
+      true
+    else
+      undefined
+
 module.exports = React.createClass
   displayName: 'MainFooter'

@@ -35,19 +62,14 @@

   componentDidMount: ->
     auth.listen 'change', @handleAuthChange
-    apiClient.listen 'change', @handleClientChange
     @handleAuthChange()

   componentWillUnmount: ->
     auth.stopListening 'change', @handleAuthChange
-    apiClient.stopListening 'change', @handleClientChange

   handleAuthChange: ->
     auth.checkCurrent().then (user) =>
       @setState {user}
-
-  handleClientChange: ->
-    @forceUpdate()

   render: ->

@@ -58,10 +80,7 @@
           </Link>

           {if @state.user?.admin or @state.user?.id is '3' # brian-testing
-
-              <input type="checkbox" checked={apiClient.params.admin} onChange={@toggleAdminMode} />{' '}
-              <Translate content="footer.adminMode" />
-            }
+            <AdminToggle />}

@@ -92,9 +111,3 @@

-
-  toggleAdminMode: (e) ->
-    apiClient.update 'params.admin': if e.target.checked
-      true
-    else
-      undefined


remove games with ghost players

remove games with ghost players

--- server.coffee
+++ server.coffee
@@ -28,7 +28,7 @@
 removePlayer = (socket, username, reason) ->
   for game, i in games
     for player, j in game.players
-      if player and player.user.username is username
+      if not player.user or player.user.username is username
         game.players.splice(j, 1)
         if reason is "disconnect"
           if game.started
@@ -70,7 +70,7 @@
         lobby.emit('netrunner', {type: "games", games: games, notification: "ting"})

       when "leave"
-        removePlayer(socket, socket.request.user.username)
+        removePlayer(socket, socket.request.user.username, "leave")
         socket.leave(msg.gameid)

       when "quit"


SG QR updated with VICS82 lenses

SG QR updated with VICS82 lenses

--- app/translations/en_us.coffee
+++ app/translations/en_us.coffee
@@ -46,27 +46,27 @@
     lens_examples:
       example_1:
         title: "A distant quasar being lensed by a massive galaxy in the foreground."
-        image: "images/guide/lenses/galaxy-quasar/CFHTLS_082_0427_gri_zoom.png"
+        image: "images/guide/lenses/lensed-quasar/VICS82_7009427_iJKs_zoom.png"
         data_type: "lensed-quasars"
       example_2:
-        title: "An edge-on spiral galaxy acting as a gravitational lens."
-        image: "images/guide/lenses/disk-galaxy/CFHTLS_024_2215_gri_zoom.png"
-        data_type: "lensed-galaxies"
+        title: "A distant blue galaxy being lensed by a group of massive galaxies."
+        image: "images/guide/lenses/lensing-group/VICS82_5008231_iJKs_zoom.png"
+        data_type: "lensing-clusters"
       example_3:
         title: "A group of massive galaxies acting as a gravitational lens."
-        image: "images/guide/lenses/cluster-galaxy/CFHTLS_052_1191_gri_zoom.png"
+        image: "images/guide/lenses/lensing-group/VICS82_5004036_iJKs_zoom.png"
         data_type: "lensing-clusters"
       example_4:
         title: "A distant blue galaxy being lensed by a massive galaxy in the foreground."
-        image: "images/guide/lenses/elliptical-galaxy/CFHTLS_030_2132_gri_zoom.png"
+        image: "images/guide/lenses/lensed-galaxy/VICS82_7008306_iJKs_zoom.png"
         data_type: "lensed-galaxies"
       example_5:
-        title: "A distant blue galaxy being lensed by a massive galaxy in the foreground."
-        image: "images/guide/lenses/elliptical-galaxy/CFHTLS_039_1471_gri_zoom.png"
-        data_type: "lensed-galaxies"
+        title: "A distant quasar being lensed by a massive galaxy in the foreground"
+        image: "images/guide/lenses/lensed-quasar/VICS82_7007297_iJKs_zoom.png"
+        data_type: "lensed-quasars"
       example_6:
         title: "A distant quasar being lensed by a massive galaxy in the foreground."
-        image: "images/guide/lenses/galaxy-quasar/CFHTLS_082_1138_gri_zoom.png"
+        image: "images/guide/lenses/lensed-galaxy/VICS82_7008406_iJKs_zoom.png"
         data_type: "lensed-quasars"

     non_lens_examples:


[Openshift] Listen only on OPENSHIFT_NODEJS_IP, to solve listen EACCES. (http://stackoverflow.com/a/20974952)

--- server.coffee
+++ server.coffee
@@ -2,13 +2,14 @@
 http = require('http')
 assert = require('assert')

-port = process.env.PORT || process.env.OPENSHIFT_NODEJS_PORT || 8080;
+port = process.env.PORT || process.env.OPENSHIFT_NODEJS_PORT || 8080
+interface = process.env.OPENSHIFT_NODEJS_IP || '0.0.0.0'  # INADDR_ANY
 server = http.createServer(st({
   path: process.cwd()
   index: 'index.html'
 }))
 server.on 'request', (req, res) ->
   console.log('[%s] < %s %s', new Date().toISOString(), req.method, req.url)
-server.listen(port)
 server.on 'listening', ->
   console.log('Server up, e.g. http://localhost:' + port + '/?doc=demo');
+server.listen(port, interface)


Use current stable version of Sinon.JS, require browser packaged version when using webpack, fix usage of sinon-chai

--- gulp/webpack_config.coffee
+++ gulp/webpack_config.coffee
@@ -1,3 +1,7 @@
+webpackStream = require 'webpack-stream'
+
+NormalModuleReplacementPlugin = webpackStream.webpack.NormalModuleReplacementPlugin
+
 class WebpackConfig

   getDefaultConfiguration: ->
@@ -5,8 +9,15 @@
       loaders: [
         {test: /\.coffee$/i, loader: 'coffee-loader'}
       ]
+      noParse: [
+        /node_modules\/sinon\/pkg\/.*/
+      ]
+    plugins: [
+      new NormalModuleReplacementPlugin /^sinon$/, process.cwd() + '/node_modules/sinon/pkg/sinon.js'
+    ]
+
     resolve:
       extensions: ['', '.js', '.coffee']

-module.exports = new WebpackConfig+module.exports = new WebpackConfig


editor: fix editor tab pane title set procedure on save as

--- client/Ace/aceappview.coffee
+++ client/Ace/aceappview.coffee
@@ -57,7 +57,7 @@
       ace.focus()
       ace.on "ace.ready", -> ace.focus()
       ace.on "AceDidSaveAs", (name, parentPath) ->
-        pane.tabHandle.setTitle title
+        pane.setTitle name

       title = FSHelper.minimizePath(ace.data.path).replace /^localfile:\//, ''
       pane.tabHandle.setTitle title


Fix JS exception on list page

Fix JS exception on list page

Sometime, Spin pluggin wasn't initialized on page load, triggering an
exception

--- client/app/initialize.coffee
+++ client/app/initialize.coffee
@@ -32,9 +32,8 @@

         ColorHash.addScheme 'cozy', colorSet

-        app.initialize()
-
         # Initialize Spin JS the lib that displays loading indicators
+        # /!\ this must be done before app init to prevent $().spin() to be undefined
         $.fn.spin = (opts, color) ->
             presets =
                 tiny:
@@ -78,6 +77,8 @@
                 console.log "Spinner class not available."
                 null

+        app.initialize()
+
     catch e
         console.error e, e?.stack
         exception = e.toString()


Fix data structure

Fix data structure

--- client/helpers/router.coffee
+++ client/helpers/router.coffee
@@ -5,6 +5,8 @@
   yieldTemplates:
     footer:
       to: "footer"
+
+Router.onBeforeAction 'loading'

 Router.map ->
   @route 'needs',
@@ -26,8 +28,7 @@
     path: '/mine'
     template: 'needs'
     data: ->
-      if Meteor.userId()
-        Needs.find({userId: Meteor.userId()}, sort: {score: -1})
+      needs: Needs.find({userId: Meteor.userId()}, sort: {score: -1})
     waitOn: ->
       Meteor.subscribe 'needs', Session.get('query')
     onBeforeAction: ->
@@ -93,7 +94,7 @@
     path: '/fav'
     template: 'needs'
     data: ->
-      Needs.find ({starUsers: { $in: [Meteor.user()._id] }}  )
+      needs: Needs.find ({starUsers: { $in: [Meteor.user()._id] }}  )
     waitOn: ->
       Meteor.subscribe 'needs', Session.get('strNeeds')
     onBeforeAction: ->


Subscribe to different data depending on if app is private, user is admin vs regular user

--- client/helpers/router.coffee
+++ client/helpers/router.coffee
@@ -15,10 +15,19 @@
       needs: Needs.find({}, {sort: {score: -1}})
       backgrounds: Backgrounds.find()
     waitOn: ->
-      unless Meteor.settings.public.visibility is "private"
-        Meteor.subscribe 'needs', Session.get('query')
-        Meteor.subscribe('offers')
-        Meteor.subscribe('backgrounds')
+      if Meteor.settings["public"].visibility isnt "private"
+        Meteor.subscribe "needs", Session.get("query")
+        Meteor.subscribe "offers"
+        Meteor.subscribe "backgrounds"
+      else
+        if Meteor.user()
+          if Roles.userIsInRole(Meteor.user()._id, ['admin'])
+            Meteor.subscribe "needs", Session.get("query")
+            Meteor.subscribe "offers"
+            Meteor.subscribe "backgrounds"
+          else
+            Meteor.subscribe('userNeeds')
+
     onBeforeAction: ->
       if (Meteor.loggingIn())
         @render 'loading'


Routes: Create Machine instead if it's not.

--- client/ide/lib/routes.coffee
+++ client/ide/lib/routes.coffee
@@ -112,6 +112,9 @@
   latestWorkspace = getLatestWorkspace machine
   workspaceSlug   = 'my-workspace'

+  unless machine instanceof Machine
+    machine = new Machine { machine }
+
   if latestWorkspace
     { machineLabel } = latestWorkspace
     if machineLabel is machine.label


cleanup tmp folder after each production builds

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -251,6 +251,14 @@
                 .on 'response', _.noop
                 .on 'error', _.noop

+        .on 'end', ->
+
+            return unless utils.prod
+
+            $.del {force: true}, [
+                utils.path.tmp()
+            ]
+


update test task

update test task

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -90,12 +90,18 @@
     .pipe gulp.dest './www/assets/scripts'

 gulp.task 'test-build', ->
-  return gulp.src ['./src/scripts/helpers/**/*.*',
-            './src/scripts/bindings/**/*.*',
-            './src/scripts/widgets/**/*.*',
-            './src/scripts/models/**/*.*',
-            './specs/testHelpers/*.coffee',
-            './specs/**/*.spec.coffee']
+  return gulp.src './src/scripts/helpers/init.coffee'
+    .pipe addSrc.append ['./src/scripts/helpers/**/*.*',
+      '!./src/scripts/helpers/init.coffee']
+    .pipe addSrc.append './src/scripts/extensions/**/*.*'
+    .pipe addSrc.append './src/scripts/bindings/**/*.*'
+    .pipe addSrc.append './src/scripts/widgets/**/*.*'
+    .pipe addSrc.append './src/scripts/models/**/*.*'
+    .pipe addSrc.append ['./src/scripts/validators/**/*.*',
+      '!./src/scripts/validators/register.coffee']
+    .pipe addSrc.append './src/scripts/validators/register.coffee'
+    .pipe addSrc.append './src/scripts/*.*'
+    .pipe addSrc.append ['./specs/testHelpers/*.coffee', './specs/**/*.spec.coffee']
     .pipe coffee({bare: true})
     .pipe gulp.dest './temp'


make gulpfile more readable

make gulpfile more readable

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -2,6 +2,8 @@

 # use dependencies
 gulp = require 'gulp'
+from = gulp.src
+to = gulp.dest
 $ = do require 'gulp-load-plugins'

 gutil = require 'gulp-util'
@@ -22,26 +24,26 @@
 env = if gutil.env.mode then gutil.env.mode else 'development'

 gulp.task 'compile-sass', ->
-  gulp.src 'src/main/web/**/*.scss'
+  from 'src/main/web/**/*.scss'
   .pipe do css2sass
-  .pipe gulp.dest 'build'
+  .pipe to 'build'
   .pipe reload stream:true

 gulp.task 'build-conf', [], ->
-  gulp.src ['src/main/conf/' + env + '.json']
+  from ['src/main/conf/' + env + '.json']
   .pipe json2ng 'app.conf'
   .pipe $.rename basename:'conf'
-  .pipe gulp.dest 'build'
+  .pipe to 'build'

 gulp.task 'build-statics', [], ->
-  gulp.src ['src/main/web/**/*.html']
-  .pipe gulp.dest 'build'
+  from ['src/main/web/**/*.html']
+  .pipe to 'build'

 gulp.task 'build-vendors', [], ->
-  gulp.src do bowerFiles
+  from do bowerFiles
   .pipe $.filter '**/*.js'
   .pipe $.concat 'vendors.js'
-  .pipe gulp.dest 'build/libs'
+  .pipe to 'build/libs'

 gulp.task 'clean', (cb) ->
   rm ['.tmp', 'dist'], cb


Fix for CI

Fix for CI

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -22,13 +22,16 @@
 gulp.task 'mocha:ci', ->
   gulp.src(specFiles, read: false)
     .pipe(mocha(ui: 'bdd'))
+    .on 'error', (err) ->
+      console.log(err.stack)
+      process.exit(1)

 gulp.task 'mocha:dev', ->
   gulp.src(specFiles, read: false)
     .pipe(watch(emit: 'all', (files) ->
       files
         .pipe(mocha(ui: 'bdd'))
-        .on 'error', (err, res) ->
+        .on 'error', (err) ->
           console.log(err.toString())
           console.log(err.stack)
           @emit('end')


update gulp deps for installing bower components

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -24,6 +24,10 @@
   target: 'ES5'
   module: 'commonjs'

+gulp.task 'bower', ->
+  bower = require 'bower'
+  bower.commands.install()
+
 gulp.task 'build', (done) ->
   run = require 'run-sequence'
   run.apply run, [
@@ -49,7 +53,7 @@
   run = require 'run-sequence'
   run.apply(run, ['clean', 'build'])

-gulp.task 'deps', ['tsd']
+gulp.task 'deps', ['bower', 'tsd']

 gulp.task 'font', ->
   gulp


Remove extra line

Remove extra line

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -109,4 +109,3 @@
   .pipe mocha {reporter: 'spec'}

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


Gulp gulp gulp

Gulp gulp gulp

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -0,0 +1,42 @@
+gulp = require 'gulp'
+gutil = require 'gulp-util'
+coffee = require 'gulp-coffee'
+concat = require 'gulp-concat'
+shell = require 'gulp-shell'
+coverage = require 'coffee-coverage'
+
+# alias for gulp
+task = gulp.task.bind gulp
+src = gulp.src.bind gulp
+
+mochaCmd = [
+  'mocha'
+  '--compilers coffee:coffee-script/register'
+  '--reporter html-cov'
+  '--bail'
+  '--require js/test/init.js'
+  'test/spec_init.coffee'
+  'test/unit/test_spec.coffee'
+  '> coffee-coverage/coverage.html'
+].join ' '
+
+# Run the mocha test suite
+task 'tests-with-coverage', ['build-test'], shell.task(mochaCmd)
+
+coverageCmd = [
+  'coffeeCoverage'
+  '--path relative'
+  '-i ./js/test/test-app.js'
+  './src'
+  './test/_tmp'
+].join ' '
+
+# Instruments and compiles coffeescript files and outputs them to a tmpdir,
+# so they can be concatenated and used in the test suite.
+task 'coffee-coverage', shell.task(coverageCmd)
+
+# Concatenates instrumented js files.
+task 'build-test', ['coffee-coverage'], ->
+  src srclist.testApp
+    .pipe concat 'test-app.js'
+    .pipe dest TEST_DIR


fixed issue where the build directory is not cleaned

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -2,7 +2,7 @@
 gulps = require('require-dir')('./src/tasks')

 gulp.task 'default', ['test']
-gulp.task 'clean', -> gulps['clean']
+gulp.task 'clean', -> gulps['clean']()

 gulp.task 'test', ['test-module', 'test-deferred', 'test-unit']
 gulp.task 'test-unit', ['clean'], -> gulps['test-unit']()


Simpler syntax

Simpler syntax

--- resources/assets/coffee/_classes/landing-hero.coffee
+++ resources/assets/coffee/_classes/landing-hero.coffee
@@ -34,8 +34,8 @@

     os = osu.getOS()
     others = osu.otherOS os
-    $('.js-download-platform').text Lang.get('home.landing.download.for', {'os': os})
-    $('.js-download-other').text Lang.get('home.landing.download.other', {'os1': others[0], 'os2': others[1]})
+    $('.js-download-platform').text Lang.get('home.landing.download.for', os: os)
+    $('.js-download-other').text Lang.get('home.landing.download.other', os1: others[0], os2: others[1])

   remove: =>


rsstodolist.coffee : Use parenthesis (not square brackets)...

Sometimes, browser will include "]" character in links, forcing user to
remove it manually. Parenthesis should prevent that.

--- src/scripts/rsstodolist.coffee
+++ src/scripts/rsstodolist.coffee
@@ -54,7 +54,7 @@

                   reply += " - #{title},"
                   reply += " #{description}" if description?
-                  reply += " [#{link}]\n"
+                  reply += " (#{link})\n"
             catch err
                   msg.reply err


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/active_bounds.coffee
+++ test/active_bounds.coffee
@@ -16,7 +16,8 @@

   destroySpy = false

-  obj.bind 'destroy',
+  obj.bind 'destroy', ->
+    destroySpy = true

 test "should remove objects outside of activeBounds", 2, ->
   obj = GameObject


Rename license.txt to LICENSE Part.2

Rename license.txt to LICENSE Part.2

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -96,7 +96,7 @@
   compileManifest compile_path, (manifest_json) ->
     manifest_json.version = argv.version

-  for file_name in ['font', '_locales', 'license.txt']
+  for file_name in ['font', '_locales', 'LICENSE']
     fs.copySync file_name, path.join(compile_path, file_name)
   fs.copySync path.join(resources_path, 'img'), path.join(compile_path, 'img')


playground doesnt have a kd.js because of pipe order

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -86,8 +86,8 @@
       .pipe gulpBuffer()
       .pipe pistachioCompiler()
       .pipe gulpif useUglify, uglify()
+      .pipe rename "kd.#{version}js"
       .pipe gulp.dest "playground/js"
-      .pipe rename "kd.#{version}js"
       .pipe gulp.dest "#{buildDir}/js"

     stream.pipe livereload()  if useLiveReload


before completing functionification

before completing functionification

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -288,7 +288,7 @@
         else
             handleRequestError(err, httpResponse)

-# Calls cb(multiform)
+# Calls cb(url, file, multiform, jar)
 prepareUploadForm = (link, jar, cb) ->
     templateData = JSON.parse(fs.readFileSync(files.template))
     year = templateData.year
@@ -297,14 +297,12 @@

     page = templateData.links[link]
     if page is 'index'
-        url = BASE_URL + '?action=edit'
+        url = BASE_URL
     else
-        url = BASE_URL + '/' + page + '?action=edit'
-
-    gutil.log("./build-live/#{page}.html -> ", url)
+        url = BASE_URL + '/' + page

     request {
-        url : url,
+        url : url + '?action=edit'
         jar : jar
     }, (err, httpResponse, body) ->
         if !err and httpResponse.statusCode is 200
@@ -333,9 +331,37 @@
             parser.write(body)
             parser.end();

-            multiform['wpTextbox1'] = fs.readFileSync("./build-live/#{page}.html", 'utf8')
-
-            cb(multiform)
+            file = "#{dests.live.folder}/#{page}.html"
+            multiform['wpTextbox1'] = fs.readFileSync(file, 'utf8')
+
+            gutil.log("#{file} -> ", url)
+
+            cb(url, file, multiform, jar)
+
+# **postEdit**
+postEdit = (url, file, multiform, jar) ->
+    request {
+        url: url + '?action=submit'
+        method: 'POST'
+        formData: multiform
+        jar: jar
+    }, (err, httpResponse, body) ->
+        if !err and httpResponse.statusCode is 302
+            # Follow redirect to new page
+            request {
+                url: httpResponse.headers.location
+                jar: jar
+            }, (err, httpResponse, body) ->
+                if !err and httpResponse.statusCode is 200
+                    gutil.log("Successfully uploaded #{file} -> #{url}")
+                    fs.writeFileSync("response/#{file}.html", body)
+                else
+                    handleRequestError(err, httpResponse)
+        else
+            handleRequestError(err, httpResponse)
+
+upload = (link, jar) ->
+    prepareUploadForm(link, jar, postEdit)

 # **push**
 gulp.task 'push', ->


gulp file improvements

gulp file improvements

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -223,3 +223,13 @@
   return merge copyCSS, copyScripts, copyTemplates, copySvg, copyStarterPages, copyImages, renderHTML

 gulp.task 'dev', ['less-concat', 'template-concat', 'svg-concat', 'script-concat', 'client-watch', 'server-watch']
+
+gulp.task 'dist', ['compile'], ->
+  console.log 'To distribute a new version of cyclops'
+  console.log '     * Pull and get latests from https://github.com/CenturyLinkCloud/AssetsServer.git'
+  console.log '     * Copy the contents of the \'dist\' folder from Cyclops to the the \'cyclops\' folder'
+  console.log '     * Commit the changes to the AssetsServer Repository'
+  console.log '     * Create a tag for the release in this repository'
+  console.log "         * git tag -a v#{pkg.version} -m 'Add tag v#{pkg.version}'"
+  console.log '         * git push origin --tags'
+  console.log '     * Go bump the version in package.json to the next version and commit that as a \'version bump\''


using $.util instead of gutil

using $.util instead of gutil

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -7,7 +7,6 @@
 $ = do require 'gulp-load-plugins'
 $.if = require 'gulp-if-else'

-gutil = require 'gulp-util'
 rm = require 'del'
 sequence = require 'run-sequence'
 css = minify:(require 'gulp-minify-css'), fromSASS:(require 'gulp-sass')
@@ -20,7 +19,7 @@
 (require 'require-dir') './gulp';

 # global variables
-env = if gutil.env.mode then gutil.env.mode else 'development'
+env = if $.util.env.mode then $.util.env.mode else 'development'
 compressed = env == 'production'

 gulp.task 'compile-sass', [], ->


add isProd to templateOptions

add isProd to templateOptions

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -183,11 +183,13 @@
 ngClassifyOptions =
 	appName: APP_NAME

-templateOptions =
+templateOptions = {
 	appName: APP_NAME
-	useBackendless: useBackendless
+	isProd
+	useBackendless
 	scripts: []
 	styles: []
+}

 getScriptSources = (ext) ->
 	["**/*#{ext}"]


close #908 show dialog when discard the card editor with content

--- app/assets/javascripts/card.js.coffee
+++ app/assets/javascripts/card.js.coffee
@@ -102,6 +102,9 @@
     $.fancybox.open
       padding: 0
       href: "#card-form-container"
+      helpers:
+        overlay:
+          closeClick: false

   $(document).on "ajax:success", ".delete-card", (xhr, data) ->
     link = $ this
@@ -118,6 +121,14 @@

   $(document).on "card-order-changed", "#recipe-card-list", (event) ->
     setStateIndex()
+
+  $(document).on "click", ".fancybox-overlay", (event) ->
+    event.preventDefault()
+    if confirm "Are you sure to discard all changes on this dialog?"
+      $.fancybox.close()
+
+  $(document).on "click", "#inner_content", (event) ->
+    event.stopPropagation()

   $(document).on "click", ".add-image", (event) ->
     figure_list = $ "ul.figures"


Refactor: use #set_fields_to_edit_mode

Refactor: use #set_fields_to_edit_mode

--- app/assets/javascripts/home.js.coffee
+++ app/assets/javascripts/home.js.coffee
@@ -131,8 +131,7 @@
 install_handlers = ->

   edit_mode_checkbox().change ->
-    set_edit_mode_class(user_name(), this.checked)
-    set_edit_mode_class(user_title(), this.checked)
+    set_fields_to_edit_mode(this.checked)
     display_data()
     save_data() if is_dirty()


Update options to maps js

Update options to maps js

--- app/assets/javascripts/maps.js.coffee
+++ app/assets/javascripts/maps.js.coffee
@@ -22,7 +22,13 @@
         )
         mapElement.prop("map", map)
         if options.background?
-          L.tileLayer(options.background.url, options.background.options).addTo map
+          opts = {
+            attribution: options.background.attribution if options.background.attribution?
+            minZoom: options.background.minZoom if options.background.minZoom?
+            maxZoom: options.background.maxZoom if options.background.maxZoom?
+          }
+          opts['bounds'] = options.view.boundingBox if options.view.boundingBox
+          L.tileLayer(options.background.url, opts).addTo map
         else
           # Add an OpenStreetMap tile layer
           L.tileLayer.provider("OpenStreetMap.HOT").addTo map
@@ -51,11 +57,8 @@
           maxWidth: 200
         )

-        #Fixme: handle bad url
-        try
-          # Bounding box
-          map.fitBounds L.latLngBounds(options.view.boundingBox)  if options.view and options.view.boundingBox
-        catch e then console.log e
+        # Bounding box
+        map.fitBounds L.latLngBounds(options.view.boundingBox)  if options.view and options.view.boundingBox

         mapElement.prop "mapLoaded", true
       return


Hide input field when press ESC in tags directive

--- app/coffee/modules/common/tags.coffee
+++ app/coffee/modules/common/tags.coffee
@@ -42,6 +42,9 @@
     link = ($scope, $el, $attrs, $ctrl) ->
         $ctrl.$formatters.push(formatter)
         $ctrl.$parsers.push(parser)
+
+        $scope.$on "$destroy", ->
+            $el.off()

     return {
         require: "ngModel"
@@ -83,6 +86,9 @@
         $scope.$watch $attrs.tgColorizeTags, (tags) ->
             render(tags) if tags?

+        $scope.$on "$destroy", ->
+            $el.off()
+
     return {link: link}

 module.directive("tgColorizeTags", ColorizeTagsDirective)
@@ -99,7 +105,7 @@

         Add tag

-    <input type="text" placeholder="Write tag..." class="tag-input hidden" />
+    <input type="text" placeholder="Write tag..." class="tag-input" />

     """

@@ -114,23 +120,24 @@

     <% }); %>""")

-    renderTags = ($el, tags, editable, tagsColors) ->
-        ctx = {
-            tags: _.map(tags, (t) -> {name: t, color: tagsColors[t]})
-            editable: editable
-        }
-        html = templateTags(ctx)
-        $el.find("div.tags-container").html(html)
-
-    normalizeTags = (tags) ->
-        tags = _.map(tags, trim)
-        tags = _.map(tags, (x) -> x.toLowerCase())
-        return _.uniq(tags)
-
     link = ($scope, $el, $attrs, $model) ->
         editable = false

         $el.addClass("tags-block")
+        $el.find("input").hide()
+
+        renderTags = ($el, tags, editable, tagsColors) ->
+            ctx = {
+                tags: _.map(tags, (t) -> {name: t, color: tagsColors[t]})
+                editable: editable
+            }
+            html = templateTags(ctx)
+            $el.find("div.tags-container").html(html)
+
+        normalizeTags = (tags) ->
+            tags = _.map(tags, trim)
+            tags = _.map(tags, (x) -> x.toLowerCase())
+            return _.uniq(tags)

         addValue = (value) ->
             value = trim(value)
@@ -158,20 +165,9 @@
             input.autocomplete("close")
             $el.find('.save').hide()

-        $scope.$watch $attrs.ngModel, (val) ->
-            tags_colors = if $scope.project?.tags_colors? then $scope.project.tags_colors else []
-            renderTags($el, val, editable, tags_colors)
-
-            if val? and val.length > 0
-                $el.find("span.add-tag-text").hide()
-            else
-                $el.find("span.add-tag-text").show()
-
-
         bindOnce $scope, "project", (project) ->
             # If not editable, no tags preloading is needed.
-            editable = if $attrs.editable == "true" then true else false
-            editable = editable and project.my_permissions.indexOf($attrs.requiredPerm) != -1
+            editable = $attrs.editable == "true" and project.my_permissions.indexOf($attrs.requiredPerm) != -1

             if not editable
                 $el.find("input").remove()
@@ -196,7 +192,7 @@
                 })

         $el.on "keypress", "input", (event) ->
-            return if event.keyCode != 13
+            return if event.keyCode not in [13, 27]
             event.preventDefault()

         $el.on "keyup", "input", (event) ->
@@ -204,6 +200,11 @@

             if event.keyCode == 13
                 saveInputTag()
+            else if event.keyCode == 27
+                $el.find('.save').hide()
+                $el.find("input").hide()
+                $el.find("input").val('')
+                $el.find('.add-tag').show()
             else if target.val().length
                 $el.find('.save').show()
             else
@@ -215,7 +216,7 @@
             event.preventDefault()
             target = angular.element(event.currentTarget)
             target.hide()
-            target.siblings('input').removeClass('hidden')
+            target.siblings('input').show()

         $el.on "click", ".icon-delete", (event) ->
             event.preventDefault()
@@ -238,6 +239,18 @@
                     promise.then null, ->
                         $confirm.notify("error")

+        $scope.$watch $attrs.ngModel, (val) ->
+            tags_colors = if $scope.project?.tags_colors? then $scope.project.tags_colors else []
+            renderTags($el, val, editable, tags_colors)
+
+            if val? and val.length > 0
+                $el.find("span.add-tag-text").hide()
+            else
+                $el.find("span.add-tag-text").show()
+
+        $scope.$on "$destroy", ->
+            $el.off()
+
     return {
         link:link,
         require:"ngModel"


fix(kanban): prevent batch render to override archived us render

--- app/coffee/modules/kanban/main.coffee
+++ app/coffee/modules/kanban/main.coffee
@@ -331,12 +331,22 @@
             include_tasks: true
         }

+        if @.filterQ
+            params.q = @.filterQ
+
         params = _.merge params, @location.search()

         return @rs.userstories.listAll(@scope.projectId, params).then (userstories) =>
-            @scope.$broadcast("kanban:shown-userstories-for-status", statusId, userstories)
+            @.waitEmptyQuote () =>
+                @scope.$broadcast("kanban:shown-userstories-for-status", statusId, userstories)

             return userstories
+
+    waitEmptyQuote: (cb) ->
+        if @.queue.length > 0
+            requestAnimationFrame () => @.waitEmptyQuote(cb);
+        else
+            scopeDefer @scope, => cb()

     hideUserStoriesForStatus: (ctx, statusId) ->
         @scope.$broadcast("kanban:hidden-userstories-for-status", statusId)


better stats

better stats

--- app/controllers/HomeController.coffee
+++ app/controllers/HomeController.coffee
@@ -23,9 +23,9 @@
     totalStats = Institute.allStats()
     @html   require('views/home/splash')()
     @append require('views/home/stats')
-      archiveCount : Archive.count()
-      subjects     : totalStats.total
-      progress     : if totalStats.total > 0  then ((totalStats.complete/totalStats.total)+"")[0..4] else 0
+      archiveCount : if Archive.count() > 0 then Archive.count() else 'loading'
+      subjects     : if Archive.count() > 0 then totalStats.total else 'loading'
+      progress     : if totalStats.total > 0  then ((totalStats.complete/totalStats.total)+"")[0..4] else "loading"
       users        : @project?.user_count || 0
     @append require('views/home/content')()


Should default to downloading all posts. Fixes #3

--- lib/fetch_posts.coffee
+++ lib/fetch_posts.coffee
@@ -60,13 +60,14 @@
   else
     url += "&until=#{untilTime}"

-  unless since?
+  if since?
+    url += "&since=#{since}"
+  else
     if program.downloadSince?
       url += "&since=#{program.downloadSince}"
+    # Nothing is indicated, set since to the beginning of (UNIX) time.
     else
-      url += "&since=#{moment.unix(untilTime).subtract(1, 'months').unix()}"
-  else
-    url += "&since=#{since}"
+      url += "&since=0"

   return url


null values should not be casted

null values should not be casted

--- src/utils/type_checking.coffee
+++ src/utils/type_checking.coffee
@@ -23,7 +23,7 @@
         defaultValue = undefined

-    if value is undefined
+    if value is undefined or value is null
         if _isArray type then return []
         else return defaultValue


Update linter-plus.coffee

Update linter-plus.coffee

Fix a copy/paste error in `deleteProjectMessages`.

--- lib/linter-plus.coffee
+++ lib/linter-plus.coffee
@@ -92,7 +92,7 @@

   deleteProjectMessages: (linter) ->
     deprecate("Linter::deleteProjectMessages is deprecated, use Linter::deleteMessages instead")
-    return @setMessages(linter, messages)
+    return @deleteMessages(linter)

   getActiveEditorLinter: ->
     return @getEditorLinter atom.workspace.getActiveTextEditor()


:art: Observe config and update tabs

:art: Observe config and update tabs

--- lib/linter-plus.coffee
+++ lib/linter-plus.coffee
@@ -25,6 +25,12 @@
       @views.setShowPanel(showPanel)
     @subscriptions.add atom.config.observe 'linter.underlineIssues', (underlineIssues) =>
       @views.setUnderlineIssues(underlineIssues)
+    @subscriptions.add atom.config.observe 'linter.showErrorTabLine', =>
+      @views.updateTabs()
+    @subscriptions.add atom.config.observe 'linter.showErrorTabFile', =>
+      @views.updateTabs()
+    @subscriptions.add atom.config.observe 'linter.showErrorTabProject', =>
+      @views.updateTabs()
     @subscriptions.add atom.config.observe 'linter.lintOnFly', (value) =>
       @lintOnFly = value
     @subscriptions.add atom.project.onDidChangePaths =>


fix createtask

fix createtask

--- lib/phabricator.coffee
+++ lib/phabricator.coffee
@@ -360,6 +360,7 @@
           params.description = description
       @getProject(params.project)
     .then (projectparams) =>
+      params.projectphid = projectparams.data.phid
       @getUser(params.user, params.user)
     .then (userPHID) =>
       query = {
@@ -368,7 +369,7 @@
         'transactions[1][type]': 'comment',
         'transactions[1][value]': "(created by #{params.user.name} on #{params.adapter})",
         'transactions[2][type]': 'subscribers.add',
-        'transactions[2][value][0]': "#{params.userPhid}",
+        'transactions[2][value][0]': "#{userPHID}",
         'transactions[3][type]': 'subscribers.remove',
         'transactions[3][value][0]': "#{params.bot_phid}",
         'transactions[4][type]': 'projects.add',


Implement deactivate that offs subscription

--- lib/spell-check.coffee
+++ lib/spell-check.coffee
@@ -8,11 +8,13 @@
       'text.git-commit'
     ]

-  createView: (editorView) ->
+  activate: ->
+    @editorSubscription = atom.workspaceView.eachEditorView(addViewToEditor)
+
+  deactivate: ->
+    @editorSubscription?.off()
+
+addViewToEditor = (editorView) ->
+  if editorView.attached and editorView.getPane()?
     SpellCheckView ?= require './spell-check-view'
-    new SpellCheckView(editorView)
-
-  activate: ->
-    atom.workspaceView.eachEditorView (editorView) =>
-      if editorView.attached and editorView.getPane()?
-        editorView.underlayer.append(@createView(editorView))
+    editorView.underlayer.append(new SpellCheckView(editorView))


Augmented Mocha's timeout because it is necessary for Paypal UT

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -136,7 +136,7 @@
     mochaTest:
       test:
         options:
-          timeout: 30000
+          timeout: 60000
           reporter: "spec"
           require : ["coffee-script", "coverage/blanket"]
         src: ["test/**/*.coffee"]


cleanup

cleanup

--- content/better-bibtex.coffee
+++ content/better-bibtex.coffee
@@ -88,38 +88,18 @@
   )
 )(Zotero.Item::save)

-###
-PatchItemRemove = ((original, name) ->
-  return Zotero.Promise.coroutine(->
-    try
-      Zotero.debug("Zotero.Item::#{name}: native...")
-      result = yield original.apply(@, arguments)
-    catch err
-      Zotero.debug("Zotero.Item::#{name}: native #{name} failed! " + err + "\n\n" + err.stack)
-      throw err
-
-    try
-      DB.getCollection('citekey').findAndRemove({itemID: @id})
-      DB.getCollection('itemToExportFormat').findAndRemove({itemID: @id})
-    catch err
-      Zotero.debug("Zotero.Item::#{name}: post-native #{name} failed: " + err + "\n\n" + err.stack)
-
-    return result
-  )
-)
-Zotero.Item::erase = PatchItemRemove(Zotero.Item::erase, 'erase')
-Zotero.Item::trash = PatchItemRemove(Zotero.Item::trash, 'trash')
-
-Must also patch Zotero.Items.trash...
-
-perhaps the notifiers are fast enough
-###
-
 Zotero.Notifier.registerObserver({
   notify: (action, type, ids, extraData) ->
     debug('item.notify', {action, type, ids, extraData})

     # safe to use Zotero.Items.get(...) rather than Zotero.Items.getAsync here
+    # https://groups.google.com/forum/#!topic/zotero-dev/99wkhAk-jm0
+    # items = Zotero.Items.get(ids)
+
+    # not needed as the parents will be signaled themselves
+    # parents = (item.parentID for item in items when item.parentID)
+    # CACHE.remove(parents)
+
     if action in ['delete', 'trash']
       DB.getCollection('citekey').findAndRemove({ itemID : { $in : ids } })
       CACHE.remove(ids)


_send() test for Remote.Client

_send() test for Remote.Client

--- src/tests/spec/public/models/remote.client.spec.coffee
+++ src/tests/spec/public/models/remote.client.spec.coffee
@@ -4,10 +4,9 @@

 require [
 	'public/library/models/remote.client'
-	'public/library/helpers/mixable'
-	'public/library/helpers/mixin.eventbindings'
+	'public/library/models/message'

-	], ( Client, Mixable, EventBindings ) ->
+	], ( Client, Message ) ->

 	describe 'Remote.Client', ->

@@ -88,4 +87,18 @@

 				client.disconnect()

-				expect(fakeConnection.disconnect).toHaveBeenCalled()+				expect(fakeConnection.disconnect).toHaveBeenCalled()
+
+		describe 'when sending', ->
+			it 'should relay the message to _connection.emit', ->
+				client = new Client(fakeController, fakeConnection)
+				spyOn(fakeConnection, 'emit')
+
+				fakeMessage = new Message('a', 'b', 'event')
+				client._send(fakeMessage)
+
+				expect(fakeConnection.emit).toHaveBeenCalled()
+				expect(fakeConnection.emit.mostRecentCall.args).toEqual([
+						'message'
+						fakeMessage.serialize()
+					])


Update Atom config

Update Atom config

--- atom/atom.symlink/config.cson
+++ atom/atom.symlink/config.cson
@@ -2,9 +2,9 @@
   editor:
     showIndentGuide: true
     invisibles: {}
-    fontSize: 17
     softWrapAtPreferredLineLength: true
     showInvisibles: true
+    fontSize: 16
   core:
     ignoredNames: [
       ".bundle"
@@ -15,8 +15,8 @@
       "vendor"
     ]
     themes: [
-      "unity-ui"
-      "glacier-syntax"
+      "one-dark-ui"
+      "solarized-dark-syntax"
     ]
     hideGitIgnoredFiles: false
     disabledPackages: [
@@ -32,3 +32,6 @@
     ]
   welcome:
     showOnStartup: false
+  "git-plus": {}
+  "one-dark-ui": {}
+  "markdown-preview-plus": {}


Remove CDXPlotContext

Remove CDXPlotContext

--- bokehjs/src/coffee/common/base.coffee
+++ bokehjs/src/coffee/common/base.coffee
@@ -53,7 +53,6 @@
     Plot:                   'common/plot'
     GMapPlot:               'common/gmap_plot'
     GridPlot:               'common/grid_plot'
-    CDXPlotContext:         'common/plot_context'
     PlotContext:            'common/plot_context'
     PlotList:               'common/plot_context'


Add deprecation warning.

Add deprecation warning.

--- js/inner.coffee
+++ js/inner.coffee
@@ -88,6 +88,7 @@
   obj.project_path = komp ->
     "/gh/#{obj.project_name()}"

+## Deprecated. Do not use for new classes.
 class Base extends Obj
   constructor: (json, defaults={}, nonObservables=[], observe=true) ->
     for k,v of $.extend {}, defaults, json


Re comment out pusher, after ballsed up merge

--- js/inner.coffee
+++ js/inner.coffee
@@ -511,7 +511,7 @@
     @project_map = {}
     observableCount += 8

-    @setupPusher()
+    #@setupPusher()

   setupPusher: () =>
     @pusher = new Pusher("356b7c379e56e14c261b")


code trimright

code trimright

--- js/nodes.coffee
+++ js/nodes.coffee
@@ -172,11 +172,11 @@
     @text += text

    render: (options) ->
-    code = @text
+    code = @text.trimRight()
     html = false

     if @lang isnt '' and hljs? and (hljs.getLanguage @lang)?
-     code = hljs.highlight @lang, @text, true
+     code = hljs.highlight @lang, code, true
      code = code.value
      html = true


Rename 'remote' to 'peer' and fix RTC connection event listeners

--- src/tests/spec/public/models/remote.peer.spec.coffee
+++ src/tests/spec/public/models/remote.peer.spec.coffee
@@ -8,7 +8,7 @@

 	describe 'Remote.Peer', ->

-		remote = null
+		peer = null
 		fakeController = null

 		# Fake RTCPeerConnection object to prevent actually connecting with something
@@ -64,7 +64,7 @@
 				constructorSpy = spyOn(global, 'FakeRTCPeerConnection').andCallThrough()
 				FakeRTCPeerConnection.prototype = originalPrototype

-				remote = new Peer(fakeController, '1', true, FakeRTCPeerConnection)
+				peer = new Peer(fakeController, '1', true, FakeRTCPeerConnection)
 				expect(constructorSpy).toHaveBeenCalled()

 				callArgs = FakeRTCPeerConnection.mostRecentCall.args
@@ -72,26 +72,23 @@
 				expect(callArgs[0]).toEqual(Peer.prototype._serverConfiguration)
 				expect(callArgs[1]).toEqual(Peer.prototype._connectionConfiguration)

-				expect(remote._connection.onicecandidate).toEqual(remote._onIceCandidate)
-				expect(remote._connection.oniceconnectionstatechange).toEqual(remote._onIceConnectionStateChange)
-				expect(remote._connection.ondatachannel).toEqual(remote._onDataChannel)
+				expect(peer._connection.onicecandidate).toEqual(peer._onIceCandidate)
+				expect(peer._connection.oniceconnectionstatechange).toEqual(peer._onIceConnectionStateChange)
+				expect(peer._connection.ondatachannel).toEqual(peer._onDataChannel)

 			it 'should listen on all RTC connection events', ->
-				remote = new Peer(fakeController, '1', true, FakeRTCPeerConnection)
+				spyOn(Peer.prototype, '_onConnect')
+				spyOn(Peer.prototype, '_onDisconnect')
+				spyOn(Peer.prototype, '_onChannelOpened')
+				spyOn(Peer.prototype, '_onChannelClosed')
+				peer = new Peer(fakeController, '1', true, FakeRTCPeerConnection)

-				console.log(remote._onConnect.toString())
-				spyOn(remote, '_onConnect')
-				spyOn(remote, '_onDisconnect')
-				spyOn(remote, '_onChannelOpened')
-				spyOn(remote, '_onChannelClosed')
+				peer.trigger('connect')
+				peer.trigger('disconnect')
+				peer.trigger('channel.opened')
+				peer.trigger('channel.closed')

-				console.log(remote._onConnect.toString())
-				remote.trigger('connect')
-				remote.trigger('disconnect')
-				remote.trigger('channel.opened')
-				remote.trigger('channel.closed')
-
-				expect(remote._onConnect).toHaveBeenCalled()
-				expect(remote._onDisconnect).toHaveBeenCalled()
-				expect(remote._onChannelOpened).toHaveBeenCalled()
-				expect(remote._onChannelClosed).toHaveBeenCalled()+				expect(peer._onConnect).toHaveBeenCalled()
+				expect(peer._onDisconnect).toHaveBeenCalled()
+				expect(peer._onChannelOpened).toHaveBeenCalled()
+				expect(peer._onChannelClosed).toHaveBeenCalled()


payment is required

payment is required

--- src/smc-webapp/billing.cjsx
+++ src/smc-webapp/billing.cjsx
@@ -1294,7 +1294,7 @@

-                Paying is optional, but will ensure that your students have a better
+                Payment is required. This will ensure that your students have a better
                 experience, network access, and receive priority support.  The cost
                 is between $4 and ${STUDENT_COURSE_PRICE} per student, depending on class size and whether
                 you or your students pay.  Start right now: you can fully setup your class


KodingKontrol: remove inject trick instead we will pass it as options

--- client/Main/kite/kodingkontrol.coffee
+++ client/Main/kite/kodingkontrol.coffee
@@ -1,6 +1,9 @@
 class KodingKontrol extends (require 'kontrol')

-  constructor: ->
+  constructor: (options = {})->
+
+    @_kontrolUrl = options.kontrolUrl  if options.kontrolUrl?
+
     super @getAuthOptions()

     @kites = {}
@@ -33,14 +36,6 @@
     }[name] ? ''

   injectQueryParams: (args) ->
-
-    if args.kontrolUrl? and args.kontrolUrl isnt @_kontrolUrl
-      @_kontrolUrl = args.kontrolUrl
-      @reauthenticate()
-
-    if @_kontrolUrl? and not args.kontrolUrl?
-      delete @_kontrolUrl
-      @reauthenticate()

     args.query.version     ?= @getVersion args.query.name
     args.query.username    ?= KD.config.kites.kontrol.username


shortcuts: reimplement save/restore :monkey:

--- client/app/lib/shortcuts/index.coffee
+++ client/app/lib/shortcuts/index.coffee
@@ -3,14 +3,15 @@
 events         = require 'events'
 _              = require 'underscore'
 kd             = require 'kd'
+traverse       = require 'traverse'
 globals        = require 'globals'
 ShortcutsModal = require './views/modal'
-cloneArray     = require 'app/util/cloneArray'
 AppController  = require 'app/appcontroller'
+AppStorage     = require 'app/appstorage'

 STORAGE_NAME    = 'shortcuts'
-STORAGE_VERSION = '4.1'
-THROTTLE_WAIT   = 500
+STORAGE_VERSION = '7'
+THROTTLE_WAIT   = 2000

 module.exports =

@@ -31,7 +32,8 @@

     super()

-    @_store = null
+    @_store  = null
+    @_buffer = null

     if opts.shortcuts instanceof Shortcuts
       @shortcuts = opts.shortcuts
@@ -41,11 +43,20 @@
           return acc
         , {}

+    @_flushBuffer()
+
+
+  # Prepares buffer for the next batch of changes.
+  #
+  _flushBuffer: ->
+
     @_buffer = @shortcuts.config.reduce (acc, collection) ->
       acc[collection.name] = []
       return acc
     , {}

+    return this
+

   # Party starts here.
   #
@@ -73,52 +84,41 @@

   # Persists changes to app storage.
   #
-  # This is a throttled method and will only be called per every
+  # This is a throttled method and will only be called once per every
   # _THROTTLE_WAIT_ milliseconds.
   #
   _save: _.throttle ->

-    throw 'could not persist changes'  unless @_store.isReady
-
-    for key, models of @_buffer
-
-      set = []
-
-      while (model = @_buffer[key].pop())?
-        value =
-        _
-          .chain(model.toJSON())
-          # since every platform has independent storages, we are only
-          # saving this platform's bindings
-          .extend binding: klass._getPlatformBinding model
-          .pick 'binding', 'options', 'name' # and nothing more
-          .tap (obj) -> # to validate
-            if _.isObject obj.options
-              # the only whitelisted options prop is _enabled_
-              obj.options = _.pick obj.options, 'enabled'
-              delete obj.options  \
-                if (_.isEmpty obj.options) or (not _.isBoolean obj.options.enabled)
-            if (not _.isArray obj.binding) or _.isEmpty obj.binding
-              delete obj.binding
-              # and disable it silently
-              obj.options or= {}
-              _.extend obj.options, enabled: no
-          .value()
-
-        # this should pass anyhow (since there will be always _name_ prop)
-        # but still it doesn't hurt to be little paranoid here.
-        set.push value  unless _.isEmpty value
-
-      unless _.isEmpty set
-        @_store.setValue key, set,
-          _
-            .chain (collectionName, modelNames) ->
-              _.each modelNames, (modelName) =>
-                collection = @shortcuts.get collectionName
-                @emit 'change', collection, collection.find name: modelName
-            .partial key, _.pluck set, 'name'
-            .bind @
-            .value()
+    unless @_store.isReady
+      console.warn 'could not persist shortcut changes'
+      @_flushBuffer()
+      return
+
+    # take over buffer
+    buffer = traverse(@_buffer).clone()
+    @_flushBuffer()
+
+    # transform it, so we can extend the remote object
+    pack =
+      _
+        .reduce buffer, (sum, collection, key) ->
+          return collection.reduce (acc, model) ->
+            name = model.name.replace /\./g, '$' # safety first
+            acc["#{AppStorage.DEFAULT_GROUP_NAME}.#{key}.#{name}"] = model
+            return acc
+          , sum
+        , {}
+
+    # so we extend the remote object
+    @_store._storage.update $set: pack, =>
+
+      # and make sure everybody is aware of the changes we've just made
+      _.each (buffer, objs, collectionName) =>
+        collection = @get collectionName
+        _.each objs, (value) =>
+          @emit 'change', collection, collection.find name: value.name
+
+    return

   , THROTTLE_WAIT,
     leading  : no
@@ -134,10 +134,21 @@
   #
   _handleShortcutsChange: (collection, model) ->

-    queue = @_buffer[collection.name]
-    idx   = _.findWhere queue, name: model.name
-
-    queue[((~idx and idx) or queue.length)] = model
+    queue   = @_buffer[collection.name]
+    idx     = _.findWhere queue, name: model.name
+    obj     = name: model.name
+    binding = klass._getPlatformBinding model
+
+    if (_.isArray binding) and  (not _.isEmpty binding)
+      obj.binding = traverse(binding).clone()
+    else
+      obj.binding = null
+
+    # even if enabled is _true_ by default for this model, we always save it on
+    # appstorage. defaults may change, but user overrides should stay.
+    obj.enabled = if model.options?.enabled is no then no else yes
+
+    queue[((~idx and idx) or queue.length)] = obj

     @_save()

@@ -146,24 +157,36 @@
   #
   _handleStoreReady: ->

-    @shortcuts.config.each (collection) =>
-      collectionName = collection.name
-
-      objs = @_store.getValue collectionName
-
-      if _.isArray objs
-        _.each objs, (obj) =>
-
-          model = @shortcuts.get collectionName, obj.name
-          return  unless model
-
+    overrides = @_store._storage[AppStorage.DEFAULT_GROUP_NAME]
+
+    _.each overrides, (objs, collectionName) =>
+      collection = @shortcuts.get collectionName
+
+      _.each objs, (override) =>
+        model = collection.find name: override.name
+        return  unless model
+
+        enabled = if model.options?.enabled is no then no else yes
+
+        matchesEnabled = override.enabled is enabled
+        matchesBinding = _.isEqual klass._getPlatformBinding(model), override.binding
+
+        console.log model, override
+        console.log matchesEnabled, matchesBinding
+
+        unless matchesEnabled and matchesBinding
           @emit 'change', collection,
-            @shortcuts.update collectionName, model.name,
-              binding: klass._insertPlatformBinding model, obj.binding
-              options: obj.options
-            , klass._isCustomShortcut model
+            @shortcuts.update collection.name, model.name, {
+              binding: klass._replacePlatformBinding model, override.binding,
+              options: enabled: override.enabled
+            },
+              klass._isCustomShortcut model
+
+        return

     @shortcuts.on 'change', _.bind @_handleShortcutsChange, this
+
+    return

   # Invalidates previous app's keyboard events and adds new
@@ -200,6 +223,7 @@

     @shortcuts.get.apply @shortcuts, Array::slice.call arguments

+
   # Updates a _keyconfig#Model_.
   #
   # This api is pretty-much same with _shortcuts#update_.
@@ -207,9 +231,9 @@
   # One important difference is _silent_ argument is handled internally,
   # and not exposed. That's because passing _silent_ to _shortcuts#update_
   # as true makes sure _shortcuts_ doesn't add any keyboard event listeners,
-  # yet it still updates the underlying _keyconfig_ instance.
-  #
-  # This is how we deal with shortcuts that should be handled manually.
+  # yet it still updates the underlying _keyconfig_ instance. This is how we
+  # deal with shortcuts that should be handled manually.
+  #
   #
   update: (collectionName, modelName, value) ->

@@ -217,22 +241,34 @@

     overrides = _.pick value, 'binding', 'options'

-    unless Object.keys(overrides).length
-      throw 'value should contain \'binding\' and/or \'options\' props'
-
-    unless @_store.isReady
-      console.warn 'changes won\'t be persisted, since storage is not ready yet'
+    throw 'missing \'binding\' and/or \'options\' props'  if _.isEmpty overrides
+    console.warn 'changes won\'t be persisted, storage is not ready yet'  unless @_store.isReady

     model = @shortcuts.get collectionName, modelName
+
     throw "#{modelName} not found"  unless model

-    if overrides.binding
-      overrides.binding = klass._insertPlatformBinding model, overrides.binding
+    # _lodash#pick_ returns a shallow copy; make sure we don't override the given value
+    overrides = traverse(overrides).clone()
+
+    # _isNull_ test is necessary here to make sure we don't update other
+    # platform bindings accidentally
+    if _.isArray(overrides.binding) or _.isNull(overrides.binding)
+      overrides.binding = klass._replacePlatformBinding model, overrides.binding
+    else
+      delete overrides.binding
+
+    # _options.enabled_ must be precisely set to _false_ in order to disable a shortcut;
+    # any other falsey setting will always be converted to _true_
+    if _.isObject overrides.options
+      whitelistedOptions = _.pick overrides.options, 'enabled'
+      unless _.isEmpty whitelistedOptions
+        overrides.options.enabled = if overrides.options.enabled is no then no else yes

     silent = klass._isCustomShortcut model
-
-    res = @shortcuts.update collectionName, modelName, overrides, silent
-
+    res    = @shortcuts.update collectionName, modelName, overrides, silent
+
+    # we manually trigger change for custom shortcuts
     if silent then @_handleShortcutsChange @shortcuts.get(collectionName), res

     return res
@@ -298,19 +334,15 @@
     if globals.keymapType is 'mac' then 1 else 0

-  # Given a keyconfig#Model and a 1d array of bindings, validates and inserts
-  # the array at the correct position for the current platform, and returns
-  # a deep clone of the model's binding.
-  #
-  @_insertPlatformBinding: (model, platformBinding) ->
-
-    binding = cloneArray model.binding
-    idx = klass._bindingPlatformIndex()
-
-    if _.isArray platformBinding
-      binding[idx] = platformBinding.filter _.isString
-    else
-      binding[idx] = []
+  # Returns a clone of model's binding with given binding array inserted at the
+  # correct position for the current platform.
+  #
+  @_replacePlatformBinding: (model, platformBinding) ->
+
+    binding      = traverse(model.binding).clone()
+    idx          = klass._bindingPlatformIndex()
+    arr          = platformBinding?.filter _.isString
+    binding[idx] = if (_.isArray arr) and arr.length then arr else null

     return binding


Allow to ignore unknown users

Allow to ignore unknown users

--- client/components/ReviewResult.coffee
+++ client/components/ReviewResult.coffee
@@ -121,18 +121,19 @@
                                     state={@state}/>
                                 <FormGroup>
                                     {
-                                    if @props.result.userList != "unknown"
-                                        bsSize = null
-                                        if not (@state.currentSubmit.outcome in ["OK", "AC", "IG"])
-                                            bsSize = "xsmall"
-                                        <ButtonGroup>
-                                            <Button onClick={@accept} bsStyle="success" bsSize={bsSize}>Зачесть</Button>
-                                            <Button onClick={@ignore} bsStyle="info" bsSize={bsSize}>Проигнорировать</Button>
-                                            <Button onClick={@comment}>Прокомментировать</Button>
-                                            <Button onClick={@disqualify} bsStyle="danger" bsSize="xsmall">Дисквалифицировать</Button>
-                                        </ButtonGroup>
-                                    else
-                                        <Button onClick={@props.handleDone}>Пропустить</Button>
+                                    bsSize = null
+                                    bsCommentSize = null
+                                    if not (@state.currentSubmit.outcome in ["OK", "AC", "IG"])
+                                        bsSize = "xsmall"
+                                    if @props.result.userList == "unknown"
+                                        bsSize = "xsmall"
+                                        bsCommentSize = "xsmall"
+                                    <ButtonGroup>
+                                        <Button onClick={@accept} bsStyle="success" bsSize={bsSize}>Зачесть</Button>
+                                        <Button onClick={@ignore} bsStyle="info" bsSize={bsSize}>Проигнорировать</Button>
+                                        <Button onClick={@comment}  bsSize={bsCommentSize}>Прокомментировать</Button>
+                                        <Button onClick={@disqualify} bsStyle="danger" bsSize="xsmall">Дисквалифицировать</Button>
+                                    </ButtonGroup>
                                     }
                                 </FormGroup>
                             </div>


Added note

Added note

--- client/components/SolvedByWeek.coffee
+++ client/components/SolvedByWeek.coffee
@@ -53,6 +53,9 @@
         {H}

             Количество зачтенных посылок за неделю; 0.5, если посылки были, но ни одной зачтенной
+

+

+            Таблица развернута: ось времени направлена влево, недавние недели находятся слева.


add comment

add comment

--- coffee/services/issueEditState.coffee
+++ coffee/services/issueEditState.coffee
@@ -8,6 +8,10 @@
     constructor: (@$scope) ->
       super()

+
+    ###
+     on click remove button, remove issue from selectable list.
+    ###
     removeItem: (item) ->
       if State.isTracking and item.equals DataAdapter.selectedTicket
         return
@@ -15,6 +19,9 @@
       Message.toast Resource.string("msgRemoved").format(item.text)

+    ###
+     load issues from Redmine.
+    ###
     load: (page) ->
       page = @currentPage if not page?
       if not DataAdapter.selectedProject?
@@ -28,6 +35,9 @@
       Redmine.get(DataAdapter.selectedAccount).getIssuesOnProject(projectId, params, @loadSuccess, @loadError)

+    ###
+     update issues.
+    ###
     loadSuccess: (data) =>
       return if not DataAdapter.selectedProject
       return if DataAdapter.selectedProject.url isnt data.url
@@ -40,6 +50,9 @@
       @$scope.issues.set(data.issues)

+    ###
+     show error message.
+    ###
     loadError: (data, status) =>
       if status is BaseEditState.STATUS_CANCEL then return
       Message.toast Resource.string("msgLoadIssueFail")


Restore image width logic

Restore image width logic

--- components/article/client/view.coffee
+++ components/article/client/view.coffee
@@ -91,10 +91,10 @@
       .each (i, img) ->
         optimizedHeight = window.innerHeight * 0.9
         newWidth = ((img.width * optimizedHeight) / img.height)
-        if img.width < (img.height * 0.9)
-          $(img).parent().addClass('portrait')
         if newWidth < 580
           $(img).parent().css('max-width', 580)
+        else if img.width < (img.height * 0.9)
+          $(img).parent().addClass('portrait')
     @$('.article-section-artworks, .article-section-container[data-section-type=image]').addClass 'images-loaded'
     @loadedImageHeights = true
     @maybeFinishedLoading()


Add TextBufferCore::getLines and ::isEmpty

--- src/text-buffer-core.coffee
+++ src/text-buffer-core.coffee
@@ -44,6 +44,12 @@
     for row in [0..@getLastRow()]
       text += (@lineForRow(row) + @lineEndingForRow(row))
     text
+
+  getLines: ->
+    @lines.slice()
+
+  isEmpty: ->
+    @getLastRow() is 0 and @lineLengthForRow(0) is 0

   getLineCount: ->
     @lines.length


removed console.log

removed console.log

Former-commit-id: fa932fc20137177c3a3caa9f517ae8387ef40d9b [formerly 08f1a8990782fd50aa74801eb69bf8266360b855]
Former-commit-id: 1dda3178e89095d22bd12c31528920fb3ac16c22

--- src/views/HeaderView.coffee
+++ src/views/HeaderView.coffee
@@ -36,7 +36,6 @@
     nMax = @model.getMaxLength()
     stepSize = @g.zoomer.get("stepSize")
     hidden = @g.columns.get "hidden"
-    console.log hidden

     while n < nMax
       if hidden.indexOf(n) >= 0