Set transfer encoding of base64 images

Set transfer encoding of base64 images

When we're saving an image (typically as a secondary file) we will
probably be encoding as base64. This sets the correct headers.

[#127513087]

--- src/code/providers/google-drive-provider.coffee
+++ src/code/providers/google-drive-provider.coffee
@@ -255,9 +255,14 @@
     else
       ['POST', '/upload/drive/v2/files']

+    transferEncoding = ""
+    if mimeType.indexOf("image/") is 0
+      # assume we're transfering any images as base64
+      transferEncoding = "\r\nContent-Transfer-Encoding: base64"
+
     body = [
       "\r\n--#{boundary}\r\nContent-Type: application/json\r\n\r\n#{header}",
-      "\r\n--#{boundary}\r\nContent-Type: #{mimeType}\r\n\r\n#{content.getContentAsJSON?() or content}",
+      "\r\n--#{boundary}\r\nContent-Type: #{mimeType}#{transferEncoding}\r\n\r\n#{content.getContentAsJSON?() or content}",
       "\r\n--#{boundary}--"
     ].join ''


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -130,7 +130,8 @@
     borderColor - Color of the meter's border
     borderWidth - Width of the meter's border
     color - Color of the meter's inner rectangle
-    nameColor - Color of the property name displayed above
+    nameColor - Color of the property name displayed above the meter
+    font -
   ###
   meter: (options={}) ->
     Object.reverseMerge options,


:art: be explicit

:art: be explicit

--- lib/hover.coffee
+++ lib/hover.coffee
@@ -38,7 +38,7 @@
   initialize: (@model) ->
     this

-  emojify: (text, size=20) ->
+  emojify: (text, size) ->
     emoji(String(text), emojiFolder, size)

   show: ->


Just use section property, not combined section/chapter

--- src/components/learning-guide/progress-bar.cjsx
+++ src/components/learning-guide/progress-bar.cjsx
@@ -7,22 +7,20 @@
   displayName: 'LearningGuideProgressBar'

   propTypes:
-    section:  ChapterSectionType
-    chapter:  ChapterSectionType
+    section:  ChapterSectionType.isRequired
     onPractice: React.PropTypes.func

   render: ->
     {section, chapter, onPractice} = @props
-    level = (section or chapter).current_level
+    level = section.current_level
     percent = Math.round((level / 1) * 100)
     color = switch
       when percent >  75 then 'high'
       when percent >= 50 then 'medium'
       else 'low'

-
     bar = <BS.ProgressBar className={color} now={percent} />
     if onPractice
-      <BS.Button onClick={-> onPractice(section or chapter)} block>{bar}</BS.Button>
+      <BS.Button onClick={-> onPractice(section)} block>{bar}</BS.Button>
     else
       bar


only spawn workers on compile/watch & disconnect after compile

--- lib/index.coffee
+++ lib/index.coffee
@@ -19,12 +19,11 @@
    * @return {Function} - instance of the Roots class
   ###

-  constructor: (@root, opts={}) ->
+  constructor: (@root, @opts={}) ->
     @root = path.resolve(@root)
     if not fs.existsSync(@root) then throw new Error("path does not exist")
     @extensions = new Extensions(@)
     @config = new Config(@, opts)
-    set_up_workers.call(@, opts)

   ###*
    * Alternate constructor, creates a new roots project in a given folder and
@@ -60,8 +59,10 @@
   ###

   compile: ->
+    set_up_workers.call(@, @opts)
     Compile = require('./api/compile')
     (new Compile(@)).exec()
+      .then(disconnect_workers.bind(@))

   ###*
    * Watches a folder for changes and compiles whenever changes happen.
@@ -70,8 +71,10 @@
   ###

   watch: ->
+    set_up_workers.call(@, @opts)
     Watch = require('./api/watch')
     (new Watch(@)).exec()
+    # when watch is cancelled take down the workers

   ###*
    * Removes a project's output folder.
@@ -123,4 +126,7 @@
         else
           @emit(msg.eventName, msg.data)

+  disconnect_workers = ->
+    worker.disconnect() for worker in @workers
+
 module.exports = Roots


ディレクトリ名を変更、サブディレクトリを含むように変更

ディレクトリ名を変更、サブディレクトリを含むように変更

--- lib/index.coffee
+++ lib/index.coffee
@@ -108,16 +108,13 @@
         ["deploy-to-aws-lambda"],
         callback)

-    # TODO: サブディレクトリも含める。ディレクトリ名を変更する。
     gulp.task "test", ->
-      gulp.src("./dist/*_spec.js")
+      gulp.src("./out/**/*_spec.js")
         .pipe(jasmine({includeStackTrace: false}))

-    # TODO: サブディレクトリも含める。ディレクトリ名を変更する。
     gulp.task "auto-test", ->
-      gulp.watch("./src/*.coffee", ["default"])
+      gulp.watch("./src/**/*.coffee", ["default"])
       gulp.start("default")

-    # TODO: 依存タスク名を変更する。
-    gulp.task "default", ["js"], ->
+    gulp.task "default", ["compile"], ->
       gulp.start("test")


Put session identifiers in server request payload

--- lib/index.coffee
+++ lib/index.coffee
@@ -345,9 +345,6 @@
     scrubber.scrub args, =>
       message = scrubber.toDnodeProtocol()
       message.method = method
-      message.sessionToken = @getSessionToken()
-      message.userArea = @getUserArea()
-
       @sendHelper message

   sendHelper: (message) ->
@@ -402,8 +399,15 @@
         return

       @handleRequest request for request in requests when request
-    messageString = JSON.stringify { @channelName, queue }
-    xhr.send messageString
+
+    payload = JSON.stringify {
+      @channelName
+      queue
+      sessionToken: @getSessionToken()
+      userArea: @getUserArea()
+    }
+
+    xhr.send payload

   authenticateUser:->
     clientId = @getSessionToken()


fixed bug with creating new models without passing properties

--- lib/model.coffee
+++ lib/model.coffee
@@ -1,5 +1,5 @@
 class Model
-  constructor: (@props) ->
+  constructor: (@props = {}) ->
     @setupProps()
     @update @props
     @persisted = @props._id?


Add destination to LogoutRequest as it's required by ADFS.

--- lib/saml2.coffee
+++ lib/saml2.coffee
@@ -50,7 +50,7 @@
   .end()

 # Creates a LogoutRequest and returns it as a string of xml.
-create_logout_request = (issuer, name_id, session_index) ->
+create_logout_request = (issuer, name_id, session_index, destination) ->
   xmlbuilder.create
     'samlp:LogoutRequest':
       '@xmlns:samlp': 'urn:oasis:names:tc:SAML:2.0:protocol'
@@ -58,6 +58,7 @@
       '@ID': '_' + crypto.randomBytes(21).toString('hex')
       '@Version': '2.0'
       '@IssueInstant': (new Date()).toISOString()
+      '@Destination': destination
       'saml:Issuer': issuer
       'saml:NameID': name_id
       'samlp:SessionIndex': session_index


Converted suite.coffee code.

Converted suite.coffee code.

--- lib/suite.coffee
+++ lib/suite.coffee
@@ -3,4 +3,182 @@
 #
 # Copyright (c) 2012 Lee Olayvar <leeolayvar@gmail.com>
 # MIT Licensed
-#+#
+{Runner} = require './runner'
+
+
+
+# Desc:
+#   A Suite represents a collection of tests.
+class Suite
+
+  # (@description, @location) -> undefined
+  #
+  # Params:
+  #   description: The description of this suite.
+  #   location: The location of this suite.
+  #
+  # Desc:
+  #   Initialize the instance variables.
+  constructor: (@description, @location) ->
+    # Lists of runners that will be executed before/after tests.
+    @_befores = []
+    @_before_eachs = []
+    @_afters = []
+    @_after_eachs = []
+
+    # A list of suites and test runners.
+    @_tests_and_suites = []
+
+  # ([befores, before_eachs, after_eachs, [session, index]]) -> Array
+  #
+  # Params:
+  #   befores: A list of before runners.
+  #   before_eachs: A list of before_each runners.
+  #   after_eachs: A list of after_each runners.
+  #   session: Optional. A list containing the built session. This object is
+  #     recursively modified and eventually returned.
+  #   index: Optiona. The index of `@_tests_and_suites` to use.
+  #
+  # Returns:
+  #   A list containing runnables. The runnavbles are in before/test/after
+  #   order, containing all the tests found in any added suites, along with
+  #   the before_eachs and after_eachs from each suite.
+  #
+  # Desc:
+  #   Build a list of runnables which can be passed to `@_run_session()`.
+  #   This is a recursive method.
+  _build_session: (befores=[], before_eachs=[], after_eachs=[], session=[], index=0) ->
+    # If we are on the first iteration, combine the given pre/post hooks
+    # with this suite's pre/post hooks.
+    if index is 0
+      befores.push @_befores...
+      before_eachs.push @_before_eachs...
+      # Note that the after_each's need to be added to the *beginning*
+      # of the list. This allows the following design..
+      # Their Befires > Our Befores > Test < Our Afters < Their Afters
+      after_eachs[...0] = @_after_eachs
+
+    # The test/suite we're working on in this iteration.
+    item = @_tests_and_suites[index]
+
+    # If the item is undefined, we're at the end of the tests_and_suites,
+    # so we'll want to return out of this iteration.
+    if not item?
+      # If we had at least one test in this suite, or any subsuites, then
+      # we want to append our `@_afters` to the session.
+      if session.length > 0
+        session.push @_afters
+      return session
+
+    if item instanceof Suite
+      # The item is a Suite. Tell it to build it's own session, so we can
+      # combine it with ours.
+      item_session = item._build_session befores, before_eachs, after_eachs
+
+      if item_session.length > 0
+        # If the item_session has any items in it, then it will have appended
+        # our before's. Since these can only execute once, we want to set
+        # it to a new list.
+        befores = []
+        # Now combine the sessions.
+        session.push item_session...
+    else
+      # The item is a test.
+
+      if befores.length > 0
+        # Since this is a test, and we have befores, append the befores to
+        # our session, so they're on the session before we add our test.
+        session.push befores...
+        befores = []
+
+      # Append our item, wrapped in before_eachs and after_eachs
+      session.push before_eachs...
+      session.push item
+      session.push after_eachs...
+
+    # Return our iteration.
+    return @_build_session befores, before_eachs, after_eachs,
+      session, ++index
+
+  # (runner) -> undefined
+  #
+  # Params:
+  #   runner: A function or Runner.
+  #
+  # Desc:
+  #   A runner to be called to be called after the last test.
+  add_after: (runner) ->
+    if runner instanceof Function
+      runner = new Runner runner
+    @_afters.push runner
+
+  # (runner) -> undefined
+  #
+  # Params:
+  #   runner: A function or Runner.
+  #
+  # Desc:
+  #   A runner to be called to be called after each test.
+  add_after_each: (runner) ->
+    if runner instanceof Function
+      runner = new Runner runner
+    @_after_eachs.push runner
+
+  # (runner) -> undefined
+  #
+  # Params:
+  #   runner: A function or Runner.
+  #
+  # Desc:
+  #   A runner to be called to be called before the first test.
+  add_before: (runner) ->
+    if runner instanceof Function
+      runner = new Runner runner
+    @_befores.push runner
+
+  # (runner) -> undefined
+  #
+  # Params:
+  #   runner: A function or Runner.
+  #
+  # Desc:
+  #   A runner to be called before each test.
+  add_before_each: (runner) ->
+    if runner instanceof Function
+      runner = new Runner runner
+    @_before_eachs.push runner
+
+  # (suite) -> undefined
+  #
+  # Params:
+  #   suite: The suite being added.
+  #
+  # Desc:
+  #   Add a suite to this suite.
+  add_suite: (suite) ->
+    @_tests_and_suites.push suite
+
+  # (test) -> undefined
+  #
+  # Params:
+  #   test: The test being added.
+  #
+  # Desc:
+  #   Add a test to this suite.
+  add_test: (test) ->
+    @_tests_and_suites.push test
+
+  # (callback) -> undefined
+  #
+  # Params:
+  #   callback: The callback called when all tests have been completed.
+  #
+  # Desc:
+  #   Run all the tests found in this suite, and any suites added to this.
+  run: (callback) ->
+
+
+
+exports.create = (description, location) -> new Suite description, location
+exports.Suite = Suite


update comment

update comment

--- lib/token.coffee
+++ lib/token.coffee
@@ -16,7 +16,7 @@
 # This Token class is a high level wrapper
 # which loads the Pudding contract, which:
 # 1) is created by Truffle, and
-# 2) under the hood uses web3 to make JSON RPC calls to geth
+# 2) under the hood uses web3 to make JSON RPC calls to an ethereum node

 class Token
   CONTRACT_NAME = 'DynamicToken'


adding/fixing a bunch of arithmetic opcodes

--- src/opcodes.coffee
+++ src/opcodes.coffee
@@ -278,19 +278,19 @@
   105: new root.Opcode 'lmul', { execute: (rs) -> rs.push(rs.pop2()*rs.pop2(), null) }
   106: new root.Opcode 'fmul', { execute: (rs) -> rs.push(rs.pop()*rs.pop()) }
   107: new root.Opcode 'dmul', { execute: (rs) -> rs.push(rs.pop2()*rs.pop2(), null) }
-  108: new root.Opcode 'idiv', { execute: (rs) -> rs.push(rs.pop()/rs.pop()) }  #TODO: do int division!
-  109: new root.Opcode 'ldiv'
-  110: new root.Opcode 'fdiv'
-  111: new root.Opcode 'ddiv'
+  108: new root.Opcode 'idiv', { execute: (rs) -> rs.push(rs.pop()/rs.pop()) }          #TODO: do int division!
+  109: new root.Opcode 'ldiv', { execute: (rs) -> rs.push(rs.pop2()/rs.pop2(), null) }  #TODO: do int division!
+  110: new root.Opcode 'fdiv', { execute: (rs) -> rs.push(rs.pop()/rs.pop()) }
+  111: new root.Opcode 'ddiv', { execute: (rs) -> rs.push(rs.pop2()/rs.pop2(), null) }
   # TODO throw an ArithmeticException if modulus is zero
-  112: new root.Opcode 'irem', { execute: (rs) -> v2=rs.pop(); v1=rs.pop(); rs.push(v1%v2) }
-  113: new root.Opcode 'lrem'
-  114: new root.Opcode 'frem'
-  115: new root.Opcode 'drem'
-  116: new root.Opcode 'ineg', { execute: (rs) -> rs.push(-rs.pop()) }
-  117: new root.Opcode 'lneg', { execute: (rs) -> rs.push(-rs.pop2()) }
-  118: new root.Opcode 'fneg', { execute: (rs) -> rs.push(-rs.pop()) }
-  119: new root.Opcode 'dneg', { execute: (rs) -> rs.push(-rs.pop2()) }
+  112: new root.Opcode 'irem', { execute: (rs) -> v2=rs.pop();  rs.push rs.pop() %v2 }
+  113: new root.Opcode 'lrem', { execute: (rs) -> v2=rs.pop2(); rs.push rs.pop2()%v2, null }
+  114: new root.Opcode 'frem', { execute: (rs) -> v2=rs.pop();  rs.push rs.pop() %v2 }
+  115: new root.Opcode 'drem', { execute: (rs) -> v2=rs.pop2(); rs.push rs.pop2()%v2, null }
+  116: new root.Opcode 'ineg', { execute: (rs) -> rs.push -rs.pop() }
+  117: new root.Opcode 'lneg', { execute: (rs) -> rs.push -rs.pop2(), null }
+  118: new root.Opcode 'fneg', { execute: (rs) -> rs.push -rs.pop() }
+  119: new root.Opcode 'dneg', { execute: (rs) -> rs.push -rs.pop2(), null }
   120: new root.Opcode 'ishl'
   121: new root.Opcode 'lshl'
   122: new root.Opcode 'ishr'
@@ -299,26 +299,26 @@
   125: new root.Opcode 'lushr'
   126: new root.Opcode 'iand', { execute: (rs) -> rs.push(rs.pop()&rs.pop()) }
   127: new root.Opcode 'land', { execute: (rs) -> rs.push(rs.pop2()&rs.pop2(), null) }
-  128: new root.Opcode 'ior', { execute: (rs) -> rs.push(rs.pop()|rs.pop()) }
-  129: new root.Opcode 'lor', { execute: (rs) -> rs.push(rs.pop2()|rs.pop2(), null) }
+  128: new root.Opcode 'ior',  { execute: (rs) -> rs.push(rs.pop()|rs.pop()) }
+  129: new root.Opcode 'lor',  { execute: (rs) -> rs.push(rs.pop2()|rs.pop2(), null) }
   130: new root.Opcode 'ixor', { execute: (rs) -> rs.push(rs.pop()^rs.pop()) }
   131: new root.Opcode 'lxor', { execute: (rs) -> rs.push(rs.pop2()^rs.pop2(), null) }
   132: new root.IIncOpcode 'iinc'
-  133: new root.Opcode 'i2l', {execute: (rs) -> rs.push(rs.pop(), null)}
-  134: new root.Opcode 'i2f', {execute: (rs) -> rs.push rs.pop() }
-  135: new root.Opcode 'i2d', {execute: (rs) -> rs.push(rs.pop(), null)}
-  136: new root.Opcode 'l2i', {execute: (rs) -> rs.push(rs.pop2())}  #TODO: truncate to 32 bit int
-  137: new root.Opcode 'l2f'
-  138: new root.Opcode 'l2d'
-  139: new root.Opcode 'f2i'
-  140: new root.Opcode 'f2l'
-  141: new root.Opcode 'f2d'
-  142: new root.Opcode 'd2i', { execute: (rs) -> rs.push(Math.floor(rs.pop2())) }
-  143: new root.Opcode 'd2l'
-  144: new root.Opcode 'd2f'
-  145: new root.Opcode 'i2b'
-  146: new root.Opcode 'i2c'
-  147: new root.Opcode 'i2s'
+  133: new root.Opcode 'i2l', { execute: (rs) -> rs.push null }
+  134: new root.Opcode 'i2f', { execute: (rs) -> }
+  135: new root.Opcode 'i2d', { execute: (rs) -> rs.push null }
+  136: new root.Opcode 'l2i', { execute: (rs) -> rs.push rs.pop2() }  #TODO: truncate to 32 bit int
+  137: new root.Opcode 'l2f', { execute: (rs) -> rs.push rs.pop2() }
+  138: new root.Opcode 'l2d', { execute: (rs) -> }
+  139: new root.Opcode 'f2i', { execute: (rs) -> rs.push Math.floor(rs.pop()) }
+  140: new root.Opcode 'f2l', { execute: (rs) -> rs.push Math.floor(rs.pop()), null }
+  141: new root.Opcode 'f2d', { execute: (rs) -> rs.push null }
+  142: new root.Opcode 'd2i', { execute: (rs) -> rs.push Math.floor(rs.pop2()) }
+  143: new root.Opcode 'd2l', { execute: (rs) -> rs.push Math.floor(rs.pop2()), null }
+  144: new root.Opcode 'd2f', { execute: (rs) -> rs.push rs.pop2() }
+  145: new root.Opcode 'i2b', { execute: (rs) -> }  #TODO: truncate to 8 bits
+  146: new root.Opcode 'i2c', { execute: (rs) -> }  #TODO: truncate to 8 bits
+  147: new root.Opcode 'i2s', { execute: (rs) -> }  #TODO: truncate to 16 bits
   148: new root.Opcode 'lcmp', { execute: (rs) -> v2=rs.pop2(); rs.push util.cmp(rs.pop2(),v2), null }
   149: new root.Opcode 'fcmpl', { execute: (rs) -> v2=rs.pop(); rs.push util.cmp(rs.pop(),v2) ? -1 }
   150: new root.Opcode 'fcmpg', { execute: (rs) -> v2=rs.pop(); rs.push util.cmp(rs.pop(),v2) ? 1 }


avoid extra opcode baggage

avoid extra opcode baggage

--- src/opcodes.coffee
+++ src/opcodes.coffee
@@ -330,14 +330,14 @@
     @byte_count = padding_size + 12 + 4 * total_offsets

 class root.NewArrayOpcode extends root.Opcode
+  arr_types = {4:'Z',5:'C',6:'F',7:'D',8:'B',9:'S',10:'I',11:'J'}
   constructor: (name, params) ->
     super name, params
     @byte_count = 1
-    @arr_types = {4:'Z',5:'C',6:'F',7:'D',8:'B',9:'S',10:'I',11:'J'}

   take_args: (code_array,constant_pool) ->
     type_code = code_array.get_uint 1
-    @element_type = @arr_types[type_code]
+    @element_type = arr_types[type_code]

   annotate: (idx, pool) -> "\t#{util.internal2external[@element_type]}"


:memo: doc public methods of Scanner class

--- src/scanner.coffee
+++ src/scanner.coffee
@@ -17,11 +17,13 @@
     @firstLineScanner = null
     @scanner = null

+  # Private: Create a new {OnigScanner} with the given options.
   createScanner: (firstLine, position, anchorPosition) ->
     patterns = @patterns.map (pattern) ->
       pattern.getRegex(firstLine, position, anchorPosition)
     scanner = new OnigScanner(patterns)

+  # Private: Get the {OnigScanner} for the given position and options.
   getScanner: (firstLine, position, anchorPosition) ->
     unless @anchored
       @scanner ?= @createScanner(firstLine, position, anchorPosition)
@@ -37,12 +39,28 @@
     else
       @scanner ?= @createScanner(firstLine, position, anchorPosition)

+  # Public: Find the next match on the line start at the given position
+  #
+  # * line: the string being scanned.
+  # * firstLine: true if the first line is being scanned.
+  # * position: numeric position to start scanning at.
+  # * anchorPosition: numeric position of the last anchored match.
+  #
+  # Returns an Object with details about the match or null if no match found.
   findNextMatch: (line, firstLine, position, anchorPosition) ->
     scanner = @getScanner(firstLine, position, anchorPosition)
     match = scanner.findNextMatch(line, position)
     match?.scanner = this
     match

+  # Public: Handle the given match by calling `handleMatch` on the
+  # matched {Pattern}.
+  #
+  # * match: An object returned from a previous call to `findNextMatch`.
+  # * stack: An array of {Rule} objects.
+  # * line: the string being scanned.
+  #
+  # Returns an array of tokens representing the match.
   handleMatch: (match, stack, line) ->
     pattern = @patterns[match.index]
     pattern.handleMatch(stack, line, match.captureIndices)


Fix method name

Fix method name

--- app/assets/javascripts/rglossa/views/corpus_view.coffee
+++ app/assets/javascripts/rglossa/views/corpus_view.coffee
@@ -16,7 +16,7 @@
       @$().on('click', @hideSidebarSelector, @hideSidebar)

-  willRemoveElement: ->
+  willDestroyElement: ->
     @$().off('click', @hideSidebarSelector, @hideSidebar)
     @$().off('click', '#show-criteria-button, #new-search-button', @showSidebar)


fix(sound): Don't play sounds in specs

fix(sound): Don't play sounds in specs

--- src/atom.coffee
+++ src/atom.coffee
@@ -651,6 +651,7 @@
     @emitter.emit 'did-beep'

   playSound: (filename) ->
+    return if @inSpecMode()
     {resourcePath} = atom.getLoadSettings()
     a = new Audio()
     a.src = path.join(resourcePath, 'static', 'sounds', filename)


(feature) Query user information for login.

--- src/auth.coffee
+++ src/auth.coffee
@@ -1,30 +1,35 @@

 session = require 'express-session'

-module.exports = (app, config) ->
-  app.use session(
-    secret: config.session.secret
-    saveUninitialized: true    # FIXME: true? yes/no?
-    resave: true               # FIXME: true? yes/no?
-    cookie:
-      secure: config.ssl.enable
-  )
+module.exports = (userExists) ->
+  (app, config) ->
+    app.use session(
+      secret: config.session.secret
+      saveUninitialized: true    # FIXME: true? yes/no?
+      resave: true               # FIXME: true? yes/no?
+      cookie:
+        secure: config.ssl.enable
+    )

-  app.use "/api/login", (req, res) ->
-    # dummy auth sets everyone to 123 and accepts only 123
-    req.session.uid = 123
-    req.session.save (err) ->
-    res.status(200).json {}
+    app.use "/api/login", (req, res) ->
+      # dummy auth sets everyone to 123 and accepts only 123
+      userExists req.body.id, (err, exists) ->
+        if err or !exists
+          res.status(401).end()
+        else
+          req.session.id = req.body.id
+          req.session.save (err) ->
+          res.status(202).json {}

-  # affects all app... requests after this one
-  # the only accessable thing before logging in is the login form
-  app.use "/api", (req, res, next) ->
-    if req.session.uid != 123
-      res.location(config.session.login_redirect)
-      next new Error "Please login before accesing the App"
-    else
-      next()
+    # affects all app... requests after this one
+    # the only accessable thing before logging in is the login form
+    app.use "/api", (req, res, next) ->
+      if !req.session.id?
+        res.location(config.session.login_redirect)
+        next new Error "Please login before accesing the App"
+      else
+        next()

-  app.use "/api/logout", (req, res) ->
-    req.session.destroy()
-    res.status(200).json {}
+    app.use "/api/logout", (req, res) ->
+      req.session.destroy()
+      res.status(202).json {}


adjust add/remove usermsg

adjust add/remove usermsg

--- src/uapanfw.coffee
+++ src/uapanfw.coffee
@@ -229,7 +229,7 @@
     fwdata.blacklist.push bl
     fs.writeFileSync blacklistfile, JSON.stringify(fwdata), 'utf-8'

-    usermsg = "You added `#{bl.val}` to firewall blacklist. " +
+    usermsg = "#{who} added `#{bl.val}` to firewall blacklist. " +
       "Expires `#{bl.expires}`.  Change will be applied in < 5 minutes."
     msg.reply usermsg

@@ -237,7 +237,6 @@
       "added entry to blacklist"
     robot.logger.info logmsg

-    usermsg = usermsg.replace(/^You/, un)
     #usermsg = usermsg.replace(/  Change will be applied in \< 5 minutes\./, '')
     notifySubscribers usermsg, who

@@ -286,7 +285,7 @@

     deltaN = fwdata.blacklist.length - new_bl.length
     if deltaN > 0
-      usermsg = "You removed `#{deltaN}` fw blacklist entries.  " +
+      usermsg = "#{who} removed `#{deltaN}` fw blacklist entries.  " +
         "Change will be applied in < 5 minutes. Removed: " +
         "```"+ deleted.join("\n") + "```"
       fwdata.blacklist = new_bl
@@ -300,7 +299,6 @@
     robot.logger.info logmsg

     if deltaN > 0
-      usermsg = usermsg.replace(/^You/,un)
       #usermsg = usermsg.replace(/  Change will be applied in \< 5 minutes\./, '')
       notifySubscribers usermsg, who


fixed bug when doing syncrecent for proxied

--- src/ui/util.coffee
+++ src/ui/util.coffee
@@ -24,7 +24,7 @@
 getProxiedName = (e) ->
     s = e?.chat_message?.message_content?.segment?[0]
     return unless s
-    return s?.formatting?.bold == 1 and isAboutLink(s?.link_data?.link_target)
+    return s?.formatting?.bold and isAboutLink(s?.link_data?.link_target)

 tryparse = (s) -> try JSON.parse(s) catch err then undefined


Fix reversed conditional

Fix reversed conditional

--- framer/Components/ShareComponent.coffee
+++ framer/Components/ShareComponent.coffee
@@ -69,7 +69,7 @@
 		@_renderInfo()
 		@_renderDescription() if @shareInfo.description
 		@_renderDate()
-		@_renderButtons() if !@shareInfo.openInFramerURL
+		@_renderButtons() if @shareInfo.openInFramerURL

 		# Evaluate content and set height accordingly
 		@_updateHeight()


add customize resource loader

add customize resource loader

--- src/server/virtual_browser/DOMWindowFactory.coffee
+++ src/server/virtual_browser/DOMWindowFactory.coffee
@@ -29,7 +29,11 @@
             url        : options.url
             browser    : @browser
             deferClose : true
-            features   :
+            resourceLoader : (resource, callback)->
+                # right now we do not do anything special
+                # logger("fetch resource #{resource.url.pathname}")
+                resource.defaultFetch(callback)
+            features :
                 FetchExternalResources : ['script', 'css', 'frame', 'link', 'iframe']
                 ProcessExternalResources : ['script']
                 MutationEvents : '2.0'


fix regtest

fix regtest

--- test/files/rsa_8192.iced
+++ test/files/rsa_8192.iced
@@ -62,7 +62,7 @@
 exports.decrypt = (T,cb) ->
   await km.unlock_pgp { passphrase : 'aabb' }, defer err
   T.no_error err
-  proc = new Message ring
+  proc = new Message { keyfetch : ring }
   await proc.parse_and_process { body : ctext }, defer err, out
   T.no_error err
   T.assert (not out[0].get_data_signer()?), "wasn't signed"


removing code for supporting 0.6

removing code for supporting 0.6

--- test/fixtures/app.coffee
+++ test/fixtures/app.coffee
@@ -51,7 +51,6 @@

 afterEach (done) ->
   db.wipe ->
-    server.close()
-    setTimeout done, 100
+    server.close done


Fix i18n substitution

Fix i18n substitution

--- src/i18n.coffee
+++ src/i18n.coffee
@@ -1,7 +1,7 @@
-I18n = (args...) ->
-  m = chrome.i18n.getMessage(args...)
+I18n = (id, args...) ->
+  m = chrome.i18n.getMessage(id, [args...])
   return escapeHtml(m) if m != ""
-  m = args[0].replace(/(.)([A-Z][a-z])/g, (m, p1, p2) -> "#{p1} #{p2.toLowerCase()}")
+  m = id.replace(/(.)([A-Z][a-z])/g, (m, p1, p2) -> "#{p1} #{p2.toLowerCase()}")
   m = m.replace(/_/g, ' ')
   escapeHtml(m)


add test for Project.set()

add test for Project.set()

--- test/project_test.coffee
+++ test/project_test.coffee
@@ -85,18 +85,6 @@

   ###
-    set: (newProjects) ->
-    setParam: (url, id, params) ->
-    remove: (url, id) ->
-    removeUrl: (url) ->
-    load: (callback) ->
-    sync: (callback) ->
-    new: (params) ->
-    clear: (callback) ->
-  ###
-
-
-  ###
    test for get()
   ###
   describe 'get()', ->
@@ -104,6 +92,7 @@
     it 'be empty', () ->
       projects = Project.get()
       expect(projects).to.be.empty
+

   ###
    test for getSelectable()
@@ -149,6 +138,134 @@

       projects = Project.getSelectable()
       expect(projects).to.not.be.empty
+
+
+  ###
+   test for set(newProjects)
+  ###
+  describe 'set(newProjects)', ->
+
+    it 'set 1 project to empty project', () ->
+      expect(Project.get()).to.be.empty
+      prj =
+        "http://redmine.com" :
+          index: 2
+          0:
+            text: "prj1_0"
+            show: SHOW.DEFAULT
+      Project.set(prj)
+      added = Project.get()
+      expect(added[prj1[0].url].index).to.be.equal(prj1[0].urlIndex)
+      expect(added[prj1[0].url][prj1[0].id].text).to.be.equal(prj1[0].text)
+      expect(added[prj1[0].url][prj1[0].id].show).to.be.equal(prj1[0].show)
+
+    it 'set 1 project to not empty project', () ->
+      prePrj =
+        "http://redmine.com/projects" :
+          index: 2
+          0:
+            text: "prj1_1"
+            show: SHOW.NOT
+      Project.set(prePrj)
+      expect(Project.get()).to.not.be.empty
+      prj =
+        "http://redmine.com" :
+          index: 2
+          0:
+            text: "prj1_0"
+            show: SHOW.DEFAULT
+      Project.set(prj)
+      added = Project.get()
+      expect(added[prj1[0].url].index).to.be.equal(prj1[0].urlIndex)
+      expect(added[prj1[0].url][prj1[0].id].text).to.be.equal(prj1[0].text)
+      expect(added[prj1[0].url][prj1[0].id].show).to.be.equal(prj1[0].show)
+
+    it 'set 2 different server\'s project', () ->
+      expect(Project.get()).to.be.empty
+      prj =
+        "http://redmine.com" :
+          index: 2
+          0:
+            text: "prj1_0"
+            show: SHOW.DEFAULT
+        "http://redmine.com2" :
+          index: 3
+          0:
+            text: "prj2_0"
+            show: SHOW.DEFAULT
+      Project.set(prj)
+      added = Project.get()
+      expect(added[prj1[0].url].index).to.be.equal(prj1[0].urlIndex)
+      expect(added[prj1[0].url][prj1[0].id].text).to.be.equal(prj1[0].text)
+      expect(added[prj1[0].url][prj1[0].id].show).to.be.equal(prj1[0].show)
+      expect(added[prj2[0].url].index).to.be.equal(prj2[0].urlIndex)
+      expect(added[prj2[0].url][prj2[0].id].text).to.be.equal(prj2[0].text)
+      expect(added[prj2[0].url][prj2[0].id].show).to.be.equal(prj2[0].show)
+
+    it 'set 2 different server\'s project', () ->
+      expect(Project.get()).to.be.empty
+      prj =
+        "http://redmine.com" :
+          index: 2
+          0:
+            text: "prj1_0"
+            show: SHOW.DEFAULT
+        "http://redmine.com2" :
+          index: 3
+          0:
+            text: "prj2_0"
+            show: SHOW.DEFAULT
+          1:
+            text: "prj2_1"
+            show: SHOW.DEFAULT
+      Project.set(prj)
+      added = Project.get()
+      expect(added[prj1[0].url].index).to.be.equal(prj1[0].urlIndex)
+      expect(added[prj1[0].url][prj1[0].id].text).to.be.equal(prj1[0].text)
+      expect(added[prj1[0].url][prj1[0].id].show).to.be.equal(prj1[0].show)
+      expect(added[prj2[0].url].index).to.be.equal(prj2[0].urlIndex)
+      expect(added[prj2[0].url][prj2[0].id].text).to.be.equal(prj2[0].text)
+      expect(added[prj2[0].url][prj2[0].id].show).to.be.equal(prj2[0].show)
+      expect(added[prj2[1].url].index).to.be.equal(prj2[1].urlIndex)
+      expect(added[prj2[1].url][prj2[1].id].text).to.be.equal(prj2[1].text)
+      expect(added[prj2[1].url][prj2[1].id].show).to.be.equal(prj2[1].show)
+
+    it 'set 3 different server\'s project', () ->
+      expect(Project.get()).to.be.empty
+      prj =
+        "http://redmine.com" :
+          index: 2
+          0:
+            text: "prj1_0"
+            show: SHOW.DEFAULT
+        "http://redmine.com3" :
+          index: 4
+          0:
+            text: "prj3_0"
+            show: SHOW.DEFAULT
+        "http://redmine.com2" :
+          index: 3
+          0:
+            text: "prj2_0"
+            show: SHOW.DEFAULT
+      Project.set(prj)
+      added = Project.get()
+      expect(added[prj1[0].url].index).to.be.equal(prj1[0].urlIndex)
+      expect(added[prj1[0].url][prj1[0].id].text).to.be.equal(prj1[0].text)
+      expect(added[prj1[0].url][prj1[0].id].show).to.be.equal(prj1[0].show)
+      expect(added[prj2[0].url].index).to.be.equal(prj2[0].urlIndex)
+      expect(added[prj2[0].url][prj2[0].id].text).to.be.equal(prj2[0].text)
+      expect(added[prj2[0].url][prj2[0].id].show).to.be.equal(prj2[0].show)
+      expect(added[prj3[0].url].index).to.be.equal(prj3[0].urlIndex)
+      expect(added[prj3[0].url][prj3[0].id].text).to.be.equal(prj3[0].text)
+      expect(added[prj3[0].url][prj3[0].id].show).to.be.equal(prj3[0].show)
+
+  ###
+   test for setParam(url, id, params)
+  ###
+  describe 'setParam(url, id, params)', ->
+
+

   ###
    test for add()


add test for getter-less field

add test for getter-less field

--- test/spec/getters.coffee
+++ test/spec/getters.coffee
@@ -114,6 +114,17 @@
     expect(model.get "answer").is.a("string").that.is.equal("42").and.is.not.equal(model.attributes.answer)
     expect(model.get "unexisting").is.equal "--undefined--"

+  it "should get attribute as-is when there is no getter", ->
+    Model = clazzMix
+      defaults: ->
+        answer: 42
+      computed:
+        answer:
+          set: ->
+
+    model = new Model
+    expect(model.get "answer").is.equal 42
+
   describe "functional dependencies", ->
     prevConfig = null


don't print out ignores when not ignoring

--- test/suite_runner.coffee
+++ test/suite_runner.coffee
@@ -30,7 +30,7 @@
           throw new Error "No such attribute to test: '#{attribute}'"
         else
           @run_attribute({constructor, context, attribute, attribute_suite, test_number})
-        if @ignore
+        if @ignore?[attribute]
           process.on "exit", =>
             console.log "Ignored these tests:", @ignore[attribute]
       else


Add numbered circle canvas icons.

Add numbered circle canvas icons.

--- src/widgets.coffee
+++ src/widgets.coffee
@@ -52,7 +52,7 @@
         initialize: (@dimension, options) ->
             @options.iconSize = new L.Point @dimension, @dimension
             @options.iconAnchor = @iconAnchor()
-            @options.reducedProminence = options.reducedProminence
+            @options.reducedProminence = options?.reducedProminence
             @options.pixelRatio = (el) ->
                 context = el.getContext('2d')
                 devicePixelRatio = window.devicePixelRatio || 1
@@ -150,6 +150,13 @@
         draw: (ctx) ->
             @clusterDrawer.draw ctx

+    NumberCircleCanvasIcon: CanvasIcon.extend
+        initialize: (@number, @dimension) ->
+            CanvasIcon.prototype.initialize.call this, @dimension
+            @drawer = new draw.NumberCircleMaker @dimension
+        draw: (ctx) ->
+            @drawer.drawNumberedCircle ctx, @number
+
     LeftAlignedPopup: L.Popup.extend
         _updatePosition: ->
             if !this._map


Add comments to explain cloning, handling of 'retriever'

--- lib/core.coffee
+++ lib/core.coffee
@@ -9,6 +9,7 @@
 _ = require 'lodash'

 bus = require './bus'
+# Clone a copy of the global default 'retriever'
 retriever = _.clone require('./retriever')

@@ -49,6 +50,9 @@
   init: (config, modules, _retriever) ->
     core.reset()
     modules or= []
+
+    # Mutate our closure-captured './retriever' clone with an
+    # injected '_retriever', if it exists
     retriever = _retriever if _retriever?

     # Attempt to load a global 'axiom.*' file from the project root
@@ -74,7 +78,12 @@
   reset: ->
     core.responders = {}
     core.modules = getAxiomModules()
+
+    # Re-clone the global './retriever' and use it to reset the closure-
+    # -captured 'retriever' reference for a clean slate, in case a
+    # '_retriever' had been injected upon the previous call to 'init'.
     core.retriever = _.clone require('./retriever')
+
     bus.utils.reset()

   load: (moduleName, module) ->


Set maxComplexity default to -1 with a description

--- lib/main.coffee
+++ lib/main.coffee
@@ -120,8 +120,9 @@
       items:
         type: 'string'
     maxComplexity:
+      description: 'McCabe complexity threshold (`-1` to disable)'
       type: 'integer'
-      default: 10
+      default: -1
     hangClosing:
       type: 'boolean'
       default: false


:bug: Fix a typo

:bug: Fix a typo

--- lib/main.coffee
+++ lib/main.coffee
@@ -24,7 +24,7 @@

   consumeLinter: (linter) ->
     if linter instanceof Array
-      for singleLinter of linter
+      for singleLinter in linter
         @consumeLinter(singleLinter)
     else
       if @_validateLinter(linter)


remove kernel commands

remove kernel commands

--- lib/main.coffee
+++ lib/main.coffee
@@ -31,7 +31,6 @@
             'hydrogen:run-all': => @runAll()
             'hydrogen:run-all-above': => @runAllAbove()
             'hydrogen:run-and-move-down': => @runAndMoveDown()
-            'hydrogen:show-kernel-commands': => @showKernelCommands()
             'hydrogen:toggle-watches': => @toggleWatchSidebar()
             'hydrogen:select-watch-kernel': => @showWatchKernelPicker()
             'hydrogen:select-kernel': => @showKernelPicker()


don't Grim until everything is on 1.0.0

don't Grim until everything is on 1.0.0

--- lib/main.coffee
+++ lib/main.coffee
@@ -98,5 +98,5 @@
   # Depreciated method associated with previous Services API
   # versioning that matched package version.
   legacyProvideStatusBar: ->
-     Grim.deprecate("Use versions ^1.0.0 of status-bar Service API.")
+     # Grim.deprecate("Use versions ^1.0.0 of status-bar Service API.")
      @provideStatusBar()


added route_prefix option

added route_prefix option

--- rest_controller.coffee
+++ rest_controller.coffee
@@ -13,6 +13,8 @@
     @white_lists or= {}
     @templates or= {}
     @logger or= console
+
+    @route = "#{@route_prefix}#{@route}" if @route_prefix

     app.get "#{@route}/:id", @_call(@show)
     app.get @route, @_call(@index)


call sclang-interpret with getBacktrace option

--- lib/repl.coffee
+++ lib/repl.coffee
@@ -87,8 +87,8 @@
         # =>
         @bus.push "
#{echo}
"

-      # expression path asString postErrors
-      @sclang.interpret(expression, nowExecutingPath, true, classic)
+      # expression path asString postErrors getBacktrace
+      @sclang.interpret(expression, nowExecutingPath, true, classic, !classic)
         .then(ok, err)

   recompile: ->


Remove rouge console.log file from the issues list view

--- static/src/scripts/app/views/IssuesListView.coffee
+++ static/src/scripts/app/views/IssuesListView.coffee
@@ -20,8 +20,6 @@

       context = _.extend context, data

-      console.log context
-
       @$el.html @template context

       @


Update listener ID

Update listener ID

--- src/mail.coffee
+++ src/mail.coffee
@@ -90,8 +90,8 @@
     #
     # Hubot events
     #
-    robot.enter (res) ->
+    robot.enter id: "mail.enter", (res) ->
         DeliverMail res

-    robot.hear /./i, id: "mail.listen", (res) ->
+    robot.hear /./i, id: "mail.hear", (res) ->
         DeliverMail res


make symbols public

make symbols public

--- src/main.coffee
+++ src/main.coffee
@@ -170,11 +170,11 @@
 #-----------------------------------------------------------------------------------------------------------
 @_get_remit_settings = ( hint, method ) ->
   defaults  =
-    first:    symbols.misfit
-    last:     symbols.misfit
-    between:  symbols.misfit
-    after:    symbols.misfit
-    before:   symbols.misfit
+    first:    @symbols.misfit
+    last:     @symbols.misfit
+    between:  @symbols.misfit
+    after:    @symbols.misfit
+    before:   @symbols.misfit
   settings  = assign {}, defaults
   switch arity = arguments.length
     when 1
@@ -187,11 +187,11 @@
         settings = assign settings, hint
     else throw new Error "µ19358 expected 1 or 2 arguments, got #{arity}"
   settings._surround = \
-    ( settings.first    isnt symbols.misfit ) or \
-    ( settings.last     isnt symbols.misfit ) or \
-    ( settings.between  isnt symbols.misfit ) or \
-    ( settings.after    isnt symbols.misfit ) or \
-    ( settings.before   isnt symbols.misfit )
+    ( settings.first    isnt @symbols.misfit ) or \
+    ( settings.last     isnt @symbols.misfit ) or \
+    ( settings.between  isnt @symbols.misfit ) or \
+    ( settings.after    isnt @symbols.misfit ) or \
+    ( settings.before   isnt @symbols.misfit )
   return { settings, method, }

 #-----------------------------------------------------------------------------------------------------------
@@ -215,11 +215,11 @@
   data_between  = settings.between
   data_after    = settings.after
   data_last     = settings.last
-  send_first    = data_first    isnt symbols.misfit
-  send_before   = data_before   isnt symbols.misfit
-  send_between  = data_between  isnt symbols.misfit
-  send_after    = data_after    isnt symbols.misfit
-  send_last     = data_last     isnt symbols.misfit
+  send_first    = data_first    isnt @symbols.misfit
+  send_before   = data_before   isnt @symbols.misfit
+  send_between  = data_between  isnt @symbols.misfit
+  send_after    = data_after    isnt @symbols.misfit
+  send_last     = data_last     isnt @symbols.misfit
   on_end        = null
   is_first      = true
   PS            = @
@@ -242,9 +242,8 @@
       self = @
       method data_last, send
       self = null
-      ### somewhat hidden in the docs: *must* call `@queue null` to end stream: ###
-      # defer -> @queue null
-      @queue null
+      # defer -> @queue PS.symbols.end
+      @queue PS.symbols.end
       return null
   #.........................................................................................................
   return pull_through on_data, on_end
@@ -269,13 +268,13 @@
   has_ended   = false
   #.........................................................................................................
   pipeline.push @$surround settings if settings._surround
-  pipeline.push @$surround { last: symbols.last, }
+  pipeline.push @$surround { last: @symbols.last, }
   #.........................................................................................................
   pipeline.push $paramap ( d, handler ) =>
     collector   = []
     #.......................................................................................................
     send = ( d ) =>
-      return handler true if d is null
+      return handler true if d is @symbols.end
       collector.unshift d
       return null
     #.......................................................................................................
@@ -285,7 +284,7 @@
       handler true if has_ended and call_count < 1
       return null
     #.......................................................................................................
-    if d is symbols.last
+    if d is @symbols.last
       has_ended = true
       handler true if call_count < 1
     else


Fix code formatting: alignment of ‘=’s

Fix code formatting: alignment of ‘=’s

--- src/main.coffee
+++ src/main.coffee
@@ -68,8 +68,8 @@
 width = size
 maxRadius = size / 2

-$canvas     = $ '#picker canvas'
-$svg        = $ '#picker svg'
+$canvas = $ '#picker canvas'
+$svg    = $ '#picker svg'

 ctx = $canvas[0].getContext '2d'
 contrasting = null


Filter out SASS partials and further refactoring

--- src/main.coffee
+++ src/main.coffee
@@ -208,8 +208,8 @@
   "#{context.config.output_dir}/" + base_path + path.basename(filepath, path.extname(filepath)) + '.html'

 # Ensure that the destination directory exists.
-ensure_directory = (dir, callback) ->
-  exec "mkdir -p #{dir}", -> callback()
+ensure_directory = (dir, cb) ->
+  exec "mkdir -p #{dir}", -> cb()

 file_exists = (path) ->
   try
@@ -224,7 +224,7 @@

 # Process our arguments, passing an array of sources to generate docs for,
 # and an optional relative root.
-parse_args = (callback) ->
+parseArgs = (cb) ->

   args = process.ARGV
   project_name = ""
@@ -256,40 +256,38 @@
   exec "find #{roots} -type f \\( #{lang_filter} \\)", (err, stdout) ->
     throw err if err

-    # Don't include hidden files, either
-    sources = stdout.split("\n").filter (file) -> file != '' and path.basename(file)[0] != '.'
+    sources = stdout.split("\n").filter (file) ->
+      return false if file is ''
+      filename = path.basename file
+      # Ignore hidden files
+      return false if filename[0] is '.'
+      # Ignore SASS partials
+      return false if filename.match /^_.*\.s[ac]ss$/
+      true
+
     console.log "styledocco: Recursively generating docs underneath #{roots}/"

-    callback(sources, project_name, args)
-
-check_config = (context)->
-  defaults = {
-    # show the timestamp on generated docs
-    show_timestamp: true,
-
-    # output directory for generated docs
-    output_dir: "docs",
-
-    # the projectname
-    project_name: context.options.project_name || '',
-
-    # source directory for any additional markdown documents including a
-    # index.md that will be included in the main generated page
-    content_dir: null
-  }
-  context.config = _.extend(defaults)
-
-parse_args (sources, project_name, raw_paths) ->
+    cb sources, project_name, args
+
+parseArgs (sources, project_name, raw_paths) ->
   # Rather than relying on globals, let's pass around a context w/ misc info
   # that we require down the line.
-  context = sources: sources, options: { project_name: project_name }
-
-
-  check_config(context)
+  context = {
+    sources
+    options: { project_name }
+  }
+  context.config = {
+    show_timestamp: yes
+    output_dir: 'docs'
+    project_name: project_name or ''
+  }

   ensure_directory context.config.output_dir, ->
     generate_readme(context, raw_paths)
     files = sources[0..sources.length]
-    next_file = -> generateDocumentation files.shift(), context, next_file if files.length
+    next_file = ->
+      if files.length
+        generateDocumentation files.shift(), context, next_file
     next_file()
-    if context.config.content_dir then generate_content context, context.config.content_dir
+    if context.config.content_dir?
+      generate_content context, context.config.content_dir


key to open dev tools (F12)

key to open dev tools (F12)

--- src/main.coffee
+++ src/main.coffee
@@ -16,6 +16,13 @@
 window.$window = ewin
 ewin.setAlwaysOnTop true
 ewin.setVisibleOnAllWorkspaces true
+
+addEventListener 'keydown', (event)->
+    if event.keyCode == 123
+        ewin.webContents.openDevTools({mode:'detach'})
+
+settings = ewin.settings
+window.$settings = settings

 AutoLaunch = require 'auto-launch'
 auto_launcher = window.auto_launcher = new AutoLaunch


no sync

no sync

--- src/main.coffee
+++ src/main.coffee
@@ -20,9 +20,10 @@
     rtokenpath:  paths.rtokenpath
     cookiespath: paths.cookiespath

+plug = (rs, rj) -> (err, val) -> if err then rj(err) else rs(val)
+
 logout = ->
     promise = client.logout()
-    plug = (rs, rj) -> (err, val) -> if err then rj(err) else rs(val)
     rm = (path) -> Q.Promise((rs, rj) -> fs.unlink(path, plug(rs, rj))).fail (err) ->
         if err.code == 'ENOENT' then null else Q.reject(err)
     promise = promise.then ->
@@ -148,11 +149,13 @@
         path = '/tmp/tmp.png'
         clipboard = require 'clipboard'
         pngData = clipboard.readImage().toPng()
-        fs.writeFileSync path, pngData
-        client.uploadimage(path).then (image_id) ->
+        Q.Promise (rs, rj) ->
+            fs.writeFile path, pngData, plug(rs, rj)
+        .then ->
+            client.uploadimage(path)
+        .then (image_id) ->
             client.sendchatmessage conv_id, null, image_id, null, client_generated_id
     , true
-

     # retry only last per conv_id
     ipc.on 'setconversationnotificationlevel', seqreq (ev, conv_id, level) ->


simplify acquisition i.o. not to confuse browserify


local store error extends storage error

local store error extends storage error

--- lib/argumenta/errors.coffee
+++ lib/argumenta/errors.coffee
@@ -8,8 +8,7 @@

   @Storage:          class StorageError extends BaseError
   @StorageConflict:  class ConflictError extends StorageError
-
-  @LocalStore:       class LocalStoreError extends BaseError
+  @LocalStore:       class LocalStoreError extends StorageError

   @User:             class UserError extends BaseError


Added destroyByQuery

Added destroyByQuery

--- rest_controller.coffee
+++ rest_controller.coffee
@@ -9,11 +9,14 @@
     @[key] = value for key, value of options
     @white_lists or= {}

+    app.get "#{@route}/:id", @show
     app.get @route, @index
-    app.get "#{@route}/:id", @show
+
     app.post @route, @create
     app.put "#{@route}/:id", @update
+
     app.del "#{@route}/:id", @destroy
+    app.del @route, @destroyByQuery

   index: (req, res) =>
     try
@@ -68,6 +71,8 @@

   destroy: (req, res) =>
     try
+      console.log "destroy: #{util.inspect(req.query)}"
+
       # TODO: is there a way to do this without the model? eg. transaction only (with confirmation of existence) - HEAD?
       if req.params.id
         @model_type.find req.params.id, (err, model) =>
@@ -77,11 +82,16 @@
             success: -> res.send(200)
             error: -> res.send(404)
           }
-      # query destroy
-      else
-        @model_type.destroy req.params, (err) =>
-          return res.status(500).send(error: err.toString()) if err
-          res.send(200)
+    catch err
+      res.status(500).send(error: err.toString())
+
+  destroyByQuery: (req, res) =>
+    try
+      @model_type.destroy Utils.parse(req.query), (err) =>
+        console.log "destroyByQuery: #{util.inspect(req.query)}"
+
+        return res.status(500).send(error: err.toString()) if err
+        res.send(200)
     catch err
       res.status(500).send(error: err.toString())


Added support for tests being given a function directly..

--- lib/test.coffee
+++ lib/test.coffee
@@ -27,6 +27,15 @@
   # Desc:
   #   Initialize the data for this object instance.
   constructor: (@_description, @_location, @_fn, @_timeout) ->
+    if @_description instanceof Function
+      @_fn = @_description
+      @_timeout = @_location
+      @_description = ''
+      @_location = ''
+    else if @_location instanceof Function
+      @_timeout = @_fn
+      @_fn = @_location
+      @_location = ''
     super @_fn, @_timeout


report line number of opening token if a pair (eg bracket) is left open

--- rewriter.coffee
+++ rewriter.coffee
@@ -171,16 +171,22 @@
   # the course of the token stream.
   ensure_balance: (pairs) ->
     levels: {}
+    open_line: {}
     @scan_tokens (prev, token, post, i) =>
       for pair in pairs
         [open, close]: pair
         levels[open] ||= 0
-        levels[open] += 1 if token[0] is open
+        if token[0] is open
+          open_line[open]: token[2] if levels[open] == 0
+          levels[open] += 1
         levels[open] -= 1 if token[0] is close
         throw new Error("too many ${token[1]} on line ${token[2] + 1}") if levels[open] < 0
       return 1
     unclosed: key for key, value of levels when value > 0
-    throw new Error("unclosed ${unclosed[0]}") if unclosed.length
+    if unclosed.length
+      open: unclosed[0]
+      line: open_line[open] + 1
+      throw new Error("unclosed ${open} on line ${line}")

   # We'd like to support syntax like this:
   #


Stop trying to make Map a polyfill.

Stop trying to make Map a polyfill.

ES6's map does provide items(), but it returns an iterator. It only really makes
sense to use it if we have `for-of` loops, so there's little point in trying to
fill in that behavior at the moment.

--- lib/util.coffee
+++ lib/util.coffee
@@ -10,47 +10,31 @@
       enumerable: false
       configurable: true

-# polyfill
-if Map?
+class exports.Map
+  constructor: ->
+    @cache = Object.create null
+    @proto_cache = undefined
+    @proto_set = false

-  if not Map.prototype.items
-    Map.prototype.items = () ->
-      results = []
-      i = this.iterator();
-      size = this.size()
-      if size
-        for num in [1..size]
-          results.push(i.next())
-      results
+  get: (key) ->
+    key = key.toString()
+    return @cache[key] unless key is '__proto__'
+    return @proto_cache

-  exports.Map = Map
+  has: (key) ->
+    key = key.toString()
+    return key of @cache unless key is '__proto__'
+    return @proto_set

-else
-  class exports.Map
-    constructor: ->
-      @cache = Object.create null
-      @proto_cache = undefined
-      @proto_set = false
+  set: (key, value) ->
+    unless key.toString() is '__proto__'
+      @cache[key] = value
+    else
+      @proto_cache = value
+      @proto_set = true
+    value

-    get: (key) ->
-      key = key.toString()
-      return @cache[key] unless key is '__proto__'
-      return @proto_cache
-
-    has: (key) ->
-      key = key.toString()
-      return key of @cache unless key is '__proto__'
-      return @proto_set
-
-    set: (key, value) ->
-      unless key.toString() is '__proto__'
-        @cache[key] = value
-      else
-        @proto_cache = value
-        @proto_set = true
-      value
-
-    items: ->
-      items = ([k,v] for k, v of @cache)
-      items.push ['__proto__', @proto_cache] if @proto_set
-      items
+  items: ->
+    items = ([k,v] for k, v of @cache)
+    items.push ['__proto__', @proto_cache] if @proto_set
+    items


remove last traces of `launchOnStartup`

remove last traces of `launchOnStartup`

--- lib/package/commands.coffee
+++ lib/package/commands.coffee
@@ -3,10 +3,6 @@

 module.exports =
   activate: (juno) ->
-
-    if atom.config.get("julia-client.launchOnStartup")
-      @withInk -> juno.connection.boot()
-
     requireClient    = (a, f) -> juno.connection.client.require a, f
     disrequireClient = (a, f) -> juno.connection.client.disrequire a, f
     boot = -> juno.connection.boot()


Cancel beforeSend if preventDefault is called

--- lib/rails/beforesend.coffee
+++ lib/rails/beforesend.coffee
@@ -30,4 +30,4 @@
       # Provide a global version of the `beforeSend` callback
       event = $.Event 'ajaxBeforeSend'
       $(element).trigger event, [xhr, settings]
-      event.result
+      if event.isDefaultPrevented() then false else event.result


Handle nested calls and function oneliners when chaining

--- rewriter.coffee
+++ rewriter.coffee
@@ -159,6 +159,10 @@
         tokens.splice i, 0, generate 'CALL_END', ')'
         i += 1

+      endAllImplicitCalls = ->
+        while inImplicitCall()
+          endImplicitCall()
+
       startImplicitObject = (j, startsLine = yes) ->
         idx = j ? i
         stack.push ['{', idx, sameLine: yes, startsLine: startsLine, ours: yes]
@@ -281,10 +285,13 @@
       #     .g b
       #     .h a
       #
-      if (prevTag is 'OUTDENT' or prevToken.newLine) and inImplicitCall() and
-          tag in ['.', '?.', '::', '?::']
-        endImplicitCall()
-        return forward(1)
+      if inImplicitCall() and tag in CALL_CLOSERS
+        if prevTag is 'OUTDENT'
+          endImplicitCall()
+          return forward(1)
+        if prevToken.newLine
+          endAllImplicitCalls()
+          return forward(1)

       stackTop()[2].sameLine = no if inImplicitObject() and tag in LINEBREAKS

@@ -363,7 +370,8 @@
       token[1] isnt ';' and token[0] in SINGLE_CLOSERS and
       not (token[0] is 'TERMINATOR' and @tag(i + 1) in EXPRESSION_CLOSE) and
       not (token[0] is 'ELSE' and starter isnt 'THEN') and
-      not (token[0] in ['CATCH', 'FINALLY'] and starter in ['->', '=>'])
+      not (token[0] in ['CATCH', 'FINALLY'] and starter in ['->', '=>']) or
+      token[0] in CALL_CLOSERS and @tokens[i - 1].newLine

     action = (token, i) ->
       @tokens.splice (if @tag(i - 1) is ',' then i - 1 else i), 0, outdent
@@ -478,3 +486,6 @@

 # Tokens that end a line.
 LINEBREAKS       = ['TERMINATOR', 'INDENT', 'OUTDENT']
+
+# Tokens that close open calls when they follow a newline.
+CALL_CLOSERS = ['.', '?.', '::', '?::']


second commit

second commit

--- scripts/example.coffee
+++ scripts/example.coffee
@@ -9,98 +9,8 @@
 #   These are from the scripting documentation: https://github.com/github/hubot/blob/master/docs/scripting.md

 module.exports = (robot) ->
-
-  # robot.hear /badger/i, (res) ->
-  #   res.send "Badgers? BADGERS? WE DON'T NEED NO STINKIN BADGERS"
-  #
-  # robot.respond /open the (.*) doors/i, (res) ->
-  #   doorType = res.match[1]
-  #   if doorType is "pod bay"
-  #     res.reply "I'm afraid I can't let you do that."
-  #   else
-  #     res.reply "Opening #{doorType} doors"
-  #
-  # robot.hear /I like pie/i, (res) ->
-  #   res.emote "makes a freshly baked pie"
-  #
-  # lulz = ['lol', 'rofl', 'lmao']
-  #
-  # robot.respond /lulz/i, (res) ->
-  #   res.send res.random lulz
-  #
-  # robot.topic (res) ->
-  #   res.send "#{res.message.text}? That's a Paddlin'"
-  #
-  #
-  # enterReplies = ['Hi', 'Target Acquired', 'Firing', 'Hello friend.', 'Gotcha', 'I see you']
-  # leaveReplies = ['Are you still there?', 'Target lost', 'Searching']
-  #
-  # robot.enter (res) ->
-  #   res.send res.random enterReplies
-  # robot.leave (res) ->
-  #   res.send res.random leaveReplies
-  #
-  # answer = process.env.HUBOT_ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING
-  #
-  # robot.respond /what is the answer to the ultimate question of life/, (res) ->
-  #   unless answer?
-  #     res.send "Missing HUBOT_ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING in environment: please set and try again"
-  #     return
-  #   res.send "#{answer}, but what is the question?"
-  #
-  # robot.respond /you are a little slow/, (res) ->
-  #   setTimeout () ->
-  #     res.send "Who you calling 'slow'?"
-  #   , 60 * 1000
-  #
-  # annoyIntervalId = null
-  #
-  # robot.respond /annoy me/, (res) ->
-  #   if annoyIntervalId
-  #     res.send "AAAAAAAAAAAEEEEEEEEEEEEEEEEEEEEEEEEIIIIIIIIHHHHHHHHHH"
-  #     return
-  #
-  #   res.send "Hey, want to hear the most annoying sound in the world?"
-  #   annoyIntervalId = setInterval () ->
-  #     res.send "AAAAAAAAAAAEEEEEEEEEEEEEEEEEEEEEEEEIIIIIIIIHHHHHHHHHH"
-  #   , 1000
-  #
-  # robot.respond /unannoy me/, (res) ->
-  #   if annoyIntervalId
-  #     res.send "GUYS, GUYS, GUYS!"
-  #     clearInterval(annoyIntervalId)
-  #     annoyIntervalId = null
-  #   else
-  #     res.send "Not annoying you right now, am I?"
-  #
-  #
-  # robot.router.post '/hubot/chatsecrets/:room', (req, res) ->
-  #   room   = req.params.room
-  #   data   = JSON.parse req.body.payload
-  #   secret = data.secret
-  #
-  #   robot.messageRoom room, "I have a secret: #{secret}"
-  #
-  #   res.send 'OK'
-  #
-  # robot.error (err, res) ->
-  #   robot.logger.error "DOES NOT COMPUTE"
-  #
-  #   if res?
-  #     res.reply "DOES NOT COMPUTE"
-  #
-  # robot.respond /have a soda/i, (res) ->
-  #   # Get number of sodas had (coerced to a number).
-  #   sodasHad = robot.brain.get('totalSodas') * 1 or 0
-  #
-  #   if sodasHad > 4
-  #     res.reply "I'm too fizzy.."
-  #
-  #   else
-  #     res.reply 'Sure!'
-  #
-  #     robot.brain.set 'totalSodas', sodasHad+1
-  #
-  # robot.respond /sleep it off/i, (res) ->
-  #   robot.brain.set 'totalSodas', 0
-  #   res.reply 'zzzzz'
+  robot.hear /疲れた/i, (msg) ->
+    msg.send msg.random ["弱気になったとき、まず一ヵ月後の自分を想像してみる。それが自分の好きな姿だとしたら、そのために何をするべきかを考える。そうすれば、少なくともその日までは目的意識を保ち続けることができる", "独りで苦しんでるんだろう 辛いだろう？ 暗いんだろう？ じゃあエースをねらえを歌ってみろよ！！！", "みんな！！竹になろうよ 竹ってさあ台風が来てもしなやかじゃない。台風負けないんだよ雪が来てもね　おもいっきりそれを跳ね除ける！！力強さがあるんだよそう、みんな！！！竹になろう！！！バンブー！！！", "僕が偉そうに話してることは全て、これまで僕ができなかったこと"]
+
+  robot.hear /帰りたい/i, (msg) ->
+    msg.send msg.random ["どうした。文句があるなら、自分でプレーしろ！", "人は完璧を求める。しかし、完璧だと思った時から全てがやり直しになる", "じゃんけんの必勝法は、強く握り締めたグーを出すこと", "苦しいか？ 笑え！！"]


Accept termination instance ids as comma or space delimited

--- scripts/pudding.coffee
+++ scripts/pudding.coffee
@@ -118,7 +118,9 @@
     list_instances robot, host, '', '', default_role, token, send_instances_summary_cb(robot, msg)

   whitelist_respond robot, /term(:?inate)? inst(:?ance)? (.+)/i, (robot, msg) ->
-    msg.match[3].split(/\s*,\s*/).map (instance_id) ->
+    msg.match[3].split(/\s*[,\s]\s*/).map (instance_id) ->
+      if instance_id is ''
+        return
       unless instance_id.match(/^i-/)
         instance_id = "i-#{instance_id}"
       channel = msg.envelope.room


Add test for entry values.

Add test for entry values.

--- scripts/tickets.coffee
+++ scripts/tickets.coffee
@@ -63,6 +63,13 @@
                 msg.send "Test: Seems you're already in the raffle."
             return

+    # Check entry values for testing
+    robot.respond /entry ([a-zA-Z0-9_]*)/i, (msg) ->
+        if robot.auth.hasRole(msg.envelope.user, ['admin'])
+            username = msg.match[1].toLowerCase()
+            entry = bought.indexOf(username)
+            msg.send entry
+            return

     # Close raffle, drawn winner and reset.
     robot.respond /raffle draw$/i, (msg) ->


fixes

fixes

--- lib/browser-plus-view.coffee
+++ lib/browser-plus-view.coffee
@@ -39,7 +39,7 @@
             @input type:'text',id:'uri',outlet:'uri',value:"#{params.uri}" ##{@uri}"

       @tag 'webview',class:"native-key-bindings",outlet: 'htmlv'#,preload:"file:///#{srcdir}/resources/browser-plus-client.js"
-      ,nodeintegration:'on',plugins:'on',src:"#{url}", disablewebsecurity:'on'
+      ,nodeintegration:'on',plugins:'on',src:"#{url}", disablewebsecurity:'on', allowfileaccessfromfiles:'on'

   initialize: ->
       @subscriptions.add atom.tooltips.add @back, title: 'Back'
@@ -60,6 +60,10 @@
       else
         Array.observe @model.browserPlus.fav, (ele)=>
           @checkFav()
+
+      @htmlv[0].addEventListener "permissionrequest", (e)->
+        debugger
+        e.request.allow()

       @htmlv[0].addEventListener "ipc-message", (evt)=>
         switch evt.channel


change comment to be a TODO

change comment to be a TODO

--- lib/browserify-create.coffee
+++ lib/browserify-create.coffee
@@ -3,7 +3,8 @@
   # create a Browserify instance with the input file and options
   browserify = require('browserify') op.inputFile, op.options

-  # NOTE: apply ignore/exclude/external options too?
+  # TODO: apply ignore/exclude/external options
+
   # apply transforms from `op`
   for transform in op.transforms
     browserify.transform transform.name, transform.options


use Bongo.bound version of bound, rather than implementing it in both places

--- core/KDObject.coffee
+++ core/KDObject.coffee
@@ -22,8 +22,7 @@
   else
     o:(o)->o

-  bound:(method, rest...)->
-    @["__bound__#{method}"] ?= => @[method] rest...
+  bound: Bongo.bound

   ready:(listener)->
     if @readyState > 0 then listener()


Remove unused cache option

Remove unused cache option

--- tasks/crawl.coffee
+++ tasks/crawl.coffee
@@ -17,14 +17,14 @@

   protocol = 'http:': http, 'https:': https

-  get = (urlStr, callback, cache = false) ->
+  get = (urlStr, callback) ->
     urlObj = url.parse urlStr
     protocol[urlObj.protocol].get urlObj, (res) ->
       if res.statusCode == 200
         data = ''
         res.on 'data', (chunk) -> data += chunk
         res.on 'end', -> callback data
-        res.pipe fs.createWriteStream(cachePath(urlStr)) if cache
+        res.pipe fs.createWriteStream(cachePath(urlStr))
       else
         grunt.warn "#{res.statusCode} while getting #{urlStr}."
     .on 'error', (err) -> grunt.warn "#{err} while getting #{urlStr}."
@@ -36,13 +36,13 @@
   , 64

   getCached = (urlStr, callback) ->
-    return get urlStr, callback, true if options.refresh
+    return get urlStr, callback if options.refresh
     cachedPath = cachePath urlStr
     fs.stat cachedPath, (err, stats) ->
       if !err && stats.mtime > cacheThreshold
         queue.push cachedPath, callback
       else
-        get urlStr, callback, true
+        get urlStr, callback

   get$ = (urlStr, callback) ->
     getCached urlStr, (data) -> callback cheerio.load(data)


implementing new background by actual visible lines

--- lib/editor-background.coffee
+++ lib/editor-background.coffee
@@ -190,10 +190,75 @@
     @elements.body.insertBefore main,@elements.body.firstChild

   insertTextBackground:->
-    txtBg = document.createElement 'style'
-    txtBg.type="text/css"
-    @elements.textBackground=txtBg
+    # CSS for background text
+    txtBgCss = document.createElement 'style'
+    txtBgCss.type="text/css"
+    txtBgCss.cssText="
+      .editor-background-line{
+        background:black;
+        color:white;
+      }
+    "
+    @elements.textBackgroundCss=txtBgCss
+    @elements.main.appendChild txtBgCss
+    # container of the background text
+    txtBg = document.createElement 'div'
+    txtBg.style.cssText="
+      position:absolute;
+      z-index:-1;
+    "
+    @elements.textBackground = txtBg
     @elements.main.appendChild txtBg
+
+  drawLine: (tokenizedLine) ->
+    line = document.createElement 'div'
+    line.className = 'editor-background-line line'
+    line.innerText = tokenizedLine.text
+    @elements.textBackground.appendChild line
+
+  drawLines: (attrs) ->
+    if attrs?
+      if attrs.editorRect? && attrs.screenLines?
+        @elements.textBackground.innerText = ''
+        @elements.textBackground.style.cssText="
+        top:#{attrs.editorRect.top}px;
+        left:#{attrs.editorRect.left}px;
+        right:#{attrs.editorRect.right}px;
+        bottom:#{attrs.editorRect.bottom}px;
+        position:absolute;
+        z-index:-1;
+        "
+        @drawLine line for line in attrs.screenLines
+
+  drawBackground: (event)->
+    console.log 'event',event
+    activeEditor = atom.workspace.getActiveEditor()
+    displayBuffer = activeEditor.displayBuffer
+    actualLines = displayBuffer.getVisibleRowRange()
+    screenLines = displayBuffer.buildScreenLines actualLines[0],actualLines[1]
+    activePane = atom.workspaceView.getActivePane()[0]
+    editorElement = activePane.querySelector 'atom-text-editor'
+    if editorElement?
+      editorRect = editorElement.getBoundingClientRect()
+      attrs =
+        {
+          editorElement:editorElement,
+          editorRect:editorRect,
+          screenLines:screenLines.screenLines
+        }
+      @drawLines attrs
+
+
+  watchEditor:(editor)->
+    console.log 'editor :)',editor
+    editor.onDidChangeScrollTop (scroll)=>@drawBackground.apply @,[{scrollTop:scroll}]
+    editor.onDidChangeScrollLeft (scroll)=>@drawBackground.apply @,[{scrolLeft:scroll}]
+    editor.onDidChange (change)=>@drawBackground.apply @,[{change:change}]
+
+
+  watchEditors: ->
+    atom.workspace.observeTextEditors (editor)=>@watchEditor.apply @,[editor]
+

   initialize: ->
     @elements.body = qr 'body'
@@ -210,9 +275,11 @@
     loaded = (@elements[k] for k in keys when @elements[k]?)

     if loaded.length == keys.length
+
       @insertMain()
       @insertTextBackground()
       @activateMouseMove()
+      @watchEditors()

       conf=atom.config.get('editor-background')

@@ -251,41 +318,6 @@
     x = (offsetX // factor)
     y = (offsetY // factor)
     inline @elements.bg,"background-position:#{x}px #{y}px !important;"
-
-  applyConfToEditors:->
-    txtBgCss="
-      .editor-background atom-text-editor::shadow .line>span{
-        position:relative;
-        overflow:visible;
-      }
-      .editor-background atom-text-editor::shadow .line>span:not(.invisible-character):not(.indent-guide):before{
-        position:absolute;
-        content:'';
-        height:80px;
-        top:-30px;
-        z-index:-1;
-        width:100%;
-        background:url(atom://editor-background/images/repeat-x.png);
-        background-size:100% 100%;
-        transform:translate3d(0,0,0);
-        perspective:1000px;
-      }
-      .editor-background atom-text-editor::shadow .line>span:not(.invisible-character):not(.indent-guide):after{
-        position:absolute;
-        content:'';
-        height:80px;
-        top:-30px;
-        left:100%;
-        z-index:-1;
-        width:45px;
-        background:url(atom://editor-background/images/right.png);
-        background-size:100% 100%;
-        transform:translate3d(0,0,0);
-        perspective:1000px;
-      }
-
-    "
-    @elements.textBackground.innerText=txtBgCss

   updateBox: (depth) ->
     conf=atom.config.get('editor-background')
@@ -438,8 +470,6 @@

       @blurImage()

-      @applyConfToEditors()
-
       if conf.treeViewOpacity > 0
         inline @elements.treeView,'background:'+newTreeRGBA+' !important;'
         inline @elements.left,'background:transparent !important;'


Cleaned up the execution manager module

Cleaned up the execution manager module

--- lib/execution-manager.coffee
+++ lib/execution-manager.coffee
@@ -1,41 +1,39 @@
-{Disposable}      = require('atom')
-child_process     = require('child_process')
-path              = require('path')
-
-# ------------------------------------------------------------------------------
+{Disposable}  = require 'atom'
+child_process = require 'child_process'
+path          = require 'path'

 module.exports =
 class ExecutionManager
-
-  ## Construction --------------------------------------------------------------
-
   constructor: (@levelCodeEditor) ->
     @executing = false

-  ## Level code execution ------------------------------------------------------
+  isExecuting: ->
+    return @executing

-  isExecuting: ->
-    @executing
+  startExecution: ({runExecArgs} = {}) ->
+    @terminal = @levelCodeEditor.getTerminal()

-  startExecution: ({runExecArgs}={}) ->
+    if @isExecuting() || @terminal.isExecuting()
+      return
+
     @textEditor = @levelCodeEditor.getTextEditor()
     @language = @levelCodeEditor.getLanguage()
     @level = @levelCodeEditor.getLevel()
-    @terminal = @levelCodeEditor.getTerminal()
-
-    return if @isExecuting() or @terminal.isExecuting()

     configKeyPath = 'levels.workspaceSettings.clearTerminalOnExecution'
-    @terminal.clear() if atom.config.get(configKeyPath)
-    @terminal.writeLn('Running level code...')
+    if atom.config.get configKeyPath
+      @terminal.clear()
+
+    @terminal.writeLn 'Running level code …'

     runExecPath = @language.getRunExecPath()
     configFilePath = @language.getConfigFilePath()
     levelNumber = @level.getNumber()
     filePath = @textEditor.getPath()
+
     cmd = ([
       "\"#{runExecPath}\""
-      '-l',"\"#{configFilePath}\""
+      '-l', "\"#{configFilePath}\""
     ].concat(runExecArgs).concat [
       "#{levelNumber}"
       "\"#{filePath}\""
@@ -45,33 +43,36 @@
     @processExited = false
     @processClosed = false

-    # spawn child process and set up handlers
-    @process = child_process.exec cmd,
-      cwd: path.dirname(runExecPath)
-      env: process.env
-    @terminalSubscr = @terminal.onDidEnterInput (input) =>
-      @process.stdin.write("#{input}\n")
-    @process.stdout.on('data',@handleProcessData)
-    @process.on('exit',@handleProcessExit)
-    @process.on('close',@handleProcessClose)
+    @process = child_process.exec cmd, {cwd: path.dirname runExecPath, env: process.env}
+    @terminalSubscription = @terminal.onDidEnterInput (input) => @process.stdin.write "#{input}\n"
+    @process.stdout.on 'data', @handleProcessData
+    @process.on 'exit', @handleProcessExit
+    @process.on 'close', @handleProcessClose
     @executionStarted()

+    return
+
   stopExecution: ->
-    return unless @isExecuting()
+    if !@isExecuting()
+      return
+
     @executionStoppedByUser = true
-    @process.stdout.removeListener('data',@handleProcessData)
+    @process.stdout.removeListener 'data', @handleProcessData
     @activeDataWriter?.dispose()
-    unless @processExited
-      @killProcess(@process.pid)
-    unless @processClosed
+    if !@processExited
+      @killProcess @process.pid
+    if !@processClosed
       @process.stdout.read()
       @process.stdout.destroy()
+
+    return

   executionStarted: ->
     @executing = true
     @terminal.enterScope()
     @terminal.didStartExecution()
     @levelCodeEditor.didStartExecution()
+    return

   executionStopped: ->
     @executing = false
@@ -80,95 +81,83 @@
     @levelCodeEditor.didStopExecution()
     if @executionStoppedByUser
       @executionStoppedByUser = false
-      @terminal.writeLn('...')
-      @terminal.writeSubtle('Execution stopped!')
-
-
-
-  ## Process event handling ----------------------------------------------------
+      @terminal.writeLn '…'
+      @terminal.writeSubtle 'Execution stopped!'
+    return

   handleProcessData: (data) =>
     @process.stdout.pause()
-    lines = data.toString().split('\n')
-    @activeDataWriter = @writeDataLines(lines)
+    lines = data.toString().split '\n'
+    @activeDataWriter = @writeDataLines lines
+    return

-  handleProcessExit: (code,signal) =>
+  handleProcessExit: (code, signal) =>
     @processExited = true
+    return

-  handleProcessClose: (code,signal) =>
-    @terminalSubscr.dispose()
+  handleProcessClose: (code, signal) =>
+    @terminalSubscription.dispose()
     @process.stdin.end()
     @processClosed = true
-    @executionStopped() unless @activeDataWriter?
-
-  ## Writing data to the terminal ----------------------------------------------
+    if !@activeDataWriter
+      @executionStopped()
+    return

   writeDataLines: (lines) ->
     intervalId = setInterval =>
-      if lines.length is 1
+      if lines.length == 1
         lastLine = lines.shift()
-        @writeDataLine(lastLine) if lastLine
+        if lastLine
+          @writeDataLine lastLine
         @activeDataWriter.dispose()
         @process.stdout.resume()
       else if lines.length > 1
-        @writeDataLine(lines.shift())
+        @writeDataLine lines.shift()
         @terminal.newLine()
-    ,10
+    , 10
     new Disposable =>
-      clearInterval(intervalId)
+      clearInterval intervalId
       @activeDataWriter = null
-      @executionStopped() if @processClosed
+      if @processClosed
+        @executionStopped()

   writeDataLine: (line) ->
-    @terminal.write(line)
-
-  ## Killing processes ---------------------------------------------------------
+    @terminal.write line
+    return

   killProcess: (pid) ->
     switch process.platform
-      when 'darwin' then @killProcessOnDarwinAndLinux(pid)
-      when 'linux'  then @killProcessOnDarwinAndLinux(pid)
-      when 'win32'  then @killProcessOnWin32(pid)
+      when 'darwin' then @killProcessOnDarwinAndLinux pid
+      when 'linux'  then @killProcessOnDarwinAndLinux pid
+      when 'win32'  then @killProcessOnWin32 pid

   killProcessOnDarwinAndLinux: (parentPid) ->
-    # get child process IDs
     try
-      out = child_process.execSync("pgrep -P #{parentPid}",{env: process.env})
+      out = child_process.execSync "pgrep -P #{parentPid}", {env: process.env}
       childPids = out.toString().split('\n')
-                    .map (pid) -> parseInt(pid)
-                    .filter (pid) -> not isNaN(pid)
+                    .map (pid) -> parseInt pid
+                    .filter (pid) -> not isNaN pid
     catch error
-      # execSync returns an error if the process has no childs, so we ignore
-      # this error here and continue with an empty childPids array
       childPids = []
-    # recursively kill child processes
     for childPid in childPids
-      @killProcessOnDarwinAndLinux(childPid)
-    # kill parent process
-    try process.kill(parentPid,'SIGINT')
+      @killProcessOnDarwinAndLinux childPid
+    try process.kill parentPid, 'SIGINT'
+
+    return

   killProcessOnWin32: (parentPid) ->
     try
       wmicProcess = child_process.spawn 'wmic', [
-        'process','where',"(ParentProcessId=#{parentPid})",'get','processid'
+        'process', 'where', "(ParentProcessId=#{parentPid})", 'get', 'processid'
       ]
       out = ''
-      wmicProcess.stdout.on 'data', (data) ->
-        out += data
+      wmicProcess.stdout.on 'data', (data) -> out += data
       wmicProcess.stdout.on 'close', =>
-        # get child process pids
         childPids = out.split(/\s+/)
-                      .filter (pid) -> /^\d+$/.test(pid)
-                      .map    (pid) -> parseInt(pid)
-                      .filter (pid) -> pid isnt parentPid and 0 < pid < Infinity
-        # recursively kill child processes
+                      .filter (pid) -> /^\d+$/.test pid
+                      .map (pid) -> parseInt pid
+                      .filter (pid) -> pid != parentPid && 0 < pid < Infinity
         for childPid in childPids
-          @killProcessOnWin32(childPid)
-        # kill parent process
-        try process.kill(parentPid,'SIGINT')
-    catch error
-      # TODO show proper error notification here
-      console.log("Spawn error!")
-      # ----------------------------------------
-
-# ------------------------------------------------------------------------------
+          @killProcessOnWin32 childPid
+        try process.kill parentPid, 'SIGINT'
+    return


Make /wall a mod command.

Make /wall a mod command.

--- server/commands.coffee
+++ server/commands.coffee
@@ -233,7 +233,7 @@
   next()

 desc "Announces something to the entire server. Usage: /wall message"
-makeAdminCommand "wall", "announce", (user, room, next, pieces...) ->
+makeModCommand "wall", "announce", (user, room, next, pieces...) ->
   message = pieces.join(',')
   return next()  if !message
   @announce("<strong>#{user.id}:</strong> #{message}")


make console scroll to bottom when shown

--- scripts/console.coffee
+++ scripts/console.coffee
@@ -90,8 +90,14 @@

   # I'd love to use CSS transitions but they don't work when the display
   # style changes.
-  show: -> @root.show arguments...
-  hide: -> @root.hide arguments...
+  show: ->
+    @root.show arguments...
+    @scrollToBottom
+    return
+
+  hide: ->
+    @root.hide arguments...
+    return

   log: (message, options) ->
     $('<div>')


Aggiunto comando ringrazia

Aggiunto comando ringrazia

--- scripts/example.coffee
+++ scripts/example.coffee
@@ -26,3 +26,6 @@

 	robot.hear /buongiorno/i, (res) ->
 		res.reply "Buongiorno il cazzo!!"
+
+	robot.respond /ringrazia (.*)/i, (res) ->
+		res.reply "Grazie " + res.match[1] + ", sei tutti noi!"


Add documentation

Add documentation

--- scripts/islolup.coffee
+++ scripts/islolup.coffee
@@ -1,4 +1,10 @@
-# Checks to see if league of legends is up
+# Description:
+#   Checks to see if league of legends is up
+# Commands:
+#   islolup - Checks isleagueoflegendsup.com to see if LoL is up
+# Author:
+#   mmatsumura
+
 module.exports = (robot) ->
   robot.respond /islolup/i, (msg) ->
     msg.http("http://isleagueoflegendsup.com")


Remove debug console.log

Remove debug console.log

--- scripts/players.coffee
+++ scripts/players.coffee
@@ -280,7 +280,6 @@
     window.resizeMedia = (width, height) ->
         return if not isVideo
         return if not ready
-        console.log(width, height)
         media.width = width
         media.height = height - 5
         if subtitleRenderers[id]?


Add delay on size detection because chrome sucks

--- scripts/players.coffee
+++ scripts/players.coffee
@@ -26,7 +26,9 @@
     if isVideo
         window.mediaSizeReporter = ->
             return { width: media.videoWidth, height: media.videoHeight }
-        window.updateSize()
+        setTimeout(() ->
+            window.updateSize() if window.updateSize?
+        , 500)

     updateMedia = ->
         if not ready


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/color2.coffee
+++ test/color2.coffee
@@ -266,22 +266,22 @@
   equal color.a, 1.0

 test "Color.mix", ->
-  color1 = Color2(50, 50, 50)
-  color2 = Color2(10, 10, 10)
+  color1 = Color2(50, 40, 60)
+  color2 = Color2(10, 5, 16)

   mixedColor = Color2.mix(color1, color2)

   equals mixedColor.r, 30
-  equals mixedColor.g, 30
-  equals mixedColor.b, 30
+  equals mixedColor.g, (45 / 2).round()
+  equals mixedColor.b, (76 / 2).round()
   equals mixedColor.a, 1.0

   weightedMixedColor = Color2.mix(color1, color2, 0.1)

-  equals mixedColor2.r(), (3.3 + 45).round()
-  equals mixedColor2.g(), (5.5 + 81).round()
-  equals mixedColor2.b(), (10 + 180).round()
-  equals mixedColor2.a(), 0.1 + (0.5 * 0.9)
+  equals weightedMixedColor.r(), ((50 * 0.1) + (10 * 0.1)).round()
+  equals weightedMixedColor.g(), (5.5 + 81)
+  equals weightedMixedColor.b(), (10 + 180)
+  equals weightedMixedColor.a(), 0.1 + (0.5 * 0.9)

 test "accepts named colors", ->
   white = Color2("white")


check content type of "potential" thumbnails

--- scripts/preview.coffee
+++ scripts/preview.coffee
@@ -147,11 +147,25 @@
   Media.register this

   PATTERN = /// \. (?: bmp | gif | jp2 | jpe?g | png | tiff? ) $///i
+  IMAGE_TYPES = [
+    'image/bmp', 'image/x-windows-bmp'
+    'image/gif',
+    'image/jpeg'
+    'image/png',
+    'image/tiff', 'image/x-tiff' ]

   @doesUriMatch: (uri) ->
     uri.path.match(PATTERN)?

   loadPreview: ->
-    @setPreviewImage 'images/camera.png'
+    $.ajax @uri, type: 'HEAD'
+      success: (_, status, response) =>
+        contentType = response.getResponseHeader 'Content-Type'
+        if contentType in IMAGE_TYPES
+          @setPreviewImage 'images/camera.png'
+          @setImageTitle @uri.path.split('/').pop()
+        else
+          @cancel()
+      error: => @cancel()

 window.Media = Media;


shuffleの引数はあらゆる空白で区切る

shuffleの引数はあらゆる空白で区切る

--- scripts/shuffle.coffee
+++ scripts/shuffle.coffee
@@ -23,5 +23,5 @@
       msg.reply "引数が必要です"
       return

-    arr = attr.split(" ")
+    arr = attr.split(/\s+/)
     msg.send shuffle(arr).join(" ")


weather script

weather script

--- scripts/weather.coffee
+++ scripts/weather.coffee
@@ -7,11 +7,10 @@
 module.exports = (robot) ->
   robot.respond /weather (.*)/i, (msg) ->
     location = escape(msg.match[1]) or 'Kansas City'
-    console.log(location)
     locationOptions =
       uri: 'http://maps.googleapis.com/maps/api/geocode/json'
       qs:
-        address: location
+        address: '64155'
       headers:
         'User-Agent': 'Request-Promise'
       json: true


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine.coffee
+++ test/engine.coffee
@@ -181,6 +181,6 @@
   engine.collides(Rectangle(0, 0, 10, 10), null)

 test "#setState", ->
-
+  engine

 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine.coffee
+++ test/engine.coffee
@@ -190,7 +190,7 @@
   engine.bind "stateEntered", ->
     ok true
   engine.bind "stateExited", ->
-
+    ok true

   engine.update()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine.coffee
+++ test/engine.coffee
@@ -185,7 +185,7 @@

   object = engine.add
     class: "GameObject"
-    velocity
+    velocity: Point(1, 0)

   engine.frameAdvance()


give time tracking its own category

give time tracking its own category

--- lib/analytics.coffee
+++ lib/analytics.coffee
@@ -32,7 +32,7 @@

   @ga? 'send', 'pageview'

-  # trackTimeTo 'impression', 'Javascript initialized'
+  trackTimeTo 'Javascript initialized'

   # Track 15 second bounce rate
   setTimeout =>
@@ -82,6 +82,7 @@
   segment: 'UI A/B Test Segments'
   error: 'UI Errors'
   multi: 'Multi-object Events'
+  timing: 'Timing'
   other: 'Other Events'

 module.exports.track = track =
@@ -124,7 +125,7 @@
 #
 # Needs to get the time from the server to account for any discrepancies with client-side time
 #
-module.exports.trackTimeTo = trackTimeTo = (type, description) ->
+module.exports.trackTimeTo = trackTimeTo = (description) ->
   return if not sd.REQUEST_TIMESTAMP?

   stopwatch = Date.now()
@@ -134,7 +135,7 @@
     success: (data) =>
       ajaxTime = Date.now() - stopwatch
       milliseconds = data.time - sd.REQUEST_TIMESTAMP - ajaxTime
-      track[type] description, {milliseconds: milliseconds}
+      track.timing description, {milliseconds: milliseconds}

 # Special multi-event
 #


Extend the shared environment to have a web server capable of testing the outcomes service

--- test/shared.coffee
+++ test/shared.coffee
@@ -1,4 +1,13 @@
-should            = require 'should'
+crypto       = require 'crypto'
+http         = require 'http'
+url          = require 'url'
+uuid         = require 'node-uuid'
+should       = require 'should'
+
+xml2js       = require 'xml2js'
+xml_builder  = require 'xmlbuilder'
+
+hmac_sha1    = require '../src/hmac-sha1'

 # Standard nonce tests
@@ -134,3 +143,217 @@
           valid.should.equal false
           done()

+
+
+# Creates a webserver that can respond to the outcomes service
+#
+exports.outcomesWebServer = () =>
+  buildXmlDocument = (type = 'Request') =>
+    # Build and configure the document
+    xmldec =
+      version:     '1.0'
+      encoding:    'UTF-8'
+
+    doc = xml_builder.create 'imsx_POXEnvelopeResponse', xmldec
+    doc.attribute 'xmlns', 'http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0'
+
+    return doc
+
+
+  cleanupValue = (value) =>
+    decodeURIComponent value.substr(1, value.length - 2)
+
+
+  verifySignature = (req, body) =>
+    params       = {}
+    signer       = new hmac_sha1
+    service_url  = 'http://127.0.0.1:1337/service/url'
+
+    return params if typeof req.headers.authorization != 'string'
+
+    for param in req.headers.authorization.split ','
+      parts = param.split '='
+      params[decodeURIComponent parts[0]] = cleanupValue parts[1]
+
+    delete params['OAuth realm']
+
+    body_signature = crypto.createHash('sha1').update(body).digest('base64')
+    req_signature  = signer.build_signature_raw service_url, url.parse(service_url), 'POST', params, 'secret'
+
+    return body_signature == params.oauth_body_hash and req_signature == params.oauth_signature
+
+
+  notFoundHandler = (req, res) =>
+    res.writeHead 404, 'Content-Type': 'text/html'
+    res.end 'Page not found'
+
+
+  outcomeTypeNotFoundHandler = (res, type) =>
+    res.writeHead 404, 'Content-Type': 'application/xml'
+
+    doc   = buildXmlDocument()
+    head  = doc.ele('imsx_POXHeader').ele('imsx_POXResponseHeaderInfo')
+
+    doc.ele 'imsx_POXBody'
+
+    head.ele 'imsx_version', 'V1.0'
+    head.ele 'imsx_messageIdentifier', uuid.v4()
+
+    sub_head = head.ele 'imsx_statusInfo'
+    sub_head.ele 'imsx_codeMajor', 'unsupported'
+    sub_head.ele 'imsx_severity', 'status'
+    sub_head.ele 'imsx_description', "#{type} is not supported"
+    sub_head.ele 'imsx_messageRefIdentifier', uuid.v4()
+    sub_head.ele 'imsx_operationRefIdentifier', type
+
+    res.end doc.end() + '\n'
+
+
+  invalidSignatureError = (res) =>
+    res.writeHead 403, 'Content-Type': 'application/xml'
+
+    doc   = buildXmlDocument()
+    head  = doc.ele('imsx_POXHeader').ele('imsx_POXResponseHeaderInfo')
+
+    doc.ele 'imsx_POXBody'
+
+    head.ele 'imsx_version', 'V1.0'
+    head.ele 'imsx_messageIdentifier', uuid.v4()
+
+    sub_head = head.ele 'imsx_statusInfo'
+    sub_head.ele 'imsx_codeMajor', 'failure'
+    sub_head.ele 'imsx_severity', 'signature'
+    sub_head.ele 'imsx_description', 'The signature provided is not valid'
+    sub_head.ele 'imsx_messageRefIdentifier', uuid.v4()
+    sub_head.ele 'imsx_operationRefIdentifier', 'signature'
+
+    res.end doc.end() + '\n'
+
+
+  invalidScoreError = (res) =>
+    res.writeHead 403, 'Content-Type': 'application/xml'
+
+    doc   = buildXmlDocument()
+    head  = doc.ele('imsx_POXHeader').ele('imsx_POXResponseHeaderInfo')
+
+    doc.ele 'imsx_POXBody'
+
+    head.ele 'imsx_version', 'V1.0'
+    head.ele 'imsx_messageIdentifier', uuid.v4()
+
+    sub_head = head.ele 'imsx_statusInfo'
+    sub_head.ele 'imsx_codeMajor', 'failure'
+    sub_head.ele 'imsx_severity', 'score'
+    sub_head.ele 'imsx_description', 'The score provided is not valid'
+    sub_head.ele 'imsx_messageRefIdentifier', uuid.v4()
+    sub_head.ele 'imsx_operationRefIdentifier', 'score'
+
+    res.end doc.end() + '\n'
+
+
+  validScoreResponse = (res, id, score) =>
+    res.writeHead 200, 'Content-Type': 'application/xml'
+
+    doc   = buildXmlDocument()
+    head  = doc.ele('imsx_POXHeader').ele('imsx_POXResponseHeaderInfo')
+
+    doc.ele('imsx_POXBody').ele('replaceResultResponse')
+
+    head.ele 'imsx_version', 'V1.0'
+    head.ele 'imsx_messageIdentifier', uuid.v4()
+
+    sub_head = head.ele 'imsx_statusInfo'
+    sub_head.ele 'imsx_codeMajor', 'success'
+    sub_head.ele 'imsx_severity', 'status'
+    sub_head.ele 'imsx_description', "The score for #{id} is now #{score}"
+    sub_head.ele 'imsx_messageRefIdentifier', uuid.v4()
+    sub_head.ele 'imsx_operationRefIdentifier', 'replaceResult'
+
+    res.end doc.end() + '\n'
+
+
+  validReadResponse = (res) =>
+    res.writeHead 200, 'Content-Type': 'application/xml'
+
+    doc   = buildXmlDocument()
+    head  = doc.ele('imsx_POXHeader').ele('imsx_POXResponseHeaderInfo')
+
+    result = doc.ele('imsx_POXBody').ele('readResultResponse').ele('result').ele('resultScore')
+    result.ele 'language', 'en'
+    result.ele 'textString', '.5'
+
+    head.ele 'imsx_version', 'V1.0'
+    head.ele 'imsx_messageIdentifier', uuid.v4()
+
+    sub_head = head.ele 'imsx_statusInfo'
+    sub_head.ele 'imsx_codeMajor', 'success'
+    sub_head.ele 'imsx_severity', 'status'
+    sub_head.ele 'imsx_description', 'Result read'
+    sub_head.ele 'imsx_messageRefIdentifier', uuid.v4()
+    sub_head.ele 'imsx_operationRefIdentifier', 'readResult'
+
+    res.end doc.end() + '\n'
+
+
+  validDeleteResponse = (res) =>
+    res.writeHead 200, 'Content-Type': 'application/xml'
+
+    doc   = buildXmlDocument()
+    head  = doc.ele('imsx_POXHeader').ele('imsx_POXResponseHeaderInfo')
+
+    doc.ele('imsx_POXBody').ele('deleteResultResponse')
+
+    head.ele 'imsx_version', 'V1.0'
+    head.ele 'imsx_messageIdentifier', uuid.v4()
+
+    sub_head = head.ele 'imsx_statusInfo'
+    sub_head.ele 'imsx_codeMajor', 'success'
+    sub_head.ele 'imsx_severity', 'status'
+    sub_head.ele 'imsx_description', 'Result deleted'
+    sub_head.ele 'imsx_messageRefIdentifier', uuid.v4()
+    sub_head.ele 'imsx_operationRefIdentifier', 'deleteResult'
+
+    res.end doc.end() + '\n'
+
+
+  outcomesHandler = (req, res) =>
+    headers  = 'Content-Type': 'application/xml'
+    body     = ''
+
+    req.on 'data', (buffer) =>
+      body += buffer.toString 'utf8'
+
+    req.on 'end', () =>
+      if (!verifySignature(req, body))
+        return invalidSignatureError(res)
+
+      xml2js.parseString body, trim: true, (err, result) =>
+        result_body = result?.imsx_POXEnvelopeRequest?.imsx_POXBody?[0]
+        result_type = Object.keys(result_body or {})[0]
+
+        switch result_type
+          when 'replaceResultRequest'
+            # As ugly as this may be this is one of the most effective XML parsers for node... yeah...
+            score = parseFloat result_body?.replaceResultRequest?[0].resultRecord?[0].result?[0].resultScore?[0].textString?[0], 10
+
+            if (score < 0 or score > 1)
+              return invalidScoreError res
+            else
+              return validScoreResponse res, null, score
+
+          when 'readResultRequest'
+            return validReadResponse res
+
+          when 'deleteResultRequest'
+            return validDeleteResponse res
+
+          else
+            return outcomeTypeNotFoundHandler(res, result_type or 'undefinedRequest')
+
+
+  http.createServer (req, res) =>
+    path     = url.parse req.url
+    handler  = if path.pathname == '/service/url' then outcomesHandler else notFoundHandler
+
+    handler req, res
+


Remove disabled tree view on deactivate

Remove disabled tree view on deactivate

--- lib/learn-ide.coffee
+++ lib/learn-ide.coffee
@@ -93,6 +93,7 @@
     @updater.checkForUpdate()

   deactivate: ->
+    localStorage.delete('disableTreeView')
     @termView = null
     @statusView = null
     @subscriptions.dispose()


Add setting initializer and Mixin support to Tool Class

--- libubret/tool.coffee
+++ libubret/tool.coffee
@@ -6,7 +6,14 @@
     'selection' : 'childSelection'
   }

+  mixin: []
+
+  settings: []
+
   constructor: (settings, parent=null) ->
+    # Mixins
+    _.each(@mixins, (mixin) => _.extend(@prototype, mixin))
+
     # Initialize Element
     @el = document.createElement('div')
     @d3el = d3.select(@el)
@@ -18,6 +25,9 @@
       .object().value()
     @state = new U.State({}, @, defaultEvents)
     @_setEvents()
+
+    # Initialize Settings
+    @settingViews = _.map(@settings, (setting) => new setting(@state))

     # Set Current State
     @setParent(parent) if parent


Added oldvalue to change events

Added oldvalue to change events

--- framer/Layer.coffee
+++ framer/Layer.coffee
@@ -38,8 +38,9 @@
 			# Convert the value
 			value = transformer(value, @, name) if transformer

+			oldValue = @_properties[name]
 			# Return unless we get a new value
-			return if value is @_properties[name]
+			return if value is oldValue

 			if value and validator and not validator(value)
 				layerValueTypeError(name, value)
@@ -58,7 +59,7 @@
 			# doesn't make sense, because no one can listen to use yet.
 			return if @__constructor

-			@emit("change:#{name}", value)
+			@emit("change:#{name}", value, oldValue)
 			@emit("change:point", value) if name in ["x", "y"]
 			@emit("change:size", value)  if name in ["width", "height"]
 			@emit("change:frame", value) if name in ["x", "y", "width", "height"]


Use model.type not model.constructor.name

--- bokehjs/src/coffee/models/canvas/cartesian_frame.coffee
+++ bokehjs/src/coffee/models/canvas/cartesian_frame.coffee
@@ -50,14 +50,14 @@
     for name, range of ranges
       if range instanceof DataRange1d or range instanceof Range1d
         if scale not instanceof LogScale and scale not instanceof LinearScale
-          throw new Error("Range #{range.constructor.name} is incompatible is Scale #{scale.constructor.name}")
+          throw new Error("Range #{range.type} is incompatible is Scale #{scale.type}")
         # special case because CategoricalScale is a subclass of LinearScale, should be removed in future
         if scale instanceof CategoricalScale
-          throw new Error("Range #{range.constructor.name} is incompatible is Scale #{scale.constructor.name}")
+          throw new Error("Range #{range.type} is incompatible is Scale #{scale.type}")

       if range instanceof FactorRange
         if scale not instanceof CategoricalScale
-          throw new Error("Range #{range.constructor.name} is incompatible is Scale #{scale.constructor.name}")
+          throw new Error("Range #{range.type} is incompatible is Scale #{scale.type}")

       if scale instanceof LogScale and range instanceof DataRange1d
         range.scale_hint = "log"


Terminate the old search if another is run.

--- src/project.coffee
+++ src/project.coffee
@@ -275,14 +275,19 @@
       excludeVcsIgnores: atom.config.get('core.excludeVcsIgnoredPaths')
       exclusions: atom.config.get('core.ignoredNames')

-    task = Task.once require.resolve('./scan-handler'), @getPath(), regex.source, searchOptions, ->
+    if @scanTask?
+      console.log 'terminating!'
+      @scanTask.terminate()
+
+    @scanTask = Task.once require.resolve('./scan-handler'), @getPath(), regex.source, searchOptions, =>
+      @scanTask = null
       deferred.resolve()

-    task.on 'scan:result-found', (result) =>
+    @scanTask.on 'scan:result-found', (result) =>
       iterator(result) unless @isPathModified(result.filePath)

     if _.isFunction(options.onPathsSearched)
-      task.on 'scan:paths-searched', (numberOfPathsSearched) ->
+      @scanTask.on 'scan:paths-searched', (numberOfPathsSearched) ->
         options.onPathsSearched(numberOfPathsSearched)

     for buffer in @buffers.getValues() when buffer.isModified()


[fix] Updated the display of the remaining unprioritized tickets in the Priority view when you move tickets into or out of the queue; Remembered sequence when clicking back and forth between other tabs

--- app/assets/javascripts/houston-scheduler/views/sequence2_view.coffee
+++ app/assets/javascripts/houston-scheduler/views/sequence2_view.coffee
@@ -68,6 +68,12 @@
             .find('.selected, .multiselectable-previous')
             .removeClass('selected multiselectable-previous')

+        receive: (event, ui)=>
+          if ui.item.closest('#sequence2_unsorted').length > 0
+            @clearSequenceOfTicketFor(ui.item)
+            @delayedNotifier.trigger()
+            @adjustVelocityIndicatorHeight()
+
         # unselect items in the opposite list
         click: (event, $e)->
           return unless $e.is('.sequence2-ticket')
@@ -92,9 +98,22 @@

+  clearSequenceOfTicketFor: ($el)->
+    id = $el.attr('data-ticket-id')
+    @setTicketSequence(id, null)
+
+  setTicketSequence: (id, value)->
+    return console.log("id was undefined") unless id
+    ticket = @tickets.get(id)
+    return console.log("ticket #{id} was not found") unless ticket
+    ticket.set('sequence': value)
+
+
+
   updateOrder: ->
     $tickets = $('#sequence2_sorted .sequence2-ticket')
     ids = _.map $tickets, (el)-> $(el).attr('data-ticket-id')
+    @setTicketSequence(ids[i], i + 1) for i in [0...ids.length]
     ids = "empty" if $tickets.length == 0
     url = window.location.pathname + '/ticket_order'
     $.put url, {order: ids}
@@ -120,10 +139,11 @@
       index = $ticket.index()
       $target = $("#sequence2_unsorted .sequence2-ticket:eq(#{index})")
       if $target.length == 0
-        $ticket.remove().appendTo('#sequence2_unsorted').data('multiselectable', true).pseudoHover()
+        $ticket = $ticket.remove().appendTo('#sequence2_unsorted').data('multiselectable', true).pseudoHover()
       else
-        $ticket.remove().insertBefore($target).data('multiselectable', true).pseudoHover()
-
+        $ticket = $ticket.remove().insertBefore($target).data('multiselectable', true).pseudoHover()
+
+      @clearSequenceOfTicketFor($ticket)
       @delayedNotifier.trigger()
       @adjustVelocityIndicatorHeight()

@@ -157,6 +177,8 @@

       @delayedNotifier.trigger()
       @adjustVelocityIndicatorHeight()
+
+

   moveSelectionLeft: ->
     $ticket = $('#sequence2_sorted .sequence2-ticket.selected')


Spacebar key toggles the open/close state of the currently highlighted entry.

--- app/assets/javascripts/ng-services/ng-KeyboardShortcutsSvc.js.coffee
+++ app/assets/javascripts/ng-services/ng-KeyboardShortcutsSvc.js.coffee
@@ -3,8 +3,8 @@
 ########################################################

 angular.module('feedbunch').service 'keyboardShortcutsSvc',
-['$rootScope', 'highlightedEntrySvc',
-($rootScope, highlightedEntrySvc)->
+['$rootScope', 'highlightedEntrySvc', 'entrySvc',
+($rootScope, highlightedEntrySvc, entrySvc)->

   #---------------------------------------------
   # Start responding to keyboard shortcuts
@@ -14,10 +14,11 @@
     # renaming a feed, subscribing to a new feed etc). Otherwise e.g. we cannot enter a folder name with "j" or "k" chars

     $(document).keypress (event)->
-      # j=106, k=107
-      if event.which in [106, 107]
+      # spacebar=32, j=106, k=107
+      if event.which in [32, 106, 107]
         if $rootScope.entries
           highlightedEntrySvc.next() if event.which == 106
           highlightedEntrySvc.previous() if event.which == 107
+          entrySvc.toggle_open_entry highlightedEntrySvc.get() if event.which == 32
         event.preventDefault()
 ]


server-side unit tests

server-side unit tests

--- server/fixtures.coffee
+++ server/fixtures.coffee
@@ -1,6 +1,7 @@
 if Footballers.find().count() == 0
-  [
+ _.shuffle([
     {name: "Francesco Totti", team: "Roma", role: "F"}
-    {name: "Mauro Icardi", team: "Inter": "F"}
-  ].forEach (data) ->
+    {name: "Mauro Icardi", team: "Inter", role: "F"}
+    {name: "Gigi Buffon", team: "Juventus", role: "G"}
+  ]).forEach (data) ->
       Footballers.insert data


Cleanup

Cleanup

--- lib/utilities/moment.coffee
+++ lib/utilities/moment.coffee
@@ -11,6 +11,5 @@
 module.exports = (Module, App, Backbone, Marionette, $, _) ->

   @listenTo App.channel, 'locale:loaded', (lang) ->
-      console.log 'Moment set locale', lang
-      if lang
-        Moment.locale lang+    if lang
+      Moment.locale lang