landing: minor unresolved dependency removal

--- client/landing/site.landing/coffee/teams/teamswaitlistview.coffee
+++ client/landing/site.landing/coffee/teams/teamswaitlistview.coffee
@@ -1,7 +1,6 @@
 CustomLinkView             = require './../core/customlinkview'
 MainHeaderView             = require './../core/mainheaderview'
 JView                      = require './../core/jview'
-FooterView                 = require './../home/footerview'
 TeamsHomeFeaturesSection   = require './partials/teamshomefeaturessection'
 TeamsSignupForm            = require './teamssignupform'
 TeamsLaunchForm            = require './teamslaunchform'


projects changefeed from project

projects changefeed from project

--- src/smc-hub/test/postgres-user-queries-changefeeds-2.coffee
+++ src/smc-hub/test/postgres-user-queries-changefeeds-2.coffee
@@ -266,4 +266,40 @@
         ], done)

-
+describe 'testing a changefeed from a project (instead of account)', ->
+    before(setup)
+    after(teardown)
+
+    it 'makes a projects, has project get a feed and see changes', (done) ->
+        changefeed_id = misc.uuid()
+        accounts = projects = obj = undefined
+        async.series([
+            (cb) ->
+                create_accounts 1, (err, x) -> accounts=x; cb(err)
+            (cb) ->
+                create_projects 1, accounts[0], ((err, v) -> projects = v; cb(err))
+            (cb) ->
+                db.user_query
+                    project_id : projects[0]
+                    query      : {projects:[{project_id:projects[0], title:null, description:null}]}
+                    changes    : changefeed_id
+                    cb         : changefeed_series([
+                        (x, cb) ->
+                            obj = { description: 'Description 0', project_id: projects[0], title: 'Project 0' }
+                            expect(x.projects).toEqual([obj])
+
+                            obj.title = 'Title'; obj.description = 'Description'
+                            db.user_query
+                                project_id : projects[0]
+                                query      : {projects:obj}
+                                cb         : cb
+                        (x, cb) ->
+                            expect(x).toEqual({action:'update', new_val:obj})
+
+                            db.user_query_cancel_changefeed(id:changefeed_id, cb:cb)
+                        (x, cb) ->
+                            expect(x).toEqual({action:'close'})
+
+                            cb()
+                    ], cb)
+        ], done)


Make sure at least one group provided otherwise fallback to koding

--- workers/social/lib/social/models/group/permissionset.coffee
+++ workers/social/lib/social/models/group/permissionset.coffee
@@ -74,7 +74,8 @@
         target.slug
       else
         module = target.constructor.name
-        target.group
+        target.group ? 'koding'
+
     client.groupName = groupName
     JGroup.one {slug: groupName}, (err, group)->
       if err then callback err, no


Remove leftover circle dashboard init code

--- app/assets/javascripts/views/circles/show.coffee
+++ app/assets/javascripts/views/circles/show.coffee
@@ -1,10 +0,0 @@
-ready = ->
-
-  init = ->
-    new Lale.StatusLink('.collection-dashboard .tab.tasks a.task_status', '.collection-dashboard .tab.tasks .task_section')
-    new Lale.StatusLink('.collection-dashboard .tab.supplies a.task_status', '.collection-dashboard .tab.supplies .task_section')
-
-  init()
-
-$(document).on 'ready', ready
-$(document).on 'page:load', ready


fix SubmissionDetailsDialogSpec

fix SubmissionDetailsDialogSpec

fixes intermittent "Error: cannot call methods on
dialog prior to initialization" problems in js tests
caused by calling .dialog('open') on the original element

test plan:
* regression test the submission details dialog

Change-Id: I2f1b5812ad726a0b7b47b0da1ee949d2ad070a67
Reviewed-on: https://gerrit.instructure.com/48705
Tested-by: Jenkins
Reviewed-by: Simon Williams <088e16a1019277b15d58faf0541e11910eb756f6@instructure.com>
QA-Review: Clare Strong <40d6bb3ed7fef516c73274131d5a837c16de74fd@instructure.com>
Product-Review: James Williams  <3a5f4ed991dbfa5c9ef76ad729d240ca12f62180@instructure.com>

--- app/coffeescripts/SubmissionDetailsDialog.coffee
+++ app/coffeescripts/SubmissionDetailsDialog.coffee
@@ -29,38 +29,40 @@
         loading: true
         showPointsPossible: (@assignment.points_possible || @assignment.points_possible == '0') && @assignment.grading_type != "gpa_scale"
       @submission["assignment_grading_type_is_#{@assignment.grading_type}"] = true
-      @dialog = $('
')
-      @dialog.html(submissionDetailsDialog(@submission))
-        .dialog
-          title: @student.name
-          width: 600
-          resizable: false
-          open: @scrollCommentsToBottom

-        .delegate 'select', 'change', (event) =>
-          @dialog.find('.submission_detail').each (index) ->
-            $(this).showIf(index == event.currentTarget.selectedIndex)
-        .delegate '.submission_details_grade_form', 'submit', (event) =>
-          event.preventDefault()
-          $(event.currentTarget.form).disableWhileLoading $.ajaxJSON @url, 'PUT', $(event.currentTarget).getFormData(), (data) =>
-            @update(data)
-            $.publish 'submissions_updated', [@submission.all_submissions]
-            setTimeout =>
-              @dialog.dialog('close')
-            , 500
-        .delegate '.submission_details_add_comment_form', 'submit', (event) =>
-          event.preventDefault()
-          $(event.currentTarget).disableWhileLoading $.ajaxJSON @url, 'PUT', $(event.currentTarget).getFormData(), (data) =>
-            @update(data)
-            setTimeout =>
-              @dialog.dialog('close')
-            , 500
+      @$el = $('
')
+      @$el.html(submissionDetailsDialog(@submission))
+
+      @dialog = @$el.dialog
+        title: @student.name
+        width: 600
+        resizable: false
+
+      @dialog.delegate 'select', 'change', (event) =>
+        @dialog.find('.submission_detail').each (index) ->
+          $(this).showIf(index == event.currentTarget.selectedIndex)
+      .delegate '.submission_details_grade_form', 'submit', (event) =>
+        event.preventDefault()
+        $(event.currentTarget.form).disableWhileLoading $.ajaxJSON @url, 'PUT', $(event.currentTarget).getFormData(), (data) =>
+          @update(data)
+          $.publish 'submissions_updated', [@submission.all_submissions]
+          setTimeout =>
+            @dialog.dialog('close')
+          , 500
+      .delegate '.submission_details_add_comment_form', 'submit', (event) =>
+        event.preventDefault()
+        $(event.currentTarget).disableWhileLoading $.ajaxJSON @url, 'PUT', $(event.currentTarget).getFormData(), (data) =>
+          @update(data)
+          setTimeout =>
+            @dialog.dialog('close')
+          , 500

       deferred = $.ajaxJSON @url+'&include[]=submission_history&include[]=submission_comments&include[]=rubric_assessment', 'GET', {}, @update
       @dialog.find('.submission_details_comments').disableWhileLoading deferred

     open: =>
       @dialog.dialog('open')
+      @scrollCommentsToBottom()
       $('.submission_details_dialog .assignment-name').focus()

     scrollCommentsToBottom: =>


Get rid of un-used method in rules ctrl

Get rid of un-used method in rules ctrl

--- app/assets/javascripts/angular/controllers/rules_ctrl.js.coffee
+++ app/assets/javascripts/angular/controllers/rules_ctrl.js.coffee
@@ -1,6 +1,4 @@
 App.controller 'RulesCtrl', ['$scope', ($scope) ->
   $scope.ruleTypes = () ->
    ['INSTRUMENT_SURVEY_LIMIT_RULE']
-
-  $scope.instrumentSurveyLimitRuleJSON = () ->
 ]


Issue #196 - Youtube plugin should embed video

--- app/assets/javascripts/backbone/plugins/youtube_embed.js.coffee
+++ app/assets/javascripts/backbone/plugins/youtube_embed.js.coffee
@@ -6,9 +6,10 @@

     template: _.template '''

-        <a target="_blank" class="youtube-preview-link" href="<%= videoUrl %>">
-          <img class="youtube-preview-image" src="<%= thumbUrl %>" />
-
+            <iframe width="560" height="315"
+                    src="http://www.youtube.com/embed/<%= videoId %>"
+                    frameborder="0" allowfullscreen>
+

<%= subtitle %>

     '''
@@ -27,6 +28,7 @@
         videoUrl = message.content.split(" ")[0]

       videoId = message.content.match(@options.idRegex)[1]
+
       thumbUrl = "http://img.youtube.com/vi/#{ videoId }/0.jpg"

       subtitle = null
@@ -34,8 +36,7 @@
       subtitle ||= videoUrl

       message.content = @options.template({
-        videoUrl: videoUrl,
-        thumbUrl: thumbUrl,
+        videoId: videoId
         subtitle: subtitle
       })
       return Kandan.Helpers.Activities.buildFromMessageTemplate(message)


Add marker to google maps

Add marker to google maps

--- app/assets/javascripts/embeditor/adapters/google_maps.js.coffee
+++ app/assets/javascripts/embeditor/adapters/google_maps.js.coffee
@@ -17,12 +17,14 @@
         match = @_parseUrl()
         return false if not match

-        latlng  = match[1].split(',')
-        lat     = parseFloat(latlng[0])
-        lng     = parseFloat(latlng[1])
+        points  = match[1].split(',')
+        lat     = parseFloat(points[0])
+        lng     = parseFloat(points[1])
+
+        latLng = new google.maps.LatLng(lat, lng)

         mapOpts =
-            center      : new google.maps.LatLng(lat, lng)
+            center      : latLng
             zoom        : 10
             mapTypeId   : google.maps.MapTypeId.ROADMAP

@@ -31,4 +33,9 @@
             maxheight       : @queryParams.maxheight
             maxwidth        : @queryParams.maxwidth

-        new google.maps.Map($(".#{GoogleMaps.MapTarget}", @wrapper)[0], mapOpts)
+        map = new google.maps.Map(
+            $(".#{GoogleMaps.MapTarget}", @wrapper)[0], mapOpts)
+
+        marker = new google.maps.Marker
+            map         : map
+            position    : latLng


Do not pass the current time when requesting all feeds from the angularjs client.

Passing the current time as a parameter prevents the client from using HTTP caching properly.

--- app/assets/javascripts/ng-services/ng-FeedsFoldersSvc.js.coffee
+++ app/assets/javascripts/ng-services/ng-FeedsFoldersSvc.js.coffee
@@ -24,8 +24,7 @@
     feedsPaginationSvc.set_busy true

     page += 1
-    now = new Date()
-    $http.get("/api/feeds.json?include_read=#{$rootScope.show_read}&page=#{page}&time=#{now.getTime()}")
+    $http.get("/api/feeds.json?include_read=#{$rootScope.show_read}&page=#{page}")
     .success (data)->
       feedsPaginationSvc.load_feeds_page page, data.slice()
       feedsPaginationSvc.set_busy false


Workspace: Recovered AllPanesAddedToPane event for single pane workspaces.

--- client/app/CommonViews/workspace/panel/panel.coffee
+++ client/app/CommonViews/workspace/panel/panel.coffee
@@ -47,7 +47,9 @@
       cssClass     : "panel-container"

     if pane
-      @container.addSubView @createPane pane
+      newPane = @createPane pane
+      @container.addSubView newPane
+      @getDelegate().emit "AllPanesAddedToPanel", @, [newPane]
     else if layout
       @container.addSubView new WorkspaceLayout
         delegate      : @


invalidate cache entries on all item changes

--- chrome/content/zotero-better-bibtex/zotero-better-bibtex.coffee
+++ chrome/content/zotero-better-bibtex/zotero-better-bibtex.coffee
@@ -254,11 +254,11 @@
     @flash('Citation key rescan', "Scanning 'extra' fields for fixed keys\nFor a large library, this might take a while")
     patched = []
     for row in Zotero.DB.query(@findKeysSQL) or []
-      patched.push('' + row.itemID)
+      patched.push(row.itemID)
       @log('scan:', row)
       @keymanager.set(row, @keymanager.extract({extra: row.extra}).__citekey__)
     if patched.length > 0
-      for row in Zotero.DB.query("select * from betterbibtex.keys where citeKeyFormat is null and itemID not in (#{patched.join(', ')})")
+      for row in Zotero.DB.query("select * from betterbibtex.keys where citeKeyFormat is null and itemID not in #{@SQLSet(patched)}")
         @keymanager.remove(row)
     @pref.set('scan-citekeys', false)

@@ -421,16 +421,17 @@
         item.save()

     unless collections.length == 0 || Zotero.BetterBibTeX.pref.get('auto-export') == 'disabled'
-      collections = ('' + id for id in collections).join(',')
-      Zotero.DB.query("update betterbibtex.autoexport set status = 'pending' where collection_id in (#{collections})")
+      Zotero.DB.query("update betterbibtex.autoexport set status = 'pending' where collection_id in #{Zotero.BetterBibTeX.SQLSet(collections)}")
       Zotero.BetterBibTeX.auto.process('collectionChanged')

     return
 }

 Zotero.BetterBibTeX.collectionChanged = notify: (event, type, ids, extraData) ->
-  Zotero.DB.query("delete from betterbibtex.autoexport where collection_id in (#{('' + id for id in extraData).join(',')})") if event == 'delete' && extraData.length > 0
-  return
+  Zotero.DB.query("delete from betterbibtex.autoexport where collection_id in #{Zotero.BetterBibTeX.SQLSet(extraData)}") if event == 'delete' && extraData.length > 0
+  return
+
+Zotero.BetterBibTeX.SQLSet = (values) -> '(' + ('' + v for v in values).join(', ') + ')'

 Zotero.BetterBibTeX.itemChanged = notify: (event, type, ids, extraData) ->
   Zotero.BetterBibTeX.keymanager.reset()
@@ -438,18 +439,23 @@

   switch event
     when 'delete', 'trash'
-      for id in (if event == 'delete' then extraData else ids) || []
-        Zotero.BetterBibTeX.keymanager.remove({itemID: id})
-      collections = Zotero.Collections.getCollectionsContainingItems(extraData, true)
+      ids = extraData if event == 'delete'
+      if ids.length > 0
+        for id in ids
+          Zotero.BetterBibTeX.keymanager.remove({itemID: id})
+        collections = Zotero.Collections.getCollectionsContainingItems(ids, true)
+        Zotero.DB.query("delete from betterbibtex.cache where itemID in #{Zotero.BetterBibTeX.SQLSet(ids)}")

     when 'add', 'modify'
       break if ids.length == 0
+
+      Zotero.DB.query("delete from betterbibtex.cache where itemID in #{Zotero.BetterBibTeX.SQLSet(ids)}")

       collections = Zotero.Collections.getCollectionsContainingItems(ids, true)
       for id in ids
         Zotero.BetterBibTeX.keymanager.remove({itemID: id})

-      for item in Zotero.DB.query("#{Zotero.BetterBibTeX.findKeysSQL} and i.itemID in (#{('' + id for id in ids).join(',')})") or []
+      for item in Zotero.DB.query("#{Zotero.BetterBibTeX.findKeysSQL} and i.itemID in #{Zotero.BetterBibTeX.SQLSet(ids)}") or []
         citekey = Zotero.BetterBibTeX.keymanager.extract(item).__citekey__
         Zotero.BetterBibTeX.keymanager.set(item, citekey)
         Zotero.DB.query('delete from betterbibtex.cache where citekey = ?', [citekey])
@@ -458,8 +464,7 @@
         Zotero.BetterBibTeX.keymanager.get({itemID: id}, 'on-change')

   unless collections.length == 0 || Zotero.BetterBibTeX.pref.get('auto-export') == 'disabled'
-    collections = ('' + id for id in collections).join(',')
-    Zotero.DB.query("update betterbibtex.autoexport set status = 'pending' where collection_id in (#{collections})")
+    Zotero.DB.query("update betterbibtex.autoexport set status = 'pending' where collection_id in #{Zotero.BetterBibTeX.SQLSet(collections)}")
     Zotero.BetterBibTeX.auto.process('itemChanged')

   return


Finder: Controller: remove deprecated updateMountState

--- client/finder/lib/filetree/controllers/nfindercontroller.coffee
+++ client/finder/lib/filetree/controllers/nfindercontroller.coffee
@@ -120,8 +120,6 @@
     if @getMachineNode uid
       return kd.warn "Machine #{machine.getName()} is already mounted!"

-    @updateMountState uid, yes
-
     @machines.push FSHelper.createFileInstance
       name           : path
       path           : "[#{uid}]#{path}"
@@ -159,7 +157,6 @@
     machineItem = @getMachineNode uid
     return kd.warn 'No such Machine!'  unless machineItem

-    @updateMountState uid, no
     @stopWatching machineItem.data.path
     FSHelper.unregisterMachineFiles uid
     @treeController.removeNodeView machineItem
@@ -308,18 +305,6 @@
   setReadOnly: (state) -> @treeController.isReadOnly = state

-  # Settings helpers
-  #
-  updateMountState: (uid, state) ->
-
-    return  if isGuest()
-
-    machines = @appStorage.getValue('mountedMachines') or {}
-    machines[uid] = state
-
-    @appStorage.setValue 'mountedMachines', machines
-
-
   # FS Watcher helpers
   #
   registerWatcher: (path, stopWatching) ->


Better names

Better names

--- client/landing/app/MainApp/avatararea/avatarareaiconmenu.coffee
+++ client/landing/app/MainApp/avatararea/avatarareaiconmenu.coffee
@@ -28,7 +28,7 @@

     @messagesIcon = new AvatarAreaIconLink
       cssClass   : 'messages'
-      testPath   : "avatar-messages"
+      testPath   : "avatararea-messages-icon"
       attributes :
         title    : 'Messages'
       delegate   : @messagesPopup


Fix jquery-timepicker-addon require.

Fix jquery-timepicker-addon require.

--- app/assets/javascripts/back.js.coffee
+++ app/assets/javascripts/back.js.coffee
@@ -14,8 +14,8 @@
 #= require moment-timezone/moment-timezone-with-data
 #= require fullcalendar
 #= require fullcalendar/lang/fr
-#= require jQuery-Timepicker-Addon/src/jquery-ui-timepicker-addon
-#= require jQuery-Timepicker-Addon/src/i18n/jquery-ui-timepicker-fr
+#= require jQuery-Timepicker-Addon/jquery-ui-timepicker-addon
+#= require jQuery-Timepicker-Addon/i18n/jquery-ui-timepicker-fr
 #= require pwstrength-bootstrap
 #= require back/tag-it.min.js
 #= require bootstrap


Fixed line chart

Fixed line chart

--- app/assets/javascripts/d3/line.coffee
+++ app/assets/javascripts/d3/line.coffee
@@ -9,30 +9,36 @@

     can_be_shown_as_table: -> true

+    margins :
+      top: 20
+      bottom: 50
+      left: 20
+      right: 70
+
     draw: =>
-      margins =
-        top: 20
-        bottom: 50
-        left: 20
-        right: 50
+      @width = @available_width() - (@margins.left + @margins.right)
+      @height = @available_height() - (@margins.top + @margins.bottom)

-      @width = @available_width() - (margins.left + margins.right)
-      # height of the series section
-      @series_height = 190
-      @height = @series_height + margins.top + margins.bottom + 20 + (@series.length / 2 * 15)
+      legend_columns = 2
+      legend_rows = @model.series.length / legend_columns
+      legend_height = legend_rows * @legend_cell_height
+      legend_margin = 20
+
+      @series_height = @height - legend_height - legend_margin
+
       @svg = d3.select(@container_selector())
         .append("svg:svg")
-        .attr("height", @height + margins.top + margins.bottom)
-        .attr("width", @width + margins.left + margins.right)
+        .attr("height", @height + @margins.top + @margins.bottom)
+        .attr("width", @width + @margins.left + @margins.right)
         .append("svg:g")
-        .attr("transform", "translate(#{margins.left}, #{margins.top})")
+        .attr("transform", "translate(#{@margins.left}, #{@margins.top})")

       @draw_legend
         svg: @svg
         series: @series
         width: @width
-        vertical_offset: @series_height + 25
-        columns: 2
+        vertical_offset: @series_height + legend_margin
+        columns: legend_columns

       @x = d3.scale.linear().range([0, @width])
         .domain([@start_year, @end_year])


Clarity on promises

Clarity on promises

--- app/assets/javascripts/home.js.coffee
+++ app/assets/javascripts/home.js.coffee
@@ -14,7 +14,7 @@
 SeriesService = ($http) ->
   return {
     list: ->
-      return $http.get('/series.json').then (response) -> response.data
+      return promise = $http.get('/series.json').then (response) -> response.data
   }

 formatWiki = ->


Make accordion size match content

Make accordion size match content

--- app/assets/javascripts/home.js.coffee
+++ app/assets/javascripts/home.js.coffee
@@ -43,8 +43,9 @@
     $('#header').removeClass('move-header')

   $ ->
-    $("#sidebar-accordion").accordion()
-    return
+    $("#sidebar-accordion").accordion heightStyle: "content"
+  return
+

   $ ->
     $("#tabs").tabs()


Fix unread state not updating right away

--- app/assets/javascripts/main.js.coffee
+++ app/assets/javascripts/main.js.coffee
@@ -234,7 +234,9 @@
     path += '?in_thread=true'
     path += '&newsgroup=' + encodeURIComponent(newsgroup) + '&number=' + number
     abort_active_scroll()
-    after_func = -> $('#posts_list').scroll()
+    after_func = ->
+      reset_check_timeout(0)
+      $('#posts_list').scroll()
     $('#posts_list tr[data-thread="' + thread_id + '"]').removeClass('unread')
   else if newsgroup and scope == 'newsgroup'
     path += '?newsgroup=' + encodeURIComponent(newsgroup)
@@ -255,6 +257,7 @@
       after_func = -> window.onhashchange()
     else
       abort_active_scroll()
+      $('#posts_list tbody tr').removeClass('unread')
       after_func = -> $('#posts_list').scroll()

   $.ajaxScript 'PUT', path, after_func


Fix type error if there is a different location hash

--- app/assets/javascripts/tree.js.coffee
+++ app/assets/javascripts/tree.js.coffee
@@ -40,12 +40,12 @@
   # "#L12" and "#L34-56" supported
   highlightBlobLines = ->
     if window.location.hash isnt ""
-      matches = window.location.hash.match /\#L(\d+)(\-(\d+))?/
-      first_line = parseInt matches[1]
-      last_line = parseInt matches[3]
+      matches = window.location.hash.match(/\#L(\d+)(\-(\d+))?/)
+      first_line = parseInt(matches?[1])
+      last_line = parseInt(matches?[3])

       unless isNaN first_line
-        last_line = first_line if isNaN last_line
+        last_line = first_line if isNaN(last_line)
         $("#tree-content-holder .highlight .line").removeClass("hll")
         $("#LC#{line}").addClass("hll") for line in [first_line..last_line]
         $("#L#{first_line}").ScrollTo()


sending all events to particles

sending all events to particles

--- src/timed_events.coffee
+++ src/timed_events.coffee
@@ -12,7 +12,7 @@
     everyEvents: []

   self.bind "update", (elapsedTime) ->
-    for event,
+    for event in I.everyEvents

   ###*
   Execute fn every n frames.


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/timed_events.coffee
+++ src/timed_events.coffee
@@ -1,4 +1,4 @@
 TimedEvents = (I={}) ->
-  every: (n) ->
+  every: (n, fn) ->
     if I.age.mod(n) is 0
       doSomething()


Removed old visualization view

Removed old visualization view

--- community/server/src/main/coffeescript/neo4j/webadmin/views/databrowser/VisualizedView.old.coffee
+++ community/server/src/main/coffeescript/neo4j/webadmin/views/databrowser/VisualizedView.old.coffee
@@ -1,285 +0,0 @@
-###
-Copyright (c) 2002-2011 "Neo Technology,"
-Network Engine for Objects in Lund AB [http://neotechnology.com]
-
-This file is part of Neo4j.
-
-Neo4j is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU Affero General Public License for more details.
-
-You should have received a copy of the GNU Affero General Public License
-along with this program. If not, see <http://www.gnu.org/licenses/>.
-###
-
-define(
-  ['neo4j/webadmin/data/ItemUrlResolver'
-   'neo4j/webadmin/ui/LoadingSpinner'
-   'neo4j/webadmin/views/View'
-   'neo4j/webadmin/ui/Tooltip'
-   'neo4j/webadmin/security/HtmlEscaper'
-   'neo4j/webadmin/templates/databrowser/visualization'
-   'lib/raphael'
-   'lib/dracula.graffle'
-   'lib/dracula.graph'
-   'lib/backbone'],
-  (ItemUrlResolver, LoadingSpinner, View, Tooltip, HtmlEscaper, template) ->
-
-    GROUP_IDS = 0
-
-    class VisualizedView extends View
-
-      initialize : (options)->
-
-        @server = options.server
-
-        @urlResolver = new ItemUrlResolver(@server)
-        @htmlEscaper = new HtmlEscaper
-
-        @dataModel = options.dataModel
-
-        @tooltip = new Tooltip
-
-        @nodeMap = {}
-        @groupMap = {}
-
-      render : =>
-        $(@el).html(template())
-
-        @nodeMap = {}
-        @groupMap = {}
-
-        width = $(document).width() - 40;
-        height = $(document).height() - 120;
-
-        @g = new Graph()
-
-        switch @dataModel.get("type")
-          when "node"
-            node = @dataModel.getData().getItem()
-            @addNode(node)
-            @loadRelationships(node)
-          when "relationship"
-            @addRelationship(@dataModel.getData().getItem())
-          when "relationshipList"
-            @addRelationships(@dataModel.getData().getRawRelationships())
-          else
-            return this
-
-        @layout = new Graph.Layout.Spring(@g)
-        @renderer = new Graph.Renderer.Raphael('visualization', @g, width, height)
-
-        return this
-
-      redrawVisualization : =>
-        @layout.layout()
-        @renderer.draw()
-
-      hasNode : (nodeUrl) =>
-         if @nodeMap[nodeUrl]
-           return true
-         else
-           return false
-
-      addNode : (node) =>
-        @nodeMap[node.getSelf()] =
-          neoNode    : node
-          visualNode : @g.addNode(node.getSelf(), { node : node, url:node.getSelf(), explored:true, render:@nodeRenderer })
-
-      addUnexploredNode : (nodeUrl) =>
-        @nodeMap[nodeUrl] =
-          neoNode    : null
-          visualNode : @g.addNode(nodeUrl, { node : null, url:nodeUrl, explored:false, render:@unexploredNodeRenderer})
-
-      addRelationship : (rel) =>
-        @addRelationships([rel])
-
-      addAndGroupRelationships : (rels, node) =>
-        groups = {}
-        for rel in rels
-          if not groups[rel.getType()]
-            groups[rel.getType()] = { type:rel.getType(), size:0, relationships:[] }
-
-          groups[rel.getType()].size++
-          groups[rel.getType()].relationships.push(rel)
-
-        for type, group of groups
-
-          if group.size > 5
-            @addGroup(group, node)
-          else
-            @addRelationships(group.relationships)
-
-        @redrawVisualization()
-
-      addGroup : (group, node) =>
-
-        id = "group-" + GROUP_IDS++
-        @groupMap[id] =
-          group : group
-          visualNode : @g.addNode(id, { id:id, size:group.size, render:@groupRenderer })
-
-        @g.addEdge(node.getSelf(), id, { label : group.type })
-
-      addRelationships : (rels) =>
-
-        for rel in rels
-          if @hasNode(rel.getEndNodeUrl()) == false
-            @addUnexploredNode(rel.getEndNodeUrl())
-
-          if @hasNode(rel.getStartNodeUrl()) == false
-            @addUnexploredNode(rel.getStartNodeUrl())
-
-          @g.addEdge(rel.getStartNodeUrl(), rel.getEndNodeUrl(), { label : rel.getType(), directed : true })
-
-
-      nodeRenderer : (r, node) =>
-        circle = r.circle(0, 0, 10).attr({fill: "#ffffff", stroke: "#333333", "stroke-width": 2})
-
-        if node.node.hasProperty("name")
-          label = r.text(0, 0, node.node.getProperty("name"))
-        else
-          label = r.text(0, 0, @urlResolver.extractNodeId(node.url))
-
-        clickHandler = (ev) =>
-          @nodeClicked(node, circle)
-
-        mouseOverHandler = (ev) =>
-          @mouseOverNode(ev, node, circle)
-
-        mouseOutHandler = (ev) =>
-          @mouseLeavingNode(ev, node, circle)
-
-        circle.click(clickHandler)
-        label.click(clickHandler)
-        circle.hover(mouseOverHandler, mouseOutHandler)
-        label.hover(mouseOverHandler, mouseOutHandler)
-
-        shape = r.set().
-          push(circle).
-          push(label)
-        return shape
-
-      unexploredNodeRenderer : (r, node) =>
-        circle = r.circle(0, 0, 10).attr({fill: "#ffffff", stroke: "#dddddd", "stroke-width": 2})
-
-        label = r.text(0, 0, @urlResolver.extractNodeId(node.url))
-
-        clickHandler = (ev) =>
-          @nodeClicked(node, circle)
-
-        mouseOverHandler = (ev) =>
-          @mouseOverNode(ev, node, circle)
-
-        mouseOutHandler = (ev) =>
-          @mouseLeavingNode(ev, node, circle)
-
-        circle.click(clickHandler)
-        label.click(clickHandler)
-        circle.hover(mouseOverHandler, mouseOutHandler)
-        label.hover(mouseOverHandler, mouseOutHandler)
-
-        shape = r.set().
-          push(circle).
-          push(label)
-        return shape
-
-      groupRenderer : (r, group) =>
-        circle = r.circle(0, 0, 6).attr({fill: "#eeeeee", stroke: "#dddddd", "stroke-width": 2})
-
-        label = r.text(0, 0, group.size)
-
-        clickHandler = (ev) =>
-          @groupClicked(group, circle)
-        circle.click(clickHandler)
-        label.click(clickHandler)
-
-        shape = r.set().
-          push(circle).
-          push(label)
-        return shape
-
-      nodeClicked : (nodeMeta, circle) =>
-        nodeMeta = @nodeMap[nodeMeta.url].visualNode
-
-        if nodeMeta.explored == false
-          circle.attr({fill: "#ffffff", stroke: "#333333", "stroke-width": 2})
-          @server.node(nodeMeta.url).then (node) =>
-            nodeMeta.explored = true
-            nodeMeta.node = node
-            @addNode(node)
-            @loadRelationships(node)
-
-      mouseOverNode : (ev, nodeMeta, circle) =>
-        # XXX: As with most things in this class, this is a temp hack
-        if nodeMeta.explored
-          node = nodeMeta.node
-          propHtml = for key, val of node.getProperties()
-            key = @htmlEscaper.escape(key)
-            val = @htmlEscaper.escape JSON.stringify(val)
-            "
#{key}: #{val}
"
-
-          propHtml = propHtml.join("\n")
-          html = "
#{propHtml}
"
-        else
-          html = "
Unexplored node
"
-        @tooltip.show(html, [ev.clientX, ev.clientY])
-
-      mouseLeavingNode : (ev, nodeMeta, circle) =>
-        @tooltip.hide()
-
-      groupClicked : (groupMeta, circle) =>
-        group = @groupMap[groupMeta.id].group
-        visualNode = @groupMap[groupMeta.id].visualNode
-        @showLoader()
-
-        ungroup = ()=>
-          @removeVisualNode(visualNode)
-          @addExplodedGroup(group)
-          @redrawVisualization()
-          @hideLoader()
-
-        setTimeout( ungroup, 1 )
-
-      loadRelationships : (node) =>
-        @showLoader()
-        node.getRelationships().then (rels) =>
-          @addAndGroupRelationships(rels, node)
-          @hideLoader()
-
-      removeVisualNode : (visualNode, id) =>
-        visualNode.hide()
-        for edge in visualNode.edges
-          edge.connection.label.hide()
-          edge.hide()
-        @g.removeNode(visualNode.id)
-
-      showLoader : =>
-        @hideLoader()
-        @loader = new LoadingSpinner($(".workarea"))
-        @loader.show()
-
-      hideLoader : =>
-        if @loader?
-          @loader.destroy()
-
-      remove : =>
-        @dataModel.unbind("change", @render)
-        super()
-
-      detach : =>
-        @dataModel.unbind("change", @render)
-        super()
-
-      attach : (parent) =>
-        super(parent)
-        @dataModel.bind("change", @render)
-
-
-)


Hide empty warning on past assignments

Hide empty warning on past assignments

--- tutor/src/components/course-calendar/past-assignments.cjsx
+++ tutor/src/components/course-calendar/past-assignments.cjsx
@@ -8,12 +8,6 @@

 {PastTaskPlansActions, PastTaskPlansStore} = require '../../flux/past-task-plans'
 LoadableItem = require '../loadable-item'
-
-EmptyWarning = (props) ->
-  return null unless props.isVisible
-

-    No assignments were found
-

 PastAssignmentsLoading = ->
@@ -32,8 +26,6 @@
     plans = PastTaskPlansStore.get(@props.courseId) or []

-      <EmptyWarning isVisible={isEmpty(plans)} />
-
       {for plan in plans
         <CloneAssignmentLink key={plan.id} plan={plan} />}


Avoiding infinite loop for heightless parents

--- src/app/components/directive.coffee
+++ src/app/components/directive.coffee
@@ -16,15 +16,16 @@

       ngFillHeightOption.api = {
         recalcHeight : () ->
-
+
           recurrFunc = (increment) ->
             currObject.height(currObject.height() + increment)

-          recurrFunc(10) while currObject.height() < ngFillHeightOption.minHeight or parentObject.height() >= parentObject.prop('scrollHeight')
+          if parentObject.prop('scrollHeight')
+            recurrFunc(10) while currObject.height() < ngFillHeightOption.minHeight or parentObject.height() >= parentObject.prop('scrollHeight')

-          recurrFunc(-1) while currObject.height() > ngFillHeightOption.minHeight and parentObject.height() < parentObject.prop('scrollHeight')
+            recurrFunc(-1) while currObject.height() > ngFillHeightOption.minHeight and parentObject.height() < parentObject.prop('scrollHeight')

-          return
+          return currObject.height()
       }

     return {


Adding | and @ to ignore

Adding | and @ to ignore

--- src/coffee/classifier.coffee
+++ src/coffee/classifier.coffee
@@ -26,7 +26,7 @@
          # remove common words here
          dokument = dokument.replace(@stop_words, "")
          # remove punctuation
-         dokument = dokument.replace(/[><\.,-\/#!$%\^&\*;:{}=\-_`~()?]/g," ")
+         dokument = dokument.replace(/[><\.,-\/#!$%\^&\*;:{}=\-_`~()?|@]/g," ")
          # remove leading, trailing and consecutive spaces
          dokument = dokument.replace(/\[|\]/g," ")
          dokument = dokument.replace(/\s+/g," ")


Use runas to do the install when got EACESS error.

--- src/command-installer.coffee
+++ src/command-installer.coffee
@@ -3,6 +3,7 @@
 async = require 'async'
 fs = require 'fs-plus'
 mkdirp = require 'mkdirp'
+runas = require 'runas'

 symlinkCommand = (sourcePath, destinationPath, callback) ->
   mkdirp path.dirname(destinationPath), (error) ->
@@ -15,12 +16,19 @@
         else
           fs.chmod(destinationPath, 0o755, callback)

+symlinkCommandWithPrivilegeSync = (sourcePath, destinationPath) ->
+  runas('/bin/mkdir', ['-p', path.dirname(destinationPath)], admin: true)
+  runas('/bin/ln', ['-s', sourcePath, destinationPath], admin: true) is 0
+
 unlinkCommand = (destinationPath, callback) ->
   fs.unlink destinationPath, (error) ->
     if error? and error.code isnt 'ENOENT'
       callback(error)
     else
       callback()
+
+unlinkCommandWithPrivilegeSync = (destinationPath) ->
+  runas('/bin/rm', ['-f', destinationPath], admin: true) is 0

 module.exports =
   getInstallDirectory: ->
@@ -34,11 +42,19 @@
     if fs.existsSync(directory)
       destinationPath = path.join(directory, commandName)
       unlinkCommand destinationPath, (error) =>
+        # Retry with privilige escalation.
+        if error?.code is 'EACCES' and unlinkCommandWithPrivilegeSync(destinationPath)
+          error = null
+
         if error?
           error = new Error "Could not remove file at #{destinationPath}." if error
           callback?(error)
         else
           symlinkCommand commandPath, destinationPath, (error) =>
+            # Retry with privilige escalation.
+            if error?.code is 'EACCES' and symlinkCommandWithPrivilegeSync(commandPath, destinationPath)
+              error = null
+
             error = new Error "Failed to symlink #{commandPath} to #{destinationPath}." if error
             callback?(error)
     else


première ébauche du uncompress path (ne compile pas)

--- app/assets/jax/models/path.js.coffee
+++ app/assets/jax/models/path.js.coffee
@@ -134,51 +134,39 @@

     return compressed_path

-# /**
-#  * Uncompress a path (see description of Path class)
-#  * @param path compressed path
-#  * @return uncompressed path
-#  */
-#  char* Path::uncompressPath(const char * compressedPath)
-#  {
-#   int i=0, j=0, nbr;
-#   char buffer[10];
-#   int cpt=1;
-#   char * uncompressedPath;
-#
-#   // Alloc empty cell
-#   uncompressedPath=(char*)malloc(1*sizeof(char));
-#   uncompressedPath[0]='\0';
-#
-#   // While we're not in the end of compressed path
-#   while(compressedPath[i]!='\0')
-#   {
-#     buffer[0]='1';
-#     buffer[1]='\0';
-#
-#     // we put decimal in buffer
-#     while(Util::isDecimal(compressedPath[i]))
-#     {
-#       buffer[j]=compressedPath[i];
-#       buffer[j+1]='\0';
-#       j++;
-#       i++;
-#     }
-#
-#     // we get decimal on untextual mode
-#     nbr=atoi(buffer);
-#
-#     for(j=0;j<nbr;j++)
-#     {
-#       cpt++;
-#       uncompressedPath=(char*)realloc(uncompressedPath, cpt*sizeof(char));
-#       uncompressedPath[cpt-2]=compressedPath[i];
-#       uncompressedPath[cpt-1]='\0';
-#     }
-#
-#     i++;
-#     j=0;
-#   }
-#
-#   return uncompressedPath;
-#  }+###
+  Uncompress a path (see description of Path class)
+  @param path compressed path
+  @return uncompressed path
+###
+  uncompress_path: (compressed_path) ->
+   i = 0
+   j = 0
+   buffer = []
+   cpt = 1
+   uncompressed_path = []
+
+   # While we're not in the end of compressed path
+   while compressed_path[i] != '\0'
+     buffer[0] = '1'
+     buffer[1] = '\0'
+
+     # we put decimal in buffer
+     while Util::isDecimal(compressed_path[i])
+       buffer[j] = compressedPath[i]
+       buffer[j+1] = '\0'
+       j = j + 1
+       i = i + 1
+
+     # we get decimal on untextual mode
+     nbr = atoi(buffer)
+
+     for(j = 0 ; j < nbr ; j++)
+       cpt = cpt + 1
+       uncompressed_path[cpt-2] = compressed_path[i]
+       uncompressed_path[cpt-1] = '\0'
+
+     i = i + 1
+     j = 0
+
+   return uncompressed_path


disable answer changes when reviewing an exercise

--- src/components/question.cjsx
+++ src/components/question.cjsx
@@ -25,6 +25,7 @@
   render: ->
     html = @props.model.stem_html
     qid = @props.model.id or "auto-#{idCounter++}"
+    hasCorrectAnswer = !! @props.correct_answer_id

     if @props.feedback_html
       feedback = <ArbitraryHtml className="question-feedback has-html" html={@props.feedback_html} />
@@ -38,8 +39,10 @@
       classes.push('answer-correct fa') if isCorrect
       classes = classes.join(' ')

-

-        <input
+      # Allow changes only when the correct answer is not provided
+      # TODO: This should only be allowed when not(taskStep.is_completed)
+      unless hasCorrectAnswer
+        radioBox = <input
           type="radio"
           className="answer-input-box"
           checked={isChecked}
@@ -47,13 +50,15 @@
           name="#{qid}-options"
           onChange={@onChangeAnswer(answer)}
         />
+
+

+        {radioBox}

           <ArbitraryHtml className="answer-content" html={answer.content_html} />

-    hasCorrectAnswer = !! @props.correct_answer_id
     classes = ['question']
     classes.push('has-correct-answer') if hasCorrectAnswer
     classes = classes.join(' ')


use proper alignment for vertical ticks below a plot

--- bokehjs/src/coffee/core/layout/side_panel.coffee
+++ bokehjs/src/coffee/core/layout/side_panel.coffee
@@ -106,7 +106,7 @@
     parallel   : CENTER
     normal     : LEFT
     horizontal : CENTER
-    vertical   : RIGHT
+    vertical   : LEFT
   left:
     justified  : CENTER
     parallel   : CENTER


Name middleware; pass in paths

Name middleware; pass in paths

--- apps/pro_buyer/routes.coffee
+++ apps/pro_buyer/routes.coffee
@@ -1,18 +1,22 @@
+{ extend } = require 'underscore'
 Promise = require 'bluebird-q'

 module.exports = (page) ->
-  middleware: (req, res, next) ->
-    unless req.user?
-      return res.redirect page.paths.show
+  middleware:
+    isLoggedIn: (req, res, next) ->
+      unless req.user?
+        return res.redirect page.paths.show

-    next()
+      next()

   get:
     landing: (req, res, next) ->
       page
         .get()
         .then (data) ->
-          res.render 'pages/landing/templates/index', data
+          res.locals.sd.SHOW_PATH = page.paths.show
+          res.render 'pages/landing/templates/index', extend data,
+            paths: page.paths

         .catch next

@@ -23,7 +27,8 @@
       Promise
         .all [
           user.fetch()
-          collectorProfile.fetch data: access_token: user.get 'accessToken'
+          collectorProfile.fetch data:
+            access_token: user.get 'accessToken'
         ]

         .then ->
@@ -31,6 +36,4 @@
           res.locals.sd.COLLECTOR_PROFILE = collectorProfile.toJSON()
           res.render 'pages/complete/templates/index'

-        .catch (err) ->
-          console.log err
-          next err
+        .catch next


Commit lastMessageWindowHistory

Commit lastMessageWindowHistory

--- client/views/app/chatWindowDashboard.coffee
+++ client/views/app/chatWindowDashboard.coffee
@@ -14,6 +14,7 @@
 	messages: ->
 		console.log 'chatWindowDashboard.messages' if window.rocketDebug
 		window.lastMessageWindow[this._id] = undefined
+		window.lastMessageWindowHistory[this._id] = undefined
 		return ChatMessage.find { rid: this._id }, { sort: { ts: 1 } }

 	messagesHistory: ->
@@ -85,6 +86,29 @@

 		if lastMessageDate.mid is this._id
 			last = ChatMessage.find({ts: {$lt: this.ts}, t: {$exists: false}}, { sort: { ts: -1 }, limit: 1 }).fetch()[0]
+			if not last?
+				return false
+			lastMessageDate =
+				mid: last._id
+				date: moment(last.ts).format('YYYYMMDD')
+
+		return lastMessageDate.date isnt d
+
+	newDateHistory: ->
+		console.log 'chatWindowDashboard.newDate' if window.rocketDebug
+
+		lastMessageDate = window.lastMessageWindowHistory[this.rid]
+		d = moment(this.ts).format('YYYYMMDD')
+
+		window.lastMessageWindowHistory[this.rid] =
+			mid: this._id
+			date: d
+
+		if not lastMessageDate?
+			return false
+
+		if lastMessageDate.mid is this._id
+			last = ChatMessageHistory.find({ts: {$lt: this.ts}, t: {$exists: false}}, { sort: { ts: -1 }, limit: 1 }).fetch()[0]
 			if not last?
 				return false
 			lastMessageDate =


count to side text

count to side text

--- app/views/media/get_statistics.coffee
+++ app/views/media/get_statistics.coffee
@@ -83,7 +83,7 @@
           text: 'Date'
       y: title:
         display: true
-        text: 'value' )
+        text: 'count' )
 <% end %>

 $('#statisticsModal').modal('show')


KDListViewController: instead of passing KDListView.items reference, slice() is passed to itemsOrdered in moveItemToIndex method

--- components/list/KDListViewController.coffee
+++ components/list/KDListViewController.coffee
@@ -15,6 +15,9 @@
     options.noMoreItemFoundWidget or= null

     @itemsOrdered                 = [] unless @itemsOrdered
+    # CtF: this must be fixed: duplicate itemsOrdered and KDListView.items
+    # Object.defineProperty this, "itemsOrdered", get : => @getListView().items
+
     @itemsIndexed                 = {}
     @selectedItems                = []
     @lazyLoader                   = null
@@ -171,7 +174,7 @@
   moveItemToIndex:(item, newIndex)->

     newIndex = Math.max(0, Math.min(@itemsOrdered.length-1, newIndex))
-    @itemsOrdered = @getListView().moveItemToIndex item, newIndex
+    @itemsOrdered = @getListView().moveItemToIndex(item, newIndex).slice()

   ###
   HANDLING MOUSE EVENTS


delete accidental addition

delete accidental addition

--- assets/js/keyBindings.coffee
+++ assets/js/keyBindings.coffee
@@ -369,31 +369,6 @@
       drop: true
       fn: () ->
         @view.scrollPages -0.5
-    SEARCH:
-      display: 'Search'
-      drop: true
-      menu: (view, text) ->
-        # a list of {contents, highlights, fn ]
-        # SEE: menu.coffee
-        results = []
-
-        selectRow = (row) ->
-          view.rootInto row
-
-        for found in view.find text
-          row = found.row
-          index = found.index
-
-          highlights = {}
-          for i in [index ... index + text.length]
-            highlights[i] = true
-
-          results.push {
-            contents: view.data.getLine row
-            highlights: highlights
-            fn: selectRow.bind(@, row)
-          }
-        return results
     EXPORT:
       display: 'Save a file'
       fn: () ->


[DOC] Add documentation for src/ws/websocket.coffee

--- src/ws/websocket.coffee
+++ src/ws/websocket.coffee
@@ -22,21 +22,15 @@
 {Errors}          = require '../core/errors'
 {Token}           = require '../core/token'

-# Generate random ID
+# Generate a random number.
 randNb            = () ->
   return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1)

+# Generate a random key.
 generateRandomId  = () ->
   return (randNb() + randNb() + "-" + randNb() + "-" + randNb() + "-" +
           randNb() + "-" + randNb() + randNb() + randNb())

-# Variables tips:
-#
-# rid   -> roomID
-# cid   -> clientID
-# cinfo -> clientInfo
-
-# WebSocket server
 class Websocket
         constructor       : () ->
             @sockets    = []


add project id to team state

add project id to team state

--- src/common/states/controller.coffee
+++ src/common/states/controller.coffee
@@ -40,6 +40,8 @@
       icon: 'plus'
     ,
       state: 'tab.team'
+      params:
+        projectId: $scope.project?.objectId
       title: 'Team'
       icon: 'account-multiple'
     ,


Dock: semantic way of redirecting to learn.koding.com

--- client/Main/avatararea/avatarareaiconmenu.coffee
+++ client/Main/avatararea/avatarareaiconmenu.coffee
@@ -10,10 +10,12 @@
       cssClass   : "help acc-dropdown-icon"
       attributes :
         title    : 'Help'
+        href     : 'http://learn.koding.com'
+        target   : '_blank'

-    @helpIcon.click = (event)=>
-      window.open "http://learn.koding.com"
-      KD.utils.stopDOMEvent event
+    # @helpIcon.click = (event)=>
+      # window.open "http://learn.koding.com"
+      # KD.utils.stopDOMEvent event

       # We disabled this feature since '?' relies on vm to be up for
       # certain items to show properly. SA
@@ -120,4 +122,4 @@
       return  unless notification.snapshot
       try
         snapshot = JSON.parse Encoder.htmlDecode notification.snapshot
-        snapshot.anchor.constructorName in activityNameMap
+        snapshot.anchor.constructorName in activityNameMap


Onboarding: Set appStorage value when an onboarding shown.

--- client/Main/onboarding/onboardingitemview.coffee
+++ client/Main/onboarding/onboardingitemview.coffee
@@ -116,6 +116,11 @@
     @on "OnboardingCancelled", =>
       @destroy()

+    {setStorage, slug} = @getOptions()
+    if setStorage
+      viewController = @getDelegate()
+      onboardingController = viewController.getDelegate()
+      onboardingController.emit "OnboardingShown", slug

   destroy: ->
     super


IDEFinderTreeController: use same signatures with NFinderTreeController

--- client/ide/finder/idefindertreecontroller.coffee
+++ client/ide/finder/idefindertreecontroller.coffee
@@ -26,24 +26,24 @@
     appManager.tell 'IDE', 'createNewTerminal', { machine, path }

-  collapseFolder: (nodeView, callback) ->
+  collapseFolder: (nodeView, callback, silence) ->

-    super nodeView, callback
+    super nodeView, callback, silence

     return  if @dontEmitChangeEvent

     @emit 'FolderCollapsed', nodeView.getData().path

-  expandFolder: (nodeView, callback) ->
+  expandFolder: (nodeView, callback, silence) ->

-    super nodeView, callback
+    super nodeView, callback, silence

     return  if @dontEmitChangeEvent

     @emit 'FolderExpanded', nodeView.getData().path
-
-
+
+
   deleteWorkspaceRootFolder: (machineUId, rootPath) ->

     node = @nodes["[#{machineUId}]#{rootPath}"]


don't log out when doc lines are [] as this is a new empty doc

--- services/docstore/app/coffee/DocManager.coffee
+++ services/docstore/app/coffee/DocManager.coffee
@@ -16,7 +16,8 @@
 					if docFromDocCollection?
 						return callback null, docFromDocCollection, mongoPath
 					else if doc?
-						logger.warn project_id:project_id, doc_id:doc_id, "doc just used from project collection, why?"
+						if doc?.lines?.length > 0
+							logger.warn project_id:project_id, doc_id:doc_id, "doc just used from project collection, why?"
 						return callback null, doc, mongoPath
 					else
 						return callback new Errors.NotFoundError("No such doc: #{project_id}")


fixed some drawing stuff.

fixed some drawing stuff.

--- views/coffeescript/main.coffee
+++ views/coffeescript/main.coffee
@@ -42,9 +42,11 @@
         # pt:   {x:#, y:#}  node position in screen coords

         # draw a rectangle centered at pt
-        w = 10
-        ctx.fillStyle = if node.data.alone then "orange" else "black"
-        ctx.fillRect(pt.x-w/2, pt.y-w/2, w, w)
+        w = 20
+        ctx.beginPath();
+        ctx.fillStyle = if node.data.id is 0 then "blue" else "green"
+        ctx.arc(pt.x, pt.y, w, 0, Math.PI*2)
+        ctx.fill()

     initMouseHandling: ->
       # no-nonsense drag and drop (thanks springy.js)
@@ -109,7 +111,7 @@
     node['_neo_id']

   props: (node) ->
-    {name: @id(node)}
+    {id: @id(node)}


Use CourseListingStore to get course list

--- src/flux/current-user.coffee
+++ src/flux/current-user.coffee
@@ -1,7 +1,7 @@
 # coffeelint: disable=no_empty_functions
 _ = require 'underscore'
 flux = require 'flux-react'
-
+{CourseListingStore} = require './course-listing'
 {CourseActions, CourseStore} = require './course'

 # Read the CSRF token from document's meta tag.  If not found, log a warning but proceed
@@ -150,7 +150,7 @@
       if course
         if course.is_concept_coach then CONCEPT_COACH_HELP else TUTOR_HELP
       else
-        courses = CourseStore.allCourses()
+        courses = CourseListingStore.allCourses()
         # link to TUTOR_HELP if they have any tutor courses
         if _.findWhere(courses, is_concept_coach: false) then TUTOR_HELP else CONCEPT_COACH_HELP


perf(threads): Add shortcut for common case in `emailIsEquivalent`, avoid `RegExp.exec`

--- src/flux/models/utils.coffee
+++ src/flux/models/utils.coffee
@@ -218,6 +218,7 @@
     return email

   emailIsEquivalent: (email1="", email2="") ->
+    return true if email1 is email2
     email1 = Utils.toEquivalentEmailForm(email1)
     email2 = Utils.toEquivalentEmailForm(email2)
     return email1 is email2


update Batman.TestCase to use QUnit.throws

--- src/extras/testing/test_case.coffee
+++ src/extras/testing/test_case.coffee
@@ -85,7 +85,7 @@
     @assertDifference expressions, 0, message, callback

   assertRaises: (expected, callback, message) ->
-    QUnit.raises callback, expected, message
+    QUnit.throws callback, expected, message

   stubAccessor: (object, keypath, fn) ->
     stub = sinon.sandbox.stub(object.property(keypath), 'getValue', fn)


Prevent login access for authorized users

--- www/js/routes.coffee
+++ www/js/routes.coffee
@@ -67,7 +67,13 @@
           trigger: true

     userLogin: ->
-      appView.currentView = new UserViews.Login
+      # Don't allow authorized users to access the login page.
+      if window.GLOBALS.Users.getSelf()
+        @navigate '',
+          replace: true
+          trigger: true
+      else
+        appView.currentView = new UserViews.Login()

     # Show a user's profile. The template adjusts for various user
     # relationships, including the case where this is the active/self user's
@@ -88,7 +94,7 @@

     # Do some slightly-messy DOM cleanup on history state change.
     _historyCleanup: (route) ->
-      if _.contains ['', 'nearby', 'worldwide'], route
+      if _.contains ['', 'login', 'nearby', 'worldwide'], route
         $('body').addClass 'hide-back-button'
       else
         $('body').removeClass 'hide-back-button'


Make sure functions for keybinds return nothing

--- orgviz/static/timeline.coffee
+++ orgviz/static/timeline.coffee
@@ -4,6 +4,7 @@
     eventSource.loadJSON(timeline_data, url)
     tl.layout()
     callback() if callback?
+  return

 panTimeline = (tl, delta) ->
@@ -12,6 +13,7 @@
   xmax = b.getMaxVisibleDate().getTime()
   dx = (xmax - xmin) * delta / 100
   b.setCenterVisibleDate b.getCenterVisibleDate().getTime() + dx
+  return

 zoomTimeline = (tl, zoomIn) ->
@@ -20,6 +22,7 @@
   x = b.dateToPixelOffset b.getCenterVisibleDate().getTime()
   y = undefined  # reading the source, it seems that y is not used
   tl.zoom zoomIn, x, y, $("div.timeline-band")[0]
+  return

 #### Get a function to check the input checkbox and start/stop auo-reload
@@ -49,6 +52,7 @@
     else
       checkbox.attr "checked", "checked"
     callback() if callback?
+    return

 setupKeybinds = (keyboardInput, tl) ->


remove spaces before and after an hbox

remove spaces before and after an hbox

--- bokehjs/src/coffee/widget/hbox.coffee
+++ bokehjs/src/coffee/widget/hbox.coffee
@@ -26,12 +26,10 @@
     height = @mget("height")
     if height? then @$el.css(height: height + "px")

-    @$el.append($('

'))
-    for child in children
+    for child, index in children
       @$el.append(@views[child.id].$el)
-
-      @$el.append($('

'))
-
+      if index < children.length - 1
+        @$el.append($('

'))

     return @


Fix a bug in middleware error handling

Fix a bug in middleware error handling

--- src/listener.coffee
+++ src/listener.coffee
@@ -52,7 +52,7 @@
           @callback response
           done()
         catch err
-          @emit('error', err, response)
+          @robot.emit('error', err, response)

       # When everything is finished (down the middleware stack and back up),
       # pass control back to the robot
@@ -94,7 +94,7 @@
       try
         middlewareFunc.call(undefined, @robot, @, response, nextFunc, doneFunc)
       catch err
-        @emit('error', err, response)
+        @robot.emit('error', err, response)
         # Forcibly fail the middleware and stop executing deeper
         doneFunc()


Remove some code we're not using.

Remove some code we're not using.

--- keypress.coffee
+++ keypress.coffee
@@ -156,35 +156,6 @@

     # Trying to return an array of matched combos
     return potentials
-
-    ###
-
-    # Return the combo that includes key in the keys array.
-    # If multiple include it, return the longest one, if they
-    # are the same length, announce a conflict.
-    check_for_conflict = (array) ->
-        if array.length > 1 and array[0].keys.length is array[1].keys.length
-            _log_error "Conflicting combos registered"
-            return true
-
-    if potentials.length > 1
-        potentials.sort (a, b) ->
-            b.keys.length - a.keys.length
-        better_pots = []
-        for potential in potentials
-            better_pots.push(potential) if key in potential.keys
-
-        # If at least one potential contains key
-        if better_pots.length
-            return false if check_for_conflict better_pots
-            potentials = better_pots
-        # If none of the potentials contain key
-        else
-            return false if check_for_conflict potentials
-
-    return false unless potentials.length
-    return potentials[0] if _cmd_bug_check potentials[0].keys
-    ###

 _get_potential_combos = (key) ->
     # Check if we are working towards pressing a combo.


Added missing tasks

Added missing tasks

--- build/tasks/build-app.coffee
+++ build/tasks/build-app.coffee
@@ -11,7 +11,8 @@
     buildDir = grunt.config.get('buildDir')

     if !!process.env.TRAVIS
-      tasks = 'set-version check-licenses lint generate-asar '
+      tasks = 'download-atom-shell download-atom-shell-chromedriver build '
+      tasks += 'set-version check-licenses lint generate-asar '
       tasks += 'mkdeb ' if process.platform is 'linux'
       tasks += 'create-windows-installer ' if process.platform is 'win32'
       tasks += 'codesign publish-build'


Genrate docs from local api.json files

Genrate docs from local api.json files

--- build/tasks/docs-task.coffee
+++ build/tasks/docs-task.coffee
@@ -29,23 +29,23 @@
   grunt.registerTask 'build-docs', 'Builds the API docs in src', ->
     done = @async()
     docsOutputDir = grunt.config.get('docsOutputDir')
-    downloadIncludes (error, includedModules) ->
-      metadata = donna.generateMetadata(['.'].concat(includedModules))
-      telloJson = _.extend(tello.digest(metadata), getClassesToInclude())

-      files = [{
-        filePath: path.join(docsOutputDir, 'donna.json')
-        contents: JSON.stringify(metadata, null, '  ')
-      }, {
-        filePath: path.join(docsOutputDir, 'tello.json')
-        contents: JSON.stringify(telloJson, null, '  ')
-      }]
+    metadata = donna.generateMetadata(['.'])
+    telloJson = _.extend(tello.digest(metadata), getClassesToInclude())

-      writeFile = ({filePath, contents}, callback) ->
-        fs.writeFile filePath, contents, (error) ->
-          callback(error)
+    files = [{
+      filePath: path.join(docsOutputDir, 'donna.json')
+      contents: JSON.stringify(metadata, null, '  ')
+    }, {
+      filePath: path.join(docsOutputDir, 'tello.json')
+      contents: JSON.stringify(telloJson, null, '  ')
+    }]

-      async.map files, writeFile, -> done()
+    writeFile = ({filePath, contents}, callback) ->
+      fs.writeFile filePath, contents, (error) ->
+        callback(error)
+
+    async.map files, writeFile, -> done()

   grunt.registerTask 'copy-docs', 'Copies over latest API docs to atom-docs', ->
     done = @async()
@@ -75,25 +75,3 @@
           return false

     grunt.util.async.waterfall [fetchTag, copyDocs], done
-
-downloadFileFromRepo = ({repo, file}, callback) ->
-  uri = "https://raw.github.com/atom/#{repo}/master/#{file}"
-  request uri, (error, response, contents) ->
-    return callback(error) if error?
-    downloadPath = path.join('docs', 'includes', repo, file)
-    fs.writeFile downloadPath, contents, (error) ->
-      callback(error, downloadPath)
-
-
-
-downloadIncludes = (callback) ->
-  includes = [
-    {repo: 'theorist',    file: 'src/model.coffee'}
-    {repo: 'theorist',    file: 'package.json'}
-  ]
-
-  async.map includes, downloadFileFromRepo, (error, allPaths) ->
-    includeDirectories = null
-    if allPaths?
-      includeDirectories = _.unique allPaths.map (dir) -> /^docs\/includes\/[a-z-]+/.exec(dir)[0]
-    callback(error, includeDirectories)


Run integration tests on CI

Run integration tests on CI

--- build/tasks/spec-task.coffee
+++ build/tasks/spec-task.coffee
@@ -85,15 +85,27 @@
     appPath = getAppPath()
     resourcePath = process.cwd()
     coreSpecsPath = path.resolve('spec')
+    chromedriverPath = path.join(resourcePath, "atom-shell", "chromedriver")

     if process.platform in ['darwin', 'linux']
       options =
         cmd: appPath
         args: ['--test', "--resource-path=#{resourcePath}", "--spec-directory=#{coreSpecsPath}"]
+        opts:
+          env: _.extend({}, process.env,
+            ATOM_INTEGRATION_TESTS_ENABLED: true
+            PATH: [process.env.path, chromedriverPath].join(":")
+          )
+
     else if process.platform is 'win32'
       options =
         cmd: process.env.comspec
         args: ['/c', appPath, '--test', "--resource-path=#{resourcePath}", "--spec-directory=#{coreSpecsPath}", "--log-file=ci.log"]
+        opts:
+          env: _.extend({}, process.env,
+            ATOM_INTEGRATION_TESTS_ENABLED: true
+            PATH: [process.env.path, chromedriverPath].join(";")
+          )

     spawn options, (error, results, code) ->
       if process.platform is 'win32'


Routes: Set `KD.userWorkspaces`.

Routes: Set `KD.userWorkspaces`.

--- client/ide/routes.coffee
+++ client/ide/routes.coffee
@@ -126,6 +126,8 @@

         .then (workspaces) ->

+          KD.userWorkspaces = workspaces
+
           try
             workspaces.forEach (workspace) =>


Unsets internal pointer to previously selected node on DOM deselect

```javascript
$tree.tree('selectNode');
```

When calling the above without passing a `node` parameter, the majority
of jqTree's `selectNode` method is skipped *(since no `node` has bene
provided, there is nothing to select)*.

Regardless of `node` being passed, the currently selected node is first
deselected. The deselect happens in the DOM, but the internal pointer to
the selected node is not cleared. Calling

```javascript
$tree.tree('getSelectedNode');
```

will still *(incorrectly)* return the *(now **deselected**)* node.

The small change here fixes that, causing `getSelectedNode` to properly
return `false`.

--- tree.jquery.coffee
+++ tree.jquery.coffee
@@ -439,6 +439,7 @@
         if @options.selectable
             if @selected_node
                 @_getNodeElementForNode(@selected_node).deselect()
+                @selected_node = null

             if node
                 @_getNodeElementForNode(node).select()


[upd] fixed the wrong hostname of connect task

--- presentation/Gruntfile.coffee
+++ presentation/Gruntfile.coffee
@@ -51,7 +51,7 @@
                     port: 9000
                     # Change hostname to '0.0.0.0' to access
                     # the server from outside.
-                    hostname: '192.168.1.20'
+                    hostname: '0.0.0.0'
                     base: '.'
                     open: true
                     livereload: true


Fixed timestamp being send in seconds when it should have been sent in milliseconds

--- SingularityUI/app/components/taskSearch/DisplayResults.cjsx
+++ SingularityUI/app/components/taskSearch/DisplayResults.cjsx
@@ -48,8 +48,8 @@
                 deployId : @props.deployId
                 host : @props.host
                 lastTaskStatus : @props.lastTaskStatus
-                startedBefore : @props.startedBefore.unix() if @props.startedBefore
-                startedAfter : @props.startedAfter.unix() if @props.startedAfter
+                startedBefore : @props.startedBefore.valueOf() if @props.startedBefore
+                startedAfter : @props.startedAfter.valueOf() if @props.startedAfter
                 orderDirection : @props.sortDirection
                 count : @props.count
                 page : @props.page


+while -loop

+while -loop

--- lib/client.coffee
+++ lib/client.coffee
@@ -98,10 +98,9 @@
 		return if @zkStart is "starting"
 		@zkStart = "starting"

-		@once 'ready', () ->
-			loop
+		@once 'ready', () =>
+			while @_zkStartListener.length
 				callback = @_zkStartListener.pop()
-				return unless callback
 				callback()

 		@zk.once "connected", (err) =>
@@ -572,9 +571,8 @@
 		return @_prefetchRegionCacheListInProgress[event].push cb if @_prefetchRegionCacheListInProgress[event]

 		@once event, () =>
-			loop
+			while @_prefetchRegionCacheListInProgress[event].length
 				callback = @_prefetchRegionCacheListInProgress[event].pop()
-				return unless callback
 				callback()

 		@_prefetchRegionCacheListInProgress[event] = []


srgb - catch failed csv flash error

srgb - catch failed csv flash error

fixes CNVS-11634

test plan
- in srgb
- click upload CSV
- don't select a file, click upload
- verify that screenreader, JAWS read the flash error

Change-Id: I1fb82fd094e8ee4bcfd4878dd714ba76873449ac
Reviewed-on: https://gerrit.instructure.com/32950
Reviewed-by: Simon Williams <088e16a1019277b15d58faf0541e11910eb756f6@instructure.com>
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
QA-Review: Amber Taniuchi <ef4bfd1e6d278f6bc7d4a478f02be2cb9581a0a5@instructure.com>
Product-Review: Aaron Cannon <25919c673621c006b735dcd5a28df63325948e2b@instructure.com>

--- app/coffeescripts/ember/screenreader_gradebook/controllers/screenreader_gradebook_controller.coffee
+++ app/coffeescripts/ember/screenreader_gradebook/controllers/screenreader_gradebook_controller.coffee
@@ -11,7 +11,7 @@
   'compiled/AssignmentDetailsDialog'
   'compiled/AssignmentMuter'
   'compiled/grade_calculator'
-  ], (ajax, round, userSettings, fetchAllPages, parseLinkHeader, I18n, Ember, _, tz, AssignmentDetailsDialog, AssignmentMuter, GradeCalculator ) ->
+  ], (ajax, round, userSettings, fetchAllPages, parseLinkHeader, I18n, Ember, _, tz, AssignmentDetailsDialog, AssignmentMuter, GradeCalculator) ->

   {get, set, setProperties} = Ember

@@ -52,6 +52,16 @@

   ScreenreaderGradebookController = Ember.ObjectController.extend

+    errors: (->
+      # this is a sad, sad hack
+      # until we can get flash notifications working app-wide for screenreaders
+      if Ember.$('#flash_message_holder li').size() > 0
+        close = Ember.$('#flash_message_holder li a').text().trim()
+        message = Ember.$('#flash_message_holder li').text().replace(close,'').trim()
+        node = Ember.$("#{message}")
+        Ember.$(node).appendTo(Ember.$('#flash_screenreader_holder'))
+    ).on('init')
+
     contextUrl: get(window, 'ENV.GRADEBOOK_OPTIONS.context_url')

     downloadCsvUrl: (->


updated the wysiwig branch

updated the wysiwig branch

--- app/assets/javascripts/cohesive_admin/inputs/_wysiwyg.coffee
+++ app/assets/javascripts/cohesive_admin/inputs/_wysiwyg.coffee
@@ -55,5 +55,5 @@

   CohesiveAdmin.config.froala.config = froala_config
   $.extend($.FroalaEditor.DEFAULTS, froala_config)
-  $('textarea.wysiwyg').froalaEditor()
+  $('textarea.wysiwyg').froalaEditor(toolbarButtons: ['bold', 'italic', 'underline', 'strikeThrough', 'subscript', 'superscript', 'insertLink','|', 'paragraphFormat', 'align', 'formatOL', 'formatUL', 'outdent', 'indent', '|', 'color', 'quote', 'insertHR', '|', 'insertImage', 'insertVideo', 'insertFile', '|', 'undo', 'redo', '|', 'html', '|', 'fullscreen', '|'])
 )


Shutdown and start callback handling

Shutdown and start callback handling

--- src/components/Graph.coffee
+++ src/components/Graph.coffee
@@ -56,20 +56,23 @@
       @network.connect (err) =>
         return @error err if err
         notReady = false
-        for name, process of @network.processes
-          notReady = true unless @checkComponent name, process
+        for name, node of @network.processes
+          notReady = true unless @checkComponent name, node
         do @setToReady unless notReady
     , true

-  start: ->
+  start: (callback) ->
+    unless callback
+      callback = ->
     unless @isReady()
       @on 'ready', =>
-        do @start
+        @start callback
       return

-    return unless @network
-    @started = true
-    @network.start()
+    return callback null unless @network
+    @network.start (err) =>
+      return callback err if err
+      @started = true

   checkComponent: (name, process) ->
     unless process.component.isReady()
@@ -130,8 +133,11 @@
       , 0

   findEdgePorts: (name, process) ->
-    for portName, port of process.component.inPorts
-      continue if not port or typeof port is 'function' or not port.canAttach
+    # FIXME: direct process.component.inPorts/outPorts access is only for legacy compat
+    inPorts = process.component.inPorts.ports or process.component.inPorts
+    outPorts = process.component.outPorts.ports or process.component.outPorts
+
+    for portName, port of inPorts
       targetPortName = @isExportedInport port, name, portName
       continue if targetPortName is false
       @inPorts.add targetPortName, port
@@ -140,8 +146,7 @@
         return if @isStarted()
         do @start

-    for portName, port of process.component.outPorts
-      continue if not port or typeof port is 'function' or not port.canAttach
+    for portName, port of outPorts
       targetPortName = @isExportedOutport port, name, portName
       continue if targetPortName is false
       @outPorts.add targetPortName, port
@@ -154,8 +159,10 @@
   isSubgraph: ->
     true

-  shutdown: ->
-    return unless @network
-    @network.stop()
+  shutdown: (callback) ->
+    unless callback
+      callback = ->
+    return callback null unless @network
+    @network.stop callback

 exports.getComponent = (metadata) -> new Graph metadata


Connect when getting upstream connection

--- src/components/Split.coffee
+++ src/components/Split.coffee
@@ -9,6 +9,8 @@
     @outPorts =
       out: new noflo.ArrayPort()

+    @inPorts.in.on "connect", =>
+      @outPorts.out.connect()
     @inPorts.in.on "begingroup", (group) =>
       @outPorts.out.beginGroup group
     @inPorts.in.on "data", (data) =>


Add closing parenthesis

Add closing parenthesis

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -411,7 +411,7 @@
                         return false

                     return true
-                if event.which is 13 and (event.shiftKey or event.ctrlKey)
+                if event.which is 13 and (event.shiftKey or event.ctrlKey) #Ctrl or shift + enter
                     event.preventDefault()
                     if event.type isnt 'keydown'
                         return true
@@ -425,6 +425,7 @@
             @.$('.suggestion_name_list').html ''

             query_lines = @codemirror.getValue().split '\n'
+
             # Get query before the cursor
             query_before_cursor = ''
             if @codemirror.getCursor().line > 0
@@ -446,11 +447,8 @@
             if (query_before_cursor.match(/\"/g)||[]).length%2 is 1
                 @hide_suggestion()
                 return ''
-
-

             slice_index = @extract_query_first_part query_before_cursor
-            #TODO What is slice_index is -1?
             query = query_before_cursor.slice slice_index

             @query_first_part = query_before_cursor.slice 0, slice_index
@@ -480,20 +478,6 @@

             return false

-        extract_current_function: (query) =>
-            #TODO Handle string
-            start = query.lastIndexOf '.'
-            if start is -1
-                s = query
-            else
-                s = query.slice start+1
-
-            end = s.indexOf '('
-            if end is -1
-                return s
-            else
-                return s.slice 0, end
-
         extract_last_function: (query) =>
             start = 0
             count_dot = 0
@@ -672,9 +656,6 @@
             @unsafe_to_safe_regexstr.push
                 pattern: /\}/g
                 replacement: '\\}'
-
-
-

             @input_query = new DataExplorerView.InputQuery
             @data_container = new DataExplorerView.DataContainer


beaglebone-green-wifi.coffee: Set yocto version to rocko

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

--- beaglebone-green-wifi.coffee
+++ beaglebone-green-wifi.coffee
@@ -38,7 +38,7 @@
 		machine: 'beaglebone-green-wifi'
 		image: 'resin-image-flasher'
 		fstype: 'resinos-img'
-		version: 'yocto-pyro'
+		version: 'yocto-rocko'
 		deployArtifact: 'resin-image-flasher-beaglebone-green-wifi.resinos-img'
 		compressed: true


Remove debug output.

Remove debug output.

--- app/assets/javascripts/app/controllers/candidates.js.coffee
+++ app/assets/javascripts/app/controllers/candidates.js.coffee
@@ -32,7 +32,6 @@
         limit: 9
         bounded: 1
         dedupe: 1
-        debug: 1
         "accept-language": "de"
       )
       processData: true


Don't wait for auto-toggle if the explicit browseEverything() call is seen first.

--- app/assets/javascripts/browse_everything/behavior.js.coffee
+++ app/assets/javascripts/browse_everything/behavior.js.coffee
@@ -183,6 +183,7 @@

   $.fn.browseEverything = (options) ->
     ctx = $(this).data('context')
+    options = $(this).data() unless (ctx? or options?)
     if options?
       ctx = initialize(this[0], options)
       $(this).click () ->
@@ -302,7 +303,9 @@

 auto_toggle = ->
   triggers = $('*[data-toggle=browse-everything]')
-  triggers.each () -> $(this).browseEverything($(this).data())
+  triggers.each () ->
+    ctx = $(this).data('context')
+    $(this).browseEverything($(this).data()) unless ctx?

 if Turbolinks? && Turbolinks.supported
   # Use turbolinks:load for Turbolinks 5, otherwise use the old way


Set payments

Set payments

--- app/assets/javascripts/controllers/calendar/index.js.coffee
+++ app/assets/javascripts/controllers/calendar/index.js.coffee
@@ -11,6 +11,7 @@
     this.firstDate = new Date(response.data.firstDate)
     this.lastDate = new Date(response.data.lastDate)
     this.beginningOfWeek = this.firstDate.beginningOfWeek()
+    this.payments = response.data.payments
     this.weeks = this._calculateWeeks()

     this._buildCalendar()


add better comments

add better comments

--- app/assets/javascripts/hanuman/cloudinary-uploads.js.coffee
+++ app/assets/javascripts/hanuman/cloudinary-uploads.js.coffee
@@ -12,11 +12,17 @@
       version: data.result.version
       crop: 'fill'
       width: 350)
-    # need to add textarea for caption for each photo, attach photo identifier to text area so we can come back and rename apprpriately
+    # need to add a textarea to each photo, name of text area must be survey[observations_attributes][1][observation_photos_attributes][0][description]

-    # loop through the hidden fields, match the photo and description textarea with the hidden input and name text area apprpriately
+    # one photo upload would look like this
+    #survey[observations_attributes][1][observation_photos_attributes][0][photo]
+    #survey[observations_attributes][1][observation_photos_attributes][0][description]

-    # rename the hidden fields as they have
+    # next photo
+    #survey[observations_attributes][1][observation_photos_attributes][1][photo]
+    #survey[observations_attributes][1][observation_photos_attributes][1][description]
+
+    # make sure after first photo, the observation_photos_attributes[0] index is incremented for both photo and description

   $('.cloudinary-fileupload.survey-photo-upload').bind 'fileuploadfail', (e, data) ->
     # append error message


Устанавливается фокус для редактора при вставке  картинки

--- app/assets/javascripts/sitemplate_wysihat_handler.js.coffee
+++ app/assets/javascripts/sitemplate_wysihat_handler.js.coffee
@@ -109,6 +109,7 @@
         }

     insertImage: (url) ->
+      @editor.focus()
       @editor.insertImage(url)
       @adaptContent()
       return false


Allow question marks in do you

Allow question marks in do you

--- scripts/do-you.coffee
+++ scripts/do-you.coffee
@@ -5,5 +5,5 @@
 #   lcaro

 module.exports = (robot) ->
-  robot.respond /do you (.+)$/i, (msg) ->
+  robot.respond /do you (.+)\??$/i, (msg) ->
     msg.reply "yes I do in fact " + msg.match[1]


add social appid stubs

add social appid stubs

--- app/templates/app/scripts/coffee/preprocess_template.coffee
+++ app/templates/app/scripts/coffee/preprocess_template.coffee
@@ -6,6 +6,12 @@
 window.PREPROCESS = {
   mode:"debug"
   GA:"UA-XXXXX-X"
+  social:
+    fb: appID:0
+    vk: appID:0
+    ok:
+      appID:0
+      appKey:'CBAHGDCOABABABABA'
 }
 ###
 // @endif
@@ -19,6 +25,12 @@
 window.PREPROCESS = {
   mode:"testing"
   GA:"UA-XXXXX-X"
+  social:
+    fb: appID:0
+    vk: appID:0
+    ok:
+      appID:0
+      appKey:'CBAHGDCOABABABABA'
 }
 ###
 // @endif
@@ -32,6 +44,12 @@
 window.PREPROCESS = {
   mode:"production"
   GA:"UA-XXXXX-X"
+  social:
+    fb: appID:0
+    vk: appID:0
+    ok:
+      appID:0
+      appKey:'CBAHGDCOABABABABA'
 }
 ###
 // @endif


Move group membership initialization into profiled startup

--- lib/groups.coffee
+++ lib/groups.coffee
@@ -1,5 +1,7 @@
 import {check, Match} from 'meteor/check'
 import {Mongo} from 'meteor/mongo'
+
+import {profilingStartup} from './profiling'

 @wildGroup = '*'
 export anonymousUser = '*'
@@ -138,27 +140,29 @@
       ,
         multi: true

-  Meteor.users.find
-    $or: [
-      roles: $exists: true
+  profilingStartup 'groups.startup', ->
+    Meteor.users.find
+      $or: [
+        roles: $exists: true
+      ,
+        rolesPartial: $exists: true
+      ]
     ,
-      rolesPartial: $exists: true
-    ]
-  ,
-    fields:
-      roles: true
-      rolesPartial: true
-      username: true
-  .observe
-    added: (user) ->
-      membersAddUsername user.username, memberOfGroups user
-    removed: (user) ->
-      membersRemoveUsername user.username, memberOfGroups user
-    changed: (userNew, userOld) ->
-      groupsNew = memberOfGroups userNew
-      groupsOld = memberOfGroups userOld
-      membersRemoveUsername userOld.username, _.difference groupsOld, groupsNew
-      membersAddUsername userNew.username, _.difference groupsNew, groupsOld
+      fields:
+        roles: true
+        rolesPartial: true
+        username: true
+    .observe
+      added: (user) ->
+        membersAddUsername user.username, memberOfGroups user
+      removed: (user) ->
+        membersRemoveUsername user.username, memberOfGroups user
+      changed: (userNew, userOld) ->
+        groupsNew = memberOfGroups userNew
+        groupsOld = memberOfGroups userOld
+        membersRemoveUsername userOld.username, _.difference groupsOld, groupsNew
+        membersAddUsername userNew.username, _.difference groupsNew, groupsOld
+    'Maintaining group membership list'

   Meteor.publish 'groups.members', (group) ->
     check group, String


update

update

--- app/assets/javascripts/common.js.coffee
+++ app/assets/javascripts/common.js.coffee
@@ -1,3 +1,4 @@
+
 widthIsOver850 = () ->
   $("article, .body *, article header").width("500")
   $("#tags, .group_body").show("fadein")
@@ -7,8 +8,8 @@

 responsiveWindow = ()->
   $('iframe').each ->
-  url = $(this).attr("src")
-  $(this).attr("src",url+"?wmode=transparent")
+    url = $(this).attr("src")
+    $(this).attr("src",url+"?wmode=transparent")

   if $(window).width() > 850
     widthIsOver850()


bezier chart tooltips

bezier chart tooltips

--- app/assets/javascripts/d3/bezier.coffee
+++ app/assets/javascripts/d3/bezier.coffee
@@ -88,6 +88,7 @@
         .attr('class', 'serie')
         .attr('d', (d) => @area d.values)
         .style('fill', (d) => d.values[0].color)
+        .attr('data-title', (d) -> d.values[0].label)

       # draw a nice axis
       @y_axis = d3.svg.axis()
@@ -101,6 +102,22 @@
         .attr("transform", "translate(#{@width - 15}, 0)")
         .call(@y_axis)

+      # series tooltips
+      $('path.serie').qtip
+        content:
+          title: -> $(this).attr('data-title')
+          text: -> $(this).attr('data-tooltip')
+        show:
+          event: 'mouseover' # silly IE
+        hide:
+          event: 'mouseout'  # silly IE
+        position:
+          target: 'mouse'
+          my: 'bottom right'
+          at: 'top center'
+        style:
+          classes: "ui-tooltip-bootstrap"
+
     refresh: =>
       # calculate tallest column
       tallest = Math.max(
@@ -127,6 +144,10 @@
         .data(stacked_data, (s) -> s.key)
         .transition()
         .attr('d', (d) => @area d.values)
+        .attr('data-tooltip', (d) => "
+          #{@start_year}: #{Metric.autoscale_value d.values[0].y, @model.get 'unit'}</br>
+          #{@end_year}: #{Metric.autoscale_value d.values[2].y, @model.get 'unit'}
+        ")

     # We need to pass the chart series through the stacking function and the SVG
     # area function. To do this let's format the data as an array. An
@@ -162,25 +183,28 @@
         left_stack += s.safe_present_value()
         right_stack += s.safe_future_value()

+        gquery = s.get 'gquery_key'
+
         mid_year = (@start_year + @end_year) / 2

         out = [
           {
             x: @start_year
             y: s.safe_present_value()
-            id: "#{s.get 'gquery_key'}"
+            id: gquery
             color: s.get('color')
+            label: s.get('label')
           },
           {
             x: mid_year
             y: mid_point
-            id: "#{s.get 'gquery_key'}"
+            id: gquery
             color: s.get('color')
           },
           {
             x: @end_year
             y: s.safe_future_value()
-            id: "#{s.get 'gquery_key'}"
+            id: gquery
             color: s.get('color')
           }
         ]


removed commented out code

removed commented out code

--- app/assets/javascripts/groups.js.coffee
+++ app/assets/javascripts/groups.js.coffee
@@ -11,21 +11,3 @@
       (e) ->
         $(this).text("Membership Requested")
     )
-
-#$ ->
-  #isVisible = false
-  #clickedAway = false
-
-  #$('.info-help').popover(html: true, trigger: 'manual', placement: 'top').click((e) ->
-    #$(this).popover('show')
-    #isVisible = true
-    #e.preventDefault()
-  #)
-
-  #$(document).click((e) ->
-    #if(isVisible & clickedAway)
-      #$('.info-help').popover('hide')
-      #isVisible = clickedAway = false
-    #else
-      #clickedAway = true
-  #)


Remove unnecessary returns

Remove unnecessary returns

--- app/assets/javascripts/layout.js.coffee
+++ app/assets/javascripts/layout.js.coffee
@@ -17,19 +17,15 @@
     # Add focus classes to container divs when an input/textarea is focused on (used for styling)
     $("input").focus ->
       $(this).parent().addClass "focus"
-      return
-
+
     $("input").blur ->
       $(this).parent().removeClass "focus"
-      return

     $("textarea").focus ->
       $(this).parent().addClass "focus"
-      return

     $("textarea").blur ->
       $(this).parent().removeClass "focus"
-      return

     # Make some custom tooltips happen (for form elements only, at least right now)
     $('label').mouseover ->


Giving blank concepts a different graph colour to services.

--- app/assets/javascripts/record.js.coffee
+++ app/assets/javascripts/record.js.coffee
@@ -54,8 +54,10 @@
                 '#0f0'
               when 'activity'
                 '#44f'
+              when 'service'
+                '#f0f'
               else
-                '#f0f'
+                '#444'
           # Spiral outwards rather than random
           angle = 0.5 * node_i++
           node.x = (1 + 1 * angle) * Math.cos(angle)


resource definitions

resource definitions

--- app/assets/javascripts/resources.coffee
+++ app/assets/javascripts/resources.coffee
@@ -2,7 +2,9 @@
   Resources = {}

   Resources.Session = $resource('/sessions/:id')
-  Resources.User = $resource('/users/:id')
+  Resources.User = $resource('/users/:id', null,
+    update: { method: 'PUT' }
+  )
   Resources.Project = $resource('/projects/:id')
   Resources.Currency = $resource('/currencies/:id')


fix navigation between views

fix navigation between views

--- app/assets/javascripts/router.js.coffee
+++ app/assets/javascripts/router.js.coffee
@@ -1,3 +1,3 @@
 Sks.Router.map ->
   this.resource 'users'
-  this.resource 'user', path: ':user_id'+  this.resource 'user', path: '/users/:user_id'


fixed some lines to fit proper context

fixed some lines to fit proper context

--- app/locale/nl-BE.coffee
+++ app/locale/nl-BE.coffee
@@ -1,11 +1,11 @@
 module.exports = nativeDescription: "Nederlands (België)", englishDescription: "Dutch (Belgium)", translation:
   common:
-    loading: "Aan het laden..."
+    loading: "Bezig met laden..."
     saving: "Opslaan..."
     sending: "Verzenden..."
     send: "Verzend"
     cancel: "Annuleren"
-    save: "Opslagen"
+    save: "Opslaan"
     publish: "Publiceren"
     create: "Creëer"
     delay_1_sec: "1 seconde"
@@ -46,7 +46,7 @@
     employers: "Werkgevers"

   versions:
-    save_version_title: "Nieuwe versie opslagen"
+    save_version_title: "Nieuwe versie opslaan"
     new_major_version: "Nieuwe hoofd versie"
     cla_prefix: "Om bewerkingen op te slaan, moet je eerst akkoord gaan met onze"
     cla_url: "CLA"
@@ -308,7 +308,7 @@

   editor:
     main_title: "CodeCombat Editors"
-    main_description: "Maak je eigen levels, campagnes, eenheden en leermateriaal. Wij bieden alle programma's aan die u nodig heeft!"
+    main_description: "Maak je eigen levels, campagnes, eenheden en leermateriaal. Wij bieden alle programma's aan die je nodig hebt!"
     article_title: "Artikel Editor"
     article_description: "Schrijf artikels die spelers een overzicht geven over programmeer concepten die kunnen gebruikt worden over een variëteit van levels en campagnes."
     thang_title: "Thang Editor"


Update pt-BR.coffee

Update pt-BR.coffee

--- app/locale/pt-BR.coffee
+++ app/locale/pt-BR.coffee
@@ -575,7 +575,7 @@
     ready_to_share: "Pronto para publicar seu projeto?"
     click_publish: "Clique em \"Publicar\" para que apareça na galeria da turma, depois confira o que seus colegas construíram! Você pode voltar e continuar trabalhando neste projeto. Qualquer outra alteração será salva automaticamente e compartilhada com seus colegas."
     already_published_prefix: "Suas mudanças foram publicadas na galeria da turma."
-    already_published_suffix: Continue experimentando e tornando este projeto ainda melhor, ou veja o que o resto da sua turma construiu! Suas mudanças serão automaticamente salvas e compartilhadas com seus colegas."
+    already_published_suffix: "Continue experimentando e tornando este projeto ainda melhor, ou veja o que o resto da sua turma construiu! Suas mudanças serão automaticamente salvas e compartilhadas com seus colegas."
     view_gallery: "Visualizar Galeria"
     project_published_noty: "Seu nível foi o publicado!"
     keep_editing: "Continue Editando"


crossbowser detection of document readiness

--- src/renderer.coffee
+++ src/renderer.coffee
@@ -104,27 +104,36 @@
     @iframe.height = @iframe.width = '100%'
     @container.appendChild(@iframe)
     window.test = @iframe
-    doc = @iframe.contentWindow.document
-    @root = doc.createElement('div')
+    @root = this.getDocument().createElement('div')
     Scribe.DOM.addClass(@root, 'editor')
     @root.id = @options.id
     @root.innerHTML = Scribe.Normalizer.normalizeHtml(html) if @options.keepHTML
     this.addStyles(@options.styles)
     this.runWhenLoaded( =>
-      @iframe.contentWindow.document.body.appendChild(@root) # Firefox does not like doc.body
+      this.getDocument().body.appendChild(@root)
     )

+  getDocument: ->
+    # Firefox does not like us saving a reference to this result so retrieve every time
+    # IE does not have contentWindow
+    return @iframe.document or @iframe.contentWindow.document
+
   runWhenLoaded: (fn) ->
-    return fn.call(this) if @iframe.contentWindow.document.readyState == 'complete'
+    return fn.call(this) if this.getDocument().readyState == 'complete'
     if @callbacks?
       @callbacks.push(fn)
     else
       @callbacks = [fn]
-      @iframe.contentWindow.onload = =>
-        _.each(@callbacks, (callback) =>
-          callback.call(this)
-        )
-        @callbacks = undefined
+      interval = setInterval( =>
+        if this.getDocument().readyState == 'complete'
+          clearInterval(interval)
+          _.defer( =>
+            _.each(@callbacks, (callback) =>
+              callback.call(this)
+            )
+            @callbacks = []
+          )
+      , 100)

 module.exports = Scribe


Social: add all created channels to default channels

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -1463,8 +1463,9 @@
     doRequest 'createChannel', client, defaultChannel, (err, channel) =>
       return callback err if err

-      op = { $set:{} }
+      op = { $set: {}, $push: {} }
       op.$set[varName] = channel.channel.id
+      op.$push["defaultChannels"] = channel.channel.id

       @update op, (err) ->
         return callback err if err


fix job runner

fix job runner

--- server/api/job.coffee
+++ server/api/job.coffee
@@ -252,7 +252,7 @@
     job_process.insert pn
   else if proc.order
     job_process.update {job: proc.job, order: proc.order+1}, {available:true}
-  job_job.each 'job.processes.process:'+proc._id, (job) -> API.job.progress(job,300000) if job._id isnt proc._id and not job_processing.get(job._id)?
+  job_job.each 'job.processes.process:'+proc._id, (job) -> API.job.progress(job,(if job.processes.length > 1 then 300000 else 2000)) if job._id isnt proc._id and not job_processing.get(job._id)?
   if proc.callback
     cb = API
     cb = cb[c] for c in proc.callback.replace('API.','').split('.')
@@ -356,7 +356,7 @@
   )
   return ret

-API.job._checking = (jobid) ->
+API.job._progress = (jobid) ->
   job = if typeof jobid is 'object' then jobid else job_job.get jobid
   job_result.remove job._id
   total = job.processes.length
@@ -380,17 +380,14 @@
   job = if typeof jobid is 'object' then jobid else job_job.get jobid
   if job.new or job.done
     return {createdAt:job.createdAt, progress:(if job.done then 100 else 0), name:job.name, email:job.email, _id:job._id, new:job.new}
-  else if checking = job_processing.get job._id
-    while checking
-      future = new Future()
-      Meteor.setTimeout (() -> future.return()), 3000
-      future.wait()
-      checking = job_processing.get job._id
-    checked = job_result.get job._id
-    return checked.result['API.job._checking']
+  else if job_processing.get job._id
+    if checked = job_result.get job._id
+      return checked.result['API.job._progress']
+    else
+      return {createdAt:job.createdAt, progress:0, name:job.name, email:job.email, _id:job._id, new:false}
   else
     API.log msg: 'Checking job progress of ' + job._id, level: 'debug'
-    return API.job.process({_id: job._id, group: 'PROGRESS', limit: limit ? 2000, function: 'API.job._checking', args: job._id}).result['API.job._checking']
+    return API.job.process({_id: job._id, group: 'PROGRESS', limit: limit ? 2000, function: 'API.job._progress', args: job._id}).result['API.job._progress']

 API.job.rerun = (jobid,uid) ->
   job = job_job.get jobid


ssh to the correct host for stopping

ssh to the correct host for stopping

--- server/cluster.coffee
+++ server/cluster.coffee
@@ -3,7 +3,7 @@
   cmd = JSON.stringify cmd
   console.log options, Settings.isAdmin()
   console.log 'command on the docker host ->', cmd
-  if Settings.isAdmin() and options.targetHost
+  if options.targetHost
     cmd = JSON.stringify "ssh core@#{options.targetHost} #{cmd}"
   else
     cmd = JSON.stringify "#{Meteor.settings.coreos.ssh} #{cmd}"


Vectors are defined as arrays as discussed in #1

--- components/Vector.coffee
+++ components/Vector.coffee
@@ -13,7 +13,7 @@
       coords: new noflo.Port 'array'

     @outPorts =
-      vector: new noflo.Port 'object'
+      vector: new noflo.Port 'array'

     @inPorts.x.on 'data', (data) =>
       @x = data


dont load facebook if we dont have a fb app

--- src/remote/services/facebook.coffee
+++ src/remote/services/facebook.coffee
@@ -102,18 +102,21 @@

   loadFbSdk: (config)->
     dfd = promises.deferred()
-    fb = document.createElement 'script'
-    fb.type = 'text/javascript'
-    fb.async = true
-    fb.src =  "https://connect.facebook.net/en_US/all.js"
-    (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(fb);
     config = assign({},config,{status:true})
-    window.fbAsyncInit = ()=>
-      @fb = true
-      FB.init config
-      FB.getLoginStatus @updateFBUserStatus
-      @subscribeToFBEvents()
-      dfd.resolve({})
+    if config.appId
+      fb = document.createElement 'script'
+      fb.type = 'text/javascript'
+      fb.async = true
+      fb.src =  "https://connect.facebook.net/en_US/all.js"
+      (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(fb);
+      window.fbAsyncInit = ()=>
+        @fb = true
+        FB.init config
+        FB.getLoginStatus @updateFBUserStatus
+        @subscribeToFBEvents()
+        dfd.resolve({})
+    else
+      dfd.reject({})
     dfd.promise


Adapt "setWatchState" method.

Adapt "setWatchState" method.

--- client/ide/lib/views/statusbar/idestatusbaravatarview.coffee
+++ client/ide/lib/views/statusbar/idestatusbaravatarview.coffee
@@ -179,6 +179,9 @@

     kd.singletons.appManager.tell 'IDE', methodName, @nickname

+    if state
+      kd.singletons.appManager.tell 'IDE', 'showConfirmToSyncLayout', @nickname
+

   setReadOnlyState: (state) ->

@@ -187,9 +190,6 @@

     kd.singletons.appManager.tell 'IDE', methodName, @nickname, permission

-    if shouldWatch
-      kd.singletons.appManager.tell 'IDE', 'showConfirmToSyncLayout', nickname
-

   destroy: ->


groups: markdown heading parser

groups: markdown heading parser

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

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

     @checkGroupUserRelation()
     @attachListeners()

+
     @registerSingleton 'staticGroupController', @, yes

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

   reviveViews :->

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

   checkGroupUserRelation:->


fixed publish.coffee userId refs.

fixed publish.coffee userId refs.

--- server/publish.coffee
+++ server/publish.coffee
@@ -7,7 +7,7 @@
 )

 Meteor.publish("my_posts", ->
-  user_id = Session.get('user_id')
+  user_id = Meteor.call('userId')
   if user_id
     q = Posts.find( author_id: user_id )
     Session.set( 'my_posts_query', q)
@@ -15,7 +15,7 @@
 )

 Meteor.publish("assigned_posts", ->
-  user_id = Session.get('user_id')
+  user_id = Meteor.call('userId')
   if user_id
     ids = []
     for item in Session.get( 'my_posts_query' ).fetch() #For each of my posts
@@ -30,6 +30,3 @@
     )
 )

-Meteor.startup( ->
-  Session.set 'user_id', Users.findOne( {} )['_id']
-)


Soft delete IRs associated with deleted events

--- server/startup.coffee
+++ server/startup.coffee
@@ -61,3 +61,10 @@
     if disease
       Incidents.update incident._id,
         $set: disease: disease
+
+  # Soft delete incident reports of deleted user events
+  UserEvents.find({deleted: true}, {fields: _id:1}).forEach (event) ->
+    Incidents.update {userEventId: event._id, deleted: {$in: [null, false]}},
+      $set:
+        deleted: true
+        deletedDate: new Date()


just making sure API keys work

just making sure API keys work

--- src/scripts/opendns-umbrella.coffee
+++ src/scripts/opendns-umbrella.coffee
@@ -13,37 +13,34 @@
 # Author:
 #   Scott J Roberts - @sroberts

-VIRUSTOTAL_API_KEY = process.env.VIRUSTOTAL_API_KEY
-vt_url = "https://www.virustotal.com"
-
-vt_file_report_url = vt_url + "/vtapi/v2/file/report"
-vt_url_report_url = vt_url + "/vtapi/v2/url/report"
-vt_ip_report_url = vt_url + "/vtapi/v2/ip-address/report"
+VIRUSTOTAL_API_KEY = process.env.OPENDNS_KEY
+opendns_base = "https://investigate.api.opendns.com/"

 module.exports = (robot) ->
-  robot.respond /virustotal hash (.*)/i, (msg) ->
+  robot.respond /opendns (.*)/i, (msg) ->

-    if VIRUSTOTAL_API_KEY?
-      hash = msg.match[1].toLowerCase()
-      data = "apikey=#{encodeURIComponent VIRUSTOTAL_API_KEY}&resource=#{encodeURIComponent hash}"
-
-      robot.http(vt_file_report_url)
-        .post(data) (err, res, body) ->
-          if res.statusCode is 200
-            vt_json = JSON.parse(body)
-
-            if vt_json.response_code == 1
-              summary = """VirusTotal Result: #{vt_json.resource}
-              - Scanned at: #{vt_json.scan_date}
-              - Results:    #{vt_json.positives}/#{vt_json.total}
-              - Link:       #{vt_json.permalink}
-              """
-
-              msg.send summary
-
-            else
-              msg.send "VirusTotal URL Analysis: #{vt_json.verbose_msg}"
-          else
-            msg.send "Error: Couldn't access #{vt_url}."
+    if OPENDNSKEY_KEY?
+      msg.send "#{OPENDNS_KEY}"
+    #   hash = msg.match[1].toLowerCase()
+    #   data = "apikey=#{encodeURIComponent VIRUSTOTAL_API_KEY}&resource=#{encodeURIComponent hash}"
+    #
+    #   robot.http(vt_file_report_url)
+    #     .post(data) (err, res, body) ->
+    #       if res.statusCode is 200
+    #         vt_json = JSON.parse(body)
+    #
+    #         if vt_json.response_code == 1
+    #           summary = """VirusTotal Result: #{vt_json.resource}
+    #           - Scanned at: #{vt_json.scan_date}
+    #           - Results:    #{vt_json.positives}/#{vt_json.total}
+    #           - Link:       #{vt_json.permalink}
+    #           """
+    #
+    #           msg.send summary
+    #
+    #         else
+    #           msg.send "VirusTotal URL Analysis: #{vt_json.verbose_msg}"
+    #       else
+    #         msg.send "Error: Couldn't access #{vt_url}."
     else
-      msg.send "VirusTotal API key not configured. Get one at https://www.virustotal.com/en/user/ in the API tab"
+      msg.send "OpenDNS API key not configured."


Add setInvisibles method

Add setInvisibles method

--- src/editor-component.coffee
+++ src/editor-component.coffee
@@ -1,6 +1,6 @@
 React = require 'react'
 {div, span} = require 'reactionary'
-{debounce} = require 'underscore-plus'
+{debounce, defaults} = require 'underscore-plus'
 scrollbarStyle = require 'scrollbar-style'

 GutterComponent = require './gutter-component'
@@ -268,6 +268,7 @@
     @subscribe atom.config.observe 'editor.fontFamily', @setFontFamily
     @subscribe atom.config.observe 'editor.fontSize', @setFontSize
     @subscribe atom.config.observe 'editor.showIndentGuide', @setShowIndentGuide
+    @subscribe atom.config.observe 'editor.invisibles', @setInvisibles

   measureScrollbars: ->
     @measuringScrollbars = false
@@ -290,6 +291,22 @@

   setShowIndentGuide: (showIndentGuide) ->
     @setState({showIndentGuide})
+
+  # Public: Defines which characters are invisible.
+  #
+  # invisibles - An {Object} defining the invisible characters:
+  #   :eol   - The end of line invisible {String} (default: `\u00ac`).
+  #   :space - The space invisible {String} (default: `\u00b7`).
+  #   :tab   - The tab invisible {String} (default: `\u00bb`).
+  #   :cr    - The carriage return invisible {String} (default: `\u00a4`).
+  setInvisibles: (invisibles={}) ->
+    defaults invisibles,
+      eol: '\u00ac'
+      space: '\u00b7'
+      tab: '\u00bb'
+      cr: '\u00a4'
+
+    @setState({invisibles})

   onFocus: ->
     @refs.scrollView.focus()


fixed an error in date_demo

fixed an error in date_demo


fix https://github.com/sagemathinc/cocalc/issues/2497 - copy from snapshots and permissions

--- src/smc-webapp/project_store.coffee
+++ src/smc-webapp/project_store.coffee
@@ -926,10 +926,23 @@
         id = opts.id ? misc.uuid()
         @set_activity(id:id, status:"Copying #{opts.src.length} #{misc.plural(opts.src.length, 'file')} to #{opts.dest}")

+        args = ['-rltgoDxH']
+
+        # We ensure the target copy is writable if *any* source path starts with .snapshots.
+        # See https://github.com/sagemathinc/cocalc/issues/2497
+        # This is a little lazy, but whatever.
+        for x in opts.src
+            if misc.startswith(x, '.snapshots')
+                args = args.concat(['--perms', '--chmod', 'u+w'])
+                break
+
+        args = args.concat(opts.src)
+        args = args.concat([opts.dest])
+
         webapp_client.exec
             project_id      : @project_id
             command         : 'rsync'  # don't use "a" option to rsync, since on snapshots results in destroying project access!
-            args            : ['-rltgoDxH'].concat(opts.src).concat([opts.dest])
+            args            : args
             timeout         : 120   # how long rsync runs on client
             network_timeout : 120   # how long network call has until it must return something or get total error.
             err_on_exit     : true