Halfway-converted to Graph API

Halfway-converted to Graph API

--- components/ConnectRuntime.coffee
+++ components/ConnectRuntime.coffee
@@ -48,48 +48,49 @@

   sendGraph: (runtime, graph) ->
     runtime.sendGraph 'clear',
-      id: graph.id
+      id: graph.properties.id
       name: graph.name
-      library: graph.project
-      main: (@project and graph.id is @project.main)
-    for name, definition of graph.processes
+      library: graph.properties.project
+      main: (@project and graph.properties.id is @project.main)
+    for node of graph.nodes
       runtime.sendGraph 'addnode',
-        id: name
-        component: definition.component
-        metadata: definition.metadata
-        graph: graph.id
-    for edge in graph.connections
-      if edge.src
-        runtime.sendGraph 'addedge',
-          src:
-            node: edge.src.process
-            port: edge.src.port
-          tgt:
-            node: edge.tgt.process
-            port: edge.tgt.port
-          graph: graph.id
-        continue
+        id: node.id
+        component: node.component
+        metadata: node.metadata
+        graph: graph.properties.id
+    for edge in graph.edges
+      runtime.sendGraph 'addedge',
+        src:
+          node: edge.from.node
+          port: edge.from.port
+        tgt:
+          node: edge.to.node
+          port: edge.to.port
+        metadata: edge.metadata
+        graph: graph.properties.id
+    for iip in graph.initializers
       runtime.sendGraph 'addinitial',
         src:
-          data: edge.data
+          data: iip.from.data
         tgt:
-          node: edge.tgt.process
-          port: edge.tgt.port
-        graph: graph.id
+          node: iip.to.node
+          port: iip.to.port
+        metadata: iip.metadata
+        graph: graph.properties.id
     if graph.inports
       for pub, priv of graph.inports
         runtime.sendGraph 'addinport',
           public: pub
           node: priv.process
           port: priv.port
-          graph: graph.id
+          graph: graph.properties.id
     if graph.outports
       for pub, priv of graph.outports
         runtime.sendGraph 'addoutport',
           public: pub
           node: priv.process
           port: priv.port
-          graph: graph.id
+          graph: graph.properties.id

   convertNode: (id, node) ->
     data = node.toJSON()
@@ -161,11 +162,12 @@
     @connected = false
     runtime.on 'connected', =>
       @connected = true
+      editor.library = {}
       runtime.sendComponent 'list', ''
       @sendProject @runtime, @project if @project
     runtime.on 'disconnected', =>
       @connected = false
-    @subscribeEditor editor.graph.id, editor, runtime
+    @subscribeEditor editor.graph.properties.id, editor, runtime

     runtime.on 'component', (message) ->
       if message.payload.name is 'Graph' or message.payload.name is 'ReadDocument'


fix introduced bug

fix introduced bug

--- components/cms/lib/Module.coffee
+++ components/cms/lib/Module.coffee
@@ -43,8 +43,6 @@
       routes[@Config.moduleName+'/edit/:id'] = "edit"
       routes[@Config.moduleName+'/show/:id'] = "show"
       routes[@Config.moduleName+'/action/:action'] = "action"
-      routes[@Config.moduleName+'/remove/'] = "removeSelected"
-      routes[@Config.moduleName+'/export/'] = "exportSelected"

       views = @Controller.controls.get "views"
       Object.keys(views).forEach (key)=>


Fixes #2

Fixes #2

--- source/music.coffee
+++ source/music.coffee
@@ -20,6 +20,16 @@
   track = $ "<audio />",
     loop: "loop"
   .appendTo('body').get(0)
+
+  appendSources = (audio, name) ->
+    [
+      ["ogg", "audio/ogg"]
+      ["mp3", "audio/mpeg"]
+    ].forEach ([extension, mimeType]) ->
+      source = document.createElement('source')
+      source.type = mimeType
+      source.src = "#{name}.#{extension}"
+      audio.appendChild(source)

   updateTrackVolume = ->
     track.volume = globalMusicVolume * trackVolume
@@ -53,8 +63,9 @@
   ###
   play: (name) ->
     updateTrackVolume()
-    # TODO: Format fallbacks
-    track.src = "#{BASE_URL}/sounds/#{name}.mp3"
+
+    appendSources(track, "#{BASE_URL}/music/#{name}")
+
     track.play()

   ###*


Flow impl bug with first element larger than flow size fixed

--- impl/base/level1/flow.coffee
+++ impl/base/level1/flow.coffee
@@ -170,7 +170,7 @@
 			right += rightMargin

 		# get x
-		if right > maxFlowWidth
+		if right > maxFlowWidth and y > 0
 			right -= x
 			x = right - width
 			currentRowY += rowsHeight[currentRow]


protect against no task in store for testing

--- src/components/book-content-mixin.cjsx
+++ src/components/book-content-mixin.cjsx
@@ -49,9 +49,10 @@
         { bookId, cnxId: cnxId or @getCnxId() }
       )
     else
-      {related_content} = TaskStepStore.get(@props.id)
-      section = @sectionFormat?(related_content[0]?.chapter_section or related_content[0]?.book_location)
-      referenceBookLink = @context.router.makeHref('viewReferenceBookSection', {bookId, section}) if section?
+      related_content = TaskStepStore.get(@props.id)?.related_content
+      if related_content?
+        section = @sectionFormat?(related_content[0]?.chapter_section or related_content[0]?.book_location)
+        referenceBookLink = @context.router.makeHref('viewReferenceBookSection', {bookId, section}) if section?

     referenceBookLink


Fix the contentsPath error

Fix the contentsPath error

--- build/tasks/rebrand-electron-task.coffee
+++ build/tasks/rebrand-electron-task.coffee
@@ -30,12 +30,12 @@
         newName = path.resolve(shellAppDir, appName)
         fs.renameSync oldName, newName
       when 'darwin'
-        contentsDir = grunt.config.get("#{pkgName}.contentsDir")
+        contentsPath = grunt.config.get("#{pkgName}.contentsDir")
         defaultBundleName = 'com.electron.' + pkgName

         frameworksPath = path.join(contentsPath, 'Frameworks')
         helperPlistFilename = path.join(frameworksPath, 'Electron Helper.app', 'Contents', 'Info.plist')
-        appPlistFilename = path.join(contentsDir, 'Info.plist')
+        appPlistFilename = path.join(contentsPath, 'Info.plist')

         plist = require('plist')
         appPlist = plist.parse(fs.readFileSync(appPlistFilename).toString())


Add tests for graph edge add/remove

Add tests for graph edge add/remove

--- spec/Network.coffee
+++ spec/Network.coffee
@@ -112,6 +112,36 @@
           chai.expect(err).to.be.an 'error'
           chai.expect(err.message).to.contain 'not found'
           done()
+    describe 'with new edge', ->
+      before ->
+        n.loader.components.Split = Split
+        g.addNode 'A', 'Split',
+        g.addNode 'B', 'Split',
+      after ->
+        g.removeNode 'A'
+        g.removeNode 'B'
+      it 'should contain the edge', (done) ->
+        g.once 'addEdge', ->
+          setTimeout ->
+            chai.expect(n.connections).not.to.be.empty
+            chai.expect(n.connections[0].from).to.eql
+              process: n.getNode 'A'
+              port: 'out'
+              index: undefined
+            chai.expect(n.connections[0].to).to.eql
+              process: n.getNode 'B'
+              port: 'in'
+              index: undefined
+            done()
+          , 10
+        g.addEdge 'A', 'out', 'B', 'in'
+      it 'should not contain the edge after removal', (done) ->
+        g.once 'removeEdge', ->
+          setTimeout ->
+            chai.expect(n.connections).to.be.empty
+            done()
+          , 10
+        g.removeEdge 'A', 'out', 'B', 'in'

   describe 'with a simple graph', ->
     g = null


Remove log

Remove log

--- spec/Subtree.coffee
+++ spec/Subtree.coffee
@@ -72,7 +72,6 @@
         t = subtree.then 'tripled', (c, d) ->
           chai.expect(c.get('non-existant1')).to.equal null
           chai.expect(c.get('existant2')).to.equal 'foo'
-          console.log d*3
           d * 3

       t = Root()


Fix overwritting .run() in the data explorer

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -3407,6 +3407,8 @@
                 is_at_bottom: 'true'

     class @DriverHandler
+        query_error_template: Handlebars.templates['dataexplorer-query_error-template']
+
         # I don't want that thing in window
         constructor: (args) ->
             @container = args.container
@@ -3435,7 +3437,7 @@
                 that = @
                 TermBase.prototype.private_run = TermBase.prototype.run
                 TermBase.prototype.run = ->
-                    throw that.container.query_error_template
+                    throw that.query_error_template
                         found_run: true

         connect: =>


Fix: Diagrams maintain image meta-data when reloaded / opened.

[#100136692]

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

--- src/code/models/palette-manager.coffee
+++ src/code/models/palette-manager.coffee
@@ -50,10 +50,14 @@
   onLoadData: (data) ->
     @info "onLoadData called"
     # reload the palette
+    @palette = []
     if data.palette
-      @palette = data.palette.slice 0
+      for p_item in data.palette
+        @_addToPallete p_item
     for node in data.nodes
       @_addToPallete node
+
+
     @_updateChanges()

   _addToPallete: (node) ->


fix URIError: URI malformed  in case of not closed surrogate pair

--- src/validators/text-diff.coffee
+++ src/validators/text-diff.coffee
@@ -23,11 +23,25 @@
   #Validates if given strings are equal
   #@return [ValidationErrors]
   validate: ->
+
+    sanitizeSurrogatePairs = (data) ->
+      data.replace(/[\uD800-\uDBFF]/g, '').replace(/[\uDC00-\uDFFF]/g, '')
+
     @output = null
     dmp = new DiffMatchPatch
-    patch = dmp.patch_make @real, @expected
-    @output = dmp.patch_toText patch
-
+
+    try
+      patch = dmp.patch_make @real, @expected
+      @output = dmp.patch_toText patch
+      return @output
+    catch e
+      if e instanceof URIError
+        patch = dmp.patch_make sanitizeSurrogatePairs(@real), sanitizeSurrogatePairs(@expected)
+        @output = dmp.patch_toText patch
+        return @output
+      else
+        throw e
+
   @evaluateOutputToResults: (data) ->
     results = []
     if data == null


Transform input tel|url in view to make it clickable.

--- app/assets/javascripts/app/index.coffee
+++ app/assets/javascripts/app/index.coffee
@@ -102,6 +102,15 @@
       isHtmlEscape = true
       result       = App.i18n.translateDate(result)

+    # transform input tel|url to make it clickable
+    if attribute_config.tag is 'input'
+      isHtmlEscape = true
+      result       = App.Utils.htmlEscape(result)
+      if attribute_config.type is 'tel'
+        result = "#{result}"
+      if attribute_config.type is 'url'
+        result = App.Utils.linkify(result)
+
     # use pretty time for datetime
     else if attribute_config.tag is 'datetime'
       isHtmlEscape = true


removed javascript stuff from old site

removed javascript stuff from old site

--- app/assets/javascripts/edit_trip.coffee
+++ app/assets/javascripts/edit_trip.coffee
@@ -1,40 +0,0 @@
-$(document).ready ->
-  init_map()
-  set_new_route($("#trip_route").val())
-
-  # Accordion for rides:
-  $( "#accordion" ).accordion({collapsible: true,autoHeight: false})
-  # Tabs for ride:
-  $( ".tabs" ).tabs()
-
-  # Add Story:
-  $( "#add_story" )
-    .button()
-    .click( -> $( "#tripstory" ).dialog({minWidth: 532 }))
-
-  $('#slider').nivoSlider
-    effect: 'fade' # Specify sets like: 'fold,fade,sliceDown'
-    pauseTime: 8000 # How long each slide will show
-    controlNav: false # 1,2,3... navigation
-    # startSlide: 0, # Set starting Slide (0 index)
-    # directionNav: true, # Next & Prev navigation
-    # directionNavHide: true, # Only show on hover
-    # controlNavThumbs: false, # Use thumbnails for Control Nav
-    # controlNavThumbsFromRel: false, # Use image rel for thumbs
-    # controlNavThumbsSearch: '.jpg', # Replace this with...
-    # controlNavThumbsReplace: '_thumb.jpg', # ...this in thumb Image src
-    # keyboardNav: true, # Use left & right arrows
-    # pauseOnHover: true, # Stop animation while hovering
-    # manualAdvance: false, # Force manual transitions
-    # captionOpacity: 0.8, # Universal caption opacity
-    # prevText: 'Prev', # Prev directionNav text
-    # nextText: 'Next', # Next directionNav text
-    # randomStart: false, # Start on a random slide
-    # beforeChange: function(){}, # Triggers before a slide transition
-    # afterChange: function(){}, # Triggers after a slide transition
-    # slideshowEnd: function(){}, # Triggers after all slides have been shown
-    # lastSlide: function(){}, # Triggers when last slide is shown
-    # afterLoad: -> # Triggers when slider has loaded
-  if $("#slider img").size() == 1
-    $('#slider').data('nivoslider').stop(); # Stop the Slider
-    $(".nivo-directionNav").remove()


Marge Conflict Fix

Marge Conflict Fix

--- src/model/audits.coffee
+++ src/model/audits.coffee
@@ -7,37 +7,37 @@
   "codeSystemName": { type: String, required: false }

-activeParticipantSchema = new Schema
-  "UserID": { type: String, required: false }
-  "AlternativeUserID": { type: String, required: false }
-  "UserIsRequestor": { type: String, required: false }
-  "NetworkAccessPointID": { type: String, required: false }
-  "NetworkAccessPointTypeCode": { type: String, required: false }
-  "RoleIDCode": codeTypeSchema
+ActiveParticipantSchema = new Schema
+  "userID": { type: String, required: false }
+  "alternativeUserID": { type: String, required: false }
+  "userIsRequestor": { type: String, required: false }
+  "networkAccessPointID": { type: String, required: false }
+  "networkAccessPointTypeCode": { type: String, required: false }
+  "roleIDCode": codeTypeSchema

 ParticipantObjectIdentificationSchema = new Schema
-  "ParticipantObjectID": { type: String, required: false }
-  "ParticipantObjectTypeCode": { type: String, required: false }
-  "ParticipantObjectTypeCodeRole": { type: String, required: false }
-  "ParticipantObjectIDTypeCode": codeTypeSchema
-  "ParticipantObjectQuery": { type: String, required: false }
-  "ParticipantObjectDetail":
+  "participantObjectID": { type: String, required: false }
+  "participantObjectTypeCode": { type: String, required: false }
+  "participantObjectTypeCodeRole": { type: String, required: false }
+  "participantObjectIDTypeCode": codeTypeSchema
+  "participantObjectQuery": { type: String, required: false }
+  "participantObjectDetail":
     "type": { type: String, required: false }
     "value": { type: String, required: false }

-auditRecordSchema = new Schema
+AuditRecordSchema = new Schema
   "rawMessage":  { type: String, required: false }
-  "EventIdentification":
-    "EventDateTime": { type: Date, required: true, default: Date.now }
-    "EventOutcomeIndicator": { type: String, required: false }
-    "EventActionCode": { type: String, required: false }
-    "EventID": codeTypeSchema
-    "EventTypeCode": codeTypeSchema
-  "ActiveParticipant": [ activeParticipantSchema ]
-  "AuditSourceIdentification":
-    "AuditSourceID": { type: String, required: false }
-  "ParticipantObjectIdentification": [ ParticipantObjectIdentificationSchema ]
+  "eventIdentification":
+    "eventDateTime": { type: Date, required: true, default: Date.now }
+    "eventOutcomeIndicator": { type: String, required: false }
+    "eventActionCode": { type: String, required: false }
+    "eventID": codeTypeSchema
+    "eventTypeCode": codeTypeSchema
+  "activeParticipant": [ ActiveParticipantSchema ]
+  "auditSourceIdentification":
+    "auditSourceID": { type: String, required: false }
+  "participantObjectIdentification": [ ParticipantObjectIdentificationSchema ]

-exports.Audit = mongoose.model 'Audit', auditRecordSchema+exports.Audit = mongoose.model 'Audit', AuditRecordSchema


semicolon fix

semicolon fix

--- src/models/Audio.coffee
+++ src/models/Audio.coffee
@@ -2,12 +2,13 @@
 class Audio

   play: (options={}, callbacks={})->
+
     readyCapableDevice = false

     setTimeout =>
       return if readyCapableDevice

-      navigator.notification.alert("Audio playback requires a newer version of Scanner, please update from the App Store.", null, "Update Required");
+      navigator.notification.alert("Audio playback requires a newer version of Scanner, please update from the App Store.", null, "Update Required")
     , 500

     steroids.on "ready", ()=>


Close popover when clicking outside

Close popover when clicking outside

--- app/assets/javascripts/suites.js.coffee
+++ app/assets/javascripts/suites.js.coffee
@@ -47,6 +47,20 @@
             cls.replace /suite-show-\w+/, "suite-show-#{$button.data("value")}"

     # Display a popover of student data
-    $(".suite-results a.student").popover(html: true).click (e) ->
-        e.preventDefault()
-        $(".suite-results a.student").not(this).popover("hide")
+    addedCloseHandler = false
+
+    $(".suite-results a.student").each ->
+        $trigger = $(this)
+        $trigger.popover(html: true)
+        $trigger.click (e) ->
+            e.preventDefault()
+            $(".suite-results a.student").not(this).popover("hide")
+
+        if !addedCloseHandler
+            $("html").on "click.popover.data-api", (event) ->
+                $target = $(event.target)
+
+                if $target.data("toggle") != "popover" && $target.closest(".popover").length == 0
+                    $(".suite-results a.student").popover("hide")
+
+            addedCloseHandler = true


needed to allow this type on the front end too.

--- app/assets/javascripts/videos.js.coffee
+++ app/assets/javascripts/videos.js.coffee
@@ -16,7 +16,7 @@
     maxFilesize: $('#max_file_size').val() || 1000
     uploadMultiple: false
     addRemoveLinks: false
-    acceptedFiles: 'video/mp4,video/quicktime'
+    acceptedFiles: 'video/mp4,video/quicktime,video/m4v'
     headers:
       'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
     url: $('form.upload_form').attr('action')


Render frame request tweak

Render frame request tweak

--- SingularityUI/app/views/requests.coffee
+++ SingularityUI/app/views/requests.coffee
@@ -44,8 +44,6 @@

     initialize: ({@requestsFilter, @requestsSubFilter, @searchFilter}) ->
         @bodyTemplate = @bodyTemplateMap[@requestsFilter]
-
-        window.requestAnimationFrame @handleScroll

         # Set up collection
         collectionMap =
@@ -135,6 +133,9 @@

         @renderTableChunk()

+        # Start following the render frames for scrolls
+        window.requestAnimationFrame @handleScroll
+
     renderTableChunk: =>
         if @ isnt app.views.current
             return
@@ -184,8 +185,8 @@
         @renderTable()

     handleScroll: =>
+        return if @renderProgress >= @collection.length
         window.requestAnimationFrame @handleScroll
-        return if @renderProgress >= @collection.length

         $table = @$ "tbody"
         return if $table.length is 0


:fire: win-powershell.coffee

:fire: win-powershell.coffee

--- src/main-process/win-powershell.coffee
+++ src/main-process/win-powershell.coffee
@@ -1,39 +0,0 @@
-path = require 'path'
-Spawner = require './spawner'
-
-if process.env.SystemRoot
-  system32Path = path.join(process.env.SystemRoot, 'System32')
-  powershellPath = path.join(system32Path, 'WindowsPowerShell', 'v1.0', 'powershell.exe')
-else
-  powershellPath = 'powershell.exe'
-
-# Spawn powershell.exe and callback when it completes
-spawnPowershell = (args, callback) ->
-  # Set encoding and execute the command, capture the output, and return it
-  # via .NET's console in order to have consistent UTF-8 encoding.
-  # See http://stackoverflow.com/questions/22349139/utf-8-output-from-powershell
-  # to address https://github.com/atom/atom/issues/5063
-  args[0] = """
-    [Console]::OutputEncoding=[System.Text.Encoding]::UTF8
-    $output=#{args[0]}
-    [Console]::WriteLine($output)
-  """
-  args.unshift('-command')
-  args.unshift('RemoteSigned')
-  args.unshift('-ExecutionPolicy')
-  args.unshift('-noprofile')
-  Spawner.spawn(powershellPath, args, callback)
-
-# Get the user's PATH environment variable registry value.
-#
-# * `callback` The {Function} to call after registry operation is done.
-#   It will be invoked with the same arguments provided by {Spawner.spawn}.
-#
-# Returns the user's path {String}.
-exports.getPath = (callback) ->
-  spawnPowershell ['[environment]::GetEnvironmentVariable(\'Path\',\'User\')'], (error, stdout) ->
-    if error?
-      return callback(error)
-
-    pathOutput = stdout.replace(/^\s+|\s+$/g, '')
-    callback(null, pathOutput)


valid coffeescript in bar and colorbar (no --- for jekyll)

--- colorbar.coffee
+++ colorbar.coffee
@@ -1,6 +1,3 @@
----
----
-
 class d3.chart.Colorbar extends d3.chart.BaseChart

     constructor: ->


modified

modified

--- src/sortsina.coffee
+++ src/sortsina.coffee
@@ -51,7 +51,7 @@
         console.error 'sortsina.coffee >> qsort', error
         return

-      if isetf
+      if isetf #去掉貨幣基金,缺行情數據故
         resp = (each for each in arr when (not /^(1590|511)/.test each.code))
       else
         resp = arr


remove incorrect comment

remove incorrect comment

--- src/components/app/pages/thread.coffee
+++ src/components/app/pages/thread.coffee
@@ -31,7 +31,6 @@
       for node in messageNode.children
         queue.push node

-    # Reverse the message order. This is needed because we're using flexbox
     return messages

   render: ->


extend generateUserForm data by argument

--- workers/social/testhelper.coffee
+++ workers/social/testhelper.coffee
@@ -45,7 +45,7 @@
       callback 'session error'

-generateDummyUserFormData = ->
+generateDummyUserFormData = (opts = {}) ->

   dummyUserFormData =
     email                     : generateRandomEmail()
@@ -53,6 +53,8 @@
     password                  : 'testpass'
     username                  : generateRandomUsername()
     passwordConfirm           : 'testpass'
+
+  dummyUserFormData = _.extend dummyUserFormData, opts

   return dummyUserFormData


Add upload-assets grunt task

Add upload-assets grunt task

--- build/tasks/publish-build-task.coffee
+++ build/tasks/publish-build-task.coffee
@@ -18,7 +18,9 @@
 module.exports = (gruntObject) ->
   grunt = gruntObject

-  grunt.registerTask 'publish-build', 'Publish the built app', ->
+  grunt.registerTask 'publish-build', 'Publish the built app', ['build-docs', 'upload-assets']
+
+  grunt.registerTask 'upload-assets', ->
     return if process.env.JANKY_SHA1 and process.env.JANKY_BRANCH isnt 'master'

     done = @async()


Remove \begin{ special syntax highlight

Remove \begin{ special syntax highlight

--- grammars/latex.cson
+++ grammars/latex.cson
@@ -74,7 +74,7 @@
       					(\\[)([^\\[]*?)(\\])								# Optional Title
       				)??
       				(\\{)												# Opening Bracket
-
+
     '''
     beginCaptures:
       "1":
@@ -292,7 +292,7 @@
       						)
       					(\\})										# Close Bracket
       					(\\s*\\n)?				# Match to end of line absent of content
-
+
     '''
     captures:
       "1":
@@ -314,7 +314,7 @@
       						(\\4)				# Previous capture from begin
       					(\\})										# Close Bracket
       					(?:\\s*\\n)?				# Match to end of line absent of content
-
+
     '''
     name: "meta.function.environment.math.latex"
     patterns: [
@@ -332,7 +332,7 @@
       						(array|tabular[xy*]?)
       					(\\})										# Close Bracket
       					(\\s*\\n)?				# Match to end of line absent of content
-
+
     '''
     captures:
       "1":
@@ -354,7 +354,7 @@
       						(\\4)				# Previous capture from begin
       					(\\})										# Close Bracket
       					(?:\\s*\\n)?				# Match to end of line absent of content
-
+
     '''
     name: "meta.function.environment.tabular.latex"
     patterns: [
@@ -434,27 +434,6 @@
     ]
   }
   {
-    begin: "(?:\\s*)((\\\\)begin)(\\{)(\\w+[*]?)(\\})"
-    captures:
-      "1":
-        name: "support.function.be.latex"
-      "2":
-        name: "punctuation.definition.function.latex"
-      "3":
-        name: "punctuation.definition.arguments.latex"
-      "4":
-        name: "variable.parameter.function.latex"
-      "5":
-        name: "punctuation.definition.arguments.latex"
-    end: "((\\\\)end)(\\{)(\\4)(\\})(?:\\s*\\n)?"
-    name: "meta.function.environment.general.latex"
-    patterns: [
-      {
-        include: "$base"
-      }
-    ]
-  }
-  {
     captures:
       "1":
         name: "punctuation.definition.function.latex"
@@ -546,7 +525,7 @@
         name: "punctuation.definition.textit.begin.latex"
     comment: '''
       We put the keyword in a capture and name this capture, so that disabling spell checking for “keyword” won't be inherited by the argument to \\textit{...}.
-
+
       Put specific matches for particular LaTeX keyword.functions before the last two more general functions
     '''
     contentName: "markup.italic.textit.latex"
@@ -625,7 +604,7 @@
       					(?:(\\[)[^\\]]*(\\]))?								# Optional
       					(?:(\\[)[^\\]]*(\\]))?								#   Arguments
       					(\\{)											# Opening Bracket
-
+
     '''
     captures:
       "1":


Remove debug

Remove debug

--- lib/assets/javascripts/views/subscription_toggle.js.coffee
+++ lib/assets/javascripts/views/subscription_toggle.js.coffee
@@ -49,7 +49,6 @@
         success: (posts, xhr) =>
           if posts.length
             @subscription_cids = _.map(posts, (post) => post.cid)
-            console.log @subscription_cids
             @subscribed = true
           else
             @subscribed = false


LogsView: add router support /Admin/Logs, /Admin/Logs/warns etc.

--- client/admin/lib/views/logs/index.coffee
+++ client/admin/lib/views/logs/index.coffee
@@ -1,8 +1,16 @@
-kd           = require 'kd'
-LogsListView = require './logslistview'
+kd                     = require 'kd'
+LogsListView           = require './logslistview'
+AdminSubTabHandleView  = require 'admin/views/customviews/adminsubtabhandleview'

 module.exports = class LogsView extends kd.View
+
+  SCOPES   =
+    all    : 'All Logs'
+    log    : 'Logs'
+    info   : 'Infos'
+    warn   : 'Warnings'
+    error  : 'Errors'

   constructor: (options = {}, data) ->

@@ -12,25 +20,34 @@

     @createTabView()

+    @on 'SubTabRequested', (scope, query) =>
+      index = (Object.keys SCOPES).indexOf(scope)
+      return @setRoute()  if index < 0
+
+      @tabView.showPaneByIndex index
+

   createTabView: ->

-    data     = @getData()
-    tabView  = new kd.TabView hideHandleCloseIcons: yes
+    @tabView = new kd.TabView
+      hideHandleCloseIcons : yes
+      tabHandleClass       : AdminSubTabHandleView

-    tabView.tabHandleContainer.addSubView new kd.ButtonView
+    @tabView.tabHandleContainer.addSubView new kd.ButtonView
       cssClass : 'solid compact green add-new'
       title    : 'Reload'
       callback : =>
-        @logsListView.listController.fetchLogs()
+        @tabView.activePane.getMainView().reload()

-    logsPane = tabView.addPane new kd.TabPaneView name: 'Logs'
+    Object.keys(SCOPES).forEach (scope) =>
+      @tabView.addPane new kd.TabPaneView
+        name  : SCOPES[scope]
+        route : "/Admin/Logs/#{scope}"
+        view  : new LogsListView { scope }
+        lazy  : yes

-    logsPane.addSubView @logsListView = new LogsListView
-      noItemFoundWidget : new kd.CustomHTMLView
-        partial         : 'No log found!'
-        cssClass        : 'no-item-view'
-    , data
+    @tabView.showPaneByIndex 0
+    @addSubView @tabView

-    tabView.showPaneByIndex 0
-    @addSubView tabView
+  setRoute: (route = '') ->
+    kd.singletons.router.handleRoute "/Admin/Logs#{route}"


somewhat working kites and auth worker.

somewhat working kites and auth worker.

--- client/app/MainApp/account-mixins.coffee
+++ client/app/MainApp/account-mixins.coffee
@@ -12,42 +12,32 @@

       channels = {}

-      scrub = (method, args, callback) ->
+      scrub =(method, args, callback) ->
         scrubber = new Scrubber localStore
         scrubber.scrub args, =>
           scrubbed = scrubber.toDnodeProtocol()
           scrubbed.method or= method
           callback scrubbed

-      request =(kiteName, method, args, onMethod='on')->
-        console.log {kiteName}
+      request =(kiteName, method, args)->
         scrub method, args, (scrubbed) ->
-          # declaredBefore = channels[getChannelName(kiteName)]
           fetchChannel kiteName, (channel)->
-            unless declaredBefore?
-              channel[onMethod](
-                "message",
-                messageHandler.bind channel, kiteName
-              )
             messageString = JSON.stringify(scrubbed)
-            onReady channel, ->
-              window.sss = channel
-              channel.publish messageString
+            channel.publish messageString

-      response = (kiteName, method, args) ->
+      response =(kiteName, method, args) ->
         scrub method, args, (scrubbed) ->
           fetchChannel kiteName, (channel)=>
-            channel.emit "client-message", JSON.stringify(scrubbed)
+            channel.publis "client-message", JSON.stringify(scrubbed)

       readyChannels = {}

-      onReady =
-        (channel, callback)->
-          {name} = channel
-          if readyChannels[name] then callback()
-          else
-            readyChannels[name] = channel
-            channel.once 'ready', callback
+      onReady = (channel, callback)->
+        {name} = channel
+        if readyChannels[name] then callback()
+        else
+          readyChannels[name] = channel
+          channel.once 'ready', callback

       ready =(resourceName)->
         @exchange = resourceName
@@ -66,13 +56,13 @@
           remoteStore.get callbackId
         callback.apply this, unscrubbed

-      getChannelName =do ->
+      getChannelName = do ->
         namesCache = {}
         (kiteName)->
           return namesCache[kiteName]  if namesCache[kiteName]
           delegate  = KD.whoami()
           nickname  = delegate?.profile.nickname ?
-                      if delegate.guestId then "Guest #{delegate.guestId}" ?
+                      if delegate.guestId then "guest#{delegate.guestId}" ?
                       'unknown'
           channelName = "#{Bongo.createId 128}.#{nickname}.kite-#{kiteName}"
           namesCache[kiteName] = channelName
@@ -80,16 +70,18 @@

       fetchChannel =(kiteName, callback)->
         channelName = getChannelName(kiteName)
-        unless channels[channelName]
-          channel = KD.remote.mq.subscribe channelName
-          channels[channelName] = channel
-          channel.on 'broker.subscribed', -> callback channel
-          channel.setAuthenticationInfo
-            serviceType : 'kite'
-            name        : "kite-#{kiteName}"
-            clientId    : KD.remote.getSessionToken()
+        return callback channels[channelName]  if channels[channelName]
+        channel = KD.remote.mq.subscribe channelName
+        channels[channelName] = channel
+        channel.on 'broker.subscribed', ->
+          onReady channel, -> callback channel
+        channel.setAuthenticationInfo
+          serviceType : 'kite'
+          name        : "kite-#{kiteName}"
+          clientId    : KD.remote.getSessionToken()
+        channel.on "message", messageHandler.bind channel, kiteName

-      (options, callback=->)->
+      tellKite =(options, callback=->)->
         scrubber = new Scrubber localStore
         args = [options, callback]
         {method} = options


Fix running in browser

Fix running in browser

--- src/svgtiler.coffee
+++ src/svgtiler.coffee
@@ -1454,7 +1454,7 @@
 module?.exports = svgtiler
 window?.svgtiler = svgtiler

-if require?.main == module and not window?
+if module? and require?.main == module and not window?
   paths = [
     ## Enable require('svgtiler') (as autoimported by `svgtiler` access)
     ## to load this module (needed if the module is installed globally).


feat: add source to level sessions schema

--- app/schemas/models/level_session.coffee
+++ app/schemas/models/level_session.coffee
@@ -159,27 +159,27 @@

   codeLanguage:
     type: 'string'
-
+
   codeConcepts:
     type: 'array'
     items:
-      type: 'string'
+      type: 'string'

   playtime:
     type: 'number'
     title: 'Playtime'
     description: 'The total playtime on this session in seconds'
-
+
   hintTime:
     type: 'number'
     title: 'Hint Time'
     description: 'The total time hints viewed in seconds'
-
+
   timesCodeRun:
     type: 'number'
     title: 'Times Code Run'
     description: 'The total times the code has been run'
-
+
   timesAutocompleteUsed:
     type: 'number'
     title: 'Times Autocomplete Used'
@@ -340,6 +340,13 @@
     title: 'Key Value DB'
     description: 'Simplified key-value database for game-dev levels'

+  source: c.object {},
+    id: c.objectId({})
+    name:
+      type: 'string'
+      title: 'Level session source'
+      description: 'Source of the level session, if present level session was added from an external source'
+
 LevelSessionSchema.properties.leagues.items.properties.stats.properties = _.pick LevelSessionSchema.properties, 'meanStrength', 'standardDeviation', 'totalScore', 'numberOfWinsAndTies', 'numberOfLosses', 'scoreHistory', 'matches'

 c.extendBasicProperties LevelSessionSchema, 'level.session'


Add explicit shift modifier

Add explicit shift modifier

Refs atom/settings-view#42

--- keymaps/command-palette.cson
+++ keymaps/command-palette.cson
@@ -1,5 +1,5 @@
 '.platform-darwin, .platform-darwin .command-palette .editor':
-  'cmd-P': 'command-palette:toggle'
+  'cmd-shift-p': 'command-palette:toggle'

 '.platform-win32, .platform-win32 .command-palette .editor':
-  'ctrl-P': 'command-palette:toggle'
+  'ctrl-shift-p': 'command-palette:toggle'


Improved the error message for missing series indexes.

--- .metalsmith/plugins/series.coffee
+++ .metalsmith/plugins/series.coffee
@@ -1,7 +1,9 @@
 #
 # # Series
 #
-#
+# The plugin responsible for chaining multiple guides marked as Series
+# together.
+#

@@ -20,7 +22,7 @@
     # of pages for that series.
     for filename, file of files
       if not file.series? then continue
-
+
       if not file['series-index']?
         console.warn "File #{filename} of series #{file.series}
           is missing the `series-index` metadata. Defaulting to 1"
@@ -36,7 +38,7 @@
       # into the index. If it doesn't exist, we just assign the index
       # manually.
       #
-      # This is done to ensure page order for the series, rather than pushing
+      # This is done to ensure page order for the series, rather than pushing
       # then all and then sorting them after.
       if series[index]? then series.splice index, 0, file
       else series[index] = file
@@ -46,8 +48,14 @@
     for seriesName, series of metadata.series
       prev  = null
       title = series[0]?.title
-      for page, index in series
-        if opts.prependSeriesTitle is true and index isnt 0
+      for page, index in series
+        if not page?
+          console.warn "Error: The Series plugin found a series with
+            a missing guide. Check the series '#{title}' for a missing
+            guide at the index of '#{index}'."
+          throw new Error "Missing Series Guide"
+
+        if opts.prependSeriesTitle is true and index isnt 0
           page.title = "#{title}: #{page.title}"

         page.series =


join js code with newlines instead of ';'

--- src/pry/compiler.coffee
+++ src/pry/compiler.coffee
@@ -21,7 +21,7 @@

   execute_coffee: (code) ->
     linesOfJs = coffee.compile(code, bare: true).split("\n")
-    @execute_js(linesOfJs.filter((l) -> l.length > 0 and l.trim()[0..2] isnt 'var').join(';'))
+    @execute_js(linesOfJs.filter((l) -> l.length > 0 and l.trim()[0..2] isnt 'var').join("\n"))

   execute_js: (code) ->
     @scope(code)


PUBDEV-1041 Format all array-valued model parameters properly

--- src/ext/components/model-output.coffee
+++ src/ext/components/model-output.coffee
@@ -13,7 +13,7 @@
         if actual_value then actual_value.name else null
       when 'VecSpecifier'
         if actual_value then actual_value.column_name else null
-      when 'string[]'
+      when 'string[]', 'byte[]', 'short[]', 'int[]', 'long[]', 'float[]', 'double[]'
         if actual_value then join actual_value, ', ' else null
       else
         actual_value


Add indentation and support for parameters.

--- src/compiler.coffee
+++ src/compiler.coffee
@@ -3,10 +3,14 @@

 root = exports ? window.opcodes = {}

+INDENT_SIZE = 4
+
 class RuntimeState
   constructor: ->
     @_cs = []
     @locals = []
+    @var_count = 0
+
     @blocks = []
     @block_start_idxs = []
     @new_block 0
@@ -41,17 +45,18 @@
         @blocks[d1].in.push idx
         @blocks[d2].in.push idx

-    @compile_block(@blocks[0])
+    @compile_block(@blocks[0], 1)

-  compile_block: (block) ->
+  compile_block: (block, indent) ->
+    indentation = (" " for i in [0...INDENT_SIZE * indent] by 1).join ''
     if block.in.length <= 1 && block.out.length == 2
-      "if (#{block.cond.condFn block.cond.expr}) {\n#{
-        @compile_block @blocks[block.out[1]]
-      }\n} else {\n#{
-        @compile_block @blocks[block.out[0]]
-      }\n}"
+      "#{indentation}if (#{block.cond.condFn block.cond.expr}) {\n#{
+        @compile_block @blocks[block.out[1]], indent + 1
+      }\n#{indentation}} else {\n#{
+        @compile_block @blocks[block.out[0]], indent + 1
+      }\n#{indentation}}"
     else
-      "#{block}"
+      indentation + block

 class BasicBlock
   constructor: ->
@@ -71,6 +76,16 @@

   toString: -> @val

+class Variable extends Expr
+
+  constructor: (id) ->
+    @strId = ""
+    while id >= 0
+      @strId = String.fromCharCode((id % 26) + 97) # 97 = 'a'
+      id -= 26
+
+  toString: -> @strId
+
 class BinaryOp extends Expr
   constructor: (@op_func, @left, @right) ->

@@ -78,7 +93,7 @@

 opcodes = {
   nop: { execute: -> }
-  aconst_null: { execute: (rs) -> rs.push null }
+  aconst_null: { execute: (rs) -> rs.push new Primitive null }
   iconst_m1: { execute: (rs) -> rs.push new Primitive -1 }
   iconst_0: { execute: (rs) -> rs.push new Primitive 0 }
   iconst_1: { execute: (rs) -> rs.push new Primitive 1 }
@@ -118,10 +133,24 @@
           if m.name is '<init>' then class_name
           else if m.name is '<clinit>' then '__clinit__'
           else m.name
+
         rs = new RuntimeState
+
+        vars = []
+        params_size = 0
+        for p in m.param_types
+          vars.push new Variable rs.var_count++
+          if p.toString() in ['D','J']
+            rs.put_cl2 params_size, _.last vars
+            params_size += 2
+          else
+            rs.put_cl params_size, _.last vars
+            params_size++
+
         m.code.each_opcode (idx, oc) ->
           opcodes[oc.name]?.execute rs, oc, idx
-        "#{name}: function() {\n#{rs.compile()}\n},"
+        "#{name}: function(#{vars.join ", "}) {\n#{rs.compile()}\n},"
+
   "var #{class_name} = {\n#{methods.join "\n"}\n};\n"

 # TODO: move to a separate file


Added a method for defining explicit ordering.

--- src/compiler.coffee
+++ src/compiler.coffee
@@ -140,6 +140,9 @@

       steps = []

+      if @configuration.watch
+        steps.push filters.plumber()
+
       if @configuration.cache.enabled
         cacheOptions = @configuration.plugins.cache or {}
         steps.push filters.cache output, cacheOptions
@@ -148,14 +151,13 @@
         steps.push filters.changed output,
           extension: extension

-      if @configuration.watch
-        steps.push filters.plumber()
-
       steps = steps.concat @filteredPipeline type, output
+
+      ordering = @configuration.ordering?[type]

       if shouldConcat
         steps.push filters.remember output
-        # steps.push filters.order type, inputs
+        steps.push filters.order ordering if ordering?

         transformConcat = @transform {}


Workspace: Removed headerIcon and headerStyling from Panel class.

--- client/landing/Workspace/panel.coffee
+++ client/landing/Workspace/panel.coffee
@@ -31,18 +31,9 @@
       cssClass     : "title"
       partial      : """ #{title} """

-    @headerIcon    = new KDCustomHTMLView
-      tagName      : "span"
-      cssClass     : "icon"
-
-    @headerTitle.addSubView @headerIcon, null, yes
-
     @header.addSubView @headerTitle
     @header.addSubView @headerButtonsContainer = new KDCustomHTMLView
       cssClass     : "tw-header-buttons"
-
-    {headerStyling} = @getOptions()
-    @applyHeaderStyling headerStyling if headerStyling

   createHeaderButtons: ->
     # TODO: fatihacet - DRY
@@ -133,20 +124,6 @@
           cssClass : "modal-cancel"
           callback : -> modal.destroy()

-  applyHeaderStyling: (options) ->
-    if options.custom
-      return @header.getElement().setAttribute "style", options.custom
-
-    {bgColor, bgGradient, bgImage, textColor, textShadowColor, borderColor} = options
-
-    @header.setCss      "color"             , textColor                     if textColor
-    @header.setCss      "borderBottomColor" , "#{borderColor}"              if borderColor
-    @header.setCss      "background"        , "#{bgColor}"                  if bgColor
-    @headerIcon.setCss  "backgroundImage"   , "url(#{bgImage})"             if bgImage
-    @header.setCss      "textShadowColor"   , "0 1px 0 #{textShadowColor}"  if textShadowColor
-
-    KD.utils.applyGradient @header, bgGradient.first, bgGradient.last       if bgGradient
-
   viewAppended: ->
     super
     @getDelegate().emit "NewPanelAdded", this


add guard in deeplink parser

add guard in deeplink parser

--- src/javascripts/deeplink_parser.coffee
+++ src/javascripts/deeplink_parser.coffee
@@ -24,6 +24,7 @@
   # private

   _parseLocation: (location) ->
+    return null unless location
     hash = location[1..-1].split('&')
     timeHash = _(hash).find (h) -> _(h).startsWith('t')


refs #5 bug fix for modal datepicker load

--- app/assets/javascripts/dashboard.coffee
+++ app/assets/javascripts/dashboard.coffee
@@ -9,7 +9,7 @@

   $("#stream_table").stream_table({view: view}, repos)

-$ ->
+$(document).on "page:change", ->
   $('#datepicker1').datepicker format: 'dd/mm/yyyy'


Add ToDo

Add ToDo

--- app/assets/javascripts/playlists.coffee
+++ app/assets/javascripts/playlists.coffee
@@ -124,8 +124,9 @@
     )()
   )()

-  # TODO: Seeking. Remove playlist from youtube link if present. Previous
-  # will rewind the current track after X seconds
+  # TODO: Song ends, detect event and  go to next song. Seeking. Remove playlist
+  # from youtube link if present. Previous will rewind the current track after
+  # X seconds

   # User presses play (Will 'start' or 'resume')
   #   - If there is a paused song in one of the players should play


Add a knob for setting the global default for the 'hidden' class.

--- app/assets/javascripts/pwn-fx.js.coffee
+++ app/assets/javascripts/pwn-fx.js.coffee
@@ -15,12 +15,21 @@
   constructor: ->
     @effects = []
     @effectsByName = {}
+    @hiddenClassName = null
+
+  # @attribute {String} the default name of the DOM class used to hide
+  #     elements; by default, this is set to the data-pwnfx-hidden-class
+  #     attribute of the <body> element
+  hiddenClassName: null

   # Wires JS to elements with data-pwnfx attributes.
   #
   # @param {Element} root the element whose content is wired; use document at
   #                       load time
   wire: (root) ->
+    @hiddenClassName ||=
+        document.body.getAttribute('data-pwnfx-hidden-class') || 'hidden'
+
     for effect in @effects
       attrName = "data-pwnfx-#{effect[0]}"
       effectClass = effect[1]
@@ -55,7 +64,8 @@
   #     returned if no such element exists or if scope is null
   resolveScope: (scopeId, element) ->
     element = null if scopeId is null
-    while element != null && element.getAttribute('data-pwnfx-scope') != scopeId
+    while element isnt null &&
+        element.getAttribute('data-pwnfx-scope') isnt scopeId
       element = element.parentElement
     element || document

@@ -67,7 +77,7 @@
   #     selector; the scope container can belong to the returned array
   queryScope: (scope, selector) ->
     scopeMatches = false
-    if scope != document
+    if scope isnt document
       # TODO: machesSelector is in a W3C spec, but only implemented using
       #       prefixes; the code below should be simplified once browsers
       #       implement it without vendor prefixes
@@ -106,7 +116,7 @@
   #     element is not wrapped in a <form>
   parentForm: (element) ->
     while element
-      return element if element.nodeName == 'FORM'
+      return element if element.nodeName is 'FORM'
       element = element.parentNode
     null

@@ -130,7 +140,7 @@

   # Called when an XHR request issued by PwnFx.xhr works out.
   _xhr_onload: ->
-    if (@status < 200 || @status >= 300) && (@status != 304)
+    if (@status < 200 || @status >= 300) && (@status isnt 304)
       throw new Error(
           "XHR result ignored due to HTTP #{@status}: #{@statusText}")
     @pwnfxOnData @responseText
@@ -223,8 +233,8 @@
 #   data-pwnfx-delayed-method: the HTTP method of AJAX request (default: POST)
 #   data-pwnfx-delayed-ms: the delay between the page load and the issuing of
 #                          the AJAX request (default: 1000ms)
-#   data-pwnfx-delayed-target: a space-separated list of identifiers, one of which
-#       should match the value of data-pwnfx-delayed; set on the element
+#   data-pwnfx-delayed-target: a space-separated list of identifiers, one of
+#       which should match the value of data-pwnfx-delayed; set on the element
 #       populated with the AJAX response
 class PwnFxDelayed
   constructor: (element, identifier, scopeId) ->
@@ -257,8 +267,8 @@
 #   data-pwnfx-refresh-method: the HTTP method of AJAX request (default: POST)
 #   data-pwnfx-refresh-ms: delay between a change on the source element and
 #                          AJAX refresh requests (default: 200ms)
-#   data-pwnfx-refresh-target: a space-separated list of identifiers, one of which
-#       should match the value of data-pwnfx-refresh; set on the element
+#   data-pwnfx-refresh-target: a space-separated list of identifiers, one of
+#       which should match the value of data-pwnfx-refresh; set on the element
 #       populated with the AJAX response
 class PwnFxRefresh
   constructor: (element, identifier, scopeId) ->
@@ -282,10 +292,10 @@

     onChange = ->
       value = element.value
-      return true if value == refreshOldValue
+      return true if value is refreshOldValue
       refreshOldValue = value

-      window.clearTimeout changeTimeout if changeTimeout != null
+      window.clearTimeout changeTimeout if changeTimeout isnt null
       changeTimeout = window.setTimeout ajaxRefresh, refreshDelay
       true

@@ -303,14 +313,16 @@
 #       belong to the same confirmation group; their values have to match to
 #       trigger the "win" condition
 #   data-pwnfx-confirm-class: the CSS class that is added to hidden elements;
-#       (default: hidden)
+#       by default, this is {PwnFx.hiddenClassName}, which gets its value from
+#       the data-pwnfx-hidden-class attribute on <body>
 #   data-pwnfx-confirm-win: CSS selector identifying the elements to be shown
 #       when the "win" condition is triggered, and hidden otherwise
 #   data-pwnfx-confirm-fail: CSS selector identifying the elements to be hidden
 #       when the "win" condition is triggered, and shown otherwise
 class PwnFxConfirm
   constructor: (element, identifier, scopeId) ->
-    hiddenClass = element.getAttribute('data-pwnfx-confirm-class') || 'hidden'
+    hiddenClass = element.getAttribute('data-pwnfx-confirm-class') ||
+                  PwnFx.hiddenClassName
     sourceSelector = "[data-pwnfx-confirm-done=\"#{identifier}\"]"
     winSelector = "[data-pwnfx-confirm-win=\"#{identifier}\"]"
     failSelector = "[data-pwnfx-confirm-fail=\"#{identifier}\"]"
@@ -320,9 +332,9 @@
       value = null
       matching = true
       for sourceElement, index in PwnFx.queryScope(scope, sourceSelector)
-        if index == 0
+        if index is 0
           value = sourceElement.value
-        else if sourceElement.value != value
+        else if sourceElement.value isnt value
           matching = false
           break

@@ -350,7 +362,8 @@
 #       element is clicked, "checked" means events are triggered when the
 #       element is checked; (default: click)
 #   data-pwnfx-hide-class: the CSS class that is added to hidden elements;
-#       (default: hidden)
+#       by default, this is {PwnFx.hiddenClassName}, which gets its value from
+#       the data-pwnfx-hidden-class attribute on <body>
 #   data-pwnfx-hide-positive: set to the same value as data-pwnfx-hide on
 #       elements that will be hidden when a positive event (click / check) is
 #       triggered, and shown when a negative event (uncheck) is triggered
@@ -360,11 +373,12 @@
 class PwnFxHide
   constructor: (element, identifier, scopeId) ->
     trigger = element.getAttribute('data-pwnfx-hide-trigger') || 'click'
-    hiddenClass = element.getAttribute('data-pwnfx-hide-class') || 'hidden'
+    hiddenClass = element.getAttribute('data-pwnfx-hide-class') ||
+                  PwnFx.hiddenClassName
     positiveSelector = "[data-pwnfx-hide-positive=\"#{identifier}\"]"
     negativeSelector = "[data-pwnfx-hide-negative=\"#{identifier}\"]"
     onChange = (event) ->
-      positive = (trigger == 'click') || element.checked
+      positive = (trigger is 'click') || element.checked
       hideSelector = if positive then positiveSelector else negativeSelector
       showSelector = if positive then negativeSelector else positiveSelector

@@ -373,15 +387,15 @@
         targetElement.classList.add hiddenClass
       for targetElement in PwnFx.queryScope(scope, showSelector)
         targetElement.classList.remove hiddenClass
-      if trigger == 'click'
+      if trigger is 'click'
         event.preventDefault()
         false
       else
         true

-    if trigger == 'click'
+    if trigger is 'click'
       element.addEventListener 'click', onChange, false
-    else if trigger == 'checked'
+    else if trigger is 'checked'
       element.addEventListener 'change', onChange, false
       onChange()
     else
@@ -397,14 +411,16 @@
 #   data-pwnfx-showif-replace: the name of a tag that will be used to replace
 #       the hidden element (default: don't replace the hidden element)
 #   data-pwnfx-showif-class: the CSS class that is added to hidden elements;
-#       (default: hidden)
+#       by default, this is {PwnFx.hiddenClassName}, which gets its value from
+#       the data-pwnfx-hidden-class attribute on <body>
 #   data-pwnfx-showif-is: the value that the <input> has to match for this
 #       element to be shown
 #   data-pwnfx-showif-source: set to the identifier in data-pwnfx-showif
 #       on the <input> whose value determines if this element is shown or not
 class PwnFxShowIf
   constructor: (element, identifier, scopeId) ->
-    hiddenClass = element.getAttribute('data-pwnfx-showif-class') || 'hidden'
+    hiddenClass = element.getAttribute('data-pwnfx-showif-class') ||
+                  PwnFx.hiddenClassName
     showValue = element.getAttribute 'data-pwnfx-showif-is'
     sourceSelector = "[data-pwnfx-showif-source=\"#{identifier}\"]"

@@ -418,8 +434,8 @@
     isHidden = false
     onChange = (event) ->
       value = event.target.value
-      willHide = value != showValue
-      return if isHidden == willHide
+      willHide = value isnt showValue
+      return if isHidden is willHide
       isHidden = willHide

       if replacement
@@ -448,9 +464,9 @@
 #
 # Attributes:
 #   data-pwnfx-remove: an identifier connecting the elements to be removed
-#   data-pwnfx-remove-target: a space-separated list of identifiers, one of which
-#       should match the value of data-pwnfx-remove; set on elements that will
-#       be removed when the element is clicked
+#   data-pwnfx-remove-target: a space-separated list of identifiers, one of
+#       which should match the value of data-pwnfx-remove; set on elements that
+#       will be removed when the element is clicked
 class PwnFxRemove
   constructor: (element, identifier, scopeId) ->
     targetSelector = "[data-pwnfx-remove-target~=\"#{identifier}\"]"
@@ -470,11 +486,11 @@
 window.PwnFx = PwnFx

 # Wire up the entire DOM after the document is loaded.
-if document.readyState == 'loading'
+if document.readyState is 'loading'
   loaded = false
   document.addEventListener 'readystatechange', ->
     return if loaded
-    if document.readyState != 'loading'
+    if document.readyState isnt 'loading'
       PwnFx.wire(document)
       loaded = true
 else


close #1326 fix markup bug with space or CRLF

--- app/assets/javascripts/recipe.js.coffee
+++ app/assets/javascripts/recipe.js.coffee
@@ -5,13 +5,15 @@
     ul = container.find "ul"
     ul.empty()
     attachments.attr "target", "_blank"
-    if attachments.length is 0
+    filtered_attachments = attachments.filter (i) ->
+      return this.textContent.replace(/[\n\s ]/g, "").length != 0
+    if filtered_attachments.length is 0
       container.removeClass "has-data"
       $("#making-list .inner").removeClass "has-data"
       return
     container.addClass "has-data"
     $("#making-list .inner").addClass "has-data"
-    for attachment in attachments
+    for attachment in filtered_attachments
       href = attachment.getAttribute "href"
       description = attachment.getAttribute "data-description"
       name = attachment.textContent


Fix weird whitespace in room scaling

Fix weird whitespace in room scaling

--- apps/artwork/client/view-in-room.coffee
+++ apps/artwork/client/view-in-room.coffee
@@ -123,7 +123,7 @@
     viewportRatio = @$el.width() / @$el.height()
     direction = if viewportRatio > roomRatio then 'width' else 'height'
     factor = @$el[direction]() / @$room[direction]()
-    Math.round(factor * 100) / 100
+    Math.ceil(factor * 100) / 100

   artworkScalingFactor: ->
     @__artworkFactor__ ?= if @artwork.hasDimension('diameter') and not @artwork.hasDimension('width')


Version update to jquery.waypoints

Version update to jquery.waypoints

…which doesn't actually change the code, but whatever.

--- app/assets/javascripts/waypoints.coffee
+++ app/assets/javascripts/waypoints.coffee
@@ -1,5 +1,5 @@
 ###
-jQuery Waypoints - v2.0.2
+jQuery Waypoints - v2.0.3
 Copyright (c) 2011-2013 Caleb Troughton
 Dual licensed under the MIT license and GPL license.
 https://github.com/imakewebthings/jquery-waypoints/blob/master/licenses.txt


Fixes statistics reporting

Fixes statistics reporting

Handle NaN calculations. Also stats are now per player by default
to cut down on channel spam.

--- src/scripts/one_four_twentyfour.coffee
+++ src/scripts/one_four_twentyfour.coffee
@@ -12,7 +12,8 @@
 # Commands:
 #   dice start - starts a game of one, four, twenty four
 #   dice take <dice letters> - takes dice at given levels
-#   dice stats - displays the stats for the game
+#   dice stats - displays your statistics
+#   dice stats all - displays all players' statistics
 #
 # Authors:
 #   zbowling
@@ -300,12 +301,17 @@
     @save()

   playerstats: (player) ->
-    "\n#{player.name}: \n
-\tlast score: #{@playerdata[player.id].lastScore}\n
-\taverage score: #{@playerdata[player.id].totalScore/@playerdata[player.id].totalGamesFinished}\n
-\ttotal games finished: #{@playerdata[player.id].totalGamesFinished}\n
-\ttotal games started: #{@playerdata[player.id].totalGamesStarted}\n"
-
+    data = @playerdata[player.id]
+    return "You have not played a game yet." unless data?
+    average = if data.totalGamesFinished > 0
+      data.totalScore / data.totalGamesFinished
+    else
+      0
+    "#{player.name}: \n
+\tlast score: #{data.lastScore}\n
+\taverage score: #{average}\n
+\ttotal games finished: #{data.totalGamesFinished}\n
+\ttotal games started: #{data.totalGamesStarted}\n"

   save: ->
     @robot.brain.data.buddhagames = @games
@@ -328,10 +334,13 @@

   robot.hear /buddha start|dice start|bdstart/i, (msg) ->
     buddha.startGame msg, msg.message.user
-
-  robot.hear /buddha stats|dice stats|bdstat/i, (msg) ->
+
+  robot.hear /buddha stats|dice stats/i, (msg) ->
+    msg.reply buddha.playerstats(msg.message.user)
+
+  robot.hear /buddha stats all|dice stats all|bdstat/i, (msg) ->
     buddha.stats msg
-
+
   robot.hear /buddha reset|dice reset/i, (msg) ->
     buddha.reset msg


React now only passes single object (event) on event

Catching mousedown prevents click anyway.

--- resources/assets/coffee/react/beatmaps/search-panel.coffee
+++ resources/assets/coffee/react/beatmaps/search-panel.coffee
@@ -51,8 +51,7 @@
   componentWillUnmount: ->
     $('#searchbox').off 'keyup'

-  show_more: (i, e) ->
-    e.preventDefault
+  show_more: ->
     $('#search').addClass 'expanded'

   render: ->


jupyter backend; change params and increase kernel spawn timeout to 2 minutes

--- src/smc-project/jupyter/jupyter.coffee
+++ src/smc-project/jupyter/jupyter.coffee
@@ -181,7 +181,7 @@
                 start_delay : 500
                 max_delay   : 5000
                 factor      : 1.4
-                max_time    : 45000
+                max_time    : 2*60000  # long in case of starting many at once -- we don't want them to all fail and start again and fail ad infinitum!
                 f : (cb) =>
                     @kernel_info(cb : =>)
                     cb(@_state == 'starting')
@@ -230,8 +230,8 @@
             clearInterval(@_usage_interval)
         locals =
             last_usage : {cpu:0, memory:0}
-            thresh     : 0.25  # report any change of at least thresh percent (we always report cpu dropping to 0)
-            interval   : 10000  # frequently (~8s) should be OK, since it just reads /proc filesystem
+            thresh     : 0.2 # report any change of at least thresh percent (we always report cpu dropping to 0)
+            interval   : 5000  # frequently should be OK, since it just reads /proc filesystem
         @emit('usage', locals.last_usage)
         dbg = @dbg("usage_monitor")
         update_usage = =>


Fix broken editor link for new media

Fix broken editor link for new media

--- scripts/views/workspace/content/search-results-item.coffee
+++ scripts/views/workspace/content/search-results-item.coffee
@@ -21,7 +21,7 @@

     initialize: () ->
       @template = (data) =>
-        data.id = @model.id
+        data.id = @model.id or @model.cid
         return searchResultsItemTemplate(data)

       @listenTo @model, 'change', => @render()


update product list for `enone` and `wnone` closes #101

--- assets/coffeescript/product_list.coffee
+++ assets/coffeescript/product_list.coffee
@@ -5,6 +5,7 @@
       title_store: "none"
       description_store: "No world camera"
       title_cart: "World camera: no world camera"
+      description_cart: "No world camera"
       weight: 0
     w30:
       id: 'w30'
@@ -45,6 +46,7 @@
       title_store: "none"
       title_cart: "Eye camera: no eye camera"
       description_store: "No eye camera"
+      description_cart: "No eye camera"
       weight: 26
     e30:
       id: 'e30'


RewindBuffer: Implement a stub `recordListen` function

We have a test that creates a Rewinder object using just a RewindBuffer.
It later throws an exception if tests are still running because
the RewindBuffer doesn't have `recordListen`.

--- src/streammachine/rewind_buffer.coffee
+++ src/streammachine/rewind_buffer.coffee
@@ -167,6 +167,11 @@
         unless opts.pumpOnly
             # add it to our list of listeners
             @_raddListener rewind
+
+    #----------
+
+    recordListen: (opts) ->
+        # stub function. must be defined for real in the implementing class

     #----------


Revert "add plain text link to email address confirmation emails"

This reverts commit 48cd7e604dcc5f3b7ae8eb699f14b44bc073f107.

--- services/web/app/coffee/Features/Email/EmailBuilder.coffee
+++ services/web/app/coffee/Features/Email/EmailBuilder.coffee
@@ -97,8 +97,6 @@
 	title: () -> "Confirm Email"
 	message: () -> "Please confirm your email on #{settings.appName}."
 	ctaText: () -> "Confirm Email"
-	secondaryMessage: (opts) ->
-		"If the button does not appear, open this link in your browser: #{opts.confirmEmailUrl}"
 	ctaURL: (opts) -> opts.confirmEmailUrl
 })


Remove unused text attribute

Remove unused text attribute

--- src/trix/config/text_attributes.coffee
+++ src/trix/config/text_attributes.coffee
@@ -18,9 +18,6 @@
       matchingSelector = "a:not(#{attachmentSelector})"
       if link = Trix.findClosestElementFromNode(element, {matchingSelector})
         link.getAttribute("href")
-  underline:
-    style: { "textDecoration": "underline" }
-    inheritable: true
   strike:
     tagName: "del"
     inheritable: true


skip explicit root

skip explicit root

--- lib/SteroidsConfigure.coffee
+++ lib/SteroidsConfigure.coffee
@@ -26,10 +26,14 @@

 module.exports =
   fromXml: (xmlString, done) ->
-    xml2js.parseString xmlString, (err, rawXmlConfig) ->
-      return done err if err?
+    xml2js.parseString(
+      xmlString
+      { explicitRoot: false }
+      (err, rawXmlConfig) ->
+        return done err if err?

-      friendlyConfig = makeFriendlier rawXmlConfig
+        friendlyConfig = makeFriendlier rawXmlConfig

-      result = JSON.stringify(friendlyConfig, null, 2)
-      done null, result
+        result = JSON.stringify(friendlyConfig, null, 2)
+        done null, result
+    )


Removed redundant set of impact

Removed redundant set of impact

--- core/app/backbone/models/evidence.coffee
+++ core/app/backbone/models/evidence.coffee
@@ -5,7 +5,7 @@
   # TODO: eventually, fetching this model should populate
   #       the collection, not the other way around
   initialize: ->
-    @fetchImpact()
+    @opinionaters().fetch()

   opinionaters: ->
     return @_opinionaters if @_opinionaters?
@@ -16,7 +16,3 @@
       @set impact: @_opinionaters.impact

     @_opinionaters
-
-  fetchImpact: ->
-    @opinionaters().fetch
-      success: => @set 'impact', @opinionaters().impact


added easier way to retrieve textview (might be useful for emptyviews)

--- core/app/backbone/views/text_view.coffee
+++ core/app/backbone/views/text_view.coffee
@@ -1,2 +1,7 @@
 class window.TextView extends Backbone.Marionette.ItemView
-  template: 'generic/text'+  template: 'generic/text'
+  tag: 'span'
+
+window.getTextView = (text) ->
+  TextView.extend
+    initialize: -> @model = new Backbone.Model({text: text})


Make route dynamic

Make route dynamic

--- src/routes/index.coffee
+++ src/routes/index.coffee
@@ -32,7 +32,7 @@

   for entityKey,entity of req.body
     for fieldKey, field of req.body
-      form[wageTheftReportInfo.mapping[entityKey][fieldKey]] = v
+      form[wageTheftReportInfo.mapping[entityKey][fieldKey]] = field

   request.post {url: wageTheftReportInfo.post_url, form: form}, (err, httpResponse, body) ->
     return res.status(400).json({err, httpResponse, body}).end() if err?


added process.nextTick to forIdentity wrapping loops done

--- lib/auth/Controllable.coffee
+++ lib/auth/Controllable.coffee
@@ -77,7 +77,7 @@
         ## @param identity -
         ##
         forIdentity: (identity, fn)->
-
+
             @logger.debug 'forIdentity', identity.id

             @_allowAccess identity, (err)=>
@@ -134,10 +134,10 @@

                     ##
-                    done: (err)=>
+                    done: (err)->
                         return fn(err) if err
-
-                        fn null, wrapper
+                        process.nextTick ()-> ## this is to prevent loooong stack traces
+                            fn null, wrapper


Subscribe to TextEditor::onWillInsertText instead of string event

--- lib/autocomplete-view.coffee
+++ lib/autocomplete-view.coffee
@@ -34,7 +34,7 @@
     @subscriptions = new CompositeDisposable
     @subscriptions.add @editor.onDidDestroy => @subscriptions.dispose()

-    @filterEditorView.getModel().on 'will-insert-text', ({cancel, text}) =>
+    @filterEditorView.getModel().onWillInsertText ({cancel, text}) =>
       unless text.match(@wordRegex)
         @confirmSelection()
         @editor.insertText(text)


update pids

update pids

--- assets/coffeescript/product_list.coffee
+++ assets/coffeescript/product_list.coffee
@@ -149,10 +149,10 @@
     description_cart: "For use within an academic (non-commercial) context."

 get_vr_ar_product_ids = ->
-  return ['hololens_w120_e200b','htcviveb','oculusdk2b','epsonbt300b_e120b']
+  return ['hololens_w120_e200b','htcvive_e120b','oculusdk2_e120b','epsonbt300_e200b']

 get_additional_product_ids = ->
-  return ['e200upgrade','usbcupgrade','pm_motoz2','support6','support12']
+  return ['camera_e200','hub_usbc','pm_motoz2','support_6','support_12']

 get_product_database = ->
@@ -468,14 +468,14 @@
         discount: edu_discount
       img: [pupil_img_path,'pupil_w3d_e200b.jpg'].join('/')

-    oculusdk2m:
+    oculusdk2_e120:
       cost: 800
       title_product: 'Oculus Rift DK2 Add-on'
       title_store: 'Oculus Rift DK2 Monocular Add-on Cup'
       description_store: "Add eye tracking to your Oculus Rift DK2 with our 120hz monocular eye tracking add-on. Includes one custom lens cup with: IR mirror, IR LEDs, 1 x 120 Hz eye camera, and USB cables."
       description_cart: "Monocular Oculus Rift DK2 add-on cup with 1 x 120hz eye camera."
       img: [vr_ar_img_path,'oculusdk2m.jpg'].join('/')
-    oculusdk2b:
+    oculusdk2_e120b:
       cost: 1400
       title_product: 'Oculus Rift DK2 Add-on'
       title_store: 'Oculus Rift DK2 Binocular Add-on Cup'
@@ -496,14 +496,14 @@
       description_store: "Binocular camera mounts for Epson Moverio BT-300 with USBC clip. Use with the 120hz eye camera upgrades or existing 120hz eye cameras from a Pupil headset."
       description_cart: "Binocular camera mounts for Epson Moverio BT-200 with USBC clip. Use with the 120hz eye camera upgrades or existing 120hz eye cameras from a Pupil headset."
       img: [vr_ar_img_path,'addon_epson_bt300b.jpg'].join('/')
-    epsonbt300b_e120b:
+    epsonbt300_e200b:
       cost: 1400
       title_product: 'Epson Moverio BT-300 Add-on'
       title_store: 'Epson Moverio BT-300 Binocular Mount Add-on'
       description_store: "Binocular camera mounts for Epson Moverio BT-300. Ships with USBC clip and 2 x 200hz eye cameras."
       description_cart: "Binocular camera mounts for Epson Moverio BT-300. Ships with USBC clip and 2 x 200hz eye cameras."
       img: [vr_ar_img_path,'addon_epson_bt300b.jpg'].join('/')
-    htcviveb:
+    htcvive_e120b:
       cost: 1400
       title_product: 'HTC Vive Binocular Add-on'
       title_store: 'HTC Vive Binocular Add-on'
@@ -538,21 +538,21 @@
       description_store: "Add binocular eye tracking to your Microsoft HoloLens AR headset. Includes binocular 200hz eye tracking cameras, clip on attachment mounts, and USB connector clip."
       description_cart: "200hz binocular eye tracking add-on for your Microsoft HoloLens AR headset."
       img: [vr_ar_img_path,'addon_hololens_e200.jpg'].join('/')
-    usbcupgrade:
+    hub_usbc:
       cost: 210
       title_product: 'USB-C Clip Update'
       title_store: 'USB-C Clip Update'
       description_store: 'Update older micro USB Pupil headsets with the new USB-C clip. Comes with USB-C to USB-A cable.'
       description_cart: 'USB-C clip upgrade older micro USB Pupil headsets. Comes with USB-C to USB-A cable.'
       img: [additional_product_path,'usbcupgrade.jpg'].join('/')
-    e120upgrade:
+    camera_e120:
       cost: 450
       title_product: '120hz Eye Camera'
       title_store: "120hz Eye Camera"
       description_store: "Update any existing Pupil headset with our custom made 120hz camera. This is a drop-in replacement. No tools needed. For more info, check out the camera specs in the configurator above."
       description_cart: "Camera upgrade: 120hz eye camera upgrade"
       img: [additional_product_path,'e120upgrade.jpg'].join('/')
-    e200upgrade:
+    camera_e200:
       cost: 650
       title_product: '200hz Eye Camera'
       title_store: "200hz Eye Camera"
@@ -580,14 +580,14 @@
       description_store: "Connect your Pupil headset to the Moto Z2 play. Record up to 4 hours of video locally on the phone, or stream video and sensor data over Wifi. The bundle comes with: Moto Z2 Play (black), hot-swappable Moto power pack, 64gb SD card, USBC-USBC cable, and is pre-loaded with the Pupil Mobile app. (Note: Pupil headsets with 3d world camera are not compatible with Pupil Mobile)."
       description_cart: "Moto Z2 Play (black), hot-swappable Moto power pack, 64gb SD card, USBC-USBC cable, and is pre-loaded with the Pupil Mobile app. Connects to Pupil headset via USBC cable (included)."
       img: [additional_product_path,'pm_nexus5x.jpg'].join('/')
-    support6:
+    support_6:
       cost: 1000
       title_product: 'Support Contract'
       title_store: '6 Month Support Contract'
       description_store: '6 month support contract. 3 one hour video support sessions and email support.'
       description_cart: '6 month support contract. 3 one hour video support sessions and email support.'
       img: [additional_product_path,'support6.svg'].join('/')
-    support12:
+    support_12:
       cost: 2000
       title_product: 'Support Contract'
       title_store: '12 Month Support Contract'
@@ -619,6 +619,16 @@
     product_usb_c: 'usbcupgrade'
     product_htc_vive_bi: 'htcviveb'
     w3dupgrade: 'w3dupgrade'
+    htcviveb: 'htcvive_e120b'
+    oculusdk2b: 'oculusdks_e120b'
+    oculusdk2m: 'oculusdk2_e120'
+    epsonbt300b_e120b: 'epsonbt300_wnone_e200b'
+    support6: 'support_6'
+    support12: 'support_12'
+    e120upgrade: 'camera_e120'
+    e200upgrade: 'camera_e200'
+    usbcupgrade: 'hub_usbc'
+
   res = []
   i = 0
   while i < ids.length


fix trix

fix trix

--- app/assets/javascripts/media.coffee
+++ app/assets/javascripts/media.coffee
@@ -310,41 +310,40 @@
   trixElement = document.querySelector('#medium-content-trix')
   if trixElement?
     trixElement.addEventListener 'trix-initialize', ->
-      content = this.dataset.content
-      editor = trixElement.editor
-      editor.setSelectedRange([0,65535])
-      editor.deleteInDirection("forward")
-      editor.insertHTML(content)
-      document.activeElement.blur()
-      trixElement.addEventListener 'trix-change', ->
-        $('#medium-basics-warning').show()
-        $('#medium-content-preview').html($('#medium-content-trix').html())
-        mediumContentDetails = document.getElementById('medium-content-preview')
-        renderMathInElement mediumContentDetails,
-          delimiters: [
-            {
-              left: '$$'
-              right: '$$'
-              display: true
-            }
-            {
-              left: '$'
-              right: '$'
-              display: false
-            }
-            {
-              left: '\\('
-              right: '\\)'
-              display: false
-            }
-            {
-              left: '\\['
-              right: '\\]'
-              display: true
-            }
-          ]
-          throwOnError: false
-        return
+    content = trixElement.dataset.content
+    editor = trixElement.editor
+    editor.setSelectedRange([0,65535])
+    editor.deleteInDirection("forward")
+    editor.insertHTML(content)
+    document.activeElement.blur()
+    trixElement.addEventListener 'trix-change', ->
+      $('#medium-basics-warning').show()
+      $('#medium-content-preview').html($('#medium-content-trix').html())
+      mediumContentDetails = document.getElementById('medium-content-preview')
+      renderMathInElement mediumContentDetails,
+        delimiters: [
+          {
+            left: '$$'
+            right: '$$'
+            display: true
+          }
+          {
+            left: '$'
+            right: '$'
+            display: false
+          }
+          {
+            left: '\\('
+            right: '\\)'
+            display: false
+          }
+          {
+            left: '\\['
+            right: '\\]'
+            display: true
+          }
+        ]
+        throwOnError: false
       return

   $(document).on 'click', '.triggerDownload', ->


Fix display of multiple messages.

Fix display of multiple messages.

--- app/assets/javascripts/nodes.coffee
+++ app/assets/javascripts/nodes.coffee
@@ -96,7 +96,14 @@
   delay = message.split(' ').length / 0.00333333333 + 0.5 # based on 200 WpM

   setTimeout (()-> $message.addClass('in')), 0
-  setTimeout (()-> $message.removeClass('in')), delay
+  setTimeout (()->
+    $message.removeClass('in')
+
+    if ($.support.transition)
+      $message.one $.support.transition.end, -> $message.remove()
+    else
+      $message.remove()
+  ), delay

   $message


Category select now shows the selected group

2. Fix: artificially ""invisible"" selects were being focused when
tabbing

--- app/assets/javascripts/pools.coffee
+++ app/assets/javascripts/pools.coffee
@@ -27,8 +27,10 @@
   $('.chosen-select').each ->
     $(this).chosen
       allow_single_deselect: true
+      include_group_label_in_selected: true
       no_results_text: 'No existe'
       width: '100%'
     $(this).attr('style','display:block; position:absolute; clip:rect(0,0,0,0)')
+    $(this).attr('tabindex','-1')

   $('[data-toggle="tooltip"]').tooltip()


Tidy up the search function

Tidy up the search function

--- app/assets/javascripts/posts.coffee
+++ app/assets/javascripts/posts.coffee
@@ -98,9 +98,10 @@

 search = (text) ->
   console.log("search for: " + text)
-  console.log(index.search text, fields:
+  results = index.search text, fields:
     title: boost: 2
-    body: boost: 1)
+    body: boost: 1
+  console.log(results)

 change_floor = (direction) ->
   console.log("Current floor: "  + current_floor.toString())


fix how we format time for github

fix how we format time for github

--- gist-meta.coffee
+++ gist-meta.coffee
@@ -9,8 +9,9 @@
 filename = process.argv[2] || "data/gist-meta.json"
 since = process.argv[3] || ""  # "2015-10-01T00:00:00Z"
 if since == "15min"
-  since = moment().subtract(15, "minutes").format()#.format("YYYY-MM-DDTHH:mm:ssZ")
-  console.log "SINCE", since
+  since = moment().subtract(15, "minutes")
+  .utc()
+    .format("YYYY-MM-DDTHH:mm:ss[Z]")
 singleUsername = process.argv[4]

 # we will log our progress in ES


Call correct api

Call correct api

--- src/scripts/spot.coffee
+++ src/scripts/spot.coffee
@@ -111,5 +111,5 @@
       message.send("#{body} :mega:")

   robot.respond /spot/i, (message) ->
-    spotRequest message, '/airplay', 'put', {}, (err, res, body) ->
+    spotRequest message, '/spot', 'put', {}, (err, res, body) ->
       message.send(body)


Use real ellipsis instead of three periods

--- src/forecast.coffee
+++ src/forecast.coffee
@@ -362,7 +362,7 @@
     if robot.adapterName == 'slack'
       attachmentsObj = [ {
         color: '#000000'
-        title: 'Here is your weather report...'
+        title: 'Here is your weather report…'
         text: response
         fallback: response
       } ]


Clean up some extra lines for readability

Space is good, but too much is hard to read

--- src/forecast.coffee
+++ src/forecast.coffee
@@ -258,29 +258,22 @@
       handleContinuingWeather(json, callback)

   handleJSON = (json, callback) ->
-
     last_json = json

     if json['minutely']
-
       dataPoints = json['minutely']['data'] || []

       if dataPoints.length > 0
-
         totalIntensity = 0
         for dataPoint in dataPoints
           totalIntensity += (dataPoint['precipIntensity'] || 0)

         if totalIntensity == 0
-
           handleClear(json, callback)
-
         else
-
           handleWeather(json, callback)

   fetchForecast = (callback) ->
-
     forecastKey = process.env.HUBOT_FORECAST_KEY
     unitType = process.env.HUBOT_FORECAST_UNITS || 'si'
     exclude = 'flags'
@@ -315,12 +308,11 @@
   forecast = ->
     now = new Date()

-    isActive =
+    active =
       now.toUTCString().substr(0,3).toLowerCase() in activeDays and
       now.getUTCHours() in activeHours

-    if isActive
-
+    if active
       # Only run during specified time windows

       room = process.env.HUBOT_FORECAST_ROOM
@@ -377,7 +369,6 @@
     else
       msg.send response

-
   robot.respond /forecast|weather/i, (msg) ->
     if Object.keys(last_json).length == 0
       manualForecast (json) ->


merging in parity checks.

merging in parity checks.

--- test/test_comprehensions.coffee
+++ test/test_comprehensions.coffee
@@ -1,5 +1,5 @@
 # Basic array comprehensions.
-nums    = (n * n for n in [1, 2, 3] when n&1)
+nums    = (n * n for n in [1, 2, 3] when n & 1)
 results = (n * 2 for n in nums)

 ok results.join(',') is '2,18'
@@ -8,7 +8,7 @@
 # Basic object comprehensions.
 obj   = {one: 1, two: 2, three: 3}
 names = (prop + '!' for prop of obj)
-odds  = (prop + '!' for prop, value of obj when value&1)
+odds  = (prop + '!' for prop, value of obj when value & 1)

 ok names.join(' ') is "one! two! three!"
 ok odds.join(' ')  is "one! three!"
@@ -27,7 +27,7 @@

 # Multiline array comprehension with filter.
-evens = for num in [1, 2, 3, 4, 5, 6] when not (num&1)
+evens = for num in [1, 2, 3, 4, 5, 6] when not (num & 1)
            num *= -1
            num -= 2
            num * -1


relationships

relationships

 - `getProperty`
 - `setProperty `
 - `updateProperties `
 - `setProperties `
 - `setProperty `
 - `properties `
 - `setProperties `

--- relationship.coffee
+++ relationship.coffee
@@ -6,7 +6,6 @@
          Might be reactive data source, if `_isReactive` passed as `true` - data of relationship will be updated before returning

          First argument must be number (id) or object returned from db
-
          This class is event-driven and all methods is chainable
 @class Neo4jRelationship
 ###
@@ -74,13 +73,122 @@

   ###
   @locus Server
-  @summary Create relationship between two nodes
-  @name create
+  @summary Get current relationship's properties
+  @name properties
   @class Neo4jRelationship
-  @url http://neo4j.com/docs/2.2.5/rest-api-relationships.html#rest-api-create-a-relationship-with-properties
+  @url http://neo4j.com/docs/2.2.5/rest-api-relationships.html#rest-api-get-all-properties-on-a-relationship
+  @returns {Object}
+  ###
+  properties: -> @__return (fut) ->
+    @update()
+    fut.return _.omit @node, ['_service', 'id', 'type', 'metadata', 'start', 'end']
+
+  ###
+  @locus Server
+  @summary Set (or override, if exists) one property on current relationship
+  @name setProperty
+  @class Neo4jRelationship
+  @url http://neo4j.com/docs/2.2.5/rest-api-relationships.html#rest-api-set-single-property-on-a-relationship
+  @param {String} name  - Name of the property
+  @param {String} value - Value of the property
   @returns {Neo4jRelationship}
   ###
-  # create: (from, to, type, properties = {}) ->
-    # @__return (fut) -> fut.return super
+  setProperty: (name, value) ->
+    if _.isObject name
+      k = Object.keys(name)[0]
+      value = name[k]
+      name = k
+
+    check name, String
+    check value, Match.OneOf String, Number, Boolean, [String], [Number], [Boolean]
+
+    @__return (fut) ->
+      @_node[name] = value
+      @_db.__batch
+        method: 'PUT'
+        to: @_node._service.property.endpoint.replace '{key}', name
+        body: value
+      ,
+        => fut.return @
+      , undefined, true
+
+  ###
+  @locus Server
+  @summary Set (or override, if exists) multiple property on current relationship
+  @name setProperties
+  @class Neo4jRelationship
+  @param {Object} nameValue - Object of key:value pairs
+  @returns {Neo4jRelationship}
+  ###
+  setProperties: (nameValue) ->
+    check nameValue, Object
+    @__return (fut) ->
+      tasks = []
+      for name, value of nameValue
+        @_node[name] = value
+
+        tasks.push
+          method: 'PUT'
+          to: @_node._service.property.endpoint.replace '{key}', name
+          body: value
+
+      @_db.batch tasks, =>
+        fut.return @
+      , false, true
+
+  ###
+  @locus Server
+  @summary This ~~will replace all existing properties~~ (not actually due to [this bug](https://github.com/neo4j/neo4j/issues/5341)), it will update existing properties and add new.
+  @name updateProperties
+  @class Neo4jRelationship
+  @url http://neo4j.com/docs/2.2.5/rest-api-relationships.html#rest-api-set-all-properties-on-a-relationship
+  @param {Object} nameValue - Object of key:value pairs
+  @returns {Neo4jRelationship}
+  ###
+  updateProperties: (nameValue) ->
+    check nameValue, Object
+    @__return (fut) ->
+      delete @_node[k] for k, v of _.omit @_node, ['_service', 'id', 'type', 'metadata', 'start', 'end']
+
+      for k, v of nameValue
+        @_node[k] = v
+
+      @_db.__batch
+        method: 'PUT'
+        to: @_node._service.properties.endpoint
+        body: nameValue
+      ,
+        => fut.return @
+      , undefined, true
+
+  ###
+  @locus Server
+  @summary Set / Get property on current relationship, if only first argument is passed - will return property value, if both arguments is presented - property will be updated or created
+  @name setProperty
+  @class Neo4jRelationship
+  @url http://neo4j.com/docs/2.2.5/rest-api-relationships.html#rest-api-get-single-property-on-a-relationship
+  @url http://neo4j.com/docs/2.2.5/rest-api-node-properties.html#rest-api-set-property-on-node
+  @param {String} name  - Name of the property
+  @param {String} value - [OPTIONAL] Value of the property
+  @returns {Neo4jRelationship | String | Boolean | Number | [String] | [Boolean] | [Number]}
+  ###
+  property: (name, value) ->
+    check name, String
+    return @getProperty name if not value
+    check value, Match.Optional Match.OneOf String, Number, Boolean, [String], [Number], [Boolean]
+    return @setProperty name, value
+
+  ###
+  @locus Server
+  @summary Get one property on current node
+  @name getProperty
+  @class Neo4jRelationship
+  @url http://neo4j.com/docs/2.2.5/rest-api-relationships.html#rest-api-get-single-property-on-a-relationship
+  @param {String} name - Name of the property
+  @returns {String | Boolean | Number | [String] | [Boolean] | [Number]}
+  ###
+  getProperty: (name) -> @__return (fut) =>
+    @update()
+    fut.return @node[name]


:memo: Mention 10% zoom factor

:memo: Mention 10% zoom factor

--- lib/image-editor-view.coffee
+++ lib/image-editor-view.coffee
@@ -51,15 +51,11 @@
   getPane: ->
     @parents('.pane').view()

-  # Zooms the image out.
-  #
-  # This is done by a factor of `0.9`.
+  # Zooms the image out by 10%.
   zoomOut: ->
     @adjustSize(0.9)

-  # Zooms the image in.
-  #
-  # This is done by a factor of `1.1`.
+  # Zooms the image in by 10%.
   zoomIn: ->
     @adjustSize(1.1)


increase account creation, etc., limits -- especially because they are getting the ip address wrong - - they get it inside the vpn

--- salvus/hub.coffee
+++ salvus/hub.coffee
@@ -3432,10 +3432,10 @@
 #
 # Anti-DOS cracking throttling policy:
 #
-#   * POLICY 1: A given email address is allowed at most 3 failed login attempts per minute.
-#   * POLICY 2: A given email address is allowed at most 10 failed login attempts per hour.
-#   * POLICY 3: A given ip address is allowed at most 10 failed login attempts per minute.
-#   * POLICY 4: A given ip address is allowed at most 25 failed login attempts per hour.
+#   * POLICY 1: A given email address is allowed at most 5 failed login attempts per minute.
+#   * POLICY 2: A given email address is allowed at most 100 failed login attempts per hour.
+#   * POLICY 3: A given ip address is allowed at most 100 failed login attempts per minute.
+#   * POLICY 4: A given ip address is allowed at most 250 failed login attempts per hour.
 #############################################################################
 sign_in = (client, mesg) =>
     #winston.debug("sign_in")
@@ -3452,7 +3452,7 @@

     signed_in_mesg = null
     async.series([
-        # POLICY 1: A given email address is allowed at most 3 failed login attempts per minute.
+        # POLICY 1: A given email address is allowed at most 5 failed login attempts per minute.
         (cb) ->
             database.count
                 table: "failed_sign_ins_by_email_address"
@@ -3461,11 +3461,11 @@
                     if error
                         sign_in_error(error)
                         cb(true); return
-                    if count > 3
-                        sign_in_error("A given email address is allowed at most 3 failed login attempts per minute. Please wait.")
+                    if count > 5
+                        sign_in_error("A given email address is allowed at most 5 failed login attempts per minute. Please wait.")
                         cb(true); return
                     cb()
-        # POLICY 2: A given email address is allowed at most 10 failed login attempts per hour.
+        # POLICY 2: A given email address is allowed at most 100 failed login attempts per hour.
         (cb) ->
             database.count
                 table: "failed_sign_ins_by_email_address"
@@ -3474,12 +3474,12 @@
                     if error
                         sign_in_error(error)
                         cb(true); return
-                    if count > 10
-                        sign_in_error("A given email address is allowed at most 10 failed login attempts per hour. Please wait.")
+                    if count > 100
+                        sign_in_error("A given email address is allowed at most 100 failed login attempts per hour. Please wait.")
                         cb(true); return
                     cb()

-        # POLICY 3: A given ip address is allowed at most 10 failed login attempts per minute.
+        # POLICY 3: A given ip address is allowed at most 100 failed login attempts per minute.
         (cb) ->
             database.count
                 table: "failed_sign_ins_by_ip_address"
@@ -3488,12 +3488,12 @@
                     if error
                         sign_in_error(error)
                         cb(true); return
-                    if count > 10
-                        sign_in_error("A given ip address is allowed at most 10 failed login attempts per minute. Please wait.")
+                    if count > 100
+                        sign_in_error("A given ip address is allowed at most 100 failed login attempts per minute. Please wait.")
                         cb(true); return
                     cb()

-        # POLICY 4: A given ip address is allowed at most 25 failed login attempts per hour.
+        # POLICY 4: A given ip address is allowed at most 250 failed login attempts per hour.
         (cb) ->
             database.count
                 table: "failed_sign_ins_by_ip_address"
@@ -3502,8 +3502,8 @@
                     if error
                         sign_in_error(error)
                         cb(true); return
-                    if count > 25
-                        sign_in_error("A given ip address is allowed at most 25 failed login attempts per hour. Please wait.")
+                    if count > 250
+                        sign_in_error("A given ip address is allowed at most 250 failed login attempts per hour. Please wait.")
                         cb(true); return
                     cb()

@@ -3751,7 +3751,7 @@
             else
                 cb()

-        # make sure this ip address hasn't requested more than 100
+        # make sure this ip address hasn't requested more than 1000
         # accounts in the last 6 hours (just to avoid really nasty
         # evils, but still allow for demo registration behind a wifi
         # router -- say)
@@ -3766,7 +3766,7 @@
                     if not value?
                         ip_tracker.set(key: client.ip_address, value:1, ttl:6*3600)
                         cb()
-                    else if value < 100
+                    else if value < 1000
                         ip_tracker.set(key: client.ip_address, value:value+1, ttl:6*3600)
                         cb()
                     else # bad situation


Copy user clientPermissions schema property from CodeCombat


Add stratis defaults

Add stratis defaults

--- app/src/preferences/defaults.coffee
+++ app/src/preferences/defaults.coffee
@@ -231,4 +231,19 @@
         address: 'http://www.clubcha.in/tx/%s'
     discoveryGap: 20

+ledger.preferences.stratis =
+  Display:
+    units:
+      bitcoin:
+        symbol: 'CLUB'
+        unit: 8
+
+# Coin preferences
+  Coin:
+    explorers:
+      cryptoID:
+        name: 'CryptoID'
+        address: 'https://chainz.cryptoid.info/strat/tx.dws?%s.htm'
+    discoveryGap: 20
+
 ledger.preferences.common.setCoin("bitcoin")


Defer the use of banner images to a better time.

--- src/js/menus.coffee
+++ src/js/menus.coffee
@@ -456,14 +456,14 @@

         changeSubtitleGivenEvent data.originalSubtitle, e

-        bannerURL = "#{config.BASE_SERVER_URL}/convert2png64?url=#{encodeURIComponent showData.images.fanart.thumb}"
-        log.info "Banner URL: #{bannerURL}"
+        # bannerURL = "#{config.BASE_SERVER_URL}/convert2png64?url=#{encodeURIComponent showData.images.fanart.thumb}"
+        # log.info "Banner URL: #{bannerURL}"

         detailedItemCard = new cards.Default(
           title: showData.title
           subtitle: showData.year
           body: "Overview: #{showData.overview}"
-          banner: bannerURL
+          # banner: bannerURL
         )
         detailedItemCard.show()


Git-commit only if something staged or for amend

--- lib/models/git-commit.coffee
+++ lib/models/git-commit.coffee
@@ -10,16 +10,19 @@
 gitCommit = (_amend='') ->
   if amend?
     atom.workspace.open(file, activatePane: true, searchAllPanes: true)
-    return
-
-  amend = _amend
-  git.cmd(
-    args: ['status'],
-    stdout: (data) -> prepFile data, amend
-  )
+  else
+    git.stagedFiles (files) ->
+      if _amend isnt '' or files.length >= 1
+        git.cmd(
+          args: ['status'],
+          stdout: (data) -> prepFile data, _amend
+        )
+      else
+        new StatusView(type: 'error', message: 'Nothing to commit.')

 # FIXME?: maybe I shouldn't use the COMMIT file in .git/
-prepFile = (status, amend) ->
+prepFile = (status, _amend) ->
+  amend = _amend
   # format the status to be ignored in the commit message
   status = status.replace(/\s*\(.*\)\n/g, '')
   status = status.trim().replace(/\n/g, "\n# ")


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/timed_events.coffee
+++ src/timed_events.coffee
@@ -14,7 +14,7 @@
 @see GameObject

 TimedEvents module
-@name Bounded
+@name TimedEvents
 @module
 @constructor
 @param {Object} I Instance variables


sending all events to particles

sending all events to particles

--- src/timed_events.coffee
+++ src/timed_events.coffee
@@ -9,7 +9,7 @@
 ###
 TimedEvents = (I={}) ->
   Object.reverseMerge I,
-    every: 0
+    everyEvents: 0

   ###*
   Execute fn every n frames.


clean up code

clean up code

--- lib/notifications-log.coffee
+++ lib/notifications-log.coffee
@@ -1,13 +1,13 @@
 {Emitter} = require 'atom'

+typeIcons =
+  fatal: 'bug'
+  error: 'flame'
+  warning: 'alert'
+  info: 'info'
+  success: 'check'
+
 module.exports = class NotificationsLog
-  typesShown:
-    fatal: true
-    error: true
-    warning: true
-    info: true
-    success: true
-
   constructor: ->
     @render()
     @emitter = new Emitter
@@ -24,32 +24,21 @@
     @element.classList.add('notifications-log')

     # Add Header
-    @header = document.createElement('header')
-    @element.appendChild(@header)
+    header = document.createElement('header')
+    @element.appendChild(header)

     # Add Container
     @list = document.createElement('ul')
     @element.appendChild(@list)

     # Add Buttons
-    for type, shown of @typesShown
-
-      icon = switch type
-        when 'fatal' then 'bug'
-        when 'error' then 'flame'
-        when 'warning' then 'alert'
-        when 'info' then 'info'
-        when 'success' then 'check'
-
-      @list.classList.toggle("hide-#{type}", not shown)
-
+    for type, icon of typeIcons
       button = document.createElement('button')
-      button.classList.add('notification-type', 'btn', 'icon', "icon-#{icon}", type)
-      button.classList.toggle('show-type', shown)
+      button.classList.add('notification-type', 'btn', 'icon', "icon-#{icon}", 'show-type', type)
       button.dataset.type = type
       button.addEventListener 'click', (e) => @toggleType(e.target.dataset.type)
       atom.tooltips.add(button, {title: "Toggle #{type} notifications"})
-      @header.appendChild(button)
+      header.appendChild(button)

     # Add Notifications
     for notification in atom.notifications.getNotifications()
@@ -70,12 +59,10 @@

   toggle: -> atom.workspace.toggle(this)

-  toggleType: (type) ->
-    show = not @typesShown[type]
-    @typesShown[type] = show
-    button = @header.querySelector(".notification-type.#{type}")
-    @list.classList.toggle("hide-#{type}", not show)
-    button.classList.toggle('show-type', show)
+  toggleType: (type, force) ->
+    button = @element.querySelector(".notification-type.#{type}")
+    hide = not button.classList.toggle('show-type', force)
+    @list.classList.toggle("hide-#{type}", hide)

   addNotification: (notification) ->
     # TODO: should probably create new element instead of cloning


:bug: Catch the errors in @Save and Use setPath instead of setPaths in @Load Possible Fix for danielmahon/atom-open-last-project#28

--- lib/open-last-project.coffee
+++ lib/open-last-project.coffee
@@ -3,19 +3,20 @@
 FS = require('fs')
 module.exports =
   Save:->
-    Files = []
-    ActiveEditor = atom.workspace.getActiveEditor()
-    atom.workspace.eachEditor (editor)->
-      File = editor.getPath()
-      return unless File
-      Files.push File
-    CurrentFile = ActiveEditor && ActiveEditor.getPath() || null;
-    localStorage.setItem('open-last-project',JSON.stringify({Path: atom.project.getPath(), Files: Files, CurrentFile: CurrentFile}))
+    try
+      Files = []
+      ActiveEditor = atom.workspace.getActiveEditor()
+      atom.workspace.eachEditor (editor)->
+        File = editor.getPath()
+        return unless File
+        Files.push File
+      CurrentFile = ActiveEditor && ActiveEditor.getPath() || null;
+      localStorage.setItem('open-last-project',JSON.stringify({Path: atom.project.getPath(), Files: Files, CurrentFile: CurrentFile}))
   LoadProject:->
     LastProject = localStorage.getItem('open-last-project')
     return unless LastProject
     LastProject = JSON.parse(LastProject)
-    atom.project.setPaths [LastProject.Path]
+    atom.project.setPath LastProject.Path
     Promises = []
     LastProject.Files.forEach (file)->
       Promises.push new Promise (resolve)->


add comment to src/util

add comment to src/util

--- src/lib/util.coffee
+++ src/lib/util.coffee
@@ -8,7 +8,6 @@
         'shinout-no-macbook-pro' => 'shinoutNoMacbookPro' # if lowerFirst = true

     @method camelize
-    @private
     @static
     @param {String} hyphened
     @param {Boolean} [lowerFirst=false] make capital char lower
@@ -30,7 +29,6 @@
         'ABC' => 'a-b-c' # current implementation... FIXME ?

     @method hyphenize
-    @private
     @static
     @param {String} hyphened
     @return {String} cameled
@@ -41,6 +39,17 @@
         cameled.replace(/([A-Z])/g, (st)-> '-' + st.charAt(0).toLowerCase()).slice(1)

+    ###*
+    requires js file
+    in Titanium, file-not-found-like-exception occurred in require function cannot be caught.
+    Thus, before require function is called, check the existence of the file.
+    File extension must be '.js' in Titanium.
+
+    @method requireFile
+    @static
+    @param {String} file name without extension
+    @return {any} required value
+    ###
     @requireFile: (file) ->
         if not Ti?
             return require file


sprite boundingType to rect on default with tmlib.js

--- lib/main2.coffee
+++ lib/main2.coffee
@@ -354,6 +354,7 @@
                 motionsprite.visible = visible
                 motionsprite.width = width
                 motionsprite.height = height
+                motionsprite.boundingType = "rect"
             else
                 motionsprite = tm.display.Sprite()
                 motionsprite.visible = true


cleanup

cleanup

--- lib/marie.coffee
+++ lib/marie.coffee
@@ -260,12 +260,6 @@
 	@param [App]
 	###
 	configureMongoDB: (app, skip) ->
-	# 	ui.warn 'Configure MongoDB database.'
-	# 	input = [' Local/Remote']
-	# 	ui.line()
-	# 	utils.prompt.get input, (err, result) =>
-	# 		ui.line()
-	# 		if result[input].match(/^r/i) then @configureRemoteMongoDB(app, skip) else @configureLocalMongoDB(app, skip)

 	###
 	Local mongodb database configuration
@@ -342,18 +336,17 @@
 	save: (app) ->
 		app.add (err, app) =>
 			if err then @throwFatalError err
-			else
-				ui.ok "#{app.name} was successfully created."
-				ui.notice "Path: #{app.path}"
-				@onSave()
+			else @onSave app

 	###
 	App creation callback method
 	Will ask to start newly created app
 	###
-	onSave: ->
-		console.log '-->'
-		console.log @app
+	onSave: (app) ->
+		ui.notice "#{app.name} was successfully created."
+		ui.notice "Id: #{app.id}"
+		ui.notice "Path: #{app.path}"
+		ui.notice "Done."

 	###
 	Configure `add` app method. Creates new app


adding other ways to lookup pages

adding other ways to lookup pages

--- routes/pages.coffee
+++ routes/pages.coffee
@@ -44,6 +44,15 @@
   db.collection "pages", (err, collection) ->
     collection.find().toArray (err, items) ->
       res.send items
+
+exports.findByUrl = (req, res) ->
+  id = req.params.id
+  console.log "Retrieving page: " + id
+  db.collection "pages", (err, collection) ->
+    collection.findOne
+      _id: new BSON.ObjectID(id)
+    , (err, item) ->
+      res.send item

 ###
   exports.addPage = (req, res) ->


Avoid use of the soon to be deprecated Atom function showSaveDialogSync

--- lib/workspace-manager.coffee
+++ lib/workspace-manager.coffee
@@ -342,11 +342,19 @@
       activeLevelCodeEditor = workspace.getActiveLevelCodeEditor()
       activeTextEditor = activeLevelCodeEditor.getTextEditor()
       activeTerminal = activeLevelCodeEditor.getTerminal()
-      if (filePath = activeTextEditor.getPath() ? atom.showSaveDialogSync())?
-        activeTextEditor.saveAs(filePath).then () =>
-          activeLevelCodeEditor.startExecution()
-          activeTerminal.show()
-          activeTerminal.focus()
+
+      path = activeTextEditor.getPath()
+      startExecutionAction = () =>
+        activeLevelCodeEditor.startExecution()
+        activeTerminal.show()
+        activeTerminal.focus()
+
+      if path
+        activeTextEditor.saveAs(path).then startExecutionAction
+      else
+        textEditorPaneContainer = atom.workspace.paneContainerForItem activeTextEditor
+        textEditorPaneContainer.getActivePane().saveItemAs activeTextEditor, (error) =>
+          startExecutionAction() if !error
     else
       event.abortKeyBinding()


uncomment

uncomment

--- lib/setup.coffee
+++ lib/setup.coffee
@@ -100,7 +100,7 @@
     res.send 200, { status: 'OK' }

   # More general middleware
-  # app.use express.static(path.resolve __dirname, "../public")
+  app.use express.static(path.resolve __dirname, "../public")

   # Proxy unhandled requests to Gravity using node-http-proxy
   app.use (req, res) ->