Add new channel to ChannelsStore when fetchChannel is called

--- client/ide/lib/collaboration/helpers/social.coffee
+++ client/ide/lib/collaboration/helpers/social.coffee
@@ -1,7 +1,10 @@
-kd = require 'kd'
-remote = require('app/remote').getInstance()
-getNick = require 'app/util/nick'
+kd                            = require 'kd'
+remote                        = require('app/remote').getInstance()
+isKoding                      = require 'app/util/isKoding'
+getNick                       = require 'app/util/nick'
+actionTypes                   = require 'activity/flux/actions/actiontypes'
 getCollaborativeChannelPrefix = require 'app/util/getCollaborativeChannelPrefix'
+

 ###*
  * Wrapper function around `SocialApiController#addParticipants`
@@ -49,7 +52,15 @@
 ###
 fetchChannel = (id, callback) ->

-  kd.singletons.socialapi.cacheable 'channel', id, callback
+  kd.singletons.socialapi.cacheable 'channel', id, (err, channel) ->
+    callback err, channel
+
+    return  if isKoding()
+
+    kd.singletons.reactor.dispatch actionTypes.LOAD_CHANNEL_SUCCESS, {
+      channelId: id,
+      channel
+    }

 ###*


Removed forgotten comment

Removed forgotten comment

--- core/app/backbone/views/facts/top_fact_view.coffee
+++ core/app/backbone/views/facts/top_fact_view.coffee
@@ -18,7 +18,7 @@
   onRender: ->
     @userHeadingRegion.show new TopFactHeadingLinkView model: @model

-    @deleteRegion.show @_deleteButtonView() #if @model.can_destroy()
+    @deleteRegion.show @_deleteButtonView() if @model.can_destroy()
     @factVoteTableRegion.show new FactVoteTableView model: @model

     Backbone.Factlink.makeTooltipForView @,


Relative path seems to work.

Relative path seems to work.

--- common/lib/xmodule/xmodule/js/src/html/edit.coffee
+++ common/lib/xmodule/xmodule/js/src/html/edit.coffee
@@ -14,7 +14,7 @@

 #   This is a workaround for the fact that tinyMCE's baseURL property is not getting correctly set on AWS
 #   instances (like sandbox). It is not necessary to explicitly set baseURL when running locally.
-    tinyMCE.baseURL = window.location.protocol + "//" + window.location.host + '/static/js/vendor/tiny_mce'
+    tinyMCE.baseURL = '/static/js/vendor/tiny_mce'
     @tiny_mce_textarea = $(".tiny-mce", @element).tinymce({
       script_url : '/static/js/vendor/tiny_mce/tiny_mce.js',
       theme : "advanced",


Display a spinner while waiting to close

--- components/inquiry_questionnaire/views/done.coffee
+++ components/inquiry_questionnaire/views/done.coffee
@@ -1,5 +1,9 @@
-Backbone = require 'backbone'
+StepView = require './step.coffee'

-module.exports = class Done extends Backbone.View
+module.exports = class Done extends StepView
+  className: 'iq-loadable is-loading'
+
+  template: -> ''
+
   initialize: ({ @state }) ->
     @state.trigger 'done'


don't merge in package or version props when merging into namespaces

--- src/Runtime/Neptune.coffee
+++ src/Runtime/Neptune.coffee
@@ -28,6 +28,8 @@
   @modules: []
   @moduleNames: []
   @_name: "Base"
+  @package: null
+  @version: null

   @initAsSubNamespace: (name, @namespace) ->
     @_name = name


Show the static comments

Show the static comments

--- core/app/backbone/views/fact_relations_view.coffee
+++ core/app/backbone/views/fact_relations_view.coffee
@@ -48,8 +48,8 @@
       collection: @model.getInteractors()

     # Todo: add to initialize?
-    # @commentsRegion.show new CommentsListView
-    #   collection: @model.comments()
+    @commentsRegion.show new CommentsListView
+      collection: @model.comments()

     if @model.relations()
       @factRelationSearchRegion.show new AddEvidenceView


Improve ntp

Improve ntp

--- actions/ntp.coffee
+++ actions/ntp.coffee
@@ -25,19 +25,24 @@
   , (err, serviced) ->
     return next err if err
     return next null, ctx.PASS unless serviced
+    # Here's good place to compare the date, maybe with the host maching:
+    # if gap is greather than threehold, stop ntpd, ntpdate, start ntpd
     ctx.log "Synchronize the system clock with #{ctx.config.ntp.servers[0]}"
     # NTPD must not be started for ntpdate to work
+    ctx.log "Sync clock on first install since ntpd isnt yet started"
     ctx.execute
       cmd: "ntpdate #{ctx.config.ntp.servers[0]}"
     , (err) ->
-      return next err if err
-      ctx.log "Start the NTP service"
-      ctx.service
-        name: 'ntp'
-        srv_name: 'ntpd'
-        action: 'start'
-      , (err, serviced) ->
-        next err, ctx.OK
+      next err, ctx.OK
+
+ntp.push name: 'NTP # Start', timeout: -1, callback: (ctx, next) ->
+  ctx.log "Start the NTP service"
+  ctx.service
+    name: 'ntp'
+    srv_name: 'ntpd'
+    action: 'start'
+  , (err, serviced) ->
+    next err, if serviced then ctx.OK else ctx.PASS

 ntp.push name: 'NTP # Configure', callback: (ctx, next) ->
   write = []


remove notion of BaseCollection::meta from Rendr; move to Moweb

--- shared/base/collection.coffee
+++ shared/base/collection.coffee
@@ -5,8 +5,6 @@
 module.exports = class Base extends Backbone.Collection

   model: BaseModel
-
-  meta: {}

   constructor: (models, options = {}) ->
     super
@@ -21,12 +19,8 @@
     # query for these models.
     @params = @options.params

-    _.extend(@meta, options.meta) if options.meta?
-
-
   # Idempotent parse
   parse: (resp) ->
-    _.extend(@meta, resp.meta) if resp.meta?
     if @jsonKey
       resp[@jsonKey] || resp
     else


was not calling the method, but the namespace

--- app/assets/javascripts/digital_opera/ellipsis.js.coffee
+++ app/assets/javascripts/digital_opera/ellipsis.js.coffee
@@ -25,4 +25,4 @@
 )(window.digitalOpera)

 $('[data-does="animate-ellipsis"]').each (idx, ele) ->
-  window.digitalOpera(ele)+  window.digitalOpera.ellipsis(ele)


Fix avatars?

Fix avatars?

--- src/controllers/guides.coffee
+++ src/controllers/guides.coffee
@@ -118,7 +118,7 @@
 			if item.contributors
 				async.map item.contributors, ((id, done) ->
 					User.findOne({ _id: id })
-						.select('username name avatar_url profile')
+						.select('username name avatar_url avatarUrl profile')
 						.exec (err, user) ->
 							if not err and not user
 								logger.warn("Couldn't find contributor with id:", id)
@@ -138,7 +138,7 @@
 		readAuthor = (cb) ->
 			if item.author
 				User.findOne({ _id: item.author })
-					.select('username name avatar_url profile')
+					.select('username name avatar_url avatarUrl profile')
 					.exec (err, user) ->
 						if not err and not user
 							logger.warn("Couldn't find author with id:", id)


using global helper functions in writeSchedule instead of re-implementing them locally

--- source/javascripts/index/writeSchedule.coffee
+++ source/javascripts/index/writeSchedule.coffee
@@ -1,13 +1,13 @@
 //= require ../site/zmanim
 //= require ../site/hebrewDateExtensions
+//= require ../site/helpers
 //= require ../site/config
 //= require ./announcements
 //= require ./arbit
 //= require ./hachrazatTaanit

 time_format = (time) -> time.format('h:mm') if time?
-round_down_to_5_minutes = (time) -> time.subtract(time.minute() % 5, 'minutes')
-minutes_before_event = (event, minutes)-> round_down_to_5_minutes(moment(event).subtract(minutes, 'minutes'))
+minutes_before_event = (event, minutes)-> NaharShalomScheduleHelpers.roundedToNearest5Minutes(moment(event).subtract(minutes, 'minutes'))
 recent_hadlakat_nerot = null;

 show_mosaei_yom_tob = (date, zmanim) -> # This code is no longer called from anywhere
@@ -69,7 +69,7 @@
   rabbenu_tam_schedule: ->
     $(".#{@chag()}.ends").removeClass("hidden").find(".time").html(@set_hakochabim())
     $(".#{@chag()}.rabbenu-tam").removeClass("hidden").find(".time").html(@rabbenu_tam()) if @hebrew_date.isShabbat() || @hebrew_date.isYomKippur()
-    half_hour_after_rabbenu_tam = round_down_to_5_minutes(moment(@sunset).add(101, 'minute'))
+    half_hour_after_rabbenu_tam = NaharShalomScheduleHelpers.roundedToNearest5Minutes(moment(@sunset).add(101, 'minute'))
     unless @hebrew_date.isShabbatZachor() && 13 == @hebrew_date.dayOfMonth
       if half_hour_after_rabbenu_tam.isBefore(@today().hour(20).minute(16))
         $(".#{@chag()}.abot-ubanim").removeClass('hidden').find(".time").html(time_format(half_hour_after_rabbenu_tam))


Remove redundant array

Remove redundant array

--- resources/assets/coffee/react/beatmap-discussions-history/main.coffee
+++ resources/assets/coffee/react/beatmap-discussions-history/main.coffee
@@ -115,28 +115,26 @@
           if @props.discussions.length == 0
             div className: 'modding-profile-list__empty', osu.trans('beatmap_discussions.index.none_found')
           else
-            [
-              for discussion in @props.discussions
-                div
-                  className: 'modding-profile-list__row'
-                  key: discussion.id,
+            for discussion in @props.discussions
+              div
+                className: 'modding-profile-list__row'
+                key: discussion.id,

-                  a
-                    className: 'modding-profile-list__thumbnail'
-                    href: BeatmapDiscussionHelper.url(discussion: discussion),
+                a
+                  className: 'modding-profile-list__thumbnail'
+                  href: BeatmapDiscussionHelper.url(discussion: discussion),

-                    img className: 'beatmapset-activities__beatmapset-cover', src: discussion.beatmapset.covers.list
+                  img className: 'beatmapset-activities__beatmapset-cover', src: discussion.beatmapset.covers.list

-                  el Discussion,
-                    discussion: discussion
-                    users: @users()
-                    currentUser: currentUser
-                    beatmapset: discussion.beatmapset
-                    isTimelineVisible: false
-                    visible: false
-                    showDeleted: true
-                    preview: true
-              ]
+                el Discussion,
+                  discussion: discussion
+                  users: @users()
+                  currentUser: currentUser
+                  beatmapset: discussion.beatmapset
+                  isTimelineVisible: false
+                  visible: false
+                  showDeleted: true
+                  preview: true

   users: =>


Some tweaks to how commands are counted, fixes #22

--- web/scripts/playerCodeEditor.coffee
+++ web/scripts/playerCodeEditor.coffee
@@ -47,10 +47,10 @@
         else
             @deleteImg = 'img/cx.png'

+        @interpreter = new CodeInterpreter @commands
         @editor = new PlayerCodeEditor 'ace-editor', \
             @commands, @codeConfig.initial, @codeConfig.show, @codeConfig.prefix, \
-            @codeConfig.postfix, @editorConfig.freeformEditting
-        @interpreter = new CodeInterpreter @commands
+            @codeConfig.postfix, @editorConfig.freeformEditting, @interpreter

         # Create editor buttons
         @acelne = document.createElement("div")
@@ -121,6 +121,7 @@
             setTimeout @moveEditorButtons, @moveEditorButtonDelay
             return
         ed.editSession.on 'changeScrollTop', updateMove
+
         normalResize = ed.editor.renderer.onResize.bind ed.editor.renderer
         addOurResize = (force, gutterWidth, width, height) ->
             normalResize(force, gutterWidth, width, height)
@@ -144,10 +145,13 @@
             When the student code changes, run it through the
             interpreter to figure out commands remaining.
         ###
-        if @scanTimer?
-            window.clearTimeout @scanTimer
-            @scanTimer = null
-        @scanTimer = window.setTimeout @scan, 500
+        if @editorConfig.freeformEditting
+            if @scanTimer?
+                window.clearTimeout @scanTimer
+                @scanTimer = null
+            @scanTimer = window.setTimeout @scan, 300
+        else
+        @UpdateCommandsStatus null
         if @onStudentCodeChangeCallback?
             @onStudentCodeChangeCallback changeData
         return
@@ -167,7 +171,10 @@
             button = buttonField.find "##{command}"
             line = @editor.createBlankFunctionHeader command

-            usesRemaining = remaining[command]
+            if remaining != null
+                usesRemaining = remaining[command]
+            else
+                usesRemaining = @commands[command]['usesRemaining']
             if usesRemaining <= 0
                 button.attr 'disabled', true
                 if usesRemaining < 0
@@ -320,7 +327,7 @@
     ###
         Creates and provides functionality for an Ace editor representing player's code.
     ###
-    constructor: (@editorDivId, @commands, codeText, @wrapCode, @codePrefix, @codeSuffix, @freeEdit) ->
+    constructor: (@editorDivId, @commands, codeText, @wrapCode, @codePrefix, @codeSuffix, @freeEdit, @interpreter) ->
         ###
             Sets internal variables, the default text and buttons
             and their event handlers.
@@ -401,10 +408,6 @@
         @editor.on 'changeSelection', callback
         return

-    # onMove: (cursorEvent) ->
-    #     if @onMoveCallback != null
-    #         @onMoveCallback cursorEvent
-
     switchUp: ({currentRow, currentColumn}) ->
         maxRow = @editSession.getLength()
         if currentRow - 1 < @codePrefixLength or currentRow >= maxRow - @codeSuffixLength
@@ -428,6 +431,8 @@
         if currentRow >= maxRow - @codeSuffixLength or currentRow < @codePrefixLength
             return
         line = text.getLine currentRow
+        command = @interpreter.identifyCommand line
+        @commands[command]['usesRemaining']++
         if text.getLength() == 1
             text.insertLines currentRow + 1, ["\n"]
             text.removeNewLine currentRow
@@ -439,6 +444,7 @@
         if currentRow + 1 < @codePrefixLength or currentRow + 1 >= maxRow - (@codeSuffixLength - 1)
             return

+        @commands[command]['usesRemaining']--
         printLine = (@createBlankFunctionHeader command) + ';'
         text.insertLines currentRow + 1, [printLine]

@@ -468,6 +474,8 @@
         @editor.clearSelection()
         @reIndentCode()
         @gotoLine @codePrefixLength + 1
+        for name, command of @commands
+            command['usesRemaining'] = command['maxUses']
         return

     reIndentCode: =>


misssed one, now htey are not hard coded

--- web/scripts/playerCodeEditor.coffee
+++ web/scripts/playerCodeEditor.coffee
@@ -341,7 +341,7 @@

     insertLine: ({text, line, currentRow}) ->
         maxRow = @editSession.getLength()
-        if currentRow + 1 <= 3 or currentRow + 1 >= maxRow - @codeSuffixLength
+        if currentRow + 1 < @codePrefixLength or currentRow + 1 >= maxRow - @codeSuffixLength
             return
         inputsDiv = jQuery('#insertButtons')


resurrection.activity: pass ActivityInputWidget::submit callback to create/update methods

--- client/Social/Activity/views/inputwidget.coffee
+++ client/Social/Activity/views/inputwidget.coffee
@@ -53,9 +53,9 @@
         data.link_url   = @embedBox.url or ""
         data.link_embed = @embedBox.getDataForSubmit() or {}

-        if activity
-        then @update data
-        else @create data
+        fn = @bound if activity then "update" else "create"
+        fn data, (err, activity) =>
+          callback? err, activity
     ]

   encodeTagSuggestions: (str, tags) ->
@@ -64,7 +64,7 @@
       return  "" unless tag
       return  "|#{prefix}:JTag:#{tag.getId()}|"

-  create: (data) ->
+  create: (data, callback) ->
     JStatusUpdate.create data, (err, activity) =>
       @reset()  unless err

@@ -78,7 +78,7 @@
           duration   : 5000
         KodingError  : 'Something went wrong while creating activity'

-  update: (data) ->
+  update: (data, callback) ->
     activity = @getData()
     return  @reset() unless activity
     activity.modify data, (err) =>


ReadmeView: htmlDecode the readme content before set

--- client/admin/lib/views/stacks/readmeview.coffee
+++ client/admin/lib/views/stacks/readmeview.coffee
@@ -1,7 +1,8 @@
 kd                      = require 'kd'
-KDCustomHTMLView        = kd.CustomHTMLView
+Encoder                 = require 'htmlencode'
+
+MarkdownEditorView      = require './editors/markdowneditorview'
 StackBaseEditorTabView  = require './stackbaseeditortabview'
-MarkdownEditorView      = require './editors/markdowneditorview'

 module.exports = class ReadmeView extends StackBaseEditorTabView
@@ -23,9 +24,11 @@

     """

+    content = if stackTemplate?.description \
+      then Encoder.htmlDecode stackTemplate?.description
+      else defaultContent
+
     @editorView   = @addSubView new MarkdownEditorView
-      content     : stackTemplate?.description or defaultContent
+      content     : content
       delegate    : this
       contentType : 'md'
-
-


managed: Explicit return to prevent erroneous promise return

--- client/app/lib/providers/managed/helpers.coffee
+++ client/app/lib/providers/managed/helpers.coffee
@@ -144,6 +144,7 @@
       heartbeat()
     .catch (err) =>
       kallback err
+  return

 module.exports = {


always send the latest position when emitting updatePosition event

Avoid sending outdated position by capturing the current position in
$scope.currentPostion on every call and sending it the when the timer
for updatePosition runs.

--- services/web/public/coffee/ide/online-users/OnlineUsersManager.coffee
+++ services/web/public/coffee/ide/online-users/OnlineUsersManager.coffee
@@ -82,13 +82,15 @@

 		sendCursorPositionUpdate: (position) ->
+			if position?
+				@$scope.currentPosition = position  # keep track of the latest position
 			if !@cursorUpdateTimeout?
 				@cursorUpdateTimeout = setTimeout ()=>
 					doc_id   = @$scope.editor.open_doc_id
-
+					# always send the latest position to other clients
 					@ide.socket.emit "clientTracking.updatePosition", {
-						row: position.row
-						column: position.column
+						row: @$scope.currentPosition?.row
+						column: @$scope.currentPosition?.column
 						doc_id: doc_id
 					}


bug fix on show me how button

bug fix on show me how button

--- client/landing/app/MainApp/book/bookview.coffee
+++ client/landing/app/MainApp/book/bookview.coffee
@@ -154,11 +154,12 @@
     if @pointer then @destroyPointer()

     # check if page has tutorial
-    if @page.getData().howToSteps.length < 1 and
-       KD.singletons.vmController.defaultVmName
+    if @page.getData().howToSteps.length < 1 or
+       KD.getSingleton("vmController").defaultVmName
       @showMeButton.hide()
     else
       @showMeButton.show()
+      debugger

   showMeButtonClicked:->
     @pointer?.destroy()


Better way to ensure 32 byte size private key buffer

--- dl/src/ecc/key_private.coffee
+++ dl/src/ecc/key_private.coffee
@@ -61,7 +61,7 @@
         @public_key = PublicKey.fromPoint @toPublicKeyPoint()

     toBuffer: ->
-        pad32 @d.toBuffer()
+        @d.toBuffer(32)

     ###* ECIES ###
     get_shared_secret:(public_key)->
@@ -92,11 +92,4 @@

     ### </helper_functions> ###

-module.exports = PrivateKey
-
-pad32 = (buf) ->
-    pad = 32 - buf.length
-    zeros = ""
-    zeros += "00" for i in [0...pad] by 1
-    Buffer.concat [new Buffer(zeros, 'hex'), buf]
-
+module.exports = PrivateKey


add team validation check case to createteam.test.coffee

--- servers/lib/server/handlers/createteam.test.coffee
+++ servers/lib/server/handlers/createteam.test.coffee
@@ -1,13 +1,15 @@
-Bongo                               = require 'bongo'
 koding                              = require './../bongo'
 { argv }                            = require 'optimist'
 KONFIG                              = require('koding-config-manager').load "main.#{argv.c}"

-{ daisy }                           = Bongo
-{ expect }                          = require 'chai'
 { hostname }                        = KONFIG
 { Relationship }                    = require 'jraphical'
-{ convertToArray
+{ hat
+  daisy
+  expect
+  request
+  querystring
+  convertToArray
   generateRandomEmail
   generateRandomString }            = require '../../../testhelper'
 { generateRegisterRequestParams }   = require '../../../testhelper/handler/registerhelper'
@@ -15,9 +17,7 @@
 { generateCreateTeamRequestBody
   generateCreateTeamRequestParams } = require '../../../testhelper/handler/teamhelper'

-hat                                 = require 'hat'
-request                             = require 'request'
-querystring                         = require 'querystring'
+reservedTeamDomains = require '../../../../workers/social/lib/social/models/user/reservedteamdomains'

 JUser                               = null
 JName                               = null
@@ -388,6 +388,48 @@
       done()

+  it 'should send HTTP 400 when slug is invalid or a reserved one', (done) ->
+
+    invalidTeamDomains = [
+      # testing invalid domains
+      '-'
+      '-domain'
+      'domain-'
+      '(domain'
+      '!domain'
+      '#domain'
+      'domain@'
+      'domain%'
+      'domain?'
+      'domain☺'
+      'domainCamelCase'
+      'domain.with.dots'
+      'domain with whitespaces'
+      # testing some reserved domains
+      reservedTeamDomains[0]
+      reservedTeamDomains[Math.round(reservedTeamDomains.length / 2)]
+      reservedTeamDomains[reservedTeamDomains.length - 1]
+    ]
+
+    queue = []
+
+    invalidTeamDomains.forEach (teamDomain) ->
+      createTeamRequestParams = generateCreateTeamRequestParams
+        body   :
+          slug : teamDomain
+
+      queue.push ->
+        request.post createTeamRequestParams, (err, res, body) ->
+          expect(err)             .to.not.exist
+          expect(res.statusCode)  .to.be.equal 400
+          expect(body)            .to.be.equal 'Invalid group slug.'
+          queue.next()
+
+    queue.push -> done()
+
+    daisy queue
+
+
   it 'should send HTTP 403 when slug is set as koding', (done) ->

     createTeamRequestParams = generateCreateTeamRequestParams
@@ -492,8 +534,7 @@
       done()

-  # TODO: this somehow returns 200, needs to be investigated
-  it.skip 'should send HTTP 400 when email is not valid', (done) ->
+  it 'should send HTTP 400 when email is not valid', (done) ->

     # setting email as random string
     createTeamRequestParams = generateCreateTeamRequestParams


only keep a single cached output directory in per-user containers

--- services/clsi/app/coffee/OutputCacheManager.coffee
+++ services/clsi/app/coffee/OutputCacheManager.coffee
@@ -45,9 +45,8 @@
 		cacheRoot = Path.join(compileDir, OutputCacheManager.CACHE_SUBDIR)
 		# Put the files into a new cache subdirectory
 		cacheDir = Path.join(compileDir, OutputCacheManager.CACHE_SUBDIR, buildId)
-
-		# let file expiry run in the background
-		OutputCacheManager.expireOutputFiles cacheRoot, {keep: buildId}
+		# Is it a per-user compile? check if compile directory is PROJECTID-USERID
+		perUser = Path.basename(compileDir).match(/^[0-9a-f]{24}-[0-9a-f]{24}$/)

 		# Archive logs in background
 		if Settings.clsi?.archive_logs or Settings.clsi?.strace
@@ -83,9 +82,15 @@
 					if err?
 						# pass back the original files if we encountered *any* error
 						callback(err, outputFiles)
+						# clean up the directory we just created
+						fse.remove cacheDir, (err) ->
+							if err?
+								logger.error err: err, dir: dir, "error removing cache dir after failure"
 					else
 						# pass back the list of new files in the cache
 						callback(err, results)
+						# let file expiry run in the background, expire all previous files if per-user
+						OutputCacheManager.expireOutputFiles cacheRoot, {keep: buildId, limit: if perUser then 0 else null}

 	archiveLogs: (outputFiles, compileDir, buildId, callback = (error) ->) ->
 		archiveDir = Path.join(compileDir, OutputCacheManager.ARCHIVE_SUBDIR, buildId)
@@ -116,6 +121,8 @@

 			isExpired = (dir, index) ->
 				return false if options?.keep == dir
+				# remove any directories over the requested (non-null) limit
+				return true if options?.limit? and index > options.limit
 				# remove any directories over the hard limit
 				return true if index > OutputCacheManager.CACHE_LIMIT
 				# we can get the build time from the first part of the directory name DDDD-RRRR


changes the project_title_spec accordingly

--- spec/javascripts/project_title_spec.js.coffee
+++ spec/javascripts/project_title_spec.js.coffee
@@ -22,7 +22,7 @@
       @projects_data = fixture.load('projects.json')[0]

       spyOn(jQuery, 'ajax').and.callFake (req) =>
-        expect(req.url).toBe('/api/v3/projects.json')
+        expect(req.url).toBe('/api/v3/projects.json?format=simple')
         d = $.Deferred()
         d.resolve @projects_data
         d.promise()


Bring back repo to caches controller

Bring back repo to caches controller

It's passed to caches-item component on caches list

--- app/controllers/caches.coffee
+++ app/controllers/caches.coffee
@@ -2,6 +2,9 @@
 `import Ajax from 'travis/utils/ajax'`

 Controller = Ember.Controller.extend
+  needs: ['repo']
+  repo: Ember.computed.alias('controllers.repo.repo')
+
   isDeleting: false

   actions:


add tests

add tests

--- src/desktop/apps/auction_support/test/client/registration_form.coffee
+++ src/desktop/apps/auction_support/test/client/registration_form.coffee
@@ -26,7 +26,7 @@
     benv.teardown()

   beforeEach (done) ->
-    sinon.stub(Backbone, 'sync')#.yieldsTo('success')
+    @submitStub = sinon.stub(Backbone, 'sync')#.yieldsTo('success')

     @order = new Order()
     @sale = new Sale fabricate 'sale'
@@ -51,8 +51,8 @@
   describe '#submit', ->

     beforeEach ->
+      @acceptTerms = => @view.$acceptTerms.prop('checked', true)
       @submitValidForm = =>
-        @view.$acceptTerms.prop('checked', true)
         @view.$('input[name="card_name"]').val 'Foo Bar'
         @view.$('select[name="card_expiration_month"]').val '1'
         @view.$('select[name="card_expiration_year"]').val '2024'
@@ -73,6 +73,7 @@
         .onCall 2
         .yieldsTo 'error', { responseJSON: { message: 'Sale is already taken.' } } # bidder creation failure

+      @acceptTerms()
       @submitValidForm()

       @view.once 'submitted', =>
@@ -115,6 +116,7 @@
         Backbone.sync.onThirdCall().yieldsTo('success')

+        @acceptTerms()
         @submitValidForm()
         @view.once "submitted", =>
           @Stripe.card.createToken.args[0][1](200, {})
@@ -138,6 +140,7 @@
         .onCall 2
         .yieldsTo 'success', {}

+      @acceptTerms()
       @submitValidForm()

       @view.once "submitted", =>
@@ -153,6 +156,7 @@
         .onCall 2
         .yieldsTo 'success', {}

+      @acceptTerms()
       @submitValidForm()

       @view.once "submitted", =>
@@ -168,6 +172,7 @@
         .onCall 2
         .yieldsTo 'success', {}

+      @acceptTerms()
       @submitValidForm()

       @view.once "submitted", =>
@@ -181,4 +186,13 @@
         Backbone.sync.args[2][1].attributes.sale_id.should.equal @sale.id
         Backbone.sync.args[2][2].url.should.containEql '/api/v1/bidder'

-        done()+        done()
+
+    it 'does not submit the form if Conditions of Sale are not accepted', ->
+      spy = sinon.spy(@submitStub)
+      @submitValidForm()
+      spy.called.should.be.false()
+
+    it 'adds an error class on submit if Conditions of Sale are not accepted', ->
+      @submitValidForm()
+      @view.$('.artsy-checkbox').hasClass('error').should.be.true()


Use a factory function for exposing module

--- src/bootstrap-dialogs.coffee
+++ src/bootstrap-dialogs.coffee
@@ -1,189 +1,187 @@
-if require?
-  $ = require('jquery')
-  Bootstrap = require('bootstrap')
-else
-  $ = @jQuery
-  Bootstrap = @Bootstrap or= {}
-
-if not module?
-  module = exports: exports = {}
-  Bootstrap.Dialogs = exports
+factory = (exports, $) ->
+  RETURN = 13
+  ESC = 27

-RETURN = 13
-ESC = 27
-
-
-mkbutton = (text, btnClass) ->
-  $btn = $('<button type="button" class="btn">').html(text)
-  $btn.addClass("btn-#{btnClass}") if btnClass
-  $btn
-
-
-normalizeButtons = (buttons) ->
-  for button in buttons
-    handler = null
-
-    if button instanceof Array
-      handler = button[1]
-      button = button[0]
-
-    if typeof button is 'string'
-      $btn = mkbutton(button)
-    else
-      $btn = $(button)
-
-    $btn.click(handler) if handler instanceof Function
+  mkbutton = (text, btnClass) ->
+    $btn = $('<button type="button" class="btn">').html(text)
+    $btn.addClass("btn-#{btnClass}") if btnClass
     $btn

-exports.alert = (options={}) ->
-  defaultOptions =
-    title: 'Alert'
-    ok: 'OK'
-    lock: true
-    danger: false
-  options = $.extend(defaultOptions, options)
+  normalizeButtons = (buttons) ->
+    for button in buttons
+      handler = null

-  okClass = if options.danger then 'danger' else 'primary'
-  promise = exports.dialog
-    title: options.title
-    body: options.body
-    lock: options.lock
-    buttons: [
-      [ mkbutton(options.ok, okClass), -> promise.resolve() ]
-    ]
+      if button instanceof Array
+        handler = button[1]
+        button = button[0]

-  returnHandler = (e) -> promise.resolve() if e.which is RETURN
-  $('body').on('keyup', returnHandler)
-  promise.always ->
-    $('body').off('keyup', returnHandler)
+      if typeof button is 'string'
+        $btn = mkbutton(button)
+      else
+        $btn = $(button)
+
+      $btn.click(handler) if handler instanceof Function
+      $btn

-exports.confirm = (options={}) ->
-  defaultOptions =
-    title: 'Please confirm'
-    ok: 'OK'
-    cancel: 'Cancel'
-    danger: false
-  options = $.extend(defaultOptions, options)
+  exports.alert = (options={}) ->
+    defaultOptions =
+      title: 'Alert'
+      ok: 'OK'
+      lock: true
+      danger: false
+    options = $.extend(defaultOptions, options)

-  okClass = if options.danger then 'danger' else 'primary'
+    okClass = if options.danger then 'danger' else 'primary'
+    promise = exports.dialog
+      title: options.title
+      body: options.body
+      lock: options.lock
+      buttons: [
+        [ mkbutton(options.ok, okClass), -> promise.resolve() ]
+      ]

-  promise = exports.dialog
-    title: options.title
-    body: options.body
-    buttons: [
-      [ options.cancel, -> promise.reject() ]
-      [ mkbutton(options.ok, okClass), -> promise.resolve() ]
-    ]
-
-  if options.return
     returnHandler = (e) -> promise.resolve() if e.which is RETURN
     $('body').on('keyup', returnHandler)
     promise.always ->
       $('body').off('keyup', returnHandler)

-  promise
+
+  exports.confirm = (options={}) ->
+    defaultOptions =
+      title: 'Please confirm'
+      ok: 'OK'
+      cancel: 'Cancel'
+      danger: false
+    options = $.extend(defaultOptions, options)
+
+    okClass = if options.danger then 'danger' else 'primary'
+
+    promise = exports.dialog
+      title: options.title
+      body: options.body
+      buttons: [
+        [ options.cancel, -> promise.reject() ]
+        [ mkbutton(options.ok, okClass), -> promise.resolve() ]
+      ]
+
+    if options.return
+      returnHandler = (e) -> promise.resolve() if e.which is RETURN
+      $('body').on('keyup', returnHandler)
+      promise.always ->
+        $('body').off('keyup', returnHandler)
+
+    promise

-exports.dialog = (options={}) ->
-  title = options.title
-  body = options.body
-  buttons = options.buttons or []
+  exports.dialog = (options={}) ->
+    title = options.title
+    body = options.body
+    buttons = options.buttons or []

-  titleEls = [ $('
').html(title) ]
-  if not options.lock
-    $closeButton = $('''
-      <button type="button" class="close" data-dismiss="modal"
-        aria-hidden="true">×</button>
-    ''')
-    titleEls.unshift($closeButton)
+    titleEls = [ $('
').html(title) ]
+    if not options.lock
+      $closeButton = $('''
+        <button type="button" class="close" data-dismiss="modal"
+          aria-hidden="true">×</button>
+      ''')
+      titleEls.unshift($closeButton)

-  $el = $('
').html([
-    $('
').html(titleEls)
-    if body
-      $('
').html(body)
+    $el = $('
').html([
+      $('
').html(titleEls)
+      if body
+        $('
').html(body)
+      else
+        ''
+      $('
').html(
+        normalizeButtons(buttons)
+      )
+    ])
+
+    promise = $.Deferred()
+    promise.el = $el[0]
+    promise.$el = $el
+
+    $el.on 'hidden', ->
+      if promise.state() is 'pending'
+        promise.reject()
+
+    if not options.lock
+      escHandler = (e) -> promise.reject() if e.which is ESC
+
+    promise.always ->
+      exports.enableScrolling()
+      $('body').off('keyup', escHandler) if escHandler
+      $el.modal('hide')
+      $el.remove()
+
+    $('body').on('keyup', escHandler) if escHandler
+
+    if options.lock
+      $el.modal(backdrop: 'static', keyboard: false)
     else
-      ''
-    $('
').html(
-      normalizeButtons(buttons)
-    )
-  ])
+      $el.modal(keyboard: false)

-  promise = $.Deferred()
-  promise.el = $el[0]
-  promise.$el = $el
-
-  $el.on 'hidden', ->
-    if promise.state() is 'pending'
-      promise.reject()
-
-  if not options.lock
-    escHandler = (e) -> promise.reject() if e.which is ESC
-
-  promise.always ->
-    exports.enableScrolling()
-    $('body').off('keyup', escHandler) if escHandler
-    $el.modal('hide')
-    $el.remove()
-
-  $('body').on('keyup', escHandler) if escHandler
-
-  if options.lock
-    $el.modal(backdrop: 'static', keyboard: false)
-  else
-    $el.modal(keyboard: false)
-
-  exports.disableScrolling()
-  promise
+    exports.disableScrolling()
+    promise

-exports.disableScrolling = ->
-  $('html').css
-    position: 'fixed'
-    top: - Math.abs($(window.document).scrollTop())
-    width: '100%'
-  undefined
+  exports.disableScrolling = ->
+    $('html').css
+      position: 'fixed'
+      top: - Math.abs($(window.document).scrollTop())
+      width: '100%'
+    undefined

-exports.enableScrolling = ->
-  offset = Math.abs(parseInt($('html').css('top')))
-  $('html').css
-    position: 'static'
-    top: 'auto'
-  $(window.document).scrollTop(offset)
-  undefined
+  exports.enableScrolling = ->
+    offset = Math.abs(parseInt($('html').css('top')))
+    $('html').css
+      position: 'static'
+      top: 'auto'
+    $(window.document).scrollTop(offset)
+    undefined

-exports.prompt = (options={}) ->
-  defaultOptions =
-    title: 'Please enter a value'
-    body: ''
-    ok: 'OK'
-    cancel: 'Cancel'
-  options = $.extend(defaultOptions, options)
+  exports.prompt = (options={}) ->
+    defaultOptions =
+      title: 'Please enter a value'
+      body: ''
+      ok: 'OK'
+      cancel: 'Cancel'
+    options = $.extend(defaultOptions, options)

-  okClass = if options.danger then 'danger' else 'primary'
+    okClass = if options.danger then 'danger' else 'primary'

-  resolve = -> promise.resolve($input.val())
-  reject = -> promise.reject()
-  keyup = (e) -> resolve() if e.which is RETURN
+    resolve = -> promise.resolve($input.val())
+    reject = -> promise.reject()
+    keyup = (e) -> resolve() if e.which is RETURN

-  $input = $('<input type="text">')
+    $input = $('<input type="text">')

-  promise = exports.dialog
-    title: options.title
-    body: [ options.body, $input ]
-    buttons: [
-      [ options.cancel, reject ]
-      [ mkbutton(options.ok, okClass), resolve ]
-    ]
+    promise = exports.dialog
+      title: options.title
+      body: [ options.body, $input ]
+      buttons: [
+        [ options.cancel, reject ]
+        [ mkbutton(options.ok, okClass), resolve ]
+      ]

-  $('body').on('keyup', keyup)
-  promise.always ->
-    $('body').off('keyup', keyup)
+    $('body').on('keyup', keyup)
+    promise.always ->
+      $('body').off('keyup', keyup)

-  $input.focus()
-  promise.$input = $input
-  promise
+    $input.focus()
+    promise.$input = $input
+    promise
+
+
+do ->
+  if typeof exports is 'object'
+    factory(exports, require('jquery'))
+  else
+    Bootstrap = @Bootstrap or= {}
+    Bootstrap.Dialogs = {}
+    factory(Bootstrap.Dialogs, @jQuery)


Fix README syntax

Fix README syntax

- observable.take was repeated (not parallel with other fn definitions) - removed duplicate.
- Equals to -> Equal to

--- readme-src.coffee
+++ readme-src.coffee
@@ -463,8 +463,8 @@
 """

 doc.fn "observable.take(@ : Observable[A], n : Number) : Observable[A]", """
-`observable.take(n)` takes at most n elements from the stream. Equals to
-`Bacon.never()` if `n <= 0`.
+takes at most n elements from the stream.
+Equal to `Bacon.never()` if `n <= 0`.
 """

 doc.fn "observable.takeUntil(@ : Observable[A], stream : EventStream[B]) : Observable[A]", """


rawr, jk

rawr, jk

--- client/apps/edit/components/section_text/index.coffee
+++ client/apps/edit/components/section_text/index.coffee
@@ -3,12 +3,15 @@
 # between empty paragraphs to insert a new section.
 #

-Scribe = -> require('scribe-editor') arguments...
-scribePluginToolbar = -> require('scribe-plugin-toolbar') arguments...
-scribePluginSanitizer = -> require('../../lib/sanitizer.coffee') arguments...
-scribePluginLinkTooltip = -> require('scribe-plugin-link-tooltip') arguments...
-scribePluginKeyboardShortcuts = -> require('scribe-plugin-keyboard-shortcuts') arguments...
-scribePluginHeadingCommand = -> require('scribe-plugin-heading-command') arguments...
+# Using `try` here b/c Scribe is an AMD module that doesn't play nice when
+# requiring it for testing in node.
+try
+  Scribe = require 'scribe-editor'
+  scribePluginToolbar = require 'scribe-plugin-toolbar'
+  scribePluginSanitizer = require '../../lib/sanitizer.coffee'
+  scribePluginLinkTooltip = require 'scribe-plugin-link-tooltip'
+  scribePluginKeyboardShortcuts = require 'scribe-plugin-keyboard-shortcuts'
+  scribePluginHeadingCommand = require 'scribe-plugin-heading-command'
 React = require 'react'
 icons = -> require('./icons.jade') arguments...
 { div, nav, button } = React.DOM


chat: fix that chatroom scrolls to top every time leave page

--- salvus/page/editor_chat.cjsx
+++ salvus/page/editor_chat.cjsx
@@ -281,6 +281,10 @@
             return
         @_scrolled = true
         e.preventDefault()
+
+    componentDidMount: ->
+        if not @_scrolled
+            @scroll_to_bottom()

     componentDidUpdate: ->
         if not @_scrolled


ipython notebook: the --> your in a message

--- salvus/page/editor.coffee
+++ salvus/page/editor.coffee
@@ -4610,7 +4610,7 @@
     initialize: (cb) =>
         async.series([
             (cb) =>
-                @status("Connecting to the IPython Notebook server")
+                @status("Connecting to your IPython Notebook server")
                 ipython_notebook_server
                     project_id : @editor.project_id
                     cb         : (err, server) =>


Fixed teaspoon test

Fixed teaspoon test

--- spec/javascripts/fixtures/r2_calculator_spec.coffee
+++ spec/javascripts/fixtures/r2_calculator_spec.coffee
@@ -1,10 +1,10 @@
-//=require income_calculator
+//=require 'income_calculator'

 describe "income_calculator", ->

   it 'will convert numbers to currency', ->
     calc = new incomeCalculator
-    expect(calc.formatCurrency(12)).toBe('£12.00')
+    expect(calc.formatCurrency(12)).toBe('£12')

   describe 'calculator', ->
     it 'will return a json object', ->
@@ -23,7 +23,6 @@
         match = calc.calculate(t.fee, t.married_status, t.children, t.income)
         expect(match.type).toEqual(t.type)
         expect(match.they_pay).toEqual(t.to_pay)
-

 seed_data = [
   { fee: '100', married_status: true, children: '0', income: '100', remit: '100', they_pay: '0', type: 'full', },


fix(bodies): Message with empty string body showed loading forever

--- internal_packages/message-list/lib/message-item-body.cjsx
+++ internal_packages/message-list/lib/message-item-body.cjsx
@@ -51,7 +51,7 @@
     </span>

   _renderBody: =>
-    if _.isString(@props.message.body) and @state.processedBody
+    if _.isString(@props.message.body) and _.isString(@state.processedBody)
       <EmailFrame showQuotedText={@state.showQuotedText} content={@state.processedBody}/>
     else if @state.error
       <div className="message-body-error">


fix(onboarding): encode welcome page info

--- internal_packages/onboarding/lib/page-router-store.coffee
+++ internal_packages/onboarding/lib/page-router-store.coffee
@@ -29,11 +29,7 @@
     if isFirstAccount
       @_onMoveToPage('initial-preferences', {account: json})
       Actions.recordUserEvent('First Account Linked')
-      account = AccountStore.accounts()[0]
-      n1_id = NylasEnv.config.get("updateIdentity")
-      params = "?n1_id=#{n1_id}&email_address=#{account.emailAddress}&provider=#{account.provider}"
-      {shell} = require('electron')
-      shell.openExternal("https://nylas.com/welcome#{params}", activate: false)
+      @openWelcomePage()
     else
       # When account JSON is received, we want to notify external services
       # that it succeeded. Unfortunately in this case we're likely to
@@ -46,6 +42,16 @@

   _onWindowPropsChanged: ({page, pageData}={}) =>
     @_onMoveToPage(page, pageData)
+
+  openWelcomePage: ->
+    encode = (str) -> encodeURIComponent(new Buffer(str).toString('base64'))
+    account = AccountStore.accounts()[0]
+    n1_id = encode(NylasEnv.config.get("updateIdentity"))
+    email = encode(account.emailAddress)
+    provider = encode(account.provider)
+    params = "?n=#{n1_id}&e=#{email}&p=#{provider}"
+    {shell} = require('electron')
+    shell.openExternal("https://nylas.com/welcome#{params}", activate: false)

   page: -> @_page


Modifing html & css classes

Modifing html & css classes

--- textroller.coffee
+++ textroller.coffee
@@ -1,7 +1,7 @@
-VALUE_HTML = ''
-RIBBON_HTML = '' + VALUE_HTML + ''
-DIGIT_HTML = '8' + RIBBON_HTML + ''
-FORMAT_MARK_HTML = ''
+VALUE_HTML = ''
+RIBBON_HTML = '' + VALUE_HTML + ''
+DIGIT_HTML = '8' + RIBBON_HTML + ''
+FORMAT_MARK_HTML = ''

 # Special values for alpha frames

@@ -88,10 +88,10 @@
       do (property) ->
         old = window.jQuery.fn[property]
         window.jQuery.fn[property] = (val) ->
-          if not val? or not this[0]?.odometer?
+          if not val? or not this[0]?.textroller?
             return old.apply this, arguments

-          this[0].odometer.update val
+          this[0].textroller.update val

 # In case jQuery is brought in after this file
 setTimeout wrapJQuery, 0
@@ -99,9 +99,9 @@
 class TextRoller
   constructor: (@options) ->
     @el = @options.el
-    return @el.odometer if @el.odometer?
-
-    @el.odometer = @
+    return @el.textroller if @el.textroller?
+
+    @el.textroller = @

     for k, v in TextRoller.options
       if not @options[k]?
@@ -134,7 +134,7 @@

   renderInside: ->
     @inside = document.createElement 'div'
-    @inside.className = 'odometer-inside'
+    @inside.className = 'textroller-inside'
     @el.innerHTML = ''
     @el.appendChild @inside

@@ -232,26 +232,26 @@
     classes = @el.className.split(' ')
     newClasses = []
     for cls in classes when cls.length
-      if match = /^odometer-theme-(.+)$/.exec(cls)
+      if match = /^textroller-theme-(.+)$/.exec(cls)
         theme = match[1]
         continue

-      if /^odometer(-|$)/.test(cls)
+      if /^textroller(-|$)/.test(cls)
         continue

       newClasses.push cls

-    newClasses.push 'odometer'
+    newClasses.push 'textroller'

     unless TRANSITION_SUPPORT
-      newClasses.push 'odometer-no-transitions'
+      newClasses.push 'textroller-no-transitions'

     if theme
-      newClasses.push "odometer-theme-#{ theme }"
+      newClasses.push "textroller-theme-#{ theme }"
     else
       # This class matches all themes, so it should do what you'd expect if only one
       # theme css file is brought into the page.
-      newClasses.push "odometer-auto-theme"
+      newClasses.push "textroller-auto-theme"

     @el.className = newClasses.join(' ')

@@ -276,9 +276,9 @@
     console.log "[noreturn ! :D]"

     if newValue > @value
-      @el.className += ' odometer-animating-up'
+      @el.className += ' textroller-animating-up'
     else
-      @el.className += ' odometer-animating-down'
+      @el.className += ' textroller-animating-down'

     @stopWatchingMutations()
     @animate newValue
@@ -287,7 +287,7 @@
     setTimeout =>
       @el.offsetHeight

-      @el.className += ' odometer-animating'
+      @el.className += ' textroller-animating'
     , 0

     @value = newValue
@@ -311,17 +311,17 @@

   addDigit: (value, repeating=true) ->
     if value is '-'
-      return @addSpacer value, null, 'odometer-negation-mark'
+      return @addSpacer value, null, 'textroller-negation-mark'

     if value is @format.radix
-      return @addSpacer value, null, 'odometer-radix-mark'
+      return @addSpacer value, null, 'textroller-radix-mark'

     if repeating
       resetted = false
       while true
         if not @format.repeating.length
           if resetted
-            throw new Error "Bad odometer format without digits"
+            throw new Error "Bad textroller format without digits"

           @resetFormat()
           resetted = true
@@ -336,7 +336,7 @@
     console.log "[after repeatin@addDigit] value = " + value
     digit = @renderDigit()
     console.log "[after renderDigit@addDigit] value = " + value
-    digit.querySelector('.odometer-value').innerHTML = @intToChar value
+    digit.querySelector('.textroller-value').innerHTML = @intToChar value
     @digits.push digit

     @insertDigit digit
@@ -510,7 +510,7 @@
       console.log @digits
         #@addDigit ' ', (i >= fractionalCount)

-      @ribbons[i] ?= @digits[i].querySelector('.odometer-ribbon-inner')
+      @ribbons[i] ?= @digits[i].querySelector('.textroller-ribbon-inner')
       @ribbons[i].innerHTML = ''
       nullArray = []
       for tr in [0..digitCount-1]
@@ -522,30 +522,30 @@
         console.log "   [#]["+i+"]["+j+"][frame:"+frame+"][frames ="
         console.log frames
         numEl = document.createElement('div')
-        numEl.className = 'odometer-value'
+        numEl.className = 'textroller-value'
         numEl.innerHTML = @intToChar frame

         @ribbons[i].appendChild numEl

         if j == frames.length - 1
-          numEl.className += ' odometer-last-value'
+          numEl.className += ' textroller-last-value'
         if j == 0
-          numEl.className += ' odometer-first-value'
+          numEl.className += ' textroller-first-value'
         console.log "   [#]"

-    mark = @inside.querySelector('.odometer-radix-mark')
+    mark = @inside.querySelector('.textroller-radix-mark')
     mark.parent.removeChild(mark) if mark?

     if fractionalCount
-      @addSpacer @format.radix, @digits[fractionalCount - 1], 'odometer-radix-mark'
-
-TextRoller.options = window.odometerOptions ? {}
+      @addSpacer @format.radix, @digits[fractionalCount - 1], 'textroller-radix-mark'
+
+TextRoller.options = window.textrollerOptions ? {}

 setTimeout ->
   # We do this in a seperate pass to allow people to set
-  # window.odometerOptions after bringing the file in.
-  if window.odometerOptions
-    for k, v of window.odometerOptions
+  # window.textrollerOptions after bringing the file in.
+  if window.textrollerOptions
+    for k, v of window.textrollerOptions
       TextRoller.options[k] ?= v
 , 0

@@ -554,10 +554,10 @@
     # IE 7 or 8 in Quirksmode
     return

-  elements = document.querySelectorAll (TextRoller.options.selector or '.odometer')
+  elements = document.querySelectorAll (TextRoller.options.selector or '.textroller')

   for el in elements
-    el.odometer = new TextRoller {el, value: el.innerText}
+    el.textroller = new TextRoller {el, value: el.innerText}

 if document.documentElement?.doScroll? and document.createEventObject?
   # IE < 9


Use improved selection iteration in onSelectionChange

Also, remove redundant check in getValue since we prevent the first
value from being selected.

--- src/coffee/cilantro/ui/controls/select.coffee
+++ src/coffee/cilantro/ui/controls/select.coffee
@@ -69,11 +69,9 @@
             @render()

         onSelectionChange: (event) ->
-            option = @ui.items.children[0]
-
-            @collection.each (model) ->
-                if event.target isnt option
-                    model.set('selected', model.get('value') == event.target.value)
+            @ui.items.children().each (i, el) =>
+                if i is 0 then return
+                @collection.findWhere(value: el.value).set('selected', el.selected)

             @change()

@@ -89,8 +87,7 @@
         getValue: ->
             selection = @collection.findWhere(selected: true)

-            # We don't want to include the placeholder as a valid value
-            if selection? and @collection.indexOf(selection) > 0
+            if selection?
                 return selection.get('value')

             return null


Social: remove setting amqp into tracker

--- workers/social/lib/social/main.coffee
+++ workers/social/lib/social/main.coffee
@@ -126,10 +126,8 @@
   if KONFIG.misc?.claimGlobalNamesForUsers
     require('./models/account').reserveNames console.log

+  { forcedRecipientEmail } = KONFIG.email
   Tracker = require './models/tracker'
-  Tracker.setMqClient broker.connection
-
-  { forcedRecipientEmail } = KONFIG.email
   Tracker.identify forcedRecipientEmail  if forcedRecipientEmail


Reference session.data directly on cilantro object

This is to provide consistent access like the session router and the
session itself.

--- src/coffee/cilantro.coffee
+++ src/coffee/cilantro.coffee
@@ -18,5 +18,6 @@
     c.sessions.on session.SESSION_OPENED, (session) ->
         c.session = session
         c.router = session.router
+        c.data = session.data

     return (@cilantro = c)


Added a little comment

Added a little comment

--- src/coffee/wanakana.coffee
+++ src/coffee/wanakana.coffee
@@ -138,7 +138,7 @@
       if chunkLC is "wi" then kanaChar = "ゐ"
       if chunkLC is "we" then kanaChar = "ゑ"

-    # Use katakana if first letter is uppercase
+    # Use katakana if first letter in chunk is uppercase
     unless ignoreCase
       if isCharUpperCase(chunk.charAt(0))
         kanaChar = wanakana._hiraganaToKatakana(kanaChar)


Dynamic name property of corpses, diff. name

--- src/definitions/items.coffee
+++ src/definitions/items.coffee
@@ -16,7 +16,8 @@
 	symbol: '%'

 	constructor: (@species) ->
-		@name = "#{@species.name} corpse" if @species?
+		Object.defineProperty @, 'name',
+			get: => "corpse of #{@species.name}"

 class exports.Gun extends Item
 	name: 'gun'


DEMO: ListViewTree: change appearance of buttons.

--- demo/src/demos/ListView/ListViewTreeDemo.coffee
+++ demo/src/demos/ListView/ListViewTreeDemo.coffee
@@ -335,8 +335,9 @@
 			cols: ((if idx == 1 then "auto" else "auto") for c, idx in columns)
 			footer_left: new CUI.Buttonbar buttons: [
 				new CUI.Button
-					group: "plus-minus"
 					icon: new CUI.Icon(class: "fa-plus")
+					appearance: "flat"
+					size: "mini"
 					onClick: ->
 						columns = __getColumns(-> ""+Math.floor(Math.random()*1000))
 						lv.appendRow(new CUI.ListViewRow(columns: columns))
@@ -344,10 +345,11 @@
 						lb.enable()

 				lb = new CUI.Button
-					group: "plus-minus"
 					disabled: true
 					icon: new CUI.Icon(class: "fa-minus")
 					text: "Last"
+					appearance: "flat"
+					size: "mini"
 					onClick: ->
 						if lv.rowsCount > 1
 							lv.removeRow(lv.getRowIdx(lv.rowsCount-1))


engine: fix test

engine: fix test

--- packages/engine/test/actions/fs/glob.coffee
+++ packages/engine/test/actions/fs/glob.coffee
@@ -18,9 +18,9 @@
       @fs.base.mkdir "#{tmpdir}/test/a_dir"
       @fs.base.writeFile "#{tmpdir}/test/a_dir/a_file", content: ''
       {files} = await @fs.glob "#{tmpdir}/test/*"
-      files.should.eql [
+      files.sort().should.eql [
+        path.normalize "#{tmpdir}/test/a_dir"
         path.normalize "#{tmpdir}/test/a_file"
-        path.normalize "#{tmpdir}/test/a_dir"
       ]

   they 'should traverse a directory recursively', ({ssh}) ->
@@ -34,12 +34,12 @@
       @fs.base.mkdir "#{tmpdir}/test/a_dir/a_sub_dir"
       @fs.base.writeFile "#{tmpdir}/test/a_dir/a_sub_dir/a_file", content: ''
       {files} = await @fs.glob "#{tmpdir}/test/**"
-      files.should.eql [
+      files.sort().should.eql [
         "#{tmpdir}/test"
-        "#{tmpdir}/test/a_file"
         "#{tmpdir}/test/a_dir"
         "#{tmpdir}/test/a_dir/a_sub_dir"
         "#{tmpdir}/test/a_dir/a_sub_dir/a_file"
+        "#{tmpdir}/test/a_file"
       ]
       # Default behavior
       (new Minimatch('/a_dir/**').match '/a_dir/').should.be.true()
@@ -54,7 +54,7 @@
       @fs.base.mkdir "#{tmpdir}/test/a_dir"
       @fs.base.writeFile "#{tmpdir}/test/a_dir/a_file.coffee", content: ''
       {files} = await @fs.glob "#{tmpdir}/test/*.coffee"
-      files.should.eql [
+      files.sort().should.eql [
         path.normalize "#{tmpdir}/test/a_file.coffee"
       ]

@@ -80,7 +80,7 @@
       tmpdir: true
     , ({metadata: {tmpdir}}) ->
       {files} = await @fs.glob "#{tmpdir}/invalid/*.coffee"
-      files.should.eql []
+      files.sort().should.eql []

   they 'config `dot`', ({ssh}) ->
     nikita
@@ -109,9 +109,9 @@
       @fs.base.mkdir "#{tmpdir}/test/a_dir"
       @fs.base.writeFile "#{tmpdir}/test/a_dir/a_file", content: ''
       {files} = await @fs.glob "#{tmpdir}/test/**", trailing: true
-      files.should.eql [
+      files.sort().should.eql [
         "#{tmpdir}/test/"
-        "#{tmpdir}/test/a_file"
         "#{tmpdir}/test/a_dir/"
         "#{tmpdir}/test/a_dir/a_file"
+        "#{tmpdir}/test/a_file"
       ]


cleanup

cleanup

--- src/core/views/base.coffee
+++ src/core/views/base.coffee
@@ -58,7 +58,6 @@
       @init()
       @_listen()

-
     ###
     ###

@@ -94,10 +93,7 @@
      returns a search for a particular element
     ###

-    $: (search) ->
-
-      # otherwise - only look within this element
-      @el?.find search
+    $: (search) -> @el?.find search

     ###
      attaches to an element
@@ -116,7 +112,6 @@
       @selector = selectorOrElement
       @

-
     ###
     ###

@@ -127,7 +122,6 @@

       # also send it to the element
       @el?.trigger.apply @el, arguments
-

     ###
     ###


Subtract preview list offset from top

Subtract preview list offset from top

--- src/packages/command-panel/lib/preview-list.coffee
+++ src/packages/command-panel/lib/preview-list.coffee
@@ -123,9 +123,7 @@
     @operations[@selectedOperationIndex]

   scrollToElement: (element) ->
-    top = @scrollTop() +
-      element.position().top +
-      element.closest('li.path').position().top
+    top = @scrollTop() + element.offset().top - @offset().top
     bottom = top + element.outerHeight()

     @scrollBottom(bottom) if bottom > @scrollBottom()


Use underlayer property on editor

Use underlayer property on editor

Removes unneeded call to find

--- src/packages/wrap-guide/lib/wrap-guide-view.coffee
+++ src/packages/wrap-guide/lib/wrap-guide-view.coffee
@@ -6,11 +6,7 @@
 class WrapGuideView extends View
   @activate: ->
     rootView.eachEditor (editor) =>
-      @appendToEditorPane(rootView, editor) if editor.attached
-
-  @appendToEditorPane: (rootView, editor) ->
-    if underlayer = editor.find('.underlayer')
-      underlayer.append(new WrapGuideView(editor))
+      editor.underlayer.append(new WrapGuideView(editor)) if editor.attached

   @content: ->
     @div class: 'wrap-guide'


Chat: Reset padding on fixed excluded area height

--- client/Social/Activity/views/privatemessage/pane.coffee
+++ client/Social/Activity/views/privatemessage/pane.coffee
@@ -495,13 +495,15 @@
   resetPadding: ->

     listView = @listController.getView()
-    headerHeight = @heads?.getHeight() or 0
-
-    contentIsShort = window.innerHeight - headerHeight < listView.getHeight()
-
-    return  unless contentIsShort
-
-    listView.unsetClass 'padded'
+
+    freeAreaHeight = @getHeight() - @excludedAreaHeight
+    contentHeight  = listView.getHeight()
+
+    if freeAreaHeight < contentHeight
+      listView.unsetClass 'padded'
+
+
+  excludedAreaHeight: 85

   fetchAccounts: PrivateMessageForm::fetchAccounts


progress view appends to dom lazily

progress view appends to dom lazily

--- client/app/MainApp/filetree/itemviews/finderitem.coffee
+++ client/app/MainApp/filetree/itemviews/finderitem.coffee
@@ -10,6 +10,7 @@
     @isLoading        = no
     @beingDeleted     = no
     @beingEdited      = no
+    @beingProgress    = no

     childConstructor = switch data.type
       when "vm"         then NVMItemView
@@ -26,8 +27,6 @@
       @childView.setDomAttributes
         title : FSHelper.plainPath data.name

-    @progress = new KDProgressBarView
-
     @on "ItemBeingDeleted", =>
       data.removeLocalFileInfo()

@@ -35,11 +34,8 @@
       fileInfo = data.getLocalFileInfo()
       if fileInfo.lastUploadedChunk
         {lastUploadedChunk, totalChunks} = fileInfo
-        if lastUploadedChunk is totalChunks
-          data.removeLocalFileInfo()
-          @updateProgressView 100
-        else
-          @updateProgressView 100 * lastUploadedChunk / totalChunks
+        data.removeLocalFileInfo() if lastUploadedChunk is totalChunks
+        @showProgressView 100 * lastUploadedChunk / totalChunks

   mouseDown:-> yes

@@ -53,11 +49,16 @@
       @renameView.destroy()
       delete @renameView

+    if @progressView
+      @progressView.destroy()
+      delete @progressView
+
     @childView.show()
     @beingDeleted = no
     @beingEdited = no
+    @beingProgress = no
     @callback = null
-    @unsetClass "being-deleted being-edited"
+    @unsetClass "being-deleted being-edited progress"
     @getDelegate().setKeyView()

   confirmDelete:(callback)->
@@ -93,16 +94,20 @@
       @resetView()
     @renameView.input.setFocus()

-  updateProgressView: (percent=0, determinate=yes)->
-    @progress.setOption "determinate", determinate
-    @progress.updateBar percent, "%", ""
+  showProgressView: (percent=0, determinate=yes)->
+
+    unless @progressView
+      @addSubView @progressView = new KDProgressBarView
+
+    @progressView.setOption "determinate", determinate
+    @progressView.updateBar percent, "%", ""
     if 0 <= percent < 100
-    then @setClass   "progress"
-    else @utils.wait 1000, => @unsetClass "progress"
+    then @setClass "progress"
+    else @utils.wait 1000, =>
+      @resetView()

   pistachio:->

     """
     {{> @childView}}
-    {{> @progress}}
     """


:art: newline

:art: newline

--- spec/results-view-spec.coffee
+++ spec/results-view-spec.coffee
@@ -279,7 +279,6 @@
         runs ->
           resultsView = getResultsView()

-
       it "renders all results and selects the last item when core:move-to-bottom is triggered; selects the first item when core:move-to-top is triggered", ->
         expect(resultsView.find("li").length).toBeLessThan resultsView.getPathCount() + resultsView.getMatchCount()


Fix the target progress bar

Fix the target progress bar

--- static/src/scripts/app/views/IndexStatsView.coffee
+++ static/src/scripts/app/views/IndexStatsView.coffee
@@ -12,11 +12,12 @@
     template: jade.indexStats
     events:
       'click .donate-button': 'donate'
+    target: 50000 # targeted amount in euros

     render: =>
       loadingContext = @getRenderContext()

-      percent = (((@model.get('amountRaised') / 100) / 100000) * 100) or 0
+      percent = (((@model.get('amountRaised') / 100) / @target) * 100) or 0
       percentWidth = if (percent > 100) then 100 else percent

       context =


engine_save_state

--- src/engine_save_state.coffee
+++ src/engine_save_state.coffee
@@ -4,7 +4,6 @@
 @name SaveState
 @fieldOf Engine
 @module
-
 @param {Object} I Instance variables
 @param {Object} self Reference to the engine
 ###


never let mutation rate drop to 0

never let mutation rate drop to 0

--- evolution.coffee
+++ evolution.coffee
@@ -609,8 +609,8 @@
         (t) -> t.radius * t.dispersalMin()
         (t) -> t.radius * t.dispersalMax()
       ]
-      mutationRate: [ .1, 0, 1 ]
-      mutationRange: [ .1, 0, 1 ]
+      mutationRate: [ .1, 0.01, 1 ]
+      mutationRange: [ .1, 0.01, 1 ]
     }
   # some things to control how far away from its mother a baby can be "born"
   dispersalStart: -> 5


router middleware

router middleware

--- app/store.coffee
+++ app/store.coffee
@@ -1,13 +1,15 @@
 # Configures the store.

 {createStore, applyMiddleware} = require 'redux'
-thunk = require 'redux-thunk'
+{browserHistory} = require 'react-router'
+{routerMiddleware} = require 'react-router-redux'
+{default: thunk} = require 'redux-thunk'

 reducers = require 'reducers'
 {fetchLibs} = require 'actions/async'

 store = createStore reducers,
-  applyMiddleware thunk.default
+  applyMiddleware thunk, routerMiddleware browserHistory

 # Dispatch an initial fetch.
 store.dispatch fetchLibs()


Change home key to 'O' to fix conflict with voxel-debug 'H' to show/hide debug window

--- index.coffee
+++ index.coffee
@@ -112,7 +112,7 @@

       game.currentMaterial = slot

-    else if ev.keyCode == 'H'.charCodeAt(0)
+    else if ev.keyCode == 'O'.charCodeAt(0)
       home(game.avatar)
     else if ev.keyCode == 'C'.charCodeAt(0)
       # TODO: add gamemode event? for plugins to handle instead of us


Export CoCModuleLoader as Loader.

Export CoCModuleLoader as Loader.

--- index.coffee
+++ index.coffee
@@ -1,4 +1,5 @@
 module.exports =
   Helper: require './lib/Helper'
   Runner: require './lib/Runner'
+  Loader: require './lib/loader/CoCModuleLoader'
   beans:  require './lib/beans/index'


Added "stop" method to "AsyncIterator" and Added "Publisher" class

--- index.coffee
+++ index.coffee
@@ -239,6 +239,10 @@
       @index++
       @forEachFn.apply {}, args

+  stop: ->
+    @hasIterationEnded = true
+    @finally @finalFn
+
   finally: (@finalFn)->
     if @finalFn and @hasIterationEnded
       cb = @finalFn
@@ -335,4 +339,64 @@
 @AsyncCollector = AsyncCollector

-
+###
+  @class Publisher
+  @purpose Lightweight and optionally sequencially executed alternative to nodejs EventEmitters
+###
+
+class Publisher
+
+  constructor: ->
+    @subscriberFnList = []
+
+  publishInParallel: (data...)->
+    col = new AsyncCollector @subscriberFnList.length
+
+    col.finally =>
+      @finalCbfn() if @finalCbfn
+
+    doneFn = ->
+      col.collect()
+
+    _setImmediate =>
+      for fn in @subscriberFnList
+        args = [ doneFn ].concat data
+        fn.apply {}, args
+
+    return @
+
+  publishInSeries: (data...)->
+    it = new AsyncIterator
+
+    array = @subscriberFnList
+
+    it.generateWith (expectedIndex)->
+      if expectedIndex < array.length then [ expectedIndex, array[expectedIndex] ] else null
+
+    it.forEach (next, index, fn)->
+      stopFn = ->
+        it.stop()
+      args = [ next, stopFn ].concat data
+      fn.apply {}, args
+
+    it.finally =>
+      @finalCbfn() if @finalCbfn
+
+    return @
+
+  finally: (@finalCbfn)->
+    return @
+
+  subscribe: (fn)->
+    @subscriberFnList.push fn
+    return @
+
+  unsubscribe: (fn)->
+    if (index = @subscriberFnList.indexOf fn) > -1
+      @subscriberFnList.splice index, 1
+    return @
+
+@Publisher = Publisher
+
+
+


Fixed possible bug caused by not clearing task cache when saving.

--- db.coffee
+++ db.coffee
@@ -72,9 +72,14 @@

 saveTask = (task, callback) ->
-  task._id = mongodb.ObjectID(String(task._id))
+  # task ID can either be a string or a MongoDB ID
+  id = String(task._id)
+  task._id = mongodb.ObjectID(id)
   tasksCollection.save(task, (err) ->
-    callback(err)
+    throw new Error(err) if err
+    memcached.del(id, (err, res) ->
+      callback(err)
+    )
   )


use 300px thumbs for tagged image view

use 300px thumbs for tagged image view

--- app/talk/tags.cjsx
+++ app/talk/tags.cjsx
@@ -71,7 +71,7 @@
                         Subject {tag.subject.id}
                       </Link>

-                    <Thumbnail src={getSubjectLocation(tag.subject).src} width={100} />
+                    <Thumbnail src={getSubjectLocation(tag.subject).src} width={300} />

                       {for subjectTag in tag.subjectTags


Removed Problems link from menu

Removed Problems link from menu

--- picoCTF-web/web/coffee/navbar.coffee
+++ picoCTF-web/web/coffee/navbar.coffee
@@ -33,7 +33,7 @@

 userLoggedIn =
   Game: "/game/"
-  Problems: "/problems"
+  #Problems: "/problems"
   Shell: "/shell"
   Team: "/team"
   Scoreboard: "/scoreboard"


Properly limit annotation coordinates when zooming

--- app/assets/javascripts/annotationEditor.js.coffee
+++ app/assets/javascripts/annotationEditor.js.coffee
@@ -340,7 +340,7 @@

     # Limit x
     if pagePos.x > @submissionPage.pageWidth() + 8
-      pagePos.x = @submissionPage.pageWidth() + 8
+      pagePos.x = @submissionPage.pageWidth() + 7
       screenPos.x = pagePos.x * @zoom
       screenPos.updated = false
       @pagePosition.valueHasMutated()


UI fixes for commenting features.

UI fixes for commenting features.

--- app/assets/javascripts/api/crud_comment.js.coffee
+++ app/assets/javascripts/api/crud_comment.js.coffee
@@ -125,28 +125,43 @@
   aba.appendChild abab
   ababa = document.createElement("div")
   if @user_logged_in and editable() or @user_logged_in
+    ababa.className = "span7"
+  else
     ababa.className = "span8"
-  else
-    ababa.className = "span9"
   abab.appendChild ababa
+  abba_row = document.createElement("div")
+  abba_row.className = "row-fluid"
+  ababa.appendChild abba_row
+  abba_div5 = document.createElement("div")
+  if @user_logged_in
+    abba_div5.className = "span5"
+  else
+    abba_div5.className = "span4"
+  abba_row.appendChild abba_div5
   ababaa_p = document.createElement("p")
-  ababa.appendChild ababaa_p
+  abba_div5.appendChild ababaa_p
   ababaaa_span = document.createElement("span")
   ababaaa_span.appendChild document.createTextNode(creation_date)
   ababaa_p.appendChild ababaaa_span
+  abba_div7 = document.createElement("div")
+  if @user_logged_in
+    abba_div7.className = "span7"
+  else
+    abba_div7.className = "span8"
+  abba_row.appendChild abba_div7
+  ababbaa_p = document.createElement("p")
+  abba_div7.appendChild ababbaa_p
   ababaab_span = document.createElement("span")
   ababaab_span.id = "comment-vote-" + comment_id
   ababaab_span.className = "comment-vote"
   ababaab_span.setAttribute "data-comment-id", comment_id
-  ababaab_span.setAttribute "style", "margin-left:15px"
   ababaab_span.appendChild document.createTextNode(comment_votes)
-  ababaa_p.appendChild ababaab_span
-
+  ababbaa_p.appendChild ababaab_span
   bottom_second_colm = document.createElement("div")
   if @user_logged_in and editable() or @user_logged_in
+    bottom_second_colm.className = "span5"
+  else
     bottom_second_colm.className = "span4"
-  else
-    bottom_second_colm.className = "span3"
   abab.appendChild bottom_second_colm
   function_links = document.createElement("div")
   function_links.className = "row-fluid"


Edit js file in order to work only when editing an account

--- app/assets/javascripts/backend/accounts.js.coffee
+++ app/assets/javascripts/backend/accounts.js.coffee
@@ -2,6 +2,7 @@
   'use strict'

   $(document).ready ->
+    return unless $('.edit_account').length
     url = $('form').attr('action')
     $.ajax
       url: "#{url}.json"


Update feed list load check

Update feed list load check

--- app/assets/javascripts/components/FeedList.coffee
+++ app/assets/javascripts/components/FeedList.coffee
@@ -25,14 +25,14 @@
           $tipLoaded.hide()
         setTimeout ()->
           loading = false
-        , 1000
+        , 500
       error: (res)->
         $tipLoading.addClass 'has-error'

   checkLoad = ()->
     $last = $feeds.find('.feed').last()
     scrollTop = $(window).scrollTop() + $(window).height()
-    if scrollTop < $last.offset().top - $last.height() * 2
+    if scrollTop < $last.offset().top - $last.height()
       return
     if loading or page >= pages
       return


added google analytic.

added google analytic.

--- app/assets/javascripts/google_analytics.js.coffee
+++ app/assets/javascripts/google_analytics.js.coffee
@@ -39,6 +39,6 @@
     document.domain.indexOf(str) isnt -1

   @analyticsId: ->
-    ''
+    'UA-59177044-1'

 GoogleAnalytics.load()


Kills in eventlog mirror ingame messages

Needs more work/styling on streaks though

--- app/assets/javascripts/matches/eventlog.js.coffee
+++ app/assets/javascripts/matches/eventlog.js.coffee
@@ -34,8 +34,13 @@
       when 'aegis_denied' then "#{iconize_hero(evt.hero)} denied the aegis"
       when 'aegis_stolen' then "#{iconize_hero(evt.hero)} stole the aegis"
       when 'buyback' then "#{iconize_hero(evt.hero)} bought back for #{evt.cost} gold"
-      when 'kill' then "#{iconize_killers(evt.killers)} killed #{iconize_hero(evt.hero)}, earning #{evt.bounty_gold} gold, #{evt.bounty_xp} xp"
-      when 'deny' then "#{iconize_killers(evt.killers)} denied #{iconize_hero(evt.hero)}"
+      when 'kill'
+        if evt.killer_streak
+          "#{iconize_hero(evt.killer)} killed #{iconize_hero(evt.hero)}, earning #{evt.bounty_gold} gold [#{evt.killer_streak}, #{evt.victim_streak}]"
+        else
+          "#{iconize_hero(evt.killer)} killed #{iconize_hero(evt.hero)}, earning #{evt.bounty_gold} gold"
+
+      when 'deny' then "#{iconize_hero(evt.killer)} denied #{iconize_hero(evt.hero)}"

   update = (time) ->
     # find all events within 30 seconds on either side of current time


name variables more obvious

name variables more obvious

--- app/assets/javascripts/uploadcare/settings.coffee
+++ app/assets/javascripts/uploadcare/settings.coffee
@@ -207,11 +207,11 @@
     if settings.previewProxy and not settings.previewUrlCallback
       settings.previewUrlCallback = (url, info) =>
         encodedUrl = encodeURIComponent(url)
-        raw = /\=$/.test(settings.previewProxy)
-        namedNoAmp = /[\&\?]$/.test(settings.previewProxy)
-
-        path = if raw then encodedUrl else
-          if namedNoAmp then "url=#{encodedUrl}" else "&url=#{encodedUrl}"
+        justAppend = /\=$/.test(settings.previewProxy)
+        useAmpersand = /[^\&\?]$/.test(settings.previewProxy)
+
+        path = if justAppend then encodedUrl else
+          if useAmpersand then "&url=#{encodedUrl}" else "url=#{encodedUrl}"

         utils.normalizeUrl(settings.previewProxy) + path


Add purchase flag to inquiry request

Add purchase flag to inquiry request

--- apps/artwork_purchase/client/purchase_form.coffee
+++ apps/artwork_purchase/client/purchase_form.coffee
@@ -21,6 +21,7 @@
       artwork: @artwork.id,
       contact_gallery: true,
       inquiry_url: window.location.href,
+      purchase: true,
     }

     promises = [@inquiry.save null,


Use .tri-state-checkbox selector vs input

--- test-integration/helpers/ui/reading-builder.coffee
+++ test-integration/helpers/ui/reading-builder.coffee
@@ -101,7 +101,7 @@
   sectionItem: (section) ->
     css: "#{OPENED_PANEL_SELECTOR} [data-chapter-section='#{section}']"
   chapterHeadingSelectAll: (section) ->
-    css: "#{OPENED_PANEL_SELECTOR} [data-chapter-section='#{section.split('.')[0]}'] .chapter-checkbox input"
+    css: "#{OPENED_PANEL_SELECTOR} [data-chapter-section='#{section.split('.')[0]}'] .chapter-checkbox .tri-state-checkbox"
   chapterHeading: (section) ->
     css: "#{OPENED_PANEL_SELECTOR} [data-chapter-section='#{section.split('.')[0]}'] > a"


hover effect

hover effect

--- src/github-calendar.coffee
+++ src/github-calendar.coffee
@@ -50,10 +50,14 @@
 	else
 		square.attr("fill", "#ccc")
 	square.attr("stroke-opacity", "0")
-	square.hover(->
-		$('#github-calendar > .description').text model.created_at.toUTCString()
-		$('#github-calendar > .description').append ' commits:' + model.commitsLength
-	)
+	square.hover ( ->
+			$('#github-calendar > .description').text model.created_at.toUTCString()
+			$('#github-calendar > .description').append ' commits:' + model.commitsLength
+			square.attr("stroke-opacity", "1")
+		), (->
+			square.attr("stroke-opacity", "0")
+		)
+

 draw = ->
 	for grid, i in model


Don't send offline emails to users who aren't active

--- packages/rocketchat-lib/server/models/Users.coffee
+++ packages/rocketchat-lib/server/models/Users.coffee
@@ -371,15 +371,16 @@
 	- he is not online
 	- has a verified email
 	- has not disabled email notifications
+	- `active` is equal to true (false means they were deactivated and can't login)
 	###
 	getUsersToSendOfflineEmail: (usersIds) ->
 		query =
 			_id:
 				$in: usersIds
+			active: true
 			status: 'offline'
 			statusConnection:
 				$ne: 'online'
 			'emails.verified': true

 		return @find query, { fields: { name: 1, username: 1, emails: 1, 'settings.preferences.emailNotificationMode': 1 } }
-


More updates to the Readmill plugin

More updates to the Readmill plugin

 * Allow access to location headers in Firefox.
 * Now correctly creating a reading resource for the book.

--- src/annotator.readmill.coffee
+++ src/annotator.readmill.coffee
@@ -9,6 +9,7 @@
   constructor: (options) ->
     super

+    @user   = null
     @book   = @options.book
     @auth   = new Readmill.Auth @options
     @store  = new Readmill.Store
@@ -18,21 +19,60 @@
     @connected(token, silent: true) if token
     @unsaved = []

+  lookupBook: ->
+    @book.deferred = @client.matchBook @book unless @book.deferred
+
+  lookupReading: ->
+    @lookupBook() unless @book.id
+    jQuery.when(@book.deferred).then =>
+      data = {state: Readmill.Client.READING_STATE_OPEN}
+      request = @client.createReadingForBook @book.id, data
+      request.then(@_onCreateReadingSuccess, @_onCreateReadingError)
+
   connect: ->
     @auth.connect().then @_onConnectSuccess, @_onConnectError

   connected: (accessToken, options) ->
     @client.authorize accessToken
-    @store.set "access-token", accessToken
+    @client.me().then @_onMeSuccess, @_onMeError
+    @store.set "access-token", accessToken, options.expires

     unless options?.silent is true
       Annotator.showNotification "Successfully connected to Readmill"

+  error: (message) ->
+    Annotator.showNotification message, Annotator.Notification.ERROR
+
   _onConnectSuccess: (params) =>
-    @connected(params.access_token)
+    @connected params.access_token, params

   _onConnectError: (error) =>
-    Annotator.showNotification error, Annotator.Notification.ERROR
+    @error error
+
+  _onMeSuccess: (data) =>
+    @user = data
+    @lookupReading()
+
+  _onMeError: () =>
+    @error "Unable to fetch user info from Readmill"
+
+  _onCreateReadingSuccess: (body, status, jqXHR) =>
+    location = jqXHR.getResponseHeader "Location"
+    location = location.replace /http:\/\/[^\/]+/, ""
+
+    request = @client.request(url: location, type: "GET")
+    request.then @_onGetReadingSuccess, @_onGetReadingError
+
+  _onCreateReadingError: (jqXHR) =>
+    if jqXHR.status == 409
+      location = jqXHR.getResponseHeader "Location"
+      @_onCreateReadingSuccess(null, null, jqXHR)
+
+  _onGetReadingSuccess: (reading) =>
+    @book.reading = reading
+
+  _onGetReadingError: (reading) =>
+    @error "Unable to create reading for this book"

   _onAnnotationCreated: (annotation) =>
     if @client.isAuthorized() and @book.id
@@ -60,42 +100,69 @@
     obj

 class Client
-  @API_ENDPOINT: "http://api.readmill.com"
+  @API_ENDPOINT: "https://api.readmill.com"
+
+  @READING_STATE_INTERESTING: 1
+  @READING_STATE_OPEN: 2,
+  @READING_STATE_FINISHED: 3
+  @READING_STATE_ABANDONED: 4

   constructor: (options) ->
     {@clientId, @accessToken, @apiEndpoint} = options
     @apiEndpoint = Client.API_ENDPOINT unless @apiEndpoint

   me: ->
-    @request
-      url: "#{@apiEndpoint}/me"
-      type: "GET"
-      data: {access_token: @accessToken}
+    @request url: "/me", type: "GET"

   matchBook: (data) ->
-    @request
-      url: "#{@apiEndpoint}/books/match"
-      type: "GET"
-      data: client_id: @clientId, q: data
+    @request url: "/books/match", type: "GET", data: {q: data}

   createBook: (data) ->
-    @request
-      url: "#{@apiEndpoint}/books?access_token=#{@accessToken}"
-      type: "POST"
-      dataType: "json",
-      contentType: "application/json"
-      data: JSON.stringify(book: data)
+    @request url: "/books", type: "POST", data: {book: data}
+
+  createReadingForBook: (bookId, data) ->
+    @request type: "POST", url: "/books/#{bookId}/readings", data: {reading: data}
+
+  getHighlights: (url) ->
+    @request url: url, type: "GET"

   createHighlight: ->
-    @request
-      type: "POST"
+    @request type: "POST"

   updateHighlight: ->

   deleteHighlight: ->

-  request: (options) ->
-    jQuery.ajax options
+  request: (options={}) ->
+    xhr = null
+
+    if options.url.indexOf("http") != 0
+      options.url = "#{@apiEndpoint}#{options.url}"
+
+    if options.type.toUpperCase() of {"POST", "PUT", "DELETE"}
+      options.url = "#{options.url}?&client_id=#{@clientId}"
+      options.data = JSON.stringify(options.data) if options.data
+      options.dataType = "json"
+      options.contentType = "application/json"
+    else
+      options.data = jQuery.extend {client_id: @clientId}, options.data or {}
+
+    options.beforeSend = (jqXHR) =>
+      jqXHR.setRequestHeader "Accept", "application/json"
+      jqXHR.setRequestHeader "Authorization", "OAuth #{@accessToken}" if @accessToken
+
+    # jQuery's getResponseHeader() method is broken in Firefox when it comes
+    # to accessing CORS headers as it uses the getAllResponseHeaders method
+    # which returns an empty string. So here we provide our own xhr factory to
+    # the jQuery settings and keep a reference to the original XHR object
+    # we then monkey patch the getResponseHeader() to use the native one.
+    # See: http://bugs.jquery.com/ticket/10338
+    options.xhr = -> xhr = jQuery.ajaxSettings.xhr()
+
+    request = jQuery.ajax options
+    request.xhr = xhr
+    request.getResponseHeader = (header) -> xhr.getResponseHeader(header)
+    request

   authorize: (@accessToken) ->


trival

trival

--- src/main/ExpiringCache.coffee
+++ src/main/ExpiringCache.coffee
@@ -41,4 +41,4 @@
       for k of o
         c[k] = o[k]
       c
-    clone(@stats)+    clone(@stats)


remove log statement

remove log statement

--- jsapp/xlform/src/model.rowDetails.skipLogic.coffee
+++ jsapp/xlform/src/model.rowDetails.skipLogic.coffee
@@ -7,7 +7,6 @@
 Backbone = require 'backbone'
 $utils = require './model.utils'
 $skipLogicHelpers = require './mv.skipLogicHelpers'
-console.log($skipLogicHelpers)

 rowDetailsSkipLogic = {}


fixed issue with inflate

fixed issue with inflate

--- src/middleware/router.coffee
+++ src/middleware/router.coffee
@@ -1,6 +1,7 @@
 util = require('util');
 zlib = require('zlib');
 gunzip = require('zlib').createGunzip()
+inflate = require('zlib').createInflate()
 http = require 'http'
 https = require 'https'
 net = require 'net'
@@ -182,6 +183,13 @@
 				uncompressedBodyBufs.push data
 				return

+		if routeRes.headers['content-encoding'] == 'deflate' #attempt to inflate
+			routeRes.pipe(inflate);
+
+			inflate.on "data", (data) ->
+				uncompressedBodyBufs.push data
+				return
+
 		bufs = []
 		routeRes.on "data", (chunk) ->
 			bufs.push chunk
@@ -198,14 +206,12 @@
 						defered.resolve response
 					return
 			else if routeRes.headers['content-encoding'] == 'deflate'
-				zlib.inflate(
-					Buffer.concat bufs,
-					(inflateError, buf) ->
-						if inflateError then logger.error inflateError
-						else response.body = buf.toString charset
-						if not defered.promise.isRejected()
-							defered.resolve response
-				)
+				inflate.on "end", ->
+					uncompressedBody =	Buffer.concat uncompressedBodyBufs
+					response.body = uncompressedBody.toString charset
+					if not defered.promise.isRejected()
+						defered.resolve response
+					return
 			else
 				response.body = Buffer.concat bufs
 				if not defered.promise.isRejected()


only map Email package on server

only map Email package on server

--- source/application.coffee
+++ source/application.coffee
@@ -36,8 +36,8 @@
       @injector.map('process').to process
       @injector.map('Future').to Npm.require 'fibers/future'

-    if Package.email?
-      @injector.map('Email').to Package.email.Email
+      if Package.email?
+        @injector.map('Email').to Package.email.Email

     if Package['accounts-base']?
       @injector.map('Accounts').to Package['accounts-base'].Accounts


event coordinate localizer is standalone now. code annotation added

--- source/lib/element.coffee
+++ source/lib/element.coffee
@@ -1,8 +1,6 @@
 Event = require './event'

-localizeEventCoordinates = (event, origin) ->
-  event.localX = (if event.localX? then event.localX else event.x) - (origin?.x or 0)
-  event.localY = (if event.localY? then event.localY else event.y) - (origin?.y or 0)
+localizeEventCoordinates = require '../helper/localize-event-coordinates'

 module.exports = class Element extends require './evented'
   constructor: (args = {}) ->
@@ -21,6 +19,11 @@
     for name in ['mousemove', 'mousedown', 'mouseup']
       @addListener name, this["#{name}CaptureListener"], yes
       @addListener name, this["#{name}Listener"], no
+
+  ###
+    All the capturing listeners below store the event
+    in the runtime and localize the event coordinates.
+  ###

   mousemoveCaptureListener: (event) ->
     @___runtime.mousemoveEvent = event


BrowserManager: if forked, emit a message when browsers are created and garbage collected.

--- src/server/browser_manager/browser_manager.coffee
+++ src/server/browser_manager/browser_manager.coffee
@@ -1,18 +1,32 @@
-Hat = require('hat')
-ko  = require('../../api/ko')
+Hat  = require('hat')
+Weak = require('weak')
+ko   = require('../../api/ko')

 if !global.browserList?
     global.browserList = ko.observableArray()
+
+# TODO: something better than both of these.
+global.weakRefList = {}

 # Base class for browser management strategies.
 # Must also define @mountPoint
 class BrowserManager
     addToBrowserList : (browser) ->
         console.log("Pushing to browserList")
-        global.browserList.push(browser)
+        #global.browserList.push(browser)
+        if process.env.WAS_FORKED
+            id = browser.id
+            process.send
+                type: 'browserCreated'
+                id: id
+            weakRefList[id] = Weak browser, () ->
+                delete weakRefList[id]
+                process.send
+                    type: 'browserCollected'
+                    id: id

     removeFromBrowserList : (browser) ->
-        global.browserList.remove(browser)
+        #global.browserList.remove(browser)

     find : () ->
         throw new Error("BrowserManager subclass must implement find.")


atom: just say no

atom: just say no

--- atom/config.cson
+++ atom/config.cson
@@ -6,6 +6,7 @@
       "metrics"
     ]
     projectHome: "/Users/nathany/src/github.com/nathany"
+    telemetryConsent: "no"
     themes: [
       "atom-material-ui"
       "material-syntax"


Change Atom Project Home to ~/Local/Repositories

The Project Home setting is the default location for new packages
generated by the Package Generator.

--- atom/config.cson
+++ atom/config.cson
@@ -61,7 +61,7 @@
       "go-to-line"
       "script"
     ]
-    projectHome: "~"
+    projectHome: "~/Local/Repositories"
     themes: [
       "one-dark-ui"
       "monokai"


Update python-black Atom package setting

--- atom/config.cson
+++ atom/config.cson
@@ -79,8 +79,7 @@
   "linter-ui-default":
     panelHeight: 236
     showPanel: true
-  "python-black":
-    skipStringNormalization: true
+  "python-black": {}
   "python-isort":
     isortPath: "/Users/jonklo/miniconda3/envs/tienda/bin/isort"
     sortOnSave: true


Revert "Re-enable whitespace package with features disabled"

This reverts commit 912a68107301d4d12f0b9f673d76fc6fa7ad4a1b.

editorconfig complains when the whitespace package is enabled, even if
all the whitespace settings are disabled.

--- atom/config.cson
+++ atom/config.cson
@@ -31,6 +31,7 @@
       "timecop"
       "update-package-dependencies"
       "welcome"
+      "whitespace"
       "teletype"
     ]
     followSymlinks: false
@@ -134,9 +135,7 @@
   welcome:
     showOnStartup: false
   whitespace:
-    ensureSingleTrailingNewline: false
     ignoreWhitespaceOnCurrentLine: false
-    removeTrailingWhitespace: false
 ".basic.html.text":
   editor:
     nonWordCharacters: "/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-"


Replace linter-jshint with linter-eslint in Atom

--- atom/packages.cson
+++ atom/packages.cson
@@ -6,10 +6,10 @@
   "linter"
   "linter-coffeelint"
   "linter-csslint"
+  "linter-eslint"
   "linter-flake8"
   "linter-htmlhint"
   "linter-js-yaml"
-  "linter-jshint"
   "linter-jsonlint"
   "linter-less"
   "linter-php"


Add snippet for inline arrow function

Add snippet for inline arrow function

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -193,6 +193,10 @@
     'prefix': 'af'
     'body': '($1) => {\n\t$2\n}$3'

+  'arrow function (inline)':
+    'prefix': 'afi'
+    'body': '($1) => ${2:returnvalue}'
+
   'immediately-invoked function expression':
     'prefix': 'iife'
     'body': '(function($1) {\n$2\n}($3));'


Add SCA snippets

Add SCA snippets

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -223,17 +223,17 @@
     'prefix': 'doc'
     'body': 'document'

-  'getElementById':
-    'prefix': 'getid'
-    'body': 'getElementById($1)$2'
-
-  'getElementsByClassName':
-    'prefix': 'getclass'
-    'body': 'getElementsByClassName($1)$2'
-
-  'getElementsByTagName':
-    'prefix': 'gettag'
-    'body': 'getElementsByTagName($1)$2'
+  # 'getElementById':
+  #   'prefix': 'getid'
+  #   'body': 'getElementById($1)$2'
+  #
+  # 'getElementsByClassName':
+  #   'prefix': 'getclass'
+  #   'body': 'getElementsByClassName($1)$2'
+  #
+  # 'getElementsByTagName':
+  #   'prefix': 'gettag'
+  #   'body': 'getElementsByTagName($1)$2'

   'querySelector':
     'prefix': 'qs'
@@ -413,6 +413,28 @@
     'prefix': 'setFieldText'
     'body': 'setFieldText(${1:fieldname}, ${2:text});'

+  # SuiteCommerce Advanced
+
+  'AMD module':
+    'prefix': 'define'
+    'body': 'define(\'${1:Extension.Module}\', [\n\t\'$2\'$3\n], function (\n\t$4\n) {\n\t\'use strict\';\n\n\treturn ${5:exported};\n\n});'
+
+  'mountToApp':
+    'prefix': 'mountToApp'
+    'body': '{\n\tmountToApp: function ($1) {\n\n\t\t $2\n\n\t}\n}'
+
+  'wrap prototype method':
+    'prefix': 'wrapptm'
+    'body': '${1:Module}.prototype.${2:methodName} = _.wrap(${1:Module}.prototype.${2:methodName}, function (fn) {\n\t$3\n});'
+
+  'wrap getContext':
+    'prefix': 'getcontext'
+    'body': '${1:Module}.prototype.getContext = _.wrap(${1:Module}.prototype.getContext, function (fn) {\n\tvar context = fn.apply(this, _.toArray(arguments).slice(1));\n\t${3:// additional context variables here}\n\treturn context;\n});'
+
+  'call parent method':
+    'prefix': 'fnapply'
+    'body': 'fn.apply(this, _.toArray(arguments).slice(1));'
+
   # Miscellaneous

   'prototype':


Ajout d'une action custom

Ajout d'une action custom

--- coffee/game.coffee
+++ coffee/game.coffee
@@ -17,12 +17,12 @@
 )

 fbShare = (score) ->
-	FB.ui({
-		method: "share_open_graph",
-		action_type: "games.passes",
-		action_properties: JSON.stringify({
-			game: "http://arkanoid.herokuapp.com/",
-			score: score,
-			passed_users: localStorage.username
-		})
-	}, (response) -> console.log response)
+	FB.ui(
+		method: "share_open_graph"
+		action_type: "arkanovoid:score"
+		action_properties: JSON.stringify(
+			game: "http://arkanoid.herokuapp.com/"
+			score: score
+		)
+		, (response) -> console.log response
+	)


removing trailing whitespace

removing trailing whitespace

--- client/app/MainApp/maincontroller/mainview.coffee
+++ client/app/MainApp/maincontroller/mainview.coffee
@@ -1,7 +1,7 @@
 class MainView extends KDView
-
+
   viewAppended:->
-
+
     # @putLoader()
     @addHeader()
     @createMainPanels()
@@ -10,14 +10,14 @@
     # @removeLoader()
     @windowController = @getSingleton("windowController")
     @listenWindowResize()
-
+
     setTimeout =>
       @putWhatYouShouldKnowLink()
     ,5000

   addBook:->
     @addSubView new BookView
-
+
   setViewState:(state)->
     if state is 'background'
       @contentPanel.setClass 'no-shadow'
@@ -48,7 +48,7 @@
       # delete KD.singletons.loadingScreen
       $loadingScreen.remove()
       $('body').removeClass 'loading'
-
+
   createMainPanels:->
     @addSubView @panelWrapper = new KDView
       tagName  : "section"
@@ -62,7 +62,7 @@

     @registerSingleton "contentPanel", @contentPanel, yes
     @registerSingleton "sidebarPanel", @sidebarPanel, yes
-
+
   addHeader:()->
     @addSubView @header = new KDView
       tagName : "header"
@@ -71,7 +71,7 @@
       tagName   : "a"
       domId     : "koding-logo"
       # cssClass  : "hidden"
-      attributes:
+      attributes:
         href    : "#"
       click     : (pubInst,event)=>
         if KD.isLoggedIn()
@@ -80,19 +80,19 @@
           appManager.openApplication "Home"

     @addLoginButtons()
-
+
   addLoginButtons:->
     @header.addSubView @buttonHolder = new KDView
       cssClass  : "button-holder hidden"

     mainController = @getSingleton('mainController')
-
+
     # @buttonHolder.addSubView new KDButtonView
     #   title     : "About Koding"
     #   domId     : "about-button"
     #   callback  : =>
     #     mainController.propagateEvent KDEventType : "AboutButtonClicked", globalEvent : yes
-    #
+    #
     @buttonHolder.addSubView new KDButtonView
       title     : "Sign In"
       style     : "koding-blue"
@@ -112,7 +112,7 @@
       domId    : "main-tab-handle-holder"
       cssClass : "kdtabhandlecontainer"
       delegate : @
-
+
     @mainTabView = new MainTabView
       domId              : "main-tab-view"
       listenToFinder     : yes
@@ -128,7 +128,7 @@
     @sidebar = new Sidebar domId : "sidebar", delegate : @
     @emit "SidebarCreated", @sidebar
     @sidebarPanel.addSubView @sidebar
-
+
   changeHomeLayout:(isLoggedIn)->

   decorateLoginState:(isLoggedIn = no)->
@@ -147,14 +147,14 @@

     @changeHomeLayout isLoggedIn
     @utils.wait 300, => @notifyResizeListeners()
-
+
   _windowDidResize:->

     {winHeight} = @windowController
     @panelWrapper.setHeight winHeight - 51

   putWhatYouShouldKnowLink:->
-
+
     @header.addSubView link = new KDCustomHTMLView
       tagName     : "a"
       domId       : "what-you-should-know-link"
@@ -169,7 +169,7 @@
             # url       : KD.apiUri+'https://api.koding.com/1.0/logout'
             url       : "/beta.txt"
             success	  : (response)=>
-
+
               modal = new KDModalView
                 title       : "Thanks for joining our beta."
                 cssClass    : "what-you-should-know-modal"
@@ -181,7 +181,7 @@
                     title   : 'Close'
                     style   : 'modal-clean-gray'
                     callback: -> modal.destroy()
-
+
               {winHeight} = @getSingleton('windowController')
               modal.$('.kdmodal-content').css 'max-height', winHeight - 200
               modal.setY (winHeight - modal.getHeight())/2


ComputeController: fetchStackReadme: helper for fetching readme of given stack's template

--- client/app/lib/providers/computecontroller.coffee
+++ client/app/lib/providers/computecontroller.coffee
@@ -852,6 +852,23 @@

   ###*
+   * Fetch given stack's README from the stackTemplate which
+   * is generated from.
+  ###
+
+  fetchStackReadme: (stack, callback = kd.noop) ->
+
+    return callback null, ''  unless stack?.baseStackId
+
+    { baseStackId } = stack
+
+    remote.cacheable 'JStackTemplate', baseStackId, (err, templates) ->
+      return callback err  if err
+      [template] = templates
+      return callback null, template.description ? ''
+
+
+  ###*
    * Automatically kill active collaboration sessions if any
   ###
   stopCollaborationSession: ->


consider all packs for processing

consider all packs for processing

to allow finalisation of old head packs

--- services/track-changes/app/coffee/PackManager.coffee
+++ services/track-changes/app/coffee/PackManager.coffee
@@ -277,10 +277,13 @@
 			packs.push(last) if last.finalised # it's finalised so we push it back to archive it
 			callback(null, packs)

-	# findPacks: (project_id, doc_id, queryFilter, callback) ->
-	# 	query = { doc_id: ObjectId(doc_id.toString()) }
-	# 	query = _.defaults query, queryFilter if queryFilter?
-	# 	db.docHistory.find(query, {pack:false}).sort {v:1}, callback
+	findPacks: (project_id, doc_id, callback) ->
+		query = { doc_id: ObjectId(doc_id.toString()), expiresAt: {$exists:false} }
+		db.docHistory.find(query, {pack:false}).sort {v:1}, (err, packs) ->
+			return callback(err) if err?
+			return callback() if not packs?
+			return callback() if not packs?.length
+			callback(null, packs)

 	findUnindexedPacks: (project_id, doc_id, callback) ->
 		PackManager.getIndexWithKeys doc_id, (err, indexResult) ->
@@ -361,7 +364,7 @@
 	# Extra methods to test archive/unarchive for a doc_id

 	pushOldPacks: (project_id, doc_id, callback) ->
-		PackManager.findCompletedPacks project_id, doc_id, (err, packs) ->
+		PackManager.findPacks project_id, doc_id, (err, packs) ->
 			return callback(err) if err?
 			return callback() if not packs?.length
 			PackManager.processOldPack project_id, doc_id, packs[0]._id, callback


fix close editor button

fix close editor button

- only evaulate close on web router, not api/static assets
- allow /admin pages to still be available

--- services/web/app/coffee/infrastructure/Server.coffee
+++ services/web/app/coffee/infrastructure/Server.coffee
@@ -101,19 +101,19 @@
 	logger.info "Production Enviroment"
 	app.enable('view cache')

-
-
 app.use (req, res, next)->
 	metrics.inc "http-request"
 	crawlerLogger.log(req)
 	next()

-app.use (req, res, next) ->
-	if !Settings.editorIsOpen
+webRouter.use (req, res, next) ->
+	if Settings.editorIsOpen
+		next()
+	else if req.url.indexOf("/admin") == 0
+		next()
+	else
 		res.status(503)
 		res.render("general/closed", {title:"maintenance"})
-	else
-		next()

 apiRouter.get "/status", (req, res)->
 	res.send("web sharelatex is alive")


increase bodyparser limit to 64kb

increase bodyparser limit to 64kb

16kb is not enough for bibtex files with more escaping.

--- services/web/app/coffee/infrastructure/Server.coffee
+++ services/web/app/coffee/infrastructure/Server.coffee
@@ -68,7 +68,7 @@

 app.use bodyParser.urlencoded({ extended: true, limit: "2mb"})
 # Make sure we can process the max doc length plus some overhead for JSON encoding
-app.use bodyParser.json({limit: Settings.max_doc_length + 16 * 1024}) # 16kb overhead
+app.use bodyParser.json({limit: Settings.max_doc_length + 64 * 1024}) # 64kb overhead
 app.use multer(dest: Settings.path.uploadFolder)
 app.use methodOverride()


Admin: Integrations: Create empty auth view.

--- client/admin/lib/views/integrations/adminintegrationdetailsview.coffee
+++ client/admin/lib/views/integrations/adminintegrationdetailsview.coffee
@@ -153,6 +153,8 @@
       # hide unnecessary views.
       @instructionsView?.hide()
       @settingsForm.fields.url.hide()
+    else
+      @authView = new KDCustomHTMLView cssClass: 'hidden'

   auth: ->


Workspace: Added openUrl method to CollaborativePreviewPane.

--- client/app/CommonViews/workspace/panes/collaborativepreviewpane.coffee
+++ client/app/CommonViews/workspace/panes/collaborativepreviewpane.coffee
@@ -25,6 +25,10 @@
     value = snapshot.val()
     @previewer.openPath value.url  if value?.url

+  openUrl: (url) ->
+    @previewer.openPath url
+    @saveUrl yes
+
   saveUrl: (force) ->
     {path} = @previewer
     url    = unless force then path.replace(/\?.*/, "") else "#{path}?#{Date.now()}"


Workspace: Changed disconnected modal title and content for host.

--- client/landing/app/CommonViews/workspace/collaborativeworkspace.coffee
+++ client/landing/app/CommonViews/workspace/collaborativeworkspace.coffee
@@ -110,10 +110,17 @@
     log "user joined a new session:", sessionKey

   showDisconnectedModal: ->
+    if @amIHost()
+      title   = "Disconnected from remote"
+      content = "It seems, you have been disconnected from Firebase server. You cannot continue this session."
+    else
+      title   = "Host disconnected"
+      content = "It seems, host is disconnected from Firebase server. You cannot continue this session."
+
     @disconnectedModal = new KDModalView
-      title        : "Host disconnected"
+      title        : title
+      content      : "
#{content}
"
       cssClass     : "host-disconnected-modal"
-      content      : "
It seems host is disconnected
"
       overlay      : yes
       buttons      :
         Start      :


Toggle view menu on click icon

Toggle view menu on click icon

--- components/view_menu/client/view_menu_view.coffee
+++ components/view_menu/client/view_menu_view.coffee
@@ -12,7 +12,8 @@
     list: 'list'

   events:
-    'tap .view-menu__dropdown__option' : 'setMode'
+    'tap .view-menu__dropdown__option' : 'switchMode'
+    'tap .js-dropdown-trigger' : 'toggleView'

   initialize: ->
     @desktopEvents = _.extend @desktopEvents, @events
@@ -20,21 +21,19 @@

     super

-    @$el.on 'click', @toggleView
+  toggleView: (e) =>
+    mode = if @model.get('view_mode') is 'list' then 'grid' else 'list'
+    @_setMode mode

-  toggleView: (e) ->
-    return if $(e.currentTarget).hasClass 'view-menu__dropdown__option'
-    mode = if @model.get('view_mode') is 'list' then 'grid' else 'list'
-    model.set view_mode: setMode
-    glyph = @glyphs[mode]
+  switchMode: (e) ->
+    console.log 'switchMode', $(e.currentTarget).data 'mode'
+    @_setMode $(e.currentTarget).data 'mode'
+
+  _setMode: (mode)->
+    console.log '_setMode', mode
+    return unless mode is 'list' or mode is 'grid'
+    console.log 'should set mode'
+    @model.set view_mode: mode
     @$el.removeClass 'dropdown--is_active'
-    @$('.view-menu__selected').attr 'data-glyph', glyph
-    window.location.reload()
-
-  setMode: (e) ->
-    mode = $(e.currentTarget).data 'mode'
-    glyph = $(e.currentTarget).data 'glyph'
-    @model.set 'view_mode', mode
-    @$('.view-menu__selected').attr 'data-glyph', glyph
-    @$el.removeClass 'dropdown--is_active'
+    @$('.view-menu__selected').attr 'data-glyph', @glyphs[mode]
     window.location.reload()