Update schema

Update schema

--- app/schemas/models/level.coffee
+++ app/schemas/models/level.coffee
@@ -102,7 +102,7 @@
   move: c.object {title: 'Move', description: 'Tell the Thang to move.', required: ['target'], default: {target: {}, duration: 500}},
     target: _.extend _.cloneDeep(PointSchema), {title: 'Target', description: 'Target point to which the Thang will move.', default: {x: 20, y: 20}}
     duration: {title: 'Duration', description: 'Number of milliseconds over which to move, or 0 for an instant move.', type: 'integer', minimum: 0, format: 'milliseconds'}
-  tutorial: c.object {title: 'Tutorial', description: 'Move Vega around on the screen as a step by step tutorial to explain things.', default: {targetElement: 'editor', animate: 'Outline'}},
+  tutorial: c.object {title: 'Tutorial', description: 'Move Vega around on the screen as a step by step tutorial to explain things.', default: {targetElement: 'editor', animation: 'Outline'}},
     targetElement: c.shortString(
       title: 'Target Element'
       description: 'What part of the screen to point at, like the editor, code bank or play button'
@@ -114,16 +114,17 @@
         'Goal List'
         'Code Bank button'
         'Code Editor Window'
+        'Intro / Center'
       ]
       default: 'Code Editor Window'
     )
-    animate: c.shortString(
-      title: 'Animate Element'
-      description: 'How to animate the target'
+    animation: c.shortString(
+      title: 'Element Animation'
+      description: 'How to animation the target'
       enum: [
         'Shake'
-        'Outline'
-        'Balloon'
+        'Highlight'
+        'Zoom'
       ]
     )


Update level_about.coffee

Update level_about.coffee

--- server/materials/data/level_about.coffee
+++ server/materials/data/level_about.coffee
@@ -151,7 +151,7 @@

Для школьников из Нижегородской области

Для нижегородских школьников занятия бесплатны, и в обозримом будущем останутся бесплатными (т.к. мне проведение занятий оплачивает лицей 40 и ВШЭ).

-
Формально вы считаетесь нижегородским школьником, если можете участвовать во Всероссийской олимпиаде школьников по информатике и имеете теоретическую возможность, пройдя школьный и муниципальный этапы, участвовать в региональном этапе ВсОШ в Нижегородской области (т.е. в Нижегородской областной олимпиаде школьников по информатике).

+
Формально вы считаетесь нижегородским школьником, если можете участвовать во Всероссийской олимпиаде школьников по информатике и имеете теоретическую возможность, пройдя школьный и муниципальный этапы, участвовать в региональном этапе ВсОШ в Нижегородской области (т.е. в Нижегородской областной олимпиаде школьников по информатике). Как правило, это обозначает, что вы должны учиться в школе, находящейся на территории Нижегородской области, хотя бывают исключения.

Если у меня возникнут подозрения, являетесь ли вы нижегородским школьников или нет, я могу попросить вас как-то это подтвердить.


Added new test for insertion binding

Added new test for insertion binding

--- tests/batman/view/insertion_binding_test.coffee
+++ tests/batman/view/insertion_binding_test.coffee
@@ -1,11 +1,6 @@
 helpers = window.viewHelpers

-QUnit.module 'Batman.View insertion bindings',
-  setup: ->
-    class @TestView extends Batman.View
-      constructor: ->
-        @constructor.instance = @
-        super
+QUnit.module 'Batman.View insertion bindings'

 asyncTest 'it should allow elements to be removed when the keypath evaluates to true', 3, ->
   source = '

'
@@ -46,6 +41,18 @@
     equal $('.test', node).html(), 'bar'
     QUnit.start()

+asyncTest 'child bindings should be rendered if the keypath starts as false', 1, ->
+  spy = createSpy()
+  class @TestView extends Batman.View
+    @accessor 'test', spy
+
+  source = '

'
+  context = foo: false, bar: 'bar', viewClass: @TestView
+
+  helpers.render source, false, context, (node) ->
+    equal spy.callCount, 1
+    QUnit.start()
+
 asyncTest 'it should allow keypaths to transition from falsy values to other falsy values', 3, ->
   source = '

'


added commas

added commas

--- atom/toolbar.cson
+++ atom/toolbar.cson
@@ -8,49 +8,49 @@
     tooltip: "Project"
     callback: "tree-view:toggle"
     icon: "file"
-  }
+  },
   {
     type: "button"
     icon: "search"
     callback: "project-find:show"
     tooltip: "Find in Project"
-  }
+  },
   {
     type: "button"
     icon: "git-branch"
     callback: "github:toggle-git-tab"
     tooltip: "Git Control"
-  }
+  },
   {
     type: "button"
     icon: "list-unordered"
     callback: "document-outline:toggle"
     tooltip: "Outline"
-  }
+  },
   {
     type: "spacer"
-  }
+  },
   {
     type: "button"
     icon: "book"
     callback: "autoflow:reflow-selection"
     tooltip: "Reflow"
-  }
+  },
   {
     type: "button"
     icon: "quote"
     callback: "smart-quotes-plus:smartreplace"
     tooltip: "AutoFormat"
-  }
+  },
   {
     type: "spacer"
-  }
+  },
   {
     type: "button"
     icon: "settings"
     callback: "settings-view:open"
     tooltip: "Preferences"
-  }
+  },
   {
     type: "button"
     icon: "gear"


Fix rocketchat:version reading wrong .info file

--- packages/rocketchat-version/plugin/compile-version.coffee
+++ packages/rocketchat-version/plugin/compile-version.coffee
@@ -13,6 +13,8 @@
 		future = new Future

 		processFile = (file, cb) ->
+			return cb() if not file.getDisplayPath().match /rocketchat\.info$/
+
 			output = JSON.parse file.getContentsAsString()
 			output.build =
 				date: new Date().toISOString()
@@ -49,7 +51,7 @@
 							output.commit?.branch = result.replace('\n', '')

 						output = """
-							RocketChat.Info = #{JSON.stringify(output, null, 4)}
+							RocketChat.Info = #{JSON.stringify(output, null, 4)};
 						"""

 						file.addJavaScript({ data: output, path: file.getPathInPackage() + '.js' })


removed global verbose option

removed global verbose option

--- lib/atom-python-test.coffee
+++ lib/atom-python-test.coffee
@@ -18,10 +18,6 @@
       type: 'string'
       default: ''
       title: 'Additional arguments for pytest command line'
-    runVerbose:
-      type: 'boolean'
-      default: true
-      title: 'Run with verbose option'
     outputColored:
       type: 'boolean'
       default: false
@@ -81,8 +77,7 @@
     if executeDocTests
       args.push '--doctest-modules'

-    runVerbose = atom.config.get('atom-python-test.runVerbose')
-    if runVerbose or verbose
+    if verbose
       args.push '--verbose'

     additionalArgs = atom.config.get('atom-python-test.additionalArgs')


Different css basics

Different css basics

--- framer/Config.coffee
+++ framer/Config.coffee
@@ -18,10 +18,11 @@
 		#"overflow": "visible"
 		#"z-index": 0
 		"-webkit-box-sizing": "border-box"
-		#"-webkit-transform-style": "flat"
-		#"-webkit-backface-visibility": "hidden"
+		"-webkit-transform-style": "flat"
+		"-webkit-backface-visibility": "hidden"
 		#"-webkit-backface-visibility": ""
 		#"-webkit-perspective": 500
 		# "pointer-events": "none"
 		"background-repeat": "no-repeat"
-		"background-size": "cover"+		"background-size": "cover"
+		"-webkit-overflow-scrolling": "touch"


payment: add expired state option to button partials

--- client/Main/payment/form.coffee
+++ client/Main/payment/form.coffee
@@ -90,10 +90,13 @@
       cssClass : 'success-msg hidden'
       partial  : ''

-    buttonPartial = switch operation
-      when  1 then 'UPGRADE YOUR PLAN'
-      when  0 then 'MAKE CHANGE'
-      when -1 then 'DOWNGRADE'
+    if @state.subscriptionState is 'expired'
+      buttonPartial = 'CONTINUE'
+    else
+      buttonPartial = switch operation
+        when  1 then 'UPGRADE YOUR PLAN'
+        when  0 then 'MAKE CHANGE'
+        when -1 then 'DOWNGRADE'

     @submitButton = new KDButtonView
       style     : 'solid medium green'


Collaboration: Apply host snapshot if participant is watching the host.

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -856,6 +856,7 @@
       nickname           = KD.nick()
       myWatchMapName     = "#{nickname}WatchMap"
       mySnapshotName     = "#{nickname}Snapshot"
+      hostName           = @collaborationHost

       @participants      = @rtm.getFromModel 'participants'
       @changes           = @rtm.getFromModel 'changes'
@@ -902,6 +903,12 @@
           accounts.forEach (account) =>
             {nickname} = account.profile
             @myWatchMap.set nickname, nickname
+
+      if not @amIHost and @myWatchMap.values().indexOf(hostName) > -1
+        hostSnapshot = @rtm.getFromModel "#{hostName}Snapshot"
+
+        for key, change of hostSnapshot.values()
+          @createPaneFromChange change

       KD.utils.repeat 60 * 55 * 1000, => @rtm.reauth()


IDE: AppController: doResize: only trigger resize for active instance

This is causing to resize hidden terminals in different workspaces especially
affecting the participants in a collaboration session.

[completes #87833330]

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -876,7 +876,9 @@

           terminal.windowDidResize()  if terminal?

-          unless @isInSession
+          {isActive} = @getActiveInstance()
+
+          if not @isInSession and isActive
             KD.utils.wait 400, -> # defer was not enough.
               webtermView.triggerFitToWindow()

@@ -1145,3 +1147,11 @@
   deleteWorkspaceRootFolder: (machineUId, rootPath) ->

     @finderPane.emit 'DeleteWorkspaceFiles', machineUId, rootPath
+
+
+  getActiveInstance: ->
+
+    {appControllers} = KD.singletons.appManager
+    instance = appControllers.IDE.instances[appControllers.IDE.lastActiveIndex]
+
+    return {instance, isActive: instance is this}


Collaboration: Renamed method name.

Collaboration: Renamed method name.

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -869,7 +869,7 @@
       @broadcastMessages or= @rtm.create 'list',   'broadcastMessages', []
       @pingTime          or= @rtm.create 'string', 'pingTime'
       @myWatchMap        or= @rtm.create 'map',    myWatchMapName, {}
-      @mySnapshot        or= @rtm.create 'map',    mySnapshotName, @createWorkspaceSnapshot()
+      @mySnapshot        or= @rtm.create 'map',    mySnapshotName, @getWorkspaceSnapshot()

       if @amIHost
         @getView().setClass 'host'
@@ -929,7 +929,7 @@
     @participants.push { nickname, hash }

-  createWorkspaceSnapshot: ->
+  getWorkspaceSnapshot: ->

     panes = {}


AppController: dry

AppController: dry

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -555,44 +555,27 @@

     @activeTabView.emit 'DrawingPaneRequested', paneHash

-  moveTab: (targetPanel) ->
-    ancestors = iv: null, svp: null, sv: null
-    Object.keys(ancestors).reduce ((a, b) ->
-      ancestors[b] = a.parent
-      return ancestors[b]),
-      @activeTabView
-
-    return  unless ancestors.svp instanceof KDSplitViewPanel
-
-    pane_ = @activeTabView.panes[@activeTabView.getActivePaneIndex()]
-    {pane} = @activeTabView.removePane pane_, yes, yes
+  moveTab: (direction) ->
+    panel = @activeTabView.parent.parent
+    return  unless panel instanceof KDSplitViewPanel
+
+    targetOffset = @layout[direction](panel._layout.data.offset)
+
+    targetPanel = @layoutMap[targetOffset]
+
+    {pane} = @activeTabView.removePane \
+      @activeTabView.panes[@activeTabView.getActivePaneIndex()], yes, yes

     targetPanel.subViews[0].tabView.addPane pane
     @setActiveTabView targetPanel.subViews[0].tabView

-  moveTabUp: ->
-    panel = @activeTabView.parent.parent
-    srcOffset = panel._layout.data.offset
-    targetOffset = @layout.north(srcOffset)
-    @moveTab @layoutMap[targetOffset]
-
-  moveTabDown: ->
-    panel = @activeTabView.parent.parent
-    srcOffset = panel._layout.data.offset
-    targetOffset = @layout.south(srcOffset)
-    @moveTab @layoutMap[targetOffset]
-
-  moveTabLeft: ->
-    panel = @activeTabView.parent.parent
-    srcOffset = panel._layout.data.offset
-    targetOffset = @layout.west(srcOffset)
-    @moveTab @layoutMap[targetOffset]
-
-  moveTabRight: ->
-    panel = @activeTabView.parent.parent
-    srcOffset = panel._layout.data.offset
-    targetOffset = @layout.east(srcOffset)
-    @moveTab @layoutMap[targetOffset]
+  moveTabUp: -> @moveTab('north')
+
+  moveTabDown: -> @moveTab('south')
+
+  moveTabLeft: -> @moveTab('west')
+
+  moveTabRight: -> @moveTab('east')

   goToLeftTab: ->


Already observed by the main component

Already observed by the main component

--- resources/assets/coffee/react/beatmaps/search-content.coffee
+++ resources/assets/coffee/react/beatmaps/search-content.coffee
@@ -40,10 +40,6 @@
 Observables = observable
   BeatmapList: VirtualList()(ListRender)
   numberOfColumns: if osu.isDesktop() then 2 else 1
-
-observe Observables, 'numberOfColumns', (change) ->
-  if change.oldValue != change.newValue
-    Observables.BeatmapList = VirtualList()(ListRender)

 export class SearchContent extends React.Component


bug fix

bug fix

--- client/Main/kodingrouter.coffee
+++ client/Main/kodingrouter.coffee
@@ -49,8 +49,8 @@

     appManager = KD.getSingleton 'appManager'
     frags      = route.split '/'
-    name       = frags[1] or ''
-    name       = if entryPoint?.type is "group" then frags[2] or '' else (name.split '?')[0]
+    name       = (if entryPoint?.type is "group" then frags[2] else frags[1]) or ''
+    name       = (name.split '?')[0]

     log 'handlingRoute', route, 'for the', name, 'app'


Collaboration: Fixed method naming.

Collaboration: Fixed method naming.

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -1011,7 +1011,7 @@
     @chat.show()

-  prepareCollaboratio_: ->
+  prepareCollaboration: ->

     @rtm        = new RealTimeManager
     {channelId} = @workspaceData


File Tree: Prefer workspace root path

File Tree: Prefer workspace root path

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -251,14 +251,13 @@
     panel        = @workspace.getView()
     filesPane    = panel.getPaneByName 'filesPane'

-    if machineData.isMine()
-      rootPath   = @workspaceData?.rootPath or null
-    else if owner = machineData.getOwner()
-      rootPath   = "/home/#{owner}"
-    else
-      rootPath   = '/'
-
-    filesPane.emit 'MachineMountRequested', machineData, rootPath
+    path = @workspaceData?.rootPath
+
+    path ?= if owner = machineData.getOwner()
+    then "/home/#{owner}"
+    else '/'
+
+    filesPane.emit 'MachineMountRequested', machineData, path

   unmountMachine: (machineData) ->


Overview on lookup

Overview on lookup

--- cmd/lookup.coffee
+++ cmd/lookup.coffee
@@ -13,7 +13,14 @@
     return error('Could not find any show') unless res['Data']['Series']
     res['Data']['Series'].forEach (s) ->
       name = bold(s['SeriesName'])
-      resLine = "#{s['seriesid']} #{name} (since #{s['FirstAired']})"
+      aired = s['FirstAired'] || '?'
+      resLine = "#{s['seriesid']} #{name} (since #{aired})"
       pr(resLine)

+      overview = s['Overview'] && s['Overview'][0]
+      if overview
+        overview = overview.substring(0, 256) + '...' if overview.length > 256
+        pr(gray(overview))
+      pr('  ')
+
 module.exports = lookup


make 2 letter account names valid

make 2 letter account names valid

--- dl/src/common/validation.coffee
+++ dl/src/common/validation.coffee
@@ -11,7 +11,7 @@
     is_account_name: (value)->
         return no if is_empty value
         length = value.length
-        return no if length < 3 or length > 63
+        return no if length < 2 or length > 63
         for label in value.split('.')
             if !(
                 # Starts with a letter, has only letters, digits, and dashes


Break up additional parts of the legislation index.

--- jobs/import-legislation.coffee
+++ jobs/import-legislation.coffee
@@ -22,10 +22,18 @@
 persistLegislationIndex = (session, legislationIndex, db, callback) ->
   assemblyIdForLegislation = legislationIndex.Id

+  codeParts = legislationIndex.Description.split(" ")
+  chamber = if codeParts[0][0] == 'H' then 'house' else 'senate'
+  legType = if codeParts[0][1] == 'R' then 'resolution' else 'bill'
+  number = codeParts[1]
+
   legislationIndex =
     sessionId: session._id,
     title: legislationIndex.Caption,
-    description: legislationIndex.Description
+    code: legislationIndex.Description,
+    chamber: chamber,
+    type: legType,
+    number: number

   db.collection("legislation").update
     assemblyId: assemblyIdForLegislation


display placeholder, then editor after clone drop

--- tutor/src/components/course-calendar/month.cjsx
+++ tutor/src/components/course-calendar/month.cjsx
@@ -6,7 +6,7 @@
 _ = require 'underscore'
 classnames = require 'classnames'
 qs = require 'qs'
-
+extend = require 'lodash/extend'
 {DropTarget} = require 'react-dnd'
 {Calendar, Month, Week, Day} = require 'react-calendar'
 {TeacherTaskPlanStore} = require '../../flux/teacher-task-plan'
@@ -16,12 +16,13 @@

 {ItemTypes, TaskDrop, DropInjector} = require './task-dnd'

+TaskPlanMiniEditor   = require '../task-plan/mini-editor'
+PlanClonePlaceholder = require './plan-clone-placeholder'
 CourseCalendarHeader = require './header'
 CourseAddMenuMixin   = require './add-menu-mixin'
 CourseDuration       = require './duration'
 CoursePlan           = require './plan'
 CourseAdd            = require './add'
-

 CourseMonth = React.createClass
   displayName: 'CourseMonth'
@@ -108,10 +109,6 @@

   getFullMonthName: ->
     @props.date?.format?('MMMM')
-
-  onTaskDrop: -> #(planId, ev) ->
-    plan = TeacherTaskPlanStore.get(planId)
-    day = ev.target.textContent

   onDrop: (item, offset) ->
     return unless @state.hoveredDay
@@ -121,13 +118,27 @@
       })
       @context.router.transitionTo(url)
     else # is a task plan to clone
-      debugger
+      @setState(
+        cloningPlan: extend({}, item,
+          due_at: @state.hoveredDay
+          position: offset
+        )
+      )
+
+  onCloneLoaded: (newPlanId) ->
+    _.defer => # give flux store time to update
+      @setState(editingPlanId: newPlanId, cloningPlan: undefined)
+
+  getEditingPlanEl: ->
+    return null unless @state.editingPlanId
+    ReactDOM.findDOMNode(@).querySelector(
+      ".course-plan-#{@state.editingPlanId}"
+    )

   onHover: (day) ->
     @setState(hoveredDay: day)

   onSidebarToggle: (isOpen) ->
-    console.log isOpen
     @setState(showingSideBar: isOpen)

   render: ->
@@ -184,15 +195,23 @@
           )}
         </BS.Col>
       </BS.Row>
+      {<PlanClonePlaceholder
+        planId={@state.cloningPlan.id}
+        planType={@state.cloningPlan.type}
+        position={@state.cloningPlan.position}
+        onLoad={@onCloneLoaded}
+        courseId={@props.courseId}
+        due_at={@state.cloningPlan.due_at}
+        onLoad={@onCloneLoaded}
+      /> if @state.cloningPlan?}
+      {<TaskPlanMiniEditor
+        planId={@state.editingPlanId}
+        courseId={@props.courseId}
+        findPopOverTarget={@getEditingPlanEl}
+      /> if @state.editingPlanId}
+
     </BS.Grid>

 module.exports = DropTarget([ItemTypes.NewTask, ItemTypes.CloneTask], TaskDrop, DropInjector)(CourseMonth)
-##
-#  (props) ->
-
-
-# )
-
-# module.exports =


PagingScrollElement now responds to childrenAlignment when the children fit within the parent area.

Known bug: when children size decreases from larger to smaller than parent area, childrenAlignment needs to be re-applied; it isn't.

--- src/art/engine/elements/widgets/paging_scroll_element.coffee
+++ src/art/engine/elements/widgets/paging_scroll_element.coffee
@@ -477,7 +477,21 @@
         # throw new Error if maxCount-- < 0
         # console.error "setScrollPosition #{position}"
         @onNextReady => @_updateAtStartAndAtEnd()
-        @_scrollContents.setLocation @newPoint position
+        ###
+        NOTES on childrenAlignment:
+          This doesn't work yet.
+
+          This needs to update whenever the size of children or parent changes.
+
+          This code only updates when scrollPosition changes.
+        ###
+        if @_scrollContents.getCurrentSize().lte @getCurrentSize()
+          axis = @_scrollContents.setAxis @getPendingChildrenAlignment()
+          @_scrollContents.setLocation ww: axis.x, hh: axis.y
+          @newPoint position
+        else
+          @_scrollContents.setAxis 0
+          @_scrollContents.setLocation @newPoint position
   # maxCount = 5

   _updatePagesSplit: (pages = @getPendingPages(), referenceFrame = @getPendingReferenceFrame())->


psot.coffee permit change

psot.coffee permit change

--- workers/social/lib/social/models/messages/post.coffee
+++ workers/social/lib/social/models/messages/post.coffee
@@ -101,7 +101,7 @@
         kodingErr[prop] = err[prop]
     kodingErr

-  @create = permit 'create content'
+  @create = permit 'create posts'
     success: (client, data, callback)->
       constructor = @
       {connection:{delegate}} = client


Allow subscribing twice

Allow subscribing twice

--- workers/social/lib/social/models/recurly/index.coffee
+++ workers/social/lib/social/models/recurly/index.coffee
@@ -149,26 +149,41 @@
       unless status
         callback yes, {}
       else
-
         userCode      = "user_#{delegate._id}"
-        data          =
-          plan        : @code
-          quantity    : data.quantity
-          accountCode : userCode
-
-        payment.addUserSubscription userCode, data, (err, result)->
+        JRecurlySubscription.one
+          userCode: userCode
+          planCode: @code
+        , (err, subs)=>
           return callback err  if err
-          sub = new JRecurlySubscription
-            planCode : result.code
-            userCode : data.accountCode
-            uuid     : result.uuid
-            quantity : result.quantity
-            status   : result.status
-            datetime : result.datetime
-            expires  : result.expires
-            renew    : result.renew
-          sub.save ->
-            callback no, sub
+          if subs
+            subs.quantity ?= 1
+            subs.quantity += 1
+            payment.updateUserSubscription userCode,
+              quantity: subs.quantity
+              plan    : @code
+              uuid    : subs.uuid
+            , (err, result)=>
+              @save ->
+                callback no, subs
+          else
+            data          =
+              plan        : @code
+              quantity    : data.quantity
+              accountCode : userCode
+
+            payment.addUserSubscription userCode, data, (err, result)->
+              return callback err  if err
+              sub = new JRecurlySubscription
+                planCode : result.plan
+                userCode : userCode
+                uuid     : result.uuid
+                quantity : result.quantity
+                status   : result.status
+                datetime : result.datetime
+                expires  : result.expires
+                renew    : result.renew
+              sub.save ->
+                callback no, sub

   getSubscription: secure (client, callback)->
     {delegate} = client.connection


Fix condition for appliedRate

Fix condition for appliedRate

--- src/components/common/trends-add/trends-add.component.coffee
+++ src/components/common/trends-add/trends-add.component.coffee
@@ -135,7 +135,7 @@
       startDate = timestamps[startIndex]
       for i in [startIndex...lastIndex]
         continue unless shouldApplyTrend(trend, startDate, timestamps[i])
-        appliedRate = if trend.rate > 0 then (trend.rate + 100.0) else (trend.rate - 100)
+        appliedRate = if trend.rate >= 0 then (trend.rate + 100.0) else (trend.rate - 100)
         increment = lastValue * appliedRate / 100.0
         incrementRemainingValues(values, i, increment)


fix the last deprecations

fix the last deprecations

--- lib/idris-controller.coffee
+++ lib/idris-controller.coffee
@@ -55,15 +55,15 @@
     @messages.clear()
     @messages.hide()
     @localChanges = false
-    if @isIdrisFile(editor.getUri())
-      @loadFile editor.getUri()
+    if @isIdrisFile(editor.getURI())
+      @loadFile editor.getURI()

   idrisFileChanged: (editor) ->
     @localChanges = editor.isModified()
     if @localChanges
       @statusbar.setStatus 'Idris: local modifications'
-    else if @isIdrisFile(editor.getUri())
-      @loadFile editor.getUri()
+    else if @isIdrisFile(editor.getURI())
+      @loadFile editor.getURI()

   idrisFileClosed: (editor) ->
     @idrisBuffers -= 1


fix url root for spine model.

fix url root for spine model.

--- spine/templates/spine/model/model.coffee
+++ spine/templates/spine/model/model.coffee
@@ -1,3 +1,4 @@
 class {{ app_class.title }}.{{ class_name.title }} extends Spine.Model
   @configure '{{ class_name.title }}', '{{ fields|join:"', '" }}'
-  @extend Spine.Model.Ajax+  @extend Spine.Model.Ajax
+  @url: "/{{ app_class }}/{{ class_name.pluralize }}"


:memo: Correct indentation

:memo: Correct indentation

--- lib/keybinding-panel.coffee
+++ lib/keybinding-panel.coffee
@@ -53,7 +53,7 @@
         @td class: 'source', source
         @td class: 'selector', selector

-    # Private: Returns a user friendly description of where a keybinding was
+  # Private: Returns a user friendly description of where a keybinding was
   # loaded from.
   #
   # * filePath:


Fix link

Fix link

--- grammars/csound.cson
+++ grammars/csound.cson
@@ -185,7 +185,7 @@
           0: name: 'punctuation.definition.string.end.csound'
         patterns: [
           {include: '#macroUses'}
-          { # https://github.com/csound/csound/search?q=sprints1+path%3AOpcodes+filename%3Afout.c
+          { # https://github.com/csound/csound/search?q=printksset_+path%3AOOps+filename%3Augrw1.c
             name:  'constant.character.escape.csound'
             match: '%(?:\\d*(?:\\.\\d+)?[cdhilouxX]|[!%nNrRtT])|[~^]'
           }


fix show source when try to load source from bySource link

--- src/main/resources/javascript/app/feeds_controller.coffee
+++ src/main/resources/javascript/app/feeds_controller.coffee
@@ -56,14 +56,21 @@
   show: (source_name, feed_name) ->
     source_name = decodeURIComponent(source_name)
     feed_name = decodeURIComponent(feed_name)
+
     source = Sources.takeFirst (s) -> s.normalized() == source_name
     if source
+      name = decodeURI(feed_name)
+      finder = (source, name) ->
+        source.feed().filter (feed) -> feed.normalized() == name
       feeds = if !posts.is_empty()
         cf = posts.get()
-        source.feed().filter (feed) -> feed.id() == parseInt(cf)
+        xs = source.feed().filter (feed) -> feed.id() == parseInt(cf)
+        if xs.length == 0
+          finder(source, name)
+        else
+          xs
       else
-        source.feed().filter (feed) -> feed.normalized() == decodeURI(feed_name)
-
+        finder(source, name)
       if feeds.length > 0
         original_feed = feeds[0]
         ajax.show_feed feeds[0].id(),


Correct order of operations

Correct order of operations

--- components/modalize/view.coffee
+++ components/modalize/view.coffee
@@ -62,12 +62,12 @@
     this

   escape: (e) =>
-    @close(null, e) if e.which is 27
+    @close(e, null) if e.which is 27

   maybeClose: (e) ->
-    @close(null, e) if $(e.target).hasClass('js-modalize-backdrop')
+    @close(e, null) if $(e.target).hasClass('js-modalize-backdrop')

-  close: (callback, e) ->
+  close: (e, callback) ->
     $(window).off 'keyup.modalize'
     @scrollbar.reenable()
     @trigger 'closing'


Updating to use delegateTarget instead of target

--- cli/templates/app_skel/src/app/views/app_view.coffee
+++ cli/templates/app_skel/src/app/views/app_view.coffee
@@ -8,5 +8,5 @@
     # automagically route links starting with "/"
     @el.find( 'a[href*="/"]' ).each ( index, item ) =>
       $( item ).click ( event ) =>
-        @navigate $( event.target ).attr 'href'
-        return off+        @navigate $( event.delegateTarget ).attr 'href'
+        return off


sidebar: update the offscreen indicators on ParticipantUpdated

--- client/Main/maincontroller/mainviews/mainview.coffee
+++ client/Main/maincontroller/mainviews/mainview.coffee
@@ -102,6 +102,9 @@
     @sidebar.on 'NoOffscreenItemsBelow', ->
       moreItemsBelow.hide()

+    KD.singletons.notificationController.on 'ParticipantUpdated', =>
+      @sidebar.updateOffscreenIndicators()
+
   createPanelWrapper:->

     @addSubView @panelWrapper = new KDView


reactivity: reset unread count when a new message is received in active channel

--- client/activity/lib/components/chatlist/index.coffee
+++ client/activity/lib/components/chatlist/index.coffee
@@ -34,6 +34,7 @@
     super props

     @state = { selectedMessageId: @props.selectedMessageId }
+    kd.singletons.windowController.addFocusListener @bound 'handleFocus'

   getDataBindings: ->
@@ -56,6 +57,11 @@

     if kd.singletons.windowController.isFocused()
       ActivityFlux.actions.channel.glance @props.channelId
+
+
+  handleFocus: (focused) ->
+
+    @glance()  if focused and @props.unreadCount

   onGlancerEnter: -> @glance()


Client: Activity: Updated setPaddedClassName method to return if list is not rendered

--- client/activity/lib/components/chatpane/index.coffee
+++ client/activity/lib/components/chatpane/index.coffee
@@ -74,7 +74,7 @@
     scrollContainerClientHeight = scrollContainer.clientHeight
     channelInfoContainerHeight  = 0

-    return  if scrollContainerClientHeight is 0
+    return  if scrollContainerClientHeight is 0 or listHeight is 0

     if channelInfoContainer
       channelInfoContainerHeight = channelInfoContainer.offsetHeight


Client: Activity: Updated onThresholdReached method and deleted loadedWithScroll props from ChatList. Also using isThresholdReached property for isMessagesLoading flag

--- client/activity/lib/components/chatpane/index.coffee
+++ client/activity/lib/components/chatpane/index.coffee
@@ -13,7 +13,6 @@
     title             : null
     messages          : null
     isDataLoading     : no
-    onLoadMore        : kd.noop
     afterInviteOthers : kd.noop
     showItemMenu      : yes

@@ -24,12 +23,26 @@

     { thread } = nextProps

-    @loadedWithScroll       = thread.getIn ['flags', 'loadedWithScroll']
     isMessageBeingSubmitted = thread.getIn ['flags', 'isMessageBeingSubmitted']
-    @shouldScrollToBottom   = yes  if isMessageBeingSubmitted
+
+    if isMessageBeingSubmitted
+      @shouldScrollToBottom   = yes
+      @isPageLoaded           = no

-  onTopThresholdReached: -> @props.onLoadMore()
+  onTopThresholdReached: (event) ->
+
+    messages = @props.thread.get 'messages'
+    @isPageLoaded = no
+
+    return  if @isThresholdReached
+
+    return  unless messages.size
+
+    @isThresholdReached = yes
+
+    from = messages.first().get('createdAt')
+    kd.utils.wait 500, => ActivityFlux.actions.message.loadMessages @channel('id'), { from }

   afterInviteOthers: -> @props.afterInviteOthers()
@@ -49,16 +62,15 @@

   renderBody: ->

-    return null  unless @props.messages?.size
+    return null  unless @props.thread

     <Scroller
       ref="scrollContainer"
       onTopThresholdReached={@bound 'onTopThresholdReached'}>
       {@renderChannelInfoContainer()}
       <ChatList
-        isMessagesLoading={@props.thread?.getIn ['flags', 'isMessagesLoading']}
-        loadedWithScroll={@props.thread?.getIn ['flags', 'loadedWithScroll']}
-        messages={@props.messages}
+        isMessagesLoading={@isThresholdReached}
+        messages={@props.thread.get 'messages'}
         showItemMenu={@props.showItemMenu}
         channelId={@channel 'id'}
         channelName={@channel 'name'}


Client: Activity: Added Update_Channel_Success event listener to channels store

--- client/activity/lib/flux/stores/channelsstore.coffee
+++ client/activity/lib/flux/stores/channelsstore.coffee
@@ -35,6 +35,8 @@
     @on actions.GLANCE_CHANNEL_SUCCESS, @handleGlanceChannel

     @on actions.REMOVE_PARTICIPANT_FROM_CHANNEL, @handleRemoveParticipantFromChannel
+
+    @on actions.UPDATE_CHANNEL_SUCCESS, @handleLoadChannelSuccess

     @on createChannelActions.CREATE_PRIVATE_CHANNEL_SUCCESS, @handleLoadChannelSuccess
     @on createChannelActions.CREATE_PUBLIC_CHANNEL_SUCCESS, @handleLoadChannelSuccess


DefineStackView: share stack template with group when set for group is requested

--- client/admin/lib/views/stacks/definestackview.coffee
+++ client/admin/lib/views/stacks/definestackview.coffee
@@ -518,15 +518,18 @@

     @outputView.add 'Setting this as default group stack template...'

-    currentGroup.modify stackTemplates: [ stackTemplate._id ], (err) =>
-      return if @outputView.handleError err
-
-      new kd.NotificationView
-        title : "Group (#{slug}) stack has been saved!"
-        type  : 'mini'
-
-      computeController.createDefaultStack yes
-      computeController.checkStackRevisions()
-
-      @emit 'Reload'
-      @emit 'Completed', stackTemplate
+    stackTemplate.setAccess 'group', (err) =>
+      return  if @outputView.handleError err
+
+      currentGroup.modify stackTemplates: [ stackTemplate._id ], (err) =>
+        return  if @outputView.handleError err
+
+        new kd.NotificationView
+          title : "Group (#{slug}) stack has been saved!"
+          type  : 'mini'
+
+        computeController.createDefaultStack yes
+        computeController.checkStackRevisions()
+
+        @emit 'Reload'
+        @emit 'Completed', stackTemplate


groups: markdown heading parser

groups: markdown heading parser

--- client/app/MainApp/lazy/staticgroupcontroller.coffee
+++ client/app/MainApp/lazy/staticgroupcontroller.coffee
@@ -31,12 +31,30 @@
     # @currentFacets = []

     @reviveViews()
+    @parseMenuItems (items)=>
+      log items
+

     @checkGroupUserRelation()
     @attachListeners()

+
     @registerSingleton 'staticGroupController', @, yes

+  parseMenuItems :(callback)->
+    KD.remote.cacheable @groupEntryPoint, (err, groups, name)=>
+      if groups.first
+        groups.first.fetchReadme (err,{content})=>
+          unless err
+            menuItems = []
+            lines = content.split("\n");
+            for line,index in lines
+              if /^#[^#]{1,}/.test line
+                menuItems.push line.replace(/^#*\s*/g, '')
+              else if /^\s*(=){1,}\s*$/.test line
+                menuItems.push lines[index-1]
+
+            callback menuItems

   reviveViews :->

@@ -95,6 +113,7 @@
     @utils.defer =>
       groupLogoView.setClass 'animate'
       @landingView._windowDidResize()
+

   checkGroupUserRelation:->


If match 5.

If match 5.

--- vendor/assets/javascripts/autolink.hb.js.coffee
+++ vendor/assets/javascripts/autolink.hb.js.coffee
@@ -23,7 +23,7 @@
       template = @replace(pattern, (string, full, schema, url, index) ->
         match = /(^(beta|www)\.(cloudsdale)\.(org|dev)(\/.*)?)/ig.exec(url)

-        if match
+        if match[5]
           path = match[5].split('/')
           href = match[5]


Add note on gcs config to config file

Add note on gcs config to config file

--- config/settings.defaults.coffee
+++ config/settings.defaults.coffee
@@ -31,6 +31,7 @@
 		# Choices are
 		# s3 - Amazon S3
 		# fs - local filesystem
+		# gcs - Google Cloud Storage
 		backend: process.env['BACKEND']

 		s3:
@@ -40,6 +41,9 @@
 				endpoint: process.env['AWS_S3_ENDPOINT']
 				pathStyle: process.env['AWS_S3_PATH_STYLE']
 				partSize: process.env['AWS_S3_PARTSIZE'] or (100 * 1024 * 1024)
+
+		# GCS should be configured by the service account on the kubernetes pod. See GOOGLE_APPLICATION_CREDENTIALS,
+		# which will be picked up automatically.

 		stores:
 			user_files: process.env['USER_FILES_BUCKET_NAME']


fixed asset upload

fixed asset upload

--- modules/levelcreator/app/assets/javascripts/level_creator.coffee
+++ modules/levelcreator/app/assets/javascripts/level_creator.coffee
@@ -71,8 +71,10 @@

           saveForm = @$form[0]
           produceButton = $("#produce-form .btn")[0]
+          assetsForm = $("#assets-upload")[0]

           saveForm.action = saveForm.action.replace(@levelId, data.newId)
+          assetsForm.action = assetsForm.action.replace(@levelId, data.newId)
           produceButton.href = $("#produce-form .btn")[0].href.replace(@levelId, data.newId)
           @assetHandler.levelId = @levelId


teams: invitation validation api call implemented

--- client/landing/site.landing/coffee/core/utils.coffee
+++ client/landing/site.landing/coffee/core/utils.coffee
@@ -471,4 +471,19 @@
         KD.utils.clearTeamData()
         location.href = formData.redirect
       error     : ({responseText}) =>
-        new KDNotificationView title : responseText+        new KDNotificationView title : responseText  routeIfInvitationTokenIsValid: (token) ->
+
+
+  routeIfInvitationTokenIsValid: (token) ->
+
+    $.ajax
+      url       : "/-/teams/validate-token"
+      data      : { token }
+      type      : 'POST'
+      success   : ({email}) ->
+        KD.utils.storeNewTeamData 'invitation', { token, email }
+        KD.singletons.router.handleRoute '/Welcome'
+      error     : ({responseText}) ->
+        new KDNotificationView title : responseText
+
+


Remove console.log call in element toggle link.

--- app/assets/javascripts/alchemy/alchemy.element_editors.js.coffee
+++ app/assets/javascripts/alchemy/alchemy.element_editors.js.coffee
@@ -119,5 +119,4 @@

   observeToggler: (scope) ->
     $('[data-element-dirty]', scope).click ->
-      console.log('clicked toggler', this)
       Alchemy.ElementEditors.toggle $(this).data('element-dirty')


Fixes #4118 - Single tree select no longer allows displaying child values.

--- app/assets/javascripts/app/lib/app_post/searchable_select.coffee
+++ app/assets/javascripts/app/lib/app_post/searchable_select.coffee
@@ -1,22 +1,23 @@
 class App.SearchableSelect extends Spine.Controller

   events:
-    'input .js-input':                      'onInput'
-    'blur .js-input':                       'onBlur'
-    'focus .js-input':                      'onFocus'
-    'focus .js-shadow':                     'onShadowFocus'
-    'change .js-shadow':                    'onShadowChange'
-    'click .js-option':                     'selectItem'
-    'click .searchableSelect-option-text':  'selectItem'
-    'click .searchableSelect-option-arrow': 'navigateIn'
-    'click .js-back':                       'navigateOut'
-    'mouseenter .js-option':                'highlightItem'
-    'mouseenter .js-enter':                 'highlightItem'
-    'mouseenter .js-back':                  'highlightItem'
-    'shown.bs.dropdown':                    'onDropdownShown'
-    'hidden.bs.dropdown':                   'onDropdownHidden'
-    'keyup .js-input':                      'onKeyUp'
-    'click .js-remove':                     'removeThisToken'
+    'input .js-input':                                'onInput'
+    'blur .js-input':                                 'onBlur'
+    'focus .js-input':                                'onFocus'
+    'focus .js-shadow':                               'onShadowFocus'
+    'change .js-shadow':                              'onShadowChange'
+    'click .js-option':                               'selectItem'
+    'click .js-option .searchableSelect-option-text': 'selectItem'
+    'click .js-enter .searchableSelect-option-text':  'navigateInOrSelectItem'
+    'click .searchableSelect-option-arrow':           'navigateIn'
+    'click .js-back':                                 'navigateOut'
+    'mouseenter .js-option':                          'highlightItem'
+    'mouseenter .js-enter':                           'highlightItem'
+    'mouseenter .js-back':                            'highlightItem'
+    'shown.bs.dropdown':                              'onDropdownShown'
+    'hidden.bs.dropdown':                             'onDropdownHidden'
+    'keyup .js-input':                                'onKeyUp'
+    'click .js-remove':                               'removeThisToken'

   elements:
     '.js-dropdown':               'dropdown'
@@ -288,6 +289,10 @@
     @input.val value
     @shadowInput.val key

+  navigateInOrSelectItem: (event) ->
+    return @selectItem(event) if @attribute.multiple
+    return @navigateIn(event)
+
   selectItem: (event) ->
     currentText = $(event.target).text().trim()
     return if !currentText
@@ -300,6 +305,8 @@

   navigateIn: (event) ->
     event.stopPropagation()
+    if !@attribute.multiple
+      @selectItem(event)
     @navigateDepth(1)

   navigateOut: (event) ->
@@ -512,8 +519,14 @@

   addValueToShadowInput: (currentText, dataId) ->
     @input.val('')
-    return if @shadowInput.val().includes("#{dataId}") if @shadowInput.val() # cast dataId to string before check
-    @currentData = {name: currentText, value: dataId}
+
+    if @attribute.multiple
+      return if @shadowInput.val().includes("#{dataId}") if @shadowInput.val() # cast dataId to string before check
+      @currentData = {name: currentText, value: dataId}
+    else
+      @currentData = {name: currentText, value: dataId}
+      return if @shadowInput.val().includes("#{dataId}") if @shadowInput.val() # cast dataId to string before check
+
     @shadowInput.append($('<option/>').attr('selected', true).attr('value', @currentData.value).text(@currentData.name))
     @onShadowChange()


fix atoms dirty forms js

fix atoms dirty forms js

--- app/assets/javascripts/folio/console/modules/_dirty-forms.coffee
+++ app/assets/javascripts/folio/console/modules/_dirty-forms.coffee
@@ -13,6 +13,9 @@
   .not('.f-c-simple-form-with-atoms')
   .one 'change', handler

+$('.f-c-simple-form-with-atoms__form')
+  .one 'change', handler
+
 receiveMessage = (e) ->
   return if e.origin isnt window.origin
   switch e.data.type


better embering

better embering

--- app/assets/javascripts/mixins/controllers/participants.js.coffee
+++ app/assets/javascripts/mixins/controllers/participants.js.coffee
@@ -8,9 +8,9 @@
   ).property()

   participations: Em.computed.alias('model.participations')
+
   participants: (->
-    @get('participations').map (participation) ->
-      participation.get('participant')
+    @get('participations').mapBy('participant')
   ).property('participations.@each.participant')

   createParticipant: (newParticipant) ->


overscroll now targets 1/3 height, not 1/2

--- src/art/engine/elements/widgets/paging_scroll_element.coffee
+++ src/art/engine/elements/widgets/paging_scroll_element.coffee
@@ -193,7 +193,7 @@
         # DIRECT TRACKING (no animation)
         {windowSize} = @scrollElement
         boundedTargetLocation = @boundLocation targetScrollPosition
-        maxBeyond = windowSize / 2
+        maxBeyond = windowSize / 3
         minV = min boundedTargetLocation, targetScrollPosition
         maxV = max boundedTargetLocation, targetScrollPosition
         targetScrollPosition = bound minV,


support data-attributes

support data-attributes

--- lib/assets/javascripts/twine.js.coffee
+++ lib/assets/javascripts/twine.js.coffee
@@ -45,7 +45,7 @@
   if node.bindingId
     Twine.unbind(node)

-  for type, binding of Twine.bindingTypes when definition = node.getAttribute(type)
+  for type, binding of Twine.bindingTypes when definition = (node.getAttribute(type) || node.getAttribute("data-#{type}"))
     element = {bindings: []} unless element  # Defer allocation to prevent GC pressure
     fn = binding(node, context, definition, element)
     element.bindings.push(fn) if fn


Smooth scroll; yet more refactoring.

Smooth scroll; yet more refactoring.

--- content_scripts/scroller.coffee
+++ content_scripts/scroller.coffee
@@ -29,6 +29,12 @@
   else
     amount

+# Perform a scroll. Return true if we successfully scrolled by the requested amount, and false otherwise.
+performScroll = (element, axisName, amount) ->
+  before = element[axisName]
+  element[axisName] += amount
+  amount == element[axisName] - before
+
 # Test whether element should be scrolled.
 shouldScroll = (element, direction) ->
   computedStyle = window.getComputedStyle(element)
@@ -45,17 +51,13 @@
 # Bug verified in Chrome 38.0.2125.104.
 isScrollPossible = (element, direction, amount, factor) ->
   axisName = scrollProperties[direction].axisName
-  # delta, here, is treated as a relative amount, which is correct for relative scrolls. For absolute scrolls
+  # amount, here, is treated as a relative amount, which is correct for relative scrolls. For absolute scrolls
   # (only gg, G, and friends), amount can be either 'max' or zero. In the former case, we're definitely
   # scrolling forwards, so any positive value will do for delta.  In the latter case, we're definitely
   # scrolling backwards, so a delta of -1 will do.
   delta = factor * getDimension(element, direction, amount) || -1
   delta = delta / Math.abs delta # 1 or -1
-  before = element[axisName]
-  element[axisName] += delta
-  after = element[axisName]
-  element[axisName] = before
-  before != after
+  performScroll(element, axisName, delta) and performScroll(element, axisName, -delta)

 # Find the element which we should and can scroll (or document.body).
 findScrollableElement = (element, direction, amount, factor = 1) ->
@@ -65,21 +67,13 @@
       element = element.parentElement || document.body
   element

-performScroll = (element, axisName, amount, checkVisibility = true) ->
-  before = element[axisName]
-  element[axisName] += amount
-
-  if checkVisibility
-    # if the activated element has been scrolled completely offscreen, subsequent changes in its scroll
-    # position will not provide any more visual feedback to the user. therefore we deactivate it so that
-    # subsequent scrolls only move the parent element.
-    # TODO(smblott) Refactor this out of here.
-    rect = activatedElement.getBoundingClientRect()
-    if (rect.bottom < 0 || rect.top > window.innerHeight || rect.right < 0 || rect.left > window.innerWidth)
-      activatedElement = element
-
-  # Return true if we successfully scrolled by the requested amount, false otherwise.
-  amount == element[axisName] - before
+checkVisibility = (element) ->
+  # if the activated element has been scrolled completely offscreen, subsequent changes in its scroll
+  # position will not provide any more visual feedback to the user. therefore we deactivate it so that
+  # subsequent scrolls only move the parent element.
+  rect = activatedElement.getBoundingClientRect()
+  if (rect.bottom < 0 || rect.top > window.innerHeight || rect.right < 0 || rect.left > window.innerWidth)
+    activatedElement = element

 # How scrolling is handled:
 #   - For non-smooth scrolling, the entire scroll happens immediately.
@@ -109,13 +103,16 @@
     axisName = scrollProperties[direction].axisName

     unless settings.get "smoothScroll"
-      return performScroll element, axisName, amount
+      # Jump scrolling.
+      performScroll element, axisName, amount
+      checkVisibility element
+      return

     if mostRecentActivationTime == time or lastEvent?.repeat
       # Either the most-recently activated animator has not yet received its keyup event (so it's still
-      # scrolling), or this is a keyboard repeat (for which we don't initiate a new animator).  We need both
-      # of these checks because sometimes (perhaps one time in twenty) the last keyboard repeat arrives
-      # *after* the corresponding keyup.
+      # scrolling), or this is a keyboard repeat (for which we don't initiate a new animator).
+      # NOTE(smblott) We need both of these checks because sometimes (perhaps one time in twenty) the last
+      # keyboard repeat arrives *after* the corresponding keyup.
       return

     mostRecentActivationTime = activationTime = ++time
@@ -131,29 +128,26 @@
     duration = Math.max 100, 20 * Math.log amount

     totalDelta = 0
+    totalElapsed = 0.0
     calibration = 1.0
-    initialTimestamp = null
     previousTimestamp = null
     animatorId = null

-    progressAnimation = ->
+    advanceAnimation = ->
       animatorId = requestAnimationFrame animate

     cancelAnimation = ->
-      # Make one final call of performScroll to check the visibility of the activated element.
-      performScroll element, axisName, 0, true
       cancelAnimationFrame animatorId

     animate = (timestamp) ->
-      initialTimestamp ?= timestamp
       previousTimestamp ?= timestamp

       if timestamp == previousTimestamp
-        return progressAnimation()
+        return advanceAnimation()

       # The elapsed time is typically about 16ms.
       elapsed = timestamp - previousTimestamp
-      totalElapsed = timestamp - initialTimestamp
+      totalElapsed += elapsed
       previousTimestamp = timestamp

       # The constants in the duration calculation, above, are chosen to provide reasonable scroll speeds for
@@ -168,15 +162,14 @@
       delta = Math.ceil amount * (elapsed / duration) * calibration
       delta = if isKeyStillDown() then delta else Math.max 0, Math.min delta, amount - totalDelta

-      if delta and performScroll element, axisName, sign * delta, false
+      if delta and performScroll element, axisName, sign * delta
         totalDelta += delta
-        progressAnimation()
+        advanceAnimation()
       else
-        # We're done. Make one final call of performScroll to check the visibility of the activated element.
-        performScroll element, axisName, 0, true
+        checkVisibility element
         cancelAnimationFrame animatorId

-    progressAnimation()
+    advanceAnimation()

 Scroller =
   init: (frontendSettings) ->


MachineSettingsCommonView: Added notification view and helper method to deal with it.

--- client/app/lib/providers/machinesettingscommonview.coffee
+++ client/app/lib/providers/machinesettingscommonview.coffee
@@ -38,6 +38,9 @@
       title    : headerAddButtonTitle
       cssClass : "solid green compact add-button #{addButtonCssClass}"
       callback : @bound 'showAddView'
+
+    @addSubView @notificationView = new KDCustomHTMLView
+      cssClass : 'notification hidden'

   createAddView: ->
@@ -116,3 +119,13 @@
   initList: ->

     kd.warn 'unhandled method'
+
+
+  showNotification: (text, type = 'error') ->
+
+    view = @notificationView
+
+    view.unsetClass name  for name in [ 'success', 'error', 'warning' ]
+    view.setClass type
+    view.updatePartial text
+    view.show()


use SSL for static files base

use SSL for static files base

--- config/main.kontrol-prod.coffee
+++ config/main.kontrol-prod.coffee
@@ -99,7 +99,7 @@
     indexMaster   : "index-master.html"
     index         : "default.html"
     useStaticFileServer: no
-    staticFilesBaseUrl: "http://new.koding.com"
+    staticFilesBaseUrl: "https://new.koding.com"
     runtimeOptions:
       useNeo4j: yes
       logToExternal : no


improved dragging overlay

improved dragging overlay

--- client/src/app/game/overlay/overlayController.coffee
+++ client/src/app/game/overlay/overlayController.coffee
@@ -4,6 +4,7 @@
 angular.module('gamEvolve.game.overlay', [])

 .controller "OverlayCtrl", ($scope, $timeout, overlay) ->
+  $scope.isDraggingBorders = false
   $scope.showOverlay = false
   $scope.notification =
     icon: "font-icon-record"
@@ -35,7 +36,7 @@
   onUpdate = ->
     $scope.notification = createNotification()
     # Show the overlay if there is a current notification or if we are dragging the borders
-    $scope.showOverlay = $scope.notification?
+    $scope.showOverlay = $scope.isDraggingBorders or $scope.notification?

     # Get ready to cancel the notification when it expires
     if $scope.notification then $timeout(onUpdate, 100)
@@ -43,11 +44,14 @@
   # Bring the service into scope to watch it
   $scope.overlay = overlay
   $scope.$watch("overlay", onUpdate, true)
+  $scope.$watch("isDraggingBorders", onUpdate, true)

   # Activate the overlay when dragging
   # Trying to use angular here via ng-hide cripples drag n' drop, so this workaround uses jQuery directly
   # Trying to show the overlay on mousemove creates the same problem :(
-  $("body").on "mousedown", ->
-    $("#gameOverlay").show()
-  $("body").on "mouseup", ->
-    if not $scope.showOverlay then $("#gameOverlay").hide()
+  $("body").on "mousedown", ".splitter", ->
+    # $("#gameOverlay").show()
+    $scope.$apply(-> $scope.isDraggingBorders = true)
+  $("body").on "mouseup", ".splitter", ->
+    # if not $scope.showOverlay then $("#gameOverlay").hide()
+    $scope.$apply(-> $scope.isDraggingBorders = false)


adding spaces and semicolons

adding spaces and semicolons

--- client/controllers/incidentReports/incidentReports.coffee
+++ client/controllers/incidentReports/incidentReports.coffee
@@ -140,9 +140,8 @@
         label: 'Locations'
         fn: (value, object, key) ->
           if object.locations
-            $.map(object.locations, (element, index) ->
-              element.name
-            ).toString()
+            locations = _.map object.locations, (location) -> location.name
+            locations.join('; ')
           else
             ''
       }


don't remove dirs corey

don't remove dirs corey

--- plugins/project/project.coffee
+++ plugins/project/project.coffee
@@ -43,7 +43,7 @@
       for path in openedPaths
         if File.isFile path
           @editor.open path
-        else
+        else if not File.exists path
          openedPaths = _.without(openedPaths, path)
       @set "openedPaths", openedPaths


Count number of voters in municipalities

--- webapp/app/scripts/models/DistrictLoader.coffee
+++ webapp/app/scripts/models/DistrictLoader.coffee
@@ -68,7 +68,7 @@
         name: fullPostName
         districtId: districtId
         boroughId: if post.id_arrondissement then "#{districtId}-#{post.id_arrondissement}" else null
-        nVoters: null
+        nVotes: _.reduce(post.candidats, ((s, c) -> s + parseFloat(c.nb_vote)), 0)

       for candidate in post.candidats
         candidates.push


hasPeriods should also be passed in to loading version of calendar

--- src/components/task-plan/teacher-task-plans-listing.cjsx
+++ src/components/task-plan/teacher-task-plans-listing.cjsx
@@ -143,7 +143,14 @@
     loadPlansList = _.partial(TeacherTaskPlanStore.getActiveCoursePlans, courseId)
     loadedCalendarProps = {loadPlansList, courseId, date, displayAs, hasPeriods}
     loadingCalendarProps = if hasPeriods
-      {loadPlansList, courseId, date, displayAs, className: 'calendar-loading'}
+      {
+        loadPlansList,
+        courseId,
+        date,
+        displayAs,
+        hasPeriods,
+        className: 'calendar-loading'
+      }
     else
       loadedCalendarProps


fix(viewService): enable stateParams in `to` function

NON-BREAKING CHANGE / BACKWARDS COMPATIBLE
Before:
```html
<bp-button bpTap="to('home',true)">Back</bp-button>
```

After:
```html
<bp-button bpTap="to('home',{back: true})">Back</bp-button>
```

--- modules/services/view/index.coffee
+++ modules/services/view/index.coffee
@@ -10,9 +10,11 @@
   transition = ''
   direction  = 'normal'

-  @to = (state, back) ->
-    direction = if back then 'reverse' else 'normal'
-    $state.transitionTo state
+  @to = (state, stateParams = {}) ->
+    if not _.isObject stateParams
+      stateParams = back: stateParams
+    direction = if stateParams.back then 'reverse' else 'normal'
+    $state.transitionTo state, stateParams

   @setTransition  = (newTransition) ->
     transition = newTransition


Set markers to not be persistent

Set markers to not be persistent

This isn't needed since spell check is run at startup.

--- lib/misspelling-view.coffee
+++ lib/misspelling-view.coffee
@@ -27,7 +27,7 @@
     @updatePosition()

   createMarker: ->
-    @marker = @editor.markScreenRange(@getScreenRange(), invalidation: 'inside', replicate: false)
+    @marker = @editor.markScreenRange(@getScreenRange(), invalidation: 'inside', replicate: false, persistent: false)
     @subscribe @marker, 'changed', ({newHeadScreenPosition, newTailScreenPosition, isValid}) =>
       @startPosition = newTailScreenPosition
       @endPosition = newHeadScreenPosition


delete now decrements

delete now decrements

--- public/dreamcatcher/controllers/comment_controller.coffee
+++ public/dreamcatcher/controllers/comment_controller.coffee
@@ -133,8 +133,9 @@
     entryId = el.data 'entryid'
     commentId = el.data 'id'
     Dreamcatcher.Models.Comment.delete entryId,commentId
-    el.closest('.prevCommentWrap').fadeOut 200
+    el.closest('.prevCommentWrap').remove()
     @updateCommentCount entry
+

   '.save click': (el) ->
     $(".comment_body,.save",el.parent()).attr("disabled",true).addClass("disabled")


No message without body

No message without body

--- public/javascripts/app/views/profile/profile_side1.coffee
+++ public/javascripts/app/views/profile/profile_side1.coffee
@@ -52,14 +52,18 @@
           buttonClick: (btType, $win)->
             if btType is "send"
               $form = $win.find("form:first")
-              formData = $form.serialize()
-              messageModel = new Kin.MessageModel
-              messageModel.save null,
-                data: formData
-                success: ()->
-                  toName = $win.find("#message-to-name").text()
-                  $.jGrowl("Message sent to #{toName}")
-                error: ()->
-                  $.jGrowl("Message could not be sent :( Please try again.")
-            $win.close()
+              $textarea = $form.find("textarea")
+              if $textarea.val().length
+                formData = $form.serialize()
+                messageModel = new Kin.MessageModel
+                messageModel.save null,
+                  data: formData
+                  success: ()->
+                    toName = $win.find("#message-to-name").text()
+                    $.jGrowl("Message sent to #{toName}")
+                    $win.close()
+                  error: ()->
+                    $.jGrowl("Message could not be sent :( Please try again.")
+            else
+              $win.close()
         })


Fixed url.

Fixed url.

--- app/assets/javascripts/app/controllers/maintenance.js.coffee
+++ app/assets/javascripts/app/controllers/maintenance.js.coffee
@@ -23,5 +23,5 @@
     )
     @render()

-App.Config.set( 'Maintenance', { prio: 3600, name: 'Maintenances', parent: '#system', target: '#system/maintenances', controller: Index, role: ['Admin'] }, 'NavBarLevel44' )
+App.Config.set( 'Maintenance', { prio: 3600, name: 'Maintenance', parent: '#system', target: '#system/maintenance', controller: Index, role: ['Admin'] }, 'NavBarLevel44' )


Improved client update, store in local storage also.

--- app/assets/javascripts/app/lib/app_post/collection.js.coffee
+++ app/assets/javascripts/app/lib/app_post/collection.js.coffee
@@ -130,7 +130,8 @@

     # load full array once
     if _.isArray( params.data )
-      App[ params.type ].refresh( params.data )
+      if !params.refresh
+        App[ params.type ].refresh( params.data )

       # remember in store if not already requested from local storage
       if !localStorage
@@ -141,7 +142,8 @@
     # load data from object
 #    if _.isObject( params.data )
     for key, object of params.data
-      App[ params.type ].refresh( object )
+      if !params.refresh
+        App[ params.type ].refresh( object )

       # remember in store if not already requested from local storage
       if !localStorage
@@ -152,9 +154,13 @@
 #    if App[type].exists( id ) && !callback
     if !force && App[type].exists( id )
       data = App[type].find( id )
+      data = @_fillUp( type, data )
       if callback
         callback( data )
+      console.log 'find', type, data
+      return data
     else
+      console.log 'find not exists', type, data
       if force
         @log 'Collection', 'debug', 'find forced to load!', type, id
       else
@@ -163,11 +169,11 @@

         # execute callback if record got loaded
         col = @
-        App[type].one 'refresh', ->
+        App[type].one 'refresh', (record) ->
           data = App.Collection.find( type, id )

           # load update to local storage
-          col.load( localStorage: true, type: type, data: data )
+          col.load( localStorage: false, type: type, data: record, refresh: true )

           callback( data )

@@ -176,6 +182,8 @@
         App[type].fetch( id: id )
         return true
       return false
+
+  _fillUp: ( type, data ) ->

    # users
     if type == 'User'
@@ -189,8 +197,8 @@
             data['accounts'][account]['link'] = 'https://www.facebook.com/profile.php?id=' + data['accounts'][account]['uid']

       # set image url
-      if data && !data['image']
-        data['image'] = 'http://placehold.it/48x48'
+      if data && !data.image
+        data.image = 'http://placehold.it/48x48'

       return data


fixed drawing polyline under terrain #40

--- app/assets/javascripts/app/views/tracks/globe_view.js.coffee
+++ app/assets/javascripts/app/views/tracks/globe_view.js.coffee
@@ -49,8 +49,6 @@
       url : '//assets.agi.com/stk-terrain/world'
     )
     @viewer.terrainProvider = terrainProvider
-
-    @viewer.scene.globe.depthTestAgainstTerrain = true;

   setup_viewer_clock: (start, stop) ->
     @viewer.clock.startTime = start.clone()


Remove submitted trial request hipchat msgs

--- server/trial_requests/TrialRequest.coffee
+++ server/trial_requests/TrialRequest.coffee
@@ -38,10 +38,7 @@
       next()

 TrialRequestSchema.post 'save', (doc) ->
-  if doc.get('status') is 'submitted'
-    msg = "Trial Request submitted by #{doc.get('properties')?.email}"
-    hipchat.sendHipChatMessage msg, ['tower']
-  else if doc.get('status') is 'approved'
+  if doc.get('status') is 'approved'
     endDate = new Date()
     endDate.setUTCMonth(endDate.getUTCMonth() + 2)
     emailParams =


I'm a dope

I'm a dope

--- lib/models/synced-fs.coffee
+++ lib/models/synced-fs.coffee
@@ -38,7 +38,7 @@
     @ws.send JSON.stringify({
       action: "local_save",
       project: {
-        path: formatFilePath(project.getPaths()[0])
+        path: this.formatFilePath(project.getPaths()[0])
       },
       file: {
         path: this.formatFilePath(file.path)


fixed bug on pagination

fixed bug on pagination

--- app/assets/javascripts/species/views/taxon_concept_link_view.js.coffee
+++ app/assets/javascripts/species/views/taxon_concept_link_view.js.coffee
@@ -9,7 +9,7 @@
   click: (e) ->
     params =
       taxonomy: @get('controller').get('controllers.search').get('taxonomy')
-      taxon_concept_query: @get('context').toString()
+      taxonConceptQuery: @get('context').toString()
     @get('controller').get('controllers.search')
       .send('redirectToOpenSearchPage', params)


no need to render if not editable

no need to render if not editable

--- resources/assets/coffee/react/beatmapset-page/info.coffee
+++ resources/assets/coffee/react/beatmapset-page/info.coffee
@@ -86,6 +86,10 @@
       @setState isBusy: false

+  withEdit: =>
+     window.currentUser.id == @props.beatmapset.user_id
+
+
   renderEditButton: =>
     div className: 'beatmapset-info__edit-description',
       button
@@ -110,7 +114,7 @@
                 rawValue: @state.description?.bbcode || @props.beatmapset.description.bbcode

       div className: 'beatmapset-info__box beatmapset-info__box--description',
-        @renderEditButton()
+        @renderEditButton() if @withEdit()

         h3
           className: 'beatmapset-info__header'


Draggable: Fixed dragover-scroll event when outside the viewport.

--- src/base/DragDropSelect/Draggable.coffee
+++ src/base/DragDropSelect/Draggable.coffee
@@ -226,7 +226,7 @@

 			# during a dragover scroll, the original target
 			# might be not available any more, we need to recalculate it
-			pointTarget = moveEvent.getPointTarget()
+			pointTarget = moveEvent.getPointTarget() or moveEvent.getTarget()

 			Events.trigger
 				type: "dragover-scroll"


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/number_extensions.coffee
+++ src/number_extensions.coffee
@@ -472,7 +472,7 @@
   Object.defineProperty Number::, 'rotation',
     get: ->
       this * Math.TAU
-
+
 ###*
 Utility method to convert a number to an amount of degrees.


number_extensions

--- src/number_extensions.coffee
+++ src/number_extensions.coffee
@@ -228,10 +228,8 @@

 @name snap
 @methodOf Number#
-
 @param {Number} resolution The grid resolution to snap to.
-@type Number
-@returns The nearest multiple of resolution lower than the number.
+@returns {Number} The nearest multiple of resolution lower than the number.
 ###
 Number::snap = (resolution) ->
   n = this / resolution; 1/1; # This is to fix broken regex in doc parser


Finally drop _.isEmpty.

Finally drop _.isEmpty.

--- src/chaplin/views/collection_view.coffee
+++ src/chaplin/views/collection_view.coffee
@@ -244,8 +244,14 @@
     @filterCallback = filterCallback if filterCallback
     filterCallback ?= @filterCallback

+    hasItemViews = do =>
+      if @subviews.length > 0
+        for name of @subviewsByName when name.slice(0, 9) is 'itemView:'
+          return true
+      false
+
     # Show/hide existing views.
-    unless _.isEmpty @getItemViews()
+    if hasItemViews
       for item, index in @collection.models

         # Apply filter to the item.


Replace fat arrows with thin arrows where possible

--- src/Bacon.coffee
+++ src/Bacon.coffee
@@ -261,7 +261,9 @@
       @value = _.always(valueF)
   isNext: -> true
   hasValue: -> true
-  fmap: (f) -> @apply(=> f(@value()))
+  fmap: (f) ->
+    value = @value
+    @apply(-> f(value()))
   apply: (value) -> new Next(value)
   filter: (f) -> f(@value())
   toString: -> _.toString(@value())
@@ -416,11 +418,10 @@
         if reply == Bacon.noMore
           return reply
       reply
-  scan: (seed, f, lazyF) =>
+  scan: (seed, f, lazyF) ->
     f_ = toCombinator(f)
     f = if lazyF then f_ else (x,y) -> f_(x(), y())
     acc = toOption(seed).map((x) -> _.always(x))
-    root = this
     subscribe = (sink) =>
       initSent = false
       unsub = nop
@@ -433,7 +434,7 @@
             if (reply == Bacon.noMore)
               unsub()
               unsub = nop
-      unsub = @subscribeInternal (event) =>
+      unsub = @subscribeInternal (event) ->
         if (event.hasValue())
           if (initSent && event.isInitial())
             Bacon.more # init already sent, skip this one
@@ -452,7 +453,7 @@
       unsub
     resultProperty = new Property describe(this, "scan", seed, f), subscribe

-  fold: (seed, f) =>
+  fold: (seed, f) ->
     withDescription(this, "fold", seed, f, @scan(seed, f).sampledBy(@filter(false).mapEnd().toProperty()))

   zip: (other, f = Array) ->
@@ -473,10 +474,10 @@
   flatMapFirst: ->
     flatMap_(this, makeSpawner(arguments), true)

-  flatMapLatest: =>
+  flatMapLatest: ->
     f = makeSpawner(arguments)
     stream = @toEventStream()
-    withDescription(this, "flatMapLatest", f, stream.flatMap (value) =>
+    withDescription(this, "flatMapLatest", f, stream.flatMap (value) ->
       makeObservable(f(value)).takeUntil(stream))
   not: -> withDescription(this, "not", @map((x) -> !x))
   log: (args...) ->
@@ -485,7 +486,7 @@
   slidingWindow: (n, minValues = 0) ->
     withDescription(this, "slidingWindow", n, minValues, this.scan([], ((window, value) -> window.concat([value]).slice(-n)))
           .filter(((values) -> values.length >= minValues)))
-  combine: (other, f) =>
+  combine: (other, f) ->
     combinator = toCombinator(f)
     withDescription(this, "combine", other, f,
       Bacon.combineAsArray(this, other)
@@ -499,7 +500,7 @@
         .map(([myValues, otherValues]) -> otherValues.length == 0)
         .toProperty(false).skipDuplicates())

-  name: (name) ->
+  name: (name) ->
     @toString = -> name
     this

@@ -640,7 +641,7 @@

   toEventStream: -> this

-  sampledBy: (sampler, combinator) =>
+  sampledBy: (sampler, combinator) ->
     withDescription(this, "sampledBy", sampler, combinator,
       @toProperty().sampledBy(sampler, combinator))

@@ -655,7 +656,7 @@
           sink(e)
       -> unsubLeft() ; unsubRight()

-  takeUntil: (stopper) =>
+  takeUntil: (stopper) ->
     endMarker = {}
     withDescription(this, "takeUntil", stopper, Bacon.groupSimultaneous(this.mapEnd(endMarker), stopper.skipErrors())
       .withHandler((event) ->
@@ -710,37 +711,37 @@
     else
       @subscribeInternal = new PropertyDispatcher(this, subscribe, handler).subscribe

-    @sampledBy = (sampler, combinator) =>
-      if combinator?
-        combinator = toCombinator combinator
-      else
-        lazy = true
-        combinator = (f) -> f()
-      thisSource = new Source(this, false, false, this.subscribeInternal, lazy)
-      samplerSource = new Source(sampler, true, false, sampler.subscribeInternal, lazy)
-      stream = Bacon.when([thisSource, samplerSource], combinator)
-      result = if sampler instanceof Property then stream.toProperty() else stream
-      withDescription(this, "sampledBy", sampler, combinator, result)
-
     @subscribe = UpdateBarrier.wrappedSubscribe(this)
     registerObs(this)

-  sample: (interval) =>
+  sampledBy: (sampler, combinator) ->
+    if combinator?
+      combinator = toCombinator combinator
+    else
+      lazy = true
+      combinator = (f) -> f()
+    thisSource = new Source(this, false, false, this.subscribeInternal, lazy)
+    samplerSource = new Source(sampler, true, false, sampler.subscribeInternal, lazy)
+    stream = Bacon.when([thisSource, samplerSource], combinator)
+    result = if sampler instanceof Property then stream.toProperty() else stream
+    withDescription(this, "sampledBy", sampler, combinator, result)
+
+  sample: (interval) ->
     withDescription(this, "sample", interval,
       @sampledBy Bacon.interval(interval, {}))

-  changes: => new EventStream describe(this, "changes"), (sink) =>
-    @subscribeInternal (event) =>
+  changes: -> new EventStream describe(this, "changes"), (sink) =>
+    @subscribeInternal (event) ->
       #console.log "CHANGES", event.toString()
       sink event unless event.isInitial()
   withHandler: (handler) ->
     new Property describe(this, "withHandler", handler), @subscribeInternal, handler
-  toProperty: =>
+  toProperty: ->
     assertNoArguments(arguments)
     this
-  toEventStream: =>
+  toEventStream: ->
     new EventStream describe(this, "toEventStream"), (sink) =>
-      @subscribeInternal (event) =>
+      @subscribeInternal (event) ->
         event = event.toNext() if event.isInitial()
         sink event
   and: (other) -> withDescription(this, "and", other, @combine(other, (x, y) -> x && y))
@@ -1161,7 +1162,7 @@
     @subscriptions = []
     @starting = []
     @add s for s in ss
-  add: (subscription) =>
+  add: (subscription) ->
     return if @unsubscribed
     ended = false
     unsub = nop
@@ -1184,10 +1185,10 @@
     s() for s in @subscriptions
     @subscriptions = []
     @starting = []
-  count: =>
+  count: ->
     return 0 if @unsubscribed
     @subscriptions.length + @starting.length
-  empty: =>
+  empty: ->
     @count() == 0

 Bacon.CompositeUnsubscribe = CompositeUnsubscribe


We didn't end up peeking; set this back to cmd

--- lib/operators/general-operators.coffee
+++ lib/operators/general-operators.coffee
@@ -186,8 +186,8 @@
   execute: (count=1) ->
     @undoTransaction =>
       _.times count, =>
-        top = @vimState.history[0]
-        top?.execute()
+        cmd = @vimState.history[0]
+        cmd?.execute()
 #
 # It creates a mark at the current cursor position
 #


Implement delete button for queries in the QueryList

--- src/coffee/cilantro/ui/query/item.coffee
+++ src/coffee/cilantro/ui/query/item.coffee
@@ -21,6 +21,12 @@
             owner: '.owner'
             nonOwner: '.non-owner'

+        events:
+            'click .delete-query': 'deleteQuery'
+
+        deleteQuery: ->
+            @model.destroy({wait: true})
+
         onRender: ->
             if @model.get('is_owner')
                 @ui.nonOwner.hide()


Correctly pick reference type id

Correctly pick reference type id

--- src/coffee/product-type-generator.coffee
+++ src/coffee/product-type-generator.coffee
@@ -108,7 +108,7 @@
         ]
       when ATTRIBUTE_TYPES.reference
         if row['type']
-          type['referenceTypeId'] = @_type(@_typeOrElementType(rawTypeName))
+          type['referenceTypeId'] = row['referenceTypeId']
       when ATTRIBUTE_TYPES.set
         # TODO: ensure set is correctly build
         # e.g.: it will generate a wrong attribute definition for


added basic logic for compression and decompression

--- src/coffee/responsive.breadcrumbs.coffee
+++ src/coffee/responsive.breadcrumbs.coffee
@@ -26,9 +26,9 @@
         onBeforeCompress:       (obj) ->
         onCompress:             (obj) ->
         onAfterCompress:        (obj) ->
-        onBeforeShowCompressed: (obj) ->
-        onShowCompressed:       (obj) ->
-        onAfterShowCompressed:  (obj) ->
+        onBeforeDecompress:     (obj) ->
+        onDecompress:           (obj) ->
+        onAfterDecompress:      (obj) ->
         onBeforeLoad:           (obj) ->
         onLoad:                 (obj) ->
         onAfterLoad:            (obj) ->
@@ -55,16 +55,25 @@

                 if typeof optimalWidth is "undefined"
                     if optimalCrumbHeight isnt o.el.height()
+                        o.opts.onBeforeCompress(_this) if $.isFunction(o.opts.onBeforeCompress)
+
                         o.el
                             .data("#{ pluginName.toLowerCase() }-optimalwidth", (current.width() + o.opts.allowance))
-                            .addClass o.opts.wrappedClass
+                            .addClass(o.opts.wrappedClass)
+                            .trigger "compress.#{ pluginName }"
                 else
                     if current.width() >= optimalWidth
-                        console.log 'remove'
-                        o.el.removeClass o.opts.wrappedClass
+                        o.opts.onBeforeDecompress(_this) if $.isFunction(o.opts.onBeforeDecompress)
+
+                        o.el
+                            .removeClass(o.opts.wrappedClass)
+                            .trigger "decompress.#{ pluginName }"
                     else
-                        console.log 'add'
-                        o.el.addClass o.opts.wrappedClass
+                        o.opts.onBeforeCompress(_this) if $.isFunction(o.opts.onBeforeCompress)
+
+                        o.el
+                            .addClass(o.opts.wrappedClass)
+                            .trigger "compress.#{ pluginName }"

                 return null

@@ -126,6 +135,28 @@
                 _.each resize, (v, i) ->
                     resize[i] = _.debounce(v, o.opts.debounceTime) if $.isFunction(v)

+            o.el.data "#{ pluginName.toLowerCase() }-state", 'decompressed'
+
+            o.el.on "compress.#{ pluginName }", (e) ->
+                current = $ this
+                state   = current.data "#{ pluginName.toLowerCase() }-state"
+
+                current.data "#{ pluginName.toLowerCase() }-state", 'compressed'
+
+                o.opts.onAfterCompress(_this) if $.isFunction(o.opts.onAfterCompress)
+
+                console.log 'compressed'
+
+            o.el.on "decompress.#{ pluginName }", (e) ->
+                current = $ this
+                state   = current.data "#{ pluginName.toLowerCase() }-state"
+
+                current.data "#{ pluginName.toLowerCase() }-state", 'decompressed'
+
+                o.opts.onAfterDecompress(_this) if $.isFunction(o.opts.onAfterDecompress)
+
+                console.log 'decompressed'
+
             ($ window)
                 .on("resize.#{ pluginName }", resize.main)
                 .trigger "resize.#{ pluginName }"


Workspace: Added manageSessions modal to join and share a session.

--- client/landing/app/CommonViews/workspace/collaborativeworkspace.coffee
+++ client/landing/app/CommonViews/workspace/collaborativeworkspace.coffee
@@ -5,9 +5,9 @@
     super options, data

     @sessionData       = []
-    @firepadRef        = new Firebase "https://workspace.firebaseIO.com/"
-    workspaceId        = options.workspaceId or @createSessionKey()
-    @workspaceRef      = @firepadRef.child workspaceId
+    @firepadRef        = new Firebase "https://hulogggg.firebaseIO.com/"
+    @sessionKey        = options.sessionKey or @createSessionKey()
+    @workspaceRef      = @firepadRef.child @sessionKey
     @isNewSession      = no
     @isNeedToSave      = yes

@@ -59,4 +59,67 @@
     u    = KD.utils
     return  "#{nick}:#{u.generatePassword(4)}:#{u.getRandomNumber(100)}"

-  ready: ->+  ready: -> # have to override for collaborative workspace
+
+  showSessionModal: ->
+    modal                       = new KDModalViewWithForms
+      title                     : "Manage Your Session"
+      content                   : ""
+      overlay                   : yes
+      cssClass                  : "manage-session-modal"
+      width                     : 500
+      tabs                      :
+        forms                   :
+          "Share This Session"  :
+            fields              :
+              Label             :
+                itemClass       : KDCustomHTMLView
+                tagName         : "p"
+                partial         : "Share the session ID with your friends to hang out together."
+              SessionKey        :
+                itemClass       : KDCustomHTMLView
+                partial         : @sessionKey
+                tagName         : "div"
+                cssClass        : "key"
+            buttons             :
+              "Ok"              :
+                title           : "Got It"
+                cssClass        : "modal-clean-green"
+                callback        : -> modal.destroy()
+          "Join A Session"      :
+            fields              :
+              Label             :
+                itemClass       : KDCustomHTMLView
+                tagName         : "p"
+                partial         : "Paste the session ID that you want to join and start collaborating."
+              SessionInput      :
+                itemClass       : KDInputView
+                type            : "text"
+            buttons             :
+              Join              :
+                title           : "Join Session"
+                cssClass        : "modal-clean-green"
+                callback        : =>
+                  sessionKey         = modal.modalTabs.forms["Join A Session"].inputs.SessionInput.getValue()
+                  {parent}           = @
+                  options            = @getOptions()
+                  options.sessionKey = sessionKey
+                  @destroy()
+                  parent.addSubView new CollaborativeWorkspace options
+                  modal.destroy()
+                  log "user joined a new session:", sessionKey
+              Close             :
+                title           : "Close"
+                cssClass        : "modal-cancel"
+                callback        : -> modal.destroy()
+          "What is this?"       :
+            fields              :
+              Label             :
+                itemClass       : KDCustomHTMLView
+                tagName         : "p"
+                partial         : "You can share collaborating with your friends easily."
+            buttons             :
+              Close             :
+                title           : "Close"
+                cssClass        : "modal-cancel"
+                callback        : -> modal.destroy()


use didUpdate

use didUpdate

--- app/components/vnc-viewer.coffee
+++ app/components/vnc-viewer.coffee
@@ -24,7 +24,7 @@
       "Pop Out Modal"
   ).property "isPoppedOut"

-  didInsertElement: ->
+  didUpdate: ->
     canvasEl = @element.getElementsByClassName("canvas")[0]
     that = @
     @set "rfb", new RFB


remove generateDummyUserFormData

remove generateDummyUserFormData

--- workers/social/lib/social/models/computeproviders/google.test.coffee
+++ workers/social/lib/social/models/computeproviders/google.test.coffee
@@ -3,8 +3,7 @@

 { expect
   withConvertedUser
-  checkBongoConnectivity
-  generateDummyUserFormData }      = require '../../../../testhelper'
+  checkBongoConnectivity }      = require '../../../../testhelper'
 { withConvertedUserAndCredential } = require '../../../../testhelper/models/computeproviders/credentialhelper'

@@ -21,12 +20,7 @@

     it 'should reply to ping request', (done) ->

-      userFormData = generateDummyUserFormData()
-
-      withConvertedUser { userFormData }, (data) ->
-
-        { client, account } = data
-
+      withConvertedUser ({ client, account }) ->
         Google.ping client, (err, data) ->
           expect(err?.message).to.not.exist
           expect(data).to.be.equal "Google. #{account.profile.nickname}!"


Removed elastic animation when we change thang scale (#675)

* Removed elastic animation when we change thang scale

* Removed redundant vars

--- app/lib/surface/Lank.ozar.coffee
+++ app/lib/surface/Lank.ozar.coffee
@@ -353,16 +353,8 @@
     @sprite.scaleX = @sprite.baseScaleX * @scaleFactorX * scaleX
     @sprite.scaleY = @sprite.baseScaleY * @scaleFactorY * scaleY

-    newScaleFactorX = @thang?.scaleFactorX ? @thang?.scaleFactor ? 1
-    newScaleFactorY = @thang?.scaleFactorY ? @thang?.scaleFactor ? 1
-    if @layer?.name is 'Land' or @thang?.isLand or @thang?.spriteName is 'Beam' or @isCinematicLank
-      @scaleFactorX = newScaleFactorX
-      @scaleFactorY = newScaleFactorY
-    else if @thang and (newScaleFactorX isnt @targetScaleFactorX or newScaleFactorY isnt @targetScaleFactorY)
-      @targetScaleFactorX = newScaleFactorX
-      @targetScaleFactorY = newScaleFactorY
-      createjs.Tween.removeTweens(@)
-      createjs.Tween.get(@).to({scaleFactorX: @targetScaleFactorX, scaleFactorY: @targetScaleFactorY}, 2000, createjs.Ease.elasticOut)
+    @scaleFactorX = @thang?.scaleFactorX ? @thang?.scaleFactor ? 1
+    @scaleFactorY = @thang?.scaleFactorY ? @thang?.scaleFactor ? 1

   updateAlpha: ->
     @sprite.alpha = if @hiding then 0 else 1


Keep talk roles synced up with Panoptes roles

--- app/pages/lab/collaborators.cjsx
+++ app/pages/lab/collaborators.cjsx
@@ -90,6 +90,11 @@
     talkRoles = for role, talkRole of POSSIBLE_ROLES when role in roles
       talkRole

+    talkRoles = talkRoles.reduce(((memo, role) ->
+      memo.push(role) unless role in memo
+      memo
+    ), [])
+
     @setState
       error: null
       creating: true
@@ -133,6 +138,13 @@
     error: null
     saving: []

+  fetchAllRoles: ->
+    Promise.all([@props.project.get('project_roles'), talkClient.type('roles').get(section: @talkSection())])
+      .then ([panoptesRoles, talkRoles]) ->
+        for roleSet in panoptesRoles when roleSet.links.owner.type == 'users'
+          roleSet['talk_roles'] = talkRoles.filter((role) -> role.links.user == roleSet.links.owner.id)
+          roleSet
+
   render: ->

Collaborators

@@ -141,7 +153,7 @@

       {if @state.error?

{@state.error.toString()}
}
-      <PromiseRenderer promise={@props.project.get 'project_roles'} then={(projectRoleSets) =>
+      <PromiseRenderer promise={@fetchAllRoles()} then={(projectRoleSets) =>

           {if projectRoleSets.length > 1
             for projectRoleSet in projectRoleSets
@@ -180,18 +192,24 @@
   toggleRole: (projectRoleSet, role) ->
     index = projectRoleSet.roles.indexOf role

-    if index is -1
-      projectRoleSet.roles.push role
-    else
-      projectRoleSet.roles.splice index, 1
-
     @state.saving.push projectRoleSet.id

     @setState
       error: null
       saving: @state.saving

-    projectRoleSet.update('roles').save()
+    talkRoleAction = if index is -1
+      projectRoleSet.roles.push role
+      talkClient.type('roles').create(
+        user_id: parseInt(projectRoleSet.links.owner.id)
+        section: @talkSection()
+        name: POSSIBLE_ROLES[role]
+      ).save()
+    else
+      projectRoleSet.roles.splice index, 1
+      projectRoleSet.talk_roles.filter((talkRole) -> talkRole.name == POSSIBLE_ROLES[role])[0].delete()
+
+    Promise.all([projectRoleSet.update('roles').save(), talkRoleAction])
       .catch (error) =>
         @setState {error}
       .then =>
@@ -206,7 +224,7 @@
       error: null
       saving: @state.saving

-    projectRoleSet.delete()
+    Promise.all([projectRoleSet.delete(), talkRole.delete() for talkRole in projectRoleSet.talk_roles])
       .catch (error) =>
         @setState {error}

@@ -217,6 +235,9 @@
         @state.saving.splice savingIndex, 1
         @setState saving: @state.saving

+  talkSection: ->
+    "#{@props.project.id}-#{@props.project.display_name}"
+
   handleCollaboratorAddition: ->
     @props.project.uncacheLink 'project_roles'
     @forceUpdate()


Fix #3884. Adds translator role and filter it out from Talk roles.

--- app/pages/lab/collaborators.cjsx
+++ app/pages/lab/collaborators.cjsx
@@ -13,7 +13,8 @@
   expert: 'team',
   scientist: 'scientist',
   moderator: 'moderator',
-  tester: 'team'
+  tester: 'team',
+  translator: 'translator'
 }

 ROLES_INFO =
@@ -104,10 +105,11 @@
           user: id

       newTalkRoleSets = for role in talkRoles
-        talkClient.type('roles').create
-          name: role
-          section: projectSection(@props.project)
-          user_id: id
+        if role != 'translator'
+          talkClient.type('roles').create
+            name: role
+            section: projectSection(@props.project)
+            user_id: id

       memo.concat([newRoleSet]).concat(newTalkRoleSets)
     ), [])
@@ -212,11 +214,12 @@

     talkRoleAction = if index is -1
       projectRoleSet.roles.push role
-      talkClient.type('roles').create(
-        user_id: parseInt(projectRoleSet.links.owner.id)
-        section: @talkSection()
-        name: POSSIBLE_ROLES[role]
-      ).save()
+      if role != 'translator'
+        talkClient.type('roles').create(
+          user_id: parseInt(projectRoleSet.links.owner.id)
+          section: @talkSection()
+          name: POSSIBLE_ROLES[role]
+        ).save()
     else
       projectRoleSet.roles.splice index, 1
       filteredRoles = projectRoleSet.talk_roles.filter (talkRole) ->


Fixed message test

Fixed message test

--- lib/views/login-view.coffee
+++ lib/views/login-view.coffee
@@ -162,7 +162,7 @@
       @unlockUi()
       if e.code == 'ENOTFOUND'
         @errorLabel.text 'Error while connecting to ' + e.hostname
-      else if e == 'invalid_grant'
+      else if e.message == 'invalid_grant'
         @errorLabel.text 'invalid email or password'
       else
         @errorLabel.text e


#7 ensure tail and head always be set at initialization.

--- lib/visual-blockwise.coffee
+++ lib/visual-blockwise.coffee
@@ -55,8 +55,9 @@

   constructor: ->
     super
-    if @isSingle()
-      @clearTail()
+    unless @getTail()?
+      @setTail @getTop()
+      @setHead @getBottom()

   reverse: ->
     [newHead, newTail] = [@getTail(), @getHead()]
@@ -78,9 +79,8 @@
     not @isReversed()

   execute: ->
-    if @isSingle()
-      @setTail @getTop()
-
+    # if @isSingle()
+    #   @setTail @getTop()
     if @isForward()
       @editor["addSelection#{@direction}"]()
       @vimState.syncSelectionsReversedSate @getTail().isReversed()
@@ -141,6 +141,7 @@
   @extend()
   execute: ->
     @clearTail()
+    @clearHead()
     @vimState.activate('normal')
     @editor.clearSelections()


Added resources.dump() and request/response time stamp.

--- lib/zombie/resources.coffee
+++ lib/zombie/resources.coffee
@@ -145,6 +145,7 @@
       url:     url
       headers: options.headers || {}
       params:  options.params
+      time:    Date.now()

     resource =
       request:    request
@@ -163,7 +164,9 @@
         response.statusText = STATUS[response.statusCode] || "Unknown"
         response.headers    ||= {}
         response.redirects  ||= 0
+        response.time       = Date.now()
         resource.response = response
+
         @browser.emit("response", resource)
         callback(null, resource.response)
     return
@@ -223,6 +226,45 @@
   restore: (url)->
     @urlMatchers = @urlMatchers.filter(([match, _])-> match != url)
     return
+
+
+  # Human readable resource listing.  With no arguments, write it to stdout.
+  dump: (output = process.stdout)->
+    for resource in this
+      { request, response, error, target } = resource
+      # Write summary request/response header
+      if response
+        output.write "#{request.method} #{response.url} - #{response.statusCode} #{response.statusText} - #{response.time - request.time}ms\n"
+      else
+        output.write "#{resource.request.method} #{resource.request.url}\n"
+
+      # Tell us which element/document is loading this.
+      if target instanceof HTML.Document
+        output.write "  Loaded as HTML document\n"
+      else if target
+        if target.id
+          output.write "  Loading by element ##{target.id}\n"
+        else
+          output.write "  Loading as #{target.tagName} element\n"
+
+      # If response, write out response headers and sample of document entity
+      # If error, write out the error message
+      # Otherwise, indicate this is a pending request
+      if response
+        if response.redirects
+          output.write "  Followed #{response.redirects} redirects\n"
+        for name, value of response.headers
+          output.write "  #{name}: #{value}\n"
+        output.write "\n"
+        sample = response.body.slice(0, 250).toString("utf8")
+          .split("\n").map((line)-> "  #{line}").join("\n")
+        output.write sample
+      else if error
+        output.write "  Error: #{error.message}\n"
+      else
+        output.write "  Pending since #{new Date(request.time)}\n"
+      # Keep them separated
+      output.write "\n\n"

   # Add a before/after filter.  This filter will only be used by this browser.


Use new bulk method.

Use new bulk method.

--- services/document-updater/app/coffee/RangesManager.coffee
+++ services/document-updater/app/coffee/RangesManager.coffee
@@ -37,8 +37,7 @@
 		{changes, comments} = ranges
 		logger.log {change_id}, "accepting #{ change_ids.length } changes in ranges"
 		rangesTracker = new RangesTracker(changes, comments)
-		for change_id in change_ids
-			rangesTracker.removeChangeId(change_id)
+		rangesTracker.removeChangeIds(change_ids)
 		response = RangesManager._getRanges(rangesTracker)
 		callback null, response


Update RangesTRacker

Update RangesTRacker

--- services/document-updater/app/coffee/RangesTracker.coffee
+++ services/document-updater/app/coffee/RangesTracker.coffee
@@ -55,7 +55,7 @@
 				'0000'.substr(0, 4 - pid.length) + pid

 		@generateId: () ->
-			@generateId() + "000001"
+			@generateIdSeed() + "000001"

 		newId: () ->
 			@id_increment++


clean code

clean code

--- test/test.coffee
+++ test/test.coffee
@@ -37,12 +37,6 @@
     rand.shuffle randomIndice
     return
   return
-
-#describe 'test', ->
-#  it '#1', ->
-#    console.log trie.keysWithinDamerauLevenshteinDistance '天文數字', 2
-#    return
-#  return

 describe 'get(key)', ->
   it 'should get the proper value for each key #1', ->


Mimic actual temp dir detection during tests

This fixes a bug where upon second run of the tests, the temp files would always
be used instead of cleared as desired.

--- test/test.coffee
+++ test/test.coffee
@@ -1,10 +1,12 @@
 fs = require 'fs'
 assert = require 'assert'
 rmrf = require 'rimraf'
+
+tempdir = require '../lib/tempdir'
 seleniumDownload = require '..'

 BIN_PATH = __dirname + '/bin'
-TMP_PATH = '/tmp/testium'
+TMP_PATH = "#{tempdir}testium"

 clearFileSystem = ->
   rmrf.sync TMP_PATH


Fix ROC domains to (0, 1)

Fix ROC domains to (0, 1)

--- src/ext/components/predict-output.coffee
+++ src/ext/components/predict-output.coffee
@@ -24,6 +24,8 @@
            g.strokeColor g.value 'red'
          )
          g.from _.inspect 'Prediction - Thresholds x Metric Scores', prediction
+         g.domainX_HACK 0, 1
+         g.domainY_HACK 0, 1
        )

   for tableName in _.ls prediction


Tests: Removed extra describe, unindented the block.

--- src/tests/components/draggableView/test_draggableView.coffee
+++ src/tests/components/draggableView/test_draggableView.coffee
@@ -32,96 +32,93 @@
     initial = getPositions element

-  describe 'extends', ->
+  it 'should extend spark.core.View', ->
+    expect(draggable instanceof spark.core.View).toBeTruthy()

-    it 'should extend spark.core.View', ->
-      expect(draggable instanceof spark.core.View).toBeTruthy()
+  it 'should have default options', ->
+    d = new spark.components.DraggableView null, null
+
+    expect(d.getOptions()).toBeDefined()

-    it 'should have default options', ->
-      d = new spark.components.DraggableView null, null
+  it 'should be draggable around the page', ->
+    $('#' + domId).simulate 'drag', { dx: 300, dy: 100 }

-      expect(d.getOptions()).toBeDefined()
+    latest = getPositions element
+
+    expect(latest.left - initial.left).toBe 300
+    expect(latest.top -  initial.top).toBe  100

-    it 'should be draggable around the page', ->
-      $('#' + domId).simulate 'drag', { dx: 300, dy: 100 }
+  it 'should have only resize inside the container', ->
+    c = new spark.core.View
+      attributes:
+        style   : 'height: 500px; width: 500px; background: grey;'

-      latest = getPositions element
+    c.appendToDocumentBody()

-      expect(latest.left - initial.left).toBe 300
-      expect(latest.top -  initial.top).toBe  100
+    k = new spark.components.DraggableView
+      container : c
+      domId     : 'dragInContainer'
+      attributes:
+        style   : 'height: 50px; width: 100px; background: red; position: absolute;'
+
+    c.appendView k
+
+    initial = getPositions k.getElement()
+
+    $('#dragInContainer').simulate 'drag', { dx: 800, dy: 800 }
+
+    latest = getPositions k.getElement()
+
+    expect(latest.left - initial.left + 100).toBe 500
+    expect(latest.top  - initial.top  + 50).toBe  500

-    it 'should have only resize inside the container', ->
-      c = new spark.core.View
-        attributes:
-          style   : 'height: 500px; width: 500px; background: grey;'
+  it 'should set container as DOM element', ->

-      c.appendToDocumentBody()
-
-      k = new spark.components.DraggableView
-        container : c
-        domId     : 'dragInContainer'
-        attributes:
-          style   : 'height: 50px; width: 100px; background: red; position: absolute;'
-
-      c.appendView k
-
-      initial = getPositions k.getElement()
-
-      $('#dragInContainer').simulate 'drag', { dx: 800, dy: 800 }
-
-      latest = getPositions k.getElement()
-
-      expect(latest.left - initial.left + 100).toBe 500
-      expect(latest.top  - initial.top  + 50).toBe  500
+    draggable = new spark.components.DraggableView
+      container : document.body
+      domId     : 'dragInDOM'
+      attributes:
+        style   : 'height: 50px; width: 50px; background: red; position: absolute;'

-    it 'should set container as DOM element', ->
-
-      draggable = new spark.components.DraggableView
-        container : document.body
-        domId     : 'dragInDOM'
-        attributes:
-          style   : 'height: 50px; width: 50px; background: red; position: absolute;'
+    draggable.appendToDocumentBody()

-      draggable.appendToDocumentBody()
+    $('#dragInDOM').simulate 'drag', { dx: 8000, dy: 8000 }
+    latest = getPositions draggable.getElement()
+
+    expect(latest.left < 8000).toBeTruthy()
+    expect(latest.top  < 8000).toBeTruthy()

-      $('#dragInDOM').simulate 'drag', { dx: 8000, dy: 8000 }
-      latest = getPositions draggable.getElement()
+  it 'should throw error for invalid container parameter', ->
+    error  = new Error 'Drag container must be a View instance or a DOM element.'

-      expect(latest.left < 8000).toBeTruthy()
-      expect(latest.top  < 8000).toBeTruthy()
+    expect( -> new spark.components.DraggableView container : 2).toThrow error

-    it 'should throw error for invalid container parameter', ->
-      error  = new Error 'Drag container must be a View instance or a DOM element.'
+  it 'should unset container to free the draggable area', ->
+    draggable = new spark.components.DraggableView
+      container : document.body
+      domId     : 'dragEverywhere'
+      attributes:
+        style   : 'height: 50px; width: 50px; background: red; position: absolute;'

-      expect( -> new spark.components.DraggableView container : 2).toThrow error
+    draggable.appendToDocumentBody()

+    $('#dragEverywhere').simulate 'drag', { dx: 8000, dy: 8000 }
+    latest = getPositions draggable.getElement()

-    it 'should unset container to free the draggable area', ->
-      draggable = new spark.components.DraggableView
-        container : document.body
-        domId     : 'dragEverywhere'
-        attributes:
-          style   : 'height: 50px; width: 50px; background: red; position: absolute;'
+    expect(latest.left < 8000).toBeTruthy()
+    expect(latest.top  < 8000).toBeTruthy()

-      draggable.appendToDocumentBody()
+    draggable.unsetContainer()
+    $('#dragEverywhere').simulate 'drag', { dx: 8000, dy: 8000 }
+    latest = getPositions draggable.getElement()

-      $('#dragEverywhere').simulate 'drag', { dx: 8000, dy: 8000 }
-      latest = getPositions draggable.getElement()
-
-      expect(latest.left < 8000).toBeTruthy()
-      expect(latest.top  < 8000).toBeTruthy()
-
-      draggable.unsetContainer()
-      $('#dragEverywhere').simulate 'drag', { dx: 8000, dy: 8000 }
-      latest = getPositions draggable.getElement()
-
-      expect(latest.left > 7800).toBeTruthy()
-      expect(latest.top  > 7800).toBeTruthy()
+    expect(latest.left > 7800).toBeTruthy()
+    expect(latest.top  > 7800).toBeTruthy()