switching tweaks

switching tweaks

--- src/lib/index.coffee
+++ src/lib/index.coffee
@@ -149,8 +149,13 @@
           @context_ref.child('current_app').set('UNKNOWN')
           return
         else
-          if g_current_app_msg != null and g_current_app_msg != current_app and \
+          current_app_name = Object.keys(current_app)[0]
+          debug "CURRENT APP MESSAGE"
+          debug g_current_app_msg
+          debug current_app_name
+          if g_current_app_msg != null and g_current_app_msg != current_app_name and \
               new Date() - g_current_app_time < 4000
+            debug "deciding to skip"
             return
           g_current_app_msg = null
           g_current_app_time = null
@@ -176,12 +181,10 @@
             debug("Error getting chrome tabs")
           unfiltered_chrome_tab_info = parse_chrome_tab_info(stdout)
           apps = opened_apps.get_apps(unfiltered_window_list, unfiltered_chrome_tab_info)
-          debug apps
           @context_ref.child('running_apps').set(apps)
           get_current_windows()
       else
         apps = opened_apps.get_apps(unfiltered_window_list, [])
-        debug apps
         @context_ref.child('running_apps').set(apps)
         get_current_windows()
   1000)


Use realpath if baseDir is symlink.

Use realpath if baseDir is symlink.

--- src/pipes/file-reader.coffee
+++ src/pipes/file-reader.coffee
@@ -8,6 +8,7 @@
 {BasePipe} = require "../pipe"

 readFile = RSVP.denodeify fs.readFile
+realpath = RSVP.denodeify fs.realpath

 module.exports = class ExtPipe extends BasePipe
     @schema: ->
@@ -40,7 +41,15 @@
         .catch (err) =>
             @log "error", "Could not read", name

+    init: ->
+        realpath @config.basedir
+        .then (basedir) =>
+            @config.basedir = basedir
+        .catch =>
+            @log "error", "Cannot read from #{@config.basedir}"
+            @config.basedir = null
     start: ->
+        return if not @config.basedir
         super new RSVP.Promise (resolve, reject) =>
             @watcher = sane @config.basedir,
                 glob: [@config.pattern],
@@ -91,4 +100,5 @@
                     @fileUnlink filepath

     stop: ->
+        return if not @config.basedir
         @watcher.close() if @config.continuous


rm console.log

rm console.log

--- tutor/src/api/index.coffee
+++ tutor/src/api/index.coffee
@@ -253,7 +253,6 @@

 start = (bootstrapData) ->
-  console.log(bootstrapData)
   Payments.embed_js_url = bootstrapData.payments_embed_js_url
   for storeId, action of BOOTSTRAPED_MODELS
     data = bootstrapData[storeId]


Automatically open CreateAccountView on anonymous prepaid link

Also fix prepaids that are measured in days

--- app/views/account/PrepaidView.coffee
+++ app/views/account/PrepaidView.coffee
@@ -6,7 +6,7 @@
 Prepaid = require '../../models/Prepaid'
 utils = require 'core/utils'
 Products = require 'collections/Products'
-
+CreateAccountModal = require 'views/core/CreateAccountModal/CreateAccountModal'

 module.exports = class PrepaidView extends RootView
   id: 'prepaid-view'
@@ -38,6 +38,10 @@
   afterRender: ->
     super()
     @$el.find("span[title]").tooltip()
+    if me.isAnonymous() and @ppc
+      _.defer => @openModalView(new CreateAccountModal({startOnPath: 'individual'})) unless @destroyed
+      window.nextURL = location.href
+

   statusMessage: (message, type='alert') ->
     noty text: message, layout: 'topCenter', type: type, killer: false, timeout: 5000, dismissQueue: true, maxVisible: 3
@@ -75,10 +79,12 @@
       @ppcInfo = []
       if model.get('type') is 'terminal_subscription'
         months = model.get('properties')?.months ? 0
+        days = model.get('properties')?.days ? 0
         maxRedeemers = model.get('maxRedeemers') ? 0
         redeemers = model.get('redeemers') ? []
         unlocksLeft = maxRedeemers - redeemers.length
-        @ppcInfo.push "This prepaid code adds #{months} months of subscription to your account."
+        duration = if months then "#{months} months" else "#{days} days"
+        @ppcInfo.push "This prepaid code adds #{duration} of subscription to your account."
         @ppcInfo.push "It can be used #{unlocksLeft} more times."
         # TODO: user needs to know they can't apply it more than once to their account
       else
@@ -114,3 +120,7 @@
       me.fetch cache: false
       @loadPrepaid(@ppc)
     @supermodel.addRequestResource('subscribe_prepaid', options, 0).load()
+
+  destroy: ->
+    super()
+    window.nextURL = null


welcomeemail: fixed indentation /cc @sent-hil

--- workers/social/lib/social/models/welcomeemail.coffee
+++ workers/social/lib/social/models/welcomeemail.coffee
@@ -5,19 +5,19 @@
   from    = KONFIG.email.defaultFromAddress
   subject = "Welcome to Koding!"
   content = """
-  Hi there!
+    Hi there!

-  Thanks for signing up for Koding. You are awesome!
+    Thanks for signing up for Koding. You are awesome!

-  Whether you are starting to learn something or looking to tinker with a new project, we hope that Koding will be able to help you achieve your goals.
+    Whether you are starting to learn something or looking to tinker with a new project, we hope that Koding will be able to help you achieve your goals.

-  We have prepared a lot of introductory material over at http://learn.koding.com . There you will find videos and how-to's on Koding as well as extensive guides on installing and configuring software packages like Wordpress, MySQL, MongoDB, Joomla, etc.
+    We have prepared a lot of introductory material over at http://learn.koding.com . There you will find videos and how-to's on Koding as well as extensive guides on installing and configuring software packages like Wordpress, MySQL, MongoDB, Joomla, etc.

-  Last, but not least, we're improving Koding every day so if you encounter any quirks, just drop us a line at support@koding.com.
+    Last, but not least, we're improving Koding every day so if you encounter any quirks, just drop us a line at support@koding.com.

-  Enjoy!
-  The Koding Team
-"""
+    Enjoy!
+    The Koding Team
+    """

   @send = (email, username, callback)->
     JMail = require './email'


.set operations should be immediately visible through .get

--- spec/config-spec.coffee
+++ spec/config-spec.coffee
@@ -1750,7 +1750,7 @@
           expect(atom.config.set('foo.bar.str_options', 'One')).toBe false
           expect(atom.config.get('foo.bar.str_options')).toEqual 'two'

-  describe "when .set/.unset is called prior to .loadUserConfig", ->
+  fdescribe "when .set/.unset is called prior to .loadUserConfig", ->
     beforeEach ->
       atom.config.settingsLoaded = false


This is COFFEESCRIPT ... (stamp into the pit)

--- spec/module_spec.coffee
+++ spec/module_spec.coffee
@@ -1,5 +1,5 @@
 describe "Lib.Module", ->
-
+
   beforeEach ->
     @Mod = Lib.Module

@@ -7,7 +7,7 @@
     delete @Mod

   describe "extend", ->
-
+
     it "copies over class methods", ->
       @Mod.extend
         foo: 123
@@ -15,13 +15,13 @@
       expect( @Mod ).toMatch
         foo: 123
         bar: 321
-
+
     it "calls extended callback if defined, passing the extended class", ->
       spy = jasmine.createSpy()
       @Mod.extend
         extended: spy
       expect( spy ).toHaveBeenCalledWith @Mod
-
+
     it "does not copy over 'extended' nor 'included' properties", ->
       @Mod.extend
         extended: ->
@@ -30,7 +30,7 @@
       expect( @Mod.included ).not.toBeDefined()

   describe "include", ->
-
+
     it "copies over instance methods", ->
       @Mod.include
         foo: 123
@@ -38,7 +38,7 @@
       expect( @Mod:: ).toMatch
         foo: 123
         bar: 321
-
+
     it "calls included callback if defined, passing the including class", ->
       spy = jasmine.createSpy()
       @Mod.include
@@ -60,4 +60,4 @@
       spyOn @Mod, "include"
       class Klass
       @Mod.extendAndInclude Klass
-      expect( @Mod.include ).toHaveBeenCalledWith Klass::+      expect( @Mod.include ).toHaveBeenCalledWith Klass::


Fix Reader specs.

Fix Reader specs.

--- spec/reader_spec.coffee
+++ spec/reader_spec.coffee
@@ -3,15 +3,21 @@

 describe 'Reader', ->
   beforeEach ->
-    @communicator = sinon.createStubInstance(Communicator)
-    @device       = {number: 0}
-    @dataType     = 'test'
-    @pluginMethod = 'Foo'
-    @reader       = new Reader(@communicator, @device, @dataType, @pluginMethod)
+    @communicator             = Communicator.get()
+    @device                   = {number: 0}
+    @dataType                 = 'test'
+    @pluginMethod             = 'Foo'
+    @reader                   = new Reader(@device, @dataType, @pluginMethod)
+    @communicator.pluginProxy = {StartReadFoo: -> true}
+    @invokeSpy                = sinon.spy(@communicator, 'invoke')
+
+  afterEach ->
+    Communicator.destroy()
+    @invokeSpy.restore()

   describe '#perform', ->
     it 'throws an error is the plugin is busy', ->
-      @communicator.busy.returns true
+      sinon.stub(Communicator.get(), 'busy').returns true
       expect(=> @reader.perform()).to.throw Error

     it 'returns a promise', ->


KDFramework: fixed destroy overwrite: destroy was overriden in jtreeview but super was never called, this was causing many silent errors

--- client/Framework/components/tree/treeview.coffee
+++ client/Framework/components/tree/treeview.coffee
@@ -45,6 +45,8 @@

     @getSingleton("windowController").revertKeyView @

+    super
+
   appendItemAtIndex:(itemInstance,index,animation)->

     itemInstance.setParent @


MainController: add one more Kontrol as kontrolProd to work remote machines from vagrant

--- client/Main/maincontroller/maincontroller.coffee
+++ client/Main/maincontroller/maincontroller.coffee
@@ -34,7 +34,10 @@

     KD.registerSingleton "mainController",            this

-    KD.registerSingleton "kontrol",                   new KodingKontrol
+    KD.registerSingleton "kontrol",     new KodingKontrol
+
+    KD.registerSingleton "kontrolProd", new KodingKontrol
+      kontrolUrl : "wss://kontrol.koding.com"

     KD.registerSingleton 'appManager',   appManager = new ApplicationManager
     KD.registerSingleton 'notificationController',    new NotificationController
@@ -82,7 +85,9 @@
     @on 'pageLoaded.as.loggedIn', (account)-> # ignore othter parameters
       KD.utils.setPreferredDomain account if account

-    (KD.getSingleton 'kontrol').reauthenticate()  if KD.useNewKites
+    if KD.useNewKites
+      (KD.getSingleton 'kontrol').reauthenticate()
+      (KD.getSingleton 'kontrolProd').reauthenticate()

     account.fetchMyPermissionsAndRoles (err, { permissions, roles }) =>
       return warn err  if err


OnboardingItemView: Remove overlay if exists.

--- client/Main/onboarding/onboardingitemview.coffee
+++ client/Main/onboarding/onboardingitemview.coffee
@@ -89,10 +89,10 @@

   listenEvents: ->
     @on "NavigationRequested", (direction) =>
-      @overlay.destroy()
+      @overlay?.destroy()
       @contextMenu.destroy()
       @getDelegate().emit "NavigationRequested", direction, @getData()

     @on "OnboardingCompleted", =>
-      @overlay.destroy()
+      @overlay?.destroy()
       @contextMenu.destroy()


activity: define `MessageEventHelper::addLike` method

--- client/Social/Activity/messageeventhelper.coffee
+++ client/Social/Activity/messageeventhelper.coffee
@@ -5,3 +5,14 @@
     super options, data

     @messages = {}
+
+
+  addLike: (message, {accountOldId, count}) ->
+
+    {like} = message.interactions
+
+    like.actorsCount = count
+    like.actorsPreview.unshift accountOldId  if accountOldId not in like.actorsPreview
+
+    message.emit "LikeAdded"
+    message.emit "update"


i lied

i lied

--- src/directives/device-list-directive.coffee
+++ src/directives/device-list-directive.coffee
@@ -4,5 +4,3 @@
     scope:
       devices: '='
     templateUrl: 'pages/device-list.html'
-    link: ($scope, element, attrs) =>
-      $scope.devices = attrs.devices


Blank disk image command

Blank disk image command

--- mos.coffee
+++ mos.coffee
@@ -31,6 +31,13 @@
     executeCommand 'hdiutil attach disks/mos.img'

 program
+  .command 'blank'
+  .description 'Build blank disk image'
+  .action (env, options)->
+    console.log 'Building blank disk image...'
+    executeCommand 'qemu-img create disks/blank.img 1440k'
+
+program
   .command 'build'
   .description 'Build Mos'
   .action (env, options)->


BUG -- fix an issue in the "defaults" function from misc, which was revealed by fixing another issue in hub (misnamed "required").

--- salvus/misc.coffee
+++ salvus/misc.coffee
@@ -43,7 +43,7 @@
         throw "misc.defaults -- TypeError: function takes inputs as an object #{error()}"
     r = {}
     for prop, val of obj2
-        if obj1.hasOwnProperty(prop)
+        if obj1.hasOwnProperty(prop) and obj1[prop]?
             if obj2[prop] == exports.defaults.required and not obj1[prop]?
                 console.trace()
                 throw "misc.defaults -- TypeError: property '#{prop}' must be specified: #{error()}"


qemux86.coffee: Set initial versioning and change to 1
the partition holding config.json

Signed-off-by: Florin Sarbu <3a837a37a2ac9072a088641ec330e0e5d5b86f4e@resin.io>

--- qemux86.coffee
+++ qemux86.coffee
@@ -19,6 +19,7 @@
 '''

 module.exports =
+	version: 1
 	slug: 'qemux86'
 	name: 'QEMU X86 32bit'
 	arch: 'i386'
@@ -45,8 +46,7 @@
 	configuration:
 		config:
 			partition:
-				primary: 4
-				logical: 1
+				primary: 1
 			path: '/config.json'

 	initialization: commonImg.initialization


catch async exceptions...

catch async exceptions...

--- server.coffee
+++ server.coffee
@@ -25,5 +25,9 @@
 	res.status(400)
 	res.send {success: false, errorMessage: 'Bad Request'}

+process.on 'uncaughtException', (err) ->
+  console.error('Caught exception: ' + err)
+
+#setTimeout ( ()-> d() ), 55
 app.listen 5757, ()->
 	console.log 'Started!'


Teamwork: Added chat toggle button.

Teamwork: Added chat toggle button.

--- client/Teamwork/Views/teamworkworkspace.coffee
+++ client/Teamwork/Views/teamworkworkspace.coffee
@@ -64,6 +64,18 @@
   createButtons: (panel) ->
     panel.addSubView @buttonsContainer = new KDCustomHTMLView
       cssClass : "tw-buttons-container"
+
+    @buttonsContainer.addSubView chatButton = new KDButtonView
+      cssClass : "tw-chat-toggle active"
+      iconClass: "tw-chat"
+      iconOnly : yes
+      callback : =>
+        cssClass      = "tw-chat-open"
+        isChatVisible = @hasClass cssClass
+        @toggleClass cssClass
+        chatButton.toggleClass "active"
+
+        if isChatVisible then @chatView.hide() else @chatView.show()

     @buttonsContainer.addSubView new KDButtonView
       iconClass: "tw-cog"


ComputeHelpers: handleCreate: route to /IDE after create

--- client/app/lib/providers/computehelpers.coffee
+++ client/app/lib/providers/computehelpers.coffee
@@ -176,9 +176,8 @@

             callback()

-            unless showError err
-              if redirectAfterCreation
-                kd.singletons.router.handleRoute "/IDE/#{machine.slug}"
+            return  if showError err
+            kd.singletons.router.handleRoute '/IDE'  if redirectAfterCreation

   @reviveProvisioner = (machine, callback)->


ProxifyUrl: Use {prod, dev}tunnel for tunnelled kites

--- client/app/lib/util/proxifyTransportUrl.coffee
+++ client/app/lib/util/proxifyTransportUrl.coffee
@@ -3,7 +3,7 @@
 module.exports = (url) ->

   return url  if globals.config.environment is 'dev'
-  return url  if /p.koding.com/.test url
+  return url  if /p\.koding\.com/.test url

   # let's use DOM for parsing the url
   parser = global.document.createElement('a')
@@ -17,10 +17,14 @@

   { protocol } = global.document.location

-
-  proxy = if globals.config.environment is 'production'
-  then 'prodproxy'
-  else 'devproxy'
+  if /\.koding\.me$/.test parser.hostname
+    proxy = 'devtunnel'
+    if globals.config.environment is 'production'
+      proxy = 'prodtunnel'
+  else
+    proxy = if globals.config.environment is 'production'
+    then 'prodproxy'
+    else 'devproxy'

   subdomain = if globals.config.environment is 'production'
   then 'p'


video: switch to logged in user when first connected

--- client/app/lib/videocollaboration/model.coffee
+++ client/app/lib/videocollaboration/model.coffee
@@ -95,6 +95,7 @@

     if helper.isVideoActive @channel
       @setActive()
+      @changeActiveParticipant getNick()

       return  unless @isMySession()

@@ -132,7 +133,7 @@
    *   - It just enables (starts publishing) the video chat and calls the optional callback.
    *   - If it's already being published,
    *     - it first unpublishes the current publisher.
-   *     - then creates a new publisher that is either `video-enabled` or `video-enabled`
+   *     - then creates a new publisher that is either `video-enabled` or `video-disabled`
    *       publishes that to the session.
    *     - updates the publisher's video data.
    *     - then updates the video state of the model.


Newkite should be agree with the new changes

--- client/landing/app/MainApp/kite/newkite.coffee
+++ client/landing/app/MainApp/kite/newkite.coffee
@@ -12,7 +12,7 @@

   getKiteAddr:()->
     requestData =
-      name       : "#{KD.nick()}"
+      username   : "#{KD.nick()}"
       remoteKite : @kiteName
       token      : KD.remote.getSessionToken()

@@ -24,7 +24,9 @@
        if response.status is 200
          data = JSON.parse data

-         console.log "Remote Kite is #{data[0].name}"
+         console.log "DATA", data
+
+         console.log "Remote Kite belongs to: #{data[0].username}, type: #{data[0].kitename}"
          console.log "Addr to be connected is #{data[0].addr}"
          console.log "Token to use is #{data[0].token}"


comment out codeshares.

comment out codeshares.

--- CakefileIncludes.coffee
+++ CakefileIncludes.coffee
@@ -309,7 +309,7 @@
         statusWidget                : "./client/app/Applications/Activity.kdapplication/widgets/statuswidget.coffee"
         questionWidget              : "./client/app/Applications/Activity.kdapplication/widgets/questionwidget.coffee"
         codeSnippetWidget           : "./client/app/Applications/Activity.kdapplication/widgets/codesnippetwidget.coffee"
-        codeShareWidget             : "./client/app/Applications/Activity.kdapplication/widgets/codesharewidget.coffee"
+        #codeShareWidget             : "./client/app/Applications/Activity.kdapplication/widgets/codesharewidget.coffee"
         linkWidget                  : "./client/app/Applications/Activity.kdapplication/widgets/linkwidget.coffee"
         tutorialWidget              : "./client/app/Applications/Activity.kdapplication/widgets/tutorialwidget.coffee"
         discussionWidget            : "./client/app/Applications/Activity.kdapplication/widgets/discussionwidget.coffee"
@@ -318,7 +318,7 @@
         actUpdateDisplay            : "./client/app/Applications/Activity.kdapplication/ContentDisplays/StatusUpdate.coffee"
         actCodeSnippetDisplay       : "./client/app/Applications/Activity.kdapplication/ContentDisplays/CodeSnippet.coffee"
         actDiscussionDisplay        : "./client/app/Applications/Activity.kdapplication/ContentDisplays/Discussion.coffee"
-        actCodeShareDisplay         : "./client/app/Applications/Activity.kdapplication/ContentDisplays/codeshare.coffee"
+        #actCodeShareDisplay         : "./client/app/Applications/Activity.kdapplication/ContentDisplays/codeshare.coffee"

         actQADisplay                : "./client/app/Applications/Activity.kdapplication/ContentDisplays/QA.coffee"
         actLinkDisplay              : "./client/app/Applications/Activity.kdapplication/ContentDisplays/link.coffee"


Expand preset precondition query

Expand preset precondition query

--- presets.coffee
+++ presets.coffee
@@ -3,6 +3,7 @@
 db = require './db'
 mongoQuery = require './mongo-query'
 mongodb = require 'mongodb'
+query = require './query'

 getPresets = (callback) ->
@@ -45,6 +46,7 @@
     # only fetch relevant params
     projection = {}
     for p in presets
+      p.precondition = query.expand(p.precondition)
       mongoQuery.projection(p.precondition, projection)

       for c in p.configurations


update data on list change & update hp and exp data on battle

--- src/Panel/helper.coffee
+++ src/Panel/helper.coffee
@@ -71,6 +71,10 @@
               type: 'conquest'
               msg:  @party
             }
+        # 更新结成数据
+        when 'party/setsword'
+          @parser request, (data)=>
+            @party = data
         # 启动
         when 'login/start'
           @parser request, (data)=>
@@ -88,7 +92,29 @@
               type: 'conquest'
               msg:  @party
             }
-
+        # 出阵报告
+        # TODO: 战斗报告解析，以便了解刀装破碎情况
+        when 'battle/battle'
+          @parser request, (data)=>
+            result = data.result
+            console.log result
+            _.forEach result.player.party.slot, (v, k)=>
+              # 经验值
+              console.log v.serial_id
+              @sword[v.serial_id]['exp'] = v.exp
+              @sword[v.serial_id]['level'] = v.level
+              # HP更新
+              @sword[v.serial_id]['hp'] = v.hp
+              if parseInt(v.hp, 10) < parseInt(@sword[v.serial_id]['hp_max'], 10)
+                # 损坏提醒
+                @sendMessage {
+                  type: 'notify'
+                  msg: {
+                    title: "小心！"
+                    message: "#{SID[v.sword_id]}已经受到了伤害！"
+                    contextMessage: "请谨慎行动"
+                  }
+                }
     # Parse data to json
     parser: (request, callback)->
       request.getContent (content, encoding)->


[improvement] Rendered a confirmation before deleting feedback with the keyboard shortcut (2m)

--- app/assets/javascripts/houston/feedback/views/conversations_view.coffee
+++ app/assets/javascripts/houston/feedback/views/conversations_view.coffee
@@ -123,6 +123,20 @@
     #
     rangy.init()

+    # IDEA: cycle through snippets with directional shortcuts
+
+    # Houston.shortcuts.create "mod+[", (e) =>
+    #   e.preventDefault()
+    #   if @selectedSnippetIndex > 0
+    #     @selectedSnippetIndex--
+    #     @redrawSnippets()
+    #
+    # Houston.shortcuts.create "mod+]", (e) =>
+    #   e.preventDefault()
+    #   if @selectedSnippetIndex < @selectedConversations[0].snippets().length - 1
+    #     @selectedSnippetIndex++
+    #     @redrawSnippets()
+
     # Create a snippet
     Houston.shortcuts.create "mod+k mod+s", "Create snippet from selected text", (e) =>
       e.preventDefault()
@@ -255,9 +269,9 @@
       when KEY.DELETE
         return unless e.metaKey
         return unless _.all @selectedConversations, (conversation)=> conversation.get('permissions').destroy
+        return unless @selectedConversations.length > 0
         e.preventDefault()
-        ids = (conversation.id for conversation in @selectedConversations)
-        @_deleteConversations(conversation_ids: ids)
+        @deleteConversations()

   keydownSearch: (e)->
     if e.keyCode is KEY.DOWN
@@ -573,7 +587,7 @@

   deleteConversations: (e)->
-    e.preventDefault()
+    e?.preventDefault()
     ids = @selectedIds()
     imports = _.uniq(@conversations.get(id).get('import') for id in ids)
     if imports.length is 1 and imports[0]


Start integrating the magnifier point tool

--- project.coffee
+++ project.coffee
@@ -1,4 +1,5 @@
 Pinpoint = require './pinpoint'
+MagnifierPoint = require 'marking-surface/lib/tools/magnifier-point'

 t7e = require 't7e'
 enUs = require './en-us'
@@ -41,7 +42,7 @@
       type: 'drawing'
       question: translate 'div', 'whatToDo'
       choices: [{
-        type: Pinpoint
+        type: MagnifierPoint
         value: 'adult'
         label: translate 'div', 'choices.adult'
         color: '#f90'


updated docker /update logic

updated docker /update logic

--- src/server/src/Server.coffee
+++ src/server/src/Server.coffee
@@ -25,13 +25,15 @@
       console.log '[request] /update'
       'git'.start_Process_Redirect_Console 'pull'
            .on 'exit', ()->
-              childProcess = './node_modules/.bin/gulp'.start_Process 'styles','pug','build'
-              childProcess.stdout.on 'data', (data)-> console.log(data.str().trim())
-              childProcess.stderr.on 'data', (data)-> console.log(data.str().trim())
-              console.log '----------------'
-              console.log req.url
-              childProcess.on 'exit', ()->
-                res.json { thanks: 'server-updated'}
+              'npm'.start_Process_Redirect_Console 'run  build-data'
+                .on 'exit', ()->
+                  childProcess = './node_modules/.bin/gulp'.start_Process 'styles','pug','build'
+                  childProcess.stdout.on 'data', (data)-> console.log(data.str().trim())
+                  childProcess.stderr.on 'data', (data)-> console.log(data.str().trim())
+                  console.log '----------------'
+                  console.log req.url
+                  childProcess.on 'exit', ()->
+                    res.json { thanks: 'server-updated'}

     @


change append to target

change append to target

--- app/assets/javascripts/models/active_page.coffee
+++ app/assets/javascripts/models/active_page.coffee
@@ -21,7 +21,7 @@

     @_append_data_model_to_page()

-    $("[data-append='" + @dash_name + "']").append($template)
+    $("[data-target='" + @dash_name + "']").append($template)

     error_tag = $("[data-error][data-k-uuid='" + @uuid + "']")
     error_tag.hide()


just show proposal description on open

just show proposal description on open

Former-commit-id: d597ede30ca687d8fd94e4f79b5d4f0af9e158a3 [formerly 5b1a2d22e8027910b3a21722883602ada26d7971] [formerly 20e53a24b3030d7e794464c3ea1542f050d69b64 [formerly 20e53a24b3030d7e794464c3ea1542f050d69b64 [formerly 8dcbbf8adc463a20942c80b470bcf3ec45b2bfc4]]]
Former-commit-id: 1ee39f949c8e455c010ba5d539186785ee9cfe8d [formerly c2a657d48b3c59e04039568014e4560692c35fa4]
Former-commit-id: 981f740b0c3e7ab0bfab2027b5036f14977cd14e
Former-commit-id: a2e27e1806637e85c1c82c38a79edaa9cfc72f78

--- app/assets/javascripts/views/proposalview.coffee
+++ app/assets/javascripts/views/proposalview.coffee
@@ -154,9 +154,9 @@

       @$hidden_els = $("[data-role='m-proposal']:not([data-id='#{@model.id}'])")
       @$hidden_els.css('display', 'none')
-      @$el.find('.m-proposal-description-body').slideDown()
-
-      @$el.find('.m-proposal-description-details').slideDown()
+      @$el.find('.m-proposal-description-body').show() #slideDown()
+
+      @$el.find('.m-proposal-description-details').show() #slideDown()

     callback(new_state)


:bug: Fix bad custom id

:bug: Fix bad custom id

--- lib/csv-editor-form-element.coffee
+++ lib/csv-editor-form-element.coffee
@@ -41,7 +41,7 @@
           @label class: 'setting-title', label
           @tag 'atom-text-editor', outlet: "#{outlet}TextEditorElement", mini: true

-        options.options["custom-#{name}"] = 'custom'
+        options.options["custom"] = 'custom'
         radios(options)

       CSVEditorFormElement::__bindings__ ?= []


Finish docs for up.protocol

Finish docs for up.protocol

--- lib/assets/javascripts/unpoly/protocol.coffee
+++ lib/assets/javascripts/unpoly/protocol.coffee
@@ -648,10 +648,12 @@
   Configures strings used in the optional [server protocol](/up.protocol).

   @property up.protocol.config
+
   @param {string} [config.csrfHeader='X-CSRF-Token']
     The name of the HTTP header that will include the
     [CSRF token](https://en.wikipedia.org/wiki/Cross-site_request_forgery#Synchronizer_token_pattern)
     for AJAX requests.
+
   @param {string|Function(): string} [config.csrfParam]
     The `name` of the hidden `<input>` used for sending a
     [CSRF token](https://en.wikipedia.org/wiki/Cross-site_request_forgery#Synchronizer_token_pattern) when
@@ -663,7 +665,10 @@

     Defaults to the `content` attribute of a `<meta>` tag named `csrf-param`:

-        <meta name="csrf-param" content="authenticity_token" />
+    ```html
+    <meta name="csrf-param" content="authenticity_token" />
+    ```
+
   @param {string|Function(): string} [config.csrfToken]
     The [CSRF token](https://en.wikipedia.org/wiki/Cross-site_request_forgery#Synchronizer_token_pattern)
     to send for unsafe requests. The token will be sent as either a HTTP header (for AJAX requests)
@@ -674,7 +679,10 @@

     Defaults to the `content` attribute of a `<meta>` tag named `csrf-token`:

-        <meta name='csrf-token' content='secret12345'>
+    ```
+    <meta name='csrf-token' content='secret12345'>
+    ```
+
   @param {string} [config.methodParam='_method']
     The name of request parameter containing the original request method when Unpoly needs to wrap
     the method.


webapp/quotas: make sure all quotas exist in the dict of upgrades to avoid NaN vals

--- src/smc-webapp/projects.cjsx
+++ src/smc-webapp/projects.cjsx
@@ -41,6 +41,8 @@
 {BillingPageSimplifiedRedux} = require('./billing')
 {UsersViewing} = require('./other-users')
 {PROJECT_UPGRADES} = require('smc-util/schema')
+{fromPairs} = require('lodash')
+ZERO_QUOTAS = fromPairs(Object.keys(PROJECT_UPGRADES.params).map(((x) -> [x, 0])))

 { reuseInFlight } = require("async-await-utils/hof")

@@ -778,7 +780,8 @@
         users = @getIn(['project_map', project_id, 'users'])?.toJS()
         if not users?
             return
-        upgrades = {}
+        # clone zeroed quota upgrades, to make sure they're always defined
+        upgrades = Object.assign({}, ZERO_QUOTAS)
         for account_id, info of users
             for prop, val of info.upgrades ? {}
                 upgrades[prop] = (upgrades[prop] ? 0) + val


fix https://github.com/sagemathinc/smc/issues/1026 -- "viewing a chatroom (not side chat) seems to often result in a notification claiming that you changed it even though you didn't"; this was just a weird bug introduced when simon merged something, I guess. The code is nonsense.

--- src/smc-webapp/smc_chat.cjsx
+++ src/smc-webapp/smc_chat.cjsx
@@ -533,7 +533,6 @@

     mark_as_read: ->
         @props.redux.getActions('file_use').mark_file(@props.project_id, @props.path, 'read')
-        @props.redux.getActions('file_use').mark_file(@props.project_id, @props.path, 'chat')

     keydown : (e) ->
         # TODO: Add timeout component to is_typing
@@ -612,7 +611,7 @@
             # so to see if a race happened, and in that case, have a resolution protocol
             # then close and re-open the chat window for any user where the race occurred.
             # This is https://github.com/sagemathinc/smc/issues/1007
-
+

     on_unload: ->
         @props.actions.set_is_video_chat(false)


Added clients and groups.

Added clients and groups.

--- records.coffee
+++ records.coffee
@@ -1,8 +1,61 @@
 RECORD_INFOS =
   client: [
+    name:   "user"
+    type:   "textline"
+    desc:   "Login–Name"
+   ,
+    name:   "title"
+    type:   "textline"
+    desc:   "Anrede"
+   ,
+    name:   "surname"
+    type:   "textline"
+    desc:   "Vorname"
+   ,
+    name:   "name"
+    type:   "textline"
+    desc:   "Name"
+   ,
+    name:   "email"
+    type:   "email"
+    desc:   "E–Mail"
+   ,
+    name:   "password"
+    type:   "password"
+    desc:   "Password"
+   ,
+    name:   "role"
+    type:   "selection"
+    opts:   ["User", "Admin", "Superadmin"]
+    desc:   "Rolle"
+  ]
+
+  group: [
+    name:  "name"
+    type:  "textline"
+    desc:  "Name"
+   ,
+    name:  "admin"
+    type:  "user"
+    desc:  "Gruppenadmin"
+   ,
+    name:    "clients"
+    type:    "xreflist"
+    records: "clients"
+    unique:  "yes"
+    desc:    "Apps"
+  ]
+
+  app: [
     name:  "name"
     type:  "textline"
     desc:  "Name der Applikation"
+   ,
+    name:    "client"
+    type:    "xref"
+    records: "clients"
+    field:   "name"
+    desc:    "Kunde"
    ,
     name:  "name"
     type:  "selection"
@@ -188,6 +241,7 @@
     ]
   ]

+###
   address: [
     name: "name"
     type: "text"
@@ -233,3 +287,4 @@
     type: "degree"
     desc: "Drehung"
   ]
+###


ScrollInfinite :: Reflow Tooltip plugin after ajax load

--- app/assets/javascripts/modules/scroll_infinite.coffee
+++ app/assets/javascripts/modules/scroll_infinite.coffee
@@ -8,11 +8,15 @@
       if url && $(window).scrollTop() > ($(document).height() - $(window).height() - 50)
         $pagination.text(I18n.t('scroll_infinite.fetch_nexts'))
         $.getScript(url).done((script, textStatus) ->
-          friendly_date()
-          magnific_popup_init()
-          $('.fotorama').fotorama()
+          plugins_reflow()
           return
         )
       return
     ), 100)
     $(window).scroll()
+
+plugins_reflow = ->
+  friendly_date()
+  magnific_popup_init()
+  $('.fotorama').fotorama() if $('.fotorama').length > 0
+  $('[data-tooltip=""]').foundation() if $('[data-tooltip=""]').length > 0


Focus on title field on Paper#new screen

--- app/assets/javascripts/views/paper_new_view.js.coffee
+++ app/assets/javascripts/views/paper_new_view.js.coffee
@@ -6,3 +6,7 @@
   resetBackgroundColor:(->
     $('html').removeClass('matte')
   ).on('willDestroyElement')
+
+  focusOnTitleField: (->
+    @.$('#paper-short-title').focus()
+  ).on('didInsertElement')


added query methods

added query methods

--- server.coffee
+++ server.coffee
@@ -37,17 +37,32 @@

 connection.connect()

-getData = (connection, appId) ->
-  connection.query('SELECT m.* from monitor m where m.appId=? and m.id=(select max(id) from monitor m2 where m2.appId=?)',[appId,appId], (err, rows, fields) ->
+getLastData = (connection, appId) ->
+  console.log "querying for last insert"
+  connection.query('SELECT m.* from data m where m.pc=? and m.id=(select max(id) from data m2 where m2.pc=?)',[appId,appId], (err, rows, fields) ->
     throw err if (err)
     console.log('Query result: ', rows)

     return rows
   )

-getData(connection, 'webapp1')
-getData(connection, 'webapp2')
-getData(connection, 'webapp3')
+
+getAllData = (connection, appId) ->
+  console.log "querying for all data"
+  connection.query('SELECT m.* from data m where m.pc=?',[appId], (err, rows, fields) ->
+    throw err if (err)
+    console.log('Query result: ', rows)
+
+    return rows
+  )
+
+
+getLastData(connection, 'pc1')
+getLastData(connection, 'pc2')
+
+getAllData(connection, 'pc1')
+getAllData(connection, 'pc2')
+

 connection.end()


add ability to set PORT in env var

add ability to set PORT in env var

--- server.coffee
+++ server.coffee
@@ -1,4 +1,4 @@
 process.chdir __dirname

 Kriegspiel = require './lib/kriegspiel'
-new Kriegspiel()
+new Kriegspiel port: process.env.PORT


Allow user to specify port

Allow user to specify port

--- server.coffee
+++ server.coffee
@@ -15,7 +15,7 @@

 app = express()

-app.set 'port', process.env.WEATHER_SERVICE_PORT || 3000
+app.set 'port', process.env?.WEATHER_SERVICE_PORT || process.env?.PORT || 3000
 app.set 'views', path.join(__dirname, 'views')
 app.set 'view engine', 'jade'
 # app.use favicon(__dirname + '/public/favicon.ico'


Allow removal of optional fields.

Allow removal of optional fields.

--- server.coffee
+++ server.coffee
@@ -598,7 +598,7 @@
     backward: (document, collection, currentSchema, oldSchema) =>
       assert @fields

-      @updateAll document, collection, currentSchema, newSchema
+      @updateAll document, collection, currentSchema, oldSchema

       counts = super
       counts.migrated = counts.all
@@ -709,9 +709,10 @@
           _schema: oldSchema
       for field, value of @fields
         if _.isFunction value
-          update.$set[field] = value()
+          v = value()
         else
-          update.$set[field] = value
+          v = value
+        update.$set[field] = v unless _.isUndefined v

       count = collection.update selector, update, {multi: true}


Re-activates handler change detection on backend/interventions form

--- app/assets/javascripts/backend/interventions.js.coffee
+++ app/assets/javascripts/backend/interventions.js.coffee
@@ -123,6 +123,10 @@
     $(this).each ->
       E.interventions.refresh $(this)

+  $(document).on 'keyup change', 'select[data-intervention-updater]', ->
+    $(this).each ->
+      E.interventions.refresh $(this)
+
   # $(document).on 'change', '*[data-procedure-global="at"]', ->
   #   $(this).each ->
   #     E.interventions.refresh $(this)


Toggleability for koiHover.

Toggleability for koiHover.

--- app/assets/javascripts/koi/lib/jquery/koi-hover.coffee
+++ app/assets/javascripts/koi/lib/jquery/koi-hover.coffee
@@ -2,7 +2,8 @@

 $.fn.koiHover = ( args... ) ->
   delay = if typeof args[0] is 'number' then args.shift() else 0
-  [ over, leave ] = args
+  over = args.shift()
+  leave = args.shift() or over

   @each =>
     nowRunning = false


mama mia

mama mia

--- app/assets/javascripts/lib/workflow-methods-mixin.cjsx
+++ app/assets/javascripts/lib/workflow-methods-mixin.cjsx
@@ -210,18 +210,11 @@

   getTasks: ->
     # Add completion_assessment_task to list of tasks dynamically:
-# <<<<<<< HEAD
     tasks = @getActiveWorkflow().tasks
     completion_assessment_task = @getCompletionAssessmentTask()
     # Merge keys recursively if it exists in config
     completion_assessment_task = $.extend true, tasks['completion_assessment_task'], completion_assessment_task if tasks['completion_assessment_task']?
     $.extend tasks, completion_assessment_task: completion_assessment_task
-# =======
-#     tasks = @getActiveWorkflow().tasks
-#     if @props.workflowName == 'mark'
-#       tasks = $.extend tasks, completion_assessment_task: @getCompletionAssessmentTask()
-#     tasks
-# >>>>>>> master

   # Get instance of current tool:
   getCurrentTool: ->


REFACTOR: cleaning cleaning cleaning

REFACTOR: cleaning cleaning cleaning

--- source/Art/Engine/Core/Element.coffee
+++ source/Art/Engine/Core/Element.coffee
@@ -762,12 +762,6 @@
       child.draw target, child.getElementToTargetMatrix elementToTargetMatrix
     @children # without this, coffeescript returns a new array

-  # TODO - use new filterSource stuff
-  _accountForOverdraw: (proposedTargetSpaceDrawArea, elementToTargetMatrix) ->
-    for child in @children when child.overDraw
-      proposedTargetSpaceDrawArea = child.overDraw proposedTargetSpaceDrawArea, elementToTargetMatrix
-    proposedTargetSpaceDrawArea
-
   # OVERRIDE _drawWithClipping AND hasCustomClipping for custom clipping (RectangleElement, for example)
   _drawWithClipping: (clipArea, target, elementToTargetMatrix)->
     throw new Error "bad matrix" unless elementToTargetMatrix.getIsTranslateAndScaleOnly()
@@ -882,11 +876,13 @@
     @getCacheIsValid() || @_generateDrawCache()

     drawCacheManager.useDrawCache @
-    drawCacheToTargetMatrix = @_drawCacheToElementMatrix.mul elementToTargetMatrix
-    target.drawBitmap drawCacheToTargetMatrix, @_drawCacheBitmap,
-      opacity:        @opacity
-      compositeMode:  @compositeMode
-
+    target.drawBitmap(
+      @_drawCacheToElementMatrix.mul elementToTargetMatrix
+      @_drawCacheBitmap
+      {@opacity, @compositeMode}
+    )
+
+  # TODO - use new filterSource stuff and accountForOverdraw
   _generateDrawCache: ->

     @_clearDrawCache()


REFACTOR: drawing code cleanup - cleanup mostly done

--- source/Art/Engine/Core/Element.coffee
+++ source/Art/Engine/Core/Element.coffee
@@ -774,13 +774,6 @@
     stats.stagingBitmapsCreated++

     stagingBitmap ||= @bitmapFactory.newBitmap targetSpaceDrawArea.size
-    # throw new Error "stagingBitmap too small" unless stagingBitmap.size.gte targetSpaceDrawArea.size
-
-    # @log _renderStagingBitmap:
-    #   element:@inspectedName
-    #   stagingBitmap_size:stagingBitmap.size
-    #   elementToTargetMatrix:elementToTargetMatrix
-    #   targetSpaceDrawArea:targetSpaceDrawArea

     @_currentDrawTarget = stagingBitmap
     @_currentToTargetMatrix = elementToTargetMatrix
@@ -789,12 +782,6 @@
       @_clipDraw null, stagingBitmap, elementToTargetMatrix
     else
       @_drawChildren stagingBitmap, elementToTargetMatrix, true
-
-    # log _renderStagingBitmap:
-    #   element: @inspectedName
-    #   targetSpaceDrawArea:targetSpaceDrawArea
-    #   elementToTargetMatrix: elementToTargetMatrix
-    #   stagingBitmap: stagingBitmap

     stagingBitmap

@@ -804,32 +791,14 @@
       proposedTargetSpaceDrawArea = child.overDraw proposedTargetSpaceDrawArea, elementToTargetMatrix
     proposedTargetSpaceDrawArea

-  # create, draw and composite stagingBitmap with target
-  _drawWithStagingBitmap: (targetSpaceDrawArea, target, elementToTargetMatrix) ->
-    # @log _drawWithStagingBitmap:element:@inspectedName, targetSpaceDrawArea:targetSpaceDrawArea, elementToTargetMatrix:elementToTargetMatrix
-    targetSpaceDrawArea = @_accountForOverdraw targetSpaceDrawArea, elementToTargetMatrix
-
-    stagingBitmap = @_renderStagingBitmap targetSpaceDrawArea, elementToTargetMatrix
-
-    target.drawBitmap targetSpaceDrawArea.locationMatrix, stagingBitmap, compositeMode:@_compositeMode, opacity: @opacity
-
-  _clippedDrawWithStagingBitmapInElementSpace: (target, elementToTargetMatrix) ->
-    s = elementToTargetMatrix.getExactScale()
-    stagingBitmap = @_renderStagingBitmap rect(0, 0, @_currentSize.x * s.x, @_currentSize.y * s.y), m = Matrix.scale s
-    target.drawBitmap m.inv.mul(elementToTargetMatrix), stagingBitmap, compositeMode:@_compositeMode, opacity: @opacity
-
+  # OVERRIDE _clipDraw AND hasCustomClipping for custom clipping (RectangleElement, for example)
   _clipDraw: (clipArea, target, elementToTargetMatrix)->
-    if !elementToTargetMatrix.getIsTranslateAndScaleOnly() || @needsStagingBitmap
-      @_clippedDrawWithStagingBitmapInElementSpace target, elementToTargetMatrix
-    else
-      target.clippedTo clipArea, =>
-        @_drawChildren target, elementToTargetMatrix
+    throw new Error "bad matrix" unless elementToTargetMatrix.getIsTranslateAndScaleOnly()
+    target.clippedTo clipArea, =>
+      @_drawChildren target, elementToTargetMatrix

   @getter
     hasCustomClipping: -> false
-
-  # if drawPerformanceDebug = false
-  #   drawDepth = 0

   draw: (target, elementToTargetMatrix)->
     stats.elementsDrawn++
@@ -842,7 +811,7 @@
       targetSpaceDrawArea = @drawAreaIn(elementToTargetMatrix).intersection target.getClippingArea()
       return unless targetSpaceDrawArea.area > 0

-      if @getCacheDrawRequired()
+      if @getCacheDrawRequired elementToTargetMatrix
         @_cachedFullDraw targetSpaceDrawArea, target, elementToTargetMatrix
       else
         @_clearDrawCache()
@@ -914,11 +883,18 @@

   @_cachingDraws: 0

+  getCacheDrawRequired: (elementToTargetMatrix) -> @getNeedsStagingBitmap(elementToTargetMatrix) || (config.drawCacheEnabled && Element._cachingDraws == 0 && @getCacheable() && @getCacheDraw())
+  getNeedsStagingBitmap: (elementToTargetMatrix) ->
+    (
+      (
+        (@getHasChildren() || @getIsMask()) &&
+        (@_compositeMode != "normal" || @_opacity < 1 || @getChildRequiresParentStagingBitmap())
+      ) ||
+      (elementToTargetMatrix && @_clip && !elementToTargetMatrix.getIsTranslateAndScaleOnly())
+    )
+
   @getter
-    cacheDrawRequired: -> @getNeedsStagingBitmap() || (config.drawCacheEnabled && Element._cachingDraws == 0 && @getCacheable() && @getCacheDraw())
     cacheIsValid: -> !!@_drawCacheBitmap
-    needsStagingBitmap: ->
-      (@getHasChildren() || @getIsMask()) && (@_compositeMode != "normal" || @_opacity < 1 || @getChildRequiresParentStagingBitmap())

     # override this for elements which are faster w/o caching (RectangleElement, BitmapElement)
     cacheable: -> true


Add unstack option

Add unstack option

--- app/assets/javascripts/pgdatagraph/datagraph.js.coffee
+++ app/assets/javascripts/pgdatagraph/datagraph.js.coffee
@@ -14,6 +14,7 @@
     series: {}
     dateFormat: "M d, yy"
     renderer: "line"
+    unstack: no
     detailSmoothing: 10
     overviewSmoothing: 10
     yAxisTickFormat: (y) -> y
@@ -138,6 +139,7 @@
       series: series
       renderer: "multi"
       dotSize: 2
+      unstack: @options.unstack

     xAxis = new Rickshaw.Graph.Axis.Time
       graph: @detailGraph
@@ -175,6 +177,7 @@
       preserve: yes
       series: series
       renderer: "multi"
+      unstack: @options.unstack

     xAxis = new Rickshaw.Graph.Axis.Time
       graph: @overviewGraph


Added logic to extend order adjustments to use model

--- app/assets/javascripts/sprangular/services/cart.coffee
+++ app/assets/javascripts/sprangular/services/cart.coffee
@@ -39,9 +39,9 @@
       order.adjustmentTotal = Number(data.adjustment_total)
       order.total = Number(data.total)
       order.shipToBillAddress = data.use_billing
-      order.adjustments = data.adjustments
+      order.adjustments = Sprangular.extend(data.adjustments, Sprangular.Adjustment)

-      order.locateCouponCode()
+      order.locatePromoCode()

       if data.bill_address
         order.billingAddress = Sprangular.extend(data.bill_address, Sprangular.Address)


draw recruits and top locations

draw recruits and top locations

--- source/javascripts/recruit-map.coffee
+++ source/javascripts/recruit-map.coffee
@@ -34,21 +34,64 @@
 #
 # Returns nothing
 render = (event, env) ->
-  canvas = d3.select('#recruit-map').append('canvas')
+  canvas  = d3.select('#recruit-map').append('canvas')
     .attr('width', env.width)
     .attr('height', env.height)
   context = canvas.node().getContext '2d'
-  env.path.context(context)
+  path    = d3.geo.path().projection(env.projection).context(context)
+  radius  = d3.scale.linear().domain([1, 2, 3, 4, 5]).range [0.1, 0.3, 0.5, 0.8, 2]

   scaleForRetina canvas, context

+  # Draw US national outline and states outlines
   context.beginPath()
-  env.path(env.nation)
-  env.path(env.states)
+  path(env.nation)
+  path(env.states)
+  context.save()
+
   context.lineWidth = 1
   context.strokeStyle = "#333"
   context.stroke()

+  context.shadowBlur = 6
+  context.globalCompositeOperation = 'color-dodge'

+  bygid = d3.nest()
+    .key((d) -> d.gid)
+    .rollup((d) -> d[0])
+    .map(env.places)
+
+  hotspots = d3.nest()
+    .key((d) -> d.place_gid)
+    .rollup((d) -> d.length)
+    .entries(env.recruits)
+
+  hotspots.sort (a, b) -> d3.descending(a.values, b.values)
+  topPlaces = hotspots[0..10].map (spot) -> spot.place = bygid[spot.key]; spot
+
+  for recruit in env.recruits
+    coordinates = env.projection [recruit.lat, recruit.lon]
+    context.beginPath()
+    context.arc coordinates[0], coordinates[1], radius(recruit.stars), 0, Math.PI * 2, false
+    context.fillStyle = env.colors[recruit.stars - 1]
+    context.shadowColor = env.colors[recruit.stars - 1]
+    context.fill()
+
+  context.restore()
+  context.shadowBlur = 0
+  context.globalCompositeOperation = 'normal'
+  context.fillStyle = 'fff'
+  context.lineWidth = 0.5
+  context.strokeStyle = "333"
+
+  for city in topPlaces
+    place = city.place
+    coordinates = env.projection [place.lon, place.lat]
+    context.beginPath()
+    context.arc coordinates[0], coordinates[1], 3, 0, Math.PI * 2, false
+    context.fillText("#{place.name}", coordinates[0] + 5, coordinates[1] + 5)
+    context.strokeText("#{place.name}", coordinates[0] + 5, coordinates[1] + 5)
+    context.fill()
+    context.stroke()

 $(document).on 'data.loaded', render


:white_check_mark: Test cache invalidation

--- spec/native-compile-cache-spec.coffee
+++ spec/native-compile-cache-spec.coffee
@@ -1,3 +1,6 @@
+fs = require 'fs'
+path = require 'path'
+
 describe "NativeCompileCache", ->
   nativeCompileCache = require '../src/native-compile-cache'
   [fakeCacheStore, cachedFiles] = []
@@ -8,15 +11,30 @@
     nativeCompileCache.setCacheStore(fakeCacheStore)
     nativeCompileCache.install()

+    fs.writeFileSync path.resolve('./spec/fixtures/native-cache/file-4'), """
+    module.exports = function () { return "file-4" }
+    """
+
+  afterEach ->
+    fs.unlinkSync path.resolve('./spec/fixtures/native-cache/file-4')
+
   it "writes and reads from the cache storage when requiring files", ->
-    fakeCacheStore.has.andReturn(false)
+    fakeCacheStore.has.andCallFake (cacheKey, invalidationKey) ->
+      fakeCacheStore.get(cacheKey, invalidationKey)?
+    fakeCacheStore.get.andCallFake (cacheKey, invalidationKey) ->
+      for entry in cachedFiles
+        continue if entry.cacheKey isnt cacheKey
+        continue if entry.invalidationKey isnt invalidationKey
+        return entry.cacheBuffer
+      return
     fakeCacheStore.set.andCallFake (cacheKey, invalidationKey, cacheBuffer) ->
-      cachedFiles.push({cacheKey, cacheBuffer})
+      cachedFiles.push({cacheKey, invalidationKey, cacheBuffer})

     fn1 = require('./fixtures/native-cache/file-1')
     fn2 = require('./fixtures/native-cache/file-2')
+    fn4 = require('./fixtures/native-cache/file-4')

-    expect(cachedFiles.length).toBe(2)
+    expect(cachedFiles.length).toBe(3)

     expect(cachedFiles[0].cacheKey).toBe(require.resolve('./fixtures/native-cache/file-1'))
     expect(cachedFiles[0].cacheBuffer).toBeInstanceOf(Uint8Array)
@@ -28,14 +46,26 @@
     expect(cachedFiles[1].cacheBuffer.length).toBeGreaterThan(0)
     expect(fn2()).toBe(2)

-    fakeCacheStore.has.andReturn(true)
-    fakeCacheStore.get.andReturn(cachedFiles[0].cacheBuffer)
-    fakeCacheStore.set.reset()
+    expect(cachedFiles[2].cacheKey).toBe(require.resolve('./fixtures/native-cache/file-4'))
+    expect(cachedFiles[2].cacheBuffer).toBeInstanceOf(Uint8Array)
+    expect(cachedFiles[2].cacheBuffer.length).toBeGreaterThan(0)
+    expect(fn4()).toBe("file-4")

+    fs.appendFileSync(require.resolve('./fixtures/native-cache/file-4'), "\n")
+    delete require('module')._cache[require.resolve('./fixtures/native-cache/file-1')]
+    delete require('module')._cache[require.resolve('./fixtures/native-cache/file-4')]
     fn1 = require('./fixtures/native-cache/file-1')
+    fn4 = require('./fixtures/native-cache/file-4')

-    expect(fakeCacheStore.set).not.toHaveBeenCalled()
+    # file content has changed, ensure we create a new cache entry
+    expect(cachedFiles.length).toBe(4)
+    expect(cachedFiles[3].cacheKey).toBe(require.resolve('./fixtures/native-cache/file-4'))
+    expect(cachedFiles[3].invalidationKey).not.toBe(cachedFiles[2].invalidationKey)
+    expect(cachedFiles[3].cacheBuffer).toBeInstanceOf(Uint8Array)
+    expect(cachedFiles[3].cacheBuffer.length).toBeGreaterThan(0)
+
     expect(fn1()).toBe(1)
+    expect(fn4()).toBe("file-4")

   it "deletes previously cached code when the cache is an invalid file", ->
     fakeCacheStore.has.andReturn(true)


Serialize TreeView width

Serialize TreeView width

--- src/packages/tree-view/src/tree-view.coffee
+++ src/packages/tree-view/src/tree-view.coffee
@@ -36,6 +36,7 @@
     treeView.selectEntryForPath(state.selectedPath)
     treeView.focusAfterAttach = state.hasFocus
     treeView.scrollTopAfterAttach = state.scrollTop
+    treeView.width(state.width)
     treeView.attach() if state.attached
     treeView

@@ -81,6 +82,7 @@
     hasFocus: @hasFocus()
     attached: @hasParent()
     scrollTop: @scrollTop()
+    width: @width()

   deactivate: ->
     @root?.unwatchEntries()


Tweak Twitter.

Tweak Twitter.

--- config/jknt-smblott.coffee
+++ config/jknt-smblott.coffee
@@ -104,8 +104,11 @@
     "div.home-stream div.content"
     "div.profile-stream div.content"
     "div.list-stream div.content"
+    "div.new-tweets-bar"
   ]
   offset: 80
+  style:
+    "border-color": "#55B4CF"

 configs.push
   name: "Google Plus"


fix https://github.com/sagemathinc/cocalc/issues/2599 -- "plain jupyter server" link causes exception

--- src/smc-webapp/widgets-misc/link-retry.cjsx
+++ src/smc-webapp/widgets-misc/link-retry.cjsx
@@ -17,7 +17,12 @@
         error   : false

     open: ->
-        window.open(@props.href, @props.target).focus()
+        w = window.open(@props.href, @props.target)
+        # see https://github.com/sagemathinc/cocalc/issues/2599
+        # Sometimes randomly w isn't defined -- so waiting a little and
+        # failing gracefully if not seems reasonable; user can always manually
+        # switch to the new tab/window.
+        setTimeout((->w?.focus()), 250)

     start: ->
         @setState(loading:true, error:false)


webapp error reporter: better serialization of arbitrary objects

--- src/webapp-lib/webapp-error-reporter.coffee
+++ src/webapp-lib/webapp-error-reporter.coffee
@@ -265,7 +265,7 @@
 sendLogLine = (severity, args) ->
     misc = require('smc-util/misc')
     if typeof(args) == 'object'
-        message = misc.to_safe_str(args)
+        message = misc.trunc_middle(misc.to_json(args), 1000)
     else
         message = Array.prototype.slice.call(args).join(", ")
     sendError(


Config: change staing redis config

Config: change staing redis config

--- config/main.staging.coffee
+++ config/main.staging.coffee
@@ -53,7 +53,7 @@
   buildClient   : yes
   runOsKite     : no
   runProxy      : no
-  redis         : "localhost:6379"
+  redis         : "172.16.3.13:6379"
   misc          :
     claimGlobalNamesForUsers: no
     updateAllSlugs : no


config: switch staging broker url to point to sj

--- config/main.staging.coffee
+++ config/main.staging.coffee
@@ -135,7 +135,7 @@
       mainUri   : "http://koding.com"
       broker    :
         servicesEndpoint: "/-/services/broker"
-        sockJS   : "http://stage-broker-#{version}.in.koding.com/subscribe"
+        sockJS   : "http://stage-broker-#{version}.sj.koding.com/subscribe"
       apiUri    : 'https://www.koding.com'
       appsUri   : 'https://koding-apps.s3.amazonaws.com'
       uploadsUri: 'https://koding-uploads.s3.amazonaws.com'


Config: add graphitefeeder back into vagrant config

--- config/main.vagrant.coffee
+++ config/main.vagrant.coffee
@@ -395,6 +395,7 @@
     kontrolproxy  : "notice"
     kontroldaemon : "notice"
     vmproxy       : "notice"
+    graphitefeeder: "info"
     sync          : "notice"
     postModifier  : "notice"
     router        : "notice"


Bug with setting attr on missed item fixed

--- style.coffee
+++ style.coffee
@@ -220,7 +220,9 @@
 			return r

 		props = getSplitAttr(name)
-		obj = @item
+		unless obj = @item
+			return false
+
 		for prop, i in props
 			if i is props.length - 1
 				unless prop of obj


removing index file restriction for redirects to allow redirects to dynamic routes

--- modules/http/platforms/node/lib/Server.coffee
+++ modules/http/platforms/node/lib/Server.coffee
@@ -125,12 +125,7 @@
                         ## so we send a redirect back to the browser just to add this slash
                         ## ugly but hopeless... we must ensure the browser sees a directory

-                        ## refuse the access permission if there is no index file
-                        if !files.fs.existsSync (file = files.path.join file, @data.index)
-                            return next new floyd.error.Forbidden req.url
-
-                        else
-                            return res.redirect path+'/'+params
+                        return res.redirect path+'/'+params

                     res.setHeader 'Last-Modified', stats.mtime.toUTCString()


added api call to set the text of a challenge

--- api.coffee
+++ api.coffee
@@ -29,9 +29,11 @@
       return false

   # direct etherpad interaction
-  getEtherpadAPI = (api, pad, cb) ->
+  getEtherpadAPI = (api, pad, params, cb) ->
     url = "http://localhost:#{config.etherpad_internal_port}" + \
           "/api/1/#{api}?apikey=#{config.etherpadAPIKey}&padID=#{pad}"
+    url += "&#{encodeURIComponent k}=#{encodeURIComponent v}" for k ,v of params
+
     request.get url, (err, eresp, body) ->
       if not err and eresp.statusCode == 200
         data = JSON.parse(body)
@@ -115,16 +117,26 @@
     validateApiKey req, res, (user) ->
       validArg = {challenge: {title: 'string', category: 'string', points: 'number'}}
       if validateArguments req, res, validArg
+        sendresult = (id, err) ->
+          unless err
+            res.json {challenge:
+              id: id
+              title: req.body.challenge.title
+              category: req.body.challenge.category
+              points: req.body.challenge.points
+              done: false
+            }
+            exports.broadcast {type: 'ctfmodification'}, req.params.ctf
+          else
+            res.json err
         db.addChallenge req.params.ctf, req.body.challenge.title,
             req.body.challenge.category, req.body.challenge.points, (id) ->
-              res.json {challenge:
-                id: id
-                title: req.body.challenge.title
-                category: req.body.challenge.category
-                points: req.body.challenge.points
-                done: false
-              }
-              exports.broadcast {type: 'ctfmodification'}, req.params.ctf
+              if 'text' in req.body.challenge
+                text = "#{req.body.challenge}"
+                data = getEtherpadAPI "setText", "challenge#{req.params.challenge}", {text: text}, (data, err) ->
+                  sendresult id, err
+              else
+                sendresult id, false

   app.get "#{prefix}/ctfs/:ctf/files", (req, res) ->
     try
@@ -288,3 +300,19 @@
             res.json err
       catch e
         res.send 400
+
+  app.post "#{prefix}/challenges/:challenge/text", (req, res) ->
+    try
+      req.params.challenge = parseInt req.params.challenge
+    catch e
+      res.send 400
+      return
+    validateApiKey req, res, (user) ->
+      try
+        data = getEtherpadAPI "setText", "challenge#{req.params.challenge}", {text: req.body}, (data, err) ->
+          if not err
+            res.json {success: true}
+          else
+            res.json err
+      catch e
+        res.send 400


Simplify a thing

Simplify a thing

--- app.coffee
+++ app.coffee
@@ -191,9 +191,7 @@
 	eyes_folder.add(emoji.eyes.right, 'offset_y', -1, 1).name('Right Eye Y')

-emojis = (make_random_emoji() for [1..12])
-emoji_displays = (make_emoji_display(emoji) for emoji in emojis)
-selected_emoji_display = emoji_displays[emoji_displays.length - 1]
+emoji_displays = (make_emoji_display(make_random_emoji()) for [1..12])

 animate ->
 	selected_emoji_display?.update()


app.loadCoreTemplates fix #89

app.loadCoreTemplates fix #89

--- app.coffee
+++ app.coffee
@@ -35,8 +35,16 @@
   unless fs.existsSync session_key_path
     fs.writeFileSync session_key_path, crypto.randomBytes(48).toString('hex')

+exports.loadCoreTemplates = ->
+  app.template_data = {}
+
+  for filename in fs.readdirSync "#{__dirname}/core/template"
+    template_name = path.basename filename, path.extname(filename)
+    app.template_data[template_name] = fs.readFileSync "#{__dirname}/core/template/#{filename}"
+
 exports.run = ->
   exports.checkEnvironment()
+  exports.loadCoreTemplates()

   {user, password, host, name} = config.mongodb

@@ -72,10 +80,6 @@
     app.middleware = require './core/middleware'
     app.notification = require './core/notification'
     app.authenticator = require './core/authenticator'
-
-    app.template_data =
-      ticket_create_email: fs.readFileSync('./core/template/ticket_create_email.html').toString()
-      ticket_reply_email: fs.readFileSync('./core/template/ticket_reply_email.html').toString()

     app.use connect.json()
     app.use connect.urlencoded()


Attach a log message for debugging.

Attach a log message for debugging.

--- app.coffee
+++ app.coffee
@@ -12,6 +12,7 @@

 # Set up the Job queue.
 jobs = if process.env.REDISTOGO_URL
+  console.log "Attempting to connect to " + process.env.REDISTOGO_URL
   kue.createQueue redis: process.env.REDISTOGO_URL
 else
   kue.createQueue()


save users to sip.conf

save users to sip.conf

--- app.coffee
+++ app.coffee
@@ -20,9 +20,9 @@
   res.render 'index'

 app.get '/config/save', (req, res)->
-  models.SipConfigs.find (err, configs)->
+  models.Users.find (err, configs)->
     mapper.map configs, (config)->
-      name: config.name
+      name: config.peername
       secret: config.secret
     , '/etc/asterisk/sip_2.conf', (err)->
       if err


kite: nodejs kite: revalidation of token should be a while before its expired

--- node_modules_koding/koding-kite/client.coffee
+++ node_modules_koding/koding-kite/client.coffee
@@ -110,7 +110,8 @@
       decryptedToken = JSON.parse decrypt(@authentication.key, @environment.kodingKey())
       validUntil = new Date(decryptedToken.validUntil)
       now = new Date
-      if validUntil < now
+      tokenTtl = (validUntil - new Date(now)) - 2000
+      if tokenTtl < 0
         # TODO: there must be a getKontrol function
         kontrol = new Client environment: @environment
         kontrol.on 'connected', ()=>
@@ -120,7 +121,8 @@
         kontrol.connect()
       else
         work()
-    work()
+    else
+      work()

   getToken: (options, callback)->
     @callRemote "getToken", options, (request)->


koding-worker: send an immediate message before starting the reporter

--- node_modules_koding/koding-worker/main.coffee
+++ node_modules_koding/koding-worker/main.coffee
@@ -66,14 +66,18 @@
         uptime : 0

+  publish: (command) ->
+    @fetchWorkerExchange (workerExchange) =>
+      @worker.message.command = command
+      @worker.timestamp = @isoDateString()
+      @worker.monitor.uptime += @interval
+      workerExchange.publish "input.worker", { @worker },
+        appId: "#{@worker.uuid}"
+
   startReporter: (command) ->
+    @publish command
     @reporter[command] = setInterval =>
-      @fetchWorkerExchange (workerExchange)=>
-        @worker.message.command = command
-        @worker.timestamp = @isoDateString()
-        @worker.monitor.uptime += @interval
-        workerExchange.publish "input.worker", { @worker },
-          appId: "#{@worker.uuid}"
+      @publish command
     , @interval * 1000

   stopReporter: (command) ->


UpdateCustomVariable: fix empty custom variables issue

--- client/app/lib/util/stacks/updatecustomvariable.coffee
+++ client/app/lib/util/stacks/updatecustomvariable.coffee
@@ -30,7 +30,7 @@

   { provider, title, meta, stackTemplate } = options

-  if not meta or (Object.keys meta).length is 1
+  if not meta or (Object.keys meta).length <= 1
     return callback null

   { JCredential } = remote.api


Add a couple more useless Google search parameters.

--- background_scripts/completion.coffee
+++ background_scripts/completion.coffee
@@ -149,7 +149,8 @@
     # Google search specific replacements; this replaces query parameters which are known to not be helpful.
     # There's some additional information here: http://www.teknoids.net/content/google-search-parameters-2012
     [ "^https?://www\.google\.(com|ca|com\.au|co\.uk|ie)/.*[&?]q="
-      "ei gws_rd url ved usg sa usg sig2 bih biw cd aqs ie".split(/\s+/).map (param) -> new RegExp "\&#{param}=[^&]+" ]
+      "ei gws_rd url ved usg sa usg sig2 bih biw cd aqs ie sourceid es_sm"
+        .split(/\s+/).map (param) -> new RegExp "\&#{param}=[^&]+" ]

     # General replacements; replaces leading and trailing fluff.
     [ '.', [ "^https?://", "\\W+$" ].map (re) -> new RegExp re ]


Fix silent noop assertion in getAppByOwner test

--- tests/integration/models/application.spec.coffee
+++ tests/integration/models/application.spec.coffee
@@ -100,7 +100,7 @@

 			it 'should not find the created application with a different username', ->
 				promise = resin.models.application.getAppByOwner('FooBar', 'test_username')
-				m.chai.expect(promise).to.eventually.reject
+				m.chai.expect(promise).to.be.rejected

 		describe 'resin.models.application.getAll()', ->


Removing minus zero idiocy

Removing minus zero idiocy

--- scripts/gifme.coffee
+++ scripts/gifme.coffee
@@ -5,10 +5,10 @@
       .get() (err, resp, body) ->
         data = JSON.parse(body)
         if data and data['hits'] and data['hits']['total'] and data['hits']['total'] > 0
-          randomNumAlt = Math.floor(Math.random() * (data['hits']['total'] - 0))
+          randomNumAlt = Math.floor(Math.random() * (data['hits']['total']))
           if data['hits']['hits'][randomNumAlt] and data['hits']['hits'][randomNumAlt]['_source'] and data['hits']['hits'][randomNumAlt]['_source']['url']
             res.send data['hits']['hits'][randomNumAlt]['_source']['url']
           else
             res.send "Cannot find"
         else
-          res.send "Cannot find"+          res.send "Cannot find"


Use destructuring assignment as ReactBoostrap doesn't expose modules directly anymore

--- containers/Form.jsx.coffee
+++ containers/Form.jsx.coffee
@@ -4,8 +4,7 @@
 ParserMixin = require './mixins/Parser'
 DisableOnSubmitMixin = require('../controls/mixins/DisableOnSubmit')

-ButtonToolbar = require('react-bootstrap/ButtonToolbar')
-Button = require('react-bootstrap/Button')
+{ButtonToolbar, Button} = require('react-bootstrap')

 Form = React.createClass(
   mixins: [
@@ -41,4 +40,4 @@
     )`
 )

-module.exports = Form+module.exports = Form


Some refactors in services.coffee

Some refactors in services.coffee

--- www/coffee/services.coffee
+++ www/coffee/services.coffee
@@ -1,22 +1,10 @@
 angular.module("proBebe.services", ["proBebe.constants"]).factory "Friends", ->
   # Some fake testing data
   friends = [
-    {
-      id: 0
-      name: "Scruff McGruff"
-    }
-    {
-      id: 1
-      name: "G.I. Joe"
-    }
-    {
-      id: 2
-      name: "Miss Frizzle"
-    }
-    {
-      id: 3
-      name: "Ash Ketchum"
-    }
+    { id: 0, name: "Scruff McGruff" }
+    { id: 1, name: "G.I. Joe" }
+    { id: 2, name: "Miss Frizzle" }
+    { id: 3, name: "Ash Ketchum" }
   ]
   all: ->
     friends
@@ -80,7 +68,6 @@
           push.unregister ->
             console.info('unregister success')

-
 # ALL GCM notifications come through here.
 onNotificationGCM = (e) ->
   console.log("EVENT RECEIVED: #{e.event} ")
@@ -102,23 +89,21 @@
       # you might want to play a sound to get the user's attention, throw up a dialog, etc.
       if (e.foreground)
         # we're using the app when a message is received.
-        console.log('--INLINE NOTIFICATION--' + '')
+        console.log('--INLINE NOTIFICATION--')
         # if the notification contains a soundname, play it.
-        # var my_media = new Media(&quot/android_asset/www/&quot+e.soundname)
+        # my_media = new Media("/android_asset/www/#{e.soundname}")
         # my_media.play()
         alert(e.payload.message)
       else
         window.open(e.payload.article_url, "_system")
         # otherwise we were launched because the user touched a notification in the notification tray.
         if (e.coldstart)
-          console.log('--COLDSTART NOTIFICATION--' + '')
+          console.log('--COLDSTART NOTIFICATION--')
         else
-          console.log('--BACKGROUND NOTIFICATION--' + '')
-        # direct user here:
-        window.location = "#/tab/featured"
+          console.log('--BACKGROUND NOTIFICATION--')
       console.log('MESSAGE -&gt MSG: ' + e.payload.message + '')
       console.log('MESSAGE: '+ JSON.stringify(e.payload))
     when 'error'
       console.log('ERROR -&gt MSG:' + e.msg + '')
     else
-      console.log('EVENT -&gt Unknown, an event was received and we do not know what it is')
+      console.log("Unknown event sent: #{e.event}")


Updating oneget resource snippet

Updating oneget resource snippet

--- snippets/oneget.cson
+++ snippets/oneget.cson
@@ -1,4 +1,4 @@
 '.source.inspec':
   'Describe oneget':
     'prefix': 'oneget'
-    'body': "# http://inspec.io/docs/reference/resources/oneget/\ndescribe oneget('${1:name}') do\n  it { should${2:_not} ${3:exist} }${4}\nend"
+    'body': "# http://inspec.io/docs/reference/resources/oneget/\ndescribe oneget('${1:package_name}') do\n  it { should${2:_not} be_installed }${3}\nend"


feat(common): toggle a class to <body> on page scrolling

--- brasilcomvc/common/static/coffee/common-header.coffee
+++ brasilcomvc/common/static/coffee/common-header.coffee
@@ -1,3 +1,4 @@
+win = $(window)
 body = $(document.body)

 _prevent_ghost_click = (e) ->
@@ -16,3 +17,13 @@
 		body.on 'touchend', _prevent_ghost_click

 	body.toggleClass 'header-focus', in_nav
+
+
+
+# Add a class to <body> when the page is scrolled past the header
+header_height = $('#main-header').height()
+win.on 'scroll', ->
+	if win.scrollTop() >= header_height
+		body.addClass 'past-header'
+	else
+		body.removeClass 'past-header'


show other answers correctly

show other answers correctly

--- app/assets/javascripts/components/question.cjsx
+++ app/assets/javascripts/components/question.cjsx
@@ -126,9 +126,9 @@
                                     data={@props.data} answer={@props.answer} mode={@props.mode}/>

   renderAppropriateAnswer: =>
-    return null if (@props.mode != "show" and (!@props.answered or !@state.hidden)) or !@props.answer?.selected?
+    return
N/A
 unless @props.answer?.selected
+
     general = @renderAnswerLabel(@props.answer)
-
     switch @props.data.type
       when "single"           then general
       when "select"           then general
@@ -161,13 +161,17 @@

DNA Confirmation: {"✓" if @props.answer?.dna_confirmation}

-  renderAnswerLabel: (answer) ->
-    return
N/A
 unless answer?.selected
+  renderAnswerLabel: (answer) =>
+    isString = answer.selected.constructor == String
+    matches = if isString then answer.selected.match(/(.*) \((.*)\)/) else false
+    isAnOtherAnswer = _.contains(@props.data.other_answers, answer.selected)

-    if (answer.selected.constructor == String) and (matches = answer.selected.match(/(.*) \((.*)\)/))
+    if not isAnOtherAnswer and isString and matches

{matches[1]} ({matches[2]})

     else if answer.selected == "other"

Other: {answer.other_answer}

+    else if isAnOtherAnswer
+
{answer.selected}: {answer.other_answer}

     else

{answer.selected}


Don't expand `!$`

Don't expand `!$`

--- app/assets/javascripts/console-ish-input.coffee
+++ app/assets/javascripts/console-ish-input.coffee
@@ -54,6 +54,7 @@
     history = TS.chat_history.initializeChannelHistory(TS.model.active_cid)
     last_message = history.entries[0]
     last_word = last_message.slice(last_message.trim().lastIndexOf(" ") + 1)
-    replaced = current.replace(/!\$/g, last_word)
+    # Prevents expanding `!$`. Should prevent expanding in backticks entirely, maybe?
+    replaced = current.replace(/(^|[^`])!\$/g, "$1" + last_word)
     input.val(replaced)
   old_submit(event)


Set buttons to edit existing drawing answer

[#58968010] Checking the thumbnail as well as the answer source to show us if there's an existing answer, and thus set the buttons properly. (Also, hide the "draw" button if we're showing "edit".)

https://www.pivotaltracker.com/story/show/58968010

--- app/assets/javascripts/image_question.js.coffee
+++ app/assets/javascripts/image_question.js.coffee
@@ -116,8 +116,9 @@
     @$snapshot_button.show()
     @$edit_button.hide()

-    if @current_src
+    if @current_src or @current_thumbnail
       @$edit_button.show()
+      @$drawing_button.hide()
       @$snapshot_button.hide()

     if @undo_button


Correct private method names

Correct private method names

--- app/assets/javascripts/modules/router.js.coffee
+++ app/assets/javascripts/modules/router.js.coffee
@@ -14,7 +14,7 @@
   constructor: ($routeProvider) ->
     this.provider = $routeProvider

-  bindRoutes: () ->
+  _bindRoutes: () ->
     _.each(this.directRoutes, this._setRouteConfig)
     _.each(this.customRoutes, this._setRouteConfig)

@@ -24,12 +24,12 @@
       config = {}

     config = _.extend({}, this.defaultConfig, {
-      templateUrl: this.buildTemplateFor(route)
+      templateUrl: this._buildTemplateFor(route)
     }, config)

     this.provider.when(route, config)

-  buildTemplateFor: (route)->
+  _buildTemplateFor: (route)->
     (params) ->
       if params != undefined
         window.d = params
@@ -39,7 +39,7 @@
       route + '?ajax=true'

 RouterBuilder = ($routeProvider) ->
-    new Router($routeProvider).bindRoutes()
+    new Router($routeProvider)._bindRoutes()

 app = angular.module('teddy')


Make sure that all necessary parameters are accurate before performing search action

--- app/assets/javascripts/widgets/direction.coffee
+++ app/assets/javascripts/widgets/direction.coffee
@@ -267,11 +267,17 @@
       # search_params.path = route.path.map((e) -> "#{e.latitude},#{e.longitude}").join(";")
       # $('#search_path').val(search_params.path)

-  performSearch search_params, (result) ->
-    renderResults(result.clusters, result.relics)
+      if bounds = gmap.getLatLngBounds()
+        search_params.bounding_box = bounds.toString()
+
+      performSearch search_params, (result) ->
+        renderResults(result.clusters, result.relics)
+  else
+    performSearch search_params, (result) ->
+      renderResults(result.clusters, [])

   false
-, 3000
+, 500

 haversineDistance = (lat1, lon1, lat2, lon2) ->


added duration factor object

added duration factor object

--- app/coffee/drm-timestamps/drm-timestamps.coffee
+++ app/coffee/drm-timestamps/drm-timestamps.coffee
@@ -300,7 +300,7 @@
         if date? and time?
             return new Date date.year, date.month, date.date, time.hour, time.minute, time.second
         else if date? and !time
-            return new Date date.year, date.month, date.date, @today.hour, @today.minute, @today.second
+            return new Date date.year, date.month, date.date, 0, 0, 0
         else if !date and time?
             return new Date @today.year, @today.month, @today.date, time.hour, time.minute, time.second

@@ -408,30 +408,40 @@
         _getDaysToYears = (days) ->
             return (days * 400) / 146097

+        factors =
+            ms: 1
+            seconds: 1e3
+            minutes: 6e4
+            hours: 36e5
+            days: 864e5
+            weeks: 7
+            months: 2592e6
+            # years:
+
         now = new Date()
         # now = new Date 2014, 5, 26, 23, 41, 0
         # console.log @prettifyDate(now)
         # console.log @prettifyDate(date)
         ms = now.getTime() - date.getTime()
-        seconds = if (ms/1e3) >= 0 then Math.floor(ms/1e3) else Math.ceil(ms/1e3)
-
-        minutes = if (ms/6e4) >= 0 then Math.floor(ms/6e4) else Math.ceil(ms/6e4)
-        remainingSeconds = if ((ms % 6e4)/1e3) >= 0 then Math.floor(((ms % 6e4)/1e3)) else Math.ceil(((ms % 6e4)/1e3))
-
-        hours = if (ms/36e5) >= 0 then Math.floor(ms/36e5) else Math.ceil(ms/36e5)
-        remainingMinutes = if ((ms % 36e5)/6e4) >= 0 then Math.floor(((ms % 36e5)/6e4)) else Math.ceil(((ms % 36e5)/6e4))
-
-        days = if (ms/864e5) >= 0 then Math.floor(ms/864e5) else Math.ceil(ms/864e5)
-        remainingHours = if ((ms % 864e5)/36e5) >= 0 then Math.floor(((ms % 864e5)/36e5)) else Math.ceil(((ms % 864e5)/36e5))
-
-        weeks = if (days/7) >= 0 then Math.floor(days/7) else Math.ceil(days/7)
-        remainingDays = if (days % 7) >= 0 then Math.floor(days % 7) else Math.ceil(days % 7)
+        seconds = if (ms/factors.seconds) >= 0 then Math.floor(ms/factors.seconds) else Math.ceil(ms/factors.seconds)
+
+        minutes = if (ms/factors.minutes) >= 0 then Math.floor(ms/factors.minutes) else Math.ceil(ms/factors.minutes)
+        remainingSeconds = if ((ms % factors.minutes)/factors.seconds) >= 0 then Math.floor(((ms % factors.minutes)/factors.seconds)) else Math.ceil(((ms % factors.minutes)/factors.seconds))
+
+        hours = if (ms/factors.hours) >= 0 then Math.floor(ms/factors.hours) else Math.ceil(ms/factors.hours)
+        remainingMinutes = if ((ms % factors.hours)/factors.minutes) >= 0 then Math.floor(((ms % factors.hours)/factors.minutes)) else Math.ceil(((ms % factors.hours)/factors.minutes))
+
+        days = if (ms/factors.days) >= 0 then Math.floor(ms/factors.days) else Math.ceil(ms/factors.days)
+        remainingHours = if ((ms % factors.days)/factors.hours) >= 0 then Math.floor(((ms % factors.days)/factors.hours)) else Math.ceil(((ms % factors.days)/factors.hours))
+
+        weeks = if (days/factors.weeks) >= 0 then Math.floor(days/factors.weeks) else Math.ceil(days/factors.weeks)
+        remainingDays = if (days % factors.weeks) >= 0 then Math.floor(days % factors.weeks) else Math.ceil(days % factors.weeks)

         years = if (_getDaysToYears(days)) >= 0 then Math.floor(_getDaysToYears(days)) else Math.ceil(_getDaysToYears(days))
         remainingDaysInYear = if (days - _getYearsToDays(years)) >= 0 then Math.floor(days - _getYearsToDays(years)) else Math.ceil(days - _getYearsToDays(years))

         # 52 weeks / 12 months about 4.33333
-        months = (ms/2592e6)
+        months = (ms/factors.months)

         if Math.abs(months) >= 1
             # round months up to account for number of weeks estimated weirdness


残り80円になりました

残り80円になりました

--- scripts/ito_test.coffee
+++ scripts/ito_test.coffee
@@ -1,7 +1,7 @@
 # Description:
 #   AAを表示するbot
 # Author:
-#   raoka0000
+#   itonorimitsu

 module.exports = (robot) ->
   robot.respond /(ito)/i, (msg) ->


shall know no fear

shall know no fear

--- scripts/offshore.coffee
+++ scripts/offshore.coffee
@@ -45,3 +45,6 @@

   robot.respond /who will unleash righteous vengeance upon evil/i, (msg)->
     msg.send "The heroes of offshore team! :sachin: :prem: :kostas: :chris: :drmatt:"
+
+  robot.respond /when all hope is lost and darkness is upon us/i, (msg)->
+    msg.send "The offhore team shall know no fear! :sachin: :prem: :kostas: :chris: :drmatt:"


adding list of offshore team pics

adding list of offshore team pics

--- scripts/offshore.coffee
+++ scripts/offshore.coffee
@@ -13,9 +13,15 @@
 # Author:
 #   Mateusz Kopij <sagasu>

+
+offshorepics = [
+  "https://github.com/sagasu/offshore-team/blob/master/img/OffshoreTeam2.jpg?raw=true",
+  "https://github.com/sagasu/offshore-team/blob/master/img/vicTeam.png?raw=true"
+]
+
 module.exports = (robot) ->

   robot.respond /offshore team/i, (msg)->
-    msg.send "https://github.com/sagasu/offshore-team/blob/master/img/OffshoreTeam2.jpg?raw=true"
+    msg.send msg.random offshorepics


Update scripts

Update scripts

--- scripts/saychuso.coffee
+++ scripts/saychuso.coffee
@@ -14,7 +14,7 @@
 #   chuzo - Display motivation image - Chris
 #
 # Author:
-#   maddox
+#   chris

 chusoimages = [
   "https://i.imgur.com/Mlpfb.gif",
@@ -61,6 +61,29 @@
   "https://i.imgur.com/p17Zs.gif",
   "https://i.imgur.com/p17Zs.gif",
   "https://i.imgur.com/p17Zs.gif",
+  "https://i.imgur.com/O8tFZ.gif",
+  "https://i.imgur.com/B0wld.gif",
+  "https://i.imgur.com/eZNacLq.gif",
+  "https://i.imgur.com/iEN2gIh.gif",
+  "https://i.imgur.com/NIh934D.gif",
+  "https://i.imgur.com/NoElYdb.gif",
+  "https://i.imgur.com/qAKn4dn.gif",
+  "https://i.imgur.com/SQANizl.gif",
+  "https://i.imgur.com/wRRkPq4.gif",
+  "https://i.imgur.com/YtKWRKk.gif",
+  "https://i.imgur.com/wSetJvt.gifv",
+  "https://i.imgur.com/TI793.gif",
+  "https://i.imgur.com/oeXlZ.gif",
+  "https://i.imgur.com/d7TBk.gif",
+  "https://i.imgur.com/gu8UW.gif",
+  "https://i.imgur.com/MTXzH.gif",
+  "https://i.imgur.com/nPPIr.gif",
+  "https://i.imgur.com/0rg9W.gif",
+  "https://i.imgur.com/7JN8L.gif",
+  "https://i.imgur.com/K0dpR.gif",
+  "https://i.imgur.com/8mhxt.gif",
+  "https://i.imgur.com/VGNX2.gif",
+  "https://i.imgur.com/LTfLK.gif",
 ]

 module.exports = (robot) ->
@@ -74,3 +97,15 @@
   robot.hear /linda/i, (msg) ->
     msg.send "Linda linda! " + msg.random chusoimages

+  robot.hear /exelente/i, (msg) ->
+    msg.send "Exelente!! " + msg.random chusoimages
+
+  robot.hear /excelente/i, (msg) ->
+    msg.send "Exelente!! " + msg.random chusoimages
+
+  robot.hear /rajado/i, (msg) ->
+    msg.send "Rajado rajado!! " + msg.random chusoimages
+
+  robot.hear /pichudo/i, (msg) ->
+    msg.send "Pichudo pichudo!! " + msg.random chusoimages
+


resurrection: add/remove popup view in window controller layer mechanism

--- client/Main/avatararea/avatarareaiconlink.coffee
+++ client/Main/avatararea/avatarareaiconlink.coffee
@@ -26,11 +26,15 @@
       @$('.count').addClass "in"

   click:(event)->
+    windowController = KD.singleton "windowController"
     KD.utils.stopDOMEvent event

     delegate = @getDelegate()
     if delegate.hasClass "active"
       @delegate.hide()
+      windowController.removeLayer @delegate
     else
       @delegate.show()
-      @once 'ReceivedClickElsewhere', => @delegate.hide()
+      windowController.addLayer @delegate
+      @delegate.once "ReceivedClickElsewhere", =>
+        @delegate.hide()


Pass pre-fetched object to router

Pass pre-fetched object to router

--- client/app/MainApp/notificationcontroller.coffee
+++ client/app/MainApp/notificationcontroller.coffee
@@ -165,7 +165,7 @@
               KD.getSingleton('router').handleRoute "/Inbox"
             else if subjectObj.constructor.name is "JOpinion"
               KD.remote.api.JOpinion.fetchRelated subjectObj._id, (err, post) ->
-                KD.getSingleton('router').handleRoute "/Activity/#{post.slug}"
+                KD.getSingleton('router').handleRoute "/Activity/#{post.slug}", state:post
                 view.destroy()
             else if subject.constructorName is 'JGroup'
               suffix = ''
@@ -173,7 +173,7 @@
               KD.getSingleton('router').handleRoute "/#{subjectObj.slug}#{suffix}"
               view.destroy()
             else
-              KD.getSingleton('router').handleRoute "/Activity/#{subjectObj.slug}"
+              KD.getSingleton('router').handleRoute "/Activity/#{subjectObj.slug}", state:subjectObj
               view.destroy()

         options.type  = actionType or actorType or ''


Add e2e spec for TestCase.time()

Add e2e spec for TestCase.time()

--- spec/e2e_spec.coffee
+++ spec/e2e_spec.coffee
@@ -118,6 +118,15 @@
       '  </testsuite>')

+  it 'should add the reported time to the test case', ->
+    builder.testSuite().testCase().time(2.5)
+
+    expect(builder.build()).toBe reportWith(
+      '  <testsuite tests="1" failures="0" errors="0" skipped="0">\n' +
+      '    <testcase time="2.5"/>\n' +
+      '  </testsuite>')
+
+
   it 'should print the reported standard output log to system-out', ->
     builder.testSuite().testCase().standardOutput("This was written to stdout!")


check for an existing teamleader; fixes #156

--- client/views/common/shiftModal/shiftModal.coffee
+++ client/views/common/shiftModal/shiftModal.coffee
@@ -277,9 +277,13 @@
 			chosenId = ''
 			chosenIsTeamleader = false
 			chosenIsSubstituteTeamleader = false
+			alreadyHasTeamleader = false
 			shift = Shifts.findOne shiftId, fields: teams: 1

 			for team in shift.teams when team._id == teamId
+				for user in team.participants when user.thisTeamleader
+					alreadyHasTeamleader = true
+
 				for user in team.pending when user.checked
 					Meteor.call 'approveRequest', shiftId, teamId, user._id, handleError

@@ -296,7 +300,8 @@
 						chosenIsTeamleader = true
 				break

-			if chosenId then Meteor.call 'setLeader', shiftId, teamId, chosenId, handleError
+			if chosenId && !alreadyHasTeamleader
+				Meteor.call 'setLeader', shiftId, teamId, chosenId, handleError

 		selectedCount++ for checkbox in $team.find('input[type=checkbox]') when checkbox.checked
 		selectedCount++ for participant in $team.find('.participant')


Changed collision spec description

Changed collision spec description

--- spec/gamespec.coffee
+++ spec/gamespec.coffee
@@ -38,7 +38,7 @@
                                              [new Position([Game.WIDTH/2, Game.HEIGHT - Game.VELOCITY*.1], Game.NORTH, .1)],
                                              [new Position([0 + Game.VELOCITY*.1, Game.HEIGHT/2], Game.EAST, .1)]])

-    it "notifies the listeners about a collision", ->
+    it "notifies the listeners about a head-collision for each player", ->
       listener = jasmine.createSpyObj('listener', ['notify'])
       SynchronizedTime.setTimeForTesting(0)
       game.addListener(listener)


Just quick fixes.

Just quick fixes.

--- client/Framework/components/modals/KDModalView.coffee
+++ client/Framework/components/modals/KDModalView.coffee
@@ -1,7 +1,6 @@
 class KDModalView extends KDView

   constructor:(options = {}, data)->
-
     options.overlay       ?= no            # a Boolean
     options.overlayClick  ?= yes           # a Boolean
     options.height       or= "auto"        # a Number for pixel value or a String e.g. "100px" or "20%" or "auto"
@@ -27,7 +26,7 @@
     @setClass "fx"                                if options.fx
     @setTitle options.title                       if options.title
     @setContent options.content                   if options.content
-    @addSubView options.view, ".kdmodal-content"  if options.view
+    @addSubView options.view                      if options.view

     @on 'ModalCancelled', options.cancel          if options.cancel

@@ -60,7 +59,6 @@
     @listenWindowResize()

   setDomElement:(cssClass)->
-
     {helpContent, helpTitle} = @getOptions()

     if helpContent
@@ -82,12 +80,10 @@
     """

   addSubView:(view, selector = ".kdmodal-content")->
-
     selector = null if @$(selector).length is 0
     super view, selector

   setButtons:(buttonDataSet)->
-
     @buttons or= {}
     @setClass "with-buttons"
     defaultFocusTitle = null
@@ -180,7 +176,6 @@
     @getDomElement().find(".kdmodal-content").html content

   display:->
-
     if @getOptions().fx
       @utils.defer =>
         @setClass "active"


activity: add name, group name and type constant properties to public feed link view data

--- client/Social/Activity/sidebar/activitysidebar.coffee
+++ client/Social/Activity/sidebar/activitysidebar.coffee
@@ -146,14 +146,17 @@
   addPublicFeedLink: ->

     {activityController} = KD.singletons
+    {slug, socialApiChannelId} = KD.getGroup()

     @wrapper.addSubView @public = new CustomLinkView
       title    : 'PUBLIC FEED'
       href     : '/Activity/Public'
       cssClass : 'kdlistitemview-sidebar-item public-feed-link'
     ,
-      name     : 'koding_public_feed'
-      id       : KD.getGroup().socialApiChannelId ? '1'
+      name         : slug
+      typeConstant : 'group'
+      groupName    : slug
+      id           : socialApiChannelId ? '1'

     @public.addSubView new KDCustomHTMLView
       cssClass : 'count hidden'


TeamMembersView: Temporarily don't override listMembers.

For now all tabs will show the same user list since we don't have proper backend for them.

--- client/admin/lib/views/members/teammembersview.coffee
+++ client/admin/lib/views/members/teammembersview.coffee
@@ -9,6 +9,3 @@

     super options, data

-
-  listMembers: ->
-


missing commit although in commit history, I assume it is wrong merging: added register link to sidebar and re-added loggedIn functionality

--- client/landing/app/MainApp/sidebar/sidebarview.coffee
+++ client/landing/app/MainApp/sidebar/sidebarview.coffee
@@ -38,11 +38,11 @@
       items     : []

     navAdditions = [
-      { type  : 'separator',      order : 65}
-      { title : 'Invite Friends', order : 66,  type : 'account',    role : 'member' }
-      { title : 'Logout',         order : 100, path : '/Logout',    type : 'account', role : 'member' }
-      { title : 'Login',          order : 101, path : '/Login',     type : 'account', role : 'guest' }
-      { title : 'Register',       order : 102, path : '/Register',  type : 'account', role : 'guest' }
+      { type  : 'separator',      order : 65 }
+      { title : 'Invite Friends', order : 66,  type : 'account',   role : 'member' }
+      { title : 'Logout',         order : 100, path : '/Logout',   type : 'account', loggedIn : yes }
+      { title : 'Login',          order : 101, path : '/Login',    type : 'account', loggedIn : no  }
+      { title : 'Register',       order : 102, path : '/Register', type : 'account', loggedIn : no  }
     ]

     KD.registerNavItem navItem for navItem in navAdditions


sort collection by labelField

sort collection by labelField

--- client/lanes/components/select-field/SelectField.cjsx
+++ client/lanes/components/select-field/SelectField.cjsx
@@ -20,9 +20,11 @@
     dataObjects:
         model: 'props'
         collection: ->
-            @props.collection || (
+            collection = @props.collection || (
                 @props.model?.associations?.collectionFor(@props.name, @props.model)
             )
+            collection.comparator = @props.labelField
+            collection

     onChange: (selections) ->
         selections = [selections] unless _.isArray(selections)


disable comment field on enter

disable comment field on enter

--- components/new_comment/client/new_comment_view.coffee
+++ components/new_comment/client/new_comment_view.coffee
@@ -36,8 +36,11 @@
         created_at: new Date()
       , block_id: @block_id

+      @$input.attr "disabled", "disabled"
+
       comment.save {},
         success: =>
+          @$input.removeAttr 'disabled'
           @$input.val ""
           console.log 'triggering new comment'
           mediator.trigger 'new:comment', comment


another bug

another bug

--- src/Landscape.coffee
+++ src/Landscape.coffee
@@ -262,26 +262,27 @@
         cancel: "Play Again"
     alertify.set(options)
     alertify.confirm message, (event, input) =>
-      names = ("@#{player.avatar.name}" for id, player of @players)
-      names = _.uniq(names)
-      chasers = switch names.length
-        when 0
-          "nobody at all"
-        when 1
-          names[0]
-        when 2
-          "#{names[0]} and #{names[1]}"
-        else
-          "#{names[0]}, #{names[1]} and others"
-      message = switch @score
-        when 0
-          "I ate a bomb after collecting nothing while being chased by #{chasers}!"
-        when 1
-          "I ate a bomb after collecting one measly coin while being chased by #{chasers}!"
-        else
-          "I ate a bomb after collecting #{@score} coins while being chased by #{chasers}!"
-      window.laggydash.tweet(message, names)
       @restart = true
+      if event
+        names = ("@#{player.avatar.name}" for id, player of @players)
+        names = _.uniq(names)
+        chasers = switch names.length
+          when 0
+            "nobody at all"
+          when 1
+            names[0]
+          when 2
+            "#{names[0]} and #{names[1]}"
+          else
+            "#{names[0]}, #{names[1]} and others"
+        message = switch @score
+          when 0
+            "I ate a bomb after collecting nothing while being chased by #{chasers}!"
+          when 1
+            "I ate a bomb after collecting one measly coin while being chased by #{chasers}!"
+          else
+            "I ate a bomb after collecting #{@score} coins while being chased by #{chasers}!"
+        window.laggydash.tweet(message, names)

   bang:(player, x, y)=>
     player.runner.body.velocity.y = -150


Implemente D3Bar::getTransitionElement

Implemente D3Bar::getTransitionElement

--- src/chart-d3-bar.coffee
+++ src/chart-d3-bar.coffee
@@ -51,6 +51,12 @@
     margin = top: 13, right: 23, bottom: 24, left: 55
     super(selectors, data, options, margin)

+  getTransitionElement: (duration, options) =>
+    @getRootElement()
+      .transition()
+      .duration(duration)
+      .ease(options.animationEasing)
+
   # TODO: enable test
   render: =>
     labels = @data.labels
@@ -81,10 +87,7 @@
     @updateScaleStrokeStyle(options)
     @updateScaleTextStyle(options)

-    el = @getRootElement()
-      .transition()
-      .duration(@duration())
-      .ease(options.animationEasing)
+    el = @getTransitionElement(@duration(), options)
     @transitBar(el, chartHeight, yScale)
     @transitBarBorder(el, chartHeight)
     this