flux/teams: use unblockMember instead of removeBlocked user

--- client/app/lib/flux/teams/actions.coffee
+++ client/app/lib/flux/teams/actions.coffee
@@ -245,8 +245,11 @@
   team = groupsController.getCurrentGroup()

   memberId = member.get '_id'
-
-  team.unblockMember memberId, (err) ->
+  options =
+    id: memberId
+    disable: yes
+
+  team.unblockMember options, (err) ->
     unless err
       fetchMembers().then ->
         fetchMembersRole()
@@ -261,7 +264,11 @@
   team = groupsController.getCurrentGroup()

   memberId = member.get '_id'
-  team.removeBlockedAccount memberId, (err) ->
+  options =
+    id: memberId
+    disable: no
+
+  team.unblockMember options, (err) ->
     reactor.dispatch actions.REMOVE_ENABLED_MEMBER, { memberId }


Remove another verbose log object

Remove another verbose log object

--- services/docstore/app/coffee/DocManager.coffee
+++ services/docstore/app/coffee/DocManager.coffee
@@ -98,7 +98,7 @@
 						update.lines = lines
 					if updateRanges
 						update.ranges = ranges
-					logger.log { project_id, doc_id, oldDoc: doc }, "updating doc lines and ranges"
+					logger.log { project_id, doc_id }, "updating doc lines and ranges"

 					modified = true
 					rev += 1 # rev will be incremented in mongo by MongoManager.upsertIntoDocCollection


Add in null check

Add in null check

--- services/docstore/app/coffee/DocManager.coffee
+++ services/docstore/app/coffee/DocManager.coffee
@@ -63,7 +63,7 @@

 	_findDocInFolder: (folder = {}, doc_id, currentPath) ->
 		for doc, i in folder.docs or []
-			if doc._id.toString() == doc_id.toString()
+			if doc._id? and doc._id.toString() == doc_id.toString()
 				return {
 					doc: doc
 					mongoPath: "#{currentPath}.docs.#{i}"


Update git-plus.coffee

Update git-plus.coffee

Add GitPush handler

--- lib/git-plus.coffee
+++ lib/git-plus.coffee
@@ -2,6 +2,7 @@
 GitWrite = require './git-write'
 GitBranch = require './git-branch'
 GitPull = require './git-pull'
+GitPush = require './git-push'

 module.exports =
   activate: (state) ->
@@ -11,6 +12,7 @@
     atom.workspaceView.command "git-plus:change-branch", -> GitBranch.gitBranches()
     atom.workspaceView.command "git-plus:new-branch", -> GitBranch.newBranch()
     atom.workspaceView.command "git-plus:pull", -> GitPull()
+    atom.workspaceView.command "git-plus:push", -> GitPush()

   deactivate: ->


Refactor activation logic.

Refactor activation logic.

Show init command when there is a non-repo project open.
If there are no projects open, re-activate once a project is added.

--- lib/git-plus.coffee
+++ lib/git-plus.coffee
@@ -90,10 +90,11 @@
     setDiffGrammar()
     @subscriptions = new CompositeDisposable
     repos = atom.project.getRepositories().filter (r) -> r?
-    if repos.length is 0
+    if atom.project.getDirectories().length is 0
       atom.project.onDidChangePaths (paths) => @activate()
+    if repos.length is 0 and atom.project.getDirectories().length > 0
       @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:init', => GitInit().then(@activate)
-    else
+    if repos.length > 0
       contextMenu()
       @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:menu', -> new GitPaletteView()
       @subscriptions.add atom.commands.add 'atom-workspace', 'git-plus:add', -> git.getRepo().then((repo) -> git.add(repo, file: currentFile(repo)))


Add Ren'Py file support

Add Ren'Py file support

Launches Ren'Py on project of current file.

--- lib/grammars.coffee
+++ lib/grammars.coffee
@@ -696,6 +696,11 @@
         else
           args = ['-c', "rebuild '#{progname}.native' && '#{progname}.native'"]
         return args
+
+  "Ren'Py":
+    "File Based":
+      command: "renpy"
+      args: (context) -> [context.filepath.substr(0, context.filepath.lastIndexOf("/"))]

   RSpec:
     "Selection Based":


Removed RealmHandler.join

Removed RealmHandler.join

--- src/wrathnet/expansions/wotlk/handlers/realm-handler.coffee
+++ src/wrathnet/expansions/wotlk/handlers/realm-handler.coffee
@@ -46,16 +46,6 @@

     return @session.auth.send(ap)

-  # Attempts to join given realm
-  join: (realm) ->
-    if realm
-      console.info 'joining realm', realm.name
-
-      @session.world.connect(realm.host, realm.port)
-      return true
-
-    return false
-
   # Realm list refresh handler (REALM_LIST)
   realmList: (ap) ->
     ap.readShort()         # packet-size


Fix align-self cleaning on webkit and webkit 2009

--- lib/prefixes.coffee
+++ lib/prefixes.coffee
@@ -179,6 +179,13 @@
           for prefix in prefixes
             prop = vendor.unprefixed(name)
             olds = @decl(name).old(name, prefix)
+            if name == 'align-self'
+              a = add[name]?.prefixes
+              if a
+                if prefix == '-webkit- 2009' && a.indexOf('-webkit-') != -1
+                  continue
+                else if prefix == '-webkit-' && a.indexOf('-webkit- 2009') != -1
+                  continue
             for prefixed in olds
               remove[prefixed] = {} unless remove[prefixed]
               remove[prefixed].remove = true


Fixing the Armoury map spelling change

Fixing the Armoury map spelling change

--- lib/provider.coffee
+++ lib/provider.coffee
@@ -916,7 +916,7 @@
     { snippet: '"Castle Ruins Map"' },
     { snippet: '"Ashen Wood Map"' },
     { snippet: '"Arachnid Tomb Map"' },
-    { snippet: '"Armory Map"' },
+    { snippet: '"Armoury Map"' },
     { snippet: '"Mud Geyser Map"' },
     { snippet: '"Shaped Beach Map"' },
     { snippet: '"Shaped Ghetto Map"' },
@@ -1002,7 +1002,7 @@
     { snippet: '"Shaped Castle Ruins Map"' },
     { snippet: '"Shaped Ashen Wood Map"' },
     { snippet: '"Shaped Arachnid Tomb Map"' },
-    { snippet: '"Shaped Armory Map"' },
+    { snippet: '"Shaped Armoury Map"' },
     { snippet: '"Shaped Mud Geyser Map"' },
     #Tier 13
     { snippet: '"Lair Map"' },


Remove checkStale call

Remove checkStale call

--- lib/surveyor.coffee
+++ lib/surveyor.coffee
@@ -74,7 +74,6 @@
                 if data.opts.commit isnt model.manifest[item].opts.commit
                   model.prevCommits.put item, model.manifest[item].opts.commit
             frozenManifest = JSON.parse JSON.stringify model.manifest
-            @checkStale frozenManifest, manifest
           #Load in the fresh manifest and we're done
           model.manifest = manifest
           cb errs


Temporarily disable the floating console until dependencies are straightened out

--- tapestry-core/src/main/coffeescript/org/apache/tapestry5/corelib/modulejs/console.coffee
+++ tapestry-core/src/main/coffeescript/org/apache/tapestry5/corelib/modulejs/console.coffee
@@ -26,25 +26,27 @@
   catch e

   display = (className, message) ->
-    # TODO: Dependency on Prototype here

-    unless floatingConsole
-      floatingConsole = new Element "div", class: "t-console"
-      $(document.body).insert top: floatingConsole
+    # Disable the floating console temporarily, since we have to resolve the tangle of dependencies
+    # related to loading vs. the core stack, etc.

-    div = new Element "div", class: "t-console-entry #{className}"
-    div.update(message).hide()
-    floatingConsole.insert top:div
-
-    new Effect.Appear div, duration: .25
-
-    fade = new Effect.Fade div,
-      delay: exports.DURATION
-      afterFinish: -> div.remove()  # was T5.dom.remove(div)
-
-    div.observe "click", ->
-      fade.cancel()
-      div.remove() # was T5.dom.remove(div)
+#    unless floatingConsole
+#      floatingConsole = new Element "div", class: "t-console"
+#      $(document.body).insert top: floatingConsole
+#
+#    div = new Element "div", class: "t-console-entry #{className}"
+#    div.update(message).hide()
+#    floatingConsole.insert top:div
+#
+#    new Effect.Appear div, duration: .25
+#
+#    fade = new Effect.Fade div,
+#      delay: exports.DURATION
+#      afterFinish: -> div.remove()  # was T5.dom.remove(div)
+#
+#    div.observe "click", ->
+#      fade.cancel()
+#      div.remove() # was T5.dom.remove(div)

   level = (className, consolefn) ->
     (message) ->


get current time and decide to send heartbeat or not inside sendHeartbeat function

--- lib/wakatime.coffee
+++ lib/wakatime.coffee
@@ -41,14 +41,11 @@
             buffer.on 'saved', (e) =>
                 file = e.file
                 if file? and file
-                    time = Date.now()
-                    sendHeartbeat(file, time, true)
+                    sendHeartbeat(file, true)
             buffer.on 'changed', (e) =>
                 file = buffer.file
                 if file? and file
-                    time = Date.now()
-                    if enoughTimePassed(time) or lastFile isnt file.path
-                        sendHeartbeat(file, time)
+                    sendHeartbeat(file)

 isPythonInstalled = (callback) ->
     pythonLocation((result) ->
@@ -158,27 +155,29 @@
     if cleanup
         fs.unlink(file)

-sendHeartbeat = (file, time, isWrite) ->
-    pythonLocation((python) ->
-        if python?
-            if not file.path? or file.path is undefined or fileIsIgnored(file.path)
-                return
-            apikey = atom.config.get("wakatime.apikey")
-            unless apikey
-                return
+sendHeartbeat = (file, isWrite) ->
+    time = Date.now()
+    if isWrite or enoughTimePassed(time) or lastFile isnt file.path
+        pythonLocation((python) ->
+            if python?
+                if not file.path? or file.path is undefined or fileIsIgnored(file.path)
+                    return
+                apikey = atom.config.get("wakatime.apikey")
+                unless apikey
+                    return

-            args = [cliLocation(), '--file', file.path, '--key', apikey, '--plugin', 'atom-wakatime/' + VERSION]
-            if isWrite
-                args.push('--write')
-            process.execFile(python, args, (error, stdout, stderr) ->
-                if error?
-                    console.warn error
-                # else
-                #     console.log(args)
-            )
-            lastAction = time
-            lastFile = file.path
-    )
+                args = [cliLocation(), '--file', file.path, '--key', apikey, '--plugin', 'atom-wakatime/' + VERSION]
+                if isWrite
+                    args.push('--write')
+                process.execFile(python, args, (error, stdout, stderr) ->
+                    if error?
+                        console.warn error
+                    # else
+                    #     console.log(args)
+                )
+                lastAction = time
+                lastFile = file.path
+        )

 fileIsIgnored = (file) ->
     patterns = atom.config.get("wakatime.ignore")


Certificates files declared at YML

Certificates files declared at YML

--- lib/zenproxy.coffee
+++ lib/zenproxy.coffee
@@ -117,7 +117,7 @@
   if ZENproxy.protocol is "https"
     certificates = __dirname + "/../../../certificates/"
     https.createServer
-      key   : fs.readFileSync("#{certificates}key.pem")
-      cert  : fs.readFileSync("#{certificates}cert.pem")
+      cert  : fs.readFileSync("#{certificates}#{ZENproxy.cert}")
+      key   : fs.readFileSync("#{certificates}#{ZENproxy.key}")
   else
     http.createServer()


fixed reconnect closes #20

fixed reconnect
closes #20

--- _src/lib/rsmq-worker.coffee
+++ _src/lib/rsmq-worker.coffee
@@ -329,7 +329,7 @@
 		@reconnectActive = false
 		@queue = @_getRsmq( true )
 		@_runOfflineMessages()
-		@interval() if _interval
+		@interval()
 		@warning "redis connection reconnected"
 		return


Add Elixir as matrix column

Add Elixir as matrix column

--- assets/scripts/travis.coffee
+++ assets/scripts/travis.coffee
@@ -135,6 +135,7 @@
     julia:       'Julia'
     csharp:      'C#'
     dart:        'Dart'
+    elixir:      'Elixir'
   }

   QUEUES: [


Deprecate ace.ready event in favor of ready state of kd.Object

--- client/finder/lib/filetree/controllers/nfindertreecontroller.coffee
+++ client/finder/lib/filetree/controllers/nfindertreecontroller.coffee
@@ -60,7 +60,7 @@

     if editor
     then editor.focus()
-    else ace.on 'ace.ready', -> editor.focus()
+    else ace.ready -> editor.focus()

   navigateToNewFile:(newFile)->


Workspace: Implemented user presence firebase node.

--- client/landing/CollaborativeWorkspace/collaborativeworkspace.coffee
+++ client/landing/CollaborativeWorkspace/collaborativeworkspace.coffee
@@ -33,6 +33,7 @@
     @chatRef        = @workspaceRef.child "chat"
     @watchRef       = @workspaceRef.child "watch"
     @usersRef       = @workspaceRef.child "users"
+    @userRef        = @usersRef.child KD.nick()
     @sessionKeysRef = @firebaseRef.child  "session_keys"

   bindRemoteEvents: ->
@@ -57,6 +58,7 @@
         @addToHistory { message: record, by: KD.nick() }
         @watchRef.child(@nickname).set "everybody"
         @sessionKeysRef.child(@nickname).set @sessionKey
+        @userRef.child("status").set "online"

         @loader.destroy()
         @chatView?.show()
@@ -64,19 +66,17 @@
         @emit "WorkspaceSyncedWithRemote"
         @emit "SomeoneJoinedToSession", KD.nick() if isOldSession

-      if @amIHost()
-      then cb()
+      if @amIHost() then cb()
       else
         @pingHost (status) =>
-          return  unless status is "online"
-          cb()
+          cb()  if status is "online"

     @usersRef.on "child_added", (snapshot) =>
       @fetchUsers()

     @usersRef.on "child_changed", (snapshot) =>
       name = snapshot.name()
-      if @amIHost() and snapshot.val() is "offline"
+      if @amIHost() and snapshot.val()?.status is "offline"
         message = "#{name} has left the session"

         @broadcastMessage
@@ -108,6 +108,8 @@

     @watchRef.on "value", (snapshot) =>
       @watchMap = snapshot.val() or {}
+
+    @userRef.child("status").onDisconnect().set "offline"

   fetchUsers: ->
     @workspaceRef.once "value", (snapshot) =>


navigation: item selection fix on main tab pane change

--- client/landing/app/MainApp/maincontroller/mainviewcontroller.coffee
+++ client/landing/app/MainApp/maincontroller/mainviewcontroller.coffee
@@ -41,7 +41,7 @@
     cdController.emit "ContentDisplaysShouldBeHidden"

     @setViewState pane.getOptions()
-    navController.selectItemByName name
+    navController.selectItemByName route.slice(1)

   isEntryPointSet = null


Fix bug in premium level selection code

Fix bug in premium level selection code

--- app/views/play/CampaignView.coffee
+++ app/views/play/CampaignView.coffee
@@ -176,7 +176,7 @@
     if me.level() < 12 and @terrain is 'dungeon' and not @editorMode
       reject = if me.getFourthLevelGroup() is 'signs-and-portents' then 'forgetful-gemsmith' else 'signs-and-portents'
       context.levels = _.reject context.levels, slug: reject
-    if me.isOnFreeOnlyServer
+    if me.isOnFreeOnlyServer()
       context.levels = _.reject context.levels, 'requiresSubscription'
     @annotateLevel level for level in context.levels
     count = @countLevels context.levels


:bug:Fix game-dev-hoc campaign view level totals

--- app/views/play/CampaignView.coffee
+++ app/views/play/CampaignView.coffee
@@ -339,10 +339,18 @@
           level.unlockedInSameCampaign ||= reward.level is level.original
       level.unlockedInSameCampaign = false if level.slug in me.getDungeonLevelsHidden()

-  countLevels: (levels) ->
+  countLevels: (orderedLevels) ->
     count = total: 0, completed: 0
-    for level, levelIndex in levels
-      @annotateLevels(levels) unless level.locked?  # Annotate if we haven't already.
+
+    if @campaign?.get('slug') is 'game-dev-hoc'
+      # HoC: Just order left-to-right instead of looking at unlocks, which we don't use for this copycat campaign
+      orderedLevels = _.sortBy orderedLevels, (level) -> level.position.x
+      count.completed++ for level in orderedLevels when @levelStatusMap[level.slug] is 'complete'
+      count.total = orderedLevels.length
+      return count
+
+    for level, levelIndex in orderedLevels
+      @annotateLevels(orderedLevels) unless level.locked?  # Annotate if we haven't already.
       continue if level.disabled
       completed = @levelStatusMap[level.slug] is 'complete'
       started = @levelStatusMap[level.slug] is 'started'


minor syntax fix before converting to es6

--- app/scripts/services.coffee
+++ app/scripts/services.coffee
@@ -184,13 +184,13 @@
                         return
                     [name, countryCode, lat, lng] = hit.split ";"

-                    points.push (
+                    points.push {
                         abs: actual_hit.abs_freq
                         rel: actual_hit.rel_freq
                         name: name
                         countryCode: countryCode
                         lat : parseFloat lat
-                        lng : parseFloat lng)
+                        lng : parseFloat lng}

                 return (
                     label: label


- removed some console.log entries, way too verbose

--- src/app/modules/core/menuView.coffee
+++ src/app/modules/core/menuView.coffee
@@ -208,8 +208,6 @@
         deferred = $.get(filePath)
         deferredList.push(deferred)
         $.when(deferred).done (fileContent)=>
-          console.log "content recieved"
-          console.log fileContent
           projectFile.content = fileContent

       $.when.apply($, deferredList).done ()=>


Adding 'join' Handlebars helper

Adding 'join' Handlebars helper

--- src/client/handlebars-helpers.coffee
+++ src/client/handlebars-helpers.coffee
@@ -127,29 +127,6 @@
       "#{prefix}-#{__seq++}"

-    ###*
-     * Renders a singular English language noun into its plural form normal results can be overridden by passing in an alternative
-     *
-     *     {{pluralize "activity"}}
-     *     => 'activities'
-     *
-     * @param  {String} string A text string
-     * @return {String}        A processed string
-    ###
-    HandlebarsHelpers.pluralize = (string)->
-      string.pluralize()
-
-    ###*
-     * Renders a plural English language noun into its singular form normal results can be overridden by passing in an alterative
-     *
-     *     {{singularize "activies"}}
-     *     => 'activity'
-     *
-     * @param  {String} string A text string
-     * @return {String}        A processed string
-    ###
-    HandlebarsHelpers.singularize = (string)->
-      string.singularize()

     ###*
      * Renders a lower case underscored word into camel case.
@@ -295,5 +272,27 @@
         buffer.push fn(item)
       buffer.join('')

+    ###*
+     * joins the elements of an array
+     *
+     *     var list = ['a', 'b', 'c']
+     *
+     *     {{join list ","}}
+     *     => a,b,c
+     *
+     * @param  {Array}   array  The Array of objects
+    ###
+
+    HandlebarsHelpers.join = (items, options)->
+      if options && _.isFunction(options.fn)
+        items = _.map items, (i)-> options.fn(i)
+      sep = options.hash['sep'] || ', '
+      last = items.splice(-1) if options.hash['lastSep'] && items.length > 1
+      ret = items.join sep
+      ret += options.hash['lastSep'] + last if last
+      ret
+
+
+

     handlebars.registerHelper(k, v) for k,v of HandlebarsHelpers


hide delay for tooltips increased to 7s, closes #93

--- src/coffee/interceptors/toast.coffee
+++ src/coffee/interceptors/toast.coffee
@@ -22,6 +22,7 @@
         .simple()
         .position('top right')
         .content response.data.message
+        .hideDelay 7000
         .action 'Close'
       $toast.show toast
     response


Add ability to not fade when duration is 0.

--- src/coffee/ux/callout/Callout.coffee
+++ src/coffee/ux/callout/Callout.coffee
@@ -155,10 +155,11 @@

 		@mon( Ext.getDoc(), 'mousedown', @onDocMouseDown, @ )

-		@getEl().setOpacity( 0.0 )
-		@getEl().fadeIn(
-			duration: @getFadeInDuration()
-		)
+		if @getFadeInDuration() > 0
+			@getEl().setOpacity( 0.0 )
+			@getEl().fadeIn(
+				duration: @getFadeInDuration()
+			)
 		return

 	###*
@@ -167,14 +168,17 @@
 	onHide: ( animateTarget, cb, scope ) ->
 		@mun( Ext.getDoc(), 'mousedown', @onDocMouseDown, @ )

-		@getEl().fadeOut(
-			duration: @getFadeOutDuration()
-			callback: ->
-				@getEl().hide()
-				@afterHide( cb, scope )
-				return
-			scope: @
-		)
+		if @getFadeOutDuration() > 0
+			@getEl().fadeOut(
+				duration: @getFadeOutDuration()
+				callback: ->
+					@getEl().hide()
+					@afterHide( cb, scope )
+					return
+				scope: @
+			)
+		else
+			@callParent( arguments )
 		return

 	###*


1) fixed an error with parsing comments

1) fixed an error with parsing comments

--- assets/js/durable-json-lint.coffee
+++ assets/js/durable-json-lint.coffee
@@ -2,15 +2,16 @@
 falafel = if typeof module == 'undefined' then window.falafel else require('free-falafel')
 jsonLint=(src)->
     if !src or /^\s*$/.test(src) then return {json:null, errors:[{lineNumber:1,column:1,description:"An empty string is not valid Json",status:"crash"}]}
-    wrappedSrc = "(function(){return "+src+";})();"
+    wrappedSrc = "(function(){return "+src+";\n})();"
     errors = []
     try
         ast = esprima.parse(wrappedSrc, {range:true, tolerant:true, loc:true, raw:true})
     catch err
         err.status = "crash"
-        if err.index >= wrappedSrc.length-6
-            err.column -= 6
-            err.description = "Invalid Json did you forget a '}'?"
+        if err.index >= wrappedSrc.length-7
+            if err.lineNumber >= wrappedSrc.match(/\r\n?|\n/g).length+1 then err.lineNumber--
+            err.column = 1
+            err.description = "Invalid Json. Did you forget to a close bracket?"
         errors.push(err)
         return {errors:errors,json:null}
     #^(?:-?(?=[1-9]|0(?!\d))\d+(\.\d+)?([eE][+-]?\d+)?|true|false|null|"([^"\\]|(?:\\["\\/bfnrt])|(?:\\u[][0-9a-f]{4}))*")$
@@ -99,7 +100,7 @@

     # do the processing
     res = falafel(wrappedSrc, {ast:ast}, depthFirstFunc, breadthFirstFunc).toString()
-    res = res.substring(19,res.length-6)
+    res = res.substring(19,res.length-7)
     return {json:res, errors:errors}
 #export
 if typeof module == 'undefined' then window.durableJsonLint = jsonLint else module.exports = jsonLint


.

Former-commit-id: dfdc96f6bebf9f496be5c30c1c9fb5399e6a80bd [formerly 5de983c2c8a272859d8491c77ff6602446bb5611] [formerly 5dc76fa0cbe0dbc82238587e5c2addd55dff3277 [formerly b6daca2198d935a621cd6eae563a7577c22b187b]] [formerly 7d2811193b563d76a3df58b62fc36b29e5140765 [formerly 32b10b27a7185a0da06acdc9e694cb1a9094bbd4] [formerly 0c9afc37df624bc79ab3096579712713e4a55d33 [formerly 0c9afc37df624bc79ab3096579712713e4a55d33 [formerly c6d6f2e3b0e67d65729111a8bc198d2474946a32]]]]
Former-commit-id: 116aba08971ccd68d760500f1a791b39002bf47b [formerly fcc00f8164436067d4d2f4cf5ad22d35b69ce0cc] [formerly c7a00cecc6cdd3c8e963b54086af40c8f8accde2 [formerly efad5096f13bee99bdcade889a204b509fa7b193]]
Former-commit-id: 7757054c66cd7173afc13de4cd7ce94279342d3c [formerly 22f98cf70c86fca7e496b8d2916025ad069cf87a]
Former-commit-id: 2753b14ebb1090c8e0cc785bdc74f86b64cfa1c4
Former-commit-id: d6dc7cbbfe479739276b3fe82966266239d41a5f

--- @client/franklin.coffee
+++ @client/franklin.coffee
@@ -1261,13 +1261,13 @@
     points = (pnt for pnt in points when !_.contains(included_points, pnt.key) )

   # Filter down to the points included in the selection opinions, if set.
-  # hist = fetch(namespaced_key('histogram', proposal))
-  # if hist.selected_opinion
-  #   opinions = [hist.selected_opinion]
-  #   filtered = true
-  # else if hist.selected_opinions
-  #   opinions = hist.selected_opinions
-  #   filtered = true
+  hist = fetch(namespaced_key('histogram', proposal))
+  if hist.selected_opinion
+    opinions = [hist.selected_opinion]
+    filtered = true
+  else if hist.selected_opinions
+    opinions = hist.selected_opinions
+    filtered = true

   # order points by resonance to users in view.
   point_inclusions_per_point = {} # map of points to including users


Keep user muted even if he reload his stream.

     Not working on a refresh/deconnexion.

--- src/frontend/modules/userList.coffee
+++ src/frontend/modules/userList.coffee
@@ -24,6 +24,7 @@
         @users       = { }
         @users[window.Voicious.currentUser.uid] = window.Voicious.currentUser
         @users[window.Voicious.currentUser.uid]['isLocal'] = on
+        @users[window.Voicious.currentUser.uid]['volume'] = on
         do @configureEvents
         do @display
         ($ window).on 'resize', () =>
@@ -38,18 +39,19 @@
         @emitter.on 'stream.display', (event, video) =>
             uid = ($ video).attr 'rel'
             @users[uid].video = video
+            video.volume = @users[uid]['volume']
             ($ "li#video_#{uid}").append video

     # Fill the user list with new users.
     fill            : (event, data) =>
         for user in data.peers
-            @users[user.id] = { name : user.name , uid : user.id, 'isLocal' : off }
+            @users[user.id] = { name : user.name , uid : user.id, 'isLocal' : off, volume : on}
         do @display

     # Update the user list by creating or removing a user from the list.
     update          : (event, user) =>
         switch event
-            when 'create' then @users[user.id] = { name : user.name , uid : user.id, 'isLocal' : off }
+            when 'create' then @users[user.id] = { name : user.name , uid : user.id, isLocal : off, volume : on }
             when 'remove' then delete @users[user.id]
         do @display

@@ -69,7 +71,7 @@
         video = ((button.parents 'li.thumbnail-wrapper').find 'video')[0] # get the video tag for the li.
         classI = if (do button.text) is 'mute' then 'icon-microphone' else 'icon-microphone-off'
         text = if (do button.text) is 'mute' then 'unmute' else 'mute'
-        video.volume = !video.volume
+        video.volume = @users[video.getAttribute 'rel']['volume'] = !video.volume
         do button.empty
         button.append "<i class='#{classI}'></i>#{text}"


custom colors: fix advanced play control colors

--- src/javascripts/custom_styles.coffee
+++ src/javascripts/custom_styles.coffee
@@ -51,6 +51,10 @@
     .podcast-player .controls .play-button:hover {
       border-color: #{@progressBarAlt};
       color: #{@progressBarAlt};
+    }
+
+    .podcast-player .controls-advanced button {
+      color: #{@primary};
     }

     .podcast-player .progress-bar .progress-bar-rail {


* ajaxGetScript method

* ajaxGetScript method

--- Bacon.UI.coffee
+++ Bacon.UI.coffee
@@ -37,9 +37,11 @@

 Bacon.UI.ajaxGet = (url, data, dataType) -> Bacon.UI.ajax({url, dataType, data})

-Bacon.UI.ajaxGetJSON = (url, data) -> Bacon.UI.ajax({url, dataType:"json", data})
+Bacon.UI.ajaxGetJSON = (url, data) -> Bacon.UI.ajax({url, dataType: "json", data})

-Bacon.UI.ajaxPost = (url, data, dataType) -> Bacon.UI.ajax({url, dataType, data, type:"POST"})
+Bacon.UI.ajaxPost = (url, data, dataType) -> Bacon.UI.ajax({url, dataType, data, type: "POST"})
+
+Bacon.UI.ajaxGetScript = (url) -> Bacon.UI.ajax({url, dataType: "script"})

 Bacon.Observable::awaiting = (response) ->
   @map(true).merge(response.map(false)).toProperty(false).skipDuplicates()
@@ -62,5 +64,5 @@
 Bacon.UI.hash = (defaultValue) ->
   getHash = ->
     (if !!document.location.hash then document.location.hash else defaultValue)
-  defaultValue = "" if defaultValue is undefined
+  defaultValue = "" if defaultValue is `undefined`
   $(window).asEventStream("hashchange").map(getHash).toProperty(getHash()).skipDuplicates()


Nodejs Web Server: fix coffeelint error

Nodejs Web Server: fix coffeelint error

--- servers/testhelper/handler/confirmhelper.coffee
+++ servers/testhelper/handler/confirmhelper.coffee
@@ -9,7 +9,7 @@

   params =
     qs         : { _csrf : csrfToken, token : 'token' }
-    url        : generateUrl { route : "-/confirm" }
+    url        : generateUrl { route : '-/confirm' }
     csrfCookie : csrfToken

   requestParams = generateRequestParamsEncodeBody params, opts


Remove unneeded -1 substraction from time

--- lib/fuzzy-finder-view.coffee
+++ lib/fuzzy-finder-view.coffee
@@ -25,8 +25,8 @@
     @subscribe $(window), 'focus', => @reloadProjectPaths = true
     @observeConfig 'fuzzy-finder.ignoredNames', => @reloadProjectPaths = true
     atom.workspaceView.eachPane (pane) ->
-      pane.activeItem?.lastOpened = Date.now() - 1
-      pane.on 'pane:active-item-changed', (e, item) -> item.lastOpened = Date.now() - 1
+      pane.activeItem?.lastOpened = Date.now()
+      pane.on 'pane:active-item-changed', (e, item) -> item.lastOpened = Date.now()

     @miniEditor.command 'pane:split-left', =>
       @splitOpenPath (pane, session) -> pane.splitLeft(session)


:lipstick:

:lipstick:

--- lib/snippet-expansion.coffee
+++ lib/snippet-expansion.coffee
@@ -84,7 +84,7 @@
     for markers in @tabStopMarkers
       marker.destroy() for marker in markers
     @tabStopMarkers = []
-    @editor.snippetExpansion = _.without @editor.snippetExpansion, this
+    @editor.snippetExpansion = _.remove @editor.snippetExpansion, this

   restore: (@editor) ->
     @editor.snippetExpansion.push this


Readded method (needed elsewhere)

Readded method (needed elsewhere)

--- core/app/backbone/models/evidence.coffee
+++ core/app/backbone/models/evidence.coffee
@@ -1,4 +1,6 @@
 class window.Evidence extends Backbone.Model
+  positiveImpact: -> @get('impact') >= 0
+
   toJSON: ->
     _.extend super(),
       formatted_impact: format_as_short_number(@get('impact'))


Removed unused require's.

Removed unused require's.

--- server/lib/crawler/staticpages/profile.coffee
+++ server/lib/crawler/staticpages/profile.coffee
@@ -50,11 +50,8 @@
   getStyles  = require './styleblock'
   getGraphMeta  = require './graphmeta'
   {
-    forceTwoDigits
     formatDate
     getFullName
-    getNickname
-    getUserHash
   }          = require '../helpers'

   {profile:{nickname}} = account if account


JPermissionSet: remove old cache mechanism, use fetchGroupAndPermissionSet helper for both MAIN_GROUP and current one

--- workers/social/lib/social/models/group/permissionset.coffee
+++ workers/social/lib/social/models/group/permissionset.coffee
@@ -50,10 +50,6 @@

     super data

-    # Flush mainGroupData cache on update
-    @on 'updateInstance', =>
-      @constructor.mainGroupData = null
-
     return  if @isCustom

     # initialize the permission set with some sane defaults:
@@ -116,17 +112,6 @@
           queue[groupName] = []

-  @fetchMainGroupAndPermissionSet = (callback) ->
-
-    # We do have a in memory cache here for `MAIN_GROUP` which will be
-    # flushed once a JPermissionSet is getting updated ~ GG
-    return callback null, @mainGroupData  if @mainGroupData
-
-    fetchGroupAndPermissionSet MAIN_GROUP, (err, res) =>
-      if err then callback err
-      else callback null, @mainGroupData = res
-
-
   getGroupnameFrom = (target, client) ->
     JGroup = require '../group'
     return if 'function' is typeof target
@@ -176,10 +161,10 @@
     # set groupName from given target or client
     client.groupName = getGroupnameFrom target, client

-    # if it's the main group fetch it from cached helper
+    # if it's the main group fetch only that one
     if client.groupName is MAIN_GROUP

-      @fetchMainGroupAndPermissionSet (err, main) ->
+      fetchGroupAndPermissionSet MAIN_GROUP, (err, main) ->
         if err or not main then callback err, no
         else kallback main, main # pass same group and permissionSet for
                                  # current and the main group ~ GG
@@ -187,10 +172,11 @@
     else

       # fetch permission set for the given group and start checking permissions
-      fetchGroupAndPermissionSet client.groupName, (err, current) =>
+      fetchGroupAndPermissionSet client.groupName, (err, current) ->
         if err or not current then callback err, no
         else
-          @fetchMainGroupAndPermissionSet (err, main) ->
+          # also provide the main group permission set for superadmin checks
+          fetchGroupAndPermissionSet MAIN_GROUP, (err, main) ->
             if err or not main then callback err, no
             else kallback current, main


devilry_subjectadmin: Fixed the Gruntfile.

--- src/devilry_subjectadmin/devilry_subjectadmin/static/devilry_subjectadmin/Gruntfile.coffee
+++ src/devilry_subjectadmin/devilry_subjectadmin/static/devilry_subjectadmin/Gruntfile.coffee
@@ -3,16 +3,16 @@
   grunt.initConfig({
     pkg: grunt.file.readJSON('package.json')
     watch:
-      coffee_edit_feedback:
-        tasks: 'coffee:edit_feedback'
-        files: ['coffee/*.coffee']
+      #coffee_edit_feedback:
+        #tasks: 'coffee:edit_feedback'
+        #files: ['coffee/*.coffee']
       less:
         tasks: 'less'
         files: ['less/*.less', 'less/views/*.less']
-    coffee:
-      edit_feedback:
-        files:
-          'js/edit_feedback.js': 'coffee/edit_feedback.coffee'
+    #coffee:
+      #edit_feedback:
+        #files:
+          #'js/edit_feedback.js': 'coffee/edit_feedback.coffee'
     less:
       development:
         options:
@@ -25,5 +25,6 @@
   grunt.loadNpmTasks('grunt-contrib-coffee')
   grunt.loadNpmTasks('grunt-contrib-less')
   grunt.registerTask('default', [
-    'coffee:editor'
+    'less'
+    #'coffee:editor'
   ])


Quite liking the xml builder

Quite liking the xml builder

--- chrome/content/zotero-opds/zotero-opds.coffee
+++ chrome/content/zotero-opds/zotero-opds.coffee
@@ -108,130 +108,85 @@
       init: (url, data, sendResponseCallback) ->
         updated = Zotero.DB.valueQuery(Zotero.OPDS.sql.index)

-        feed = new Zotero.OPDS.XmlDocument('feed', 'http://www.w3.org/2005/Atom', ->
-          @add('id', '/opds')
-          @add('link', {
-            rel: 'self'
-            href: '/opds'
-            type: 'application/atom+xml;profile=opds-catalog;kind=navigation'
-            })
-          @add('link', {
-            rel: 'start'
-            href: '/opds'
-            type: 'application/atom+xml;profile=opds-catalog;kind=navigation'
-            })
-
-          @add('title', 'Zotero Library')
-          @add('updated', @date(updated))
-          @add('author', ->
-            @add('name', 'Zotero OPDS')
-            @add('uri', 'http://ZotPlus')
-            return)
-
+        feed = new Zotero.OPDS.Feed('/opds', 'Zotero Library', updated, ->
           for collection in Zotero.getCollections()
             @collection(collection)

-          # TODO: add saved searches
           for group in Zotero.Groups.getAll()
             @group(group)

+          # TODO: add saved searches
+          return)
+
         sendResponseCallback(200, "application/atom+xml", feed.serialize())
         return

     item:
       supportedMethods: ["GET"]
       init: (url, data, sendResponseCallback) ->
-        q = url.query.id.split(":")
-        q =
-          group: q.shift()
-          item: q.shift()
-
-        return sendResponseCallback(500, "text/plain", "Unexpected OPDS item " + url.query.id)  if not q.group or not q.item
-        library = ((if q.group == "0" then null else Zotero.Groups.getLibraryIDFromGroupID(q.group)))
-        item = Zotero.Items.getByLibraryAndKey(library, q.item)
-        sendResponseCallback(200, item.attachmentMIMEType, Zotero.File.getBinaryContents(item.getFile()))
+        item = Zotero.Items.getByLibraryAndKey(url.query.library, url.query.key)
+        sendResponseCallback(200, item.attachmentMIMEType || 'application/pdf', Zotero.File.getBinaryContents(item.getFile()))
         return

     group:
       supportedMethods: ["GET"]
       init: (url, data, sendResponseCallback) ->
-        libraryID = Zotero.Groups.getLibraryIDFromGroupID(url.query.id)
-        group = Zotero.Groups.getByLibraryID(libraryID)
-        collections = group.getCollections()
-        updated = Zotero.DB.valueQuery(Zotero.OPDS.sql.group, libraryID)
-
-        kind = url.query.kind || 'navigation'
-
-        doc = new Zotero.OPDS.Feed("Zotero Library Group '#{group.name}'", updated, Zotero.OPDS.buildurl("/opds/group", url.query), kind)
-
-        if kind == 'acquisition'
-          items = (new Zotero.ItemGroup("group", group)).getItems()
-          for item in items or []
-            doc.item(url.query.id, item)
-
-        else
-          for collection in collections or []
-            updated = Zotero.DB.valueQuery(Zotero.OPDS.sql.collection, collection.id) or collection.dateModified
-            doc.entry(collection.name, "/opds/collection?id=#{url.query.id}:#{collection.key}", updated)
-
-          doc.entry('::Items', Zotero.OPDS.buildurl('/opds/group', { id: url.query.id, kind: 'acquisition'}), updated)
-        sendResponseCallback(200, "application/atom+xml", doc.serialize())
+        group = Zotero.Groups.getByLibraryID(url.query.id)
+        updated = Zotero.DB.valueQuery(Zotero.OPDS.sql.group, url.query.id)
+
+        feed = new Zotero.OPDS.Feed("/opds/group?id=#{url.query.id}", "Group: #{group.name}", updated, ->
+          for collection in group.getCollections() || []
+            @collection(collection)
+
+          for item in (new Zotero.ItemGroup('group', group)).getItems() || []
+            @item(item)
+
+          return)
+
+        sendResponseCallback(200, "application/atom+xml", feed.serialize())
         return

     collection:
       supportedMethods: ["GET"]
       init: (url, data, sendResponseCallback) ->
-        query = url.query.id.split(":")
-        q = {}
-        q.group = query.shift()
-        q.collection = query.shift()
-
-        return sendResponseCallback(500, "text/plain", "Unexpected OPDS collection " + url.query.id)  if not q.group or not q.collection
-
-        library = (if q.group == "0" then null else Zotero.Groups.getLibraryIDFromGroupID(q.group))
-        kind = url.query.kind || 'navigation'
-
-        collection = Zotero.Collections.getByLibraryAndKey(library, q.collection)
+        collection = Zotero.Collections.getByLibraryAndKey(url.queryy.library, url.query.key)
         updated = Zotero.DB.valueQuery(Zotero.OPDS.sql.collection, collection.id) or collection.dateModified
-        doc = new Zotero.OPDS.Feed("Zotero Library Collection '#{collection.name}'", updated, Zotero.OPDS.buildurl("/opds/collection", url.query), kind)
-
-        Zotero.OPDS.log("Collection feed type #{kind}")
-        if kind == 'acquisition'
-          items = (new Zotero.ItemGroup('collection', collection)).getItems()
-          for item in items or []
-            doc.item(q.group, item)
-
-        else
+
+        feed = new Zotero.OPDS.Feed("/opds/collection?library=#{url.query.library}&key=#{url.query.key}", "Collection: #{collection.name}", updated, ->
           for collection in collection.getChildCollections() or []
-            updated = Zotero.DB.valueQuery(Zotero.OPDS.sql.collection, collection.id) or collection.dateModified
-            doc.entry(collection.name, "/opds/collection?id=#{q.group}:#{collection.key}")
-          doc.entry('::Items', Zotero.OPDS.buildurl('/opds/collection', { id: url.query.id, kind: 'acquisition'}), updated)
-
-        sendResponseCallback(200, "application/atom+xml", doc.serialize())
+            @collection(collection)
+
+          for item in (new Zotero.ItemGroup('collection', collection)).getItems() || []
+            @item(item)
+
+          return)
+
+        sendResponseCallback(200, "application/atom+xml", feed.serialize())
         return

 class Zotero.OPDS.XmlNode
   constructor: (@doc, @root, @namespace) ->

-  add: (name, content) ->
-    node = @doc.createElementNS(@namespace, name)
-    @root.appendChild(node)
-
-    switch typeof content
-      when 'function'
-        content.call(new Zotero.OPDS.XmlNode(@doc, node, @namespace))
-
-      when 'string'
-        node.appendChild(@doc.createTextNode(content))
-
-      else # assume node with attributes
-        for own k, v of content
-          if k == ''
-            node.appendChild(@doc.createTextNode(v))
-          else
-            node.setAttribute(k, v)
-
-    return node
+  add: (what) ->
+    for own name, content of what
+      node = @doc.createElementNS(@namespace, name)
+      @root.appendChild(node)
+
+      switch typeof content
+        when 'function'
+          content.call(new Zotero.OPDS.XmlNode(@doc, node, @namespace))
+
+        when 'string'
+          node.appendChild(@doc.createTextNode(content))
+
+        else # assume node with attributes
+          for own k, v of content
+            if k == ''
+              node.appendChild(@doc.createTextNode(v))
+            else
+              node.setAttribute(k, v)
+
+    return

   date: (timestamp) ->
     timestamp = Zotero.Date.sqlToDate(timestamp)  if typeof timestamp == "string"
@@ -247,44 +202,72 @@
   serialize: -> Zotero.OPDS.serializer.serializeToString(@doc)

 class Zotero.OPDS.Feed extends Zotero.OPDS.XmlDocument
-  constructor: (content) ->
-    super('feed', 'http://www.w3.org/2005/Atom', content)
+  constructor: (url, title, updated, content) ->
+    super('feed', 'http://www.w3.org/2005/Atom', ->)
+    @add(id: url)
+    @add(link: { rel: 'self', href: url, type: 'application/atom+xml;profile=opds-catalog;kind=navigation' })
+    @add(link: { rel: 'start', href: '/opds', type: 'application/atom+xml;profile=opds-catalog;kind=navigation' })
+    @add(title: title)
+    @add(updated: @date(updated))
+
+    @add(author: ->
+      @add(name: 'Zotero OPDS')
+      @add(uri: 'http://zotplus.github.io/opds')
+      return)
+    content.call(@)

   collection: (collection) ->
     updated = Zotero.DB.valueQuery(Zotero.OPDS.sql.collection, collection.id) or collection.dateModified
-    @add('entry', ->
-      url = "/opds/collection?id=0:#{collection.key}"
-      @add('title', collection.name)
-      @add('link', {
-        rel: 'subsection'
-        href: url
-        type: 'application/atom+xml;profile=opds-catalog;kind=acquisition'
-        })
-      @add('updated', @date(updated))
-      @add('id', url)
-      @add('content', {
-        type: 'text'
-        '': collection.name
-        })
+    @add(entry: ->
+      url = "/opds/collection?library=#{collection.libraryID || 0}&collection=#{collection.key}"
+      @add(title: collection.name)
+      @add(link: { rel: 'subsection', href: url, type: 'application/atom+xml;profile=opds-catalog;kind=acquisition' })
+      @add(updated: @date(updated))
+      @add(id: url)
+      @add(content: { type: 'text', '': collection.name })
       return)
     return

   group: (group) ->
     updated = Zotero.DB.valueQuery(Zotero.OPDS.sql.group, group.id)
-    @add('entry', ->
-      url = "/opds/group?id=#{group.id}"
-      @add('title', group.name)
-      @add('link', {
-        rel: 'subsection'
-        href: url
-        type: 'application/atom+xml;profile=opds-catalog;kind=acquisition'
-        })
-      @add('updated', @date(updated))
-      @add('id', url)
-      @add('content', {
-        type: 'text'
-        '': group.name
-        })
+    libraryID = Zotero.Groups.getLibraryIDFromGroupID(group.id)
+    @add(entry: ->
+      url = "/opds/group?id=#{libraryID}"
+      @add(title: group.name)
+      @add(link: { rel: 'subsection', href: url, type: 'application/atom+xml;profile=opds-catalog;kind=acquisition' })
+      @add(updated: @date(updated))
+      @add(id: url)
+      @add(content: { type: 'text', '': group.name })
+      return)
+    return
+
+  item: (item) ->
+    attachments = []
+    if item.isAttachment()
+      attachments = [item]
+    else
+      attachments = item.getAttachments() or []
+    attachments = (a for a in attachments when a.attachmentMIMEType? != "text/html")
+
+    return if attachments.length == 0
+    @add(entry: ->
+      @add(title: item.getDisplayTitle(true))
+      @add(id: "/opds/item/#{item.libraryID || 0}:#{item.key}")
+      @add(author: ->
+        @add(name: item.firstCreator)
+        return)
+      @add(updated: @date(item.getField('dateModified')))
+
+      abstr = item.getField("abstract")
+      @add(summary: {type: 'text', '': abstr}) if abstr && abstr.length != 0
+
+      for attachment in attachments
+        @add(link: {
+          type: attachment.attachmentMIMEType || 'application/pdf'
+          rel: 'http://opds-spec.org/acquisition'
+          href: "/opds/item?library=#{attachment.libraryID || 0}&key=#{attachment.key}"
+          })
+
       return)
     return


hide progress bar when pdfng has loaded

hide progress bar when pdfng has loaded

--- services/web/public/coffee/ide/pdfng/directives/pdfJs.coffee
+++ services/web/public/coffee/ide/pdfng/directives/pdfJs.coffee
@@ -91,8 +91,8 @@
 						#				flashControls()

 				scope.$on "loaded", () ->
-					# console.log "LOADED"
 					scope.loading = false
+					delete scope.progress

 				#scope.$watch "highlights", (areas) ->
 					# console.log 'got HIGHLIGHTS in pdfJS', areas


remove layout-ready event from pdfjs, we get it from jq layout

--- services/web/public/coffee/ide/pdfng/directives/pdfJs.coffee
+++ services/web/public/coffee/ide/pdfng/directives/pdfJs.coffee
@@ -81,7 +81,6 @@
 						# console.log 'pdfSrc =', url
 						initializePosition()
 						flashControls()
-						scope.$broadcast 'layout-ready'
 						# pdfListView
 						#		.loadPdf(url, onProgress)
 						#		.then () ->


activity: propagate keydown event of input view

--- client/Social/Activity/views/inputview.coffee
+++ client/Social/Activity/views/inputview.coffee
@@ -74,6 +74,8 @@
     if /\W/.test String.fromCharCode event.which
       if @tokenInput and /^\W+$/.test @tokenInput.textContent then @cancel()
       else if @selectToken() then KD.utils.stopDOMEvent event
+
+    return yes

   keyUp: ->
     return  if @getTokens().length >= TOKEN_LIMIT


CustomViews::replaceViews: will destroy sub views first then creates new ones

--- client/app/lib/commonviews/customviews.coffee
+++ client/app/lib/commonviews/customviews.coffee
@@ -2,7 +2,7 @@

 module.exports = class CustomViews

-  MIXINS = ['views', 'addTo', 'addCustomViews']
+  MIXINS = ['views', 'addTo', 'addCustomViews', 'replaceViews']
   @mixin = (target) ->
     target[mixin] = this[mixin] for mixin in MIXINS

@@ -46,5 +46,9 @@
     return map[key]  if length is 1
     return map

-  @addCustomViews = (views)->
+  @addCustomViews = (views) ->
     @addTo this, views
+
+  @replaceViews = (views) ->
+    @destroySubViews()
+    @addTo this, views


Don’t animate the cover when disabling the preloader.

--- framer/Extras/Preloader.coffee
+++ framer/Extras/Preloader.coffee
@@ -97,7 +97,7 @@
 		return unless @isLoading
 		@_end()

-	_end: =>
+	_end: (animated=true) =>

 		Framer.DefaultContext.visible = true

@@ -105,8 +105,8 @@
 			@emit("end")
 			@_isLoading = false
 			@context?.destroy()
-
-		if @cover?.visible
+
+		if @cover?.visible and animated
 			@cover?.animate
 				properties: {opacity: 0}
 				time: 0.13
@@ -179,7 +179,7 @@

 exports.disable = ->
 	return unless Framer.Preloader
-	Framer.Preloader._end()
+	Framer.Preloader._end(false)
 	Framer.Preloader = null

 exports.addImage = (url) ->


collaboration: controller improvements

collaboration: controller improvements

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -143,6 +143,8 @@

   onRealtimeParticipantJoined: (data) ->

+    return  unless @stateMachine.state is 'Active'
+
     {sessionId} = data.collaborator

     {targetUser} =
@@ -160,6 +162,8 @@

   onRealtimeParticipantLeft: (data) ->
+
+    return  unless @stateMachine.state is 'Active'

     {sessionId} = data.collaborator

@@ -632,9 +636,9 @@

     title = @getRealtimeFileName()
     realtimeHelpers.fetchCollaborationFile @rtm, title, (err, file) =>
-      # return callbacks.error err  if err
+      return callbacks.error err  if err
       realtimeHelpers.loadCollaborationFile @rtm, file.id, (err, doc) =>
-        # return callbacks.error err  if err
+        return callbacks.error err  if err
         @rtmFileId = file.id
         callbacks.success @socialChannel, doc

@@ -756,7 +760,9 @@

   cleanupCollaboration: (options = {}) ->

-    return  unless @stateMachine.state is 'Ending'
+    # TODO: remove Active session from here,
+    # we will deffo need a leaving state.
+    return  unless @stateMachine.state in ['Ending', 'Active']

     @rtm.once 'RealtimeManagerWillDispose', =>
       kd.utils.killRepeat @pingInterval


Clean up for coffeescript convention

Clean up for coffeescript convention

--- app/lib/utils.coffee
+++ app/lib/utils.coffee
@@ -1,5 +1,5 @@
 module.exports.clone = (obj) ->
-  return obj if obj is null or typeof (obj) isnt "object"
+  return obj if obj is null or typeof (obj) isnt 'object'
   temp = obj.constructor()
   for key of obj
     temp[key] = module.exports.clone(obj[key])
@@ -25,7 +25,7 @@
   if _.isString(func_thing)
     func = object[func_thing]
     if not func
-      console.error("Could not find method", func_thing, 'in object', @)
+      console.error "Could not find method #{func_thing} in object #{@}"
       return => null # always return a func, or Mediator will go boom
     func_thing = func
   return func_thing
@@ -36,7 +36,7 @@
 hexToR = (h) -> parseInt (cutHex(h)).substring(0, 2), 16
 hexToG = (h) -> parseInt (cutHex(h)).substring(2, 4), 16
 hexToB = (h) -> parseInt (cutHex(h)).substring(4, 6), 16
-cutHex = (h) -> (if (h.charAt(0) is "#") then h.substring(1, 7) else h)
+cutHex = (h) -> (if (h.charAt(0) is '#') then h.substring(1, 7) else h)

 module.exports.hslToHex = (hsl) ->
   '#' + (toHex(n) for n in hslToRgb(hsl...)).join('')
@@ -57,10 +57,10 @@
     if target of locale
       result = locale[target]
     else continue
-    return result if localeName == language
-    generalResult = result if localeName == generalName
-    fallbackResult = result if localeName == fallback
-    fallforwardResult = result if localeName.indexOf(language) == 0 and not fallforwardResult?
+    return result if localeName is language
+    generalResult = result if localeName is generalName
+    fallbackResult = result if localeName is fallback
+    fallforwardResult = result if localeName.indexOf(language) is 0 and not fallforwardResult?

   return generalResult if generalResult?
   return fallforwardResult if fallforwardResult?


Update ja.coffee

Update ja.coffee

Updated misc translations.

--- app/locale/ja.coffee
+++ app/locale/ja.coffee
@@ -270,7 +270,7 @@
 #    tome_help: "Help"
 #    tome_current_method: "Current Method"
     hud_continue_short: "コンテニュー"
-#    code_saved: "Code Saved"
+    code_saved: "コードがセーブされました"
     skip_tutorial: "スキップ (esc)"
 #    keyboard_shortcuts: "Key Shortcuts"
 #    loading_ready: "Ready!"
@@ -374,7 +374,7 @@
     purchasing: "購入中..."
     declined: "カードが拒否されました。"
     retrying: "サーバーエラー、再試行中。"
-    prompt_title: "ジェムが足りません。"
+    prompt_title: "ジェムが不足"
     prompt_body: "もっと購入しますか？"
     prompt_button: "ショップに入る"
     recovered: "前のジェム購入をリカバリーしました。ページを更新してください。"
@@ -432,43 +432,43 @@
     level_to_unlock: "アンロックレベル：" # Label for which level you have to beat to unlock a particular hero (click a locked hero in the store to see)
     restricted_to_certain_heroes: "特定のヒーローのみ、このレベルをプレイする事が出来ます。"

-#  skill_docs:
+  skill_docs:
 #    writable: "writable" # Hover over "attack" in Your Skills while playing a level to see most of this
-#    read_only: "read-only"
-#    action_name: "name"
-#    action_cooldown: "Takes"
-#    action_specific_cooldown: "Cooldown"
-#    action_damage: "Damage"
-#    action_range: "Range"
-#    action_radius: "Radius"
-#    action_duration: "Duration"
-#    example: "Example"
-#    ex: "ex" # Abbreviation of "example"
+    read_only: "リードオンリー"
+    action_name: "ネーム"
+    action_cooldown: "テイク"
+    action_specific_cooldown: "クールダウン"
+    action_damage: "ダメージ"
+    action_range: "レンジ"
+    action_radius: "ラジウス"
+    action_duration: "デュレーション"
+    example: "例"
+    ex: "例" # Abbreviation of "example"
 #    current_value: "Current Value"
 #    default_value: "Default value"
-#    parameters: "Parameters"
-#    returns: "Returns"
+    parameters: "パラメータ"
+    returns: "リターン"
 #    granted_by: "Granted by"

-#  save_load:
-#    granularity_saved_games: "Saved"
-#    granularity_change_history: "History"
+  save_load:
+    granularity_saved_games: "セーブされました"
+    granularity_change_history: "履歴"

   options:
-#    general_options: "General Options" # Check out the Options tab in the Game Menu while playing a level
-#    volume_label: "Volume"
-#    music_label: "Music"
-#    music_description: "Turn background music on/off."
-#    autorun_label: "Autorun"
-#    autorun_description: "Control automatic code execution."
+    general_options: "一般設定" # Check out the Options tab in the Game Menu while playing a level
+    volume_label: "音量"
+    music_label: "音楽"
+    music_description: "BGMをオン/オフ"
+    autorun_label: "オートラン"
+    autorun_description: "自動コード実行の設定"
     editor_config: "エディター設定"
     editor_config_title: "エディターの設定"
-#    editor_config_level_language_label: "Language for This Level"
+    editor_config_level_language_label: "このレベルの言語"
 #    editor_config_level_language_description: "Define the programming language for this particular level."
-#    editor_config_default_language_label: "Default Programming Language"
+    editor_config_default_language_label: "デフォルトプログラミング言語"
 #    editor_config_default_language_description: "Define the programming language you want to code in when starting new levels."
-#    editor_config_keybindings_label: "Key Bindings"
-#    editor_config_keybindings_default: "Default (Ace)"
+    editor_config_keybindings_label: "キーバインディング"
+    editor_config_keybindings_default: "デフォルト(エース)"
 #    editor_config_keybindings_description: "Adds additional shortcuts known from the common editors."
 #    editor_config_livecompletion_label: "Live Autocompletion"
 #    editor_config_livecompletion_description: "Displays autocomplete suggestions while typing."
@@ -547,7 +547,7 @@
   versions:
     save_version_title: "新しいバージョンをセーブ"
     new_major_version: "メジャーバージョンを新しくする"
-#    submitting_patch: "Submitting Patch..."
+    submitting_patch: "パッチを送信中..."
     cla_prefix: "変更を適用するには, 私達のCLAに同意する必要があります。"
     cla_url: "CLA"
 #    cla_suffix: "."
@@ -560,7 +560,7 @@
     forum_page: "こちらのフォーラム"
     forum_suffix: " でお願いします。"
 #    faq_prefix: "There's also a"
-#    faq: "FAQ"
+    faq: "よくある質問(FAQ)"
 #    subscribe_prefix: "If you need help figuring out a level, please"
 #    subscribe: "buy a CodeCombat subscription"
 #    subscribe_suffix: "and we'll be happy to help you with your code."
@@ -577,7 +577,7 @@
     autosave: "変更は自動的にセーブします"
     me_tab: "自分"
     picture_tab: "画像"
-#    upload_picture: "Upload a picture"
+    upload_picture: "画像をアップロード"
     password_tab: "パスワード"
     emails_tab: "メール"
     admin: "管理者"
@@ -591,8 +591,8 @@
 #    email_notifications_summary: "Controls for personalized, automatic email notifications related to your CodeCombat activity."
 #    email_any_notes: "Any Notifications"
 #    email_any_notes_description: "Disable to stop all activity notification emails."
-#    email_news: "News"
-#    email_recruit_notes: "Job Opportunities"
+    email_news: "ニュース"
+    email_recruit_notes: "求人"
 #    email_recruit_notes_description: "If you play really well, we may contact you about getting you a (better) job."
     contributor_emails: "開発を手伝ってくれる人向けのメール"
     contribute_prefix: "私達は開発を手伝ってくれる人を探しています。 詳しくは "
@@ -606,15 +606,15 @@
     job_profile: "求職情報" # Rest of this section (the job profile stuff and wizard stuff) is deprecated
     job_profile_approved: "CodeCombatは、あなたの求職情報を承りました。無効にする、もしくは4週間の間変更をしなければ雇用者はあなたの求職情報を見ることができなくなります。"
 #    job_profile_explanation: "Hi! Fill this out, and we will get in touch about finding you a software developer job."
-#    sample_profile: "See a sample profile"
-#    view_profile: "View Your Profile"
-
-#  keyboard_shortcuts:
-#    keyboard_shortcuts: "Keyboard Shortcuts"
-#    space: "Space"
-#    enter: "Enter"
-#    escape: "Escape"
-#    shift: "Shift"
+    sample_profile: "サンプルプロファイルを参照してください"
+    view_profile: "あなたのプロフィールを表示"
+
+  keyboard_shortcuts:
+    keyboard_shortcuts: "キーボードショートカット"
+    space: "スペース"
+    enter: "エンター"
+    escape: "エスケープ"
+    shift: "シフト"
 #    run_code: "Run current code."
 #    run_real_time: "Run in real time."
 #    continue_script: "Continue past current script."
@@ -648,22 +648,22 @@
 #    contribute_to_the_project: "Contribute to the project"

   classes:
-#    archmage_title: "Archmage"
+    archmage_title: "アークメイジ"
     archmage_title_description: "(コーダー)"
 #    archmage_summary: "If you are a developer interested in coding educational games, become an archmage to help us build CodeCombat!"
-#    artisan_title: "Artisan"
+    artisan_title: "アーチザン"
     artisan_title_description: "(レベルの製作者)"
 #    artisan_summary: "Build and share levels for you and your friends to play. Become an Artisan to learn the art of teaching others to program."
-    adventurer_title: "Adventurer"
+    adventurer_title: "冒険者"
     adventurer_title_description: "(レベルのテストプレイヤー)"
 #    adventurer_summary: "Get our new levels (even our subscriber content) for free one week early and help us work out bugs before our public release."
-#    scribe_title: "Scribe"
+    scribe_title: "代書人"
     scribe_title_description: "(記事の編集者)"
 #    scribe_summary: "Good code needs good documentation. Write, edit, and improve the docs read by millions of players across the globe."
-#    diplomat_title: "Diplomat"
+    diplomat_title: "外交官"
     diplomat_title_description: "(翻訳者)"
 #    diplomat_summary: "CodeCombat is localized in 45+ languages by our Diplomats. Help us out and contribute translations."
-#    ambassador_title: "Ambassador"
+    ambassador_title: "大使"
     ambassador_title_description: "(サポート)"
 #    ambassador_summary: "Tame our forum users and provide direction for those with questions. Our ambassadors represent CodeCombat to the world."


Nginx: added new rules for nginx

Nginx: added new rules for nginx

--- deployment/nginx.coffee
+++ deployment/nginx.coffee
@@ -314,27 +314,31 @@
         resolver 8.8.8.8;
       }

-      location = /Hackathon {
+      # Hackathon2014 is the old hackathon page and served via webserver
+      # todo(cihangir) remove after hubspot integration
+      location = /Hackathon2014 {
         proxy_set_header      X-Real-IP       $remote_addr;
         proxy_set_header      X-Forwarded-For $proxy_add_x_forwarded_for;

         resolver 8.8.8.8;
         proxy_connect_timeout 10;
-        proxy_pass https://teams-koding.hs-sites.com;
-      }
-
-      location = /Hackathon/rules {
+        proxy_pass http://webserver;
+      }
+
+      # mac and windows are case insensitive, redirect lowercased hackathon to
+      # Uppercase Hackathon
+      location ~ "(?-i)/hackathon" {
+        return 301 /Hackathon ;
+      }
+
+      # proxy all Hackathon subpages to hubspot
+      location ~^/Hackathon(.*) {
         proxy_set_header      X-Real-IP       $remote_addr;
         proxy_set_header      X-Forwarded-For $proxy_add_x_forwarded_for;

         resolver 8.8.8.8;
         proxy_connect_timeout 10;
-        proxy_pass https://teams-koding.hs-sites.com/hackathon-rules;
-      }
-
-      # mac and windows are case insensitive
-      location ~ "(?-i)/hackathon" {
-        return 301 /Hackathon ;
+        proxy_pass https://teams-koding.hs-sites.com/Hackathon$1$is_args$args;
       }


Use a taller panel for linter errors

Use a taller panel for linter errors

--- atom/config.cson
+++ atom/config.cson
@@ -26,7 +26,8 @@
     showIconsInEditorGutter: true
   "git-plus": {}
   linter:
-    errorPanelHeight: 26
+    errorPanelHeight: 104
+    showErrorTabLine: true
   "linter-eslint":
     fixOnSave: true
   "linter-rubocop": {}


Clean up some old config cruft

Clean up some old config cruft

--- atom/config.cson
+++ atom/config.cson
@@ -1,13 +1,8 @@
 "*":
-  Pompomodoro:
-    workIntervalLength: 1
   "advanced-new-file":
     suggestCurrentFilePath: true
   "advanced-open-file":
     createFileInstantly: true
-  "atom-beautify":
-    general:
-      _analyticsUserId: "0ad7da6d-8f3a-447c-aa25-b731478a0f71"
   "atom-color-highlight":
     hideMarkersInComments: true
     hideMarkersInStrings: true
@@ -31,6 +26,9 @@
       ]
       "source.ini": [
         ".flowconfig"
+      ]
+      "source.json": [
+        ".compilerc"
       ]
       "source.ruby": [
         "Brewfile"
@@ -56,7 +54,6 @@
       "*.pyc"
       "Thumbs.db"
     ]
-    openEmptyEditorOnStart: false
     projectHome: "/Users/Lee/Source"
     telemetryConsent: "limited"
     themes: [


Atom config again

Atom config again

--- atom/config.cson
+++ atom/config.cson
@@ -2,6 +2,8 @@
   "atom-beautify":
     _loggerLevel: "verbose"
     analytics: false
+    c_configPath: "/home/stephen/.uncrustify.cfg"
+    language_c_beautify_on_save: true
     language_php_beautify_on_save: true
     language_rust_beautify_on_save: true
     php_cs_fixer_path: "/home/stephen/.composer/vendor/bin/php-cs-fixer"
@@ -50,6 +52,7 @@
   editor:
     autoIndentOnPaste: false
     fontFamily: "Input Mono"
+    fontSize: 13
     invisibles: {}
     lineHeight: 1.75
     preferredLineLength: 120
@@ -64,8 +67,17 @@
   "highlight-selected": {}
   "linter-php":
     executablePath: "/home/stephen/.phps/7.0.2/bin/php"
+  "markdown-preview":
+    breakOnSingleNewline: true
+    openPreviewInSplitPane: false
+  "markdown-writer": {}
+  "pdf-view": {}
   tabs:
     enableVcsColoring: true
     usePreviewTabs: true
   whitespace:
     ignoreWhitespaceOnCurrentLine: false
+".md.text":
+  editor:
+    preferredLineLength: 120
+    softWrapHangingIndent: 4


Simplify the grammar settings code even further

Since we're now using standard CoffeeScript object notation, the
code can be simplified even further to make things more modular and
obvious.

--- atom/init.coffee
+++ atom/init.coffee
@@ -6,26 +6,37 @@
 #

 # Settings to assign based on grammar name.
-fileTypes =
-  "GitHub Markdown": (editor) ->
-    editor.setSoftWrap(true)
-    editor.setTabLength(4)
+grammarSettings =
+  'GitHub Markdown':
+    softWrap: true
+    tabLength: 4
+  Java:
+    tabLength: 4
+  Python:
+    tabLength: 4

-  "Java": (editor) ->
-    editor.setTabLength(4)
-
-  "Python": (editor) ->
-    editor.setTabLength(4)
+# Apply the settings to the editor session.
+#
+# editor - Editor to which to apply the settings.
+# settings - The settings to apply.
+applySettingsForGrammar = (editor, settings) ->
+  for key, value of settings
+    switch key
+      when 'softWrap' then editor.setSoftWrap(value)
+      when 'tabLength' then editor.setTabLength(value)

 # Apply grammar-specific settings.
 #
 # editor - Editor to which to apply the grammar-specific settings.
-applyGrammarSettings = (editor) ->
-  func(editor) for name, func of fileTypes when name is editor.getGrammar().name
+applySettings = (editor) ->
+  settings = grammarSettings[editor.getGrammar().name]
+  return unless settings?
+
+  applySettingsForGrammar(editor, settings)

 # Executes for each and every Editor, past and future.
 atom.workspace.eachEditor (editor) ->
-  applyGrammarSettings(editor)
+  applySettings(editor)

   editor.on 'grammar-changed', ->
-    applyGrammarSettings(editor)
+    applySettings(editor)


Add atom keymaps to go to next/previous git diff hunk

--- atom/keymap.cson
+++ atom/keymap.cson
@@ -6,6 +6,9 @@

   'ctrl-|': 'pane:split-right'
   'ctrl--': 'pane:split-down'
+
+  'alt-g j': 'git-diff:move-to-next-diff'
+  'alt-g k': 'git-diff:move-to-previous-diff'

   # Declare keymaps for the commands defined in init.coffee
   'alt-cmd-ctrl-b': 'dot-atom:demo'


charts: lock icon wasn't updated correctly

--- app/assets/javascripts/lib/models/chart_list.coffee
+++ app/assets/javascripts/lib/models/chart_list.coffee
@@ -151,12 +151,7 @@
     $(document).on "click", "a.pick_charts", (e) =>
       holder_id = $(e.target).parents('a').data('chart_holder')
       chart_id = $(e.target).parents('a').data('chart_id')
-
-      chart_settings = App.settings.get('charts')
-      chart_settings[holder_id].chart_id = null
-      chart_settings[holder_id].format = null
-      App.settings.save({charts: chart_settings})
-
+      @remove_pin holder_id
       @load chart_id, holder_id
       close_fancybox()


fix js error

fix js error

--- app/assets/javascripts/sharing_tags/share.js.coffee
+++ app/assets/javascripts/sharing_tags/share.js.coffee
@@ -13,13 +13,15 @@

   class @Share

-    @line: ({title, url}) ->
+    @line: ({title, url, context, network}) ->
       message = "#{title} #{url}"
       @share_popup(
-        network: "line",
-        url: url,
+        network: "line"
+        url: url
         popup_url: "http://line.me/R/msg/text/?#{encodeURIComponent(message)}"
         popup_params: null
+        network: network
+        context: context
       )

     @facebook: ({url, app_id, caption, title, description, image, return_url, provider, network}) ->
@@ -39,12 +41,12 @@

     @vkontakte: ({title, url, description, image, context, network}) ->
       @share_popup(
-        url: url,
-        popup_url: "http://vk.com/share.php",
+        url: url
+        popup_url: "http://vk.com/share.php"
         popup_params:
-          url: url,
-          title: title,
-          description: description,
+          url: url
+          title: title
+          description: description
           image: image
         network: network
         context: context
@@ -52,8 +54,8 @@

     @google: ({url, context, network}) ->
       @share_popup(
-        url: url,
-        popup_url: "https://plus.google.com/share",
+        url: url
+        popup_url: "https://plus.google.com/share"
         popup_params: {url: url}
         network: network
         context: context
@@ -61,9 +63,8 @@

     @odnoklassniki: ({url, description, context, network}) ->
       @share_popup(
-        network: 'odnoklassniki'
-        url: url,
-        popup_url: "http://www.odnoklassniki.ru/dk",
+        url: url
+        popup_url: "http://www.odnoklassniki.ru/dk"
         popup_params: {'st._surl': url, 'st.comments': description, 'st.cmd': 'addShare', 'st.s': 1}
         network: network
         context: context
@@ -71,9 +72,8 @@

     @mailru: ({url, title, image, description, context, network}) ->
       @share_popup(
-        network: 'mailru'
-        url: url,
-        popup_url: 'http://connect.mail.ru/share',
+        url: url
+        popup_url: 'http://connect.mail.ru/share'
         popup_params: {url: url, title: title, description: description, imageurl: image}
         network: network
         context: context
@@ -81,9 +81,8 @@

     @linkedin: ({url, title, description, context, network}) ->
       @share_popup(
-        network: 'linkedin'
-        url: url,
-        popup_url: 'http://www.linkedin.com/shareArticle',
+        url: url
+        popup_url: 'http://www.linkedin.com/shareArticle'
         popup_params: {mini: true, url: url, title: title, summary: description}
         network: network
         context: context


log: minor fix

log: minor fix

--- workers/log/lib/log/models/pagehit.coffee
+++ workers/log/lib/log/models/pagehit.coffee
@@ -28,8 +28,6 @@
       {pathname, query} = (require "url").parse path
       query             = (require "querystring").parse query

-      record.userAgent =
-
       record = _.extend {
         pathname
         query


Fixing autosave bug in non-editable form

When assessors were going through the application, whever they changed
steps, the autosave functionality was being called, and when that
happens and FAILS, the behaviour implemented is to refresh the page.

The fix was to simply check if the page was in read-only mode, and use
the same autosave disabling mechanism as when there's multiple
collaborators.

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -426,6 +426,10 @@
       #
       # Then autosave request should be disabled
       #
+      autosave_enabled = false
+
+    # Assessor viewing form, autosave should be disabled
+    if $(".page-read-only-form").length > 0
       autosave_enabled = false

     if autosave_enabled


this validations are not being used

this validations are not being used

--- app/assets/javascripts/models/user.js.coffee
+++ app/assets/javascripts/models/user.js.coffee
@@ -1,10 +1,4 @@
-Bandango.User = Ember.Object.extend Ember.Validations, Bandango.PersonImagen,
-  validations: {
-    nombres: {
-      presence: true
-    }
-  }
-
+Bandango.User = Ember.Object.extend Bandango.PersonImagen,
   firstNames: (->
     unless nombres = @get("nombres")
       return @get("username")


Increase bullet size

Increase bullet size

--- app/scripts/lazerscripts/stage1/player_clone.coffee
+++ app/scripts/lazerscripts/stage1/player_clone.coffee
@@ -18,8 +18,8 @@
       sightAngle: 20
       projectile: (x, y, angle) =>
         projectile = Crafty.e('Sphere, Enemy, Projectile').attr(
-          w: 8
-          h: 4
+          w: 9
+          h: 9
           speed: 600
         ).blink()
         projectile.shoot(x, y, angle)


Clear the save indicator if there are no questions

This fixes [#58915442]. The save indicator object in JS, which controls the state of the indicator on the page, is cleared on creation, but if no questions were found, it's never created and thus the indicator on the page remains in its initial state (suggesting nothing will be saved, which is technically correct but needlessly alarming). This creates the JS object if it hasn't been already, solely to clear the indicator.

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

--- app/assets/javascripts/save-on-change.coffee
+++ app/assets/javascripts/save-on-change.coffee
@@ -76,6 +76,9 @@

 $(document).ready ->
   window.SaveOnChange = SaveOnChange
-  $('.live_submit').each (i,e) =>
-    new SaveOnChange($(e))
-
+  if $('.live_submit').length
+    $('.live_submit').each (i,e) =>
+      new SaveOnChange($(e))
+  else
+    # construct the indicator so it will enter its clear state
+    indicator = new SaveIndicator($("#save"))


Refreshing US status when modifying task status in US edition

--- app/coffee/modules/userstories/detail.coffee
+++ app/coffee/modules/userstories/detail.coffee
@@ -269,6 +269,7 @@
     link = ($scope, $el, $attrs, $model) ->
         editable = $attrs.editable?
         updatingSelectedRoleId = null
+        $ctrl = $el.controller()

         showSelectPoints = (onClose) ->
             us = $model.$modelValue
@@ -323,6 +324,8 @@
         $scope.$on "related-tasks:update", ->
             us = $scope.$eval $attrs.ngModel
             if us?
+                # Reload the us because the status could have changed
+                $ctrl.loadUs()
                 renderUsstatus(us)

         if editable


fixed model after backend changed

fixed model after backend changed

--- core/app/backbone/models/fact_relation.coffee
+++ core/app/backbone/models/fact_relation.coffee
@@ -7,7 +7,7 @@
           type: type
           evidence_id: @id

-        @set data[0]
+        @set data

       type: "post"


added error stringified to testdiffsizes

--- testApp/app/controllers/drawers.coffee
+++ testApp/app/controllers/drawers.coffee
@@ -327,8 +327,8 @@
   @testDiffSizes: ->
     success = ->
       steroids.logger.log "SUCCESS in testing different sizes for drawers"
-    failure = ->
-      steroids.logger.log "FAILURE in testDiffSizes"
+    failure = (error) ->
+      steroids.logger.log "FAILURE in testDiffSizes: " + JSON.stringify error
       navigator.notification.alert "FAILURE in testDiffSizes"

     steroids.drawers.update {


Selected nodes stay selected unless specified

--- app/scripts/alchemy/core/interactions.coffee
+++ app/scripts/alchemy/core/interactions.coffee
@@ -27,9 +27,9 @@

     nodeMouseOver: (n) ->
         node = alchemy._nodes[n.id]
-
-        node._state = "highlighted"
-        node.setStyles()
+        if node._state != "selected"
+            node._state = "highlighted"
+            node.setStyles()
         if typeof alchemy.conf.nodeMouseOver is 'function'
             alchemy.conf.nodeMouseOver(node)
         else if typeof alchemy.conf.nodeMouseOver is ('number' or 'string')
@@ -39,21 +39,22 @@

     nodeMouseOut: (n) ->
         node = alchemy._nodes[n.id]
-        node._state = "active"
-        node.setStyles()
+        if node._state != "selected"
+            node._state = "active"
+            node.setStyles()
         if alchemy.conf.nodeMouseOut? and typeof alchemy.conf.nodeMouseOut is 'function'
             alchemy.conf.nodeMouseOut(n)

-    nodeClick: (c) ->
+    nodeClick: (n) ->
         d3.event.stopPropagation()
-        # select the correct nodes
-        if !alchemy.vis.select("#node-#{c.id}").empty()
-            selected = alchemy.vis.select("#node-#{c.id}").classed('selected')
-            alchemy.vis.select("#node-#{c.id}").classed('selected', !selected)
+        node = alchemy._nodes[n.id]

+        node._state = do ->
+            return "active" if node._state is "selected"
+            "selected"
+        node.setStyles()
         if typeof alchemy.conf.nodeClick is 'function'
-            alchemy.conf.nodeClick(c)
-            return
+            alchemy.conf.nodeClick(n)

     zoom: (extent) ->
                 if not @._zoomBehavior?


Use new bulk method.

Use new bulk method.

--- app/coffee/RangesManager.coffee
+++ 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


Add FAQ text

Add FAQ text

--- app/controllers/faq-page.coffee
+++ app/controllers/faq-page.coffee
@@ -1,7 +1,67 @@
 Page = require './page'

 content = '''
-
FAQs

+
Project Background

+
+
What is a bryophyte?

+
Bryophytes are a diverse group of early land plants that includes liverworts, hornworts, and mosses.  They are considered to be the living descendents of the first terrestrial plants that evolved here on Earth. While much remains to be discovered about their biodiversity, biology, and geographic distribution, they are widespread throughout the globe and have important evolutionary, ecological, and medicinal characteristics that make them critically important to document, identify, and study.

+
+
Why are liverworts important?

+
Chemical compounds extracted from liverworts exhibit important biological activities.  For example, chemical extracts derived from Frullania species have activity against certain cancer cell lines, and some show significant anti-microbial and anti- fungal activity.

+
Liverworts and related species are also important indicators of air quality and climate change, as they are acutely sensitive to pollution and other environmental factors. Scientists can map potential human health hazards, such as chronic air pollution, by surveying liverwort species diversity and density in a given area.

+
+
What is a liverwort?

+
Liverworts derive their name from old times when early botanists believed that the shape of a plant dictated its function.  Some liverworts have leaves that look, roughly, like small livers, hence they believed these small plants were beneficial medicines for liver troubles.  Today, we know that liverworts are a division of plants…..

+
+
What are Frullania?

+
Frullania is a hyper-diverse genus of liverwort species that includes over 700 species many of which, as of yet, are unclassified and remain to be described!

+
+
Where are these images from?

+
Liverworts grow all over the world, but these Frullania images have typically been collected from the southern hemisphere in areas like Fiji, New Caledonia, and Chile.  Specimens were collected in the field, brought back to the museum, and then imaged with a microscope. Images were taken by interns at the Field Museum from Northeastern Illinois University, DePaul University, Harold Washington Chicago City College, and The Ohio State University. You might be the first person ever to study and analyze a particular image!

+
+
What is a lobule?

+
A lobule is simply a special name for a modified leaf structure.

+
+
How do I benefit from participating?

+
Even through this short exercise, you have contributed to a global biodiversity research program and will have provided scientists with critically needed data to advance the identification and study of these important plants.

+
+
How does my involvement contribute to the project?

+
Scientist will use the data you collect to analyze, catalog and discover new species of early land plants, which are of great evolutionary and ecological significance.

+
+
What is the scientific significance of measuring a lobule?

+
Just like you might be able to tell the difference between your older brother and younger sister from afar because one is taller than the other, we can distinguish between species based on particular defining characteristics.  Lobule size is just one of these characteristics.

+
+
How do you sort through the data?

+
After the data has been collected it will be statistically processed, outliers will be eliminated, and then it will be applied to the taxonomic identification of liverworts.

+
+

+
+
Web Tool

+
+
Oops!  I made a mistake.  How do I undo a mistake?

+
First off, don’t panic!  Simply, select the “X” that appears in the grey box after each measurement to delete that set of measures.  In any event, if you make a mistake, we will be able to sort that out when all the data is collected.

+
+
How do I differentiate a lobule from the rest of the image?

+
Usually the structures in clear focus within the image will be the lobules; it will generally be toward the center of the image.  We will measure only lobules (modified leaves) that are clearly focused and fall entirely within the image.  Here are some pictures to help guide you.

+
+  INSERT IMAGE 1
+
+  INSERT IMAGE 2
+
+
How many lobules are on each image?

+
Each image will have at least one lobule; however, some images will have several.

+
+
The app is not loading, what should I do?

+
The website may be affected by your connection speed.  If you’re able to connect to the internet, check to see if you’re using the latest version of the recommended browser (Google Chrome of Firefox.)

+
+
How is the data recorded?

+
Datum are automatically uploaded to our servers and stored in a database.

+
+
How long do I need to do this activity?

+
We appreciate your help!  Anytime you would like to exit the activity, just click “finish this image” and then please click the link, “fill out a short survey” to fill out a short survey about the project.  We greatly value your feedback.

+
+
Who can I contact with any concerns?

+
If you have any concerns or questions about this project, please feel free to contact Dr. Matt von Konrat (mkonrat@fieldmuseum.org) or Dr. Tom Campbell (T-Campbell7@neiu.edu).

 '''

 class FaqPage extends Page


Auto-merged master into lp-ssh-key-permissions on deployment.

--- app/controllers/settings.coffee
+++ app/controllers/settings.coffee
@@ -10,9 +10,9 @@
     sshKeyDeleted: ->
       @set('model.customSshKey', null)

-    deactivate: ->
-      console.log('deactivate')
-      debugger
+    # deactivate: ->
+    #   console.log('deactivate')
+    #   debugger

 `export default SettingsController`


Fix the handling of non-first languages without an s_id

--- app/assets/javascripts/rglossa/react/components/centre/results/cwb_results_table.jsx.coffee
+++ app/assets/javascripts/rglossa/react/components/centre/results/cwb_results_table.jsx.coffee
@@ -32,16 +32,19 @@
           sId = m[2].trim()
           fields = [m[3], m[4], m[5]]
         else
-          # Try again without the surrounding structural attribue
+          # Try again without the surrounding structural attribute
           m = result.text.match(/(.*){{(.+?)}}(.*)/)
           sId = ''
           fields = [m[1], m[2], m[3]]
       else
         # No {{ was found, so this is a result from a non-first language in a multilingual search
-        m = result.text.match(/<(\w+_id)(.*?)>(.*)<\/\1>?$/)
+        m = result.text.match(/<(\w+_id)(.*?)>(.*)<\/\1>$/)
         if m
           sId = m[2].trim()
           fields = [m[3]]
+        else
+          # Without a surrounding structural attribute we just use the entire text
+          fields = [result.text]

       fields = fields.map (field) ->
         tokens = field.split(/\s+/).map (token) ->


Prevent default action on pagination links

--- app/assets/javascripts/rglossa/react/components/centre/results/results_paginator.jsx.coffee
+++ app/assets/javascripts/rglossa/react/components/centre/results/results_paginator.jsx.coffee
@@ -38,16 +38,20 @@
   handlePageNoClicked: ->
     @refs.pageNoInput.getDOMNode().select()

-  showFirstPage: ->
+  showFirstPage: (e) ->
+    e.preventDefault()
     @setCurrentPageNo(1)

-  showPreviousPage: ->
+  showPreviousPage: (e) ->
+    e.preventDefault()
     @setCurrentPageNo(@props.currentResultPageNo - 1)

-  showNextPage: ->
+  showNextPage: (e) ->
+    e.preventDefault()
     @setCurrentPageNo(@props.currentResultPageNo + 1)

-  showLastPage: ->
+  showLastPage: (e) ->
+    e.preventDefault()
     @setCurrentPageNo(@getNumPages())

   setCurrentPageNo: (pageNo) ->


Clear table on new searches

Clear table on new searches

--- src/scripts/control.search.js.coffee
+++ src/scripts/control.search.js.coffee
@@ -69,6 +69,7 @@

   _search: (query) ->
     @resultMarkers.clearLayers()
+    @datatable.clearTable()

     $.ajax({
       method: 'get'


editor: improve error handling of preview

--- client/Ace/aceappview.coffee
+++ client/Ace/aceappview.coffee
@@ -125,14 +125,19 @@
     return  if /^localfile/.test path
     path = KD.getPublicURLOfPath path

+    notify = =>
+      @getActiveAceView().ace.notify "File needs to be under ~/Web folder", "error"
+
     if path
+      match = path.match /\.kd\.io\/(.*)/
+      return notify()  unless match
+      path = "https://#{KD.nick()}.kd.io/#{match[1]}"
       KD.singleton("appManager").require "Viewer", {path, vmName}, (app) =>
-        path = "https://#{KD.nick()}.kd.io/#{path.match(/.kd.io\/(.*)/)[1]}"
         @tabView.addPane new KDTabPaneView
           name    : "[#{path.split("/").last}]"
           view    : app.getView()
     else
-      @getActiveAceView().ace.notify "File needs to be under ~/Web folder", "error"
+      notify()

   reopenLastSession: ->
     data   = @sessionData


KD: improve isMyPost function with defaults

Just a precaution for property access of undefined properties. Such as `post.account._id` will throw an error if account is not defined.

--- client/Main/KD.extend.coffee
+++ client/Main/KD.extend.coffee
@@ -139,7 +139,10 @@
     else if target?.originId?
       KD.whoami()._id is target.originId

-  isMyPost: (post) -> post.account._id is KD.whoami().getId() and post.typeConstant not in ['join', 'leave']
+  isMyPost: (post) ->
+    post         or= {}
+    post.account or= {}
+    post.account._id is KD.whoami().getId() and post.typeConstant not in ['join', 'leave']

   isMyChannel: (channel) -> channel.creatorId is KD.whoami().socialApiId


Remove reduntant machine check

Remove reduntant machine check

`getLatestWorkspace` function returns a workspace that belongs to
specified machine.

--- client/ide/lib/routes.coffee
+++ client/ide/lib/routes.coffee
@@ -110,12 +110,10 @@
 routeToMachineWorkspace = (machine) ->

   latestWorkspace = getLatestWorkspace machine
-  workspaceSlug   = 'my-workspace'

   if latestWorkspace
-    { machineLabel } = latestWorkspace
-    if machineLabel is machine.label
-      workspaceSlug = latestWorkspace.workspaceSlug
+  then {workspaceSlug} = latestWorkspace
+  else workspaceSlug   = 'my-workspace'

   kd.getSingleton('router').handleRoute "/IDE/#{machine.slug}/#{workspaceSlug}"


client: remove unused debug statement

client: remove unused debug statement

Signed-off-by: Umut Sirin <4055abbc0e40a1254e715b952ccd414607e0b365@gmail.com>

--- client/ide/lib/routes.coffee
+++ client/ide/lib/routes.coffee
@@ -30,7 +30,6 @@
   if showInstance

     { sidebar } = kd.singletons
-    console.log 'showInstance', yes

     sidebar.setSelected 'machineId', machine.getId()


storage: reduce number of rolling snapshots

--- src/smc-hub/rolling_snapshots.coffee
+++ src/smc-hub/rolling_snapshots.coffee
@@ -54,11 +54,11 @@
 exports.update_snapshots = (opts) ->
     opts = defaults opts,
         filesystem : required
-        five       : 12*10  # number of five-minute snapshots to retain
-        hourly     : 24*10   # ...
-        daily      : 60
-        weekly     : 20
-        monthly    : 12
+        five       : 12*6   # 6 hours worth of five-minute snapshots to retain
+        hourly     : 24*7   # 1 week of hourly snapshots
+        daily      : 30     # 1 month of daily snapshots
+        weekly     : 8      # 2 months of weekly snapshots
+        monthly    : 6      # 6 months of monthly snapshots
         cb         : undefined
     dbg = (m) -> winston.debug("snapshot('#{opts.filesystem}'): #{m}")
     dbg()


webapp: remove host info from project settings

--- src/smc-webapp/project_settings.cjsx
+++ src/smc-webapp/project_settings.cjsx
@@ -627,26 +627,6 @@
                 cb()
         ])

-    ssh_notice: ->
-        project_id = @props.project.get('project_id')
-        host = @props.project.get('host')?.get('host')
-        if host?
-            if @state.show_ssh
-

-                    SSH into your project: First add your public key to ~/.ssh/authorized_keys, then use the following username@host:
-                    {### WARNING: previous use of <FormControl> here completely breaks copy on Firefox. ###}
-
{"#{misc.replace_all(project_id, '-', '')}@#{host}.cocalc.com"}

-
-                    <Icon name='life-ring'/> How to create SSH keys
-

-            else
-                <Row>
-                    <Col sm={12}>
-                        <Button bsStyle='info' onClick={=>@setState(show_ssh : true)} style={float:'right'}>
-                            <Icon name='terminal' /> SSH Into Your Project...
-                        </Button>
-                    </Col>
-                </Row>

     render_state: ->

@@ -718,13 +698,6 @@
                 <Icon name='stop' /> Stop Project...
             </Button>
         </ButtonToolbar>
-
-    show_host: ->
-        host = @props.project.get('host')?.get('host')
-        if host
-            <LabeledRow key='host' label='Host'>
-
{host}.sagemath.com

-            </LabeledRow>

     render_idle_timeout_row: ->
         if @props.project.getIn(['state', 'state']) != 'running'
@@ -888,10 +861,8 @@
             <LabeledRow key='project_id' label='Project id'>

{@props.project.get('project_id')}

             </LabeledRow>
-            {@show_host() if @props.allow_ssh}
+            {
 if @props.kucalc != 'yes'}
             {@render_select_compute_image_row()}
-            {
 if @props.allow_ssh}
-            {@ssh_notice() if @props.allow_ssh}
         </ProjectSettingsPanel>

 exports.CollaboratorsList = CollaboratorsList = rclass


webapp: missed a link...

webapp: missed a link...

--- src/smc-webapp/project_settings.cjsx
+++ src/smc-webapp/project_settings.cjsx
@@ -899,7 +899,7 @@

             Use the following username@host:

{addr}

-
+
                 <Icon name='life-ring'/> How to create SSH keys


s/content/text/

s/content/text/

Signed-off-by: Sonmez Kartal <d2d15c3d37396a5f2a09f8b42cf5f27d43a3fbde@koding.com>

--- workers/emailer/lib/emailer/emailerworker.coffee
+++ workers/emailer/lib/emailer/emailerworker.coffee
@@ -82,7 +82,7 @@
       from    : KONFIG.email.defaultFromMail
       to      : to
       subject : template.subject
-      content : template.content
+      text    : template.content

     @log "sending #{type} mail to #{to}..."


KiteSubscription: add method to fetch plan subscriptions

--- workers/social/lib/social/models/kiteplan.coffee
+++ workers/social/lib/social/models/kiteplan.coffee
@@ -55,10 +55,10 @@

   modify: permit 'edit kite plan',
     success:(client, formData, callback)->
-      {kiteId} = formData
+      {kite} = formData
       Relationship.one {
         targetName  : "JKite"
-        targetId    : kiteId
+        targetId    : kite._id
         sourceName  : "JAccount"
         sourceId    : account.getId()
         as          : "owner"
@@ -85,6 +85,16 @@
         @remove (err, res)->
           return  callback err, res

+  # we may change this method after implementing
+  # recurly calls for each subscription.
+  fetchPlanSubscriptions: permit 'fetch plans',
+    success:(client, callback)->
+      Relationship.some {
+        targeName   : "JKitePlan"
+        targetId    : @getId()
+        sourceName  : "JAccount"
+        as          : "kiteSubscription"
+      }, callback


Fix return value if no id given

Fix return value if no id given

--- app/models/DialogManager.coffee
+++ app/models/DialogManager.coffee
@@ -56,8 +56,9 @@

     result.children('.dialog-option').click (event) ->
       that.hideDialog()
-      id = parseInt( $(this).prop('id') )
-      callback(id + 1) if callback
+      id = parseInt( $(this).prop('id') ) + 1
+      id = -1 if id == NaN
+      callback?(id)

     $('#dialog').append(result).


Localise project metadata counts Fixes #2105

--- app/pages/project/metadata.cjsx
+++ app/pages/project/metadata.cjsx
@@ -17,22 +17,22 @@

-
{project.classifiers_count}

+
{project.classifiers_count.toLocaleString()}

Registered Volunteers

-
{project.classifications_count}

+
{project.classifications_count.toLocaleString()}

Classifications

-
{project.subjects_count}

+
{project.subjects_count.toLocaleString()}

Subjects

-
{project.retired_subjects_count}

+
{project.retired_subjects_count.toLocaleString()}

Retired Subjects


Use the osu.src2x for achievement badge as well

--- resources/assets/coffee/react/profile-page/achievement-badge.coffee
+++ resources/assets/coffee/react/profile-page/achievement-badge.coffee
@@ -41,24 +41,14 @@
     $(event.target).qtip options, event

-  filename: (retina = false, suffix) =>
+  iconUrl: (big = false) =>
     filename = "/images/badges/user-achievements/#{@props.achievement.slug}"
-
-    filename += "-#{suffix}" if suffix
-    filename += '@2x' if retina
+    filename += '-big' if big

     "#{filename}.png"

   render: =>
-    if @props.bigIcon
-      src = @filename false, 'big'
-      src2x = @filename true, 'big'
-    else
-      src = @filename()
-      src2x = @filename true
-
-    srcSet = "#{src} 1x, #{src2x} 2x"
     tooltipId = "#{@props.achievement.slug}-#{Math.floor(Math.random() * 1000000)}"
     imageClasses = 'js-tooltip-achievement badge-achievement__image'

@@ -66,14 +56,13 @@

     div
       className: "badge-achievement #{@props.additionalClasses}",
-      img
-        src: src
-        srcSet: srcSet
+      img _.merge
         alt: @props.achievement.name
         title: @props.achievement.name
         className: imageClasses
         'data-tooltip-target': tooltipId
         onMouseOver: @onMouseOver
+        osu.src2x @iconUrl(@props.bigIcon)

       div
         className: 'hidden'
@@ -85,10 +74,9 @@
             @props.achievement.name
           div
             className: 'tooltip-achievement__badge'
-            img
-              src: @filename false, 'big'
-              srcSet: "#{@filename false, 'big'} 1x, #{@filename true, 'big'} 2x"
+            img _.merge
               alt: @props.achievement.name
+              osu.src2x @iconUrl(true)
           div
             className: 'tooltip-achievement__content'
             div


restore popupDownloads=true for pdf downloads

--- services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
+++ services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
@@ -122,6 +122,8 @@
 				# special case for the download url
 				if perUserCompile
 					qs.isolated = true
+				# Save all downloads as files
+				qs.popupDownload = true
 				$scope.pdf.downloadUrl = "/project/#{$scope.project_id}/output/output.pdf" + createQueryString(qs)

 				fetchLogs(fileByPath['output.log'], fileByPath['output.blg'])


added the missing dot

added the missing dot

--- client/landing/app/Applications/Activity.kdapplication/views/tutorialactivityactions.coffee
+++ client/landing/app/Applications/Activity.kdapplication/views/tutorialactivityactions.coffee
@@ -48,7 +48,7 @@
   pistachio:->
     """
     {{> @loader}}
-    {{> @opinionCountLink}} {{> @opinionCount}}
+    {{> @opinionCountLink}} {{> @opinionCount}} ·
     <span class='optional'>
     {{> @shareLink}} ·
     </span>


adapted to new handler syntax

adapted to new handler syntax


Not needed

Not needed

--- apps/artwork/test/client/view-in-room.coffee
+++ apps/artwork/test/client/view-in-room.coffee
@@ -6,7 +6,6 @@
 { fabricate }   = require 'antigravity'

 ViewInRoomView  = benv.requireWithJadeify resolve(__dirname, '../../client/view-in-room'), ['template']
-ViewInRoomView.__set__ 'imagesLoaded', sinon.stub()
 Artwork         = require '../../../../models/artwork'

 describe 'ViewInRoomView', ->


Use Gauges for docs.

Use Gauges for docs.

--- docs/_compile_docs.cjsx
+++ docs/_compile_docs.cjsx
@@ -42,7 +42,7 @@
 putFilesToS3 = require '../deployment/putFilesToS3'
 walkSync = require '../compiler/walkSync'

-{ GoogleAnalytics, ChartbeatStart } = require '../base/analytics'
+{ Gauges } = require '../base/analytics'
 { BuildInfo, makeMetaTags, Asset, Favicon, GoogleFonts } = require '../base'

 console.log 'Compiling docs in', build_directory, for_deploy
@@ -54,7 +54,6 @@

 Base = React.createClass
     render: ->
-        google_analytics_id = null
         <html>
             <head>
                 <title>{ if @props.title then "#{ @props.title } - " else null }Marquee Static SDK</title>
@@ -77,7 +76,7 @@

                 {@props.extra_body}

-                <GoogleAnalytics id=google_analytics_id />
+                <Gauges id=deploy_config.GAUGES_ID />
                 <BuildInfo />
             </body>
         </html>