clean up for extension api refinements

clean up for extension api refinements

--- lib/index.coffee
+++ lib/index.coffee
@@ -33,7 +33,6 @@
       @write = if @concat then !@extract else true

     fs: ->
-      category: @category
       extract: @extract
       ordered: false
       detect: (f) => minimatch(f.relative, @pattern)
@@ -50,7 +49,7 @@

     # before the last pass, save out the original content
     before_hook = (ctx) ->
-      if @category == ctx.file.category && ctx.index == ctx.file.adapters.length
+      if ctx.index == ctx.file.adapters.length
         ctx.file.original_content = ctx.file.content
         # we client-compile the file by ourselves anyway,
         # so letting roots compile it would be useless
@@ -59,8 +58,6 @@
         ctx.content = ""

     after_hook = (ctx) ->
-      if @category != ctx.category then return
-
       # last valid adapter is assumed to be your precompile target
       adapter = _.find(_.clone(ctx.adapters).reverse(), (a) -> a.name)

@@ -88,9 +85,6 @@
         return @write

     write_hook = (ctx) ->
-      # if out of category, write as usual
-      if @category != ctx.category then return true
-
       # if concat is true, prevent write
       if @concat then return false

@@ -98,8 +92,6 @@
       return { extension: 'js' }

     after_category = (ctx, category) ->
-      if @category != category then return
-
       tasks = []

       # for each category in templates


Updating "glob" lib and adding error catch

Updating "glob" dependency to latest to get more glob options.
Added if err check after stat call for when a full path is passed
instead of file names with cwd option set.
Passing in "c:\path\file.txt" runs a stat on "c:\c:\path\file.txt" then
returns undefined. That caused a crashing error when it tried to follow
with if stats.isFile().

--- lib/index.coffee
+++ lib/index.coffee
@@ -34,6 +34,7 @@
       return callback err if err
       files.forEach (file, i) =>
         fs.stat "#{cwd}/#{file}", (err, stats) =>
+          return callback err if err
           if stats.isFile()
             do (file) =>
               @fileList.push file


deployタスクを追加

deployタスクを追加

--- lib/index.coffee
+++ lib/index.coffee
@@ -114,6 +114,12 @@
           console.log(result.stack) if result?.stack?
           # callback(result)

+    gulp.task "deploy", (callback)->
+      return runSequence(
+        ["build-zip"],
+        ["deploy-to-aws-lambda"],
+        callback)
+
     # TODO: 「lambduh-gulp」に依存しないように修正する。
     lambduhGulp(gulp)


Search by title first then fall back to body

--- src/hubot-confluence.coffee
+++ src/hubot-confluence.coffee
@@ -8,21 +8,37 @@
     .env()
     .file('defaults', DEFAULTS_FILE)

-search = (msg, query) ->
+search = (msg, query, text) ->
+

   num_results = nconf.get("HUBOT_CONFLUENCE_NUM_RESULTS") or 1
   space = nconf.get("HUBOT_CONFLUENCE_SEARCH_SPACE")
-  suffix = "content/search?cql=type=page&space=#{space}&query=#{query}"
+  if text
+    text_search = "text~\"#{query}\""
+  else
+    text_search = "title~\"#{query}\""
+
+  query_str = encodeURIComponent("type=page and space=#{space} and #{text_search}")
+  suffix = "/content/search?cql=#{query_str}"
   url = make_url(suffix)
   headers = make_headers()

-  msg.http(url).headers(headers).get() (e, res, body) ->
+  msg.http(url).headers(headers).get() (e, res, body) ->
     if e
       msg.send "Error: #{e}"
       return

     content = JSON.parse(body)

+    if !content.results or content.results.length == 0
+      #Fall back to text search
+      if !text
+        search(msg, query, true)
+        return
+      else
+        msg.send "No results found"
+        return
+
     count = 0
     for result in content.results
       count += 1
@@ -30,7 +46,7 @@
         break
       link = make_url(result._links.webui)
       msg.send "#{result.title} - #{link}"
-
+
 make_headers = ->

   user = nconf.get("HUBOT_CONFLUENCE_USER")
@@ -47,7 +63,7 @@
     host = nconf.get("HUBOT_CONFLUENCE_HOST")
     port = nconf.get("HUBOT_CONFLUENCE_PORT")

-    "https://#{host}:#{port}/wiki/rest/api/#{suffix}"
+    "https://#{host}:#{port}/wiki/rest/api#{suffix}"

 help = (msg) ->
   commands = [
@@ -59,11 +75,11 @@
     buf += "#{command}\n"

   msg.send buf
+
 module.exports = (robot) ->

   robot.hear /confluence search (.*)/i, (msg) ->
-    msg.send msg.match[1]
-    search(msg, msg.match[1])
+    search(msg, msg.match[1], false)

   robot.hear /confluence help/i, (msg) ->
     help(msg)


Improve error messages with Latexmk is not found.

Not being an atom expert, I ran into some trouble getting the latex
plugin to build my document. It was very hard for me to figure out from
the error messages that I simply needed to update the Tex Path setting
of the plugin. With this modification, users should be able to resolve
the same problem faster.

I kept the modification as simple as possible so it will need updating
as more builders are added.

--- lib/latex.coffee
+++ lib/latex.coffee
@@ -28,8 +28,26 @@
       @destroyProgressIndicator()
       if statusCode == 0
         @showResult()
+      else if statusCode == 127
+        @showError(
+          """
+          TeXification failed! Builder executable not found.
+
+            latex.texPath
+              as configured: #{atom.config.get("latex.texPath")}
+              when resolved: #{builder.constructPath()}
+
+          Make sure latex.texPath is configured correctly; either adjust it \
+          via the settings view, or directly in your config.cson file.
+          """
+        )
       else
-        @showError("TeXification failed! Check the log file for more info...")
+        @showError(
+          """
+          TeXification failed with status code #{statusCode}! \
+          Check the log file for more info...
+          """
+        )

     return


Singular classifications_export is updated_at

--- app/pages/lab/project-details.cjsx
+++ app/pages/lab/project-details.cjsx
@@ -162,7 +162,7 @@
               if mostRecent?

                   Most recent data available requested{' '}
-                  {moment(mostRecent.created_at).fromNow()}.
+                  {moment(mostRecent.updated_at).fromNow()}.

               else
                 Never requested.


Add a class for the total number

Add a class for the total number

--- ui/widgets.coffee
+++ ui/widgets.coffee
@@ -70,7 +70,7 @@
       (li {className: 'pass'}, passing)
       (li {className: 'fail'}, failing)
       (li {className: 'skip'}, skipped)
-      (li {}, total)
+      (li {className: 'total'}, total)
     ])

 TestStatus = React.createFactory TestStatusClass


client: IDE: show relevant warning if not allowed to create stacks

--- client/app/lib/nostackfoundview.coffee
+++ client/app/lib/nostackfoundview.coffee
@@ -2,6 +2,8 @@
 React = require 'kd-react'
 globals = require 'globals'
 ReactView = require 'app/react/reactview'
+canCreateStacks = require 'app/util/canCreateStacks'
+isAdmin = require 'app/util/isAdmin'

 module.exports = class NoStackFoundView extends ReactView

@@ -27,12 +29,12 @@

   renderReact: ->

-    { groupsController, computeController, mainView } = kd.singletons
+    { computeController, mainView } = kd.singletons

     return
  unless globals.userRoles
     return
  if mainView.introVideoViewIsShown

-    if groupsController.canEditGroup()
+    if isAdmin()

Create a stack for your team

@@ -44,6 +46,17 @@

           <button className='GenericButton' onClick={@bound 'onClick'}>CREATE A TEAM STACK</button>

+

+
+    else if not canCreateStacks()
+
+

+
Your Team Stack is Pending

+

+          Your team admins haven't created your stack yet.
+          Unfortunately there is not much you can do at this moment.
+          Please contact your team admin.
+

     else


BUG FIX: fix modal trigger problem under turbolinks

--- app/assets/javascripts/blocks/modal.coffee
+++ app/assets/javascripts/blocks/modal.coffee
@@ -10,8 +10,8 @@
   #
   # <button class="js-modal-trigger" data-modal-target="#target">

-  $modal = $("
").appendTo('body')
-  $overlay = $('
').appendTo($modal)
+  $modal = $("
")
+  $overlay = $('
')
   $overlay.css {
     display: "none"
     position: "fixed"
@@ -32,7 +32,8 @@
     if $currentTarget != null
       $currentTarget.hide()
     $currentTarget = $(target)
-    $overlay.show()
+    $modal.appendTo('body')
+    $overlay.appendTo($modal).show()
     $currentTarget.show()

   $modal.on "hide", (e) ->
@@ -41,6 +42,7 @@
     $currentTarget = null

-  $(".js-modal-trigger").on "click", ->
-    target = $(this).data("modal-target")
-    $modal.trigger("show",target)+  $(document)
+    .on 'click', ".js-modal-trigger", ->
+      target = $(this).data("modal-target")
+      $modal.trigger("show",target)


Moved language autocomplete to bootstrap as was deleted from projects

--- app/assets/javascripts/bootstrap.js.coffee
+++ app/assets/javascripts/bootstrap.js.coffee
@@ -7,4 +7,16 @@

   $("#presents a").click (e) ->
     e.preventDefault()
-    $(this).tab "show"+    $(this).tab "show"
+
+
+  languageAutocomplete = ->
+    return false if !$("[data-provide=typeahead]").length
+
+    projectLanguages = $("[data-provide=typeahead]").data "source"
+
+    $("[data-provide=typeahead]").typeahead
+      name: $(this).attr "name"
+      local: projectLanguages
+
+  languageAutocomplete()


OX-729 started panZoom for svg

OX-729 started panZoom for svg

--- app/assets/javascripts/main/routing.coffee
+++ app/assets/javascripts/main/routing.coffee
@@ -3,6 +3,7 @@
 underscore : _
 libs/toast : Toast
 libs/keyboard : KeyboardJS
+libs/input : Input
 main/routing_utils : RoutingUtils
 oxalis/constants : constants
 ###
@@ -121,7 +122,43 @@
             $svg[0].setAttribute("height", "100%")
             $svg.css("max-width", "100%")

-            $svg.svgPan("graph1")
+	    #$svg.svgPan("graph1")
+	    panZoom = (delta, position) ->
+
+	      svgElement = $svg[0]
+
+	      if position
+
+		mouse =
+		  x: position[0] - $svg.offset().left
+		  y: position[1] - $svg.offset().top
+
+	      else
+
+		mouse =
+		  x: $svg.width() / 2
+		  y: $svg.height() / 2
+
+	      scale = 1 #zoomLevel / @oldZoomLevel
+
+	      p = svgElement.createSVGPoint()
+	      p.x = mouse.x
+	      p.y = mouse.y
+
+	      p.matrixTransform(@transformationGroup.getCTM().inverse())
+
+	      transformationMatrix = svgElement.createSVGMatrix()
+		.translate(p.x, p.y)
+		.scale(scale)
+		.translate(-p.x, -p.y)
+
+	      matrix = svgElement.getCTM().multiply(transformationMatrix)
+	      matrixString = "#{matrix.a} #{matrix.b} #{matrix.c} #{matrix.d} #{matrix.e} #{matrix.f}"
+	      $svg.attr("transform", "matrix(#{matrixString})")
+	      #@oldZoomLevel = zoomLevel
+
+	    mouse = new Input.Mouse($svg)
+	    mouse.on("leftDownMove", panZoom)

           (error) ->
             $(".graph").html(" #{error.replace(/\n/g,"
")}")


Teamwork: Added export button to finder item.

--- client/CollaborativeWorkspace/panes/collaborativefinderpane.coffee
+++ client/CollaborativeWorkspace/panes/collaborativefinderpane.coffee
@@ -1,3 +1,9 @@
+class TeamworkFinderItem extends NFinderItem
+
+  removeHighlight: ->
+    super
+    @exporter?.destroy()
+
 # TODO: Should implement non-collaborative finder
 # TODO: Should extend this class from NonCollab one.

@@ -15,6 +21,7 @@
       contextMenu         : yes
       useStorage          : no
       treeControllerClass : CollaborativeFinderTreeController
+      treeItemClass       : TeamworkFinderItem

     @container?.destroy()
     @finder = @container = @finderController.getView()
@@ -51,6 +58,9 @@

     @finderController.treeController.on "HistoryItemCreated", (historyItem) =>
       @workspace.addToHistory historyItem
+
+    @finderController.treeController.on "ExportRequested", (node) =>
+      new TeamworkExportModal {}, node

   syncContent: (files) ->
     @workspaceRef.set { files }
@@ -104,3 +114,14 @@
     file = nodeView.getData()
     file.fetchContents (err, contents) =>
       @getDelegate().emit "OpenedAFile", file, contents
+
+  selectNode: (node) ->
+    super
+
+    return if node.getData() instanceof FSVm
+
+    node.exporter = new KDCustomHTMLView
+      cssClass    : "tw-export"
+      click       : => @emit "ExportRequested", node
+
+    node.addSubView node.exporter


tutorial FTW

tutorial FTW


Allow column stacking graphs to be used

Allow column stacking graphs to be used

--- app/assets/javascripts/variables.js.coffee
+++ app/assets/javascripts/variables.js.coffee
@@ -22,7 +22,7 @@
           text: json['units']
       tooltip:
         headerFormat: '{point.key}
'
-        pointFormat: '
{series.name}: 	' + "{point.y:.1f} #{json['units']}
"
+        pointFormat: '
{series.name}: 	' + "{point.y} #{(if json['stacking'] then '({point.percentage:.0f}%)' else json['units'])}
"
         footerFormat: '
'
         shared: true,
         useHTML: true
@@ -31,8 +31,9 @@
           pointPadding: 0.2
           borderWidth: 0
           dataLabels:
-            enabled: true
+            enabled: (if json['stacking'] then false else true)
           # enableMouseTracking: false
+          stacking: json['stacking'] #'percent'
       series: json['series']
     )
   else


properly handle lack of datacb in calendar GetEvents

Change-Id: I39353fce2b4010f3c4ff0c4a9485c5f9d7683987
Reviewed-on: https://gerrit.instructure.com/78838
Tested-by: Jenkins
Reviewed-by: Steven Burnett <a0b450117c5d20a22d5c4d52bb8ae13adee003d8@instructure.com>
Product-Review: Joel Hough <4816dabf8db1bc6cac35b3a24cab2ff844b5b0c7@instructure.com>
QA-Review: Joel Hough <4816dabf8db1bc6cac35b3a24cab2ff844b5b0c7@instructure.com>

--- app/coffeescripts/calendar/Calendar.coffee
+++ app/coffeescripts/calendar/Calendar.coffee
@@ -191,7 +191,7 @@
             donecb([])
           else
             donecb(calendarEventFilter(@displayAppointmentEvents, events))
-      , datacb? && (events) =>
+      , datacb && (events) =>
         datacb(calendarEventFilter(@displayAppointmentEvents, events))

     # Close all event details popup on the page and have them cleaned up.


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/clamp_bounds.coffee
+++ src/clamp_bounds.coffee
@@ -5,7 +5,7 @@
     width: 32
     height: 32

-  App.wi
+  Ap

   self.bind "update", ->
     I.x = I.x.clamp(I.width / 2, App.width - I.width / 2)


return 404 if blog returns 403, caused by file not existing

--- services/web/app/coffee/Features/Blog/BlogController.coffee
+++ services/web/app/coffee/Features/Blog/BlogController.coffee
@@ -20,7 +20,7 @@

 		logger.log url:url, "proxying request to blog api"
 		request.get blogUrl, (err, r, data)->
-			if r?.statusCode == 404
+			if r?.statusCode == 404 or r?.statusCode == 403
 				return ErrorController.notFound(req, res, next)
 			if err?
 				return res.send 500


Spectate mode starts paused until loading is done, and starts at time 1, so there is less stuttering during playback.

--- app/views/play/SpectateView.coffee
+++ app/views/play/SpectateView.coffee
@@ -158,7 +158,8 @@
     # Don't remove it; we want its decoration around on large screens.
     #@removeSubView @loadingView
     #@loadingView = null
-    Backbone.Mediator.publish 'level:set-playing', playing: true
+    Backbone.Mediator.publish 'level:set-playing', playing: false
+    Backbone.Mediator.publish 'level:set-time', time: 1  # Helps to have perhaps built a few Thangs and gotten a good list of spritesheets we need to render for our initial paused frame

   onSupermodelLoadedOne: =>
     @modelsLoaded ?= 0
@@ -256,6 +257,7 @@
     startFrame = @lastWorldFramesLoaded ? 0
     if @world.frames.length is @world.totalFrames  # Finished loading
       @lastWorldFramesLoaded = 0
+      Backbone.Mediator.publish 'level:set-playing', playing: true  # Since we paused at first, now we autostart playback.
     else
       @lastWorldFramesLoaded = @world.frames.length
     for [spriteName, message] in @world.thangDialogueSounds startFrame


No longer disable address when no location is available

--- app/assets/javascripts/mweb_events/app/events/_form.js.coffee
+++ app/assets/javascripts/mweb_events/app/events/_form.js.coffee
@@ -12,18 +12,6 @@

     # Time zone
     $("#event_time_zone").select2()
-
-    # Location
-    if $("#event_location")[0].value is ""
-        $("#event_address").attr('disabled', true)
-
-    enable_address = ->
-      if @value isnt ""
-        $("#event_address").removeAttr("disabled")
-      else
-        $("#event_address").attr("disabled", true)
-
-    $("#event_location").on 'input keyup', enable_address

     # Description editor box
     opts =


Prevented auto-sorting of d3 pie charts, closes #532

--- app/assets/javascripts/reports/display_types/pie_chart.coffee
+++ app/assets/javascripts/reports/display_types/pie_chart.coffee
@@ -8,7 +8,7 @@
       colors[i] = randomBlue() if !(i%2)
       i++

-  pie = d3.layout.pie()
+  pie = d3.layout.pie().sort(null)
   pie(data)
   arc = d3.svg.arc()
     .innerRadius(0)


put the article infinite scroll object for a/b test back

--- src/desktop/components/split_test/running_tests.coffee
+++ src/desktop/components/split_test/running_tests.coffee
@@ -43,4 +43,8 @@
       'control'
       'market'
     ]
+  article_infinite_scroll:
+    key: 'article_infinite_scroll'
+    outcomes:
+      control: 100
 }


Use updateSongs for pause+play calls

Use updateSongs for pause+play calls

--- app/frontend/scripts/application.js.coffee
+++ app/frontend/scripts/application.js.coffee
@@ -23,33 +23,25 @@

 $(document).ready () ->
   $('#play').click () ->
-    $.ajax
-      type: 'PUT',
-      url: '/play',
-      success: (response) ->
-        $('#songs').html('done')
+    updateSongs("/play", "PUT")
     false

   $('#pause').click () ->
-    $.ajax
-      type: 'PUT',
-      url: '/pause',
-      success: (response) ->
-        $('#songs').html('done')
+    updateSongs("/pause", "PUT")
     false

   $('#previous').click () ->
     updateSongs("/previous", "PUT")
-    return false
+    false

   $('#next').click () ->
     updateSongs("/next", "PUT")
-    return false
+    false

   $('#search').submit () ->
     keyword = $('#search').find('input')[0].value
     updateSongs("/search?q=#{keyword}", "GET")
-    return false
+    false

 # Update the Songs listing with, you know, songs.
 #


CustomPartials: Set syntax for editor.

CustomPartials: Set syntax for editor.

--- client/Dashboard/views/homepages/homepagesdashboardview.coffee
+++ client/Dashboard/views/homepages/homepagesdashboardview.coffee
@@ -30,6 +30,10 @@
       title       : "SAVE"
       cssClass    : "solid green"
       callback    : @bound "addNew"
+
+    @editor.on "viewAppended", =>
+      @editor.ace.on "ace.ready", =>
+        @editor.ace.setSyntax "html"

   addNew: ->
     isUpdate       = @getData()


FinderController:deleteFiles remove async.parallel dependency, uses promises ( kudos @chris )

--- client/Finder/filetree/controllers/findertreecontroller.coffee
+++ client/Finder/filetree/controllers/findertreecontroller.coffee
@@ -225,20 +225,20 @@

   deleteFiles:(nodes, callback)->

-    stack = []
-    nodes.forEach (node)=>
-      stack.push (cb) =>
-        node.getData().remove (err, response)=>
-          if err then @notify null, null, err
-          else
-            node.emit "ItemBeingDeleted"
-            cb err, node
-
-    async.parallel stack, (error, result) =>
-      @notify "#{result.length} item#{if result.length > 1 then 's' else ''} deleted!", "success"
-      @removeNodeView node for node in result
-      callback?()
-
+    deletedNodes = []
+    results = nodes.map (node) ->
+      node.getData().remove().then ->
+        node.emit "ItemBeingDeleted"
+        deletedNodes.push node
+
+    Promise.all(results).then =>
+      @notify "#{deletedNodes.length} item#{if deletedNodes.length > 1 then 's' else ''} deleted!", "success"
+      @removeNodeView node for node in deletedNodes
+
+    .catch (err) =>
+      @notify null, null, err
+
+    .nodeify callback

   showRenameDialog:(nodeView)->


FinderController:duplicateFiles remove async.parallel dependency, uses promises

--- client/finder/filetree/controllers/findertreecontroller.coffee
+++ client/finder/filetree/controllers/findertreecontroller.coffee
@@ -326,25 +326,25 @@

   duplicateFiles:(nodes, callback)->

-    stack = []
-    nodes.forEach (node)=>
-      stack.push (cb) =>
-        sourceItem = node.getData()
-        targetItem = @nodes[sourceItem.parentPath].getData()
-        FSItem.copy sourceItem, targetItem, (err, response)=>
-          if err then @notify null, null, err
-          else
-            cb err, node
-
-    callback or= (error, result) =>
-      @notify "#{result.length} item#{if result.length > 1 then 's' else ''} duplicated!", "success"
+    duplicatedNodes = []
+    results = nodes.map (node) =>
+      sourceItem = node.getData()
+      targetItem = @nodes[sourceItem.parentPath].getData()
+      FSItem.copy(sourceItem, targetItem).then ->
+        duplicatedNodes.push node
+
+    Promise.all(results).then =>
+      @notify "#{duplicatedNodes.length} item#{if duplicatedNodes.length > 1 then 's' else ''} duplicated!", "success"
       parentNodes = []
-      result.forEach (node)=>
+      duplicatedNodes.forEach (node)=>
         parentNode = @nodes[node.getData().parentPath]
         parentNodes.push parentNode unless parentNode in parentNodes
       @refreshFolder parentNode for parentNode in parentNodes

-    async.parallel stack, callback
+    .catch (err) =>
+      @notify null, null, err
+
+    .nodeify callback

   compressFiles:(nodeView, type)->


Add Github API client_id and client_secret parameters.

This greatly increases the rate limit from 60/hour to 5000/hour.

--- viewer/app.coffee
+++ viewer/app.coffee
@@ -9,7 +9,18 @@

 getGist = (url, cb) ->
     GIST_API = 'https://api.github.com/gists'
-    post_req = rest.get(GIST_API + url)
+
+    # Adding these parameters greatly increases the rate limit.
+    if process.env.GITHUB_CLIENT_ID and process.env.GITHUB_CLIENT_SECRET
+        params =
+            client_id       : process.env.GITHUB_CLIENT_ID
+            client_secret   : process.env.GITHUB_CLIENT_SECRET
+    else
+        params = null
+
+    post_req = rest.get GIST_API + url,
+        query: params
+
     post_req.on 'error', (err, response) ->
         sys.puts('getGist error:')
         sys.puts(err)


Adds support for multiple time blocks

Adds support for multiple time blocks

--- models/partner_show.coffee
+++ models/partner_show.coffee
@@ -192,19 +192,22 @@
   formatDaySchedule: (day) ->
     if @daySchedules()
       if _.contains ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'], day
-        daySchedule = _.find (@location().get 'day_schedules'), (daySchedule) -> daySchedule['day_of_week'] is day
-        if daySchedule
-          startHour = moment().hour(daySchedule['start_time'] / 60 / 60)
-          startMinute = moment().minutes(daySchedule['start_time'] / 60)
-          endHour = moment().hour(daySchedule['end_time'] / 60 / 60)
-          endMinute = moment().minutes(daySchedule['end_time'] / 60 )
+        daySchedules = _.where (@location().get 'day_schedules'), day_of_week: day
+        if daySchedules
+          hours = []
+          for daySchedule in daySchedules
+            startHour = moment().hour(daySchedule['start_time'] / 60 / 60)
+            startMinute = moment().minutes(daySchedule['start_time'] / 60)
+            endHour = moment().hour(daySchedule['end_time'] / 60 / 60)
+            endMinute = moment().minutes(daySchedule['end_time'] / 60 )
+            hours.push "#{startHour.format('h')}\
+                    #{ if startMinute.format('mm') == '00' then '' else startMinute.format(':mm')}\
+                    #{startHour.format('a')}–\
+                    #{endHour.format('h')}\
+                    #{if endMinute.format('mm') == '00' then '' else endMinute.format(':mm')}\
+                    #{endHour.format('a')}"
           start: day
-          hours: "#{startHour.format('h')}\
-                  #{ if startMinute.format('mm') == '00' then '' else startMinute.format(':mm')}\
-                  #{startHour.format('a')}–\
-                  #{endHour.format('h')}\
-                  #{if endMinute.format('mm') == '00' then '' else endMinute.format(':mm')}\
-                  #{endHour.format('a')}"
+          hours: hours.join(', ')
         else
           start: day
           hours: 'Closed'
@@ -218,7 +221,7 @@

   # returns an array of grouped and formatted 'day schedules' objects in the format:
   # [{ days: 'Monday–Thursday, Sunday', hours: '10am - 7pm' }, { days: 'Friday', hours: '6:30am - 7pm' }]
-  formatModalDaySchedules: () ->
+  formatModalDaySchedules: ->
     if @daySchedules()
       daysOpen = [@formatDaySchedules()[0]]
       _.each @formatDaySchedules().slice(1), (daySchedule) ->


Allow configurable connections from client side

--- lib/pages.coffee
+++ lib/pages.coffee
@@ -24,7 +24,8 @@
     routerTemplate: [true, String, "pages"]
     routerLayout: [true, Match.Optional(String), undefined]
     sort: [true, Object, {}]
-
+    connection: [true, Object, Meteor]
+
     # Unavailable to the client after initialization

     auth: [false, Match.Optional(Function), undefined]
@@ -958,7 +959,7 @@
       @enforceSubscriptionLimit()
       Meteor.defer _.bind ((page) ->
         #@log "subscribing to page #{page}"
-        @subscriptions[page] = Meteor.subscribe @id, page,
+        @subscriptions[page] = @connection.subscribe @id, page,
           onReady: _.bind (page) ->
             @onPage page
           , @, page


Use 'all' in the logging tests to confirm it behaves correctly

Signed-off-by: Tim Perry <5ee0edb9e2229c0838f1959779f1949031de0123@resin.io>

--- tests/integration/logs.spec.coffee
+++ tests/integration/logs.spec.coffee
@@ -100,7 +100,7 @@
 					timestamp: Date.now()
 				}]
 				.then =>
-					resin.logs.subscribe(@uuid, { count: 100 })
+					resin.logs.subscribe(@uuid, { count: 'all' })
 				.then (logs) ->
 					new Promise (resolve, reject) ->
 						lines = []


defer change for clietn prepaid

defer change for clietn prepaid

--- src/core/javascripts/models/client.js.coffee
+++ src/core/javascripts/models/client.js.coffee
@@ -192,6 +192,7 @@
         , (err) ->
           defer.reject(err)
       else
-        defer.reject('missing pre_paid_bookings link')
+        defer.resolve([])
+#        defer.reject('missing pre_paid_bookings link')
       defer.promise


uncomment

uncomment

--- lib/setup.coffee
+++ lib/setup.coffee
@@ -207,7 +207,7 @@
   app.use require "../apps/auction_lots"
   app.use require "../apps/auction_support"
   app.use require "../apps/auctions"
-  # app.use require "../apps/artist_new"
+  app.use require "../apps/artist_new"
   app.use require "../apps/artist_old"
   app.use require "../apps/artists"
   app.use require "../apps/artwork"


init helper after server started

init helper after server started

--- lib/atom-ternjs.coffee
+++ lib/atom-ternjs.coffee
@@ -46,7 +46,6 @@
       default: true

   activate: (state) ->
-    @helper = new Helper()
     @startServer()
     @addComponents(state)
     @disposables.push atom.workspace.onDidOpen (e) =>
@@ -91,6 +90,8 @@
       if !@client
         @client = new TernClient()
       @client.port = port
+      if !@helper
+        @helper = new Helper()
       if !@provider
         @init()


去掉pickParam时的特殊处理

去掉pickParam时的特殊处理

--- lib/utils.coffee
+++ lib/utils.coffee
@@ -38,10 +38,7 @@
   ###
   pickParams: (req, cols) ->
     attr = {}
-    _.each cols, (col) ->
-      attr[col] = req.params[col] if req.params[col]?
-      attr[col] = null if req.params[col] is ''
-
+    attr[x] = req.params[x] for x in cols when req.params.hasOwnProperty(x)
     attr

   ###


Point docs URL to HexDocs instead of elixir-lang.org

--- lib/utils.coffee
+++ lib/utils.coffee
@@ -44,7 +44,7 @@
     "http://www.erlang.org/doc/man/#{mod.replace(/^:/, '')}.html#{erl_func_arity}"
   else
     module = mod || 'Kernel'
-    "http://elixir-lang.org/docs/v#{elixir_version}/elixir/#{module}.html#{elixir_func_arity}"
+    "https://hexdocs.pm/elixir/#{elixir_version}/#{module}.html#{elixir_func_arity}"

 convertCodeBlocksToAtomEditors = (domFragment, defaultLanguage='text') ->
   if fontFamily = atom.config.get('editor.fontFamily')


update for user-status 0.3.0

update for user-status 0.3.0

--- lib/connections.coffee
+++ lib/connections.coffee
@@ -2,7 +2,7 @@
   Connect callbacks
 ###

-UserStatus.events.on "sessionLogin", (doc) ->
+UserStatus.events.on "connectionLogin", (doc) ->
   # Update ip address in assignments for this worker
   user = Meteor.users.findOne(doc.userId)
   return if user?.admin
@@ -19,7 +19,7 @@

 connectCallbacks = []

-UserStatus.events.on "sessionLogin", (doc) ->
+UserStatus.events.on "connectionLogin", (doc) ->
   return unless doc.userId
   # No side effects from admin, please
   return if Meteor.users.findOne(doc.userId)?.admin
@@ -46,13 +46,13 @@
   Disconnect callbacks
 ###

-UserStatus.events.on "sessionLogout", (doc) ->
+UserStatus.events.on "connectionLogout", (doc) ->
   # Remove disconnected users from lobby, if they are there
   TurkServer.Lobby.removeUser(doc.userId)

 disconnectCallbacks = []

-UserStatus.events.on "sessionLogout", (doc) ->
+UserStatus.events.on "connectionLogout", (doc) ->
   return unless doc.userId
   # No side effects from admin, please
   return if Meteor.users.findOne(doc.userId)?.admin


Client:Login: if there is error than show it

--- client/Login/AppView.coffee
+++ client/Login/AppView.coffee
@@ -252,10 +252,14 @@

   doReset:({recoveryToken, password, clientId})->
     KD.remote.api.JPasswordRecovery.resetPassword recoveryToken, password, (err, username)=>
-      @resetForm.button.hideLoader()
-      @resetForm.reset()
-      @headBanner.hide()
-      @doLogin {username, password, clientId}
+      if err
+        new KDNotificationView
+          title : "An error occurred: #{err.message}"
+      else
+        @resetForm.button.hideLoader()
+        @resetForm.reset()
+        @headBanner.hide()
+        @doLogin {username, password, clientId}

   doRecover:(formData)->
     KD.remote.api.JPasswordRecovery.recoverPassword formData['username-or-email'], (err)=>


enable per-page pdf downloads by default

--- services/web/public/coffee/ide/pdfng/directives/pdfViewer.coffee
+++ services/web/public/coffee/ide/pdfng/directives/pdfViewer.coffee
@@ -32,7 +32,7 @@
 			queryStringExists = url.match(/\?/)
 			url = url + (if not queryStringExists then '?' else '&') + 'pdfng=true'
 			# for isolated compiles, load the pdf on-demand because nobody will overwrite it
-			onDemandLoading = window.location?.search?.match(/isolated=true/)?
+			onDemandLoading = true
 			$scope.document = new PDFRenderer(url, {
 				scale: 1,
 				disableAutoFetch: if onDemandLoading then true else undefined


Add missing "return" statement

Add missing "return" statement

--- client/ide/lib/index.coffee
+++ client/ide/lib/index.coffee
@@ -1272,7 +1272,7 @@

     return  if not @myWatchMap or not @myWatchMap.keys()

-    @myWatchMap.keys().indexOf(origin) > -1
+    return @myWatchMap.keys().indexOf(origin) > -1

   handleChange: (change) ->


IDE: Don't resurrect panes on initial.

IDE: Don't resurrect panes on initial.

--- client/ide/lib/index.coffee
+++ client/ide/lib/index.coffee
@@ -347,8 +347,7 @@
         else
           snapshot = @localStorageController.getValue @getWorkspaceSnapshotName()

-          if snapshot then @resurrectLocalSnapshot snapshot
-          else
+          unless snapshot
             @ideViews.first.createEditor()
             @ideViews.last.createTerminal { machine }
             @setActiveTabView @ideViews.first.tabView


Replace emojis when quoting

Replace emojis when quoting

--- app/assets/javascripts/backbone/views/post.js.coffee
+++ app/assets/javascripts/backbone/views/post.js.coffee
@@ -74,6 +74,10 @@
     text = text.replace(/class="spoiler revealed"/g, 'class="spoiler"')
     html = html.replace(/class="spoiler revealed"/g, 'class="spoiler"')

+    # Emojis
+    text = text.replace(/]*)>/g, ":$1:")
+    html = html.replace(/]*)>/g, ":$1:")
+
     $(Sugar).trigger "quote",
       username: username
       permalink: permalink
@@ -101,4 +105,3 @@
     $(this.el).find("iframe[src*='//instagram.com/']").each ->
       unless $(this).parent().hasClass("instagram-wrapper")
         $(this).wrap('
')
-


Hide add/delete block/week. Close #554

Hide add/delete block/week. Close #554

--- app/assets/javascripts/components/timeline/week.cjsx
+++ app/assets/javascripts/components/timeline/week.cjsx
@@ -94,7 +94,7 @@
       Add Block
     )

-    week_add_delete = if @props.meetings && @props.edit_permissions then (
+    week_add_delete = if !@props.reorderable && @props.meetings && @props.edit_permissions then (

         {add_week}
         Delete Week


Remove some unused code.

Remove some unused code.

--- lib/conflict-marker.coffee
+++ lib/conflict-marker.coffee
@@ -7,11 +7,6 @@
 NavigationView = require './navigation-view'
 ResolverView = require './resolver-view'
 {EditorAdapter} = require './editor-adapter'
-
-OUR_CLASSES = "conflict-line ours"
-THEIR_CLASSES = "conflict-line theirs"
-RESOLVED_CLASSES = "conflict-line resolved"
-DIRTY_CLASSES = "conflict-line dirty"

 module.exports =
 class ConflictMarker


force focus next input seems more consistent this way

--- lib/console/console.coffee
+++ lib/console/console.coffee
@@ -126,7 +126,7 @@
       item = type: 'input', input: true
       @push @setMode item
       @watchModes item
-      @focusInput()
+      @focusInput true
       item
     else
       @getInput()


Fix switch case

Fix switch case

--- lib/drive/drive.coffee
+++ lib/drive/drive.coffee
@@ -60,11 +60,8 @@

 exports.listDrives = (callback) ->
 	switch os.platform()
-		when 'darwin' then
-			osx.list(callback)
-		when 'win32' then
-			win32.list(callback)
-		when 'linux' then
-			linux.list(callback)
+		when 'darwin' then osx.list(callback)
+		when 'win32' then win32.list(callback)
+		when 'linux' then linux.list(callback)
 		else
 			throw new Error('Your OS is not supported by this module')


get rid of 'controllerFor' deprecation warnings

--- assets/scripts/app/controllers/repo.coffee
+++ assets/scripts/app/controllers/repo.coffee
@@ -51,7 +51,7 @@

   lastBuildDidChange: ->
     build = @get('repo.lastBuild')
-    @controllerFor('build').set('build', build)
+    @set('build', build)

   stopObservingLastBuild: ->
     @removeObserver('repo.lastBuild', this, 'lastBuildDidChange')


Fix path

Fix path

--- lib/ide-backend.coffee
+++ lib/ide-backend.coffee
@@ -136,10 +136,10 @@

     # set PATH depending on config settings
     ghcPath = @getConfigOpt 'pathTo'
-    if getConfigOpt 'pathExclusive'
-      env.PATH = ghcPath
+    if @getConfigOpt 'pathExclusive'
+      env.PATH = ghcPath.join(delimiter)
     else if ghcPath
-      env.PATH = ghcPath.split(delimiter).concat(env.PATH.split(delimiter)).join(delimiter)
+      env.PATH = ghcPath.concat(env.PATH.split(delimiter)).join(delimiter)

     # Set sandbox file (if specified)
     sandboxConfig = @getConfigOpt 'sandbox'


:bug: Fix a typo

:bug: Fix a typo

--- lib/linter-plus.coffee
+++ lib/linter-plus.coffee
@@ -115,7 +115,7 @@
   deactivate: ->
     @subscriptions.dispose()
     @eachEditorLinter (linter) ->
-      linter.destroy()
+      linter.deactivate()
     @views.destroy()
     @commands.destroy()
     @messages.destroy()


standardize view objects.

standardize view objects.

--- lib/marie.utils.coffee
+++ lib/marie.utils.coffee
@@ -80,33 +80,20 @@
 			id: 'someRedisServer'
 			adapter: 'sails-redis'
 			version: 'latest'
-	viewDirs: [
-		'/views/modules'
-		'/views/partials'
-		'/views/layouts'
-	]
-	views: [
-		'/views/homepage'
-		'/views/403'
-		'/views/404'
-		'/views/500'
-		'/views/partials/partial'
-		'/views/layouts/master'
-	]
-	# views:
-	# 	DIRS: [
-	# 		'/views/modules'
-	# 		'/views/partials'
-	# 		'/views/layouts'
-	# 	]
-	# 	FILES: [
-	# 		'/views/homepage'
-	# 		'/views/403'
-	# 		'/views/404'
-	# 		'/views/500'
-	# 		'/views/partials/partial'
-	# 		'/views/layouts/master'
-	# 	]
+	views:
+		DIRS: [
+			'/views/modules'
+			'/views/partials'
+			'/views/layouts'
+		]
+		FILES: [
+			'/views/homepage'
+			'/views/403'
+			'/views/404'
+			'/views/500'
+			'/views/partials/partial'
+			'/views/layouts/master'
+		]

 	###
 	Construct app
@@ -345,7 +332,7 @@
 			stream = stream.replace /'layout'/gi, layout
 			@fs.writeFileSync config, stream
 		@fs.mkdirsSync app.file dir
-		for vdir in @viewDirs then @fs.mkdirsSync app.file vdir
+		for vdir in @views.DIRS then @fs.mkdirsSync app.file vdir
 		cb()

 	###
@@ -354,7 +341,7 @@
 	@param [Function] cb callback function
 	###
 	generateViews: (engine, app, cb) ->
-		for view in @views
+		for view in @views.FILES
 			sfile = @config "/templates/#{engine.id}#{view}#{engine.extension}"
 			dfile = app.file "#{view}#{engine.extension}"
 			@fs.copySync sfile, dfile


jQueryのオブジェクトを引数に指定された場合に対応

jQueryのオブジェクトを引数に指定された場合に対応

--- lib/generators/webrtc_rails/install/templates/main.js.coffee
+++ lib/generators/webrtc_rails/install/templates/main.js.coffee
@@ -1,5 +1,7 @@
 class @WebRTC
   constructor: (userID, @localOutput, @remoteOutput) ->
+    @localOutput = @localOutput[0] || @localOutput
+    @remoteOutput = @remoteOutput[0] || @remoteOutput
     @_startOutput(@localOutput.tagName.toUpperCase() == 'VIDEO')
     @wsRails = new WebSocketRails(location.host + "/websocket?webrtc=true&user_identifier=" + userID)
     @wsRails.bind("webrtc"


utils: send signup email to myself

utils: send signup email to myself

--- client/Framework/core/utils.coffee
+++ client/Framework/core/utils.coffee
@@ -555,7 +555,7 @@
     uniqueness = (Date.now()+"").slice(6)
     formData   =
       agree           : "on"
-      email           : "sinanyasar+#{uniqueness}@gmail.com"
+      email           : "senthil+#{uniqueness}@koding.com"
       firstName       : gp(gr(10), yes)
       inviteCode      : "twitterfriends"
       lastName        : gp(gr(10), yes)


terminal: return from timed out procedure if vm start is failed

--- client/Terminal/webtermview.coffee
+++ client/Terminal/webtermview.coffee
@@ -11,7 +11,7 @@
         isRunningCallback()
       else
         vmController.start vmName, (err, state)=>
-          warn "Failed to turn on vm:", err  if err
+          return warn "Failed to turn on vm:", err  if err
           isStartedCallback()
       KD.mixpanel "Open Webterm, success", {vmName}


Revert "[#3864702] reverting to get tests to pass again"

This reverts commit bcee7a9ddc11d280b1361718507fe37df57bbc2b.

--- math_spec.coffee
+++ math_spec.coffee
@@ -21,7 +21,7 @@

 describe "Math Library", ->
   beforeEach ->
-    @math = require('lib/math')
+    @math = ttm.require('lib/math')

   describe "Expression", ->
     it "assigns components from its construtor", ->


update available placeholders

update available placeholders

--- lib/edit-in-new-tab.coffee
+++ lib/edit-in-new-tab.coffee
@@ -41,7 +41,7 @@
       order: 4
     defaultTabName:
       title: "Default Tab-name"
-      description: "Define a default scheme for new tabs. Accepts `%origin%` placeholder for the original's file-name"
+      description: "Define a default scheme for new tabs. Available placeholders: `%file%` for file-name, `%id%` for editor ID, and `%count%` to count tabs"
       type: "string"
       default: ""
       order: 5
@@ -80,6 +80,7 @@
           default: true
           order: 5
   subscriptions: null
+  counter: 0

   activate: (state) ->
     # Events subscribed to in atom's system can be easily cleaned up with a CompositeDisposable
@@ -114,11 +115,15 @@

     targetPane = atom.config.get('edit-in-new-tab.targetPane')
     defaultTabName = atom.config.get('edit-in-new-tab.defaultTabName')
+    @counter += 1

     if not defaultTabName
       newTabname = null
     else
-      newTabName = defaultTabName.replaceAll("%origin%", atom.workspace.getActiveTextEditor().getFileName().toString())
+      newTabName = defaultTabName
+                    .replaceAll("%file%", atom.workspace.getActiveTextEditor().getFileName().toString())
+                    .replaceAll("%id%", parentEditor.id)
+                    .replaceAll("%count%", @counter)

     atom.workspace.open(newTabName, { split: targetPane })
       .then (newTab) ->


Remove empty.cson (is now created programmatically)

--- languages/empty.cson
+++ languages/empty.cson
@@ -1,8 +0,0 @@
-# This is the empty grammar.
-
-# The empty grammar will be used as the default grammar for languages whose
-# configuration files do not define the `defaultGrammar` key. Thus, no syntax
-# highlighting will be available for all levels of such languages that are not
-# bound to a level-specific grammar.
-
-'scopeName': 'levels.source.empty'


fix: Atom beautify empty config

fix: Atom beautify empty config

--- configs/atom.cson
+++ configs/atom.cson
@@ -1,4 +1,6 @@
 "*":
+  "atom-beautify":
+    general: {}
   core:
     telemetryConsent: "limited"
   editor:


chore: delete atom config

chore: delete atom config

--- configs/atom.cson
+++ configs/atom.cson
@@ -1,33 +0,0 @@
-"*":
-  "atom-beautify":
-    general: {}
-  "atom-ide-ui":
-    use: {}
-  "atom-package-deps":
-    ignored: [
-      "linter-package-json-validator"
-      "linter-jsonlint"
-    ]
-  core:
-    telemetryConsent: "limited"
-  editor:
-    fontSize: 16
-    preferredLineLength: 100
-    showIndentGuide: true
-    showInvisibles: true
-    softWrap: true
-  "exception-reporting":
-    userId: "c3e419a6-250a-4e79-85f8-48fdda7d7718"
-  filesize:
-    UseDecimal: true
-  "ide-flowtype":
-    tryFlowBin: true
-  "linter-stylelint":
-    disableWhenNoConfig: true
-    enableHtmlLinting: true
-  "markdown-preview":
-    useGitHubStyle: true
-  "tree-view":
-    hideIgnoredNames: true
-  welcome:
-    showOnStartup: false


Fixed done error callback

Fixed done error callback

--- app/config.coffee
+++ app/config.coffee
@@ -36,7 +36,6 @@
       getDeviceToken createdDevice?.uuid

     deviceFindCallback = (error, foundDevice) =>
-      return done error if error?
       return getDeviceToken foundDevice.uuid if foundDevice?
       deviceModel.create query, device, profileId, fakeSecret, deviceCreateCallback


Use constructor shorthand

Use constructor shorthand

--- www/tupelo.coffee
+++ www/tupelo.coffee
@@ -76,9 +76,7 @@

 # Card
 class T.Card
-  constructor: (suit, value) ->
-    @suit = suit
-    @value = value
+  constructor: (@suit, @value) ->

   suits = [
     name: "spades"


Fixes for textarea helper

Fixes for textarea helper

--- client/source/helpers/forms.coffee
+++ client/source/helpers/forms.coffee
@@ -65,11 +65,14 @@
     help: settings.help

 Handlebars.registerHelper 'textarea', (name, value, options) ->
-  settings = options.hash
+
+  settings = _.defaults options.hash,
+    tabindex: 1
+    className: 'form-control'

   textarea = tag 'textarea',
     name: name
-    className: settings.className || 'form-control'
+    className: settings.className
     id: "input-#{name}"
     placeholder: settings.placeholder
     tabindex: settings.tabindex


Map 'self' to 'this' in Python user code

The latest version of Filbert no longer parses 'this' to a
ThisExpression, because it's not a Python keyword.  This fix applies the
same method as Lua to map 'self' to 'this'.

'self' will be used in place of 'this' in user code:
self.moveDown()

--- src/languages/python.coffee
+++ src/languages/python.coffee
@@ -31,4 +31,9 @@
   # Using a third-party parser, produce an AST in the standardized Mozilla format.
   parse: (code, aether) ->
     ast = parser.parse code, {locations: true, ranges: true}
+
+    # 'this' is not a keyword in Python, so it does not parse to a ThisExpression
+    # Instead, we expect the variable 'self', and map it to a ThisExpression here.
+    ast.body[0].body.body.unshift {"type": "VariableDeclaration","declarations": [{ "type": "VariableDeclarator", "id": {"type": "Identifier", "name": "self" },"init": {"type": "ThisExpression"} }],"kind": "var"}
+
     ast


Stop before detach

Stop before detach

--- lib/script-view.coffee
+++ lib/script-view.coffee
@@ -25,9 +25,9 @@
   serialize: ->

   destroy: ->
-    # Dismiss window and stop any running process
+    # Stop the running process (if necessary) and dismiss window
+    @stop()
     @detach()
-    @stop()

   show: ->
     # Display window and load message


adds DbManager#Promise

adds DbManager#Promise

--- lib/DbManager.coffee
+++ lib/DbManager.coffee
@@ -4,10 +4,12 @@
 async = require "async"
 typeOf = require 'typeof'
 Pool = require 'rethinkdb-pool'
+Promise = require 'bluebird'

 module.exports = class DbManager extends EventEmitter
   @Pool: Pool
   Pool: Pool
+  Promise: Promise
   conn: null
   models: null
   reconnectDelay: 100


Out of inspiration

Out of inspiration

--- src/modules/zwadonk/ZwadonkController.coffee
+++ src/modules/zwadonk/ZwadonkController.coffee
@@ -14,6 +14,7 @@
     console.log 'Open info panel'
     if event
       # load contents of the panel
+      # donno why but I donnu what do actually _do_ here
       event.stopPropagation()
       event.preventDefault()
     return


:fire: leftover

:fire: leftover

--- lib/text-object.coffee
+++ lib/text-object.coffee
@@ -373,9 +373,6 @@
       return @adjustRowRange(startRow, endRow)
     null

-  getLanguageName: ->
-    @editor.getGrammar().scopeName.replace(/^source\./, '')
-
   adjustRowRange: (startRow, endRow) ->
     unless @isInclusive()
       startRow += 1


operations: Register created default configuration

--- src/local/operations.coffee
+++ src/local/operations.coffee
@@ -6,6 +6,43 @@
 exports.setModel = (model) ->
     runTransaction = model.transaction

+defaultConfiguration = (user) ->
+    posts:
+        title: "#{user} Channel Posts"
+        description: "A buddycloud channel"
+        accessModel: "open"
+        publishModel: "subscribers"
+        defaultAffiliation: "member"
+    status:
+        title: "#{user} Status Updates"
+        description: "M000D"
+        accessModel: "open"
+        publishModel: "publishers"
+        defaultAffiliation: "member"
+    'geoloc/previous':
+        title: "#{user} Previous Location"
+        description: "Where #{user} has been before"
+        accessModel: "open"
+        publishModel: "publishers"
+        defaultAffiliation: "member"
+    'geoloc/current':
+        title: "#{user} Current Location"
+        description: "Where #{user} is at now"
+        accessModel: "open"
+        publishModel: "publishers"
+        defaultAffiliation: "member"
+    'geoloc/next':
+        title: "#{user} Next Location"
+        description: "Where #{user} intends to go"
+        accessModel: "open"
+        publishModel: "publishers"
+        defaultAffiliation: "member"
+    subscriptions:
+        title: "#{user} Subscriptions"
+        description: ""
+        accessModel: "open"
+        publishModel: "publishers"
+        defaultAffiliation: "member"

 ##
 # Is created with options from the request
@@ -59,23 +96,23 @@
     transaction: (t, cb) ->
         user = @req.actor
         listener = @req.sender
-        nodeTypes = [
-                'posts', 'status',
-                'geoloc/previous', 'geoloc/current',
-                'geoloc/next', 'subscriptions']
-        async.series nodeTypes.map((nodeType) ->
-            (cb2) ->
-                node = "/user/#{user}/#{nodeType}"
-                console.log "creating #{node}"
-                async.series [(cb3) ->
-                    t.createNode node, cb3
-                , (cb3) ->
-                    t.setAffiliation node, user, 'owner', cb3
-                , (cb3) ->
-                    t.setSubscription node, user, listener, 'subscribed', cb3
-                ], cb2
-        ), (err) ->
+        async.series(for own nodeType, config in defaultConfiguration(user)
+            do (nodeType, config) ->
+                (cb2) ->
+                    node = "/user/#{user}/#{nodeType}"
+                    console.log "creating #{node}"
+                    async.series [(cb3) ->
+                        t.createNode node, cb3
+                    , (cb3) ->
+                        t.setAffiliation node, user, 'owner', cb3
+                    , (cb3) ->
+                        t.setSubscription node, user, listener, 'subscribed', cb3
+                    , (cb3) ->
+                        t.setConfig node, config, cb3
+                    ], cb2
+        , (err) ->
             cb err
+        )

 class Publish extends PrivilegedOperation


Make country lookup case insensitive

Make country lookup case insensitive

--- app/lib/3w.coffee
+++ app/lib/3w.coffee
@@ -40,7 +40,7 @@
     result

   drawCharts: =>
-    keys = (f.properties[@joiner] for f in @geom.features)
+    keys = ((f.properties[@joiner] or '').toLowerCase() for f in @geom.features)
     values = (f.properties[@namer] for f in @geom.features)
     lookup = _.object keys, values
     margins = top: 0, left: 10, right: 10, bottom: 35
@@ -57,11 +57,11 @@

     whoDimension = cf.dimension (d) => d[@whoField]
     whatDimension = cf.dimension (d) => d[@whatField]
-    whereDimension = cf.dimension (d) => d[@whereField]
     @startDimension = cf.dimension (d) -> new Date d['Start']
     @endDimension = cf.dimension (d) -> new Date d['End']
     @firstDate = new Date @startDimension.bottom(1)[0].Start
     @lastDate = new Date @endDimension.top(1)[0].End
+    whereDimension = cf.dimension (d) => d[@whereField].toLowerCase()

     whoGroup = whoDimension.group()
     whatGroup = whatDimension.group()
@@ -112,7 +112,7 @@
       .colorDomain(d3.extent(_.pluck(whereGroup.all(), 'value')))
       .colorCalculator((d) => if d then @whereChart.colors()(d) else '#ccc')
       .overlayGeoJson(@geom.features, 'County', (d) =>
-        d.properties[@joiner] or '')
+        (d.properties[@joiner] or '').toLowerCase())
       .title((d) ->"County: #{lookup[d.key]}\nActivities: #{d.value or 0}")

     # @countChart


remove ability to override constructor set transition

--- src/models/Animation.coffee
+++ src/models/Animation.coffee
@@ -18,6 +18,9 @@
     else
       options.transition ? "curlUp"

+    throw "transition required" unless @transition?
+
+
     @reversedTransition = @.constructor.TRANSITION_REVERSION_MAPPING[@transition]

     @duration = options.duration || 0.7
@@ -29,7 +32,6 @@
     steroids.nativeBridge.nativeCall
       method: "performTransition"
       parameters: {
-        transition: options.transition || @transition
         curve: options.curve || @curve
         duration: options.duration || @duration
       }


Removes css id attribute of editor only if there is an active editor. Resolves #39

--- lib/atom_pair.coffee
+++ lib/atom_pair.coffee
@@ -83,7 +83,7 @@
     @pusher.disconnect()
     @editorListeners.dispose()
     _.each @friendColours, (colour) => @clearMarkers(colour)
-    atom.views.getView(@editor).removeAttribute('id')
+    atom.views.getView(@editor)?.removeAttribute('id')
     @editor = @buffer = null
     @markerColour = null


clean up

clean up

--- extension/chrome/js/src/GMailUI.coffee
+++ extension/chrome/js/src/GMailUI.coffee
@@ -301,7 +301,7 @@

 class GMailUI.Popup extends GMailUI.Container
 	constructor: ->
-		element = @setElement 	"""
+		element = @setElement	"""

@@ -315,7 +315,7 @@
 			return

 class GMailUI.PopupLabel extends GMailUI.UIElement
-	@template: _.template 	"""
+	@template: _.template	"""

<%- label %>

 							"""
 	constructor: (label) ->
@@ -370,13 +370,13 @@

 								@menu.getElement().attr 'aria-activedescendant', id
 								@menu.inMenu = true
-								x = =>
+
+								_.delay (=>
 									return unless @menu.inMenu
 									@reposition()
 									@menu.show()
-									return
-
-								_.delay x, @menu.delay
+									return), @menu.delay
+
 								return),
 							((e) =>
 								@menu.inMenu = false


Simple catch if there are multiple matches

--- lib/code-peek.coffee
+++ lib/code-peek.coffee
@@ -3,7 +3,6 @@
 SupportedFiles = require './supported-files'
 {CompositeDisposable} = require 'atom'

-# TODO handle when multiple matches occur,
 # TODO should probably have tabs or some other mechanism to swich between files
 # TODO allow saving or not saving, closing editor using escape or with X button
 # TODO handle if code peek is already open and someone toggles it again
@@ -54,21 +53,26 @@
       return

     regExp = SupportedFiles.getFunctionRegExpForFileType(fileType, functionName)
-    foundMatchingFile = false
+    matchingFiles = 0
     atom.workspace.scan(regExp, {paths: ["*.#{fileType}"]}, (matchingFile) =>
-      foundMatchingFile = true
-      atom.workspace.open(matchingFile.filePath, {
-        initialLine: matchingFile.matches[0].range[0][0],
-        activatePane: false,
-        activateItem: false
-      }).then (matchingTextEditor) =>
-        textEditorParser.setEditor(matchingTextEditor)
-        functionInfo = textEditorParser.getFunctionInfo(
-          matchingTextEditor.getCursorBufferPosition().row)
+      matchingFiles++

-        @startEditing(functionInfo, matchingTextEditor)
+      if matchingFiles is 1
+        atom.workspace.open(matchingFile.filePath, {
+          initialLine: matchingFile.matches[0].range[0][0],
+          activatePane: false,
+          activateItem: false
+        }).then (matchingTextEditor) =>
+          textEditorParser.setEditor(matchingTextEditor)
+          functionInfo = textEditorParser.getFunctionInfo(
+            matchingTextEditor.getCursorBufferPosition().row)
+
+          @startEditing(functionInfo, matchingTextEditor)
+      else
+        # TODO handle additional matches here
+        console.log "found another match"
     ).then ->
-      if not foundMatchingFile
+      if matchingFiles is 0
         atom.notifications.addWarning("Could not find function in project")

   startEditing: (functionInfo, matchingTextEditor) ->


default values

default values

--- lib/coffeekup.coffee
+++ lib/coffeekup.coffee
@@ -7,8 +7,7 @@

 coffeekup.version = '0.2.2'

-skeleton = (ck_options) ->
-  ck_options ?= {}
+skeleton = (ck_options = {}) ->
   ck_options.context ?= {}
   ck_options.locals ?= {}
   ck_options.format ?= off
@@ -46,8 +45,7 @@
   h = (txt) ->
     String(txt).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"')

-  doctype = (type) ->
-    type ?= 5
+  doctype = (type = 5) ->
     text ck_doctypes[type]
     text '\n' if ck_options.format

@@ -121,8 +119,7 @@

 tags = 'a|abbr|acronym|address|applet|area|article|aside|audio|b|base|basefont|bdo|big|blockquote|body|br|button|canvas|caption|center|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|dir|div|dl|dt|em|embed|fieldset|figcaption|figure|font|footer|form|frame|frameset|h1|h2|h3|h4|h5|h6|head|header|hgroup|hr|html|i|iframe|img|input|ins|keygen|kbd|label|legend|li|link|map|mark|menu|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|s|samp|script|section|select|small|source|span|strike|strong|style|sub|summary|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|u|ul|video|xmp'.split '|'

-coffeekup.compile = (template, options) ->
-  options ?= {}
+coffeekup.compile = (template, options = {}) ->
   options.locals ?= {}

   if typeof template is 'function' then template = String(template)
@@ -152,8 +149,7 @@

 cache = {}

-coffeekup.render = (template, options) ->
-  options ?= {}
+coffeekup.render = (template, options = {}) ->
   options.context ?= {}
   options.locals ?= {}
   options.cache ?= on


trivial

trivial

--- src/smc-hub/test/postgres-user-queries-changefeeds-2.coffee
+++ src/smc-hub/test/postgres-user-queries-changefeeds-2.coffee
@@ -379,8 +379,7 @@
                 ], done)

-
-    it 'tests must be admin to read from projects_admin table', (done) ->
+    it 'tests that user must be an admin to read from (or get changefeed on) projects_admin table', (done) ->
         changefeed_id = misc.uuid()
         db.user_query
             account_id : accounts[1]  # NOT admin


Do not change selection if selection exists.

Fixes #1430.

--- lib/dom_utils.coffee
+++ lib/dom_utils.coffee
@@ -177,9 +177,10 @@

   simulateSelect: (element) ->
     element.focus()
-    # When focusing a textbox, put the selection caret at the end of the textbox's contents.
-    # For some HTML5 input types (eg. date) we can't position the caret, so we wrap this with a try.
-    try element.setSelectionRange(element.value.length, element.value.length)
+    unless @isSelected element
+      # When focusing a textbox, put the selection caret at the end of the textbox's contents.
+      # For some HTML5 input types (eg. date) we can't position the caret, so we wrap this with a try.
+      try element.setSelectionRange(element.value.length, element.value.length)

   simulateClick: (element, modifiers) ->
     modifiers ||= {}


Live search, bro

Live search, bro

--- lib/find-view.coffee
+++ lib/find-view.coffee
@@ -114,6 +114,9 @@
     atom.workspaceView.on 'selection:changed', @setCurrentMarkerFromSelection

   handleFindEvents: ->
+    @findEditor.editor.on 'contents-modified', =>
+      pattern = @findEditor.getText()
+      @liveSearch() if !pattern or pattern.length > 1
     @nextButton.on 'click', => @findNext()
     @previousButton.on 'click', => @findPrevious()
     atom.workspaceView.command 'find-and-replace:find-next', @findNext
@@ -163,6 +166,10 @@

   confirm: ->
     @findNext()
+
+  liveSearch: ->
+    pattern = @findEditor.getText()
+    @updateModel { pattern }

   findNext: (focusEditorAfter = true) =>
     @findAndSelectResult(@selectFirstMarkerAfterCursor, focusEditorAfter)


Added support for custom Highlight.js options.

--- src/plugins/markdown.coffee
+++ src/plugins/markdown.coffee
@@ -5,8 +5,6 @@
 path = require 'path'
 url = require 'url'
 yaml = require 'js-yaml'
-
-hljs.configure {classPrefix: ''} # keep compatibility with old stylesheets (pre hljs 8.0.0)

 # monkeypatch to add url resolving to marked
 if not marked.InlineLexer.prototype._outputLink?
@@ -64,6 +62,15 @@
   return marked markdown

 module.exports = (env, callback) ->
+  # Highlight.js configuration
+  hljsConfigDefaults =
+    classPrefix: ''
+
+  hljsConfig = env.config.highlightjs or {}
+  for key, value of hljsConfigDefaults
+    hljsConfig[key] ?= hljsConfigDefaults[key]
+
+  hljs.configure hljsConfig

   class MarkdownPage extends env.plugins.Page


Don't timeout while waiting for delta

Don't timeout while waiting for delta

--- src/docker-utils.coffee
+++ src/docker-utils.coffee
@@ -1,7 +1,6 @@
 Docker = require 'dockerode'
 DockerProgress = require 'docker-progress'
 Promise = require 'bluebird'
-request = Promise.promisifyAll require('request')
 { spawn } = require 'child_process'
 progress = require 'request-progress'
 config = require './config'
@@ -85,13 +84,13 @@
 						else
 							resolve()

-					progress request.get("#{config.deltaEndpoint}/api/v1/delta?src=#{repoTag}&dest=#{image}")
+					progress request.get("#{config.deltaEndpoint}/api/v1/delta?src=#{repoTag}&dest=#{image}", timeout: 0)
 					.on 'progress', onProgress
 					.on 'response', ({statusCode}) -> reject() if statusCode isnt 200
 					.on 'error', reject
 					.pipe rsync.stdin

-				config = request.getAsync("#{config.deltaEndpoint}/api/v1/config?image=#{image}", json: true)
+				config = request.getAsync("#{config.deltaEndpoint}/api/v1/config?image=#{image}", {json: true, timeout: 0})

 				Promise.all [ config, delta ]
 			.get(0)


shortcuts/pane: add controller logic

shortcuts/pane: add controller logic

--- client/app/lib/shortcuts/views/pane.coffee
+++ client/app/lib/shortcuts/views/pane.coffee
@@ -1,7 +1,36 @@
-kd = require 'kd'
-_  = require 'lodash'
-Item = require './item'
-record = require '../record'
+kd       = require 'kd'
+_        = require 'lodash'
+recorder = require 'record-shortcuts'
+Item     = require './item'
+
+
+listeners = {}
+
+selectHandler = (cb) ->
+
+  recorder.start()
+    .on 'end', (res) ->
+      { shortcuts } = kd.singletons
+      cb res
+    .on 'cancel', cb
+
+
+toggleHandler = (enabled) ->
+
+  { shortcuts } = kd.singletons
+
+  uid = [@model.collection, @model.name].join '|'
+  fn = listeners[uid] = (collection, model) =>
+    console.log arguments
+    @emit 'Synced', model
+
+  shortcuts
+    .once 'change', fn
+    .update @model.collection, @model.name,
+      options: enabled: enabled
+
+  return
+

 module.exports =

@@ -10,15 +39,25 @@
   constructor: (options={}) ->

     @collection = options.collection
+
     super _.omit options, 'collection'
+
+
+  destroy: ->
+
+    @getSubViews().forEach (item) =>
+      item.off 'Selected', selectHandler
+      item.off 'Toggled' , toggleHandler
+
+    super

   viewAppended: ->

     i = 0
+
     @collection.each (model) =>
       if ++i > 5 then return
       item = @addSubView new Item null, model
-      item.on 'BindingSelected', (id) =>
-        record (seq) ->
-          console.log seq
+      item.on 'Selected', selectHandler
+      item.on 'Toggled' , toggleHandler


Don't draw root of skill graph.

Don't draw root of skill graph.


JMachine: some: increase max resource size at once to 60

--- workers/social/lib/social/models/computeproviders/machine.coffee
+++ workers/social/lib/social/models/computeproviders/machine.coffee
@@ -517,7 +517,7 @@
       selector['users.id']  = user.getId()
       selector['groups.id'] = group.getId()

-      JMachine.some selector, { limit: 30 }, (err, machines) ->
+      JMachine.some selector, { limit: 60 }, (err, machines) ->
         callback err, machines

@@ -534,7 +534,7 @@
         'users.sudo'  : yes
         'users.owner' : yes

-      JMachine.some selector, { limit: 30 }, (err, machines) ->
+      JMachine.some selector, { limit: 60 }, (err, machines) ->
         callback err, machines


attachment mixin

attachment mixin

--- src/tower/model/attachment/processing.coffee
+++ src/tower/model/attachment/processing.coffee
@@ -45,12 +45,6 @@
     else
       {}
   ).cacheable()
-
-  styles: #Ember.computed(-> {}).volatile()
-    thumb:  ['25x25', 'png']
-    small:  ['50x50', 'png']
-    medium: ['125x125', 'png']
-    large:  ['300x300', 'png']

   # https://github.com/rsms/node-imagemagick/
   processors: #Ember.computed(->).cacheable()


fix naming inconsistency

fix naming inconsistency

--- lib/operation-stack.coffee
+++ lib/operation-stack.coffee
@@ -78,11 +78,11 @@
     if @vimState.isMode('normal')
       unless @editor.getLastSelection().isEmpty()
         throw new OperationStackError('Selection remains on normal-mode')
-      @ensureCursorNotOnEOL()
+      @ensureCursorNotAtEOL()
     @vimState.showCursors()
     @vimState.reset()

-  ensureCursorNotOnEOL: ->
+  ensureCursorNotAtEOL: ->
     for c in @editor.getCursors() when c.isAtEndOfLine() and not c.isAtBeginningOfLine()
       withKeepingGoalColumn c, (c) ->
         c.moveLeft()


Add a necessary fat arrow

Add a necessary fat arrow

--- lib/package-manager.coffee
+++ lib/package-manager.coffee
@@ -312,7 +312,7 @@
     Promise.all([uninstallPromise, installPromise]).then =>
       callback(null, eventArg)
       @emit('package-installed-alternative', eventArg)
-    .catch (error) ->
+    .catch (error) =>
       console.error error.message, error.stack
       callback(error, eventArg)
       @emit('package-install-alternative-failed', eventArg, error)


moved range population function to provider for more controllability by provider-side

--- lib/provider/search.coffee
+++ lib/provider/search.coffee
@@ -52,20 +52,16 @@
       items.push(new Item(parsed))

 class Item
-  # this.range is populated on-need.
-  Object.defineProperty @prototype, 'range',
-    get: ->
-      if @isRegExpSearch
-        null
-      else
-        @_range ?= Range.fromPointWithDelta(@point, 0, @searchTermLength)
-
   constructor: (parsed, project) ->
     {row, column, @filePath, @text} = parsed
     @point = new Point(row, column)
     @filePath = path.join(project, @filePath) if project

-  setRangeHint: ({@isRegExpSearch, @searchTermLength}) ->
+  setRangeHint: (@getRange) ->
+  # this.range is populated on-need via @setRange which is externally set by provider.
+  Object.defineProperty @prototype, 'range',
+    get: ->
+      @_range ?= @getRange(@point, @filePath)

 search = ({command, args, project, filePath}) ->
   options =
@@ -96,6 +92,12 @@
 module.exports =
 class Search extends SearchBase
   propertiesToRestoreOnReopen: ['projects']
+
+  getRange: (point, filePath) =>
+    if @isRegExpSearch
+      null
+    else
+      Range.fromPointWithDelta(point, 0, @searchTerm.length)

   checkReady: ->
     if @options.currentProject
@@ -141,9 +143,8 @@
   flattenSortAndSetRangeHint: (items) =>
     items = _.flatten(items)
     items = _.sortBy items, (item) -> item.filePath
-    searchTermLength = @searchTerm.length
     for item in items
-      item.setRangeHint({@isRegExpSearch, searchTermLength})
+      item.setRangeHint(@getRange)
     return items

   getItems: (filePath) ->


Fix 'dd' confusing cursor movement

Fix 'dd' confusing cursor movement

--- lib/operators.coffee
+++ lib/operators.coffee
@@ -104,14 +104,13 @@
   #
   # Returns nothing.
   execute: (count=1) ->
+    cursor = @editor.getCursor()
+
     _.times count, =>
       if _.last(@motion.select())
         @editor.getSelection().delete()

-      # FIXME: This should be fixed by atom/vim-mode#2
-      {row, column} = @editor.getCursorScreenPosition()
-      rowLength = @editor.getCursor().getCurrentBufferLine().length
-      @editor.moveCursorLeft() if column == rowLength
+      @editor.moveCursorLeft() if cursor.isAtEndOfLine()

     if @motion.isLinewise?()
       @editor.setCursorScreenPosition([@editor.getCursor().getScreenRow(), 0])


Remove some kbd for the select-list

Remove some kbd for the select-list

--- lib/styleguide-view.coffee
+++ lib/styleguide-view.coffee
@@ -903,10 +903,6 @@

                       ⌘⌥A
                       ⌘⌥O
-                      ⌘⌥1
-                      ⌘⌥↓
-                      ⌘⌥5
-                      ⌘⌥↓

                     Another file with a long name


Do not empty table when dragging pane (close #109)

--- lib/todo-table-view.coffee
+++ lib/todo-table-view.coffee
@@ -36,7 +36,7 @@
     @disposables.add atom.config.onDidChange 'todo-show.sortAscending', ({newValue, oldValue}) =>
       @sort(@sortBy, @sortAsc = newValue)

-  detached: ->
+  destroy: ->
     @disposables.dispose()
     @empty()


Change CSS styles directly on element

Change CSS styles directly on element

--- lib/wrap-guide-view.coffee
+++ lib/wrap-guide-view.coffee
@@ -43,6 +43,7 @@
     column = @getGuideColumn(editor.getPath(), editor.getGrammar().scopeName)
     if column > 0
       columnWidth = @editorView.charWidth * column
-      @css('left', columnWidth).show()
+      @element.style.left = "#{columnWidth}px"
+      @element.style.display = 'block'
     else
-      @hide()
+      @element.style.display = 'none'


only show  direct tracing links for anonymous tasks #1126

--- app/assets/javascripts/admin/views/task/task_create_subviews/task_create_from_view.coffee
+++ app/assets/javascripts/admin/views/task/task_create_subviews/task_create_from_view.coffee
@@ -228,7 +228,7 @@
     Toast.success("The task was successfully #{@getActionName().toLowerCase()}d")

     url = "/tasks"
-    if task.get("directLinks")
+    if not _.isEmpty(task.get("directLinks"))
       url += "?showAnonymousLinks=#{task.id}"

     app.router.navigate("#{url}##{task.id}", {trigger : true})


deprecated current post-parser variable system (ref #34)

--- src/lib/mistfile.coffee
+++ src/lib/mistfile.coffee
@@ -35,9 +35,7 @@
   # val:
   #   The new value
   ###
-  set: (name, val)->
-    val = @expand val if val
-    @vars[name] = val
+  set: (name, val)-> # TODO to be expanded (see #34)

   ###
   # Gets a parse variable's value
@@ -45,11 +43,8 @@
   # name:
   #   The name of the variable to get
   ###
-  get: (name)->
-    @vars[name] || ''
-    switch
-      when (m = name.match /^ENV_(.+)/) then process.env[m[1]] || ''
-      else @vars[name] || ''
+  get: (name)-> # TODO to be expanded (see #34)
+    throw 'not ready yet'

   ###
   # Unsets a variable
@@ -57,18 +52,13 @@
   # name:
   #   The name of the variable to unset
   ###
-  unset: (name)->
-    delete @vars[name]
+  unset: (name)-> # TODO to be expanded (see #34)

   ###
   # Expands a string using the currently configured parse variables
   ###
-  expand: (str)->
-    if typeof str is 'string'
-      return str.replace /(?:(?!\$).)\$\(\s*?([a-z0-9_]+)\s*\)/gi, (m, name)=>
-        @expand @get name
-    else if str.type? and str.value?
-      return type: str.type, value: @expand str.value
+  expand: (str)-> # TODO to be expanded (see #34)
+    str

   ###
   # Adds a rule given inputs and outputs and a command


cleanup

cleanup

--- components/filter/artworks_nav/view.coffee
+++ components/filter/artworks_nav/view.coffee
@@ -11,8 +11,7 @@
     counts = _.compact _.pluck @counts.get(attr), 'count'
     counts = _.compact _.pluck @counts.get(attr), 'c' unless counts.length
     counts = _.compact _.values @counts.get(attr) unless counts.length
-    minCount = _.last _.take counts.sort((a, b) -> b - a), n
-    minCount
+    _.last _.take counts.sort((a, b) -> b - a), n

   renderCounts: ->
     for parent, children of @counts.attributes


adds dimension to split test

adds dimension to split test

--- components/split_test/running_tests.coffee
+++ components/split_test/running_tests.coffee
@@ -27,3 +27,4 @@
     outcomes:
       old: 0.5
       new: 0.5
+    dimension: 'dimension5'


Add in dry run option for holding account migration and log out removed users

--- server-ce/migrations/5_remove_holding_accounts.coffee
+++ server-ce/migrations/5_remove_holding_accounts.coffee
@@ -4,50 +4,80 @@
 db = mongojs(Settings.mongo.url, ['users', 'projects'])
 async = require "async"

-findHoldingAccounts = (callback = (error, users) ->) ->
-	db.users.find({holdingAccount: true}, {holdingAccount: 1, email: 1}, callback)
+module.exports = HoldingAccountMigration =
+	DRY_RUN: true

-deleteUserProjects = (user_id, callback = (error) ->) ->
-	# Holding accounts can't own projects, so only remove from
-	# collaberator_refs and readOnly_refs
-	console.log "[Removing user from projects]", user_id
-	db.projects.update {
-		$or: [
-			{collaberator_refs: user_id},
-			{readOnly_refs: user_id}
-		]
-	}, {
-		$pull: {
-			collaberator_refs: user_id,
-			readOnly_refs: user_id
-		}
-	}, {
-		multi: true
-	}, (error, result) ->
-		console.log "[Removed user from projects]", user_id, result
-		callback(error)
+	findHoldingAccounts: (callback = (error, users) ->) ->
+		db.users.find({holdingAccount: true}, {holdingAccount: 1, email: 1}, callback)

-deleteUser = (user_id, callback = (error) ->) ->
-	if !user_id?
-		throw new Error("must have user_id")
-	console.log "[Removing user]", user_id
-	db.users.remove {_id: user_id}, (error, result) ->
-		console.log "[Removed user]", user_id, result
-		callback(error)
+	deleteUserProjects: (user_id, callback = (error) ->) ->
+		# Holding accounts can't own projects, so only remove from
+		# collaberator_refs and readOnly_refs
+		console.log "[Removing user from projects]", user_id
+		db.projects.find {
+			$or: [
+				{collaberator_refs: user_id},
+				{readOnly_refs: user_id}
+			]
+		}, { collaberator_refs: 1, readOnly_refs: 1 }, (error, projects = []) ->
+			return callback(error) if error?
+			jobs = projects.map (project) ->
+				(cb) ->
+					console.log "[Removing user from project]", user_id, JSON.stringify(project)
+					if !project._id?
+						throw new Error("no project id")
+
+					if !HoldingAccountMigration.DRY_RUN
+						db.projects.update {
+							_id: project._id
+						}, {
+							$pull: {
+								collaberator_refs: user_id,
+								readOnly_refs: user_id
+							}
+						}, (error, result) ->
+							return cb(error) if error?
+							console.log "[Removed user from project]", user_id, project._id, result
+							cb()
+					else
+						console.log "[Would have removed user from project]", user_id, project._id
+						cb()
+
+			async.series jobs, callback

-exports.migrate = (client, done=()->) ->
-	findHoldingAccounts (error, users) ->
-		throw error if error?
-		console.log "[Got list of holding accounts]", users.map (u) -> u._id
-		jobs = users.map (u) ->
-			(cb) ->
-				deleteUserProjects u._id, (error) ->
-					return cb(error) if error?
-					deleteUser u._id, cb
-		async.series jobs, (error) ->
+	deleteUser: (user_id, callback = (error) ->) ->
+		if !user_id?
+			throw new Error("must have user_id")
+		db.users.find {_id: user_id}, (error, user) ->
+			return callback(error) if error?
+			if !user?
+				throw new Error("expected user")
+			console.log "[Removing user]", user_id, JSON.stringify(user)
+			if !HoldingAccountMigration.DRY_RUN
+				db.users.remove {_id: user_id}, (error, result) ->
+					console.log "[Removed user]", user_id, result
+					callback(error)
+			else
+				console.log "[Would have removed user]", user_id
+				callback()
+
+	run: (done) ->
+		HoldingAccountMigration.findHoldingAccounts (error, users) ->
 			throw error if error?
-			console.log "[Removed holding accounts]"
-			done()
-
-exports.rollback = (client, done) ->
-	done()
+			console.log "[Got list of holding accounts]", users.map (u) -> u._id
+			jobs = users.map (u) ->
+				(cb) ->
+					HoldingAccountMigration.deleteUserProjects u._id, (error) ->
+						return cb(error) if error?
+						HoldingAccountMigration.deleteUser u._id, cb
+			async.series jobs, (error) ->
+				throw error if error?
+				console.log "[Removed holding accounts]"
+				done()
+
+	migrate: (client, done=()->) ->
+		HoldingAccountMigration.DRY_RUN = false
+		HoldingAccountMigration.run(done)
+
+	rollback: (client, done) ->
+		done()


simulator: Catch fbp parse errors

simulator: Catch fbp parse errors

--- lib/simulator.coffee
+++ lib/simulator.coffee
@@ -88,13 +88,18 @@
                 @removeListener 'message', checkUploadDone
                 return callback()

+        console.log 'send'
         @on 'message', checkUploadDone
         @handleMessage
             protocol: 'network'
             command: 'start'

     uploadFBP: (prog, callback) ->
-        @uploadGraph fbp.parse(prog), callback
+        try
+            graph = fbp.parse(prog)
+        catch e
+            return callback e
+        @uploadGraph graph, callback

     # Blocking iteration
     runTick: (tickIntervalMs) ->


Make sure drag callbacks are not documented as public

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -795,7 +795,7 @@

     e.currentTarget.parentNode.classList.remove('selected')

-  # Public: Handle entry name object dragstart event
+  # Handle entry name object dragstart event
   onDragStart: (e) ->
     e.stopPropagation()

@@ -820,12 +820,12 @@
     window.requestAnimationFrame =>
       fileNameElement.remove()

-  # Public: Handle entry dragover event; reset default dragover actions
+  # Handle entry dragover event; reset default dragover actions
   onDragOver: (e) ->
     e.preventDefault()
     e.stopPropagation()

-  # Public: Handle entry drop event
+  # Handle entry drop event
   onDrop: (e) ->
     e.preventDefault()
     e.stopPropagation()


fixed issue with endpont

fixed issue with endpont

--- src/model/statsd.coffee
+++ src/model/statsd.coffee
@@ -70,15 +70,20 @@
 exports.retrieveChannelMetrics = `function *(type, channelId) {
   var data = [];
   var status_array = ['Failed','Successful', 'Completed']
+  var results = {}, path = ''
   var render_url = "/render?target=transformNull(summarize(stats.counters." + domain + ".Channels." + channelId

   if (type == 'status'){
-    var path = render_url + ".Statuses.*.count,'1week'))&from=-1weeks&format=json";
-    var raw = yield fetchData(path);
+    _.forEach(status_array, function *(item){
+      path = render_url + ".Statuses." + item + ".count,'1week'))&from=-1weeks&format=json";
+      console.log(path);
+      results[item] = yield fetchData(path);
+    });
+
     var i = 0;

-    console.log(path);
-    console.log(JSON.stringify(raw));
+
+    console.log(JSON.stringify(results));

     var failed = !raw.data.length ? raw.data[0][0] : 0;
     var successful = !raw.data1.length  ? raw.data1[0][0] : 0;
@@ -95,7 +100,7 @@

   } else {

-    var path = render_url + ".count,'1day'))&from=-7days&format=json";
+        path = render_url + ".count,'1day'))&from=-7days&format=json";
         path += "&target=transformNull(summarize(stats.timers." + domain + ".Channels." + channelId + ".sum,'1day','avg'))";
     var raw = yield fetchData(path);
     var i = 0;


added some new commands to sendgrid to let you pick number of days to go back, list categories, and more category level options

--- src/scripts/sendgrid.coffee
+++ src/scripts/sendgrid.coffee
@@ -11,10 +11,14 @@
 # Commands:
 #   hubot sendgrid total - total sendgrid usage for the account
 #   hubot sendgrid today - Total usage for today
-#   hubot sendgrid c[ategory] <category> - Today's usage for the given category
+#   hubot sendgrid last X [days] - Total usage for the last X days
+#   hubot sendgrid c[ategory] [total] <category> - Today or all time usage for the given category
+#   hubot sendgrid c[ategory] last X [days] <category> - Total usage for the last X days for the given category
+#   hubot sendgrid categories - list all categories for account
 #
 # Author:
 #   sixfeetover
+#   drdamour

 env = process.env

@@ -24,22 +28,64 @@
       opts =
         days: 0
       query msg, opts, (json) ->
-        msg.send formatResponse(json[0])
+        msg.send formatResponse(json[0], json[0].date)

     robot.respond /(sendgrid)( me)? total/i, (msg) ->
       opts =
         aggregate: 1
       query msg, opts, (json) ->
-        msg.send formatResponse(json)
+        msg.send formatResponse(json, "All Time")

-    robot.respond /(sendgrid)( me)? c(ategory)? (.*)/i, (msg) ->
-      category = msg.match[4]
+    robot.respond /(sendgrid)( me)? last (\d+)( days)?/i, (msg) ->
+      opts =
+        days: msg.match[3]
+        aggregate: 1
+      query msg, opts, (json) ->
+        msg.send formatResponse(json, "Last #{opts.days} days")
+
+    robot.respond /(sendgrid)( me)? c(ategory)?( total)? (.*)/i, (msg) ->
+      category = msg.match[5].trim()
+      match = /last (\d+)( days)?/i
+      #this response matches the next respond, so we need to short circuit it
+      #anyone got a better way?
+      if match.test(category)
+        return
+      isAllTime = msg.match[4] is " total"
       msg.send "Category: #{category}"
       opts =
-        days: 0
         category: [category]
+
+      if(isAllTime)
+        opts.aggregate = 1
+      else
+        opts.days = 0
       query msg, opts, (json) ->
-        msg.send formatResponse(json[0])
+        if(isAllTime)
+          #surprisingly when you set a cateogry, the aggregate is sent in an array
+          msg.send formatResponse(json[0], "All Time")
+        else
+          msg.send formatResponse(json[0], json[0].date)
+
+    robot.respond /(sendgrid)( me)? c(ategory)? last (\d+)( days)? (.*)/i, (msg) ->
+      category = msg.match[6].trim()
+      msg.send "Category: #{category}"
+      opts =
+        category: [category]
+        aggregate: 1
+        days: msg.match[4]
+      query msg, opts, (json) ->
+        #surprisingly when you set a cateogry, the aggregate is sent in an array
+        msg.send formatResponse(json[0], "Last #{opts.days} days")
+
+
+    robot.respond /(sendgrid)( me)? categories/i, (msg) ->
+      opts =
+        list: "true"
+      query msg, opts, (json) ->
+        msg.send json[0].category
+        categories = for cat in json
+          "  #{cat.category}"
+        msg.send categories.join('\n')

 query = (msg, opts, callback) ->
   opts.api_user = env.HUBOT_SENDGRID_USER
@@ -47,7 +93,11 @@
   msg.http("https://sendgrid.com/api/stats.get.json")
     .query(opts)
     .get() (err, res, body) ->
-      callback JSON.parse(body)
+      parsedBody = JSON.parse(body)
+      if parsedBody.error
+        msg.send parsedBody.error
+        return
+      callback parsedBody

 stats =
   requests: 'Requests'
@@ -73,8 +123,8 @@
   spamreports: 'Spam Reports'
   repeat_spamreports: 'Repeat Spam Reports'

-formatResponse = (json) =>
+formatResponse = (json, header) =>
   details = for stat, description of stats
     "  #{description}: #{json[stat]}"
-  details.unshift "Stats for #{json.date}:"
+  details.unshift "Stats for #{header}:"
   details.join("\n")


* trim spaces

* trim spaces

--- src/nosql-subkey.coffee
+++ src/nosql-subkey.coffee
@@ -89,7 +89,7 @@
     super
   ###
     first check preHooks if operationType
-    return falsse if prehooks disallow this operation
+    return falsse if prehooks disallow this operation
     or return encoded string.
   ###
   encodeKey: (aPathArray, aKey, op, operationType)->
@@ -100,7 +100,7 @@
     if result is SKIP_OP
       #skip path wrapped key, just encode aKey directly.
       aKey = keyEncoding.encode aKey if keyEncoding
-      return aKey
+      return aKey
     _encodeKey op.path, op.key, keyEncoding, op
   decodeKey: (key, options)->
     keyEncoding = @keyEncoding options
@@ -191,7 +191,7 @@
       k = @encodeKey path, k, vOptions, GET_OP
       vOptions.path = options.path
       result.push k if k isnt false
-
+
     result = AbstractNoSQL::mGetSync.call(@, result, options)
     if options.keys isnt false
       result.map (item)=>
@@ -411,4 +411,3 @@
   #TODO: separate the eventable ability from SubkeyNoSQL
   eventable = require('events-ex/ability')
   eventable SubkeyNoSQL
-


md5 check for doc lines

md5 check for doc lines

--- services/docstore/app/coffee/DocArchiveManager.coffee
+++ services/docstore/app/coffee/DocArchiveManager.coffee
@@ -25,9 +25,14 @@
 		logger.log project_id: project_id, doc_id: doc._id, "sending doc to s3"
 		options = DocArchive.buildS3Options(doc.lines, project_id+"/"+doc._id)
 		request.put options, (err, res)->
+			md5lines = crypto.createHash("md5").update(JSON.stringify(doc.lines)).digest("hex")
+			md5response = res.headers.etag.toString().replace(/\"/g, '')
 			if err? || res.statusCode != 200
 				logger.err err:err, res:res, "something went wrong archiving doc in aws"
 				return callback new Errors.NotFoundError("Error in S3 request")
+			if md5lines != md5response
+				logger.err responseMD5:md5response, linesMD5:md5lines, "error in response md5 from s3"
+				return callback new Errors.NotFoundError("Error in S3 md5 response")
 			MongoManager.markDocAsArchived doc._id, doc.rev, (error) ->
 				return callback(error) if error?
 				callback()
@@ -66,6 +71,6 @@
 				timeout: thirtySeconds
 				json: content
 				#headers:
-				#	'content-md5': crypto.createHash("md5").update(content).digest("hex")
+				#	'content-md5': crypto.createHash("md5").update(JSON.stringify(content)).digest("hex")
 				uri:"https://#{settings.filestore.stores.user_files}.s3.amazonaws.com/#{key}"
 		}


webapp/library2: preliminary support for categories

--- src/smc-webapp/library.cjsx
+++ src/smc-webapp/library.cjsx
@@ -47,6 +47,7 @@
 sortBy = (key) ->
     (list) ->
         _.sortBy(list, (k) -> k[key]?.toLowerCase() ? k)
+

 exports.Library = rclass ({name}) ->
     displayName : 'Library'
@@ -78,14 +79,16 @@
         @scroll_into_view()

     componentWillMount: ->
-        #if next.library != @props.library
+        meta = @props.library.getIn(['examples'])?.metadata
         docs = @props.library.getIn(['examples'])?.documents
+
         if docs?
-            sdocs = sortBy('title')(docs)
+            sortfn = (doc) -> [meta.categories[doc.category], doc.title?.toLowerCase() ? doc.id]
+            sdocs  = _.sortBy(docs, sortfn)
             @setState
                 copy        : false
                 sorted_docs : sdocs
-                metadata    : @props.library.getIn(['examples'])?.metadata
+                metadata    : meta

     target_path: ->
         doc = @state.selected
@@ -129,10 +132,7 @@
         evt.nativeEvent.stopImmediatePropagation()
         return false

-    select_docs: (list, tag, heading) ->
-        # "+tag" or "tag" for inclusion, "-tag" for exclusion
-
-        list.push(
{heading}
)
+    select_docs: (list) ->

         item_style =
             width        : '100%'
@@ -141,27 +141,27 @@
             border       : 'none'
             textAlign    : 'left'

-        include = true
-        if tag[0] == '-'
-            include = false
-            tag = tag[1...]
-        else if tag[0] == '+'
-            tag = tag[1...]
+        list    = []
+        cur_cat = undefined

         @state.sorted_docs.map (doc) =>
-            tags = doc.tags ? []
-            if (include and (tag in tags)) or ((not include) and not (tag in tags))
-                list.push(
-                    <ListGroupItem
-                        key         = {doc.id}
-                        active      = {doc.id == @state.selected?.id}
-                        onClick     = {=> @setState(selected:doc, show_thumb:false)}
-                        style       = {item_style}
-                        bsSize      = {'small'}
-                    >
-                        {doc.title ? doc.id}
-                    </ListGroupItem>
-                )
+            if doc.category isnt cur_cat
+                cur_cat         = doc.category
+                cur_cat_title   = @state.metadata.categories[cur_cat]
+                list.push(
{cur_cat_title}
)
+
+            list.push(
+                <ListGroupItem
+                    key         = {doc.id}
+                    active      = {doc.id == @state.selected?.id}
+                    onClick     = {=> @setState(selected:doc, show_thumb:false)}
+                    style       = {item_style}
+                    bsSize      = {'small'}
+                >
+                    {doc.title ? doc.id}
+                </ListGroupItem>
+            )
+        return list

     selector: ->
         list_style =
@@ -174,12 +174,8 @@
             borderRadius : '5px'
             padding      : '5px'

-        list = []
-        @select_docs(list, '+intro', 'Introduction')
-        @select_docs(list, '-intro', 'Library')
-
         <ListGroup style={list_style} onKeyUp={@selector_keyup} ref='selector_list'>
-            {list}
+            {@select_docs()}
         </ListGroup>

     thumbnail: ->
@@ -212,7 +208,7 @@
             overflow   : 'auto'

-

+

                 {doc.title ? doc.id}
                 {" by #{doc.author}" if doc.author?}


Use https instead of http for updates.appgyver.com

--- src/steroids/Updater.coffee
+++ src/steroids/Updater.coffee
@@ -47,7 +47,7 @@

     currentVersion = opts.version

-    endpointURL = "http://updates.appgyver.com/client/latest.json?platform=#{encodedPlatform}&version=#{encodedVersion}&os_version=#{encodedOsVersion}&device=#{encodedDevice}&simulator=#{simulator}&user_id=#{currentUserId}"
+    endpointURL = "https://updates.appgyver.com/client/latest.json?platform=#{encodedPlatform}&version=#{encodedVersion}&os_version=#{encodedOsVersion}&device=#{encodedDevice}&simulator=#{simulator}&user_id=#{currentUserId}"

     @getFromEndpoint endpointURL, (latestVersion) =>

@@ -72,7 +72,7 @@
     encodedOsType = encodeURIComponent(osType)
     encodedVersion = encodeURIComponent(currentVersion)

-    endpointURL = "http://updates.appgyver.com/steroids/latest.json?os=#{encodedOsType}&version=#{encodedVersion}&from=#{opts.from}&user_id=#{currentUserId}"
+    endpointURL = "https://updates.appgyver.com/steroids/latest.json?os=#{encodedOsType}&version=#{encodedVersion}&from=#{opts.from}&user_id=#{currentUserId}"

     @getFromEndpoint endpointURL, (latestVersion) =>

@@ -93,4 +93,4 @@
     return deferred.promise

-module.exports = Updater+module.exports = Updater


only log instanceid

only log instanceid

--- src/terminator-model.coffee
+++ src/terminator-model.coffee
@@ -85,7 +85,7 @@
   _terminate: (ip, callback=->) =>
     @_findInstance ip, (error, instance) =>
       return callback error if error?
-      debug 'found instance', instance
+      debug 'found instance', instance?.InstanceId

       @_terminateInstance instance?.InstanceId, (error) =>
         return callback error if error?


Fixed getter being overwritten

Fixed getter being overwritten

--- src/underscore-query.coffee
+++ src/underscore-query.coffee
@@ -255,10 +255,10 @@
   scoreInc = 1 / query.length

   for q in query
-    if q.getter
-      attr = q.getter model, q.key
-    else if getter
+    if getter
       attr = getter model, q.key
+    else if q.getter
+        attr = q.getter model, q.key
     else
       attr = model[q.key]
     # Check if the attribute value is the right type (some operators need a string, or an array)


Remove trailing newline

Remove trailing newline

--- src/scripts/tael.coffee
+++ src/scripts/tael.coffee
@@ -110,4 +110,3 @@

     for tile in new_tiles
         newTile tiles, tile[0], tile[1]
-


regexpEscape cleanup

regexpEscape cleanup

--- src/shared/brand.coffee
+++ src/shared/brand.coffee
@@ -1,4 +1,4 @@
-regExpEscape = require('./regexpescape.coffee')
+regExpEscape = require('./regexpEscape')
 arrayFind = require('./arrayFind')

 module.exports = class Brand


fixing raise condition. createdAt of newMemberBucket overview, if it did not exist in cache and if it is not created in a brand new cache collection

--- workers/social/lib/social/models/activity/cache.coffee
+++ workers/social/lib/social/models/activity/cache.coffee
@@ -375,8 +375,7 @@
           setModifier.newMemberBucketIndex  = index
           setModifier["#{key}.ids"]         = newIds
           setModifier["#{key}.count"]       = freshNewMemberBucket.count
-          setModifier["#{key}.createdAt.0"] = freshNewMemberBucket.createdAt.first
-          setModifier["#{key}.createdAt.1"] = freshNewMemberBucket.createdAt.last
+          setModifier["#{key}.createdAt"]   = freshNewMemberBucket.createdAt
           setModifier["#{key}.type"]        = 'CNewMemberBucketActivity'

         # only keep CNewMemberBucketActivity's of newIds


fixed a manual merge

fixed a manual merge

--- workers/social/lib/social/models/activity/index.coffee
+++ workers/social/lib/social/models/activity/index.coffee
@@ -357,26 +357,36 @@
   @on 'BucketIsUpdated',   notifyCache.bind this, 'BucketIsUpdated'
   @on 'UserMarkedAsTroll', notifyCache.bind this, 'UserMarkedAsTroll'

-  @fetchPublicActivityFeed: permit 'read activity',
-    success: (client, seed, options, callback)->
-      group.canReadActivity client, (err, res)->
-        if err then return callback {error: "Not allowed to open this group"}
-
-        timestamp  = options.timestamp
-        rawStartDate  = if timestamp? then parseInt(timestamp, 10) else (new Date).getTime()
-        # this is for unix and javascript timestamp differance
-        startDate  = Math.floor(rawStartDate/1000)
-
-        neo4jConfig = KONFIG.neo4j
-        requestOptions =
-          startDate : startDate
-          neo4j : neo4jConfig
-          group :
-            groupName : group.slug
-            groupId : group._id
-
-
-        FetchAllActivityParallel = require './../graph/fetch'
-        fetch = new FetchAllActivityParallel requestOptions
-        fetch.get (results)->
-          callback null, results
+
+  @fetchPublicActivityFeed = secure (client, options, callback)->
+    {delegate} = client.connection
+    if not delegate
+      callback null, []
+    else
+      groupName = options.groupName
+      unless groupName then return callback new Error "Group name is undefined"
+      JGroup = require '../group'
+      JGroup.one slug : groupName, (err, group)=>
+        if err then return callback err
+        unless group then return callback {error: "Group not found"}
+        group.canReadActivity client, (err, res)->
+          if err then return callback {error: "Not allowed to open this group"}
+
+          timestamp  = options.timestamp
+          rawStartDate  = if timestamp? then parseInt(timestamp, 10) else (new Date).getTime()
+          # this is for unix and javascript timestamp differance
+          startDate  = Math.floor(rawStartDate/1000)
+
+          neo4jConfig = KONFIG.neo4j
+          requestOptions =
+            startDate : startDate
+            neo4j : neo4jConfig
+            group :
+              groupName : group.slug
+              groupId : group._id
+
+
+          FetchAllActivityParallel = require './../graph/fetch'
+          fetch = new FetchAllActivityParallel requestOptions
+          fetch.get (results)->
+            callback null, results


fix https://github.com/sagemathinc/smc/issues/485 -- "latex dpi preview issues" -- fixed another bug with mesg.file not defined; formatted.

--- src/smc-webapp/editor_latex.coffee
+++ src/smc-webapp/editor_latex.coffee
@@ -59,7 +59,7 @@
         n = @filename.length

         # The pdf preview.
-        @preview = new editor.PDF_Preview(@project_id, @filename, undefined, {resolution:200})
+        @preview = new editor.PDF_Preview(@project_id, @filename, undefined, {resolution:@get_resolution()})
         @element.find(".salvus-editor-latex-png-preview").append(@preview.element)
         @_pages['png-preview'] = @preview
         @preview.on 'shift-click', (opts) => @_inverse_search(opts)
@@ -259,8 +259,8 @@
             return false

         @element.find("a[href=\"#zoom-preview-out\"]").click () =>
-            @preview.zoom(delta:-5)
-            @set_conf(zoom_width:@preview.zoom_width)
+            @preview.zoom(delta: -5)
+            @set_conf(zoom_width: @preview.zoom_width)
             return false

         @element.find("a[href=\"#zoom-preview-in\"]").click () =>
@@ -356,16 +356,19 @@
         else
             try
                 res = parseInt(res)
-                if res < 150
-                    res = 150
+                if res < 75
+                    res = 75
                 else if res > 600
                     res = 600
                 @preview.opts.resolution = res
+                @set_conf(resolution : res)
                 @preview.update()
             catch e
                 alert_message(type:"error", message:"Invalid resolution #{res}")

     get_resolution: () =>
+        if not @preview?
+            return @load_conf()['resolution'] ? 150
         return @preview.opts.resolution

     # This function isn't called on save button click since
@@ -593,12 +596,12 @@
         if not mesg.line
             if mesg.page
                 @_inverse_search
-                    n : mesg.page
-                    active : false
-                    x : 50
-                    y : 50
-                    resolution:200
-                    cb: cb
+                    n          : mesg.page
+                    active     : false
+                    x          : 50
+                    y          : 50
+                    resolution : @get_resolution()
+                    cb         : cb
             else
                 alert_message(type:"error", "Unknown location in '#{file}'.")
                 cb?()
@@ -620,7 +623,6 @@
                 @forward_search(active:true)

     render_error_message: (mesg) =>
-
         if not mesg.line
             r = mesg.raw
             i = r.lastIndexOf('[')
@@ -629,7 +631,7 @@
                 j += 1
             mesg.page = r.slice(i+1,j)

-        if mesg.file.slice(0,2) == './'
+        if mesg.file?.slice(0,2) == './'
             mesg.file = mesg.file.slice(2)

         elt = @_error_message_template.clone().show()


must be something along these lines #norelease

--- content/BetterBibTeX.coffee
+++ content/BetterBibTeX.coffee
@@ -34,18 +34,14 @@
   AddonManager.addAddonListener({
     onUninstalling: (addon, needsRestart) ->
       return unless addon.id == 'better-bibtex@iris-advies.com'
+      debug('uninstall')

       for label, metadata of Translators.byName
         try
           Translators.uninstall(label, metadata.translatorID)

-      try
-        panePersist = JSON.parse(Zotero.Prefs.get('pane.persist'))
-        if panePersist['zotero-items-column-citekey']
-          delete panePersist['zotero-items-column-citekey']
-          Zotero.Prefs.set('pane.persist', JSON.stringify(panePersist))
-      catch err
-        debug('Could not remove zotero-items-column-citekey:', err)
+      column.removeAttribute('zotero-perist') if column = document.getElementById('zotero-items-column-citekey')
+
       return

     onOperationCancelled: (addon, needsRestart) ->
@@ -55,6 +51,8 @@
       for id, header of Translators.byId
         try
           Translators.install(header)
+
+      column.setAttribute('zotero-perist', 'width ordinal hidden sortActive sortDirection') if column = document.getElementById('zotero-items-column-citekey')

       return
   })


DRY up delegate calls

DRY up delegate calls

--- src/trix/models/composition.coffee
+++ src/trix/models/composition.coffee
@@ -32,7 +32,7 @@
   # Responder protocol

   insertText: (text, {updatePosition} = updatePosition: true) ->
-    @delegate?.compositionWillSetLocationRange?() if updatePosition
+    @notifyDelegateOfIntentionToSetLocationRange() if updatePosition

     range = @getLocationRange()
     @document.insertTextAtLocationRange(text, range)
@@ -43,14 +43,14 @@
       @setLocationRange({index, offset})

   insertPlaceholderBlock: ->
-    @delegate?.compositionWillSetLocationRange?()
+    @notifyDelegateOfIntentionToSetLocationRange()
     range = @getLocationRange()
     document = new Trix.Document [Trix.Block.createPlaceholder()]
     @insertDocument(document)
     @setLocationRange(index: range.end.index + 1, offset: 0)

   insertDocument: (document) ->
-    @delegate?.compositionWillSetLocationRange?()
+    @notifyDelegateOfIntentionToSetLocationRange()
     range = @getLocationRange()
     @document.insertDocumentAtLocationRange(document, range)

@@ -93,7 +93,7 @@
       @insertText(text)

   deleteInDirectionWithGranularity: (direction, granularity) ->
-    @delegate?.compositionWillSetLocationRange?()
+    @notifyDelegateOfIntentionToSetLocationRange()
     range = @getLocationRange()

     if range.isCollapsed()
@@ -113,7 +113,7 @@
     @deleteInDirectionWithGranularity("backward", "word")

   moveTextFromLocationRange: (locationRange) ->
-    @delegate?.compositionWillSetLocationRange?()
+    @notifyDelegateOfIntentionToSetLocationRange()
     position = @getPosition()
     @document.moveTextFromLocationRangeToPosition(locationRange, position)
     @setPosition(position)
@@ -207,6 +207,9 @@
   preserveSelection: (block) ->
     @selectionDelegate?.preserveSelection?(block) ? block()

+  notifyDelegateOfIntentionToSetLocationRange: ->
+    @delegate?.compositionWillSetLocationRange?()
+
   expandSelectionForEditing: ->
     for key, value of Trix.attributes when value.parent
       if @hasCurrentAttribute(key)


FIX: Recreation of PDFs

FIX: Recreation of PDFs

--- src/app/projects/states/dashboard/directives/task-dashboard/directives/task-submission-card/task-submission-card.coffee
+++ src/app/projects/states/dashboard/directives/task-dashboard/directives/task-submission-card/task-submission-card.coffee
@@ -33,6 +33,6 @@
     $scope.uploadAlternateFiles = ->
       taskService.presentTaskSubmissionModal($scope.task, $scope.task.status, true)
     $scope.regeneratePdf = ->
-      taskService.recreatePDF($scope.task, null)
+      $scope.task.recreateSubmissionPdf()

 )