Implement scraping multiple Google results

--- lib/sourcefetch.coffee
+++ lib/sourcefetch.coffee
@@ -1,33 +1,62 @@
 {CompositeDisposable} = require 'atom'

-getSnippet = (soLink) ->
+# Returns the code from the first accepted SO answer
+# from the provided URLs to SO pages.
+findSnippet = (soLinks) ->
+  return new Promise (resolve, reject) ->
+    if soLinks.length == 0
+      reject reason: "No results found for this query."
+
+    findSnippetRecursive = ->
+      currentLink = soLinks.shift()
+      console.log currentLink
+      scrapeStackOverflow(currentLink).then (result) ->
+        resolve result
+      , (err) ->
+        if soLinks.length == 0
+          reject reason: "No accepted answers on StackOverflow"
+        else
+          findSnippetRecursive()
+
+    findSnippetRecursive()
+
+# Extracts the accepted answer code from
+# a StackOverflow page
+scrapeStackOverflow = (soLink) ->
   return new Promise (resolve, reject) ->
     request = require 'request'
     request soLink, (error, response, body) ->
       if !error && response.statusCode == 200
         cheerio = require 'cheerio'
         $ = cheerio.load body
-        resolve $('div.accepted-answer code').text()
+        snippet = $('div.accepted-answer pre code').text()
+        if snippet == ""
+          reject reason: 'No accepted answer / no code in accepted answer'
+        else
+          resolve snippet
       else
-        console.log error
         reject reason: 'Problem scraping StackOverflow'

+
+# Returns a list of StackOverlow links
+# for the given query and language
 searchGoogle = (query, language) ->
   return new Promise (resolve, reject) ->
     google = require "google"
-    google.resultsPerPage = 1
+    google.resultsPerPage = 10

     searchString = "#{query} in #{language} site:stackoverflow.com"
+    console.log "SEARCHING: #{searchString}"
     google searchString, (err, next, links) ->
       if err
         reject reason: "An error has occured"

       if links.length == 0
-        console.log "No results"
         reject reason: "No results were found"

-      soLink = links[0].link
-      resolve soLink
+      soLinks = links.map (item) -> item.link
+      resolve soLinks
+

 module.exports =
   subscriptions: null
@@ -51,13 +80,13 @@
       language = editor.getGrammar().name

       searchGoogle(selection, language)
-      .then (soLink) ->
+      .then (soLinks) ->
         atom.notifications.addSuccess "Googled problem."
-        getSnippet(soLink)
+        findSnippet(soLinks)
         .then (snippet) ->
           atom.notifications.addSuccess "Got snippet!"
           editor.insertText(snippet)
         , (err) ->
-          atom.notifications.addError err.reason
+          atom.notifications.addError "FindSnippet error: " + JSON.stringify(err)
       , (err) ->
-        atom.notifications.addError err.reason
+        atom.notifications.addError "SearchGoogle error: " + JSON.stringify(err)


Add support for using plaintext code output when pygments is not present, rather than failing entirely.

--- src/docco.coffee
+++ src/docco.coffee
@@ -118,15 +118,17 @@
     console.error error.toString() if error

   pygments.stdin.on 'error',  (error)  ->
-    console.error 'Could not use Pygments to highlight the source.'
-    process.exit 1
+    console.error 'Could not highlight code, using plaintext'

   pygments.stdout.on 'data', (result) ->
     output += result if result

   pygments.on 'exit', ->
     output = output.replace(highlightStart, '').replace(highlightEnd, '')
-    codeFragments = output.split language.codeSplitHtml
+    if output is ''
+      codeFragments = (htmlEscape section.codeText for section in sections)
+    else
+      codeFragments = output.split language.codeSplitHtml
     docsFragments = showdown.makeHtml(docs).split language.docsSplitHtml

     for section, i in sections
@@ -138,6 +140,16 @@
     pygments.stdin.write code
     pygments.stdin.end()

+# Escape an html string, to produce valid non-highlighted output when pygments
+# is not present on the system.
+htmlEscape = (string) ->
+  string.replace(/&/g, '&')
+    .replace(/</g, '<')
+    .replace(/>/g, '>')
+    .replace(/"/g, '"')
+    .replace(/'/g, ''')
+    .replace(/\//g,'/')
+
 # Once all of the code is finished highlighting, we can generate the HTML file by
 # passing the completed sections into the template, and then writing the file to
 # the specified output path.


Changed the robot.respond to "just listen"

--- src/ector.coffee
+++ src/ector.coffee
@@ -40,7 +40,7 @@
   robot.respond /shut up/i, (msg) ->
     quiet = true

-  robot.respond /shut up/i, (msg) ->
+  robot.respond /just listen/i, (msg) ->
     quiet = true
     msg.reply "Now I'm just listening."


fix throttle

fix throttle

--- src/event.coffee
+++ src/event.coffee
@@ -2,9 +2,9 @@
     alight.hooks.attribute.unshift
         code: 'events'
         fn: ->
-            d = @.attrName.match /^\@([\w\.]+)$/
+            d = @.attrName.match /^\@([\w\.\-]+)$/
             if not d
-                d = @.attrName.match /^\(([\w\.]+)\)$/
+                d = @.attrName.match /^\(([\w\.\-]+)\)$/
                 if not d
                     return


config: use VERSION file

config: use VERSION file

--- config/main.kontrol-prod.coffee
+++ config/main.kontrol-prod.coffee
@@ -2,9 +2,7 @@
 nodePath = require 'path'
 deepFreeze = require 'koding-deep-freeze'

-# jenkins write to this as last step after building, we use RELEASE to avoid confusion with
-# building it manually
-version = (fs.readFileSync nodePath.join(__dirname, '../RELEASE'), 'utf-8').trim()
+version = (fs.readFileSync nodePath.join(__dirname, '../VERSION'), 'utf-8').trim()
 projectRoot = nodePath.join __dirname, '..'

 socialQueueName = "koding-social-#{version}"


Remove unused `require` calls

Remove unused `require` calls

--- src/index.coffee
+++ src/index.coffee
@@ -1,13 +1,9 @@

-assertTypes = require "assertTypes"
-assertType = require "assertType"
 setProto = require "setProto"
 Promise = require "Promise"
 Type = require "Type"
-path = require "path"
 now = require "performance-now"
 log = require "log"
-ip = require "ip"

 createServer = require "./utils/createServer"
 Response = require "../response"


Rename #getImageMeta() to #getMeta()

Rename #getImageMeta() to #getMeta()

--- src/index.coffee
+++ src/index.coffee
@@ -64,7 +64,7 @@

   @

-Image.prototype.getImageMetadata = (cb) ->
+Image.prototype.getMeta = (cb) ->
   @gm = gm(@src).identify (err, val) =>
     return cb err if err
     @meta =
@@ -142,7 +142,7 @@
     cb null, version

 Image.prototype.exec = (cb) ->
-  @getImageMetadata (err) =>
+  @getMeta (err) =>
     return cb err if err
     versions = JSON.parse(JSON.stringify(@config.versions))
     async.map versions, @resizeAndUpload.bind(@), (err, versions) =>


* [bug] the options argument of load method should not replace the path,cwd,base! + getOptions(aOptions) function to avoid the path, cwd, base replaced.

--- src/index.coffee
+++ src/index.coffee
@@ -57,12 +57,18 @@
   toString: -> @path
   _inspect: -> '"'+@relative+'"'
   inspect: -> '<'+ @constructor.name + ' ' + @_inspect() + '>'
+  getOptions: (aOptions)->
+    result = @mergeTo(aOptions)
+    result.path = @path
+    result.base = @base
+    result.cwd = @cwd
+    result

   load: (aOptions, done)->
     if isFunction aOptions
       done = aOptions
       aOptions = null
-    aOptions = @mergeTo(aOptions)
+    aOptions = @getOptions(aOptions)
     checkValid = aOptions.validate isnt false
     unless @stat?
       @_loadStat aOptions, (err, stat)=>
@@ -89,7 +95,7 @@
     @
   loadSync: (aOptions)->
     if isFunction(@_loadStatSync)
-      aOptions = @mergeTo(aOptions)
+      aOptions = @getOptions(aOptions)
       checkValid = aOptions.validate isnt false
       @stat = @_loadStatSync(aOptions) unless @stat?
       @validate() if checkValid
@@ -133,11 +139,11 @@
     if isFunction aOptions
       done = aOptions
       aOptions = null
-    aOptions = @mergeTo(aOptions)
+    aOptions = @getOptions(aOptions)
     _loadStat aOptions, done
     @
   loadStatSync: (aOptions)->
-    aOptions = @mergeTo(aOptions)
+    aOptions = @getOptions(aOptions)
     if @_loadStatSync
       result = @_loadStatSync aOptions
     else
@@ -148,7 +154,7 @@
     if isFunction aOptions
       done = aOptions
       aOptions = null
-    aOptions = @mergeTo(aOptions)
+    aOptions = @getOptions(aOptions)
     @_loadContent aOptions, (err, result)=>
       @contents = result unless err
       done(err, result)
@@ -156,7 +162,7 @@

   loadContentSync: (aOptions)->
     if isFunction(@_loadContentSync)
-      aOptions = @mergeTo aOptions
+      aOptions = @getOptions aOptions
       @contents = result = @_loadContentSync aOptions
     else
       throw new TypeError 'loadContentSync not implemented'
@@ -168,13 +174,13 @@
     if isBoolean aOptions
       raiseError = aOptions
       aOptions = null
-    aOptions = @mergeTo(aOptions)
+    aOptions = @getOptions(aOptions)
     result = @_validate aOptions
     if raiseError and not result
       throw new TypeError @name+': invalid path '+aOptions.path
     result
   isValid: (aOptions)->
-    @Validate(aOptions, false)
+    @validate(aOptions, false)

   getContentSync: (aOptions)->
     if isFunction aOptions


refactor

refactor

--- src/index.coffee
+++ src/index.coffee
@@ -5,7 +5,6 @@
     @children = []

   @parse: (s) ->
-    parsed = null
     [
       EmptyElement
       Element
@@ -14,9 +13,9 @@
       parse: (s) =>
         { level, node } = Node.parseBasic s
         new Text level: level, name: node
-    ].some (i) ->
-      parsed = i.parse s
-    parsed
+    ].reduce (parsed, i) ->
+      return parsed if parsed?
+      i.parse s

   @parseBasic: (s) ->
     match = s.match /^(\s*)(.+)$/


fixed isScriptLoaded

fixed isScriptLoaded

--- src/index.coffee
+++ src/index.coffee
@@ -55,8 +55,8 @@

 Ubret.Loader = (tools, cb) ->
   isScriptLoaded = (script) ->
-    (not typeof script is 'undefined') and (not (typeof window[script] is 'undefined' and typeof Ubret[script] is 'undefined'))
-
+    return (window[script]? or Ubret[script]?)
+
   loadScript = (source, cb=null) ->
     script = document.createElement 'script'
     script.onload = cb


fixed typo in index.coffee to reflect namechange of Client class

--- src/index.coffee
+++ src/index.coffee
@@ -2,8 +2,8 @@

 if typeof window isnt 'undefined'
   if window.Crowdstart?
-    window.Crowdstart.Client  = Crowdstart
+    window.Crowdstart.Client  = Client
   else
-    window.Crowdstart = Client: Crowdstart
+    window.Crowdstart = Client: Client
 else
   module.exports = Client


extract functions

extract functions

--- src/index.coffee
+++ src/index.coffee
@@ -1,3 +1,20 @@
+parseEmptyElement = (s) ->
+  if s.match(/^<\/\S+$/)
+    [s.substring(2), 'empty element']
+
+parseElement = (s) ->
+  if s.match(/^<\S+$/)
+    [s.substring(1), 'element']
+
+parseAttribute = (s) ->
+  m = s.match(/^@(\S+)\s+(.+)$/)
+  if m
+    [m[1], 'attribute', m[2]]
+
+parseText = (s) ->
+  if s.match(/^>.+$/)
+    [s.substring(1), 'text']
+
 class Node
   constructor: ({ @level, @name, @type, @value }) ->
     @parent = null
@@ -11,19 +28,10 @@
     level = space.length
     parsed = null
     [
-      (s) ->
-        if s.match(/^<\/\S+$/)
-          [s.substring(2), 'empty element']
-      (s) ->
-        if s.match(/^<\S+$/)
-          [s.substring(1), 'element']
-      (s) ->
-        m = s.match(/^@(\S+)\s+(.+)$/)
-        if m
-          [m[1], 'attribute', m[2]]
-      (s) ->
-        if s.match(/^>.+$/)
-          [s.substring(1), 'text']
+      parseEmptyElement
+      parseElement
+      parseAttribute
+      parseText
       (s) ->
         [s, 'text']
     ].some (f) ->


"queue" (name) is passed to sendTask in arguments

--- test/integration/queues/base.coffee
+++ test/integration/queues/base.coffee
@@ -159,10 +159,10 @@
         queue.sendTask.restore()
         done()

-      it 'check arguments for delayedCall', ->
+      it 'check arguments for delayedCall are properly JSON.stringified', ->
         assert.equal 1, ivySpy.called
         actual = JSON.stringify ivySpy.args
-        assert.equal actual, '[[null,5,null]]'
+        assert.strictEqual actual, '[[null,5,null]]'

       it 'check arguments for sendTask', ->
         assert.equal 1, queueSpy.called
@@ -170,6 +170,7 @@
         expected = {
           name: "factorial"
           options: {}
+          queue: 'ivy'
           args:[5]
         }
         assert.deepEqual actual, expected


remove unused comments

remove unused comments

--- test/server/routes/api/users.coffee
+++ test/server/routes/api/users.coffee
@@ -217,7 +217,6 @@
             .expect 401
             .end done

-      # TODO investigate failing test
       it 'returns a 400 if user is not exist', (done) ->
         auth.createAdmin (err, admin) ->
           admin
@@ -259,7 +258,6 @@
             .expect 401
             .end done

-      # TODO investigate failing test
       it 'returns a 400 if user is not exist', (done) ->
         auth.createAdmin (err, admin) ->
           admin


Add authenticated w/o authorization case

--- test/socket_permissions.spec.coffee
+++ test/socket_permissions.spec.coffee
@@ -49,19 +49,33 @@
         # Build 3 users: anon, authenticated, and authorized.
         #
         (done) =>
-          # anon
+          # anon: No session, no authentication.
           @anony = common.build_sockjs_client(done)
         (done) =>
-          # authenticated
-          session = {cookie: { maxAge: 2000 }, session_id: "authenty"}
+          # sessiony: Has session, but not authenticated.
+          session = {cookie: { maxAge: 2000 }, session_id: "sessiony"}
           @server.sockrooms.saveSession session, (err, ok) =>
-            @authenty = common.build_sockjs_client =>
-              @authenty.writeJSON {route: "identify", body: {session_id: "authenty"}}
-              @authenty.onceJSON (data) ->
-                expect(data).to.eql({route: "identify", body: {session_id: "authenty"}})
+            @sessiony = common.build_sockjs_client =>
+              @sessiony.writeJSON {route: "identify", body: {session_id: "sessiony"}}
+              @sessiony.onceJSON (data) ->
+                expect(data).to.eql({route: "identify", body: {session_id: "sessiony"}})
                 done()
         (done) =>
-          # authorized
+          # authenty: Is authenticated, but not authorized.
+          common.stubBrowserID(@browser, {email: "no_group@mockmyid.com"})
+          session = {
+            cookie: { maxAge: 2000 },
+            session_id: "authenty",
+          }
+          api_methods.authenticate session, "bogus assertion", (err, session, message) =>
+            @server.sockrooms.saveSession session, (err, ok) =>
+              @authenty = common.build_sockjs_client =>
+                @authenty.writeJSON {route: "identify", body: {session_id: "authenty"}}
+                @authenty.onceJSON (data) ->
+                  expect(data).to.eql({route: "identify", body: {session_id: "authenty"}})
+                  done()
+        (done) =>
+          # authory: Is authorized to access documents.
           common.stubBrowserID(@browser, {email: "one@mockmyid.com"})
           session = {
             cookie: { maxAge: 2000 },
@@ -109,6 +123,7 @@

   after (done) ->
     @anony.close()
+    @sessiony.close()
     @authenty.close()
     @authory.close()
     common.shutDown(@server, done)
@@ -157,7 +172,8 @@

   it "Authorizes properly for public rooms", (done) ->
     public_join_responses = [
-      [@anony, "session"], [@authenty, "success"], [@authory, "success"]
+      [@anony, "session"], [@sessiony, "success"],
+      [@authenty, "success"], [@authory, "success"]
     ]
     rooms = [ "dotstorm/#{@ds_public.id}", "firestarter/#{@fs_public.slug}",
               "resolve/#{@rs_public.id}",  "twinklepad/#{@tp_public.pad_id}"]
@@ -165,7 +181,8 @@

   it "Authorizes properly for private rooms", (done) ->
     private_join_responses = [
-      [@anony, "session"], [@authenty, "permission"], [@authory, "success"]
+      [@anony, "session"], [@sessiony, "permission"],
+      [@authenty, "permission"], [@authory, "success"]
     ]
     rooms = [ "dotstorm/#{@ds_private.id}", "firestarter/#{@fs_private.slug}",
               "resolve/#{@rs_private.id}",  "twinklepad/#{@tp_private.pad_id}"]


Use MarkerIndex.remove instead of .delete

--- src/marker-layer.coffee
+++ src/marker-layer.coffee
@@ -337,7 +337,7 @@
   destroyMarker: (marker) ->
     if @markersById.hasOwnProperty(marker.id)
       delete @markersById[marker.id]
-      @index.delete(marker.id)
+      @index.remove(marker.id)
       @markersWithChangeListeners.delete(marker)
       @markersWithDestroyListeners.delete(marker)
       @displayMarkerLayers.forEach (displayMarkerLayer) -> displayMarkerLayer.destroyMarker(marker.id)
@@ -363,7 +363,7 @@
     {start, end} = Range.fromObject(range)
     start = @delegate.clipPosition(start)
     end = @delegate.clipPosition(end)
-    @index.delete(id)
+    @index.remove(id)
     @index.insert(id, start, end)

   setMarkerIsExclusive: (id, exclusive) ->


Use test helper to create Text

Use test helper to create Text

--- test/src/trix/text_view_test.coffee
+++ test/src/trix/text_view_test.coffee
@@ -20,8 +20,7 @@
   equal node.trixPosition, 0, "child node has a trixPosition property"
   equal node.trixLength, text.getLength(), "child node has a trixLength property"

-  text = Trix.Text.textForStringWithAttributes(fixture("plain").toString() + "\n")
-  elements = getElementsForText(text)
+  elements = getElementsForText(createText("Hello\n"))
   equal elements.length, 2, "two elements for string ending with a newline"
   equal elements[0].lastChild.tagName.toLowerCase(), "br", "container element's last child is a BR"
   equal elements[1].tagName.toLowerCase(), "br", "last element is an extra BR"


remove step from game object default events

--- src/timed_events.coffee
+++ src/timed_events.coffee
@@ -13,7 +13,7 @@

   self.bind "update", (elapsedTime) ->
     for event in I.everyEvents
-      {fn, per}
+      {fn, period} = event
       while (event.lastFired) < I.age + elapsedTime
         sendOrApply(fn)
         event.lastFired += event.period


update

update

--- src/torconnections.iced
+++ src/torconnections.iced
@@ -49,7 +49,7 @@
     @originPort = parseInt(@origin.split(":")[1])
     @destination = @connectionStatement.split("->")[1]
     @destinationIP = @destination.split(":")[0]
-    @destinationPort = @destination.split(":")[1]
+    @destinationPort = parseInt(@destination.split(":")[1])
     @geo = geoip.lookup(@destinationIP)
     if @originPort is ORPort
       @connectionType=connectiontypes[0] #Exit


added global status endpoint

added global status endpoint

--- src/model/statsd.coffee
+++ src/model/statsd.coffee
@@ -34,7 +34,7 @@
   var data = []

   for (var i = 0; i < allowedIds.length; i++) {
-    var path = "/render?target=transformNull(summarize(stats.counters." + domain + ".Channels." + allowedIds[i] + ".*.count,'1week'))&from=-1weeks&format=json";
+    var path = "/render?target=transformNull(summarize(stats.counters." + domain + ".Channels." + allowedIds[i] + ".Statuses.*.count,'1week'))&from=-1weeks&format=json";
     var raw = yield fetchData(path);
     data.push({
       _id : {"channelID": allowedIds[i] },


added conditions

added conditions

--- src/model/statsd.coffee
+++ src/model/statsd.coffee
@@ -37,8 +37,8 @@
     var path = "/render?target=transformNull(summarize(stats.counters." + domain + ".Channels." + allowedIds[i] + ".Statuses.*.count,'1week'))&from=-1weeks&format=json";
     var raw = yield fetchData(path);

-    var failed = !raw.data.length  ? raw.data[0][0] : 0;
-    var successful = !raw.data1.length  ? raw.data1[0][0] : 0;
+    var failed = raw.data !== undefined  ? raw.data[0][1] : 0;
+    var successful = raw.data1 !== undefined  ? raw.data1[0][1] : 0;

     data.push({
       _id : {"channelID": allowedIds[i] },


removed normalized event for hover out and remove timer for hiding eaten breadcrumb items since it touch devices does not support mouse

--- src/coffee/responsive.breadcrumbs.coffee
+++ src/coffee/responsive.breadcrumbs.coffee
@@ -116,7 +116,6 @@

             # normalize events ::start
             o.hoverIn  = if Modernizr.touch then 'touchstart' else 'mouseenter'
-            o.hoverOut = if Modernizr.touch then 'touchend' else 'mouseleave'
             # normalize events ::end

             # set the widths of each elements ::start
@@ -235,6 +234,7 @@

                         # trigger the afterDecompress callback
                         o.opts.onAfterDecompress(_this) if $.isFunction(o.opts.onAfterDecompress)
+

             # delegate the normalized event for hoverIn to the holder element
             o.el.on "#{ o.hoverIn }.#{ pluginName }", ".#{ pluginName.toLowerCase() }-holder", ->
@@ -261,36 +261,39 @@

                             o.opts.holderAnimation.onAfterShow(_this) if $.isFunction(o.opts.holderAnimation.onAfterShow)

-            # delegate the normalized event for hoverOut to the holder element
-            o.el.on "#{ o.hoverOut }.#{ pluginName }", ".#{ pluginName.toLowerCase() }-holder", ->
-                o.dropdownTimer = window.setTimeout( ->
-                    o.dropdownWrapper.trigger "#{ o.hoverOut }.#{ pluginName }"
-                , 500)
-
-            # bind the normalized event for hoverIn to the dropdownWrapper
-            o.dropdownWrapper.on "#{ o.hoverIn }.#{ pluginName }", ->
-                window.clearTimeout o.dropdownTimer
-
-            # bind the normalized event for hoverOut to the dropdownWrapper
-            o.dropdownWrapper.on "#{ o.hoverOut }.#{ pluginName }", ->
-                current = $ this
-
-                o.opts.holderAnimation.onBeforeHide(_this) if $.isFunction(o.opts.holderAnimation.onBeforeHide)
-
-                current
-                    .stop(true, true)
-                    .fadeOut
-                        duration: o.opts.holderAnimation.hideDuration
-                        easing:   o.opts.holderAnimation.hideEasing
-                        complete: ->
-                            o.opts.holderAnimation.onHide(_this) if $.isFunction(o.opts.holderAnimation.onHide)
-
-                            # remove the hover class to the holder element
-                            o.el
-                                .children(".#{ pluginName.toLowerCase() }-holder")
-                                .removeClass o.opts.holder.hoverClass
-
-                            o.opts.holderAnimation.onAfterHide(_this) if $.isFunction(o.opts.holderAnimation.onAfterHide)
+            # delegate events for non-touch devices
+            unless Modernizr.touch
+                # delegate the mouseleave event for hoverOut to the holder element
+                o.el.on "mouseleave.#{ pluginName }", ".#{ pluginName.toLowerCase() }-holder", ->
+                    o.dropdownTimer = window.setTimeout( ->
+                        o.dropdownWrapper.trigger "#{ o.hoverOut }.#{ pluginName }"
+                    , 500)
+
+                # bind the mouseenter event for hoverIn to the dropdownWrapper
+                o.dropdownWrapper.on "mouseenter.#{ pluginName }", ->
+                    window.clearTimeout o.dropdownTimer
+
+                # bind the mouseleave event for hoverOut to the dropdownWrapper
+                o.dropdownWrapper.on "mouseleave.#{ pluginName }", ->
+                    current = $ this
+
+                    o.opts.holderAnimation.onBeforeHide(_this) if $.isFunction(o.opts.holderAnimation.onBeforeHide)
+
+                    current
+                        .stop(true, true)
+                        .fadeOut
+                            duration: o.opts.holderAnimation.hideDuration
+                            easing:   o.opts.holderAnimation.hideEasing
+                            complete: ->
+                                o.opts.holderAnimation.onHide(_this) if $.isFunction(o.opts.holderAnimation.onHide)
+
+                                # remove the hover class to the holder element
+                                o.el
+                                    .children(".#{ pluginName.toLowerCase() }-holder")
+                                    .removeClass o.opts.holder.hoverClass
+
+                                o.opts.holderAnimation.onAfterHide(_this) if $.isFunction(o.opts.holderAnimation.onAfterHide)
+

             # bind resize event to the window
             o.browserWindow.on o.resizeKey, ->


Do not attach scroll listeners when `zoomable` is not set

--- src/utils/events.coffee
+++ src/utils/events.coffee
@@ -1,5 +1,5 @@
       getEventDispatcher: () ->
-
+
         events = [
           'focus',
           'hover',
@@ -13,61 +13,40 @@
         zoom.scale(1)
         zoom.translate([0, 0])

-        if options.axes.x.zoomable?
-          svg.selectAll('.x.axis').call(axes.xAxis)
+        this.getZoomHandler(svg, dimensions, axes, data, columnWidth, options, handlers, dispatch, false)()

-        if options.axes.y.zoomable?
-          svg.selectAll('.y.axis').call(axes.yAxis)
+      getZoomHandler: (svg, dimensions, axes, data, columnWidth, options, handlers, dispatch, zoom) ->
+        self = this

-        if options.axes.y2?.zoomable?
-          svg.selectAll('.y2.axis').call(axes.y2Axis)
+        return ->
+          zoomed = false

-        if data.length
-          columnWidth = this.getBestColumnWidth(axes, dimensions, data, options)
-          this.drawData(svg, dimensions, axes, data, columnWidth, options, handlers, dispatch)
+          [ 'x', 'y', 'y2' ].forEach (axis) ->
+            if options.axes[axis]?.zoomable?
+              svg.selectAll(".#{axis}.axis").call(axes["#{axis}Axis"])
+              zoomed = true
+
+          if data.length
+            columnWidth = self.getBestColumnWidth(axes, dimensions, data, options)
+            self.drawData(svg, dimensions, axes, data, columnWidth, options, handlers, dispatch)
+
+          if zoom and zoomed
+            self.createZoomResetIcon(svg, dimensions, axes, data, columnWidth, options, handlers, dispatch, zoom)

       setZoom: (svg, dimensions, axes, data, columnWidth, options, handlers, dispatch) ->
-        self = this
-
-        zoomHandler = () ->
-            zoomed = false
+        zoom = this.getZoomListener(axes, options)

-            if options.axes.x.zoomable?
-              svg.selectAll('.x.axis').call(axes.xAxis)
-              zoomed = true
+        if zoom
+          zoom.on("zoom", this.getZoomHandler(svg, dimensions, axes, data, columnWidth, options, handlers, dispatch, zoom))
+          svg.call(zoom)

-            if options.axes.y.zoomable?
-              svg.selectAll('.y.axis').call(axes.yAxis)
-              zoomed = true
+      getZoomListener: (axes, options) ->
+        zoomable = false
+        zoom = d3.behavior.zoom()

-            if options.axes.y2?.zoomable?
-              svg.selectAll('.y2.axis').call(axes.y2Axis)
-              zoomed = true
+        [ 'x', 'y', 'y2' ].forEach (axis) ->
+          if options.axes[axis]?.zoomable?
+            zoom[axis](axes["#{axis}Scale"])
+            zoomable = true

-            if data.length
-              columnWidth = self.getBestColumnWidth(axes, dimensions, data, options)
-              self.drawData(svg, dimensions, axes, data, columnWidth, options, handlers, dispatch)
-
-            if zoomed
-              self.createZoomResetIcon(svg, dimensions, axes, data, columnWidth, options, handlers, dispatch, zoom)
-
-        zoom = this.getZoomListener(axes, options, zoomHandler)
-        svg.call(zoom)
-
-      getZoomListener: (axes, options, zoomHandler) ->
-
-        zoomListener = d3.behavior.zoom()
-
-        if zoomHandler?
-          zoomListener.on("zoom", zoomHandler)
-
-        if options.axes.x?.zoomable?
-          zoomListener.x(axes.xScale)
-
-        if options.axes.y?.zoomable?
-          zoomListener.y(axes.yScale)
-
-        if options.axes.y2?.zoomable?
-          zoomListener.y(axes.y2Scale)
-
-        return zoomListener+        return if zoomable then zoom else false


use makePathname for generating urls

use makePathname for generating urls

--- tutor/src/components/navbar/user-actions-menu.cjsx
+++ tutor/src/components/navbar/user-actions-menu.cjsx
@@ -1,9 +1,11 @@
 React = require 'react'
 BS = require 'react-bootstrap'
+

 _ = require 'underscore'
 classnames = require 'classnames'

+Router = require '../../router'
 UserName = require './username'
 AccountLink = require './account-link'
 BrowseTheBook = require '../buttons/browse-the-book'
@@ -36,12 +38,13 @@
     @props.onItemClick()

   renderMenuItem: (route, index) ->
-    isActive = true
+    isActive = route.name and Router.isActive(route.name, route.params)
+
     menuGoProps = if route.href
       href: route.href
       onSelect: @props.onItemClick
     else
-      href: @context.router.createHref(route.name, route.params)
+      href: Router.makePathname(route.name, route.params)
       onSelect: (event) =>
         @transitionToMenuItem(route.name, route.params, event)
         @props.onItemClick()
@@ -55,7 +58,8 @@
       className={classnames(route.name, 'active': isActive)}
       key={key}
       data-name={route.name}
-      eventKey={index + 2}>
+      eventKey={index + 2}
+    >
         {route.label}
     </BS.MenuItem>


SocialWorker: JGroupRole: replace dash with async.parallel

--- workers/social/lib/social/models/group/role.coffee
+++ workers/social/lib/social/models/group/role.coffee
@@ -1,8 +1,8 @@
+async      = require 'async'
 { Module } = require 'jraphical'

 module.exports = class JGroupRole extends Module

-  { dash }        = require 'bongo'
   KodingError   = require '../../error'

   @set
@@ -48,10 +48,10 @@
   createDefaultRolesHelper = (callback) ->

     queue = JGroupRole.defaultRoles.map (roleData) ->
-      ->
-        JGroupRole.create roleData, queue.fin.bind queue
+      (fin) ->
+        JGroupRole.create roleData, fin

-    dash queue, callback
+    async.parallel queue, callback

   @createDefaultRoles = (callback) ->


SocialWorker: refactor fetchInvitationByData

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -420,17 +420,14 @@
       callback null, invitation

-  fetchInvitationByData = (options, queue, callback, fetchData) ->
-
-    { user, groupName, invitationToken } = options
-    # check if user has pending invitation
-    return queue.next()  if invitationToken
+  fetchInvitationByData = (options, callback) ->
+
+    { user, groupName } = options

     selector = { email: user.email, groupName }
     JInvitation = require '../invitation'
     JInvitation.one selector, {}, (err, invitation) ->
-      fetchData invitation  if invitation
-      queue.next()
+      callback err, invitation

   validateLogin = (options, callback) ->
@@ -514,9 +511,13 @@
           queue.next()

       ->
-        fetchInvitationByData {
-          user, groupName, invitationToken
-        }, queue, callback, (invitation_) -> invitation = invitation_
+        # check if user has pending invitation
+        return queue.next()  if invitationToken
+
+        fetchInvitationByData { user, groupName }, (err, invitation_) ->
+          return callback err  if err
+          invitation = invitation_
+          queue.next()

       =>
         @addToGroupByInvitation {


Fix destruction of page names that end in 'index.html' e.g. 'pageindex.html'

--- src/plugins/page.coffee
+++ src/plugins/page.coffee
@@ -98,7 +98,7 @@

     getUrl: (base) ->
       # remove index.html for prettier links
-      super(base).replace /index\.html$/, ''
+      super(base).replace /([\/^])index\.html$/, '$1'

     getView: ->
       @metadata.view or 'template'


Remove color field

Remove color field

--- app/models/file.coffee
+++ app/models/file.coffee
@@ -8,6 +8,5 @@
   md5hash: DS.attr 'string'
   sha1hash: DS.attr 'string'
   name: DS.attr 'string'
-  color: DS.attr 'string'

 `export default File;`


Device shutting down string

Device shutting down string

--- app/models/file.coffee
+++ app/models/file.coffee
@@ -33,6 +33,7 @@
   tDeviceInstalling: t("deviceInstalling")
   tDeviceLaunching: t("deviceLaunching")
   tDeviceHooking: t("deviceHooking")
+  tDeviceShuttingDown: t("deviceShuttingDown")

   analysesSorting: ['risk:desc']
   sortedAnalyses: Ember.computed.sort 'analyses', 'analysesSorting'


26th Commit

26th Commit

--- protractor.conf.coffee
+++ protractor.conf.coffee
@@ -8,7 +8,7 @@
   @onPrepare: ->
     SpecReporter = require 'jasmine-spec-reporter'
     jasmine.getEnv().addReporter new SpecReporter displayStacktrace: true
-    browser.ignoreSynchronization = false
+    browser.ignoreSynchronization = true
   @framework: 'jasmine2'
   @jasmineNodeOpts:
     isVerbose: false


WO timing error

WO timing error

--- src/glitchjs-ui/GameOfLifeControl.coffee
+++ src/glitchjs-ui/GameOfLifeControl.coffee
@@ -28,7 +28,7 @@

         for seqIdx in [0...6]
           sequencer = @_mainController._gameOfLife.sequencerAt seqIdx
-          if sequencer.isCellActive(col,row)
+          if sequencer?.isCellActive(col,row)
             context.fillStyle = @_colors[seqIdx]

         context.fillRect(@_squareSide * col + @_padding * (col - 1),


Update display buffer spec

Update display buffer spec

--- spec/app/display-buffer-spec.coffee
+++ spec/app/display-buffer-spec.coffee
@@ -105,8 +105,8 @@

           expect(changeHandler).toHaveBeenCalled()
           [event] = changeHandler.argsForCall[0]
-          expect(event.oldRange).toEqual([[3, 0], [11, 45]])
-          expect(event.newRange).toEqual([[3, 0], [5, 45]])
+          expect(event.oldRange).toEqual([[3, 0], [9, 5]])
+          expect(event.newRange).toEqual([[3, 0], [3, 22]])
           expect(event.lineNumbersChanged).toBeTruthy()

     describe "position translation", ->


Add tests on edit-line :tea:

Add tests on edit-line :tea:

--- spec/commands/edit-line-spec.coffee
+++ spec/commands/edit-line-spec.coffee
@@ -316,6 +316,18 @@

       editLine.trigger(event)
       expect(editor.getText()).toBe("  - list")
+
+    it "indent line if it is an unordered list (ulBullet config)", ->
+      atom.config.set("markdown-writer.templateVariables.ulBullet1", "*")
+      atom.config.set("markdown-writer.templateVariables.ulBullet2", "+")
+
+      editor.setText "- list"
+      editor.setCursorBufferPosition([0, 5])
+
+      editLine.trigger(event)
+      expect(editor.getText()).toBe("  * list")
+      editLine.trigger(event)
+      expect(editor.getText()).toBe("    + list")

     it "indent line if it is an ordered list", ->
       editor.setText "3. list"
@@ -367,6 +379,17 @@
       editLine.trigger(event)
       expect(editor.getText()).toBe("- list")
       expect(editor.getCursorBufferPosition().toString()).toBe("(0, 3)")
+
+    it "undent line if it is an unordered list (ulBullet config)", ->
+      atom.config.set("markdown-writer.templateVariables.ulBullet1", "*")
+
+      editor.setText "    + list"
+      editor.setCursorBufferPosition([0, 5])
+
+      editLine.trigger(event)
+      expect(editor.getText()).toBe("  * list")
+      editLine.trigger(event)
+      expect(editor.getText()).toBe("- list")

     it "undent line if it is an ordered list", ->
       editor.setText "    3. list"


Keeping realtime events up to date

Keeping realtime events up to date

--- realtime.coffee
+++ realtime.coffee
@@ -11,18 +11,26 @@
   ['user:deleted', userDeleted]
   ['competition:started', competitionStarted]
   ['competition:ended', competitionEnded]
+  ['vote:cast', voteCast]
+  ['vote:withdrawn', voteWithdrawn]
 ]

 userCreated = (user) ->
-  console.log "[REALTIME] user created"
+  console.log "[REALTIME] user created (userId=#{user._id})"

 userDeleted = (user) ->
-  console.log "[REALTIME] user deleted"
+  console.log "[REALTIME] user deleted (userId=#{user._id})"

 competitionStarted = (competition) ->
-  console.log "[REALTIME] competition started"
+  console.log "[REALTIME] competition started (competitionId=#{competition._id})"

 competitionEnded = (competition) ->
-  console.log "[REALTIME] competition ended"
+  console.log "[REALTIME] competition ended (competitionId=#{competition._id})"
+
+voteCast = (vote) ->
+  console.log "[REALTIME] vote cast (userId=#{vote.user_id}, choiceId=#{vote.choice_id})"
+
+voteWithdrawn = (vote) ->
+  console.log "[REALTIME] vote withdrawn (userId=#{vote.user_id}, choiceId=#{vote.choice_id})"

 module.exports = {init}


display warning if flubr env vars are not set

--- src/flubr.coffee
+++ src/flubr.coffee
@@ -28,6 +28,9 @@

 module.exports = (robot) ->

+  if not process.env.HUBOT_FLUBR_URL or not process.env.HUBOT_FLUBR_PASS or not process.env.HUBOT_FLUBR_FAIL
+    robot.logger.warning 'Please set the HUBOT_FLUBR_URL, HUBOT_FLUBR_PASS and HUBOT_FLUBR_FAIL environment variables'
+
   robot.hear PASS, (msg) ->
     getImage msg, 'pass'


KodingKontrol: VM region fetching implemented

--- client/Main/kite/kodingkontrol.coffee
+++ client/Main/kite/kodingkontrol.coffee
@@ -4,6 +4,7 @@
     super @getAuthOptions()

     @kites = {}
+    @regions = {}

   getAuthOptions: ->
     autoConnect : no
@@ -48,9 +49,25 @@
     return (kite = @getCachedKite name, correlationName)?

   fetchRegion: (correlationName, region) ->
+
     if region
-    then Promise.resolve region
-    else Promise.reject new Error "TODO: implement vm region fetching"
+      Promise.resolve region
+
+    else
+
+      if region = @regions[correlationName]
+        return Promise.resolve region
+
+      KD.remote.api.JVM.fetchVmRegion correlationName, (err, region)=>
+
+        if err or not region
+          warn err  if err
+          region = 'sj'
+
+        @regions[correlationName] = region
+
+        Promise.resolve region
+

   getWhoParams: (kiteName, correlationName) ->
     if kiteName in ['oskite', 'terminal']


client: var name fix (this is why you use search and replace)

--- client/Social/Activity/AppView.coffee
+++ client/Social/Activity/AppView.coffee
@@ -61,7 +61,7 @@
     @addSubView @header
     @addSubView @innerNav
     @addSubView @leftBlock
-    @addSubView @rightCorner
+    @addSubView @rightBlock

     @leftBlock.addSubView @widget
     @leftBlock.addSubView @feedWrapper


Terminal: resize handlers removed

Terminal: resize handlers removed

--- client/Terminal/webtermappview.coffee
+++ client/Terminal/webtermappview.coffee
@@ -3,8 +3,6 @@
   constructor: (options = {}, data) ->

     super options, data
-
-    @listenWindowResize()

     @tabHandleContainer = new ApplicationTabHandleHolder
       delegate: this
@@ -107,15 +105,9 @@

     @addSubView new ChromeTerminalBanner

-  _windowDidResize:->
-    # 10px being the application page's padding
-    @tabView.setHeight @getHeight() - @tabHandleContainer.getHeight() - 10
-
   viewAppended: ->
     super
     @addNewTab()
-    @utils.defer =>
-      @_windowDidResize()

   addNewTab: ->
     webTermView = new WebTermView


shortcuts: type-check apps to make sure we are not dealing with a non-appcontroller :koala:

--- client/app/lib/shortcuts/index.coffee
+++ client/app/lib/shortcuts/index.coffee
@@ -6,6 +6,7 @@
 globals        = require 'globals'
 ShortcutsModal = require './views/modal'
 cloneArray     = require 'app/util/cloneArray'
+AppController  = require 'app/appcontroller'

 STORAGE_NAME    = 'shortcuts'
 STORAGE_VERSION = '4'
@@ -165,16 +166,16 @@
   #
   _handleFrontAppChange: (app, prevApp) ->

-    appId     = app.canonicalName
+    appId     = app?.canonicalName
     prevAppId = prevApp?.canonicalName

     return  if appId is prevAppId

-    if prevApp and _.isArray(sets = prevApp.getConfig().shortcuts)
+    if prevApp instanceof AppController and _.isArray(sets = prevApp.getConfig().shortcuts)
       for key in sets
         @shortcuts.removeListener "key:#{key}", prevApp.bound 'handleShortcut'

-    if _.isArray(sets = app.getConfig().shortcuts)
+    if app instanceof AppController and _.isArray(sets = app.getConfig().shortcuts)
       for key in sets
         @shortcuts.on "key:#{key}", app.bound 'handleShortcut'


Don't require pubsub when listening for net events

--- client/lanes/react/mixins/Data.coffee
+++ client/lanes/react/mixins/Data.coffee
@@ -28,13 +28,13 @@

             this.bindEvents(name, state, customEvents[name]) if state

-            if Lanes.u.isModel(state) and not ( false == @component.pubsub or false == @component?.pubsub?[name] )
-                if !prevState? or prevState.getId() != state.getId()
-                    Lanes.Models.PubSub.remove(prevState) if prevState
-                    Lanes.Models.PubSub.add(state)
-
-                @listenTo(state, 'remote-update', @onPubSubChangeSet)
+            if Lanes.u.isModel(state)
                 @listenToNetworkEvents(state) if @component.listenNetworkEvents
+                unless false == @component.pubsub or false == @component?.pubsub?[name]
+                    if !prevState? or prevState.getId() != state.getId()
+                        Lanes.Models.PubSub.remove(prevState) if prevState
+                        Lanes.Models.PubSub.add(state)
+                    @listenTo(state, 'remote-update', @onPubSubChangeSet)
         this.setComponentState({}) unless _.isEmpty(objects) or options.silent

     listenToNetworkEvents: (state) ->


cleanup

cleanup

--- src/login.coffee
+++ src/login.coffee
@@ -16,14 +16,14 @@
         console.log 'login: did-finish-load', url

         if url.indexOf('/o/oauth2/programmatic_auth') > 0
-          console.log 'login: programmatic auth'
-          # get the cookie from browser session, it has to be there
-          session.defaultSession.cookies.get {}, (err, values=[]) ->
-            oauth_code = false
-            for value in values
-              if value.name is 'oauth_code'
-                oauth_code = value.value
-            rs(oauth_code) if oauth_code
+            console.log 'login: programmatic auth'
+            # get the cookie from browser session, it has to be there
+            session.defaultSession.cookies.get {}, (err, values=[]) ->
+                oauth_code = false
+                for value in values
+                    if value.name is 'oauth_code'
+                        oauth_code = value.value
+                rs(oauth_code) if oauth_code

     # redirect to google oauth
     mainWindow.loadURL LOGIN_URL


Remove unused require

Remove unused require

--- src/login.coffee
+++ src/login.coffee
@@ -2,7 +2,6 @@
 optimist = require 'optimist'
 Q = require 'q'
 read = require 'read'
-request = require 'request'
 open = require 'open'

 auth = require './auth'


suffix and prefix length no longer hard coded

--- web/scripts/playerCodeEditor.coffee
+++ web/scripts/playerCodeEditor.coffee
@@ -31,6 +31,8 @@
                 public static void main(String[] args) {\n
                 """
         codeSuffix = '    }\n}'
+        @codePrefixLength = codePrefix.split('\n').length - 1
+        @codeSuffixLength = codeSuffix.split('\n').length
         unless codeText.startsWith codePrefix
             tab = @editSession.getTabString()
             tab += tab
@@ -306,12 +308,11 @@
                 button.attr 'disabled', true
             else
                 button.attr 'disabled', false
-
             button.text "#{line}: #{usesRemaining}"
         return

     switchUp: ({currentRow, currentColumn}) ->
-        if @editor.getReadOnly() or currentRow - 1 <= 3
+        if @editor.getReadOnly() or currentRow - 1 < @codePrefixLength
             return
         if currentRow > 0
             @editSession.moveLinesUp(currentRow, currentRow)
@@ -320,7 +321,7 @@

     switchDown: ({currentRow, currentColumn}) ->
         maxRow = @editSession.getLength()
-        if @editor.getReadOnly() or currentRow + 1 >= maxRow - 2
+        if @editor.getReadOnly() or currentRow + 1 >= maxRow - @codeSuffixLength
             return
         if currentRow < maxRow - 1
             @editSession.moveLinesDown(currentRow, currentRow)
@@ -329,7 +330,7 @@

     deleteLine: ({text, currentRow}) ->
         maxRow = @editSession.getLength()
-        if @editor.getReadOnly() or currentRow >= maxRow - 2
+        if @editor.getReadOnly() or currentRow >= maxRow - @codeSuffixLength
             return
         line = text.getLine currentRow
         if text.getLength() == 1
@@ -340,7 +341,7 @@

     insertLine: ({text, line, currentRow}) ->
         maxRow = @editSession.getLength()
-        if currentRow + 1 <= 3 or currentRow + 1 >= maxRow - 2
+        if currentRow + 1 <= 3 or currentRow + 1 >= maxRow - @codeSuffixLength
             return
         inputsDiv = jQuery('#insertButtons')


Talk links are go.

Talk links are go.

--- src/scripts/site.coffee
+++ src/scripts/site.coffee
@@ -78,11 +78,3 @@

     homeScoreboard: new Scoreboard
       el: '[data-page="home"] .scoreboard'
-
-  $ = require 'jQuery'
-  $(document).on 'click', "a[href^='#{config.talkHost}']", (e) ->
-    e.preventDefault()
-    alert '''
-      BETA NOTE
-      Talk for Seafloor Explorer is coming soon!
-    '''


add analytics event to authentication

add analytics event to authentication

--- coffee/controllers/accountCtrl.coffee
+++ coffee/controllers/accountCtrl.coffee
@@ -1,4 +1,4 @@
-timeTracker.controller 'AccountCtrl', ($scope, $modal, Redmine, Account, Project, Ticket, Message, State, Resource) ->
+timeTracker.controller 'AccountCtrl', ($scope, $modal, Redmine, Account, Project, Ticket, Message, State, Resource, Analytics) ->

   ID_PASS = 'id_pass'

@@ -84,6 +84,7 @@
         if result
           $scope.isSaving = $scope.state.isAdding = false
           Message.toast Resource.string("msgAuthSuccess"), 3000
+          Analytics.sendEvent 'internal', 'auth', 'success'
           loadProject msg.account
         else
           failAuthentication null
@@ -97,6 +98,7 @@
   failAuthentication = (msg) ->
     $scope.isSaving = false
     Message.toast Resource.string("msgAuthFail"), 3000
+    Analytics.sendEvent 'internal', 'auth', 'fail'

   ###


DropdownView: render must return @

DropdownView: render must return @

--- coffee/src/views/dropdown_view.coffee
+++ coffee/src/views/dropdown_view.coffee
@@ -44,3 +44,4 @@
     @$el.html @template
       selected: (@options.selected ? items[0])
       items: items
+    @


* [bug] raise error if no .contributors configuration file + the output fields limits added

--- src/index.coffee
+++ src/index.coffee
@@ -56,7 +56,7 @@
 module.exports = (options, done)->
   options = options || {}
   dirname = options.dirname
-  usersCache = options.users
+  usersCache = options.users || {}
   fields  = options.fields
   ixEmails = genEmailIndex usersCache
   needUpdateCache = false
@@ -106,4 +106,10 @@
       .then ->users
     else
       users
+  .then (users)->
+    if _.isArray(fields)
+      users.forEach (user)->
+        for k,v of user
+          delete user[k] if not (k in fields)
+    users
   .nodeify(done)


Iterating through objects

Iterating through objects

--- src/index.coffee
+++ src/index.coffee
@@ -43,7 +43,6 @@
     .wrap(null)
     .version((() -> require('../package').version))

-
 # Parse command line args
 argv = yargs.parse process.argv
 console.dir argv
@@ -57,7 +56,7 @@
   exit 1

 # Make sure this is the root of a git repo
-unless ls('.git').indexOf('objects') isnt -1
+unless 'objects' in ls('.git')
   console.error 'Not a valid git repo!'
   exit 1

@@ -79,10 +78,16 @@
 db = new mongo.Db argv.db, server, {w:1}
 db.open (err) ->
   console.error "Couldn't open database connection, #{err}" if err
-  console.log "Connected to mongo!", db
+  console.log "Connected to mongo!"
+
+  for dir in ls '.git/objects/*/' when dir.length is 15
+    console.log "Dir", dir
+    for obj in ls dir # when obj.length is 15
+      console.log "Obj: #{dir}/#{obj}"
+
   db.close (err) ->
     console.error "Couldn't close database connection, #{err}" if err
-    console.log "Disconnected from mongo!", db
+    console.log "Disconnected from mongo!"

 copyObjects = () ->
   console.log "Copying Objects!"


style

style

--- src/index.coffee
+++ src/index.coffee
@@ -6,7 +6,7 @@
 snake_case = require "snake-case"
 is_primitive = require "is-primitive"

-cases = (s) -> [camel_case(s), snake_case(s)]
+cases = (s) -> [(camel_case s), snake_case s]

 handler =
   has: (target, prop) ->


* [bug] it should not process files if error occur and not force to continue.

--- src/index.coffee
+++ src/index.coffee
@@ -69,10 +69,14 @@
         continue unless file.filter file
         file.loadSync(read:true) unless file.loaded()
         task = @processSync(file)
+        if !aFile.force and task.error
+          result.error = true
+          break
         task
     else if aFile and aFile.tasks
       result = file:aFile
       result.result = tasks.executeSync aFile
+      result.error = tasks.lastError if tasks.lastError
     result
   _executeSync: (aOptions)->
     cwd = aOptions.cwd


testing

testing

--- src/index.coffee
+++ src/index.coffee
@@ -10,7 +10,7 @@
 class Connector extends EventEmitter
   constructor: ->
     @limitMinutes = 2
-    @validUntil = moment().utc().add(@limitMinutes, 'minute').toISOString()
+    @validUntil = moment().utc().add(@limitMinutes, 'minute')
     @board = new five.Board {io: new Raspi(),repl: false,debug: false}
     @board.on 'ready', @handleReady

@@ -33,9 +33,10 @@
     if currentMeeting?
       console.log "====================================="
       console.log "curentMeeting found : ", currentMeeting
+      console.log "validUntil as of now is : ", @validUntil.toISOString()
       meetingStartTime = _.get event, 'genisys.currentMeeting.startTime'
-      noShowLimit = moment(meetingStartTime).utc().add(@limitMinutes, 'minute').toISOString()
-      if (moment().isBefore(moment(noShowLimit)) && moment(@validUntil).isBefore(moment()))
+      noShowLimit = moment(meetingStartTime).utc().add(@limitMinutes, 'minutes')
+      if (moment().isBefore(noShowLimit) && moment(@validUntil).isBefore(moment()))
         @validUntil = noShowLimit
         console.log "Initializing valid until to no show limit as : #{@validUntil.toISOString()}"


Clean up version.suffix for master version

--- src/index.coffee
+++ src/index.coffee
@@ -114,6 +114,7 @@

 Image.prototype.upload = (version, cb) ->
   if version.original and @config.keepOriginal is false
+    delete version.suffix
     return process.nextTick -> cb null, version

   options =


Change fixpath to work server root relative path.

--- src/index.coffee
+++ src/index.coffee
@@ -71,7 +71,7 @@
   _.each {src: 'script[src]', href: 'link[href]'}, (v, k) ->
     $(v).each (script) ->
       src = $(this).attr(k)
-      unless absolutePathRegex.test(src)
+      unless src[0] == '/' || absolutePathRegex.test(src)
         filepath = getPath src, locale, options
         $(this).attr(k, filepath)


update game api server

update game api server

--- app/js/services/game_api.coffee
+++ app/js/services/game_api.coffee
@@ -6,103 +6,81 @@
     #@log.info "---- Network API Constructor ----"

-  # Attempts add or remove <node> from the peer list or try a connection to <node> once
+  # Exchange lto for chips
   # parameters:
-  #   string `node` - The node (see network_get_peer_info for nodes), example: 192.168.1.5:5678
-  #   string `command` - 'add' to add a node to the list, 'remove' to remove a node from the list, 'onetry' to try a connection to the node once, example: add
-  # return_type: `void`
-  network_add_node: (node, command, error_handler = null) ->
-    @rpc.request('network_add_node', [node, command], error_handler).then (response) ->
+  #   account_name `from_account_name` - the account that will provide funds for the bid
+  #   string `quantity` - the quantity of items you would like to buy
+  #   asset_symbol `quantity_symbol` - the type of chips you would like to buy
+  # return_type: `transaction_record`
+  buy_chips: (from_account_name, quantity, quantity_symbol, error_handler = null) ->
+    @rpc.request('game_buy_chips', [from_account_name, quantity, quantity_symbol], error_handler).then (response) ->
       response.result

-  # Returns the number of fully-established connections to other nodes
+  # Creates a new game and binding to an asset
   # parameters:
-  # return_type: `uint32_t`
-  network_get_connection_count: (error_handler = null) ->
-    @rpc.request('network_get_connection_count', error_handler).then (response) ->
+  #   string `game_name` - the name of the game
+  #   string `owner_name` - the name of the owner of the game
+  #   string `script_url` - the url of the rule script for this game
+  #   string `script_hash` - the hash of the rule script
+  #   string `description` - a description of the asset
+  #   json_variant `public_data` - arbitrary data attached to the asset
+  # return_type: `transaction_record`
+  create: (game_name, owner_name, script_url, script_hash, description, public_data, error_handler = null) ->
+    @rpc.request('game_create', [game_name, owner_name, script_url, script_hash, description, public_data], error_handler).then (response) ->
       response.result

-  # Returns data about each connected node
+  # Update a exist game
   # parameters:
-  #   bool `not_firewalled` - true to output only peers not behind a firewall and false otherwise
-  # return_type: `json_object_array`
-  network_get_peer_info: (not_firewalled, error_handler = null) ->
-    @rpc.request('network_get_peer_info', [not_firewalled], error_handler).then (response) ->
+  #   string `paying_account` - the name of the paying account
+  #   string `game_name` - the name of the game
+  #   string `script_url` - the url of the rule script for this game
+  #   string `script_hash` - the hash of the rule script
+  #   string `description` - a description of the asset
+  #   json_variant `public_data` - arbitrary data attached to the asset
+  # return_type: `transaction_record`
+  update: (paying_account, game_name, script_url, script_hash, description, public_data, error_handler = null) ->
+    @rpc.request('game_update', [paying_account, game_name, script_url, script_hash, description, public_data], error_handler).then (response) ->
       response.result

-  # Broadcast a previously-created signed transaction to the network
+  # Play game with param variant
   # parameters:
-  #   signed_transaction `transaction_to_broadcast` - The transaction to broadcast to the network
-  # return_type: `transaction_id`
-  network_broadcast_transaction: (transaction_to_broadcast, error_handler = null) ->
-    @rpc.request('network_broadcast_transaction', [transaction_to_broadcast], error_handler).then (response) ->
+  #   string `game_name` - the name of the game
+  #   json_variant `param` - the param of the game action
+  # return_type: `transaction_record`
+  play: (game_name, param, error_handler = null) ->
+    @rpc.request('game_play', [game_name, param], error_handler).then (response) ->
       response.result

-  # Sets advanced node parameters, used for setting up automated tests
+  # Returns stored game datas starting with a given game name upto a the limit provided
   # parameters:
-  #   json_object `params` - A JSON object containing the name/value pairs for the parameters to set
-  # return_type: `void`
-  network_set_advanced_node_parameters: (params, error_handler = null) ->
-    @rpc.request('network_set_advanced_node_parameters', [params], error_handler).then (response) ->
+  #   account_name `game_name` - the game name to include
+  #   uint32_t `limit` - the maximum number of items to list
+  # return_type: `game_data_record_array`
+  list_datas: (game_name, limit, error_handler = null) ->
+    @rpc.request('game_list_datas', [game_name, limit], error_handler).then (response) ->
       response.result

-  # Sets advanced node parameters, used for setting up automated tests
+  # Returns the status of a particular game, including any trading errors.
   # parameters:
-  # return_type: `json_object`
-  network_get_advanced_node_parameters: (error_handler = null) ->
-    @rpc.request('network_get_advanced_node_parameters', error_handler).then (response) ->
+  #   account_name `game_name` - the game name to include
+  # return_type: `game_status`
+  status: (game_name, error_handler = null) ->
+    @rpc.request('game_status', [game_name], error_handler).then (response) ->
       response.result

-  # Returns the time the transaction was first seen by this client
+  # Returns a list of active game statuses
   # parameters:
-  #   transaction_id `transaction_id` - the id of the transaction
-  # return_type: `message_propagation_data`
-  network_get_transaction_propagation_data: (transaction_id, error_handler = null) ->
-    @rpc.request('network_get_transaction_propagation_data', [transaction_id], error_handler).then (response) ->
+  # return_type: `game_status_array`
+  list_status: (error_handler = null) ->
+    @rpc.request('game_list_status', error_handler).then (response) ->
       response.result

-  # Returns the time the block was first seen by this client
+  # Returns a list of game result transactions executed on a given block.
   # parameters:
-  #   block_id_type `block_hash` - the id of the block
-  # return_type: `message_propagation_data`
-  network_get_block_propagation_data: (block_hash, error_handler = null) ->
-    @rpc.request('network_get_block_propagation_data', [block_hash], error_handler).then (response) ->
-      response.result
-
-  # Sets the list of peers this node is allowed to connect to
-  # parameters:
-  #   node_id_list `allowed_peers` - the list of allowable peers
-  # return_type: `void`
-  network_set_allowed_peers: (allowed_peers, error_handler = null) ->
-    @rpc.request('network_set_allowed_peers', [allowed_peers], error_handler).then (response) ->
-      response.result
-
-  # Returns assorted information about the network settings and connections
-  # parameters:
-  # return_type: `json_object`
-  network_get_info: (error_handler = null) ->
-    @rpc.request('network_get_info', error_handler).then (response) ->
-      response.result
-
-  # Returns list of potential peers
-  # parameters:
-  # return_type: `potential_peer_record_array`
-  network_list_potential_peers: (error_handler = null) ->
-    @rpc.request('network_list_potential_peers', error_handler).then (response) ->
-      response.result
-
-  # Get information on UPNP status including whether it's enabled and what the client believes its IP to be
-  # parameters:
-  # return_type: `json_object`
-  network_get_upnp_info: (error_handler = null) ->
-    @rpc.request('network_get_upnp_info', error_handler).then (response) ->
-      response.result
-
-  # Get bandwidth usage stats
-  # parameters:
-  # return_type: `json_object`
-  network_get_usage_stats: (error_handler = null) ->
-    @rpc.request('network_get_usage_stats', error_handler).then (response) ->
+  #   uint32_t `block_number` - Block to get game result transaction operations for.
+  # return_type: `game_result_transaction_array`
+  list_result_transactions: (block_number, error_handler = null) ->
+    @rpc.request('game_list_result_transactions', [block_number], error_handler).then (response) ->
       response.result


Minor copy edits

Minor copy edits

--- app/pages/about/about-home.cjsx
+++ app/pages/about/about-home.cjsx
@@ -8,57 +8,31 @@
    pageContent: '''
     ## What is the Zooniverse?

-    The Zooniverse is the world's largest and most popular online platform for
-    people-powered research projects. This research is being conducted in part by
-    volunteers—hundreds of thousands of people around the world who come together to
-    assist professional researchers with their data. Our goal is to enable research
-    that would not be possible (or practical) otherwise. People-powered research results
-    in new discoveries, datasets useful to the wider research community, and [many publications](./#/about/publications).
+    The Zooniverse is the world’s largest and most popular platform for people-powered research. This research is      made possible by volunteers—hundreds of thousands of people around the world who come together to assist           professional researchers. Our goal is to enable research that would not be possible, or practical, otherwise.      Zooniverse research results in new discoveries, datasets useful to the wider research community, and [many         publications](./#/about/publications).

-    ### At the Zooniverse, anyone can be a researcher online.
+    ### At the Zooniverse, anyone can be a researcher

-    You don't need any specialised background, training, or expertise to participate in any
-    Zooniverse projects. We make it easy for anyone to contribute to real academic research,
-    on their own computer, at their own convenience.
+    You don’t need any specialised background, training, or expertise to participate in any Zooniverse projects. We make it easy for anyone to contribute to real academic research, on their own computer, at their own convenience.

-    You'll be able to study authentic objects of interest gathered by researchers,
-    like images of faraway galaxies, historical records and diaries, or videos of animals
-    in their natural habitats. By answering simple questions about them, you'll help contribute
-    to our understanding of our world, our history, our universe, and more.
+You’ll be able to study authentic objects of interest gathered by researchers, like images of faraway galaxies, historical records and diaries, or videos of animals in their natural habitats. By answering simple questions about them, you’ll help contribute to our understanding of our world, our history, our Universe, and more.

-    And with our wide-ranging and ever-expanding suite of projects, covering many disciplines
-    and topics across the sciences and humanities, there's a place for anyone and everyone to
-    explore, learn and have fun in the Zooniverse!
+With our wide-ranging and ever-expanding suite of projects, covering many disciplines and topics across the sciences and humanities, there's a place for anyone and everyone to explore, learn and have fun in the Zooniverse.

-    ### We accelerate important research by working together.
+    ### We accelerate important research by working together

-    The major challenge of 21st century research is dealing with the flood of information we can
-    now collect about the world around us. Computers can help, but in many fields the human ability
-    for pattern recognition—and our ability to be surprised—is still superior. With the help of
-    Zooniverse volunteers, researchers can analyze that information more quickly and accurately
-    than would otherwise be possible, saving time and resources, advancing the ability of computers
-    to do the same tasks, and leading to faster research output.
+    The major challenge of 21st century research is dealing with the flood of information we can now collect about the world around us. Computers can help, but in many fields the human ability for pattern recognition—and our ability to be surprised—makes us superior. With the help of Zooniverse volunteers, researchers can analyze their information more quickly and accurately than would otherwise be possible, saving time and resources, advancing the ability of computers to do the same tasks, and leading to faster progress and understanding of the world, getting to exciting results more quickly.

-    Our projects apply the philosophy of the "wisdom of crowds." Combining the classifications of
-    many individual users produces accurate and usable data with a measurable estimate of
-    error—exactly what's needed to make progress in these fields of research.
+Our projects apply the philosophy of the “wisdom of crowds.” Combining the classifications of many individual users produces accurate and usable data with a measurable estimate of error—exactly what’s needed to make progress in these fields of research.

-    ### Volunteers and professionals make real discoveries together.
+    ### Volunteers and professionals make real discoveries together

     Zooniverse projects are constructed with the aim of converting volunteers' efforts into
     measurable results. These projects have produced a large number of [published research papers](./#/about/publications),
-    as well as several open-source sets of analyzed data. In some cases, Zooniverse volunteers have
-    even made completely new and scientifically significant discoveries!
+    as well as several open-source sets of analyzed data. In some cases, Zooniverse volunteers have even made completely unexpected and scientifically significant discoveries.

-    A significant amount of this research takes place on the Zooniverse discussion boards,
-    where volunteers can work together with each other and with the research teams. These boards
-    are integrated with each project's data to allow for everything from quick hashtagging to in-depth
-    collaborative analysis. There is also a central Zooniverse board for general chat and discussion
-    about Zooniverse-wide matters.
+A significant amount of this research takes place on the Zooniverse discussion boards, where volunteers can work together with each other and with the research teams. These boards are integrated with each project to allow for everything from quick hashtagging to in-depth collaborative analysis. There is also a central Zooniverse board for general chat and discussion about Zooniverse-wide matters.

-    Many of the most interesting discoveries from Zooniverse projects
-    have come from discussion between volunteers and researchers.
-    We encourage all users to join the conversation on the discussion boards for more in-depth participation.'''
+Many of the most interesting discoveries from Zooniverse projects have come from discussion between volunteers and researchers. We encourage all users to join the conversation on the discussion boards for more in-depth participation.'''

 module.exports = React.createClass
   displayName: 'AboutHome'
@@ -70,4 +44,4 @@
     document.documentElement.classList.remove 'on-secondary-page'

   render: ->
-    <Markdown>{counterpart "aboutHome.pageContent"}</Markdown>+    <Markdown>{counterpart "aboutHome.pageContent"}</Markdown>


add in subscript and superscript into Markdown help

add in subscript and superscript into Talk Markdown help

--- app/partials/markdown-help.cjsx
+++ app/partials/markdown-help.cjsx
@@ -84,6 +84,28 @@
             **_bolditalics_**
             <Markdown>**_bolditalics_**</Markdown>

+

+            Superscript
+
+               ^superscript^
+                ^super\ script^
+
+
+              <Markdown>^superscript^</Markdown>
+              <Markdown>^super\ script^</Markdown>
+
+

+

+            Subscript
+
+               ~subscript~
+                ~sub\ script~
+
+
+              <Markdown>^subscript^</Markdown>
+              <Markdown>~sub\ script~</Markdown>
+
+

             Hyperlink
             [zooniverse](http://www.zooniverse.org)


Atom linter-pep257 was renamed to linter-pydocstyle

--- atom/.atom/config.cson
+++ atom/.atom/config.cson
@@ -23,7 +23,7 @@
       "E128"
       "E501"
     ]
-  "linter-pep257":
+  "linter-pydocstyle":
     ignoreCodes: "D100,D101,D102,D103,D104,D105"
   welcome:
     showOnStartup: false


Using connection:keep-alive and connection:close headers in response

--- acs.coffee
+++ acs.coffee
@@ -28,6 +28,12 @@

 writeResponse = (currentRequest, res) ->
+  if res.headers['Content-Length'] == 0
+    # no more requests. terminate TCP connection
+    res.headers['Connection'] = 'close' if currentRequest.httpRequest.httpVersion == '1.1'
+  else
+    res.headers['Connection'] = 'Keep-Alive' if currentRequest.httpRequest.httpVersion == '1.0'
+
   if config.DEBUG_DEVICES[currentRequest.deviceId]
     dump = "# RESPONSE #{new Date(Date.now())}\n" + JSON.stringify(res.headers) + "\n#{res.data}\n\n"
     fs = require('fs').appendFile("debug/#{currentRequest.deviceId}.dump", dump, (err) ->


SliderComponent: Account for min ranges > 0, touchDown returns correct position in slider regardless of range, scale, offset

--- framer/Components/SliderComponent.coffee
+++ framer/Components/SliderComponent.coffee
@@ -71,7 +71,9 @@
 	_touchDown: (event) =>
 		event.preventDefault()
 		event.stopPropagation()
-		@value = @valueForPoint(event.x - @screenScaledFrame().x) / @canvasScaleX() - @min
+
+		if @min > 0 then offsetX = (@min / @canvasScaleX()) - @min else offsetX = 0
+		@value = @valueForPoint(event.x - @screenScaledFrame().x) / @canvasScaleX() - offsetX
 		@knob.draggable._touchStart(event)

 	_updateFill: =>


cosmetics

cosmetics

--- app/assets/javascripts/application/issues.js.coffee
+++ app/assets/javascripts/application/issues.js.coffee
@@ -29,6 +29,7 @@
         $(@).siblings('.form-wrapper').show()
         preventDefaultAndStopPropagation(event)

+########################################################################################################################

     # Adds an overlay over the wrapper to prevent UI interaction with wrapper.
     # Adds a spinner to the overlay.
@@ -44,6 +45,8 @@
     $(document).on 'click', '#issues .loading-overlayable input[type=submit]', (event) ->
       addLoadingOverlay(@)

+########################################################################################################################
+
     # If the url contains an anchor is something like "...#issue-wrapper-12?...",
     # finds the add comment form within that anchor, and imitates a click on it
     # This is used when we guess that user might be interested in writing a comment more than usual.


Finished the load catagry_id at treeview

--- app/assets/javascripts/bank_train/post-level.coffee
+++ app/assets/javascripts/bank_train/post-level.coffee
@@ -109,7 +109,7 @@
       if tree isnt undefined
         tree_ids = $.map($('.modal-content .modal-body .tree').treeview('getChecked', 0),(tree) -> return tree.id)
         for bsns_id in tree_ids
-          bsns_tree_ids = { 'name' : 'post[business_category_ids][]', 'value' : bsns_id }
+          bsns_tree_ids = { 'name' : 'post[business_category_ids][]', 'value' : bsns_id }
           post_form_data.push( bsns_tree_ids )

       $.ajax
@@ -122,12 +122,21 @@
         that.set_failure_im(msg)

     window.modal_dialog.get_modal_dialog().on "click", ".modal-content .modal-body .post_business_categories .bsns_btn", =>
-      ctgr_selected_data = jQuery(".modal-content .modal-body .tree").attr("data-ctgr-selected")
-      if ctgr_selected_data isnt undefined
-        for category in ctgr_selected_data
-          console.log category.post_ids
-
-      console.log(ctgr_selected_data)
+      judge_load = that.judge_inited
+      if judge_load is undefined
+        ctgr_selected_data = jQuery(".modal-content .modal-body .tree").attr("data-ctgr-selected")
+        result = new Array()
+        if ctgr_selected_data isnt undefined
+          json_obj = JSON.parse(ctgr_selected_data)
+          for category in json_obj
+            selected_id = category._id.$oid
+            result.push(selected_id)
+        # if result.length isnt 0
+        #   json_result_string = JSON.stringify(result)
+        #   json_result = JSON.parse(json_result_string)
+
+        #   console.log( json_result )
+        #   console.log result
       jQuery.ajax
         url: "/business_categories.json"
         method: "get"
@@ -137,7 +146,10 @@
         # 2 替换模态框的 body 内容为

         # 3 通过调用 jQuery('.modal-content .modal-body .tree').treeview(详细参数)
         #   把
 替换成要显示的树状内容
-        #jQuery.map(tree.treeview('getChecked', 0),(n) -> return n.category_id)
+        #jQuery.map(tree.treeview('getChecked', 0),(n) -> return n.category_id)
+        # tree.treeview('getNode', 0);
+        # {text: xxx, nodes: [], id: xxx, name: xxx, nodeId: xxx}
+        # tree.treeview('checkNode', [ ids, { silent: false } ])
         that.tree_to_bsns_ctgr = that.get_children_r( null, msg )
         jQuery(".modal-content .modal-body .simple_form").addClass("hide")
         jQuery(".modal-content .modal-body .tree").removeClass("hide")
@@ -150,6 +162,7 @@
       jQuery(".modal-content .modal-body .tree").addClass("hide")
       jQuery(".modal-content .modal-body .simple_form").removeClass("hide")
       jQuery(".modal-content .modal-body .tree-button").addClass("hide")
+      that.judge_inited = true

     # 岗位信息修改
     @$elm.on "click",".post-list .post .update-post", ->
@@ -166,11 +179,19 @@

     window.modal_dialog.get_modal_dialog().on "submit",".modal-body .page-posts-form-edit .simple_form", ->
       post_id = $(this).closest(".page-posts-form-edit").attr("data-post-id")
-      event.preventDefault();
+      event.preventDefault()
+      post_form_data = $( this ).serializeArray()
+      tree = that.tree_to_bsns_ctgr
+      if tree isnt undefined
+        tree_ids = $.map($('.modal-content .modal-body .tree').treeview('getChecked', 0),(tree) -> return tree.id)
+        for bsns_id in tree_ids
+          bsns_tree_ids = { 'name' : 'post[business_category_ids][]', 'value' : bsns_id }
+          post_form_data.push( bsns_tree_ids )
+
       $.ajax
         method: "PATCH",
         url: "/posts/"+post_id+"",
-        data: $( this ).serializeArray()
+        data: post_form_data
       .success ( msg ) =>
         that.set_success_im(msg,post_id)
       .error (msg) =>


Prevented navigation

Prevented navigation

--- app/assets/javascripts/google/street_view.js.coffee
+++ app/assets/javascripts/google/street_view.js.coffee
@@ -7,8 +7,15 @@
   telAvivRadius: 3.5
   metersInDegree: 111300

+  panoramaOptions:
+    panControl: false
+    zoomControl: false
+    addressControl: false
+    linksControl: false
+    disableDefaultUI: true
+
   constructor: ->
-    @panorama = new google.maps.StreetViewPanorama(document.getElementById("panorama"))
+    @panorama = new google.maps.StreetViewPanorama(document.getElementById('panorama'), @panoramaOptions)
     @panorama.setPosition(new google.maps.LatLng(@telAvivCenter.lat, @telAvivCenter.lng))
     @panorama.setPov(heading: 270, pitch: 0)


The Dashboard only shows papers that the user is "My Paper" or "Collaborator" on.

--- app/assets/javascripts/routes/index_route.js.coffee
+++ app/assets/javascripts/routes/index_route.js.coffee
@@ -5,7 +5,13 @@

   setupController: (controller, model) ->
     controller.set('model', model)
-    controller.set('papers', @store.all('litePaper'))
+    papers = @store.filter 'litePaper', (p) ->
+      roles = p.get('roles')
+      isMyPaper = roles.indexOf('My Paper') > -1
+      iAmCollaborator = roles.indexOf('Collaborator') > -1
+      isMyPaper || iAmCollaborator
+
+    controller.set('papers', papers)

   actions:
     didTransition: () ->


fix erros

fix erros

--- jsapp/xlform/src/view.row.coffee
+++ jsapp/xlform/src/view.row.coffee
@@ -195,7 +195,7 @@

     _deleteGroupContent: () =>
       console.log('_deleteGroupContent')
-      console.log(@findGroupRows)
+      console.log(@findGroupRows())
       # TODO delete everything inside
       # and then do _deleteGroup()
       return
@@ -209,7 +209,7 @@
         findMatch = (row) ->
           if row.cid is rowId
             matchingRow = row
-        @survey.forEachRow(findMatch, {includeGroups: true})
+        @surveyView.survey.forEachRow(findMatch, {includeGroups: true})
         rows.push(matchingRow)
       )
       return rows


use image url helper to serve from ssl

use image url helper to serve from ssl

--- apps/artwork/client/view.coffee
+++ apps/artwork/client/view.coffee
@@ -177,8 +177,7 @@

   setupMonocleView: ->
     @$('.artwork-image').append("

")
-    url = @artwork.url() + '/image/' + @artwork.defaultImage().get('id') + '/larger.jpg'
-    @$('.monocle-zoom').css('background-image', "url(#{url})")
+    @$('.monocle-zoom').css('background-image', "url(#{@artwork.defaultImage().imageUrl('larger')})")
     new MonocleView
       artwork: @artwork
       el: @$('.artwork-image')


fix in JasmineSpec

fix in JasmineSpec

--- test/JasmineSpec.coffee
+++ test/JasmineSpec.coffee
@@ -11,6 +11,7 @@

     it 'should set and return given value', ->
       expect(subject).toBeDefined()
+      expect(subject).toHaveProperties ['set', 'instance']
       expect(subject.instance).toBeUndefined()
       expect(subject.set('test')).toBeEqual 'test'
       expect(subject.instance).toBeEqual 'test'


Return query errors

Return query errors

--- lib/mongoose-repl.coffee
+++ lib/mongoose-repl.coffee
@@ -48,7 +48,7 @@
         res = vm.runInContext js, context, filename
       catch err then return cb format_error err

-      if res instanceof mongoose.Query then res.exec (err, doc) -> cb null, doc
+      if res instanceof mongoose.Query then res.exec (err, doc) -> cb err, doc
       else cb null, res

   conn.once 'open', ->


Save modified files before launching the debugger. #115

--- lib/node-debugger.coffee
+++ lib/node-debugger.coffee
@@ -2,6 +2,7 @@
 {Debugger} = require './debugger'
 logger = require './logger'
 os = require 'os'
+fs = require 'fs'

 processManager = null
 _debugger = null
@@ -41,47 +42,49 @@
     @disposables.add _debugger.subscribeDisposable 'disconnected', ->
       #atom.notifications.addInfo('finish debugging : )')
     @disposables.add atom.commands.add('atom-workspace', {
-      'node-debugger:start-resume': @startOrResume
-      'node-debugger:start-active-file': @startActiveFile
-      'node-debugger:stop': @stop
-      'node-debugger:toggle-breakpoint': @toggleBreakpoint
-      'node-debugger:step-next': @stepNext
-      'node-debugger:step-in': @stepIn
-      'node-debugger:step-out': @stepOut
-      'node-debugger:attach': @attach
-      'node-debugger:toggle-debugger': @toggleDebugger
+      'node-debugger:start-resume': => @startOrResume()
+      'node-debugger:start-active-file': => @startActiveFile()
+      'node-debugger:stop': => @stop()
+      'node-debugger:toggle-breakpoint': => @toggleBreakpoint()
+      'node-debugger:step-next': => @stepNext()
+      'node-debugger:step-in': => @stepIn()
+      'node-debugger:step-out': => @stepOut()
+      'node-debugger:attach': => @attach()
+      'node-debugger:toggle-debugger': => @toggleDebugger()
     })

-  startOrResume: =>
+  startOrResume: ->
     if _debugger.isConnected()
       _debugger.reqContinue()
     else
+      @saveAll()
       _debugger.start()

-  attach: =>
+  attach: ->
     return if _debugger.isConnected()
     _debugger.attach()

-  startActiveFile: =>
+  startActiveFile: ->
     return if _debugger.isConnected()
+    @saveAll()
     _debugger.startActiveFile()

-  toggleBreakpoint: =>
+  toggleBreakpoint: ->
     editor = atom.workspace.getActiveTextEditor()
     path = editor.getPath()
     {row} = editor.getCursorBufferPosition()
     _debugger.breakpointManager.toggleBreakpoint editor, path, row

-  stepNext: =>
+  stepNext: ->
     _debugger.step('next', 1) if _debugger.isConnected()

-  stepIn: =>
+  stepIn: ->
     _debugger.step('in', 1) if _debugger.isConnected()

-  stepOut: =>
+  stepOut: ->
     _debugger.step('out', 1) if _debugger.isConnected()

-  stop: =>
+  stop: ->
     _debugger.cleanup()

   deactivate: ->
@@ -92,3 +95,14 @@

   toggleDebugger: ->
     _debugger.toggle()
+
+  saveAll: ->
+    # code shamelessly copied from https://github.com/BrownBear2/atom-save-all/blob/master/lib/atom-save-all.coffee
+    current = atom.workspace?.getActiveEditor?()
+    current ?= atom.workspace?.getActiveTextEditor?()
+    if current? and current.getURI?()? and current.isModified?() and paneItem?.getPath?()? and (!fs.existsSync(paneItem.getPath()) or !fs.statSync(current.getPath()).isFile())
+      current.save()
+
+    for paneItem in atom.workspace.getPaneItems()
+      if paneItem.getURI?()? and paneItem.isModified?() and paneItem?.getPath?()? and fs.existsSync(paneItem.getPath()) and fs.statSync(paneItem.getPath()).isFile()
+        paneItem.save()


Restore selection for IE

Restore selection for IE

--- src/quill.coffee
+++ src/quill.coffee
@@ -105,6 +105,7 @@
     super(Quill.events.POST_EVENT, eventName, args...)

   focus: ->
+    this.setSelection(@editor.selection.range) if dom.isIE(11)
     @root.focus()

   formatLine: (start, end, name, value, source) ->


Test to prevent overwriting a Config hostname entry.

--- test/config-test.coffee
+++ test/config-test.coffee
@@ -105,21 +105,30 @@
       topic: ->
         new config.Config

-      'when we call addPlugin() twice':
+      'when we call addPlugin() twice with same args':
         topic: (config) ->
           config.addPlugin 'http://example.com', 'spell-check'
           config.addPlugin 'http://example.com', 'spell-check'

         'returns false': (plugin) ->
           assert.equal plugin, false
-
+
   .addBatch
     'Add a hostname that already exists':
       topic: ->
-        config = new config.Config
-        config.addHostname 'http://example.com'
+        new config.Config

-      'returns false': (config) ->
-        assert.equal config, false
+      'calling addHostname(http://etc.com)':
+        topic: (config) ->
+          config.addHostname 'http://etc.com'

+        'returns an empty list of plugins': (config) ->
+          assert.equal config.length, 0
+
+        'calling addHostname(http://etc.com)':
+          topic: (file, config) ->
+            config.addHostname 'http://etc.com'
+
+          'returns false': (config) ->
+            assert.equal config, false
   .export(module)


parsing working for square brackets and parens

--- lib/python-indent.coffee
+++ lib/python-indent.coffee
@@ -89,12 +89,18 @@
         else
             indentColumn = parenthesesIndent[1]

+    # this is the column where the bracket/paren was, so need to bump by one
+    indentColumn = indentColumn + 1
+
     # Get tab length for context
     tabLength = editor.getTabLength()

+    console.log('indentColumn: ', indentColumn)
+
     # Calculate soft-tabs from spaces (can have remainder)
-    tabs = (indentColumn / tabLength) - editor.indentationForBufferRow row
-    rem = indentColumn % tabLength
+    tabs = (indentColumn / tabLength)
+    rem = ((tabs - Math.floor(tabs)) * tabLength)
+    console.log('tabs: ', tabs, 'rem: ', rem)

     # If there's a remainder, `editor.buildIndentString` requires the tab to
     # be set past the desired indentation level, thus the ceiling.
@@ -106,109 +112,64 @@

     # I'm glad Atom has an optional `column` param to subtract spaces from
     # soft-tabs, though I don't see it used anywhere in the core.
+
+    console.log('tabs:', tabs, 'offset:', offset)
+
     indent = editor.buildIndentString(tabs, column=offset)

+    console.log('len:', indent.length, 'str: "', indent, '"')
+
     # Set the indent
-    editor.getBuffer().setTextInRange([[row, 0], [row, 0]], indent)
+    editor.getBuffer().setTextInRange([[row, 0], [row, editor.indentationForBufferRow(row) * tabLength]], indent)

   parseLines: (lines) ->
-    # initialize some empty stacks
+    # bracketStack is a stack of [row, column] arrays indicating the
+    # location of the opening bracket
     bracketStack = []
+    # parenthesesStack is a similar stack but for parentheses
     parenthesesStack = []
+    # if we are in a string, this tells us what character introduced the string
+    # i.e., did this string start with ' or with "?
+    stringDelimiter = []

     # loop over each line, adding to each stack
-    for i in [0..lines.length-1]
-        line = lines[i]
-        stacks = PythonIndent.parseLine(bracketStack, parenthesesStack, [], i, 0, line)
-        bracketStack.concat(stacks[0])
-        parenthesesStack.concat(stacks[1])
+    for row in [0 .. lines.length-1]
+        line = lines[row]
+
+        # boolean, whether or not the current character is being escaped
+        isEscaped = false
+
+        for col in [0 .. line.length-1]
+            c = line[col]
+
+            # if stringDelimiter is set, then we are in a string
+            if stringDelimiter.length
+                # we are currently in a string
+                if isEscaped
+                    isEscaped = false
+                else
+                    switch c
+                        when stringDelimiter
+                            stringDelimiter = []
+                        when '\\'
+                            isEscaped = true
+            else
+                switch c
+                    when '#'
+                        break
+                    when '['
+                        bracketStack.push([row, col])
+                    when ']'
+                        bracketStack.pop()
+                    when '('
+                        parenthesesStack.push([row, col])
+                    when ')'
+                        parenthesesStack.pop()
+                    when "'"
+                        stringDelimiter = "'"
+                    when '"'
+                        stringDelimiter = '"'

     # return the stacks as an array
     return [bracketStack, parenthesesStack]
-
-
-  parseLine: (bracketStack, parenthesesStack, stringDelimiter, row, col, line) ->
-    # base case
-
-    if not line.length or (not stringDelimiter.length and line[0] == '#')
-        # stacks will be turned as array
-        return [bracketStack, parenthesesStack]
-
-    c = line[0]
-
-    # if stringDelimiter is set, then we are in a string
-    if stringDelimiter.length
-        # if current character is same as string delimiter, then we have exited string
-        if c == stringDelimiter
-            stringDelimiter = []
-    else
-        switch c
-            when '['
-                bracketStack.push([row, col])
-            when ']'
-                bracketStack.pop()
-            when '('
-                parenthesesStack.push([row, col])
-            when ')'
-                parenthesesStack.pop()
-            when '\''
-                stringDelimiter = '\''
-            when '"'
-                stringDelimiter = '"'
-
-    return PythonIndent.parseLine(bracketStack, parenthesesStack,
-                                  stringDelimiter, row, col+1, line[1..])
-
-  #
-  # indentOnOpeningDelimiter: (editor, row, previousLine, matchOpeningDelimiter) ->
-  #   # Get index of opening delimiter.
-  #   # The indent should be one whitespace character past this (sorry, no tabs)
-  #   indentColumn = previousLine.lastIndexOf(matchOpeningDelimiter[1]) + 1
-  #
-  #   # Get tab length for context
-  #   tabLength = editor.getTabLength()
-  #
-  #   # Calculate soft-tabs from spaces (can have remainder)
-  #   tabs = (indentColumn / tabLength) - editor.indentationForBufferRow row
-  #   rem = indentColumn % tabLength
-  #
-  #   # If there's a remainder, `editor.buildIndentString` requires the tab to
-  #   # be set past the desired indentation level, thus the ceiling.
-  #   tabs = if rem > 0 then Math.ceil tabs else tabs
-  #
-  #   # Offset is the number of spaces to subtract from the soft-tabs if they
-  #   # are past the desired indentation (not divisible by tab length).
-  #   offset = if rem > 0 then tabLength - rem else 0
-  #
-  #   # I'm glad Atom has an optional `column` param to subtract spaces from
-  #   # soft-tabs, though I don't see it used anywhere in the core.
-  #   indent = editor.buildIndentString(tabs, column=offset)
-  #
-  #   # Set the indent
-  #   editor.getBuffer().setTextInRange([[row, 0], [row, 0]], indent)
-  #
-  # unindentOnOpeningDelimiter: (editor, row, previousLine) ->
-  #   # Get all preceding lines
-  #   lines = if row > 0 then editor.buffer.lines[0..row - 1] else []
-  #
-  #   # Loop in reverse through lines
-  #   for line, i in lines by -1
-  #
-  #     # Set indent for row after declaration of block
-  #     if PythonIndent.openingDelimiterIndentRegex.test line
-  #
-  #       # Indent one tab-level past declaration
-  #       indent = editor.indentationForBufferRow i
-  #       indent += 1 if previousLine.slice(-1) is ':'
-  #       editor.setIndentationForBufferRow row, indent
-  #
-  #       # Stop trying after success
-  #       break
-  #
-  # indentHanging: (editor, row, previousLine) ->
-  #   # Indent at the current block level plus the setting amount (1 or 2)
-  #   indent = (editor.indentationForBufferRow row) + (atom.config.get 'python-indent.hangingIndentTabs')
-  #
-  #   # Set the indent
-  #   editor.setIndentationForBufferRow row, indent


improve haptic by disabling button and add class working to container for styling

--- modules/gui/lib/widgets/LoginForm.coffee
+++ modules/gui/lib/widgets/LoginForm.coffee
@@ -23,7 +23,7 @@
                 content: ->
                     if @data.logo
                         img src:@data.logo
-
+
                     form method:'post', action:'#', class:'gui widgets LoginForm', ->

                         if !(user = @identity.login())
@@ -76,18 +76,23 @@

                 user = login.find('input[name=user]')
                 pass = login.find('input[name=pass]')
-
+                button = login.find('[name=button]')
                 div = login.find('> div')

                 login.on 'submit', ()=>

+                    hint.text('')
                     if @identity.login()

                         @_getAuthManager().logout (err)=> location.reload()

                     else
                         if div.is ':visible'
+                            button.attr 'disabled', true
+                            @__root.addClass 'working'
                             @_getAuthManager().login user.val(), pass.val(), (err)=>
+                                button.attr 'disabled', false
+                                @__root.removeClass 'working'
                                 if err
                                     pass.val ''
                                     hint.addClass('error').text(err.message)


Add `escapeMessage` to api

Add `escapeMessage` to api

--- api.coffee
+++ api.coffee
@@ -130,6 +130,9 @@
 # replies (i.e. whispers to the sender) to the given message. See `sendMessage`
 replyTo = (message, reply, enableSmilies = yes, callback) -> sendMessage "/whisper #{message.username}, #{reply}", enableSmilies, callback

+# escapes the / at the beginning of a message
+escapeMessage = (message) -> message.replace /^\//, '//'
+
 module.exports =
 	fetchSecurityToken: fetchSecurityToken
 	sendLoginRequest: sendLoginRequest
@@ -140,3 +143,4 @@
 	recursiveFetchMessages: recursiveFetchMessages
 	sendMessage: sendMessage
 	replyTo: replyTo
+	escapeMessage: escapeMessage


Guard against real-time messaging API returning empty users

--- src/slack.coffee
+++ src/slack.coffee
@@ -73,6 +73,7 @@
       if id is user.name then delete @robot.brain.data.users[user.id]

   userChange: (user) =>
+    return unless user?.id?
     newUser = {name: user.name, real_name: user.real_name, email_address: user.profile.email}
     if user.id of @robot.brain.data.users
       for key, value of @robot.brain.data.users[user.id]


make the port 3000

make the port 3000

--- app.coffee
+++ app.coffee
@@ -37,5 +37,5 @@

 # SERVER

-app.listen(1234)
+app.listen(3000)
 console.log "Express server listening on port #{app.address().port}"


cleanup

cleanup

--- app.coffee
+++ app.coffee
@@ -51,7 +51,7 @@
     action: '/keyin'
     numDigits: 4
   , () ->
-    @.say 'yolk, please enter your code'
+    @say 'yolk, please enter your code'
   send_xml res, twiml

 app.post '/ring', (req, res) ->


Improve how params and routes are looked up

--- app.coffee
+++ app.coffee
@@ -8,21 +8,21 @@

 app.get '/', (req, resp) -> resp.render 'index'

-app.get '/:filename', (req, resp) ->
-  content = req.query['content']
+app.get '/:filename.:extension', (req, resp) ->
+  content = req.param['content']
   if (content and content.length > 1024)
     resp.json({error: 'File content is longer than 1024 characters and was truncated.. Please use POST instead of GET'}, 406)
   else
     saveit(req, resp)

-app.post '/:filename', saveit
+app.post '/:filename.:extension', saveit

 saveit = (req, resp) ->
-  content = req.query["content"]
+  content = req.param.content
   unless (content)
     resp.json({error: 'File content is missing'}, 406)
   else
-    resp.attachment(req.params.filename)
+    resp.attachment(req.param.filename)
     resp.send(content)

 app.listen process.env.PORT or 4000, -> console.log 'Listening...'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/tween.coffee
+++ src/tween.coffee
@@ -17,9 +17,9 @@
       if I.age >= data.endTime
         I[property] = data.end

-
-      f = Easing[data.easing](data.start, data.end)
-      I[property] = f()
+      else
+        f = Easing[data.easing](data.start, data.end)
+        I[property] = f()

   tween: (duration, properties) ->
     for property, target of properties


making internal2external available outside the module

--- src/types.coffee
+++ src/types.coffee
@@ -18,7 +18,7 @@

 "use strict"

-internal2external =
+root.internal2external =
   B: 'byte'
   C: 'char'
   D: 'double'
@@ -30,14 +30,14 @@
   Z: 'boolean'

 external2internal = {}
-external2internal[v]=k for k,v of internal2external
+external2internal[v]=k for k,v of root.internal2external

 # consumes characters from the array until it finishes reading one full type.
 root.carr2type = (carr) ->
   c = carr.shift()
   return null unless c?
-  if c of internal2external
-    new root.PrimitiveType internal2external[c]
+  if c of root.internal2external
+    new root.PrimitiveType root.internal2external[c]
   else if c == 'L'
     new root.ClassType((c while (c = carr.shift()) != ';').join(''))
   else if c == '['
@@ -49,8 +49,8 @@
 # fast path: generate type from string
 root.str2type = (type_str) ->
   c = type_str[0]
-  if c of internal2external
-    new root.PrimitiveType internal2external[c]
+  if c of root.internal2external
+    new root.PrimitiveType root.internal2external[c]
   else if c == 'L'
     new root.ClassType(type_str[1...-1])
   else if c == '['


opt: utils

opt: utils

--- src/utils.coffee
+++ src/utils.coffee
@@ -95,8 +95,6 @@
 		else
 			return throw new TypeError 'wrong argument type: ' + list

-		utils.end ?= {}
-
 		tryIter = ->
 			try
 				iter()
@@ -181,8 +179,9 @@

 	###*
 	 * The end symbol.
-	###
-	end: {}
+	 * @return {Promise} A promise that will end the current pipeline.
+	###
+	end: -> new Promise ->

 	###*
 	 * Creates a function that is the composition of the provided functions.
@@ -265,8 +264,6 @@
 		else
 			return throw new TypeError 'wrong argument type: ' + fn

-		utils.end ?= {}
-
 		run = (preFn) ->
 			preFn.then (val) ->
 				fn = iter val
@@ -341,11 +338,13 @@

 	###*
 	 * Create a composable event source function.
-	 * @return {Function} `((v) ->, (reason) ->) -> source` The fucntion's
+	 * Promise can't resolve multiple times, this function makes it possible, so
+	 * that you can easily map, filter and debounce events in a promise way.
+	 * @return {Function} `((value) ->, (reason) ->) -> source` The fucntion's
 	 * members:
 	 * ```js
 	 * {
-	 * 	emit: function (v) {},
+	 * 	emit: function (value) {},
 	 * 	error: function (reason) {},
 	 * 	handlers: Array
 	 * }
@@ -355,25 +354,43 @@
 	 * var linear = utils.source();
 	 *
 	 * var x = 0;
-	 * setInterval(function () {
+	 * setInterval(() => {
 	 * 	linear.emit(x++);
 	 * }, 1000);
 	 *
 	 * // Wait for a moment then emit the value.
-	 * var quad = linear(function (x) { return utils.sleep(2000, x * x); });
-	 *
-	 * quad(function (v) { console.log(v); });
-	 *
-	 * # Dispose all children.
+	 * var quad = linear(x => utils.sleep(2000, x * x));
+	 *
+	 * quad(
+	 * 	value => { console.log(value); },
+	 * 	reason => { console.error(reason); }
+	 * );
+	 *
+	 * // Dispose all children.
 	 * linear.handlers = [];
 	 * ```
-	###
-	source: ->
-		src = (resolver, rejector) ->
+	 * @example
+	 * ```js
+	 * var filter = fn => v => fn(v) ? v : utils.end();
+	 *
+	 * var keyup = utils.source((emit) => {
+	 * 	document.querySelector('input').onkeyup = emit;
+	 * });
+	 *
+	 * var keyupText = keyup(e => e.target.value);
+	 *
+	 * // Now we only get the input when the text length is greater than 3.
+	 * var keyupTextGT3 = keyupText(filter(text => text.length > 3));
+	 *
+	 * keyupTextGT3(v => console.log(v));
+	 * ```
+	###
+	source: (executor) ->
+		src = (onEmit, onError) ->
 			nSrc = utils.source()
 			src.handlers.push {
-				resolver
-				rejector
+				onEmit
+				onError
 				nEmit: nSrc.emit
 				nError: nSrc.error
 			}
@@ -383,8 +400,8 @@
 		src.emit = (val) ->
 			for handler in src.handlers
 				Promise.resolve(val).then(
-					handler.resolver
-					handler.rejector
+					handler.onEmit
+					handler.onError
 				).then(
 					handler.nEmit
 					handler.nError
@@ -394,7 +411,7 @@
 		src.error = (reason) ->
 			for handler in src.handlers
 				Promise.reject(reason).catch(
-					handler.rejector
+					handler.onError
 				).then(
 					handler.nEmit
 					handler.nError
@@ -403,6 +420,8 @@

 		src.handlers = []

+		executor? src.emit, src.error
+
 		src

 	###*


fix: pass undefined to editor

fix: pass undefined to editor

--- lib/config-view.coffee
+++ lib/config-view.coffee
@@ -41,7 +41,9 @@
           console.error(err) if err?
           @nodePathInput.getEditor().setText(path) if path?

-      @nodePathInput.getEditor().setText(path)
+      else
+        @nodePathInput.getEditor().setText(path)
+
     @appPathInput.getEditor().setText(atom.workspace.getActiveEditor().getPath())


More methods from common.js fs.

More methods from common.js fs.

--- src/fs.coffee
+++ src/fs.coffee
@@ -15,6 +15,13 @@
       throw "absolute: ./ not yet implemented"
     else
       path
+
+  # Return the basename of the given path. That is the path with
+  # any leading directory components removed. If specified, also
+  # remove a trailing extension.
+  base: (path, ext) ->
+    base = path.split("/").pop()
+    if ext then base.replace(RegEx(ext + "$"), "") else base

   # Set the current working directory to `path`.
   changeWorkingDirectory: (path) ->