Docs.

Docs.

--- js2coffee.coffee
+++ js2coffee.coffee
@@ -100,10 +100,8 @@
   BlockStatementExit: (node) ->
     @popStack()
   FunctionDeclaration: (node) ->
-    @fixShadowing node
     @removeUndefinedParameter node
   FunctionExpression: (node) ->
-    @fixShadowing node
     @removeUndefinedParameter node
     @moveNamedFunctionExpressions node
   SwitchStatement: (node) ->
@@ -131,8 +129,9 @@
   VariableDeclarator: (node, parent, skip) ->
     @addExplicitUndefinedInitializer node, parent, skip

-  fixShadowing: (node) ->
-    node #TODO
+  ###
+  # Converts `this.x` into `@x` for MemberExpressions.
+  ###

   transformThisToAtSign: (node) ->
     if node.object.type is 'ThisExpression'
@@ -177,7 +176,7 @@
       @syntaxError node, "Labeled statements are not supported in CoffeeScirpt"

   ###
-  # Updates `void 0` UnaryExpressions to `undefined` identifiers
+  # Updates `void 0` UnaryExpressions to `undefined` Identifiers.
   ###

   updateVoidToUndefined: (node) ->
@@ -187,7 +186,7 @@
       node

   ###
-  # Turn 'undefined' into '`undefined`'
+  # Turn 'undefined' into '`undefined`'. This uses a new node type, CoffeeEscapedExpression.
   ###

   escapeUndefined: (node) ->
@@ -197,7 +196,7 @@
       node

   ###
-  # Updates binary expressions to their CoffeeScript equivalents
+  # Updates binary expressions to their CoffeeScript equivalents.
   ###

   updateBinaryExpression: (node) ->
@@ -209,7 +208,8 @@
     node

   ###
-  # Removes `undefined` from function parameters
+  # Removes `undefined` from function parameters.
+  # (`function (a, undefined) {}` => `(a) ->`)
   ###

   removeUndefinedParameter: (node) ->
@@ -226,8 +226,9 @@
     node

   ###
-  # Consolidates empty cases into the next case.
-  # (`case x: case y: z()` => `case x, y: z()`)
+  # Consolidates empty cases into the next case. The case tests will then be
+  # made into a new node type, CoffeeListExpression, to represent
+  # comma-separated values. (`case x: case y: z()` => `case x, y: z()`)
   ###

   consolidateCases: (node) ->


getVariableType - Do not attempt to match multiple regexes if one of them already resulted in a match.

--- lib/services/php-file-parser.coffee
+++ lib/services/php-file-parser.coffee
@@ -430,6 +430,7 @@
                     bestMatchRow = lineNumber
                     bestMatch = @findUseForClass(editor, matchesNew[1])

+            if not bestMatch
                 # Check for catch(XXX $xxx)
                 matchesCatch = regexCatch.exec(line)

@@ -437,6 +438,7 @@
                     bestMatchRow = lineNumber
                     bestMatch = @findUseForClass(editor, matchesCatch[1])

+            if not bestMatch
                 # Check for a variable assignment $x = ...
                 matches = regexElement.exec(line)


XSS encode input value for resize calculation

--- client/Framework/components/inputs/KDInputView.coffee
+++ client/Framework/components/inputs/KDInputView.coffee
@@ -312,6 +312,7 @@
   setAutoGrow:->

     $input = @$()
+    $input.css "overflow", "hidden"

     @setClass "autogrow"

@@ -323,12 +324,14 @@
         @_clone.appendTo 'body'
         @_clone.css
           height        : "auto"
-          "z-index"     : 100000
+          zIndex        : 100000
           width         : $input.width()
-          padding       : $input.css('padding')
-          "word-break"  : $input.css('word-break')
-          "font-size"   : $input.css('font-size')
-          "line-height" : $input.css('line-height')
+          border        : $input.css 'border'
+          padding       : $input.css 'padding'
+          wordBreak     : $input.css 'wordBreak'
+          fontSize      : $input.css 'fontSize'
+          lineHeight    : $input.css 'lineHeight'
+          whiteSpace    : "pre"

     @on "blur", =>
       @_clone.detach()
@@ -340,14 +343,16 @@
   resize: ->
     return  unless @_clone
     @_clone.appendTo 'body' unless document.body.contains @_clone[0]
-    @_clone.html @getValue().replace /\n/gm, "<br />"
+    @_clone.html Encoder.XSSEncode @getValue()
+    @_clone.append document.createElement "br"
+
     height = @_clone.height()
-    if @$().css('box-sizing') is "border-box"
-      padding    = parseInt(@_clone.css("padding-top"), 10) + parseInt(@_clone.css("padding-bottom"), 10)
-      border     = parseInt(@_clone.css("border-top-width"), 10) + parseInt(@_clone.css("border-bottom-width"), 10)
-      height     = height + border + padding
-
-    @setHeight height
+    if @$().css("boxSizing") is "border-box"
+      padding = parseInt(@_clone.css("paddingTop"),     10) + parseInt(@_clone.css("paddingBottom"),     10)
+      border  = parseInt(@_clone.css("borderTopWidth"), 10) + parseInt(@_clone.css("borderBottomWidth"), 10)
+      height  = height + border + padding
+
+    @setHeight Math.max @initialHeight, height

   enableTabKey:-> @inputTabKeyEnabled = yes

@@ -438,5 +443,5 @@
       event.preventDefault()
       t.selectionStart = t.selectionEnd = ss + tabLength

-
-
+  viewAppended: ->
+    @initialHeight = @$().height()


:fire: Remove tests focus

:fire: Remove tests focus

--- spec/minimap-element-spec.coffee
+++ spec/minimap-element-spec.coffee
@@ -145,7 +145,7 @@
     #    ##    ## ##    ## ##    ##
     #     ######   ######   ######

-    fdescribe 'with css filters', ->
+    describe 'with css filters', ->
       describe 'when a hue-rotate filter is applied to a rgb color', ->
         [additionnalStyleNode] = []
         beforeEach ->


NavigationView specs were easy.

NavigationView specs were easy.

--- spec/navigation-view-spec.coffee
+++ spec/navigation-view-spec.coffee
@@ -9,7 +9,7 @@
   beforeEach ->
     util.openPath "triple-2way-diff.txt", (v) ->
       editorView = v
-      editor = editorView.getEditor()
+      editor = editorView.getModel()
       conflicts = Conflict.all({}, editor)
       conflict = conflicts[1]


new streamline file extensions

new streamline file extensions

--- demo/test.coffee
+++ demo/test.coffee
@@ -1,98 +0,0 @@
-return if not require('streamline/module')(module)
-PDFDocument = require 'streamline-pdfkit'
-tiger = require './tiger'
-
-# Create a new PDFDocument
-#doc = new PDFDocument
-#doc = PDFDocument.create(_, )
-
-
-doc = PDFDocument.create(_, {})
-
-
-# Set some meta data
-doc.info['Title'] = 'Test Document'
-doc.info['Author'] = 'Devon Govett'
-
-# Register a font name for use later
-doc.registerFont('Palatino', 'fonts/PalatinoBold.ttf')
-
-# Set the font, draw some text, and embed an image
-doc.font(_,'Palatino')
-   .fontSize(25)
-   .text('Some text with an embedded font!', 100, 100)
-   .fontSize(18)
-   .text('PNG and JPEG images:')
-   .image(_,'images/test.png', 100, 160, width: 412)
-   .image(_,'images/test.jpeg', 190, 400, height: 300)
-
-# Add another page
-doc.addPage()
-   .fontSize(25)
-   .text 'Here is some vector graphics...', 100, 100
-
-# Draw a triangle and a circle
-doc.save()
-   .moveTo(100, 150)
-   .lineTo(100, 250)
-   .lineTo(200, 250)
-   .fill("#FF3300")
-
-doc.circle(280, 200, 50)
-   .fill("#6600FF")
-
-doc.scale(0.6)
-   .translate(470, -380)
-   .path('M 250,75 L 323,301 131,161 369,161 177,301 z') # render an SVG path
-   .fill('red', 'even-odd') # fill using the even-odd winding rule
-   .restore()
-
-loremIpsum = '''
-Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam in suscipit purus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus nec hendrerit felis. Morbi aliquam facilisis risus eu lacinia. Sed eu leo in turpis fringilla hendrerit. Ut nec accumsan nisl. Suspendisse rhoncus nisl posuere tortor tempus et dapibus elit porta. Cras leo neque, elementum a rhoncus ut, vestibulum non nibh. Phasellus pretium justo turpis. Etiam vulputate, odio vitae tincidunt ultricies, eros odio dapibus nisi, ut tincidunt lacus arcu eu elit. Aenean velit erat, vehicula eget lacinia ut, dignissim non tellus. Aliquam nec lacus mi, sed vestibulum nunc. Suspendisse potenti. Curabitur vitae sem turpis. Vestibulum sed neque eget dolor dapibus porttitor at sit amet sem. Fusce a turpis lorem. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;
-Mauris at ante tellus. Vestibulum a metus lectus. Praesent tempor purus a lacus blandit eget gravida ante hendrerit. Cras et eros metus. Sed commodo malesuada eros, vitae interdum augue semper quis. Fusce id magna nunc. Curabitur sollicitudin placerat semper. Cras et mi neque, a dignissim risus. Nulla venenatis porta lacus, vel rhoncus lectus tempor vitae. Duis sagittis venenatis rutrum. Curabitur tempor massa tortor.
-'''
-
-# Draw some text wrapped to 412 points wide
-doc.text('And here is some wrapped text...', 100, 300)
-   .font(_,'Helvetica', 13)
-   .moveDown() # move down 1 line
-   .text(loremIpsum, width: 412, align: 'justify', indent: 30, paragraphGap: 5)
-
-# Add another page, and set the font back
-doc.addPage()
-   .font(_,'Palatino', 25)
-   .text('Rendering some SVG paths...', 100, 100)
-   .translate(220, 300)
-
-# Render each path that makes up the tiger image
-for part in tiger
-    doc.save()
-    doc.path(part.path) # render an SVG path
-
-    if part['stroke-width']
-        doc.lineWidth part['stroke-width']
-
-    if part.fill isnt 'none' and part.stroke isnt 'none'
-        doc.fillAndStroke(part.fill, part.stroke)
-    else
-        unless part.fill is 'none'
-            doc.fill(part.fill)
-
-        unless part.stroke is 'none'
-            doc.stroke(part.stroke)
-
-    doc.restore()
-
-# Add some text with annotations
-doc.addPage()
-   .fillColor("blue")
-   .text('Here is a link!', 100, 100)
-   .underline(100, 100, 160, 27, color: "#0000FF")
-   .link(100, 100, 160, 27, 'http://google.com/')
-
-# Add a list with a font loaded from a TrueType collection file
-doc.fillColor('#000')
-   .font(_,'fonts/Chalkboard.ttc', 'Chalkboard', 16)
-   .list(['One', 'Two', 'Three'], 100, 150)
-
-doc.write 'test.pdf',_


get rid of message with password reset that is no longer needed.

--- src/smc-webapp/landing_page.cjsx
+++ src/smc-webapp/landing_page.cjsx
@@ -337,10 +337,6 @@
                     check your spam folder

                 {s[1]}
-
-

-                    If you have trouble with the reset link you get via email, open it in Private Browser Mode.
-

     hide_forgot_password: ->


add an @editorconfig property to all rules

holds the rest of the config for properties that depend on eachother

--- lib/rule.coffee
+++ lib/rule.coffee
@@ -27,7 +27,8 @@
    * @param {String} filename
   ###
   constructor: (filename) ->
-    @setting = editorconfig.parse(filename)[@propertyName]
+    @editorconfig = editorconfig.parse(filename)
+    @setting = @editorconfig[@propertyName]
     @file = new File(filename)

   ###*


read_rss.coffeeのデバッグ表示を削除

--- read_rss.coffee
+++ read_rss.coffee
@@ -98,7 +98,6 @@
       for key of rss
         read_rss rss[key]['feed']['url'], rss[key]['feed']['id'], rss[key]['feed']['password'], key, rss[key]['target'], (item) ->
           msg = []
-          console.log "item: %j", item
           msg.push("[#{robot.brain.data[label][item.link]['keyword']}] #{@sm.url(item.title, item.link)}")
           msg.push("author: #{item.author}, date: #{item.date}")
           if item.description


More new user scripts

More new user scripts

--- scripts/newuserparty.coffee
+++ scripts/newuserparty.coffee
@@ -13,14 +13,15 @@
 # Author:
 #   PiXeL16

-emojis = [
-  ":tada::tada::smile::tada::tada:",
-  ":confetti_ball::tada::smile::tada::confetti_ball:",
-  ":tada::tada::metal::tada::tada:",
+messages  = [
+  "A new user hooray!! :tada::tada::smile::tada::tada:",
+  "A new user eeeehhh!! :confetti_ball::tada::smile::tada::confetti_ball:",
+  "Its a new user, yes!! :tada::tada::metal::tada::tada:",
+  "A new user appeared in the Wild  :tada::poke::hatching_chick::tada:",
 ]

 module.exports = (robot) ->

   robot.catchAll (msg) ->
-    if msg.message.match /new user/i
-        msg.send "A new user hooray!! " + msg.random emojis
+    if msg.message.match /new user/i
+        msg.send msg.random messages


Respond with "HTTP OK" instead of "NOTIFY"

Also some minor refactoring trying to figure out why responses aren't
sent out properly.

--- lib/ssdp.coffee
+++ lib/ssdp.coffee
@@ -15,8 +15,7 @@
     )

 listen = exports.listen = (dev, httpServer) ->
-    socket = dgram.createSocket 'udp4'
-    socket.on 'message', (msg, rinfo) ->
+    socket = dgram.createSocket 'udp4', (msg, rinfo) ->
         parseHeaders msg, (err, req) ->
             # these are the ST (search type) values we should respond to
             respondTo = [ 'ssdp:all', 'upnp:rootdevice', config.uuid ]
@@ -30,37 +29,33 @@
                     searchType: req.headers.st, address: rinfo.address, port: rinfo.port
                     dev, httpServer
                 )
+    socket.addMembership config.ssdp.address
     socket.bind config.ssdp.port

-# send 3 messages about the device, and then one for each service
 advertise = (dev, httpServer) ->
-    # messages only have different notification types
-    messages = for nt in makeNotificationHeaders(dev)
+    sendMessages(
         makeMessage(
             'notify'
+            [ 'host', 'cache-control', 'location', 'nt', 'nts', 'server', 'usn' ]
             nt: nt, nts: 'ssdp:alive'
             dev, httpServer
-        )
-    sendMessages messages
+        ) for nt in makeNotificationHeaders(dev)
+    )

 answer = (req, dev, httpServer) ->
-    messages =
-        if req.searchType is 'ssdp:all'
-            for st in makeNotificationHeaders(dev)
-                makeMessage(
-                    'notify'
-                    st: st, ext: '', nts: 'ssdp:alive'
-                    dev, httpServer
-                )
-        # respond with the same search type as request's
-        else
-            [ makeMessage(
-                'notify'
-                st: req.searchType, ext: ''
-                dev, httpServer
-            ) ]
-        sendMessages messages, req.address, req.port
+    # unless ssdp:all, respond once with same search type as request's
+    sendMessages(
+        makeMessage(
+            'ok'
+            [ 'cache-control', 'ext', 'location', 'server', 'st', 'usn' ]
+            st: st
+            dev, httpServer
+        ) for st in (if req.searchType is 'ssdp:all' then makeNotificationHeaders(dev) else [ req.searchType ])
+        req.address
+        req.port
+    )

+# send 3 messages about the device, and then one for each service
 makeNotificationHeaders = (dev) ->
     arr = [ 'upnp:rootdevice', config.uuid, device.makeDeviceType(dev.type) ]
     for serviceType in config.devices[dev.type].services
@@ -69,40 +64,53 @@

 # create a UDP socket, send messages, then close socket
 sendMessages = (messages, address, port) ->
-    address ?= config.ssdp.address
     port ?= config.ssdp.port
     socket = dgram.createSocket 'udp4'
-    socket.setMulticastTTL 4
-    socket.bind()
+    if address?
+        socket.setTTL 4
+        socket.setMulticastTTL 4
+        socket.addMembership config.ssdp.address
+    else
+        address = config.ssdp.address
+    socket.bind(config.ssdp.port)
     done = messages.length
+    console.log "Sent messages to #{address}:#{port}"
     for msg in messages
         socket.send msg, 0, msg.length, port, address, ->
             if done-- is 1
                 socket.close()

 # generate SSDP (HTTPU/HTTPMU) headers suiting the message type
-makeMessage = (reqType, customHeaders, dev, httpServer) ->
+makeMessage = (reqType, useHeaders, customHeaders, dev, httpServer) ->
     # headers with static values
     headers =
         host: "#{config.ssdp.address}:#{config.ssdp.port}"
-        'cache-control': "max-age=#{config.ssdp.timeout}"
+        'cache-control': "max-age = #{config.ssdp.timeout}"
         location: makeDescriptionUrl httpServer
         server: makeServerString dev
         usn: config.uuid
+        ext: ''

     # append NT to USN, except for requests with only uuid as NT
     if customHeaders.nt and customHeaders.nt isnt config.uuid
         headers.usn += '::' + customHeaders.nt
     else if customHeaders.st and customHeaders.st isnt config.uuid
         headers.usn += '::' + customHeaders.st
+
     headers = extend(headers, customHeaders)

     # build message string
-    message = [ "#{reqType.toUpperCase()} * HTTP/1.1" ]
-    for header, value of headers
-        message.push "#{header.toUpperCase()}: #{value}"
+    message =
+        if reqType is 'ok'
+            [ "HTTP/1.1 200 OK" ]
+        else
+            [ "#{reqType.toUpperCase()} * HTTP/1.1" ]
+
+    for header in useHeaders
+        message.push "#{header.toUpperCase()}: #{headers[header]}"
     # add carriage returns and new lines as required by HTTP spec
     message.push '\r\n'
+    console.log message
     new Buffer message.join '\r\n'

 makeServerString = (dev) ->


Sidebar: removed hot topics section

Sidebar: removed hot topics section

--- client/Social/Activity/sidebar/activitysidebar.coffee
+++ client/Social/Activity/sidebar/activitysidebar.coffee
@@ -320,7 +320,6 @@

     super

-    # @addHotTopics()
     @addFollowedTopics()
     @addConversations()
     @addMessages()
@@ -356,19 +355,7 @@
       partial  : '1'

-  addHotTopics: ->
-
-    @addSubView @sections.hot = new ActivitySideView
-      title      : 'TRENDING'
-      cssClass   : 'hot topics hidden'
-      itemClass  : SidebarTopicItem
-      dataPath   : 'popularTopics'
-      delegate   : this
-      headerLink : KD.utils.groupifyLink '/Activity/Topic/All'
-      dataSource : (callback) ->
-        KD.singletons.socialapi.channel.fetchPopularTopics
-          limit  : 5
-        , callback
+

   addFollowedTopics: ->


activity: map pinned activity channel type to sidebar section

--- client/Social/Activity/sidebar/activitysidebar.coffee
+++ client/Social/Activity/sidebar/activitysidebar.coffee
@@ -68,6 +68,7 @@

     section = switch type
       when 'topic'          then @sections.followedTopics
+      when 'pinnedactivity' then @sections.followedPosts
       when 'privatemessage' then @sections.messages
       else {}


add s3 kite. updated kite examples

add s3 kite. updated kite examples

--- client/landing/app/MainApp/kite/kitecontroller.coffee
+++ client/landing/app/MainApp/kite/kitecontroller.coffee
@@ -45,6 +45,8 @@
       when 'fs'
         kite = @createNewKite {kitename: kiteName}
       when 'terminal'
+        kite = @createNewKite {kitename: kiteName}
+      when 's3'
         kite = @createNewKite {kitename: kiteName}
       else
         kite = new Kite { kiteName, correlationName, kiteKey }


tune some parameters to try and improve tearning issues on spectrogram

--- demo/spectrogram/static/coffee/spectrogram.coffee
+++ demo/spectrogram/static/coffee/spectrogram.coffee
@@ -178,9 +178,9 @@
       high: 10
     })

-    @num_images = Math.ceil(NGRAMS/500) + 3
-
-    @image_width = 500
+    @num_images = Math.ceil(NGRAMS/800) + 1
+
+    @image_width = 800
     @image_height = 256

     @images = new Array(@num_images)


fixed a bug in removal of breakpoints

fixed a bug in removal of breakpoints

--- client/editor.coffee
+++ client/editor.coffee
@@ -158,7 +158,7 @@
   createBreakpointElement = -> $('
●
')[0]
   breakpoints = [] # array of line numbers
   cm.on 'gutterClick', (cm, l) ->
-    if breakpoints.indexOf(l) >= 0 then breakpoints.splice l, 1; cm.setGutterMarker l, 'breakpoints', null
+    if (i = breakpoints.indexOf l) >= 0 then breakpoints.splice i, 1; cm.setGutterMarker l, 'breakpoints', null
     else breakpoints.push l; cm.setGutterMarker l, 'breakpoints', createBreakpointElement()
     return


added ``representation as an alias for ⊤

--- client/keymap.coffee
+++ client/keymap.coffee
@@ -108,7 +108,7 @@
     ⌈ upstile maximum ceiling
     ⌊ downstile minimum floor
     ⊥ base decode uptack
-    ⊤ antibase encode downtack
+    ⊤ antibase encode downtack representation
     ⊣ left lev lefttack
     ⊢ right dex righttack
     = equal


Unprotected app.Store notify method.

Unprotected app.Store notify method.

--- app/client/js/store.coffee
+++ app/client/js/store.coffee
@@ -45,7 +45,8 @@
       instance

   ###*
-    @protected
+    PATTERN(steida): Whenever store changes anything, just call notify to
+    dispatch change event.
   ###
   notify: ->
     @dispatchEvent 'change'


Better wording for a message

Better wording for a message

--- library/nucleus/kernel.coffee
+++ library/nucleus/kernel.coffee
@@ -125,8 +125,8 @@
     @makeKernelSetup: (options) -> ->
         @broker = new content.JsonBroker this
         assert _.isObject(options), "no options"
-        message = "Booted up the kernel instance"
         manifest = "Using %s as instance identica"
+        message = "Booted up framework kernel instance"
         sigint = "Received the SIGINT (interrupt signal)"
         sigterm = "Received the SIGTERM (terminate signal)"
         process.on "SIGINT", => @shutdownKernel sigint


refactor accepts

refactor accepts

--- library/nucleus/plumbs.coffee
+++ library/nucleus/plumbs.coffee
@@ -123,14 +123,14 @@
 module.exports.accepts = (kernel) ->
     (request, response, next) ->
         response.accepts = (mimes...) ->
-            accept = request?.headers?.accept or ""
             handles = (pattern) -> pattern.test accept
             patternize = (s) -> new RegExp RegExp.escape s
-            regexps = _.filter mimes, (x) ->_.isRegExp x
-            strings = _.filter mimes, (x) ->_.isString x
-            strings = _.map strings, patternize
-            merged = _.merge regexps, strings
-            return _.find merged, handles
+            accept = do -> request?.headers?.accept or ""
+            regexps = _.filter mimes, (sx) ->_.isRegExp sx
+            strings = _.filter mimes, (sx) ->_.isString sx
+            strings = _.map strings, patternize.bind this
+            assert merged = _.merge(regexps, strings) or []
+            return _.find(merged, handles) or undefined
         next() unless request.headersSent

 # A middeware that makes possible external specification of session


fix a bug in the scaled kernel

fix a bug in the scaled kernel

--- library/nucleus/scaled.coffee
+++ library/nucleus/scaled.coffee
@@ -168,8 +168,10 @@
     # from the master server (frontend) to actual server (backend)
     # that does the job of handling the request. The forwarder is
     # also responsible for rotating (round-robin) servers queue!
-    makeForwarder: (queue, kind, select) -> (request, response) =>
-        encrypted = request.connection.encrypted or no
+    makeForwarder: (queue, kind, select) -> (xrequest, xresponse) =>
+        assert request = _.find arguments, "connection"
+        assert response = _.find arguments, "writeHead"
+        encrypted = request.connection.encrypted or false
         assert u = try "#{request.url}".underline.yellow
         assert x = (encrypted and "HTTPS" or "HTTP").bold
         reason = "no instances found behind a frontend"
@@ -188,8 +190,11 @@
     # on one of the master servers. This is the functionality that
     # is required for WebSockets and other similar transports to
     # perform its operation correctly in a distributed environment.
-    makeUpgraders: (queue, kind, select) -> (request, response) =>
-        encrypted = request.connection.encrypted or no
+    makeUpgraders: (queue, kind, select) -> (xrequest, xsocket) =>
+        assert request = _.find arguments, "connection"
+        assert socket = _.find arguments, "localAddress"
+        socket.writeHead = http.ServerResponse::writeHead
+        encrypted = request.connection.encrypted or false
         assert u = try "#{request.url}".underline.yellow
         assert x = (encrypted and "HTTPS" or "HTTP").bold
         reason = "no instances found behind a frontend"


It could be a different service

It could be a different service

--- library/nucleus/scaled.coffee
+++ library/nucleus/scaled.coffee
@@ -87,7 +87,7 @@
         assert _.isString host = nconf.get "master:host"
         assert _.isNumber port = nconf.get "master:https"
         cmp = (exm) -> (srv) -> exm.uuid is srv.uuid or 0
-        remove = (s) => assert _.remove @queueOfHttps, cmp(s)
+        remove = (srv) => _.remove @queueOfHttps, cmp(srv)
         assert upgrade = @makeUpgraders @queueOfHttp, "https"
         assert registr = @makeRegistrar @queueOfHttps, "https"
         assert forward = @makeForwarder @queueOfHttps, "https"
@@ -110,7 +110,7 @@
         assert _.isString host = nconf.get "master:host"
         assert _.isNumber port = nconf.get "master:http"
         cmp = (exm) -> (srv) -> exm.uuid is srv.uuid or 0
-        remove = (s) => assert _.remove @queueOfHttp, cmp(s)
+        remove = (srv) => _.remove @queueOfHttp, cmp(srv)
         assert upgrade = @makeUpgraders @queueOfHttp, "http"
         assert registr = @makeRegistrar @queueOfHttp, "http"
         assert forward = @makeForwarder @queueOfHttp, "http"


Disable submit button on save click

Disable submit button on save click

--- common/lib/xmodule/xmodule/js/src/capa/display.coffee
+++ common/lib/xmodule/xmodule/js/src/capa/display.coffee
@@ -519,11 +519,13 @@
       @save_internal()

   save_internal: =>
+    @enableCheckButton false
     Logger.log 'problem_save', @answers
     $.postWithPrefix "#{@url}/problem_save", @answers, (response) =>
       saveMessage = response.msg
       @gentle_alert saveMessage
       @updateProgress response
+      @enableCheckButton true

   refreshMath: (event, element) =>
     element = event.target unless element


Submit  new channel on enter

Submit  new channel on enter

--- components/new_channel/client/new_channel_view.coffee
+++ components/new_channel/client/new_channel_view.coffee
@@ -33,6 +33,7 @@
     super

   onKeyUp: (e)->
+    return @createChannel() if e.keyCode is 13
     @model.set 'title', @$input.val()?.trim()

   createChannel: (e) ->


Duplicate success function

Duplicate success function

--- vendor/assets/javascripts/brainstem/data-loader.coffee
+++ vendor/assets/javascripts/brainstem/data-loader.coffee
@@ -3,7 +3,6 @@
 class Brainstem.CollectionLoader
   constructor: (options = {}) ->
     @storageManager = options.storageManager
-    @successFunction = options.successFunction

   _parseLoadOptions: (loadOptions) ->
     @loadOptions = $.extend {}, loadOptions
@@ -108,8 +107,16 @@
     syncOptions.data.search = @loadOptions.search if @loadOptions.search
     syncOptions

-  _success: ->
-    @successFunction.apply(this, arguments)
+  _success: (options, collection, data) ->
+    if data
+      data = data.models if data.models?
+      collection.setLoaded true, trigger: false
+      if collection.length
+        collection.add data
+      else
+        collection.reset data
+    collection.setLoaded true
+    options.success(collection) if options.success?

 ####################################

@@ -167,7 +174,7 @@
       opts = $.extend {}, options, include: include, success: (firstLayerCollection) =>
         @_success(options, collection, firstLayerCollection)

-      cl = new Brainstem.CollectionLoader(storageManager: @storageManager, successFunction: @_success)
+      cl = new Brainstem.CollectionLoader(storageManager: @storageManager)
       cl.loadCollection(opts)

       # @_loadCollectionWithFirstLayer($.extend({}, options, include: include, success: ((firstLayerCollection) =>


Move only

Move only

--- vendor/assets/javascripts/brainstem/data-loader.coffee
+++ vendor/assets/javascripts/brainstem/data-loader.coffee
@@ -7,12 +7,12 @@

   _parseLoadOptions: (loadOptions) ->
     @loadOptions = $.extend {}, loadOptions
+    @loadOptions.only = if @loadOptions.only then _.map((Brainstem.Utils.extractArray "only", @loadOptions), (id) -> String(id)) else null

   load: (loadOptions) ->
     @_parseLoadOptions(loadOptions)

     options = @loadOptions
-    only = if options.only then _.map((Brainstem.Utils.extractArray "only", options), (id) -> String(id)) else null
     search = options.search
     include = _(options.include).map((i) -> _.keys(i)[0]) # pull off the top layer of includes
     filters = options.filters || {}
@@ -24,11 +24,11 @@
     collection = @storageManager.createNewCollection @loadOptions.name, []

     unless options.cache == false
-      if only?
-        alreadyLoadedIds = _.select only, (id) => cachedCollection.get(id)?.associationsAreLoaded(include)
-        if alreadyLoadedIds.length == only.length
+      if @loadOptions.only?
+        alreadyLoadedIds = _.select @loadOptions.only, (id) => cachedCollection.get(id)?.associationsAreLoaded(include)
+        if alreadyLoadedIds.length == @loadOptions.only.length
           # We've already seen every id that is being asked for and have all the associated data.
-          @_success options, collection, _.map only, (id) => cachedCollection.get(id)
+          @_success options, collection, _.map @loadOptions.only, (id) => cachedCollection.get(id)
           return collection
       else
         # Check if we have, at some point, requested enough records with this this order and filter(s).
@@ -63,21 +63,21 @@
         for underscoredModelName in keys
           @storageManager.storage(underscoredModelName).update _(resp[underscoredModelName]).values()

-        unless options.cache == false || only?
+        unless options.cache == false || @loadOptions.only?
           @storageManager.getCollectionDetails(@loadOptions.name).cache[cacheKey] = results

-        if only?
-          @_success options, collection, _.map(only, (id) -> cachedCollection.get(id))
+        if @loadOptions.only?
+          @_success options, collection, _.map(@loadOptions.only, (id) -> cachedCollection.get(id))
         else
           @_success options, collection, _(results).map (result) -> base.data.storage(result.key).get(result.id)

     syncOptions.data.include = include.join(",") if include.length
-    syncOptions.data.only = _.difference(only, alreadyLoadedIds).join(",") if only?
+    syncOptions.data.only = _.difference(@loadOptions.only, alreadyLoadedIds).join(",") if @loadOptions.only?
     syncOptions.data.order = options.order if options.order?
     _.extend(syncOptions.data, _(filters).omit('include', 'only', 'order', 'per_page', 'page', 'limit', 'offset', 'search')) if _(filters).keys().length

-    unless only?
+    unless @loadOptions.only?
       if options.limit? && options.offset?
         syncOptions.data.limit = options.limit
         syncOptions.data.offset = options.offset
@@ -88,7 +88,7 @@
     syncOptions.data.search = search if search

     modelOrCollection = collection
-    modelOrCollection = options.model if options.only && options.model
+    modelOrCollection = options.model if @loadOptions.only && options.model

     jqXhr = Backbone.sync.call collection, 'read', modelOrCollection, syncOptions


I dunno let's try figuring out if it's even matching anything.

--- scripts/youtube-info.coffee
+++ scripts/youtube-info.coffee
@@ -24,6 +24,7 @@

 module.exports = (robot) ->
   robot.hear /(youtube\.com\/watch\?v=|youtu.be\/)(.+)/i, (msg) ->
+    console.log msg.match[2]
     url_parsed = url.parse(msg.match[2])
     getTitle msg, url_parsed.href


inline string

inline string

--- src/opensong.coffee
+++ src/opensong.coffee
@@ -195,8 +195,8 @@
             if i < chordArr.length - 1
               chordLength = chordArr[i].length
               # split String with RegExp (is there a better way?)
-              m = textLine.match(new RegExp("(.{0," + chordLength + "})(.*)"))
               textArr.push m[1].replace(cleanRegExp, "")
+              m = textLine.match(new RegExp("(.{0,#{chordLength}})(.*)"))
               textLine = m[2]
             else
               # add the whole string if at the end of the chord arr


:racehorse: Avoid recomputing state for lines twice

--- src/text-editor-presenter.coffee
+++ src/text-editor-presenter.coffee
@@ -82,8 +82,14 @@
     @updateCommonGutterState()
     @updateReflowState()

+    if @shouldUpdateDecorations
+      @fetchDecorations()
+      @updateLineDecorations()
+
     if @shouldUpdateLinesState or @shouldUpdateLineNumbersState
       @updateTilesState()
+      @shouldUpdateLinesState = false
+      @shouldUpdateLineNumbersState = false
       @shouldUpdateTilesState = true

     @updating = false
@@ -105,7 +111,7 @@
     @updateScrollbarsState() if @shouldUpdateScrollbarsState
     @updateHiddenInputState() if @shouldUpdateHiddenInputState
     @updateContentState() if @shouldUpdateContentState
-    @updateDecorations() if @shouldUpdateDecorations
+    @updateHighlightDecorations() if @shouldUpdateDecorations
     @updateTilesState() if @shouldUpdateTilesState
     @updateCursorsState() if @shouldUpdateCursorsState
     @updateOverlaysState() if @shouldUpdateOverlaysState
@@ -1195,22 +1201,34 @@

     @emitDidUpdateState()

-  updateDecorations: ->
+  fetchDecorations: ->
+    @decorations = []
+
+    return unless 0 <= @startRow <= @endRow <= Infinity
+
+    for markerId, decorations of @model.decorationsForScreenRowRange(@startRow, @endRow - 1)
+      range = @model.getMarker(markerId).getScreenRange()
+      for decoration in decorations
+        @decorations.push({decoration, range})
+
+  updateLineDecorations: ->
     @rangesByDecorationId = {}
     @lineDecorationsByScreenRow = {}
     @lineNumberDecorationsByScreenRow = {}
     @customGutterDecorationsByGutterNameAndScreenRow = {}
+
+    for {decoration, range} in @decorations
+      if decoration.isType('line') or decoration.isType('gutter')
+        @addToLineDecorationCaches(decoration, range)
+
+    return
+
+  updateHighlightDecorations: ->
     @visibleHighlights = {}

-    return unless 0 <= @startRow <= @endRow <= Infinity
-
-    for markerId, decorations of @model.decorationsForScreenRowRange(@startRow, @endRow - 1)
-      range = @model.getMarker(markerId).getScreenRange()
-      for decoration in decorations
-        if decoration.isType('line') or decoration.isType('gutter')
-          @addToLineDecorationCaches(decoration, range)
-        else if decoration.isType('highlight')
-          @updateHighlightState(decoration, range)
+    for {decoration, range} in @decorations
+      if decoration.isType('highlight')
+        @updateHighlightState(decoration, range)

     for tileId, tileState of @state.content.tiles
       for id, highlight of tileState.highlights


work around results being re-attached on click

--- lib/tree.coffee
+++ lib/tree.coffee
@@ -9,8 +9,9 @@
       div 'header gutted', head
       div 'body gutted', children
     ]
-    view.querySelector(':scope > .header').onclick = => @toggle view
-    view.querySelector(':scope > .icon').onclick = => @toggle view
+    for sel in [':scope > .header', ':scope > .icon']
+      view.querySelector(sel).onclick = =>
+        setTimeout (=> @toggle view), 0
     @toggle view unless expand
     view


<model> Driver - Covering reinit

<model> Driver - Covering reinit

--- server/models/driver.coffee
+++ server/models/driver.coffee
@@ -128,7 +128,7 @@
 				callback 'Driver already added', drivers[0]
 			else
 				# Create directory for the driver's files:
-				dirName = DRIVERS_DIR+file.name
+				dirName = DRIVERS_DIR + file.name + "/"
 				mkdirp(dirName, (err) ->
 					if err
 						callback 'Error creating the directory for the driver', null
@@ -175,4 +175,47 @@
 										sensorsDrivers[driver.id] = driverModule if driver.isSensor
 										actuatorsDrivers[driver.id] = driverModule if driver.isActuator
 										callback null, driver
-								+
+	###
+	# reinit
+	# ====
+	# Re-initializes the drivers and their devices
+	# @param callback (Function(Error):null): 	Callback
+	###
+	@reinit: (callback) ->
+		@request 'all', (err, drivers)
+			return callback err if err
+
+			# Clearing the previous drivers arrays (without changing the ref!)
+			for k,v in sensorsDrivers
+				delete sensorsDrivers[k] if sensorsDrivers.hasOwnProperty(k)
+			for k,v in actuatorsDrivers
+				delete actuatorsDrivers[k] if actuatorsDrivers.hasOwnProperty(k)
+
+			# Refilling them:
+			for driver in drivers
+				driverModule = require (DRIVERS_DIR + file.name + "/" + file.name)
+				driverModule.init (err) ->
+					return callback 'Error initializing the drivers' if err
+
+					sensorsDrivers[driver.id] = driverModule if driver.isSensor
+					actuatorsDrivers[driver.id] = driverModule if driver.isActuator
+
+			# Readding the devices per driver:
+			async.parallel([
+				(cb) ->
+					Sensor.request 'all', (err, sensors) ->
+						return cb err, null if err
+
+						async.each sensors, (sensor, cb2) ->
+							sensorsDrivers[sensor.driverId].add sensor.customId, sensor.id, cb2
+						, cb
+				, (cb) ->
+					Actuator.request 'all', (err, actuators) ->
+						return cb err, null if err
+
+						async.each actuators, (actuator, cb2) ->
+							sensorsDrivers[actuator.driverId].add actuator.customId, actuator.id, cb2
+						, cb
+			], (err, results) -> callback err
+


More debug output

More debug output

--- server/models/submit.coffee
+++ server/models/submit.coffee
@@ -61,10 +61,10 @@
         logger.log "s2=`#{Buffer.from(other.source)}`, `#{Buffer.from(other.source.replace("\r", ""))}`"
         s1 = @source.replace("\r", "")
         s2 = other.source.replace("\r", "")
-        logger.log "s1,s2.length=", s1.length, s2.length
+        logger.log "s1,s2.length=", s1.length, s2.length, @_id, other._id
         for i in [0..s1.length - 1]
             if s1.charAt(i) != s2.charAt(i)
-                print("!!!", i)
+                logger.log "!!!", i, , @_id, other._id
                 break
     if @comments.length > 0
         return false


More debug output

More debug output

--- server/models/submit.coffee
+++ server/models/submit.coffee
@@ -57,8 +57,8 @@
 submitsSchema.methods.equivalent = (other) ->
     if @user == "394891"
         logger.log "Compare submits ", @_id, other._id, @comments.length > 0, @outcome == "AC" or @outcome == "IG" or @outcome == "DQ", @force, @user == other.user, @problem == other.problem, outcomeType(@outcome) == outcomeType(other.outcome), @source.replace("\r", "") == other.source.replace("\r", ""), Math.abs(@time - other.time)
-        logger.log "s1=`#{@source}` `#{@source.replace("\r", "")}`"
-        logger.log "s2=`#{other.source}`, `#{other.source.replace("\r", "")}`"
+        logger.log "s1=`#{Buffer.from(@source)}` `#{Buffer.from(@source.replace("\r", ""))}`"
+        logger.log "s2=`#{Buffer.from(other.source)}`, `#{Buffer.from(other.source.replace("\r", ""))}`"
     if @comments.length > 0
         return false
     if @outcome == "AC" or @outcome == "IG" or @outcome == "DQ"


readme.md

readme.md

--- app.coffee
+++ app.coffee
@@ -1,9 +1,4 @@
 ####### Framer Settings #######
-# deviceType can be either "phone" or "desktop" or "custom"
-#Framer.DeviceView.Devices["desktop"] =
-#	"deviceType": "screen"
-#	"screenWidth": 40
-#	"screenHeight": 40
 Framer.Device.deviceType = "fullscreen"

 ####### Setup MQTT #######
@@ -30,7 +25,7 @@
 			layer.states.switch("hide")
 	message

-##################################
+####### Layers #######

 stateNames = ["red", "green", "blue"]
 LayerObject = []


adding 1.1.1

adding 1.1.1


also log player that sent a word

also log player that sent a word

--- app.coffee
+++ app.coffee
@@ -124,7 +124,7 @@
     game.addWord(socket.player, word)

     if (game.start)
-      console.log("WORD " + word)
+      console.log("WORD #{word} from #{socket.player.name}")
       socket.emit 'new word',
         player: socket.player.name,
         word: word


refactor out https to github

refactor out https to github

--- app.coffee
+++ app.coffee
@@ -7,6 +7,15 @@
     host: "api.github.com"
     path: "/repos/intermine/InterMine/issues"
     method: "GET"
+
+getIssues = (callback) ->
+    https.request(options, (response) ->
+        if response.statusCode is 200
+            json = ""
+            response.on "data", (chunk) -> json += chunk
+
+            response.on "end", -> callback JSON.parse json
+    ).end()

 app = express.createServer()

@@ -34,16 +43,10 @@

 # Routes
 app.get '/issues', (req, res) ->
-    https.request(options, (response) ->
-        if response.statusCode is 200
-            json = ""
-            response.on "data", (chunk) -> json += chunk
-
-            response.on "end", ->
-                res.render 'issues',
-                    'issues': JSON.parse json
-                , (html) -> res.send html, 'Content-Type': 'text/html', 200
-    ).end()
+    getIssues (issues) ->
+        res.render 'issues',
+            'issues': issues
+        , (html) -> res.send html, 'Content-Type': 'text/html', 200

 app.listen 3000
 console.log "Express server listening to port 3000"


add coffeekup

add coffeekup

--- app.coffee
+++ app.coffee
@@ -1,7 +1,7 @@
 connect = require('./lib/connect')
 sys = require("sys")
 http = require('http')
-coffeekup = require('coffeekup')
+coffeekup = require('./lib/coffeekup.coffee')

 # fs = require('fs')
 #underscore not yet in use


port 80

port 80

--- app.coffee
+++ app.coffee
@@ -6,4 +6,4 @@
 require('./config/routes') app

 # Setup the port
-app.listen 3535
+app.listen 80


Use library-supplied getRootDir/Fallback

--- lib/util.coffee
+++ lib/util.coffee
@@ -3,6 +3,7 @@
 Temp = require('temp')
 FS = require('fs')
 {EOL} = require('os')
+HsUtil = require 'atom-haskell-utils'

 debuglog = []
 logKeep = 30000 #ms
@@ -33,38 +34,9 @@
       "#{(timestamp - ts) / 1000}s: #{messages.join ','}"
     .join EOL

-  getRootDirFallback: (buffer) ->
-    [dir] = atom.project.getDirectories().filter (dir) ->
-      dir.contains(buffer.getUri())
-    unless dir?
-      dir = atom.project.getDirectories()[0]
-    if dir?.getPath?() is 'atom://config'
-      dir = null
-    unless dir?.isDirectory?()
-      dir = buffer.file?.getParent?() ? new Directory '.'
-    dir
+  getRootDirFallback: HsUtil.getRootDirFallback

-  getRootDir: (buffer) ->
-    dirHasCabalFile = (d) ->
-      return false unless d?
-      d.getEntriesSync().some (file) ->
-        file.isFile() and file.getBaseName().endsWith '.cabal'
-    dirHasSandboxFile = (d) ->
-      return false unless d?
-      d.getEntriesSync().some (file) ->
-        file.isFile() and (file.getBaseName() is 'cabal.sandbox.config')
-    findProjectRoot = (d, check) ->
-      until d?.isRoot?() or not d? or check d
-        d = d?.getParent?()
-      d if check d
-    dir = buffer.file?.getParent?() ? Util.getRootDirFallback buffer
-    dir = findProjectRoot(dir, dirHasCabalFile) ? findProjectRoot(dir, dirHasSandboxFile)
-    unless dir?.isDirectory?()
-      dir = Util.getRootDirFallback buffer
-    Util.debug "getRootDir path = #{dir.getPath()}",
-      "atom.project.getDirectories()[0] = #{atom.project.getDirectories()[0]?.getPath?()}",
-      "buffer.file?.getParent?() = #{buffer.file?.getParent?()?.getPath?()}"
-    return dir
+  getRootDir: HsUtil.getRootDir

   getProcessOptions: (rootPath) ->
     Util.debug "getProcessOptions(#{rootPath})"


back-end address 115.28.215.182

back-end address 115.28.215.182

--- src/scripts/config.coffee
+++ src/scripts/config.coffee
@@ -3,7 +3,7 @@
 .factory 'CONFIG', ->

   local = window.location.origin
-  base_host = '0.0.0.0:5000'
+  base_host = 'http://115.28.215.182:5000'
   test_host = '172.17.3.143'
   host = base_host
   api = "#{host}/api"


another router.coffee change

another router.coffee change

--- src/scripts/router.coffee
+++ src/scripts/router.coffee
@@ -23,8 +23,7 @@
         @appView.render('contents')

       # Match and extract uuid and page numbers separated by a colon
-      #@route /^contents\/([^:]+):?([0-9]*)/, 'media', (uuid, page) ->
-      @route /^contents\/([^:]+):?([^:]+)/, 'media', (uuid, page) ->
+      @route /^contents\/([^:]+):?([^:]*)/, 'media', (uuid, page) ->
         uuid = uuid.toLowerCase()
         uuid = settings.shortcodes[uuid] if settings.shortcodes[uuid]
         @appView.render('contents', {uuid: uuid, page: Number(page)})


Fix bug with js auto slug and accented letters

--- app/assets/javascripts/admin/form_translation.coffee
+++ app/assets/javascripts/admin/form_translation.coffee
@@ -38,6 +38,10 @@
   i = 0
   l = from.length

+  while i < l
+    str = str.replace(new RegExp(from.charAt(i), "g"), to.charAt(i))
+    i++
+
   str = str.toString().toLowerCase()
       .replace(/\s+/g, '-')           # Replace spaces with -
       .replace(/[^\w\-]+/g, '')       # Remove all non-word chars
@@ -45,8 +49,4 @@
       .replace(/^-+/, '')             # Trim - from start of text
       .replace(/-+$/, '');            # Trim - from end of text

-  while i < l
-    str = str.replace(new RegExp(from.charAt(i), "g"), to.charAt(i))
-    i++
-
   str


remove stray console.log in submit_button

--- app/assets/javascripts/components/submit_button.cjsx
+++ app/assets/javascripts/components/submit_button.cjsx
@@ -9,7 +9,6 @@
     if @props.enabled
       QuestionnaireStore.submitReport()
     else
-      console.log(QuestionnaireStore.unansweredQuestionsForAllPages())
       alert("""
         Sorry! You can't submit this report yet as there are some required questions without answers.


Fixed weight create parameters

--- app/assets/javascripts/components/weight_form.coffee
+++ app/assets/javascripts/components/weight_form.coffee
@@ -1,8 +1,8 @@
 @WeightForm = React.createClass
   getInitialState: ->
-    user_id: ''
+    user_id: @props.user.id
     entry: ''
-    date: moment().format('YYYY-mm-DD')
+    date: moment().format('YYYY-MM-DD')
     time: moment().format('HH:mm')
   handleChange: (e) ->
     name = e.target.name


Fixed shelljs return type (function or object)

--- src/dispatch.coffee
+++ src/dispatch.coffee
@@ -37,8 +37,12 @@
     # If first argument is one of the ShellCommands list, use shelljs instead.
     argv = command.split(' ')
     if ShellCommands.indexOf(argv[0]) isnt -1
-      shellCode = shell[argv[0]].apply @, argv[1...]
-      logging.debug "shell command '#{command}' returned '#{shellCode}'"
+      shellCmd = shell[argv[0]]
+      if typeof shellCmd is 'function'
+        shellCode = shell[argv[0]].apply @, argv[1...]
+      else
+        shellCode = shellCmd
+      logging.debug "shell command '#{command}' returned '#{JSON.stringify shellCode, undefined, 4}'"
       # check return value
       if err = shell.error()?[...-1]
         deferred.reject "Command '#{command}' returned '#{err}'"


Add show cards for trello script

Add show cards for trello script

--- src/scripts/trello.coffee
+++ src/scripts/trello.coffee
@@ -11,6 +11,7 @@
 #
 # Commands:
 #   hubot trello card <name> - Create a new Trello card
+#   hubot trello show - Show cards on list
 #
 # Notes:
 #   To get your key, go to: https://trello.com/1/appKey/generate
@@ -36,6 +37,9 @@
     if not (process.env.HUBOT_TRELLO_KEY and process.env.HUBOT_TRELLO_TOKEN and process.env.HUBOT_TRELLO_LIST)
       return
     createCard msg, cardName
+
+  robot.respond /trello show/i, (msg) ->
+    showCards msg

 createCard = (msg, cardName) ->
   Trello = require("node-trello")
@@ -45,3 +49,14 @@
       msg.send "There was an error creating the card"
       return
     msg.send data.url
+
+showCards = (msg) ->
+  Trello = require("node-trello")
+  t = new Trello(process.env.HUBOT_TRELLO_KEY, process.env.HUBOT_TRELLO_TOKEN)
+  t.get "/1/lists/"+process.env.HUBOT_TRELLO_LIST, {cards: "open"}, (err, data) ->
+    if err
+      msg.send "There was an error showing the list."
+      return
+
+    msg.send "Cards in " + data.name + ":"
+    msg.send "- " + card.name + for card in data.cards


Fix template based spec runner.

Fix template based spec runner.

--- spec/compiler_spec.coffee
+++ spec/compiler_spec.coffee
@@ -37,15 +37,17 @@
             report += "---------------------------------------------------------------\n"
             report += "Compiler settings: { escape_html: #{ escaping }, format: #{ format } }\n"
             report += "-------------------- Haml template ----------------------------\n"
+
+            if spec.haml_template
+              spec.haml = fs.readFileSync("spec/suites/templates/#{ spec.haml_template }.haml").toString()
+
             report += spec.haml

             if spec.locals
               report +=  "\n-------------------- Local variables --------------------------\n"
               report +=  JSON.stringify(spec.locals)

-            haml = spec.haml || fs.readFileSync(spec.haml_template)
-
-            compiler.parse haml
+            compiler.parse spec.haml
             cst = compiler.render 'test'

             report +=  "\n-------------------- Marked output nodes ----------------------\n"
@@ -68,7 +70,7 @@
             report +=  "\n-------------------- Rendered HTML ---------------------------\n"
             report +=  html

-            spec.html = fs.readFileSync(spec.html_template) if spec.html_template
+            spec.html = fs.readFileSync("spec/suites/templates/#{ spec.html_template }.html").toString() if spec.html_template
             result = spec.html

             report +=  "\n-------------------- Expected result--------------------------\n"


Add specs for Composer::clean

Add specs for Composer::clean

--- spec/composer-spec.coffee
+++ spec/composer-spec.coffee
@@ -1,6 +1,7 @@
 helpers = require './spec-helpers'
 fs = require 'fs-plus'
 path = require 'path'
+_ = require 'underscore-plus'
 Composer = require '../lib/composer'

 describe "Composer", ->
@@ -110,3 +111,42 @@

       runs ->
         expect(composer.showError).toHaveBeenCalled()
+
+  describe "clean", ->
+    extensions = ['.bar', '.baz', '.quux']
+    fakeFilePaths = (filePath) ->
+      filePathSansExtension = filePath.substring(0, filePath.lastIndexOf('.'))
+      filePathSansExtension + ext for ext in extensions
+
+    initializeSpies = (filePath, filesToDelete) ->
+      existsSync = fs.existsSync
+      spyOn(fs, 'existsSync').andCallFake (filePath) ->
+        return _.contains(filesToDelete, filePath)
+        existsSync(filePath)
+
+      spyOn(composer, 'getEditorDetails').andReturn({filePath})
+      spyOn(composer, 'resolveRootFilePath').andReturn(filePath)
+
+    beforeEach ->
+      spyOn(fs, 'removeSync').andReturn()
+      helpers.spyOnConfig('latex.cleanExtensions', extensions)
+
+    it "deletes all files for the current tex document when output has not been redirected", ->
+      filePath = '/a/foo.tex'
+      filesToDelete = fakeFilePaths(filePath)
+      initializeSpies(filePath, filesToDelete)
+
+      composer.clean()
+
+      expect(fs.removeSync.callCount).toBe 3
+      expect(fs.removeSync).toHaveBeenCalledWith(expectedPath) for expectedPath in filesToDelete
+
+    it "only deletes file that exist", ->
+      filePath = '/a/foo.tex'
+      fileToDelete = '/a/foo.bar'
+      initializeSpies(filePath, [fileToDelete])
+
+      composer.clean()
+
+      expect(fs.removeSync.callCount).toBe 1
+      expect(fs.removeSync).toHaveBeenCalledWith(fileToDelete)


Conflict specs pass.

Conflict specs pass.

--- spec/conflict-spec.coffee
+++ spec/conflict-spec.coffee
@@ -8,7 +8,7 @@

     beforeEach ->
       util.openPath 'single-2way-diff.txt', (editorView) ->
-        conflict = Conflict.all({ isRebase: false }, editorView.getEditor())[0]
+        conflict = Conflict.all({ isRebase: false }, editorView.getModel())[0]

     it 'identifies the correct rows', ->
       expect(util.rowRangeFrom conflict.ours.marker).toEqual([1, 2])
@@ -33,7 +33,7 @@

   it "finds multiple conflict markings", ->
     util.openPath 'multi-2way-diff.txt', (editorView) ->
-      cs = Conflict.all({}, editorView.getEditor())
+      cs = Conflict.all({}, editorView.getModel())

       expect(cs.length).toBe(2)
       expect(util.rowRangeFrom cs[0].ours.marker).toEqual([5, 7])
@@ -46,7 +46,7 @@

     beforeEach ->
       util.openPath 'rebase-2way-diff.txt', (editorView) ->
-        conflict = Conflict.all({ isRebase: true }, editorView.getEditor())[0]
+        conflict = Conflict.all({ isRebase: true }, editorView.getModel())[0]

     it 'swaps the lines for "ours" and "theirs"', ->
       expect(util.rowRangeFrom conflict.theirs.marker).toEqual([3, 4])
@@ -69,7 +69,7 @@

     beforeEach ->
       util.openPath 'single-2way-diff.txt', (editorView) ->
-        editor = editorView.getEditor()
+        editor = editorView.getModel()
         [conflict] = Conflict.all {}, editor

     it 'retains a reference to conflict', ->
@@ -107,7 +107,7 @@

     beforeEach ->
       util.openPath 'triple-2way-diff.txt', (editorView) ->
-        conflicts = Conflict.all({}, editorView.getEditor())
+        conflicts = Conflict.all({}, editorView.getModel())
         navigator = conflicts[1].navigator

     it 'knows its conflict', ->


Use fs to write instead of private buffer API

--- spec/git-diff-spec.coffee
+++ spec/git-diff-spec.coffee
@@ -64,10 +64,8 @@

   describe "when a modified file is opened", ->
     it "highlights the changed lines", ->
+      fs.writeFileSync(atom.project.resolve('sample.txt'), "Some different text.")
       nextTick = false
-      filePath = atom.project.resolve('sample.txt')
-      buffer = atom.project.bufferForPathSync(filePath)
-      buffer.setText("Some different text.")

       waitsForPromise ->
         atom.workspace.open('sample.txt')


Improvements to buildSchoolGraph to open a spreadsheet

--- scripts/buildSchoolGraph.coffee
+++ scripts/buildSchoolGraph.coffee
@@ -5,6 +5,8 @@
 log = require 'winston'
 async = require 'async'
 moment = require 'moment'
+fs = require 'fs'
+exec = require('child_process').exec

 ### SET UP ###
 do (setupLodash = this) ->
@@ -35,6 +37,7 @@
   unless userToSchool
     return console.log('Done.') or process.exit() unless [userToSchool, suggestions] = findUserToSchool users
   question ?= formatSuggestions userToSchool, suggestions
+  openTSV userToSchool, suggestions
   prompt question, (answer) ->
     answer = answer.trim()
     return console.log('Bye.') or process.exit() if answer in ['q', 'quit']
@@ -98,11 +101,14 @@
       console.log "Incorrect number #{number} higher than max: #{max}"
   numbers

-formatUser = (user, relativeToUser) ->
-  # TODO: replace date string with relative time since signup compared to target user, and actually make suggestions based on students that signed up at almost the same time
-  props = _.pick(user, ['name', 'emailLower', 'ageRange', 'dateCreated', 'lastLevel', 'points', 'referrer', 'hourOfCode'])
-  props.dateCreated = if relativeToUser then moment(props.dateCreated).from(relativeToUser.dateCreated) else moment(props.dateCreated).fromNow()
-  _.values(props).join('  ')
+formatUser = (user, relativeToUser, separator='  ') ->
+  values = []
+  for key in ['name', 'emailLower', 'ageRange', 'dateCreated', 'lastLevel', 'points', 'referrer', 'hourOfCode']
+    val = user[key]
+    if key is 'dateCreated'
+      val = if relativeToUser then moment(val).from(relativeToUser.dateCreated) else moment(val).fromNow()
+    values.push val
+  values.join separator

 formatSuggestions = (userToSchool, suggestions) ->
   suggestionPrompts = ("#{_.str.rpad(i + 1, 3)}  #{_.str.rpad(s.schoolName, 50)} #{s.reasons.length} #{if s.reasons.length > 1 then 'Matches' else 'Match'}: #{s.reasons.join(', ')}\tfrom user: #{formatUser(s.user, userToSchool)}" for s, i in suggestions).join('\n')
@@ -113,6 +119,18 @@
   #{suggestionPrompts}
   Choose a number, type a name, enter to skip, or q to quit.
   > """
+
+openTSV = (userToSchool, suggestions) ->
+  header = ['#', 'School Name', 'Matches', 'Name', 'Email', 'Age', 'Signup', 'Last Level', 'Points', 'Referrer', 'HoC'].join '\t'
+  rows = [[0, userToSchool.schoolName, '', formatUser(userToSchool, null, '\t')].join '\t']
+  for s, i in suggestions
+    matches = s.reasons.length + ' ' + if s.reasons.length > 1 then 'Matches' else 'Match' + ': ' + s.reasons.join(', ')
+    rows.push [i + 1, s.schoolName, matches, formatUser(s.user, userToSchool, '\t')].join '\t'
+  contents = [header].concat(rows).join('\n') + '\n'
+  path = "#{process.env.HOME}/Downloads/#{userToSchool.emailLower}.tsv"
+  fs.writeFile path, contents, {flags: 'w'}, (err) ->
+    console.log 'Error writing school suggestions TSV:', err if err
+    exec "open -a /Applications/Numbers.app #{path}"

 checkedTopGroups = {}
 findUserToSchool = (users) ->
@@ -147,6 +165,7 @@

 findSuggestions = (target) ->
   # Look for other users with the same IP, course instances, clans, or similar school names or non-common shared email domains.
+  # TODO: Actually make suggestions based on students that signed up at almost the same time
   suggestions = []
   t0 = new Date()
   if debugging then console.log '  Checking suggestions for', target.emailLower, target.schoolName, (new Date()) - t0
@@ -208,7 +227,7 @@
           when 'IP' then 40
           when 'Referrer' then 20
           when 'Name' then 15
-          when 'Domain' then (if getDomain(target) in ['cps.edu', 'mynewcaneyisd.org', 'fsusd.org'] then 1 else 10)
+          when 'Domain' then (if getDomain(target) in ['cps.edu', 'mynewcaneyisd.org', 'fsusd.org', 'edison.k12.nj.us'] then 1 else 10)
           when 'Clans' then 0.01
     sum
   ), 0


Don't show admin characters in Yearbook.

--- packages/pixelartacademy-pixelboy-yearbook/subscriptions.coffee
+++ packages/pixelartacademy-pixelboy-yearbook/subscriptions.coffee
@@ -9,6 +9,7 @@
   collectionName = Yearbook.studentsCollectionName

   RA.User.documents.find(
+    username: $ne: 'admin'
     characters: $exists: true
   ,
     fields:


Added Event register.

Added Event register.

--- src/fattable.coffee
+++ src/fattable.coffee
@@ -39,6 +39,7 @@
     document.removeEventListener "DOMContentLoaded", onLoad
     domReadyPromise.resolve()
 document.addEventListener "DOMContentLoaded", onLoad
+

 class TableData

@@ -267,9 +268,25 @@

+
+class EventRegister
+
+    constructor: ->
+        @boundEvents = []
+
+    bind: (target, evt, cb)->
+        @boundEvents.push [target, evt, cb]
+        target.addEventListener evt, cb
+
+    unbindAll: ->
+        for [target, evt, cb] in @boundEvents
+            target.removeEventListener evt, cb
+        @boundEvents = []
+
+
 class ScrollBarProxy

-    constructor: (@container, @W, @H)->
+    constructor: (@container, @W, @H, eventRegister)->
         @verticalScrollbar = document.createElement "div"
         @verticalScrollbar.className += " fattable-v-scrollbar"
         @horizontalScrollbar = document.createElement "div"
@@ -299,16 +316,16 @@
                 @onScroll @scrollLeft,@scrollTop

         # setting up middle click drag
-        @container.addEventListener 'mousedown', (evt)=>
+        eventRegister.bind @container, 'mousedown', (evt)=>
             if evt.button == 1
                 @dragging = true
                 @container.className = "fattable-body-container fattable-moving"
                 @dragging_dX = @scrollLeft + evt.clientX
                 @dragging_dY = @scrollTop + evt.clientY
-        @container.addEventListener 'mouseup', =>
+        eventRegister.bind @container, 'mouseup', =>
             @dragging = false
             @container.className = "fattable-body-container"
-        @container.addEventListener 'mousemove', (evt)=>
+        eventRegister.bind @container, 'mousemove', (evt)=>
             # Firefox pb see https://bugzilla.mozilla.org/show_bug.cgi?id=732621
             deferred = =>
                 if @dragging
@@ -316,8 +333,8 @@
                     newY = -evt.clientY + @dragging_dY
                     @setScrollXY newX, newY
             window.setTimeout deferred, 0
-
-        @container.addEventListener 'mouseout', (evt)=>
+
+        eventRegister.bind @container, 'mouseout', (evt)=>
             if @dragging
                 if (evt.toElement == null) || (evt.toElement.parentElement.parentElement != @container)
                     @container.className = "fattable-body-container"
@@ -336,12 +353,10 @@
             # TODO support other browsers
             if evt.type is "mousewheel"
                 @setScrollXY @scrollLeft - evt.wheelDeltaX, @scrollTop - evt.wheelDeltaY
-
-        if @container.addEventListener
-            @container.addEventListener "mousewheel", onMouseWheel, false
-            @container.addEventListener "DOMMouseScroll", onMouseWheel, false
-        else @container.attachEvent "onmousewheel", onMouseWheel
-
+
+        eventRegister.bind @container, "mousewheel", onMouseWheel
+        eventRegister.bind @container, "DOMMouseScroll", onMouseWheel
+
     onScroll: (x,y)->

     setScrollXY: (x,y)->
@@ -352,6 +367,7 @@
         @horizontalScrollbar.scrollLeft = x
         @verticalScrollbar.scrollTop = y
         @onScroll x,y
+

 class TableView

@@ -390,8 +406,8 @@
         @W = @columnOffset[@columnOffset.length-1]
         @columns = {}
         @cells = {}
-        domReadyPromise.then => @setup()
-
+        @eventRegister = new EventRegister()
+        domReadyPromise.then => @setup()

     visible: (x,y)->
         # returns the square
@@ -400,8 +416,15 @@
         j = bound binary_search(@columnOffset, x), 0, (@nbCols - @nbColsVisible)
         [i, j]

+    cleanUp: ->
+        # be nice rewind !
+        @eventRegister.unbindAll()
+        @ScrollBarProxy?.onScroll = null
+        @painter.cleanUp this
+
     setup: ->
-        @painter.cleanUp(this);
+        @cleanUp()
+
         # can be called when resizing the window
         @columns = {}
         @cells = {}
@@ -449,6 +472,7 @@
             @painter.setupColumnHeader el
             @columns[c] = el
             @headerViewport.appendChild el
+
         @firstVisibleRow = @nbRowsVisible
         @firstVisibleColumn = @nbColsVisible
         @display 0,0
@@ -456,7 +480,7 @@
         @container.appendChild @headerContainer
         @bodyContainer.appendChild @bodyViewport
         @refreshAllContent()
-        @scroll = new ScrollBarProxy @bodyContainer, @W, @H
+        @scroll = new ScrollBarProxy @bodyContainer, @W, @H, @eventRegister
         @scroll.onScroll = (x,y)=>
             [i,j] = @visible x,y
             @display i,j


Shrink header buttons

Shrink header buttons

--- SingularityUI/app/components/aggregateTail/Header.cjsx
+++ SingularityUI/app/components/aggregateTail/Header.cjsx
@@ -54,7 +54,7 @@

-            <button type="button" className="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+            <button type="button" className="btn btn-default btn-sm dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">

             </button>

@@ -76,17 +76,17 @@

-            <button type="button" className="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+            <button type="button" className="btn btn-default btn-sm dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">

             </button>

               {@renderListItems()}

-
+
             All to bottom

-
+
             All to top


Add spaces

Add spaces

--- workers/social/lib/social/models/account.coffee
+++ workers/social/lib/social/models/account.coffee
@@ -1137,7 +1137,7 @@
           return callback err  if err

           data            = {}
-          data[username]  = email  for {username, email} in list
+          data[username]  = email  for { username, email } in list

           callback null, data


removed more unused methods

removed more unused methods

--- workers/social/lib/social/models/account.coffee
+++ workers/social/lib/social/models/account.coffee
@@ -75,8 +75,6 @@
           (signature Object, Object, Function)
         someData:
           (signature Object, Object, Object, Function)
-        getAutoCompleteData:
-          (signature String, String, Function)
         count: [
           (signature Function)
           (signature Object, Function)
@@ -85,8 +83,6 @@
           (signature String, Function)
           (signature String, Object, Function)
         ]
-        fetchVersion:
-          (signature Function)
         reserveNames: [
           (signature Function)
           (signature Object, Function)
@@ -112,10 +108,6 @@
           (signature Object, Function)
         fetchRole:
           (signature Function)
-        flagAccount:
-          (signature String, Function)
-        unflagAccount:
-          (signature String, Function)
         isFollowing:
           (signature String, String, Function)
         updateFlags:
@@ -124,13 +116,7 @@
           (signature Function)
           (signature Object, Function)
         ]
-        fetchGroupRoles:
-          (signature String, Function)
         fetchGroupsWithPendingInvitations: [
-          (signature Function)
-          (signature Object, Function)
-        ]
-        fetchGroupsWithPendingRequests: [
           (signature Function)
           (signature Object, Function)
         ]
@@ -139,8 +125,6 @@
         acceptInvitation:
           (signature String, Function)
         ignoreInvitation:
-          (signature String, Function)
-        fetchMyGroupInvitationStatus:
           (signature String, Function)
         fetchMyPermissions:
           (signature Function)
@@ -158,8 +142,6 @@
           (signature String, Function)
         markUserAsExempt:
           (signature Boolean, Function)
-        userIsExempt:
-          (signature Function)
         checkGroupMembership:
           (signature String, Function)
         fetchStorage:
@@ -523,22 +505,6 @@
                 roles = (doc.as for doc in groupedDocs[group.getId()])
                 return { group, roles }

-  fetchGroupRoles: secure (client, slug, callback)->
-    {delegate} = client.connection
-    JGroup     = require './group'
-    JGroup.fetchIdBySlug slug, (err, groupId)->
-      if err then callback err
-      else
-        selector = {
-          sourceId: groupId
-          targetId: delegate.getId()
-        }
-        Relationship.someData selector, {as:1}, (err, cursor)->
-          if err then callback err
-          else
-            cursor.toArray (err, arr)->
-              if err then callback err
-              else callback null, (doc.as for doc in arr)

   @verifyEmailByUsername = secure (client, username, callback)->
     {connection:{delegate}, sessionToken} = client
@@ -575,7 +541,6 @@
                   options.skip += options.limit
                   @reserveNames options, callback

-  @fetchVersion =(callback)-> callback null, KONFIG.version

   @fetchBlockedUsers = secure ({connection:{delegate}}, options, callback) ->
     unless delegate.can 'list-blocked-users'
@@ -639,27 +604,6 @@
       sort    : 'profile.firstName' : 1
     }, callback

-  @getAutoCompleteData = (fieldString, queryString, callback)->
-    query = {}
-    desiredData = {}
-    query[fieldString] = RegExp queryString, 'i'
-    desiredData[fieldString] = yes
-    @someData query, desiredData, (err, cursor)->
-      cursor.toArray (err, docs)->
-        results = []
-        for doc in docs
-          results.push doc.profile.fullname
-        callback err, results
-
-  # I wrote it and decided that it is not necessary, feel free to remove ~ GG
-  #
-  # @filterUsernames = permit 'list members',
-  #   success: (client, nick, options, callback)->
-  #     [callback, options] = [options, callback]  unless callback
-  #     options or= {}
-  #     options.limit = 10
-  #     query = 'profile.nickname' : ///^#{nick}///
-  #     @some query, options, callback

   setEmailPreferences: (user, prefs, callback)->
     current = user.getAt('emailFrequency') or {}
@@ -715,25 +659,6 @@
                   "sent-hil", "cihangirsavas", "leeolayvar", "stefanbc",
                   "szkl", "canthefason", "nitin", "usirin", "kodinglearn" ] # kodinglearn is nitin's impersonation account

-  userIsExempt: (callback)->
-    # console.log @isExempt, this
-    callback null, @isExempt
-
-  # returns troll users ids
-  #
-  # Adding a temporary limit of 100. We currently've 24 trolls, by the
-  # time this limit runs out we'll have switched to a scalable model of
-  # filtering troll users. SA
-  @getExemptUserIds: (callback)->
-    JAccount.someData {isExempt:true}, {_id:1, limit:100}, {sort:_id:-1}, (err, cursor)->
-      return callback err, null if err
-      ids = []
-      cursor.each (err, account)->
-        return callback err, null if err
-        if account
-            ids.push account._id.toString()
-        else
-            callback null, ids

   isEmailVerified: (callback)->
     @fetchUser (err, user)->
@@ -770,21 +695,6 @@
       else
         unmarkAsTroll {accountId}, callback

-  flagAccount: secure (client, flag, callback)->
-    {delegate} = client.connection
-    JAccount.taint @getId()
-    if delegate.can 'flag', this
-      @update {$addToSet: globalFlags: flag}, callback
-    else
-      callback new KodingError 'Access denied'
-
-  unflagAccount: secure (client, flag, callback)->
-    {delegate} = client.connection
-    JAccount.taint @getId()
-    if delegate.can 'flag', this
-      @update {$pullAll: globalFlags: [flag]}, callback
-    else
-      callback new KodingError 'Access denied'

   updateFlags: secure (client, flags, callback)->
     {delegate} = client.connection
@@ -882,11 +792,6 @@

   # temp dummy stuff ends

-  fetchPrivateChannel:(callback)->
-    require('bongo').fetchChannel @getPrivateChannelName(), callback
-
-  getPrivateChannelName:-> "private-#{@getAt('pro file.nickname')}-private"
-

   modify: secure (client, fields, callback) ->

@@ -926,7 +831,6 @@
     else
       callback new KodingError 'Access denied'

-  setClientId:(@clientId)->

   getFullName:->
     {firstName, lastName} = @data.profile
@@ -1070,32 +974,10 @@
       JGroup = require './group'
       JGroup.some _id:$in:(rel.sourceId for rel in rels), options, callback

-  fetchGroupsWithPendingRequests:(options, callback)->
-    @fetchGroupsWithPending 'Request', 'pending', options, callback

   fetchGroupsWithPendingInvitations:(options, callback)->
     @fetchGroupsWithPending '', 'sent', options, callback

-  fetchMyGroupInvitationStatus: secure (client, slug, callback)->
-    return  unless @equals client.connection.delegate
-
-    JGroup = require './group'
-
-    JGroup.one { slug }, (err, group) ->
-      return callback err  if err
-
-      selector = sourceId: group.getId()
-
-      options = targetOptions: selector: status: 'pending'
-      @fetchInvitationRequests selector, options, (err, requests)=>
-        return callback err                if err
-        return callback null, 'requested'  if requests?[0]
-
-        options = targetOptions: selector: status: 'sent'
-        @fetchInvitations selector, options, (err, invites)=>
-          return callback err              if err
-          return callback null, 'invited'  if invites?[0]
-          return callback null, no

   cancelRequest: secure (client, slug, callback)->
     options = targetOptions: selector: status: 'pending'


Fix bug caused by 707b1cf0d

Fix bug caused by 707b1cf0d

--- src/code/views/file-dialog-tab-view.coffee
+++ src/code/views/file-dialog-tab-view.coffee
@@ -93,7 +93,7 @@

   getStateForFolder: (folder) ->
     metadata = if @isOpen() then @state?.metadata or null else @props.client.state.metadata
-    metadata.parent = folder
+    metadata?.parent = folder

     folder: folder
     metadata: metadata


Clone metadata in file view

Clone metadata in file view

When we update this view's state, if we haven't changed the filename
then we are operating directly on the client's own metadata.

This was causing a bug where, if we exported a secondary file) which
should create new metadata, but changed the provider without changing
the filename, the client.state.metadata's provider would change too.

--- src/code/views/file-dialog-tab-view.coffee
+++ src/code/views/file-dialog-tab-view.coffee
@@ -92,7 +92,7 @@
     @setState list: list

   getStateForFolder: (folder) ->
-    metadata = if @isOpen() then @state?.metadata or null else @props.client.state.metadata
+    metadata = if @isOpen() then @state?.metadata or null else _.cloneDeep @props.client.state.metadata
     metadata?.parent = folder

     if @props.client.state.metadata and (@props.provider isnt @props.client.state.metadata.provider)


IDE: Adde participant modal and view to includes.

--- client/ide/includes.coffee
+++ client/ide/includes.coffee
@@ -68,6 +68,9 @@
   "../../website/a/js/sketchpad.js"
   "../../website/a/js/gapi.js"

-  # realtime
+  # collaboration
   "RealTimeManager.coffee"
+  "views/modals/participantview.coffee"
+  "views/modals/participantsmodal.coffee"
+
 ]


bumping version to 0.7.2 for --options patch.

--- coffee-script.coffee
+++ coffee-script.coffee
@@ -22,7 +22,7 @@
   helpers:      this.helpers

 # The current CoffeeScript version number.
-exports.VERSION: '0.7.1'
+exports.VERSION: '0.7.2'

 # Instantiate a Lexer for our use here.
 lexer: new Lexer


Following symlinks when setting module.paths (fixes #2175)

--- coffee-script.coffee
+++ coffee-script.coffee
@@ -68,7 +68,7 @@
   mainModule.moduleCache and= {}

   # Assign paths for node_modules loading
-  mainModule.paths = require('module')._nodeModulePaths path.dirname options.filename
+  mainModule.paths = require('module')._nodeModulePaths path.dirname fs.realpathSync options.filename

   # Compile.
   if path.extname(mainModule.filename) isnt '.coffee' or require.extensions


Remove $.canvasForExport; add onInit callback to opts

--- coffee/jquery.coffee
+++ coffee/jquery.coffee
@@ -49,6 +49,9 @@
   $(window).resize(resize)
   resize()

+  if 'onInit' of opts
+    opts.onInit(lc)
+
   [lc, tb]

@@ -56,7 +59,3 @@
   @each (ix, el) =>
     [el.literallycanvas, el.literallycanvasToolbar] = LC.init(el, opts)
   this
-
-
-$.fn.canvasForExport = ->
-  @get(0).literallycanvas.canvasForExport()


Turning down the throttle to 25ms. 100ms => 10fps which is much too laggy and slow.

--- static/coffee/common/plot.coffee
+++ static/coffee/common/plot.coffee
@@ -59,8 +59,8 @@

   initialize: (options) ->
     super(_.defaults(options, @default_options))
-    @throttled_render = _.throttle(@render, 100)
-    @throttled_render_canvas = _.throttle(@render_canvas, 100)
+    @throttled_render = _.throttle(@render, 15)
+    @throttled_render_canvas = _.throttle(@render_canvas, 15)

     @title_props = new text_properties(@, {}, 'title_')


after task save, url for calendar date should be date only

--- src/components/task-plan/plan-mixin.coffee
+++ src/components/task-plan/plan-mixin.coffee
@@ -100,7 +100,9 @@
     if dueAt?
       date = dueAt
     else
-      date = moment(TimeStore.getNow()).format(CALENDAR_DATE_FORMAT)
+      date = TimeStore.getNow()
+
+    date = moment(date).format(CALENDAR_DATE_FORMAT)

     unless TaskPlanStore.isNew(id) or TaskPlanStore.isPublishing(id) or TaskPlanStore.isDeleteRequested(id)
       calendarRoute = 'calendarViewPlanStats'


improved calculation of opposite geometry

--- evolution.coffee
+++ evolution.coffee
@@ -125,7 +125,11 @@
         return offset unless offset
         op = @geo.data()
         gd = @geoData
-        @geo.calc( op, -gd[ offset + 1 ], -gd[ offset + 2 ], null, gd[offset] )
+        gd[op]       =  gd[offset]
+        gd[ op + 1 ] = -gd[offset + 1]
+        gd[ op + 2 ] = -gd[offset + 2]
+        gd[ op + 3 ] = ( gd[ offset + 3 ] + 2 ) % 4
+        op
     }

     paramsForType = ( type, dontAdd ) ->


Need to escape plus

Need to escape plus

--- frontend/registration_tz_selection.coffee
+++ frontend/registration_tz_selection.coffee
@@ -20,7 +20,7 @@
   # Should match system time zone
   # System time zone is our time zone
   offset_hours = (new Date()).getTimezoneOffset() / -60
-  sign = if offset_hours < 0 then '-' else '+'
+  sign = if offset_hours < 0 then '-' else '\\+'
   offset = Math.abs(offset_hours)
   if offset < 10
     offset = '0' + offset


Removed un-needed pub/sub

Removed un-needed pub/sub

--- packages/simplelog/simplelog.coffee
+++ packages/simplelog/simplelog.coffee
@@ -23,12 +23,6 @@
 Logs.collection.allow
     insert: -> true

-if Meteor.isClient
-    Meteor.subscribe "logs"
-
-if Meteor.isServer
-    Meteor.publish "logs", -> Logs.collection.find()
-
 logFixture = ->
     fixture =
         "message": "loaded /patients"


:art: Extract concatenation on Bacon update prop

--- src/atom/notational-items.coffee
+++ src/atom/notational-items.coffee
@@ -5,12 +5,14 @@
 columns = require './columns'
 R       = require 'ramda'

+concatNewItem = R.flip(R.invoker(1, 'concat'))
+
 # @param {Stream} watchedProjectsStream objects containing a path {String} and a task {Task}
 # @param {Stream} removedStream paths that are removed
 # @return {Property} array of projects
 createProjectsProp = (watchedProjectsStream, removedStream) ->
   Bacon.update [],
-    [watchedProjectsStream], R.flip(R.invoker(1, 'concat'))
+    [watchedProjectsStream], concatNewItem
     [removedStream], (projects, removedPath) ->
       equalsRemovedPath = R.propEq('path', removedPath)
       removedProject = R.find(equalsRemovedPath, projects)
@@ -43,6 +45,7 @@
         task.send('dispose')
       return Bacon.noMore

+
   addItemsStream = watchedProjectsStream.flatMap ({task}) ->
     Bacon.fromEvent(task, 'add')
   removeItemsStream = watchedProjectsStream.flatMap ({task}) ->
@@ -52,8 +55,7 @@
     deactivateBus.push('dispose')

   itemsProp = Bacon.update [],
-    [addItemsStream], (items, newItem) ->
-      items.concat(newItem)
+    [addItemsStream], concatNewItem
     [removeItemsStream], (items, {relPath}) ->
       items.filter (item) ->
         item.relPath isnt relPath


colored background color

colored background color

--- app/views/paragraph.coffee
+++ app/views/paragraph.coffee
@@ -149,12 +149,16 @@

     color = selections.get('lastObject.label.color')
     color = color[0] if Ember.isArray(color) #fix
+    otherColor = color

     if selections.get('length') > 1
       otherColor = selections.get('firstObject.label.color')
       otherColor = otherColor[0] if Ember.isArray(otherColor) #fix
-      color = $.xcolor.combine color, otherColor

-    ""
+    color = $.xcolor.average(color, otherColor)
+    color.a = 0.5 # alpha channel
+
+    ""

 `export default ParagraphView;`


Redefine permission symbols

Redefine permission symbols

--- scripts/shoot.coffee
+++ scripts/shoot.coffee
@@ -15,14 +15,25 @@

     switch

-      # CROSS MARK: http://www.fileformat.info/info/unicode/char/274c/index.htm
-      when ~ text.indexOf "#\u274c"
-        text = text.replace /#\u274c/g, ''
+      when text.indexOf("\ud83d\ude4a ") is 0, \ # SPEAK-NO-EVIL MONKEY
+           text.indexOf("\ud83d\ude37 ") is 0, \ # FACE WITH MEDICAL MASK
+           text.indexOf("\u3299\ufe0f ") is 0, \ # CIRCLED IDEOGRAPH SECRET
+           text.indexOf("\ud83d\ude45 ") is 0, \ # FACE WITH NO GOOD GESTURE
+           text.indexOf("\ud83c\udd96 ") is 0, \ # SQUARED NG
+           text.indexOf("\ud83c\ude32 ") is 0, \ # SQUARED CJK UNIFIED IDEOGRAPH-7981
+           text.indexOf("\ud83d\udeab ") is 0, \ # NO ENTRY SIGN
+           text.indexOf("\ud83d\ude36 ") is 0, \ # FACE WITHOUT MOUTH
+           text.indexOf("\ud83d\udd07 ") is 0, \ # SPEAKER WITH CANCELLATION STROKE
+           text.indexOf("\ud83d\udd12 ") is 0    # LOCK
         rate = 0

-      # HEAVY LARGE CIRCLE: http://www.fileformat.info/info/unicode/char/2b55/index.htm
-      when ~ text.indexOf "#\u2b55"
-        text = text.replace /#\u2b55/g, ''
+      when text.indexOf("\ud83d\ude46 ") is 0, \ # FACE WITH OK GESTURE
+           text.indexOf("\ud83c\udd97 ") is 0, \ # SQUARED OK
+           text.indexOf("\ud83d\udd13 ") is 0, \ # OPEN LOCK
+           text.indexOf("\ud83d\udd0a ") is 0, \ # SPEAKER WITH THREE SOUND WAVES
+           text.indexOf("\ud83d\udce2 ") is 0, \ # PUBLIC ADDRESS LOUDSPEAKER
+           text.indexOf("\ud83d\udce3 ") is 0    # CHEERING MEGAPHONE
+        text = text.substr 3
         rate = 1

       else


* Refine comment

* Refine comment

--- serialize_backbone_model.coffee
+++ serialize_backbone_model.coffee
@@ -1,5 +1,5 @@
 #
-# Ref #740: https://github.com/flinto/flinto/issues/740
+# Ref #740:
 #
 #  overwrite save method of Backbome.Model to serialize request to avoid
 # the requests are processed out of order on server


Changed requirements to use grunt-engine npm module instead of internal lib file.

--- src/bin/chinstrap-compile.coffee
+++ src/bin/chinstrap-compile.coffee
@@ -3,9 +3,8 @@
 mkdirp 		= require('mkdirp')
 glob 		= require("glob")
 program 	= require("commander")
-lib  		= path.join(path.dirname(fs.realpathSync(__filename)), '..', 'lib')
 VERSION 	= require(path.join("..", "..", "package.json")).version
-chinstrap 	= require(path.join(lib, "chinstrap"))
+chinstrap 	= require("chinstrap-engine")

 program
 	.version(VERSION)


Throw off login/signup events

Throw off login/signup events

--- models/logged_out_user.coffee
+++ models/logged_out_user.coffee
@@ -40,6 +40,7 @@
         url: '/users/sign_in'
         success: _.wrap options.success, (success, model, response, options) =>
           @__isLoggedIn__ = true
+          @trigger 'login'
           $.ajaxSettings.headers = _.extend ($.ajaxSettings.headers or {}),
             'X-ACCESS-TOKEN': response.user.accessToken
           success? model, response, options
@@ -50,6 +51,7 @@
         url: "#{API_URL}/api/v1/user"
         success: =>
           @__isRecentlyRegistered__ = true
+          @trigger 'signup'
           @login(_.pick options, 'success')

   register: @::signup


pass onward if there are no items in the ordered set of the feature that is of type sale and contains an item in that set that is the sale id

--- apps/auction/routes.coffee
+++ apps/auction/routes.coffee
@@ -88,12 +88,13 @@
     .catch next
     .done()

-@redirect = (req, res) ->
+@redirect = (req, res, next) ->
   new Backbone.Collection().fetch
     cache: true
     url: "#{API_URL}/api/v1/sets/contains?item_type=Sale&item_id=#{req.params.id}"
     error: res.backboneError
     success: (collection, response, options) ->
+      return next() unless collection.length
       res.redirect "/feature/#{collection.first().get('owner').id}"

 @inviteForm = (req, res, next) ->


activity: set list controller's data path view option

--- client/Social/Activity/activitylistcontroller.coffee
+++ client/Social/Activity/activitylistcontroller.coffee
@@ -18,6 +18,7 @@
     {viewOptions}                 = options
     viewOptions.cssClass          = KD.utils.curry 'activity-related', viewOptions.cssClass
     viewOptions.comments         ?= yes
+    viewOptions.dataPath          = 'id'

     options.noItemFoundWidget    ?= new KDCustomHTMLView
       cssClass : "lazy-loader hidden"


Activity: minor readability change cc/ @andrushkevich

--- client/Social/Activity/views/activitylistitem.coffee
+++ client/Social/Activity/views/activitylistitem.coffee
@@ -295,10 +295,12 @@
           @destroy()

       article.classList.add 'tall'
-      if @hasShowMoreMark
-        @addSubView @showMore, '.mark-for-show-more'
-      else
-        @addSubView @showMore, '.activity-content-wrapper'
+
+      selector = if @hasShowMoreMark
+      then '.mark-for-show-more'
+      else '.activity-content-wrapper'
+
+      @addSubView @showMore, selector

   pistachio: ->


Show first tab when any credential was set.

--- client/admin/lib/views/stacks/definestackview.coffee
+++ client/admin/lib/views/stacks/definestackview.coffee
@@ -72,8 +72,10 @@

     @stackTemplateView.on 'CredentialStatusChanged', (status) =>
       if status is 'verified'
-      then @saveButton.enable()
-      else @saveButton.disable()
+        @saveButton.enable()
+        @tabView.showPaneByIndex 0
+      else
+        @saveButton.disable()

   createMainButtons: ->


all schemas now have resource classes

all schemas now have resource classes

--- coffeescript/client.coffee
+++ coffeescript/client.coffee
@@ -9,8 +9,6 @@
     @interface = options.interface

     rigger = @
-    shred = @shred
-    schema = @schema

     for resource_name, schema_def of @schema
       interface_def = @interface[resource_name]


ace: Added Cmd+Shift+P key combo to preview file.

--- client/app/Applications/Ace.kdapplication/ace.coffee
+++ client/app/Applications/Ace.kdapplication/ace.coffee
@@ -89,6 +89,13 @@
         win   : 'Ctrl-Shift-C'
         mac   : 'Command-Shift-C'
       exec    : => @getDelegate().compileAndRun()
+
+    @editor.commands.addCommand
+      name    : 'preview'
+      bindKey :
+        win   : 'Ctrl-Shift-P'
+        mac   : 'Command-Shift-P'
+      exec    : => @getDelegate().preview()

     file = @getData()


prevent infinite recursion

prevent infinite recursion

--- src/client/embeds/sandbox.coffee
+++ src/client/embeds/sandbox.coffee
@@ -149,7 +149,8 @@
   get : ()-> @hull

   destroy: ()=>
-    scope.destroy() for scope in @_scopes
+    for scope in @_scopes
+      scope.hull.autoSize(false)
     @_scopes = []
     @getObserver().destroy()


client: welcome: stepsstore updated with video step

--- client/home/lib/flux/stores/welcomestepsstore.coffee
+++ client/home/lib/flux/stores/welcomestepsstore.coffee
@@ -14,38 +14,46 @@
         stackCreation :
           path: '/Stack-Editor'
           title: 'Create a Stack for Your Team'
+          miniTitle: 'Create a Stack'
           description: "Create a blueprint for your team’s entire infrastructure."
           isDone: no
-          order: 0
+          order: 1
         buildStack :
           path: '/IDE'
           title: 'Build Your Stack'
           description: "You have a stack ready to go, go ahead and build it."
           isDone: no
-          order: 1
+          order: 2
       member :
         stackCreation :
           path: '/Stack-Editor'
           title: 'Create a Personal Stack'
           description: "While waiting for your team resources, you can experiment stacks."
           isDone: no
-          order: 0
+          order: 1
         pendingStack :
           path: '#'
           title: 'Your Team Stack is Pending'
           description: "Your team admins haven't created your stack yet."
           isDone: no
-          order: 0
+          order: 2
         buildStack :
           path: '/IDE'
           title: 'Build Your Stack'
           description: "Your team admins have already created your stack, it is ready for you to build."
           isDone: no
-          order: 1
+          order: 2
       common :
+        watchVideo :
+          path: '/Welcome/Intro'
+          title: 'Watch Intro Video'
+          description: "You are all set, watch our short video to know how to use Koding."
+          isDone: no
+          order: 0
         inviteTeam :
           path: '/Home/My-Team'
           title: 'Invite Your Team'
+          miniTitle: 'Invite Teammates'
           description: "Get your teammates working together."
           isDone: no
           order: 10


Fixed: Need to export crud

Fixed: Need to export crud

--- server/controllers/index.coffee
+++ server/controllers/index.coffee
@@ -1,2 +1,2 @@
 module.exports =
-  crud = require './crud'+  crud: require './crud'


Fix urls and sort order

Fix urls and sort order

--- client/skr/screens/time-invoicing/TimeInvoicing.cjsx
+++ client/skr/screens/time-invoicing/TimeInvoicing.cjsx
@@ -9,6 +9,7 @@
         query: ->
             @gridSelections = new LC.Grid.Selections(onChange: @updateTotal)
             new Lanes.Models.Query({
+                defaultSort: 'start_at'
                 src: Skr.Models.TimeEntry, fields: [
                     {id:'id', visible: false}
                     @gridSelections
@@ -62,7 +63,10 @@
             @context.viewport.displayModal(@displayInvoiceResults(req))

     displayInvoiceResults: (req) ->
-        title: "Invoice created ..."
+        title: "Invoice created …"
+        buttons: [{ title: 'OK', style: 'primary' }]
+        autoHide: true
+        onClick: => @context.viewport.hideModal()
         body: ->
             <h3>
                 <SC.InvoiceLink invoice={req.invoice} /> was
@@ -127,7 +131,7 @@
         time_entry_ids: { type: 'array', default: -> [] }

     api_path: ->
-        'invoices/from-time-entries'
+        'skr/invoices/from-time-entries'

     associations:
         customer_project: { model: "CustomerProject", autoCreate: true }


server: redirect handlers cleaned

server: redirect handlers cleaned

--- servers/lib/server/index.coffee
+++ servers/lib/server/index.coffee
@@ -59,17 +59,6 @@
 app.get "/humans.txt", (req, res)->
   generateHumanstxt(req, res)

-app.get "/members/:username?*", (req, res)->
-  username = req.params.username
-  res.redirect 301, '/' + username
-
-app.get "/w/members/:username?*", (req, res)->
-  username = req.params.username
-  res.redirect 301, '/' + username
-
-app.get "/activity/p/?*", (req, res)->
-  res.redirect 301, '/Activity'
-
 app.get "/-/healthCheck", (req, res) ->
   {workers, publicPort} = KONFIG

@@ -378,6 +367,10 @@
 app.use express.basicAuth basicAuth.username, basicAuth.password  if basicAuth

+# redirects
+app.get '/members/:username?*'                  , (req, res) -> res.redirect 301, "/#{req.params.username}"
+app.get '/w/members/:username?*'                , (req, res) -> res.redirect 301, "/#{req.params.username}"
+app.get '/activity/p/?*'                        , (req, res) -> res.redirect 301, '/Activity'
 app.post '/:name?/Validate'                     , require './handlers/validate'
 app.post '/:name?/Validate/Username/:username?' , require './handlers/validateusername'
 app.post '/:name?/Validate/Email/:email?'       , require './handlers/validateemail'


removed validatehandlerhelper from testhelper/index

--- servers/testhelper/index.coffee
+++ servers/testhelper/index.coffee
@@ -100,34 +100,6 @@

 class ValidationHandlerHelper
-
-  @generateValidateRequestBody = (opts = {}) ->
-
-    defaultBodyObject =
-      fields     :
-        username : ''
-        email    : ''
-
-    deepObjectExtend defaultBodyObject, opts
-
-    return defaultBodyObject
-
-
-  @generateValidateRequestParams = (opts = {}) ->
-
-    url  = generateUrl
-      route : '-/validate'
-
-    body = ValidationHandlerHelper.generateValidateRequestBody()
-
-    params               = { url, body }
-    defaultRequestParams = generateDefaultRequestParams params
-    requestParams        = deepObjectExtend defaultRequestParams, opts
-    # after deep extending object, encodes body param to a query string
-    requestParams.body   = querystring.stringify requestParams.body
-
-    return requestParams
-

   @generateValidateUsernameRequestBody = (opts = {}) ->


Update replacer.coffee

Update replacer.coffee

--- src/replacer.coffee
+++ src/replacer.coffee
@@ -42,7 +42,7 @@
     # If the URL matches one of the "Object" types (repo, user, comment)
     # then provide all of the same methods as `octo.repo(...)` would have on it
     url = acc.url
-    Chainer(@_request, url, null, null, acc) if url
+    Chainer(@_request, url, true, null, acc) if url
     for key, re of OBJECT_MATCHER
       if re.test(url)
         context = TREE_OPTIONS


Added more debug log.

Added more debug log.

--- app/assets/javascripts/app/controllers/login.js.coffee
+++ app/assets/javascripts/app/controllers/login.js.coffee
@@ -87,12 +87,15 @@
     }

     # redirect to #
-    if @Config.get( 'requested_url' ) isnt ''
-      @navigate @Config.get( 'requested_url' )
+    requested_url = @Config.get( 'requested_url' )
+    if requested_url isnt ''
+      console.log("REDIRECT to '#{requested_url}'")
+      @navigate requested_url

       # reset
       @Config.set( 'requested_url', '' )
     else
+      console.log("REDIRECT to -#/-")
       @navigate '#/'

   error: (xhr, statusText, error) =>


Resolve firefox javascript error

Resolve firefox javascript error

--- app/assets/javascripts/backend/interventions.js.coffee
+++ app/assets/javascripts/backend/interventions.js.coffee
@@ -324,7 +324,7 @@
     $(this).each ->
       E.interventions.refresh $(this)

-  $(document).on 'keyup change', 'input[data-intervention-updater]:not([data-selector])', (e) ->
+  $(document).on 'keyup change', 'input[data-intervention-updater]:not([data-selector])', (event) ->
     $(this).each ->
       E.interventions.refresh $(this)

@@ -332,7 +332,7 @@
       unitName = $(event.target).parent().find('select[data-intervention-field="quantity-handler"]').val()
       E.interventionForm.displayCost(event.target, quantity, unitName)

-  $(document).on 'keyup change', 'select[data-intervention-updater]', ->
+  $(document).on 'keyup change', 'select[data-intervention-updater]', (event) ->
     $(this).each ->
       E.interventions.refresh $(this)


Fix bug on IE

Fix bug on IE

IE was not seen the param method.

--- app/assets/javascripts/neighborly/images/new.js.coffee
+++ app/assets/javascripts/neighborly/images/new.js.coffee
@@ -17,7 +17,7 @@
       initialize: ->
         _.bindAll this, 'onFileAdded', 'onUploadProgress', 'onUploadComplete', 'onUploadFail', 'mouseEnter', 'mouseLeave', 'openFileChooser'
         this.action_url = this.$el.closest('form')[0].getAttribute("action")
-        this.param_name = this.$el[0].dataset.param
+        this.param_name = this.$el.data('param')
         this.$image_previewer = this.$('.uploaded-image')
         this.$uploaded_image_url = $('.uploaded-image-url')
         this.initializeDropzone()


#82 create bucket only for writing

#82 create bucket only for writing

--- app/assets/javascripts/oxalis/model/binary/cube.coffee
+++ app/assets/javascripts/oxalis/model/binary/cube.coffee
@@ -109,7 +109,7 @@
     z * (1 << @BUCKET_SIZE_P * 2)

-  getBucketDataByZoomedAddress : (address, createIfNull = false) ->
+  getBucketDataByZoomedAddress : (address, createIfUndefined = false) ->

     if address[3] >= @ZOOM_STEP_COUNT
       return null
@@ -121,7 +121,7 @@
       bucket = cube[bucketIndex]
       if bucket? and bucket != @LOADING_PLACEHOLDER
         return bucket
-      else if createIfNull
+      else if createIfUndefined
         return cube[bucketIndex] = new Uint8Array(@BUCKET_LENGTH)

     return null
@@ -298,7 +298,7 @@

       for zoomStep in [0...@ZOOM_STEP_COUNT]

-        { bucket, voxelIndex } = @getBucketAndVoxelIndex( voxel, zoomStep )
+        { bucket, voxelIndex } = @getBucketAndVoxelIndex( voxel, zoomStep, true )

         break if bucket[voxelIndex] == label
         bucket[voxelIndex] = label
@@ -324,13 +324,13 @@
     return null

-  getBucketAndVoxelIndex : (voxel, zoomStep ) ->
+  getBucketAndVoxelIndex : (voxel, zoomStep, createBucketIfUndefined = false ) ->

     address     = @positionToZoomedAddress( voxel, zoomStep )
     voxelOffset = @getVoxelOffset( voxel )

     return {
-      bucket : @getBucketDataByZoomedAddress(address, true)
+      bucket : @getBucketDataByZoomedAddress(address, createBucketIfUndefined)
       voxelIndex : @BYTE_OFFSET * @getVoxelIndexByVoxelOffset(voxelOffset) }


Menu and game now integrated in a one-DOM app

--- maze.coffee
+++ maze.coffee
@@ -129,7 +129,7 @@

   constructor: (@nextLevel=3) ->
     super([4, 4, 1])
-    @start = LevelChooser.waitingPosition[@nextLevel]
+    @start = LevelChooser.waitingPosition[@nextLevel] || LevelChooser.waitingPosition[8]
     # row 0
     @passages[[[0, 0, 0], [1, 0, 0]]] = true
     @passages[[[1, 0, 0], [2, 0, 0]]] = true
@@ -430,7 +430,7 @@
   updateStatus: ->
     @messagebox.html('
' + @currentMessage() + '
')
     if @maze.isFinish([@x, @y, @z])
-      @messagebox.append($('

').append(@makeTweetActionLink()).append('      play again'))
+      @messagebox.append($('

').append(@makeTweetActionLink()).append('      ').append(@makePlayAgainActionLink()))
       @disable()
     if [@x, @y, @z] of @maze.portals
       level = @maze.portals[[@x, @y, @z]]
@@ -448,6 +448,18 @@
         })
         .append('tweet')

+  makePlayAgainActionLink: =>
+    $('')
+        .attr({
+            class: 'actionLink'
+            href: '#'
+        }).append('play again').click(=> @playAgain())
+
+  playAgain: =>
+    @destroy()
+    new LevelChooserUI(new LevelChooser(@maze.dimensions[0] + 1), @frame,
+        @messagebox, @viewport)
+
   makeTweetText: ->
     "I solved a #{@maze.dimensions[0]}x#{@maze.dimensions[1]}x#{@maze.dimensions[2]} maze in #{@moves} moves at https://texttheater.net/maze/"


Set .bindlerc file type to "source.shell" in .atom/config.cson.

--- home/.atom/config.cson
+++ home/.atom/config.cson
@@ -43,6 +43,7 @@
   "file-types":
     "^Baskfile$": "source.shell"
     "^.amethyst": "source.js"
+    "^.bindlerc$": "source.shell"
     "^.curlrc": "text.generic-config"
     "^.gitconfig.local": "source.git-config"
     "^.gitignore": "source.git-config"


Defer requiring 6to5 until it is used

Defer requiring 6to5 until it is used

It seems to have a require time of ~250ms

--- src/6to5.coffee
+++ src/6to5.coffee
@@ -7,7 +7,7 @@
 crypto = require 'crypto'
 fs = require 'fs-plus'
 path = require 'path'
-to5 = require '6to5-core'
+to5 = null # Defer until used

 stats =
   hits: 0
@@ -89,11 +89,16 @@
   updateDigestForJsonValue(shasum, options)
   shasum.digest('hex')

-cacheDir = path.join(fs.absolute('~/.atom'), 'compile-cache')
-jsCacheDir = path.join(cacheDir, 'js', '6to5', create6to5VersionAndOptionsDigest(to5.version, defaultOptions))
+jsCacheDir = null

 getCachePath = (sourceCode) ->
   digest = crypto.createHash('sha1').update(sourceCode, 'utf8').digest('hex')
+
+  unless jsCacheDir?
+    to5 ?= require '6to5-core'
+    cacheDir = path.join(fs.absolute('~/.atom'), 'compile-cache')
+    jsCacheDir = path.join(cacheDir, 'js', '6to5', create6to5VersionAndOptionsDigest(to5.version, defaultOptions))
+
   path.join(jsCacheDir, "#{digest}.js")

 getCachedJavaScript = (cachePath) ->
@@ -113,6 +118,7 @@

 transpile = (sourceCode, filePath, cachePath) ->
   options = createOptions(filePath)
+  to5 ?= require '6to5-core'
   js = to5.transform(sourceCode, options).code
   stats.misses++


Bugfix

Bugfix

--- views/application.coffee
+++ views/application.coffee
@@ -348,6 +348,17 @@
   $("#special_profession").change ->
     add_line_to_costs("my_special_profession", "Spezielle Profession", $("#special_profession :selected").attr("cost"))
     $(".quality_cost").change()
+
+    if $("#special_profession :selected").attr("value") == "none"
+      $("input[name=magic]").attr("value", 1)
+      $("#special_attributes input").change()
+      $("label[for=magic], input[name=magic]").fadeOut()
+    else
+      if $("#special_profession :selected").attr("value") == "technomancer"
+        $("label[for=magic]").text("Resonanz")
+      else
+        $("label[for=magic]").text("Magie")
+      $("label[for=magic], input[name=magic]").fadeIn()

   $('#metatype, .quality_cost, .quality_earn, .connections, #special_profession').change()