+ render vegetation texture

+ render vegetation texture

--- lib/map.coffee
+++ lib/map.coffee
@@ -16,15 +16,26 @@
     @data = data;

   createFields: () =>
-    for x in [0..@fieldWidth()]
-      for y in [0..@fieldHeight()]
-        texture = PIXI.Texture.fromImage("images/0_desert@2x.png");
-        vegetation = new PIXI.Sprite(texture); # , 110, 110
+    for y in [0..@fieldHeight()]
+      for x in [0..@fieldWidth()]
+        fieldData = @data['view'][y][x];
+        image = @vegetationTexture(fieldData['vegetation']['type']);
+        texture = PIXI.Texture.fromImage("images/#{image}");
+        vegetation = new PIXI.Sprite(texture);
         vegetation.position.x = x * @fieldSize;
         vegetation.position.y = y * @fieldSize;
-        #vegetation.tilePosition.x = 0;
-        #vegetation.tilePosition.y = 0;
         @layer.addChild(vegetation);
+
+  vegetationTexture: (vegetationType) =>
+    switch vegetationType
+      when 0 then "0_desert@2x.png"
+      when 1 then "1_grass@2x.png"
+      when 2 then "2_grass@2x.png"
+      when 3 then "3_grass@2x.png"
+      when 5 then "5_grass@2x.png"
+      when 8 then "8_forest@2x.png"
+      when 13 then "13_forest@2x.png"
+      else console.log("ERROR: unkown type: #{vegetationType}")

   fieldWidth: () ->
     Math.ceil(@width / @fieldSize) + 1


node gzip

node gzip

--- lib/nap.coffee
+++ lib/nap.coffee
@@ -18,6 +18,7 @@
 glob = require 'glob'
 rimraf = require 'rimraf'
 crypto = require 'crypto'
+zlib = require 'zlib'

 # The initial configuration function. Pass it options such as `assets` to let nap determine which
 # files to put together in packages.
@@ -407,17 +408,15 @@

 # Gzips a package.
 #
-# @param {String} contents The new file contents
+# @param {String} contents The file contents to be gzipped
 # @param {String} filename The name of the new file
+# @param {Function} callback The callback after gzipping

 gzipPkg = (contents, filename, callback) =>
   file = "#{process.cwd() + @_outputDir + '/'}#{filename}"
   ext = if _.endsWith filename, '.js' then '.jgz' else '.cgz'
-  exec "gzip #{file}", (err, stdout, stderr) ->
-    console.log stderr if stderr
-    fs.renameSync file + '.gz', file + ext
-    writeFile filename, contents
-    callback()
+  zlib.gzip contents, (err, output) ->
+    fs.writeFile file + ext, output.toString(), callback

 # Generate an md5 hash from the filename + filesize of a package.
 # Used to append a fingerprint to pacakge files for cache busting.
@@ -425,6 +424,7 @@
 # @param {String} pkgType The type `js`, `jst`, or `css` of the package
 # @param {String} pkgName The name of the package
 # @return {String} The md5 fingerprint to append
+
 fingerprintCache = { js: {}, jst: {}, css: {} }
 fingerprintForPkg = (pkgType, pkgName) =>
   return fingerprintCache[pkgType][pkgName] if fingerprintCache[pkgType][pkgName]?


Tests for packet id match change to packet ids starting at 1 instead of 0.

--- test/unit/packet-test.coffee
+++ test/unit/packet-test.coffee
@@ -8,7 +8,7 @@
   packet = new Packet(TYPE.PRELOGIN)

   test.ok(packet)
-  test.ok(packet.buffer.equals(new Buffer([TYPE.PRELOGIN, 0, 0, 8, 0, 0, 0, 0])))
+  test.ok(packet.buffer.equals(new Buffer([TYPE.PRELOGIN, 0, 0, 8, 0, 0, 1, 0])))

   test.done()

@@ -25,7 +25,7 @@

 exports.packetId = (test) ->
   packet = new Packet(TYPE.PRELOGIN)
-  test.strictEqual(packet.packetId(), 0)
+  test.strictEqual(packet.packetId(), 1)

   packet.packetId(2)
   test.strictEqual(packet.packetId(), 2)


cooldown finilazation

cooldown finilazation

--- src/cooldown.coffee
+++ src/cooldown.coffee
@@ -14,17 +14,7 @@

-player.I.health #=> 51

-player.update()
-# => `velocity is {x: 1, y: 0} and position is {x: 1, y: 0}`
-
-player.update()
-# => `velocity is {x: 2, y: 0} and position is {x: 3, y: 0}`
-
-# we've hit our maxSpeed so our velocity won't increase
-player.update()
-# => `velocity is {x: 2, y: 0} and position is {x: 5, y: 0}`

 @name Cooldown


removed phantoms from karma unit testing in dev env

--- karma.conf.coffee
+++ karma.conf.coffee
@@ -46,7 +46,7 @@
     # - Safari (only Mac)
     # - PhantomJS
     # - IE (only Windows)
-    browsers: ['Chrome', 'PhantomJS']
+    browsers: ['Chrome']

     # Continuous Integration mode
     # if true, it capture browsers, run tests and exit


Removed logging statements.

Removed logging statements.

--- picoCTF-web/web/coffee/management.coffee
+++ picoCTF-web/web/coffee/management.coffee
@@ -34,7 +34,6 @@
   onExceptionModification: ->
     apiCall "GET", "/api/admin/exceptions", {limit: 50}
     .done ((api) ->
-      console.log api.data
       @setState React.addons.update @state,
         exceptions: $set: api.data
     ).bind this
@@ -54,7 +53,6 @@
         $set: tab

   render: ->
-      console.log @state
       <TabbedArea activeKey={@state.tabKey} onSelect={@onTabSelect}>
         <TabPane eventKey='problems' tab='Manage Problems'>
           <ProblemTab problems={@state.problems} onProblemChange={@onProblemChange}


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com


Update version number in source

Update version number in source

--- src/enfield.coffee
+++ src/enfield.coffee
@@ -15,7 +15,7 @@
 CONFIG_FILENAME = '_config.yml'

 module.exports =
-  version: '0.0.1'
+  version: '0.2.0'
   main: (argv) ->
     optimist = require('optimist')
       .usage("""Enfield is a static-site generator modeled after Jekyll


Fixes flatten for no file

Fixes flatten for no file

--- lib/bucket.coffee
+++ lib/bucket.coffee
@@ -6,7 +6,7 @@
 _       = require 'underscore'
 md5     = require 'md5'
 httpBackup = require './httpBackup'
-touch = require 'touch'
+touch = require 'node-touch'

 # Keep track of any spawned child
 childProcessStash = {}


Added possibility to export the bucket to a "flattened" JSON file.

--- lib/bucket.coffee
+++ lib/bucket.coffee
@@ -34,7 +34,7 @@
     deleted     : []
     tailProcess : null
     writehashes : []
-    instanceid  : Math.floor((Math.random()*10000)+1);
+    instanceid  : Math.floor((Math.random()*10000)+1)

     obliterate : (cb) ->
       closing = () =>
@@ -188,6 +188,16 @@
     discardUnstoredChanges : () ->
       @dirty   = {}
       @deleted = []
+
+#   flattenAndExportAsync, save the current bucket to file, flattened to a single object (that is, clean up the data)
+    flattenAndExportAsync : (fileName, callback) ->
+      fs.exists fileName, (exists) ->
+        if exists
+          callback "File #{fileName} already exists"
+        else
+          bucketJson = JSON.stringify @bucket
+          fs.writeFile fileName, bucketJson, {encoding:'utf8'}, (err) ->
+            callback err
   }

 exports.bucket = null


Fixing queue ordering.

Fixing queue ordering.

--- src/tegh.coffee
+++ src/tegh.coffee
@@ -192,7 +192,7 @@
       style: { 'padding-left': 1, 'padding-right': 1 }

     i = 0
-    for job in jobs
+    for job in jobs.sortBy 'position'
       @_printJob table, job, (if job.status == 'printing' then i else i++)
     if jobs.length == 0
       @_append "  There are no jobs in the print queue."


clarify

clarify

--- lib/clever.coffee
+++ lib/clever.coffee
@@ -203,7 +203,7 @@

     remove: (cb) =>
       r = new Remove "#{clever.url_base}#{@_uri}"
-      r.post 'exec', (resp, body, cb_post) -> cb_post null # No error
+      r.post 'exec', (resp, body, cb_post) -> cb_post null # No error if we got this far
       r.exec cb

     to_json: => _(@_properties).clone()


Better format_on_save description

Better format_on_save description

--- lib/config.coffee
+++ lib/config.coffee
@@ -3,7 +3,7 @@
   format_on_save:
     type: 'boolean'
     default: true
-    description: 'Enabling will allow JSFormat to format all supported files before the file is saved'
+    description: 'JSFormat will format the file before it is saved'
   indent_with_tabs:
     type: 'boolean'
     default: false


Increase timeout on crop test

Increase timeout on crop test

--- spec/Crop.coffee
+++ spec/Crop.coffee
@@ -33,7 +33,7 @@
   describe 'when passed a canvas', ->
     describe 'with a valid rectangle', ->
       it 'should crop it', (done) ->
-        @timeout 10000
+        @timeout 20000
         rect =
           x: 40
           y: 40
@@ -59,7 +59,7 @@

     describe 'with a rectangle bigger than image', ->
       it 'should crop it respecting boundaries', (done) ->
-        @timeout 10000
+        @timeout 20000
         groupId = 'rectangle-ranges'
         groups = []
         out.once 'begingroup', (group) ->
@@ -88,7 +88,7 @@

     describe 'with a rectangle with negative coordinates', ->
       it 'should crop it respecting boundaries', (done) ->
-        @timeout 10000
+        @timeout 20000
         groupId = 'rectangle-ranges'
         groups = []
         out.once 'begingroup', (group) ->


getTextNodes: walk the tree using TreeWalker

The old implementation was really expensive on a range covering many
rows of table. I think it's because an array was being allocated at
every level of the tree, then flattened out into the level above.

Instead here we just do a depth-first search for text nodes and build up
a single array as we go.  In a little benchmark with a range covering
every cell of a 5000×3 table:

* master: 1.57s ±1.14% (6 runs sampled)
* TreeWalker: 17.5ms ±0.74% (72 runs sampled)

--- src/util.coffee
+++ src/util.coffee
@@ -60,27 +60,13 @@
 #
 # Returns a new jQuery collection of text nodes.
 Util.getTextNodes = (jq) ->
-  getTextNodes = (node) ->
-    if node and node.nodeType != Util.NodeTypes.TEXT_NODE
-      nodes = []
+  getTextNodes = (root) ->
+    document = Util.getGlobal().document
+    walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT)
+    nodes = (node while node = walker.nextNode())
+    return nodes

-      # If not a comment then traverse children collecting text nodes.
-      # We traverse the child nodes manually rather than using the .childNodes
-      # property because IE9 does not update the .childNodes property after
-      # .splitText() is called on a child text node.
-      if node.nodeType != Util.NodeTypes.COMMENT_NODE
-        # Start at the last child and walk backwards through siblings.
-        node = node.lastChild
-        while node
-          nodes.push getTextNodes(node)
-          node = node.previousSibling
-
-      # Finally reverse the array so that nodes are in the correct order.
-      return nodes.reverse()
-    else
-      return node
-
-  jq.map -> Util.flatten(getTextNodes(this))
+  jq.map -> getTextNodes(this)

 Util.getGlobal = -> (-> this)()


MoveToRelativeLine now aware-of-folded-row affect yy, dd etc.

--- lib/motion.coffee
+++ lib/motion.coffee
@@ -700,8 +700,21 @@
   wise: 'linewise'
   moveSuccessOnLinewise: true

-  moveCursor: (cursor) ->
-    setBufferRow(cursor, cursor.getBufferRow() + @getCount(-1))
+  getRow: (row) ->
+    if @editor.isFoldedAtBufferRow(row)
+      getLargestFoldRangeContainsBufferRow(@editor, row).end.row
+    else
+      row
+
+  moveCursor: (cursor) ->
+    row = @getRow(cursor.getBufferRow())
+
+    count = @getCount(-1)
+    while (count > 0)
+      row = @getRow(row + 1)
+      count--
+
+    setBufferRow(cursor, row)

 class MoveToRelativeLineMinimumOne extends MoveToRelativeLine
   @extend(false)


Added some convenience methods for numbers.

--- lib/number.coffee
+++ lib/number.coffee
@@ -163,12 +163,14 @@
         NumberBase.downcast x[namex] y

   gcd__: (other) ->
-    step = (a, b) -> if b.sgn() > 0 then -> step b, a.mod(b) else a
+    step = (a, b) -> if b.isPos() then -> step b, a.mod(b) else a

     [x, y] = [@abs(), other.abs()]
     if x.cmp(y) > 0 then bounce step x, y else bounce step x, y

-  for name in ['neg', 'abs', 'sgn']
+  for name in [
+    'neg', 'abs', 'sgn', 'isPos', 'isNeg', 'isZero', 'isEven', 'isOdd'
+  ]
     do (name) ->
       namex = "#{name}__"
       operator name, (a) -> makeNum(a)[namex]()
@@ -177,8 +179,8 @@

   operator 'pow', (a, b) ->
     step = (p, r, s) ->
-      if s.sgn() > 0
-        if s.mod(2).sgn() > 0
+      if s.isPos()
+        if s.isOdd() > 0
           -> step p.times(r), r, s.minus 1
         else
           -> step p, r.times(r), s.div 2
@@ -201,6 +203,16 @@
   abs__: -> new CheckedInt Math.abs @val

   sgn__: -> if @val < 0 then -1 else if @val > 0 then 1 else 0
+
+  isPos__: -> @val > 0
+
+  isNeg__: -> @val < 0
+
+  isZero__: -> @val == 0
+
+  isEven__: -> @val % 2 == 0
+
+  isOdd__: -> @val % 2 != 0

   sqrt__: -> new CheckedInt Math.floor Math.sqrt @val

@@ -229,13 +241,25 @@
 # Here are the beginnings of the `LongInt` class.

 class LongInt extends NumberBase
-  constructor: (sign, @digits) -> @sign = if @digits? then sign else 0
+  constructor: (sign, @digits) ->
+    @sign = if @digits? then sign else 0
+    @first = @digits?.first() or 0

   neg__: -> new LongInt -@sign, @digits

   abs__: -> new LongInt 1, @digits

   sgn__: -> @sign
+
+  isPos__: -> @sign > 0
+
+  isNeg__: -> @sign < 0
+
+  isZero__: -> @sign == 0
+
+  isEven__: -> @first % 2 == 0
+
+  isOdd__: -> @first % 2 != 0

   zeroes = BASE.toString()[1..]

@@ -258,9 +282,9 @@
     bounce step s.take n >> 1

   sqrt__: ->
-    if @sign == 0
+    if @isZero()
       asNum 0
-    else if @sign > 0
+    else if @isPos()
       new LongInt 1, sqrt @digits
     else
       throw new Error "expected a non-negative number, got #{this}"
@@ -269,9 +293,9 @@
     seq.sub(r, s)?.reverse()?.dropWhile((x) -> x == 0)?.first() or 0

   cmp__: (x) ->
-    if @sign == 0
+    if @isZero()
       -x.sign
-    else if x.sign == 0
+    else if x.isZero()
       @sign
     else if @sign != x.sign
       @sign
@@ -391,7 +415,7 @@
   toString: ->
     parts = @digits?.reverse()?.dropWhile((d) -> d == 0)?.map (d) -> d.toString()
     if parts
-      sign = if @sign < 0 then '-' else ''
+      sign = if @isNeg() then '-' else ''
       rest = parts.rest()?.map (t) -> "#{zeroes[t.length..]}#{t}"
       sign + parts.first() + if rest? then rest.join '' else ''
     else
@@ -480,3 +504,10 @@
   show -> num.sqrt 99980001
   show -> num.pow 2, 16
   show -> num.abs -12345
+  show -> num.isZero 1
+  show -> num.isZero 123456
+  show -> num.isZero 0
+  show -> num.isNeg 0
+  show -> num.isNeg -45
+  show -> num.isNeg -12345
+  show -> num.isOdd -12345


Make sure editor is set before trying to get code.

--- lib/script.coffee
+++ lib/script.coffee
@@ -22,14 +22,15 @@

     atom.workspaceView.command "script:run-selection", =>
       editor = atom.workspace.getActiveEditor()
+
+      if not editor?
+        console.log("Editor unavailable")
+        return
+
       code = editor.getSelectedText()

       if not code? or not code
         code = editor.getText()
-
-      if not editor?
-        console.log("Editor unavailable")
-        return

       grammar = editor.getGrammar()


Added on mount handler for exppress app

Added on mount handler for exppress app

--- lib/server.coffee
+++ lib/server.coffee
@@ -3,6 +3,11 @@

 app = exports = module.exports = express()

+app.on 'mount', (parent) ->
+    debug 'Seems i was mounted in another express app.'
+    debug 'locals', parent.locals
+    debug 'neo4j settings', parent.get 'neo4j'
+
 app.get '/', (request, response) ->
     response.send '
Hello World
'


wrong type sent to datadog

wrong type sent to datadog

--- lib/server.coffee
+++ lib/server.coffee
@@ -279,7 +279,7 @@
 									jobItem._status = "completed"
 									jobItem._lastchange = ~~((new Date).getTime() / 1000)
 									datalog.legauge 'task_completed', c.node.name
-									datalog.legauge 'task_exectime', c.timeTaken, [ 'exec:' +  (jobItem.exec.split("/").pop()).split(" ")[0]] if c.timeTaken?
+									datalog.gauge 'task_exectime', c.timeTaken, [ 'exec:' +  (jobItem.exec.split("/").pop()).split(" ")[0]] if c.timeTaken?
 					else
 						if c.startup
 							console.log 'Server Reconnected ... ', c.startup


fix API calls to get current themes.

fix API calls to get current themes.

--- lib/sunset.coffee
+++ lib/sunset.coffee
@@ -5,25 +5,25 @@
     daytime_syntax_theme:
       title: 'Daytime Theme (Syntax)'
       description: 'What syntax theme should I use during daylight?'
-      default: atom.themes.getActiveThemeNames()[0]
+      default: ''
       type: 'string'
       enum: atom.config.settings.core.themes
     daytime_ui_theme:
       title: 'Daytime Theme (UI)'
       description: 'What UI theme should I use during daylight?'
-      default: atom.themes.getActiveThemeNames()[1]
+      default: ''
       type: 'string'
       enum: atom.config.settings.core.themes
     nighttime_syntax_theme:
       title: 'Nighttime Theme (Syntax)'
       description: 'What syntax theme should I use during nighttime?'
-      default: atom.themes.getActiveThemeNames()[0]
+      default: ''
       type: 'string'
       enum: atom.config.settings.core.themes
     nighttime_ui_theme:
       title: 'Nighttime Theme (UI)'
       description: 'What UI theme should I use during nighttime?'
-      default: atom.themes.getActiveThemeNames()[1]
+      default: ''
       type: 'string'
       enum: atom.config.settings.core.themes
     when_does_it_get_dark:
@@ -52,13 +52,20 @@
     @bindEvents()
     @tick()

+    syntax_themes = atom.themes.getLoadedThemes().filter((theme) -> theme.metadata.theme is 'syntax').map((theme) -> theme.name)
+    ui_themes = atom.themes.getLoadedThemes().filter((theme) -> theme.metadata.theme is 'ui').map((theme) -> theme.name)
+
     # hack, but populate the settings so people can easily pick themes to change between
     @config.daytime_syntax_theme.enum =
-      @config.nighttime_syntax_theme.enum =
-      atom.themes.getLoadedThemes().filter((theme) -> theme.metadata.theme is 'syntax').map((theme) -> theme.name)
+      @config.nighttime_syntax_theme.enum = syntax_themes
+
     @config.daytime_ui_theme.enum =
-      @config.nighttime_ui_theme.enum =
-      atom.themes.getLoadedThemes().filter((theme) -> theme.metadata.theme is 'ui').map((theme) -> theme.name)
+      @config.nighttime_ui_theme.enum = ui_themes
+
+    @config.daytime_ui_theme.default =
+      @config.nighttime_ui_theme.default = ui_themes[0]
+    @config.daytime_syntax_theme.default =
+      @config.nighttime_syntax_theme.default = syntax_themes[0];

     # Events subscribed to in atom's system can be easily cleaned up with a CompositeDisposable
     @subscriptions = new CompositeDisposable
@@ -94,12 +101,14 @@
   tick: ->
     now = new Date
     twenty_four_hour = (now.getHours() * 100) + now.getMinutes()
-    themes = atom.config.settings.core.themes.slice() # copy the array
+    current_theme_names = atom.themes.getActiveThemeNames()
+    themes = current_theme_names.slice() # copy the array
     themes = @daytime_themes if twenty_four_hour >= atom.config.get('sunset.when_does_it_get_light')
     themes = @nighttime_themes if twenty_four_hour >= atom.config.get('sunset.when_does_it_get_dark')
+    themes = @nighttime_themes if twenty_four_hour < atom.config.get('sunset.when_does_it_get_light')

     # store the theme change :D
-    atom.config.set('core.themes', themes) if themes[0] != atom.config.settings.core.themes[0] || themes[1] != atom.config.settings.core.themes[1]
+    atom.config.set('core.themes', themes) unless themes.filter((t) -> current_theme_names.indexOf(t) > -1).length == current_theme_names.length

   deactivate: ->
     @subscriptions.dispose()


More debugging

More debugging

--- lib/worker.coffee
+++ lib/worker.coffee
@@ -347,29 +347,30 @@
     if (cached = @cache[key]) != undefined
       cached
     else if (value = @manager.check_helpers(key)) != undefined
-      console.log "worker: helper (#{key})"
+      wat = "#{@key} <- #{key}"
+      console.log "worker: helper (#{wat})"
       if typeof(value) == 'function'
-        console.log "worker: helper function (#{key})"
+        console.log "worker: helper function (#{wat})"
         yielded = false
         the_result = undefined
         value (result) =>
-          console.log "worker: helper callback (#{key})"
+          console.log "worker: helper callback (#{wat})"
           if yielded
-            console.log "worker: running fiber (#{key})"
+            console.log "worker: running fiber (#{wat})"
             @fiber.run result
           else
-            console.log "worker: no need to run fiber (#{key})"
+            console.log "worker: no need to run fiber (#{wat})"
             the_result = result
         if the_result != undefined
-          console.log "worker: no need to yield (#{key})"
+          console.log "worker: no need to yield (#{wat})"
           return the_result
         yielded = true
-        console.log "worker: helper yielding (#{key})"
+        console.log "worker: helper yielding (#{wat})"
         v = Fiber.yield()
-        console.log "worker: helper resumed (#{key})"
+        console.log "worker: helper resumed (#{wat})"
         v
       else
-        console.log "worker: helper value (#{key})"
+        console.log "worker: helper value (#{wat})"
         value
     else if (cached = @manager.check_cache(key)) != undefined
       msg = source: key, target: @key


Disable the overlay select test case.

Disable the overlay select test case.

--- test/e2e/imageDetailSpec.coffee
+++ test/e2e/imageDetailSpec.coffee
@@ -6,7 +6,7 @@

 class ImageDetailPage extends Page
   displayPanel: ->
-    @find('qi-series-image')
+    @find('qi-image')

   overlaySelector: ->
     @find('.qi-overlay-select')
@@ -14,7 +14,7 @@
   imageCtlPanel: ->
     @find('.qi-image-ctl')

-describe.only 'E2E Testing Image Detail', ->
+describe 'E2E Testing Image Detail', ->
   page = null

   beforeEach ->
@@ -37,7 +37,7 @@

   it 'should display a contact email link', ->
     pat = /a href="mailto:\w+@ohsu.edu"/
-    expect(page.contactInfo(), 'The email address is missing')
+    expect(page.contactInfo, 'The email address is missing')
       .to.eventually.match(pat)

   describe 'Image Display', ->
@@ -49,14 +49,15 @@
     it 'should display the image', ->
       expect(panel, 'The image panel is missing').to.eventually.exist

-  describe 'Overlay Selection Buttons', ->
-    panel = null
-
-    beforeEach ->
-      panel = page.overlaySelector()
-
-    it 'should display the overlay selection buttons', ->
-      expect(panel, 'The overlay selection buttons are missing').to.eventually.exist
+  # TODO - enable when overlays are added to the test fixture.
+  # describe 'Overlay Selection Buttons', ->
+  #   panel = null
+  #
+  #   beforeEach ->
+  #     panel = page.overlaySelector()
+  #
+  #   it 'should display the overlay selection buttons', ->
+  #     expect(panel, 'The overlay selection buttons are missing').to.eventually.exist

   describe 'Image Control Panel', ->
     panel = null


Remove brackets

Remove brackets

--- test/elvis-backbone-test.coffee
+++ test/elvis-backbone-test.coffee
@@ -18,7 +18,7 @@
   it 'can transform binding value', ->
     model = new Backbone.Model(foo: 'bar')
     reverseString = (input) -> input.split('').reverse().join('')
-    element = el('div', [ el.bind(model, 'foo', reverseString) ])
+    element = el('div', el.bind(model, 'foo', reverseString))
     expect(element.innerHTML).to.equal('rab')
     model.set(foo: 'quux')
     expect(element.innerHTML).to.equal('xuuq')


Activity: search route added

Activity: search route added

--- client/Social/Activity/routes.coffee
+++ client/Social/Activity/routes.coffee
@@ -24,6 +24,10 @@

     '/:name?/Activity/Public/Recent': ({ params: {name}}) ->
       activityPane (pane) -> pane.open 'Most Recent'
+
+    '/:name?/Activity/Public/Search': ({ params: {name}, query}) ->
+      activityPane (pane) -> pane.open 'Search', query.q
+

     # TODO ~ i tried to unify this route and following 4 routes, couldnt manage
     # to make it work, did not spend so much time on it and gave up, it would be


Open links from emails

Open links from emails

--- client/app/components/message.coffee
+++ client/app/components/message.coffee
@@ -132,8 +132,8 @@
                 image.removeAttribute 'src'
             images = doc.querySelectorAll 'IMG[src]'
             hideImage image for image in images
-            for link in doc.querySelectorAll 'a[href]'
-                link.target = '_blank'
+        for link in doc.querySelectorAll 'a[href]'
+            link.target = '_blank'
         if doc?
             @_htmlContent = doc.body.innerHTML
         else
@@ -181,7 +181,7 @@
                             iframe
                                 className: 'content',
                                 ref: 'content',
-                                sandbox: 'allow-same-origin',
+                                sandbox: 'allow-same-origin allow-popups',
                                 allowTransparency: true,
                                 frameBorder: 0,
                                 name: "message-" + message.get('id'), ''


Bugfix on confirm delete setting

Bugfix on confirm delete setting

--- client/app/components/message.coffee
+++ client/app/components/message.coffee
@@ -33,7 +33,6 @@
             headers: false
             messageDisplayHTML:   @props.settings.get 'messageDisplayHTML'
             messageDisplayImages: @props.settings.get 'messageDisplayImages'
-            messageConfirmDelete: @props.settings.get 'messageConfirmMelete'
         }

     propTypes:
@@ -96,7 +95,6 @@
             @_markRead @props.message
             state.messageDisplayHTML   = props.settings.get 'messageDisplayHTML'
             state.messageDisplayImages = props.settings.get 'messageDisplayImages'
-            state.messageConfirmDelete = props.settings.get 'messageConfirmDelete'
         @setState state

     _markRead: (message) ->
@@ -630,7 +628,7 @@
         if @props.nextID?
             next = @props.nextID
         else next = @props.prevID
-        if (not @state.messageConfirmDelete) or
+        if (not @props.settings.get('messageConfirmDelete')) or
         window.confirm(t 'mail confirm delete', {subject: message.get('subject')})
             MessageActionCreator.delete message, (error) =>
                 if error?


FSBrokenLink: extend from FSFile instead

--- client/finder/fs/fsbrokenlink.coffee
+++ client/finder/fs/fsbrokenlink.coffee
@@ -1 +1 @@
-class FSBrokenLink extends FSItem+class FSBrokenLink extends FSFile


Use promise for notify on completion

Use promise for notify on completion

--- client/lanes/views/SaveNotify.coffee
+++ client/lanes/views/SaveNotify.coffee
@@ -1,26 +1,27 @@
-
 class ModelSaver

     constructor: ( @element, @options )->
         @mask = new Lanes.Views.TimedMask( @element, @options.message )
         @mask.prefixActions( "Save" )
         _.bindAll(this,'_onError','_onSuccess')
+        this.notification = new _.DeferredPromise

     save: ->
         @options.model.save({
             success: this._onSuccess, error: this._onError
         })

-    _onSuccess: (rec,resp,options)->
+    _onSuccess: (data,success,resp)->
         @mask.displaySuccess()
-        this._callback(true,resp)
+        this._callback(true,data,resp)

-    _onError: (rec,resp,options)->
-        @mask.displayFailure(rec.lastServerMessage)
-        this._callback(false,resp)
+    _onError: (data,success,resp)->
+        @mask.displayFailure(@options.model.lastServerMessage)
+        this._callback(false,data,resp)

-    _callback: (success,resp)->
+    _callback: (success,data,resp)->
         @options.callback(success,resp,@options.model) if @options.callback
+        this.notification?.resolve(data: data.data, success: success, response: resp)

 Lanes.Views.SaveNotify = ( view, options={} )->
@@ -28,3 +29,4 @@
     _.defaults( options, { model: view.model, message: "Saving, Please Wait…"} )
     ms = new ModelSaver(el, options)
     ms.save()
+    return ms.notification.promise


Stack-Editor: openStackWizard provide secondary overlay and route handling is optional

--- client/stack-editor/lib/index.coffee
+++ client/stack-editor/lib/index.coffee
@@ -67,12 +67,15 @@
       @showView()

-  openStackWizard: ->
+  openStackWizard: (handleRoute = yes) ->

     modal = new kd.ModalView
-      cssClass : 'StackEditor-OnboardingModal'
-      width : 820
-      overlay : yes
+      cssClass       : 'StackEditor-OnboardingModal'
+      width          : 820
+      overlay        : yes
+      overlayOptions :
+        cssClass     : 'second-overlay'
+

     view = new OnboardingView

@@ -94,8 +97,9 @@

     modal.addSubView view

-    modal.on 'KDObjectWillBeDestroyed', ->
-      kd.singletons.router.back()
+    if handleRoute
+      modal.on 'KDObjectWillBeDestroyed', ->
+        kd.singletons.router.back()

   showView: (stackTemplate) ->


Fix: ローカルリポジトリのフォルダからの適応ができていなかったのを修正

Fix: ローカルリポジトリのフォルダからの適応ができていなかったのを修正


Destroy invalidated fold markers

Destroy invalidated fold markers


Use correct param

Use correct param

--- components/ExchangeGrantToken.coffee
+++ components/ExchangeGrantToken.coffee
@@ -30,6 +30,7 @@
       redirect_uri: params.redirect or window.location.href
     req.open 'POST', "#{params.token_server}#{params.token_endpoint}", true
     req.setRequestHeader "Content-Type", "application/x-www-form-urlencoded"
+    req.setRequestHeader "Accept", "application/json"
     req.send qs.stringify payload
     return
   # Normal scenario: exchange token via Gatekeeper
@@ -55,7 +56,7 @@
       redirect: '$NOFLO_OAUTH_CLIENT_REDIRECT'
       clientid: '$NOFLO_OAUTH_CLIENT_ID'
       clientsecret: '$NOFLO_OAUTH_CLIENT_SECRET'
-      token_server: '$NOFLO_OAUTH_SERVICE_USER'
+      token_server: '$NOFLO_OAUTH_PROVIDER'
       token_endpoint: '$NOFLO_OAUTH_ENDPOINT_TOKEN'
       gatekeeper_server: '$NOFLO_OAUTH_GATE'
       gatekeeper_endpoint: '$NOFLO_OAUTH_ENDPOINT_AUTHENTICATE'


Support destroy all buttons before calling setButtons if requested

--- components/modals/KDModalView.coffee
+++ components/modals/KDModalView.coffee
@@ -83,10 +83,11 @@
     selector = null if @$(selector).length is 0
     super view, selector

-  setButtons:(buttonDataSet)->
+  setButtons:(buttonDataSet, destroyExists = no)->
     @buttons or= {}
     @setClass "with-buttons"
     defaultFocusTitle = null
+    @destroyButtons()  if destroyExists
     for own buttonTitle, buttonOptions of buttonDataSet
       defaultFocusTitle ?= buttonTitle
       button = @createButton buttonOptions.title or buttonTitle, buttonOptions
@@ -94,6 +95,9 @@
       focused = yes  if buttonOptions.focus

     @buttons[defaultFocusTitle].setFocus()  unless focused
+
+  destroyButtons:->
+    button.destroy()  for own _, button of @buttons

   click:(e)->
     @destroy() if $(e.target).is(".closeModal")


Disconsider paid value on gift card unless it has an id

--- src/script/payment-group/gift-card.coffee
+++ src/script/payment-group/gift-card.coffee
@@ -66,7 +66,10 @@
         forceProviders: _.map data, (p) -> p.oauth

   updatePayment: (giftPayment) =>
-    @paidValue giftPayment.referenceValue
+    if giftPayment.id
+      @paidValue giftPayment.referenceValue
+    else
+      @paidValue 0
     @loadingGiftCard false
     cardVM = _.find(@giftCards(), (gc) -> gc.id is giftPayment.id)
     if cardVM


js state machine

js state machine

--- src/State.coffee
+++ src/State.coffee
@@ -30,8 +30,7 @@

     all: -> callbacks

-    before: ->
-      callbacks.before
+    before: -> callbacks.before

     after:
       callbacks.after


Added require snippets

Added require snippets

--- snippets/template.cson
+++ snippets/template.cson
@@ -50,9 +50,18 @@
     'prefix': 'else '
     'body': '<% else %>\n\t${0}'
   # Includes
-  'include':
+  'Include':
     'prefix': 'include '
     'body': '<% include ${1} %>${0}'
+  'Require themedCSS':
+    'prefix': 'requirethemedcss'
+    'body': '<% require themedCSS(\'${1}\') %>${0}'
+  'Require Javascript':
+    'prefix': 'requirejavascript'
+    'body': '<% require javascript(\'${1}\') %>${0}'
+  'Require CSS':
+    'prefix': 'requirecss'
+    'body': '<% require css(\'${1}\') %>${0}'
   'Base Tag':
     'prefix': 'basetag'
     'body': '<% base_tag %>${0}'


cache template

cache template

--- src/opensong.coffee
+++ src/opensong.coffee
@@ -18,11 +18,10 @@
     @el = getDomElem element
     @model = parseLyrics lyrics

-    #render initially
+    this._cacheTemplate()
     this.renderLyrics()

-
-  renderLyrics: ->
+  _cacheTemplate: ->
     # TODO: support comments
     templateSrc = """
       {{#this}}
@@ -45,10 +44,11 @@
         {{/lines}}
       {{/this}}
     """
-    template = Handlebars.compile templateSrc
-
+    @template = Handlebars.compile templateSrc
+
+  renderLyrics: ->
     # clear Html Element and add opensong class
-    @el.innerHTML = template @model
+    @el.innerHTML = @template @model
     @el.className += " opensong" unless /opensong/.test @el.className


not needed

not needed

--- resources/assets/lib/import-shims.coffee
+++ resources/assets/lib/import-shims.coffee
@@ -56,4 +56,3 @@
 window.osu.jsonClone = jsonClone
 window.osu.parseJson = parseJson
 window.osu.parseJsonNullable = parseJsonNullable
-window.osu.storeJson = storeJson


New devserve Grunt task

New devserve Grunt task

In case server gets interrupted during `grunt dev`, this will just restarts the server/watch, without recompiling from scratch.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -181,4 +181,5 @@
   grunt.registerTask 'minify', ['build', 'uglify:app', 'cssmin']

   grunt.registerTask 'dev', ['checkDatabase', 'build', 'express:dev', 'watch']
+  grunt.registerTask 'devserve', ['checkDatabase', 'express:dev', 'watch']
   grunt.registerTask 'serve', ['checkDatabase', 'minify', 'express:server']


Fix broken merge

Fix broken merge

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -135,7 +135,6 @@
       }
     ]
     targets = [
-      'build/factlink.js'
       'build/factlink_loader_basic.js'
       'build/factlink_loader_bookmarklet.js'
       'build/factlink_loader_publishers.js'


FIX: Ensure student filter works on portfolio page

--- src/app/common/filters/filters.coffee
+++ src/app/common/filters/filters.coffee
@@ -11,7 +11,7 @@
 .filter('showStudents', ->
   (input, kind, tutorName) ->
     if input
-      if kind == "mine"
+      if kind == "mine" || kind == "myStudents"
         _.filter  input, { tutorial: {tutor_name: tutorName} }
       else
         input


Add path into launching options object

Add path into launching options object

--- src/application/application.js.coffee
+++ src/application/application.js.coffee
@@ -55,10 +55,12 @@

 # CB.Run(application: object, delegate: object, arguments: [])
 CB.Run = (options) ->
+  options_argument =
+    path: CB.Pather.sharedPather().currentPath()
   if typeof options == 'function'
     $(document).ready ->
       CB.Application.sharedApplication.delegate = new CB.ApplicationDelegate
-      options()
+      options(options_argument)
       CB.DebugLog("Finished launching from an function!")
   else if typeof options == 'object'
     options.application ||= new CB.Application()
@@ -66,7 +68,7 @@
     app = CB.Application.sharedApplication()
     app._delegate = options.delegate
     $(document).ready ->
-      app.delegate.applicationWillFinishLaunchingWithOptions()
+      app.delegate.applicationWillFinishLaunchingWithOptions(options_argument)
       app.finishLaunchingWithInfo()
-      app.delegate.applicationDidFinishLaunchingWithOptions()
+      app.delegate.applicationDidFinishLaunchingWithOptions(options_argument)
       CB.DebugLog("Finished launching from an object!")


use a command wrapper for synctex

use a command wrapper for synctex

instead of an alternative child_process object

--- app/coffee/CompileManager.coffee
+++ app/coffee/CompileManager.coffee
@@ -188,8 +188,9 @@
 	_runSynctex: (args, callback = (error, stdout) ->) ->
 		bin_path = Path.resolve(__dirname + "/../../bin/synctex")
 		seconds = 1000
-		synctex = Settings.clsi?.synctex?.command?(__dirname, child_process) || child_process
-		synctex.execFile bin_path, args, timeout: 10 * seconds, (error, stdout, stderr) ->
+		if Settings.clsi?.synctexCommandWrapper?
+			[bin_path, args] = Settings.clsi?.synctexCommandWrapper bin_path, args
+		child_process.execFile bin_path, args, timeout: 10 * seconds, (error, stdout, stderr) ->
 			if error?
 				logger.err err:error, args:args, "error running synctex"
 				return callback(error)


Small test fixes

Small test fixes

--- spec/Cloud.spec.coffee
+++ spec/Cloud.spec.coffee
@@ -113,7 +113,6 @@
         NgParseCloud
             .run 'save', newObj, yes
             .then (result) =>
-                console.log result
                 resObj = result
         $httpBackend.flush()


tests

tests

--- tests/test_reversible_ca.coffee
+++ tests/test_reversible_ca.coffee
@@ -283,3 +283,54 @@
     assert.deepEqual field.field, makeArray2d(6,6,expectedPattern)

+describe "MargolusNeighborehoodField::snap_below", ->
+  it "must return even number <= x, if field phase is 0", ->
+    fld = new MargolusNeighborehoodField new Array2d 4, 4
+    assert.equal fld.phase, 0
+    assert.equal fld.snap_below(0), 0
+    assert.equal fld.snap_below(1), 0
+    assert.equal fld.snap_below(2), 2
+    assert.equal fld.snap_below(3), 2
+    assert.equal fld.snap_below(-1), -2
+    assert.equal fld.snap_below(-2), -2
+    assert.equal fld.snap_below(-1000), -1000
+
+  it "must return odd number <= x, if field phase is 1", ->
+    fld = new MargolusNeighborehoodField new Array2d 4, 4
+    fld.phase = 1
+    assert.equal fld.snap_below(0), -1
+    assert.equal fld.snap_below(1), 1
+    assert.equal fld.snap_below(2), 1
+    assert.equal fld.snap_below(3), 3
+    assert.equal fld.snap_below(-1), -1
+    assert.equal fld.snap_below(-2), -3
+    assert.equal fld.snap_below(-1000), -1001
+    assert.equal fld.snap_below(1000), 999
+
+
+describe "MargolusNeighborehoodField::snap_upper", ->
+  it "must return even number >= x, if field phase is 0", ->
+    fld = new MargolusNeighborehoodField new Array2d 4, 4
+    assert.equal fld.phase, 0
+    assert.equal fld.snap_upper(0), 0
+    assert.equal fld.snap_upper(1), 2
+    assert.equal fld.snap_upper(2), 2
+    assert.equal fld.snap_upper(3), 4
+    assert.equal fld.snap_upper(-1), 0
+    assert.equal fld.snap_upper(-2), -2
+    assert.equal fld.snap_upper(-1000), -1000
+
+  it "must return odd number <= x, if field phase is 1", ->
+    fld = new MargolusNeighborehoodField new Array2d 4, 4
+    fld.phase = 1
+    assert.equal fld.snap_upper(0), 1
+    assert.equal fld.snap_upper(1), 1
+    assert.equal fld.snap_upper(2), 3
+    assert.equal fld.snap_upper(3), 3
+    assert.equal fld.snap_upper(-1), -1
+    assert.equal fld.snap_upper(-2), -1
+    assert.equal fld.snap_upper(-3), -3
+    assert.equal fld.snap_upper(-1000), -999
+    assert.equal fld.snap_upper(1000), 1001
+
+


update

update


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -15,11 +15,11 @@
   parseHex = (hexString) ->
     hexString = hexString.replace(/#/, '')

-    if hexString[0] = '#'
-      case hexString.length
-        when 3 then #something
-        when 4 then #something
-        when 6 then #something
+    case hexString.length
+      when 3 then #something
+      when 4 then #something
+      when 6 then #something
+      when 8 then #something

   parseRGB = (colorString) ->
     bits = rgbParser.exec(colorString)


:lipstick:

:lipstick:

--- src/color.coffee
+++ src/color.coffee
@@ -31,22 +31,22 @@
     alpha = parseColor(alpha)

     Object.defineProperties this,
-      'red':
+      red:
         set: (newRed) -> red = parseColor(newRed)
         get: -> red
         enumerable: true
         configurable: false
-      'green':
+      green:
         set: (newGreen) -> green = parseColor(newGreen)
         get: -> green
         enumerable: true
         configurable: false
-      'blue':
+      blue:
         set: (newBlue) -> blue = parseColor(newBlue)
         get: -> blue
         enumerable: true
         configurable: false
-      'alpha':
+      alpha:
         set: (newAlpha) -> alpha = parseAlpha(newAlpha)
         get: -> alpha
         enumerable: true


spec for url without extension

spec for url without extension

fixed with
https://github.com/the-grid/imgflo-url/commit/02c1efd53c4e92ef112a955ffa
ce2c660eb669d1 , spec from
https://github.com/the-grid/grid-chrome/commit/41f543c8d5810f02aef984951
3e5e3e05a28132d

--- spec/imgflo-url.coffee
+++ spec/imgflo-url.coffee
@@ -169,6 +169,20 @@
           expect(url).to.contain 'passthrough.jpg'
           expect(url).to.equal 'https://imgflo.herokuapp.com/graph/key/e48c2b3866af0e91fb4b1ef4d5399b7d/passthrough.jpg?input=http%3A%2F%2F1.bp.blogspot.com%2F-1h8jX1lfGJc%2FUnlTZ3Fsq6I%2FAAAAAAAAAOo%2Fdw7IXnJBO5A%2Fs1600%2FIMG_1722.JPG'

+      context 'with a url without an extension', ->
+
+        it 'should produce the correct URL', ->
+          config = getConfig()
+
+          params =
+            input: 'https://lh6.ggpht.com/qhLc1KUYP3YpNUtf9MujZVld1ctgsU0_oEEqp6Jkte8hW1UNJqKSm9-ExP-uzyL3r2c=h556'
+            color1: '#0A2A2F'
+            color2: '#FDE7A0'
+            srgb: true
+
+          url = imgflo config, 'gradientmap', params
+
+          expect(url).to.equal 'https://imgflo.herokuapp.com/graph/key/79e7c60252e6d0b804c555b818b8b50f/gradientmap?input=https%3A%2F%2Flh6.ggpht.com%2FqhLc1KUYP3YpNUtf9MujZVld1ctgsU0_oEEqp6Jkte8hW1UNJqKSm9-ExP-uzyL3r2c%3Dh556&color1=%230A2A2F&color2=%23FDE7A0&srgb=true'

     context 'specifying an image format', ->


added a failing test

added a failing test

--- spec/parse-spec.coffee
+++ spec/parse-spec.coffee
@@ -78,6 +78,17 @@
     5
   ]

+test6 = """(:return (:ok "\\\\__pi_arg => \\\\__pi_arg1 => (__pi_arg1)") 6)"""
+list6 =
+  [
+    ":return"
+    [
+      ":ok"
+      "\\__pi_arg => \\__pi_arg1 => (__pi_arg1)"
+    ]
+    6
+  ]
+
 describe "The sub-parser(s)", ->
   it "for :True and :False should work.", ->
     expect(runP(parse.trueP, ':True')).toEqual(true)
@@ -106,6 +117,7 @@
     expect(parse.parse(test3)).toEqual(list3)
     expect(parse.parse(test4)).toEqual(list4)
     expect(parse.parse(test5)).toEqual(list5)
+    expect(parse.parse(test6)).toEqual(list6)

   it "should serialize back again.", ->
     expect(sexpFormatter.formatSexp(list1)).toEqual(test1)


Fix invalid reference

Fix invalid reference


Syntax tweak

Syntax tweak

--- src/MediaSwitch.coffee
+++ src/MediaSwitch.coffee
@@ -41,12 +41,10 @@
     if prevProps.children isnt @props.children
       @setState mediaQueryLists: @getMqls()
   removeMqlListeners: (mqls) ->
-    for own media, mql of mqls
-      mql.removeListener @handleMqlChange if mql
+    mql.removeListener @handleMqlChange for own media, mql of mqls when mql
     return
   addMqlListeners: (mqls) ->
-    for own media, mql of mqls
-      mql.addListener @handleMqlChange if mql
+    mql.addListener @handleMqlChange for own media, mql of mqls when mql
     return
   getMatchingCase: ->
     defaultCase = null


Implemented naive recipe Post handler

Implemented naive recipe Post handler

--- src/handlers/beers/PostHandler.coffee
+++ src/handlers/beers/PostHandler.coffee
@@ -10,17 +10,51 @@
 class SingleHandler extends RouteHandler

 	getRoute: () ->
-		# return [
-		# 	{
+		super [
+			{
+				path: '/api/beers'
+				method: 'POST'
+			}
+			{
+				path: '/api/beers/{id}'
+				method: 'PUT'
+			}
+		]

-		# 	}
-		# ]
-		super { path: '/beers', method: 'POST' }
+	updateBeer: (req, reply) =>
+		@logger.log 'wat'
+		reply 'wat?'
+
+	addBeer: (req, reply) =>
+		beer = req.raw.payload
+
+		beer.recipe.brewers = []
+		beer.recipe.brewers.push
+			username: req.user.username
+
+		beer.brewers = []
+		beer.brewers.push
+			username: req.user.username
+
+		if @verifyRecipe(beer)
+			console.log 'TRY POSTING IT'
+			@api.beers.post beer, (err,res,body) ->
+				console.log err
+				console.log body
+				reply body
+			, req.token
+		else
+			reply 'lol'

 	# Check that recipe contents are present
-	verifyRecipe: () ->
+	verifyRecipe: (recipe) ->
+		# @logger.log JSON.stringify recipe, false, '\t'
+		return true

-	onRequest: (request, reply) ->
+	onPost: (req, reply) ->
+		@logger.log 'onPost'
+		@addBeer req, reply
+


Add message to communicate project owner

--- app/pages/lab/collaborators.cjsx
+++ app/pages/lab/collaborators.cjsx
@@ -1,6 +1,7 @@
 React = require 'react'
 PromiseRenderer = require '../../components/promise-renderer'
 UserSearch = require '../../components/user-search'
+authClient = require '../../api/auth'
 apiClient = require '../../api/client'
 talkClient = require '../../api/talk'
 projectSection = require '../../talk/lib/project-section'
@@ -141,8 +142,28 @@
           roleSet['talk_roles'] = talkRoles.filter((role) -> role.links.user == roleSet.links.owner.id)
           roleSet

+  fetchUserAndProjectOwner: ->
+    Promise.all [
+      authClient.checkCurrent(),
+      @props.project.get('owner')
+    ]
+
   render: ->

+
Project Owner

+      <PromiseRenderer promise={@fetchUserAndProjectOwner()} then={([user, projectOwner] = []) =>
+        projectOwnerMessage = if user.id is projectOwner.id
+          {'You are the project owner.'}
+        else
+          projectOwner.display_name + ' is the project owner.'
+
+

+          {projectOwnerMessage}
+

+      } />
+
+

+

Collaborators


adding arg support to ls

adding arg support to ls

--- browser/frontend.coffee
+++ browser/frontend.coffee
@@ -232,7 +232,13 @@
     class_cache = {}
     "Cache cleared."
   ls: (args) ->
-    node.fs.readdirSync('.').sort().join '\n'
+    read_dir = (dir) -> node.fs.readdirSync(dir).sort().join '\n'
+    if args.length == 0
+      read_dir '.'
+    else if args.length == 1
+      read_dir args[0]
+    else
+      ("#{d}:\n#{read_dir d}\n" for d in args).join '\n'
   edit: (args) ->
     try
       data = if args[0]? then node.fs.readFileSync(args[0]) else defaultFile
@@ -329,7 +335,7 @@
     File management:
       cat <file>             -- Display a file in the console.
       edit <file>            -- Edit a file.
-      ls                     -- List all files.
+      ls <dir>               -- List files.
       mv <src> <dst>         -- Move / rename a file.
       rm <file>              -- Delete a file.
       cd <dir>               -- Change current directory.


modified

modified

--- src/sortsina.coffee
+++ src/sortsina.coffee
@@ -62,7 +62,7 @@

 module.exports = qsort

-### 在這行前面加或去一個#就可以測試,測試完記得加回去###
+### 在這行前面加或去一個#就可以測試,測試完記得加回去#
 qsort {category:'etf_hq_fund'},(err, arr)->
   console.log arr
 ###


adding HMAC as BlockCipherText tag for all AES-encrypted objects

--- js/src/crypto/AesCryptoService.coffee
+++ js/src/crypto/AesCryptoService.coffee
@@ -8,7 +8,7 @@
   'use strict'

   # libraries
-  Forge = require 'forge'
+  forge = require 'forge'

   # kryptnostic
   AbstractCryptoService = require 'kryptnostic.abstract-crypto-service'
@@ -23,8 +23,26 @@

   # constants
   BITS_PER_BYTE = 8
+  HMAC_HASH_FUNCTION = 'sha256'

   logger = Logger.get('AesCryptoService')
+
+  computeHMAC = (key, iv, salt, ciphertext) ->
+    try
+      hmac = forge.hmac.create()
+      hmac.start(HMAC_HASH_FUNCTION, key)
+      hmac.update(iv)
+      hmac.update(salt)
+      hmac.update(ciphertext)
+      hmacHash = hmac.digest().getBytes()
+      return hmacHash
+    catch e
+      console.error('caught exception while computing HMAC')
+      return null
+
+  checkDataIntegrity = (key, iv, salt, ciphertext, tag) ->
+    hmacHash = computeHMAC(key, iv, salt, ciphertext)
+    return tag == hmacHash

   class AesCryptoService

@@ -40,39 +58,61 @@

       if not @key
         logger.info('no key passed! generating a key.')
-        @key = Forge.random.getBytesSync(@cypher.keySize / BITS_PER_BYTE)
+        @key = forge.random.getBytesSync(@cypher.keySize / BITS_PER_BYTE)
       @abstractCryptoService = new AbstractCryptoService(@cypher)

     encrypt: (plaintext) ->

-      iv         = Forge.random.getBytesSync(AesCryptoService.BLOCK_CIPHER_KEY_SIZE)
+      iv         = forge.random.getBytesSync(AesCryptoService.BLOCK_CIPHER_KEY_SIZE)
+      salt       = forge.random.getBytesSync(0)
       ciphertext = @abstractCryptoService.encrypt(@key, iv, plaintext)
-
-      return new BlockCiphertext {
+      props = {
         iv       : btoa(iv)
-        salt     : btoa(Forge.random.getBytesSync(0))
+        salt     : btoa(salt)
         contents : btoa(ciphertext)
       }

+      hmacHash = computeHMAC(@key, iv, salt, ciphertext)
+      if hmacHash?
+        props.tag = btoa(hmacHash)
+
+      return new BlockCiphertext(props)
+
     encryptUint8Array: (uint8) ->

-      iv         = Forge.random.getBytesSync(AesCryptoService.BLOCK_CIPHER_KEY_SIZE)
-      buffer     = Forge.util.createBuffer(uint8)
+      iv         = forge.random.getBytesSync(AesCryptoService.BLOCK_CIPHER_KEY_SIZE)
+      salt       = forge.random.getBytesSync(0)
+      buffer     = forge.util.createBuffer(uint8)
       ciphertext = @abstractCryptoService.encryptBuffer(@key, iv, buffer)
-
-      return new BlockCiphertext {
+      props = {
         iv       : btoa(iv)
-        salt     : btoa(Forge.random.getBytesSync(0))
+        salt     : btoa(salt)
         contents : btoa(ciphertext)
       }
+
+      hmacHash = computeHMAC(@key, iv, salt, ciphertext)
+      if hmacHash?
+        props.tag = btoa(hmacHash)
+
+      return new BlockCiphertext(props)

     decrypt: (blockCipherText) ->

       Validator.validate(blockCipherText, BlockCiphertext, BLOCK_CIPHERTEXT_SCHEMA)

-      iv       = atob(blockCipherText.iv)
-      contents = atob(blockCipherText.contents)
-      return @abstractCryptoService.decrypt(@key, iv, contents)
+      iv         = atob(blockCipherText.iv)
+      salt       = atob(blockCipherText.salt)
+      ciphertext = atob(blockCipherText.contents)
+
+      if _.isEmpty(blockCipherText.tag)
+        logger.warn('BlockCipherText tag missing')
+      else
+        tag = atob(blockCipherText.tag)
+        isValid = checkDataIntegrity(@key, iv, salt, ciphertext, tag)
+        if not isValid
+          throw new Error('BlockCipherText data integrity check failed')
+
+      return @abstractCryptoService.decrypt(@key, iv, ciphertext)

     decryptToUint8Array: (blockCipherText) ->


Bulk uploader fixed.

Bulk uploader fixed.

--- app/assets/javascripts/bulkupload.js.coffee
+++ app/assets/javascripts/bulkupload.js.coffee
@@ -108,7 +108,7 @@
           type: "POST"
           url: self.uri
           data:
-            "ontology[uri]": uri
+            "ontology[iri]": uri
             "ontology[versions_attributes][0][source_url]": uri
           format: "json"
         ).success(->


Useless return

Useless return

--- app/assets/javascripts/download-menu.coffee
+++ app/assets/javascripts/download-menu.coffee
@@ -17,4 +17,3 @@
     hide_download_menu()
   else
     show_download_menu()
-  return


Increase layer rendering

Increase layer rendering

--- app/assets/javascripts/map_editor.js.coffee
+++ app/assets/javascripts/map_editor.js.coffee
@@ -26,9 +26,10 @@
       view: 'auto'
       useFeatures: false
       showStyle:
-        weight: 1
         color: "#333"
-        fillOpacity: 0.2
+        fillOpacity: 0.4
+        weight: 2
+        opacity: 0.8
       ghostStyle:
         weight: 4
         color: "#FFF"


enable save button only if chckbox value changed

--- app/assets/javascripts/pages/members.coffee
+++ app/assets/javascripts/pages/members.coffee
@@ -53,6 +53,10 @@
     $('input[name=realtime_error]').attr('checked', true) if data.realtime_error
     $('input[name=when_event]').attr('checked', true) if data.when_event
     $('input[name=more_than_10]').attr('checked',true) if data.more_than_10
+    $('#save').prop('disabled', true)
+
+    $('input').change ->
+      $('#save').prop('disabled', false)

     $('#save').on 'click', (e) ->
       e.preventDefault()
@@ -69,6 +73,7 @@
           }
         }
         success: (data) ->
+          $('#save').prop('disabled', true)
           swal("Success!", "You will recieve notifications soon.", "success")
 )


Start to add code to generate pagination HTML

Not only the functionality should be added on initialization, but also
the HTML elements themselves.

Signed-off-by: Konrad Reiche <4ff0213f56e2082fcd8c882d04c546881c579ce4@gmail.com>

--- app/assets/javascripts/pagination.js.coffee
+++ app/assets/javascripts/pagination.js.coffee
@@ -12,6 +12,22 @@

     @currentPageAnchor = @pageAnchors[0]
     @currentPage = $(@currentPageAnchor).data("target")
+
+  addElements: (selector, numPages) ->
+    html = """
+

+
«

+
»

+
1

+    """
+
+    for i in [2..numPages]
+      html += """
#{i}
"""
+
+    html += """
+

+    """
+    pagination = $(selector).append(html)

   pageAnchorClick: (view, pageAnchor) ->
     (event) => @setPage(view, pageAnchor)


change rc login url

change rc login url

--- app/assets/javascripts/rocketchat.js.coffee
+++ app/assets/javascripts/rocketchat.js.coffee
@@ -130,7 +130,7 @@
     $("#js-rocketchat-iframe").remove()
   window.iframeEl = document.createElement('iframe')
   window.iframeEl.id = 'js-rocketchat-iframe'
-  window.iframeEl.src = url+"/extras-ji/"+'?uid='+user_id+'&token='+token;
+  window.iframeEl.src = url+"/home"+'?uid='+user_id+'&token='+token;
   window.iframeEl.style = "width: 100%; height: 500px;"
   window.iframeEl.frameBorder = "0"
   $('.js-rocketchat-iframe-container').append(iframeEl);


focus first input after setContents

focus first input after setContents

--- app/assets/javascripts/hat-trick.js.coffee
+++ app/assets/javascripts/hat-trick.js.coffee
@@ -335,6 +335,7 @@
           $partial.find('fieldset').html()
         $step = $("fieldset##{stepId}")
         $step.html fieldsetContents
+        $step.find(":input:first").focus()
         $step.data("contents", "loaded")
         @stepsNeedUpdate = true


changed twitter input

changed twitter input

--- app/assets/javascripts/questions.js.coffee
+++ app/assets/javascripts/questions.js.coffee
@@ -37,7 +37,7 @@
         $('#question_person_id').val ''

-  $("#twitter").blur(->
+  $("#twitter").keyup(->
     if $(this).val()[0] is "@"
       getTwitter($(this))
     else


aws s3 maps

aws s3 maps

--- app/assets/javascripts/tag_building.coffee
+++ app/assets/javascripts/tag_building.coffee
@@ -12,13 +12,13 @@
 	constructor: () ->
 		$("#map-tutorial").hide()
 		$("#buttons").hide()
-		@map = L.mapbox.map('map', 'nypllabs.polygonfixer',
+		@map = L.mapbox.map('map', 'https://s3.amazonaws.com/maptiles.nypl.org/859/859spec.json',
 			zoomControl: false
 			animate: true
 			attributionControl: false
 			minZoom: 19
 			maxZoom: 19
-			# dragging: false
+			dragging: false
 		)

 		@map.on('load', @getPolygons)


remove console log calls

remove console log calls

--- app/assets/javascripts/zvg/verbatim.coffee
+++ app/assets/javascripts/zvg/verbatim.coffee
@@ -102,7 +102,6 @@
     s.exit().remove()

     selector.on('change', =>
-      console.log('change event')
       @_page = 1
       @render(@currentSelectedOptions())
     )
@@ -141,7 +140,6 @@
     pagination.push({ text: '...', page: null }) if includeEndEllipsis
     pagination.push({ text: @numberOfPages, page: @numberOfPages }) if includeEndEllipsis

-    console.log(pagination)
     links = @pagination.selectAll('a.zvg_page').data(pagination)
     pageAction = (d) =>
       @page(d.page) if d.page


remove unnecessary debounce

remove unnecessary debounce

--- app/coffee/modules/common/lightboxes.coffee
+++ app/coffee/modules/common/lightboxes.coffee
@@ -505,7 +505,7 @@
         $scope.$watch "usersSearch", (searchingText) ->
             render(selectedUser, searchingText) if searchingText?

-        $el.on "click", ".watcher-single", debounce 2000, (event) ->
+        $el.on "click", ".watcher-single", (event) ->
             event.preventDefault()
             target = angular.element(event.currentTarget)

@@ -515,7 +515,7 @@
                 $scope.$broadcast("assigned-to:added", target.data("user-id"), selectedItem)
                 $scope.usersSearch = null

-        $el.on "click", ".remove-assigned-to", debounce 2000, (event) ->
+        $el.on "click", ".remove-assigned-to", (event) ->
             event.preventDefault()
             event.stopPropagation()


preserve email param on login page enter

--- app/components/login-page/login-page.coffee
+++ app/components/login-page/login-page.coffee
@@ -8,7 +8,7 @@
     $scope.formData = {}
     email = $location.search().email
     if email
-      $location.url($location.path())
+      $location.search('email', null)
       $scope.formData.email = email

     $scope.login = (formData) ->


-z on cli tester to avoid spam for now

-z on cli tester to avoid spam for now

--- test/cli.coffee
+++ test/cli.coffee
@@ -19,6 +19,7 @@
   'QQ',
   '-d',
   'test=./examples/advanced/data.json'
+  '-z'
 ]

 exports["test CLI"] = ->


fix lead_form js callbacks

fix lead_form js callbacks

--- app/cells/folio/lead_form/lead_form.coffee
+++ app/cells/folio/lead_form/lead_form.coffee
@@ -9,4 +9,8 @@
       .always (response) ->
         $response = $(response)
         $wrap.replaceWith($response)
-        $response.trigger('submitted.folio')
+        $response.trigger('folio:submitted')
+        if $response.find('.folio-lead-form-message').length
+          $response.trigger('folio:success')
+        else
+          $response.trigger('folio:failure')


upd: test

upd: test

--- test/lab.coffee
+++ test/lab.coffee
@@ -5,13 +5,11 @@
 Promise_Bird.longStackTraces()

 test = (Promise) ->
-    promise = new Promise (r) ->
-        r {
-            then: (ful) ->
-                ful 'done'
-        }
+    p = new Promise (r) ->
+        r new Promise (r) ->
+            r 'done'

-    promise.then((value) ->
+    p.then((value) ->
         console.log(value)
     );


Updated text

Updated text

--- server/materials/data/level_about.coffee
+++ server/materials/data/level_about.coffee
@@ -1108,7 +1108,8 @@

Часть информации касается только Нижегородской области, часть относится ко всем регионам.

-
Областная олимпиада по информатике (формально — региональный этап Всероссийской олимпиады) пройдет в два тура 16 и 18 января в ННГУ им. Лобачевского.

+
Областная олимпиада по информатике (формально — региональный этап Всероссийской олимпиады) пройдет в два тура 16 и 18 января. Олимпиада пройдет в ННГУ им. Лобачевского или дистанционно,
+        я пока точно не знаю.

Отбор на область

@@ -1129,7 +1130,7 @@

(Вообще, по поводу общения с жюри в целом — я, конечно, могу что-то написать в жюри, и жюри зачастую прислушивается ко мне, но надо понимать, что я не являюсь официальным представителем вас, вашей школы и т.д., поэтому жюри может и полностью проигнорировать мой вопрос. Поэтому помимо того, что я могу общаться с жюри неофициально, вы лично, а также ваши родители и ваша школа могут общаться с жюри официально, и в случае каких-то серьезных проблем это может быть полезно.)

Система оценивания

-
До 20141 года включительно областная олимпиада оценивалась очень похоже на  школьную: вы пять часов писали задачи, после чего жюри брало ваши решения и проверяло на заранее подготовленных тестах; при этом каждый тест оценивался отдельно. В процессе этой проверки вы уже ничего не могли исправить в своих решениях.

+
До 20141 года включительно областная олимпиада оценивалась по «классической» системе: вы пять часов писали задачи, после чего жюри брало ваши решения и проверяло на заранее подготовленных тестах; при этом каждый тест оценивался отдельно. В процессе этой проверки вы уже ничего не могли исправить в своих решениях.

1 Областная олимпиада всегда проводится после Нового года, поэтому здесь и ниже, говоря «олимпиада 2014 года», я имею в виду олимпиаду 2013-14 уч. года, и аналогично про другие годы.

@@ -1157,15 +1158,19 @@

Фидбек

-
Но, чтобы компенсировать увеличение сложности от введения подзадач, также вводится так называемый фидбек. А именно, в течение тура вы можете отправлять ваши решения на проверку. Решения будут проверяться на всех финальных тестах, и после проверки вы можете попросить, чтобы вам сообщили результат этой проверки. Скорее всего, у вас на компьютере будет стоять клиент тестирующей системы нашего жюри, вы через этот клиент будете отправлять решения, и в нем же будет какая-нибудь кнопочка типа «узнать результат этого решения». Соответственно, узнав результат, вы можете дальше планировать свою работу.

-
-
Бывают три варианта того, что вам сообщают в качестве результата, и это зависит не только от задачи, но и от подзадачи. В каких-то подзадачах вам могут сообщать информацию по каждому тесту, прошел он или нет (или даже если не прошел, то почему — неправильный ответ, превышение предела времени и т.п. — всё очень похоже на то, что вы видите на этом сайте на закладке «результаты»). В других подзадачах вам могут только сообщать полный балл за эту подзадачу. В третьих подзадачах — первый непройденный тест и вердикт на нем (как на командных олимпиадах). Что именно вам сообщают, обычно указывается в условии.

+
Но, чтобы компенсировать увеличение сложности от введения подзадач, также был введен так называемый фидбек. А именно, в течение тура вы можете отправлять ваши решения на проверку. Решения будут проверяться на всех финальных тестах, и после проверки вы можете попросить, чтобы вам сообщили результат этой проверки. Скорее всего, будет использоваться уже знакомый вам Яндекс.Контест, в котором вам сразу будут сообщать результаты ваших посылок и, узнав результат, вы можете дальше планировать свою работу.

+
+
Бывают три варианта того, что вам сообщают в качестве результата, и это зависит не только от задачи, но и от подзадачи. В каких-то подзадачах вам могут сообщать информацию по каждому тесту, прошел он или нет (или даже если не прошел, то почему — неправильный ответ, превышение предела времени и т.п. — всё очень похоже на то, что вы видите на этом сайте на вкладке «результаты»). В других подзадачах вам могут только сообщать полный балл за эту подзадачу. В третьих подзадачах — первый непройденный тест и вердикт на нем (как на командных олимпиадах). Что именно вам сообщают, обычно указывается в условии.

При этом может быть установлено ограничение на количество посылок вида, например, «по каждой задаче за весь тур вы можете сделать не более 50 попыток», или что-то подобное. Тогда, после того, как у вас кончились эти 50 попыток, вы, видимо, больше не можете решать эту задачу.

Итоговая оценка

Итоговая оценка за каждую задачу — это максимальный балл из всех отправленных вами решений. Итоговый суммарный результат, конечно, есть сумма баллов по всем задачам.

+
+
Максимальный балл по каждой задаче — 100, соответственно, общий максимальный балл — 800. В этом году также ввели новую норму, что есть «первичные» и «итоговые» баллы,
+        и итоговые баллы равны первичным, деленным на 8 (т.е. баллы приводятся в диапазон от 0 до 100). Видимо, это какие-то бюрократические заморочки, думаю, что реально все будут
+        использовать «первичные» баллы.

Тут важны два момента. Во-первых, учитываются только отправленные в систему решения, а не просто сохраненные вами в вашем рабочем каталоге. Во-вторых, если одно из ваших решений набирает баллы только по одной подзадаче, а второе — только по второй, но вы не сдали решения, которое набирало бы баллы по обеим подзадачам, то вы получите баллы только за одну подзадачу (от того решения, которое набирает больше баллов). Аналогично если одно решение проходит только два теста из некоторой подзадачи, в которой все тесты оцениваются независимо, а другое решение проходит только два других теста, то вы не получите суммарного балла.

@@ -1212,7 +1217,7 @@

Про языки программирования

-
Про питон: на питоне вы, возможно, не сможете в принципе решить отдельные задачи на полный балл (возможно, не сможете решить вторую, а может быть, даже первую) — потому что решение не будет успевать по времени. (Но это не обозначает, что любые проблемы в задаче надо списывать на то, что это питон. Имейте в виду то, что на питоне может не успевать по времени; обязательно (!) оцените сложность алгоритма и прикиньте, успел ли бы такой алгоритм на C++; но если вы придумали явно неоптимальный алгоритм, то надо все-таки придумать нормальный алгоритм.) Но эту невозможность решить задачи на полный балл вы можете компенсировать тем, что на питоне вы можете заметно быстрее и легче писать небольшие подзадачи в сложных задачах, да и первую задачу вы возможно напишите быстрее, чем ваши друзья, пишущие на C++. С другой стороны, если вы хотите иметь хороший результат на областной олимпиаде, то, конечно, надо бы уже знать C++ (но если вы хотите иметь хороший результат, то наверное вы уже минимум на 3 уровне, а тогда про C++ вы уже читали).

+
Про питон: на питоне вы, возможно, не сможете в принципе решить отдельные задачи на полный балл (возможно, не сможете решить вторую, а может быть, даже первую) — потому что решение не будет успевать по времени. (Но это не обозначает, что любые проблемы в задаче надо списывать на то, что это питон. Конечно, решения на питоне может не успевать по времени из-за того, что это питон, но также они могут не успевать просто потому, что это неоптимальное решение. Поэтому обязательно (!) оцените сложность алгоритма и прикиньте, успел ли бы такой алгоритм на C++; если вы придумали явно неоптимальный алгоритм, то надо все-таки придумать нормальный алгоритм.) Но эту невозможность решить задачи на полный балл вы можете компенсировать тем, что на питоне вы можете заметно быстрее и легче писать небольшие подзадачи в сложных задачах, да и первую задачу вы возможно напишите быстрее, чем ваши друзья, пишущие на C++. С другой стороны, если вы хотите иметь хороший результат на областной олимпиаде, то, конечно, надо бы уже знать C++ (но если вы хотите иметь хороший результат, то наверное вы уже минимум на 3 уровне, а тогда про C++ вы уже читали).

Если же вы знаете и C++, и питон, то, конечно, по каждой задаче выбирайте, на каком языке лучше писать.

@@ -1298,7 +1303,7 @@

Не уходите без баллов

По идее, после каждого тура у вас будет обед в столовой университета, потом разбор задач и объявление итогов тура. Я очень вам рекомендую дождаться объявления итогов. Последнее время обычно жюри раздает каждому участнику бумажки с его баллами — вот получите эту бумажку, чтобы проверить, что все подсчитано верно. У нашего жюри регулярно случаются проблемы с подсчетом баллов, поэтому лучше дождитесь и проверьте, что все соответствует вашим ожиданиям. Конечно, вы по идее должны знать свои баллы за счет фидбека, но лучше все-таки дождитесь бумажки.

-
Я постараюсь быть на олимпиаде — скорее всего на первом туре перед туром и после тура, и возможно в день второго тура. Если окажется, что вам неправильно посчитали баллы, то мы с вами можем пойти и поругаться. Но если вы уйдете раньше и не получите бумажку с результатами, то я сам за вас ругаться не пойду; как минимум, вы намного лучше меня знаете, чего вы ожидали.

+
Я постараюсь быть на олимпиаде (если она будет в ННГУ, а не дистанционно) — скорее всего на первом туре перед туром и после тура, и возможно в день второго тура. Если окажется, что вам неправильно посчитали баллы, то мы с вами можем пойти и поругаться. Но если вы уйдете раньше и не получите бумажку с результатами, то я сам за вас ругаться не пойду; как минимум, вы намного лучше меня знаете, чего вы ожидали.

Укажите меня своим преподавателем

Как я где-то уже писал, пожалуйста, укажите меня своим преподавателем, если наши занятия были для вас достаточно полезными. А именно, попросите школу указать меня преподавателем в заявке. На регистрации на олимпиаду перед первым туром обычно можно проверить, кто у вас указан и, если хотите, наверное можно будет исправить.


chmod +x install-systemd-unit.coffee

chmod +x install-systemd-unit.coffee

--- scripts/install-systemd-unit.coffee
+++ scripts/install-systemd-unit.coffee
@@ -1,3 +1,5 @@
+#!/usr/bin/env coffee
+
 deps =
     _   : 'lodash-contrib'
     S   : 'string'


Re added report parsing

Re added report parsing

--- src/scripts/search-virus-total.coffee
+++ src/scripts/search-virus-total.coffee
@@ -53,13 +53,16 @@

     robot.http(vt_url_report_url)
       .post(data) (err, res, body) ->
-        # vt_json = JSON.parse(body)
-        #
-        # summary = """ VirusTotal URL Result: #{vt_json.resource}
-        # - Scanned at: #{vt_json.scan_date}
-        # - Results:    #{vt_json.positives}/#{vt_json.total}
-        # - Link:       #{vt_json.permalink}
-        # """
+        vt_json = JSON.parse(body)
+
+        if vt_json.response_code == 1
+          summary = """ VirusTotal URL Result: #{vt_json.url}
+          - Scanned at: #{vt_json.scan_date}
+          - Results:    #{vt_json.positives}/#{vt_json.total}
+          - Link:       #{vt_json.permalink}
+          """
+
+          msg.send summary


Change slack message to point to LogDNA instead of paper trail.

--- server/slack.coffee
+++ server/slack.coffee
@@ -28,7 +28,7 @@
       unfurl_media: false
     if options.papertrail
       secondsFromEpoch = Math.floor(new Date().getTime() / 1000)
-      link = "https://papertrailapp.com/groups/488214/events?time=#{secondsFromEpoch}"
+      link = "https://app.logdna.com/logs/view?t=timestamp:#{secondsFromEpoch}"
       form.text += " #{link}"
     # https://api.slack.com/docs/formatting
     form.text = form.text.replace('&', '&').replace('<', '<').replace('>', '>')


add regex for new deployment format (#6365)

--- server_setup.coffee
+++ server_setup.coffee
@@ -72,12 +72,17 @@
     # Don't proxy static files with sha prefixes, redirect them
     regex = /\/[0-9a-f]{40}\/.*/
     regex2 = /\/[0-9a-f]{40}-[0-9a-f]{40}\/.*/
+    regex3 = /^\/(production|next)-\d{4}-\d{2}-\d{2}-\d{2}-\d{2}-\d{2}\/.*/
     app.use (req, res, next) ->
       if regex.test(req.path)
         newPath = req.path.slice(41)
         return res.redirect(newPath)
       if regex2.test(req.path)
         newPath = req.path.slice(82)
+        return res.redirect(newPath)
+      if regex3.test(req.path)
+        split = req.path.split('/')
+        newPath = '/' + split.slice(2).join('/')
         return res.redirect(newPath)
       next()

@@ -274,7 +279,6 @@
   app.get '/play', fast('overworld.html')
   app.get '/play/level/:slug', fast('main.html')
   app.get '/play/:slug', fast('main.html')
-  app.get '/teachers/:slug', fast('main.html')

 ###Miscellaneous configuration functions###


Fix repeated name

Fix repeated name

--- containers/mixins/Handler.jsx.coffee
+++ containers/mixins/Handler.jsx.coffee
@@ -39,7 +39,7 @@
   defaultButtons: ->
     Save:   onClick: 'submitForm',  bsStyle: 'primary'
     Reset:  onClick: 'resetForm',   bsStyle: 'warning'
-    Cancel: onClick: 'goBack'
+    Cancel: onClick: 'goBackHistory'

   bindButtons: (buttons) ->
     boundButtons = {}
@@ -76,7 +76,7 @@
       @setState formData: @props.formData

   # meh
-  goBack: ->
+  goBackHistory: ->
     window.history.back()

   renderForm: (options = {}) ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/controllable.coffee
+++ test/controllable.coffee
@@ -10,7 +10,7 @@
   player.include(Controllable)

-  keydown = {}
+  window.keydown = {}
   keydown.left = true

   player.update()


exec: make test compatible w/ linux

exec: make test compatible w/ linux

--- test/core/execute.coffee
+++ test/core/execute.coffee
@@ -18,7 +18,7 @@
       executed.should.be.true() unless err
       stdout.should.eql 'yes\n' unless err
       next err
-
+
   they 'stream stdout and unpipe', (ssh, next) -> #.skip 'remote',
     @timeout 10000000
     writer_done = callback_done = null
@@ -47,7 +47,7 @@
       data.should.containEql search1
       data.should.containEql search2
       next()
-
+
   they 'stdout and stderr return empty', (ssh, next) -> #.skip 'remote',
     mecano.execute
       ssh: ssh
@@ -56,18 +56,18 @@
       stdout.should.eql ''
       stderr.should.eql ''
       next()
-
+
   they 'validate exit code', (ssh, next) ->
     # code undefined
     mecano
       ssh: ssh
     .execute
-      cmd: "ls -l #{scratch}/doesnt_exist"
+      cmd: "exit 42"
     .then (err, executed) ->
-      err.message.should.eql 'Invalid Exit Code: 1'
+      err.message.should.eql 'Invalid Exit Code: 42'
     .execute
-      cmd: "ls -l #{scratch}/doesnt_exist"
-      code: [0, 1]
+      cmd: "exit 42"
+      code: [0, 42]
     .then (err, executed) ->
       next err

@@ -90,7 +90,7 @@
       return next err if err
       executed.should.be.false()
       next()
-
+
   they 'should honor conditions', (ssh, next) ->
     mecano
       ssh: ssh
@@ -153,5 +153,3 @@
         err.should.be.an.Error
         err.code.should.eql 2
         next()
-
-


updated some failing tests

updated some failing tests

--- test/modes/normal.coffee
+++ test/modes/normal.coffee
@@ -2,7 +2,7 @@
   normal = require 'jim/modes/normal'

   test 'normal mode invalid command parsing', ->
-    deepEqual normal.parse("e"), {}
+    deepEqual normal.parse("m"), {}

   test 'normal mode partial command parsing', ->
     deepEqual normal.parse("2"), 'continueBuffering'
@@ -14,7 +14,7 @@
     deepEqual normal.parse('C'), action: 'deleteToLineEnd', changeToMode: 'insert'

   test 'normal mode visual transition parsing', ->
-    deepEqual normal.parse('v'), action: 'selectRight', changeToMode: 'visual:characterwise'
+    deepEqual normal.parse('v'), changeToMode: 'visual:characterwise'
     deepEqual normal.parse('V'), action: 'selectLine', changeToMode: 'visual:linewise'

   test 'normal mode motion parsing', ->


Ace: Lazy emit ready event.

Ace: Lazy emit ready event.

--- client/Ace/ace.coffee
+++ client/Ace/ace.coffee
@@ -23,11 +23,9 @@
           return  unless document.getElementById id
           @editor = ace.edit id
           @prepareEditor()
-          @utils.defer => @emit 'ace.ready'
           if contents
             @setContents contents
             @lastSavedContents = contents
-

           @editor.on 'change', =>
             if @isCurrentContentChanged()
@@ -35,10 +33,11 @@
             else
               @emit 'FileContentSynced'

-
           @editor.gotoLine 0
           @focus()
           @show()
+
+          @utils.defer => @emit 'ace.ready'

           KD.mixpanel 'Open Ace, success'


Fix display of shadow editors in history view

Bug introduced when unescaping in 75d67d6a

--- client/message.coffee
+++ client/message.coffee
@@ -2741,7 +2741,7 @@
 export MessageAuthor = React.memo ({message, also}) ->
   if also
     also = {}
-    also[unescapeUser author] for author of message.authors
+    also[unescapeUser author] = updated for author, updated of message.authors
   count = 0
   <div className="author text-right">
     {'(by '}
@@ -2753,7 +2753,7 @@
          subtitle={messageAuthorSubtitle message, author}/>
       </React.Fragment>
     }
-    {if also and not _.isEmpty also
+    {console.log (_.keys also); if also and not _.isEmpty also
       for author, updated of also
         <React.Fragment key={author}>
           {', ' if count++}


Fix lodash import

Fix lodash import

--- client/src/app.coffee
+++ client/src/app.coffee
@@ -2,6 +2,8 @@
 jquery = require 'jquery';
 window.jQuery = jquery;
 window.$ = jquery;
+lodash = require 'lodash'
+window._ = lodash

 require 'angular-material/angular-material.min.css'
 require 'angular-material-data-table/dist/md-data-table.min.css'
@@ -25,9 +27,9 @@
 angularDateInterceptor = require 'angular-date-interceptor'
 trello = require 'angular-trello'

+
 require 'angular-aria'
 require 'moment'
-require 'lodash'
 require 'd3'
 require 'd3-bdc'
 require 'c3'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/metered.coffee
+++ test/metered.coffee
@@ -18,6 +18,6 @@

   equals obj.I.maxHealth, 150

-tset
+test

 module()


Fixed indentation and syntax to match project.

--- src/ui/views/menu.coffee
+++ src/ui/views/menu.coffee
@@ -68,38 +68,36 @@
                     click: (it) -> action 'showconvlast', it.checked
                 }
             ]
-
         }, {
             type: 'checkbox'
             label: 'Convert text to emoji'
             checked: viewstate.convertEmoji
             enabled: viewstate.loggedin
             click: (it) -> action 'convertemoji', it.checked
-        },
-         {
-           label: 'Pop-Up Notification'
-           submenu: [
-             {
-                type: 'checkbox'
-                label: 'Show notifications'
-                checked: viewstate.showPopUpNotifications
-                enabled: viewstate.loggedin
-                click: (it) -> action 'showpopupnotifications', it.checked
-            }, {
-                type: 'checkbox'
-                label: 'Show message in notifications'
-                checked: viewstate.showMessageInNotification
-                enabled: viewstate.loggedin && viewstate.showPopUpNotifications
-                click: (it) -> action 'showmessageinnotification', it.checked
-            }, {
-                type: 'checkbox'
-                label: 'Show username in notifications'
-                checked: viewstate.showMessageInNotification
-                enabled: viewstate.loggedin && viewstate.showPopUpNotifications
-                click: (it) -> action 'showusernameinnotification', it.checked
-            }
-           ]
-         }, {
+        }, {
+            label: 'Pop-Up Notification'
+            submenu: [
+                {
+                    type: 'checkbox'
+                    label: 'Show notifications'
+                    checked: viewstate.showPopUpNotifications
+                    enabled: viewstate.loggedin
+                    click: (it) -> action 'showpopupnotifications', it.checked
+                }, {
+                    type: 'checkbox'
+                    label: 'Show message in notifications'
+                    checked: viewstate.showMessageInNotification
+                    enabled: viewstate.loggedin && viewstate.showPopUpNotifications
+                    click: (it) -> action 'showmessageinnotification', it.checked
+                }, {
+                    type: 'checkbox'
+                    label: 'Show username in notifications'
+                    checked: viewstate.showMessageInNotification
+                    enabled: viewstate.loggedin && viewstate.showPopUpNotifications
+                    click: (it) -> action 'showusernameinnotification', it.checked
+                }
+            ]
+        }, {
             label: 'Color Scheme'
             submenu: [
                 {
@@ -154,7 +152,6 @@
                     click: -> action 'changefontsize', 'x-large'
                 }
             ]
-
         }, {
             label: 'Toggle Full Screen',
             accelerator: 'Command+Control+F',
@@ -344,28 +341,28 @@
             enabled: viewstate.loggedin
             click: (it) -> action 'convertemoji', it.checked
         }, {
-          label: 'Pop-Up Notification'
-          submenu: [
-            {
-               type: 'checkbox'
-               label: 'Show notifications'
-               checked: viewstate.showPopUpNotifications
-               enabled: viewstate.loggedin
-               click: (it) -> action 'showpopupnotifications', it.checked
-           }, {
-               type: 'checkbox'
-               label: 'Show message in notifications'
-               checked: viewstate.showMessageInNotification
-               enabled: viewstate.loggedin && viewstate.showPopUpNotifications
-               click: (it) -> action 'showmessageinnotification', it.checked
-           }, {
-               type: 'checkbox'
-               label: 'Show username in notifications'
-               checked: viewstate.showUsernameInNotification
-               enabled: viewstate.loggedin && viewstate.showPopUpNotifications
-               click: (it) -> action 'showusernameinnotification', it.checked
-           }
-          ]
+            label: 'Pop-Up Notification'
+            submenu: [
+                {
+                    type: 'checkbox'
+                    label: 'Show notifications'
+                    checked: viewstate.showPopUpNotifications
+                    enabled: viewstate.loggedin
+                    click: (it) -> action 'showpopupnotifications', it.checked
+                }, {
+                    type: 'checkbox'
+                    label: 'Show message in notifications'
+                    checked: viewstate.showMessageInNotification
+                    enabled: viewstate.loggedin && viewstate.showPopUpNotifications
+                    click: (it) -> action 'showmessageinnotification', it.checked
+                }, {
+                    type: 'checkbox'
+                    label: 'Show username in notifications'
+                    checked: viewstate.showUsernameInNotification
+                    enabled: viewstate.loggedin && viewstate.showPopUpNotifications
+                    click: (it) -> action 'showusernameinnotification', it.checked
+                }
+            ]
         }, {
             label: 'Color Scheme'
             submenu: [


Ember runloop snippets

Ember runloop snippets

--- snippets/ember.cson
+++ snippets/ember.cson
@@ -255,6 +255,7 @@
     'leftLabel': 'Ember'
     'body': "uniqBy('${1:key}');"

+<<<<<<< HEAD
   #
   #
   # runloop
@@ -268,6 +269,60 @@
     'prefix': 'cancel'
     'leftLabel': 'Ember'
     'body': "cancel('${1:key}');"
+
+  'debounce(...)':
+    'prefix': 'debounce'
+    'leftLabel': 'Ember'
+    'body': "debounce(${1:this}, '${2:methodName}', ${3:500});"
+
+  'later(...)':
+    'prefix': 'later'
+    'leftLabel': 'Ember'
+    'body': "later(${1:this}, () => { ${2}\\}, ${3:500});"
+
+  'next(...)':
+    'prefix': 'next'
+    'leftLabel': 'Ember'
+    'body': "next(${1:this}, () => { ${2}\\});"
+
+  'once(...)':
+    'prefix': 'once'
+    'leftLabel': 'Ember'
+    'body': "once(${1:this}, () => { ${2}\\});"
+
+  'run(...)':
+    'prefix': 'run'
+    'leftLabel': 'Ember'
+    'body': "run(() => { ${1}\\});"
+
+  'schedule(...)':
+    'prefix': 'schedule'
+    'leftLabel': 'Ember'
+    'body': "schedule('${1:queue}', ${2:this}, () => { ${3}\\}, );"
+
+  'scheduleOnce(...)':
+    'prefix': 'scheduleOnce'
+    'leftLabel': 'Ember'
+    'body': "scheduleOnce('${1:queue}', ${2:this}, () => { ${3}\\}, );"
+
+  'throttle(...)':
+    'prefix': 'throttle'
+    'leftLabel': 'Ember'
+    'body': "throttle(${1:this}, '${2:methodName}', ${3:500});"
+
+  #
+  #
+  # runloop
+  #
+  'bind(...)':
+    'prefix': 'bind'
+    'leftLabel': 'Ember'
+    'body': "bind(${1:this}, ${2:this.methodName});"
+
+  'cancel(...)':
+    'prefix': 'cancel'
+    'leftLabel': 'Ember'
+    'body': "cancel(${1});"

   'debounce(...)':
     'prefix': 'debounce'


remove replicated topic test

remove replicated topic test

--- test/adapter_test.coffee
+++ test/adapter_test.coffee
@@ -42,13 +42,6 @@
       it 'does nothing', ->
         @adapter.topic({}, 'nothing')

-    describe 'topic', ->
-      it 'is a function', ->
-        expect(@adapter.topic).to.be.a('function')
-
-      it 'does nothing', ->
-        @adapter.topic({}, 'nothing')
-
     describe 'play', ->
       it 'is a function', ->
         expect(@adapter.play).to.be.a('function')
@@ -75,7 +68,7 @@
     @robot.receive = sinon.spy()
     @adapter = new Adapter(@robot)
     @message = sinon.spy()
-
+
     @adapter.receive(@message)

     expect(@robot.receive).to.have.been.calledWith(@message)


XY: Going forward, phase out sugar.js

XY: Going forward, phase out sugar.js

--- test/xy/abilities.coffee
+++ test/xy/abilities.coffee
@@ -9,7 +9,6 @@
 shared = require '../shared'

 require '../helpers'
-require 'sugar'

 describe "BW Abilities:", ->
   testWeatherAbility = (name, weather) ->


FIX: Code performance improvements. Specially in the getPrefix function

--- src/Moon.coffee
+++ src/Moon.coffee
@@ -1,25 +1,23 @@
 do (window, document) ->
     # Main object of MoonJS
     Moon = (target, args) ->
-        self = this
         # this function acts like a 'enhanced' init
-        if window == self
+        if window == this
             return new Moon(target, args)

         # Moon collection
-        self._collection = Moon.fn.getMoonCollection(target)
+        this._collection = Moon.fn.getMoonCollection(target)

         # Moon variables to control animation
-        self._callback = undefined
-        self._stack = []
-        self._step = -1
-        self._prefixes = {}
+        this._callback = undefined
+        this._stack = []
+        this._step = -1
+        this._prefixes = {}

-        # init with the basic prefix
-        self._prefixes["transition"] = Moon.fn.getPrefix("transition")
-        return self
+        return this

     Moon.fn = Moon.prototype =
+        _prefixes: {}
         # returns the collection of HTMLCollection or NodeList, that can be animated by Moon later
         getMoonCollection: (target) ->
             collection = []
@@ -42,21 +40,33 @@

         # returns the prefix of a css style in "javascript" style. Used mainly for css3
         getPrefix: (prop) ->
+
+            # check if Moon already tried to get this prefix
+            if Moon.fn._prefixes[prop]?
+                return Moon.fn._prefixes[prop]
+
+            # if not, continue the search for it
             prefixes = ["webkit", "moz", "ms", "O"]
+
             indexOfDash = prop.indexOf("-")
             while indexOfDash > -1
+
                 # the letter after the dash is capitalized
                 prop = prop.slice(0, indexOfDash) + prop.charAt(indexOfDash + 1).toUpperCase() + prop.slice(indexOfDash + 2)
                 indexOfDash = prop.indexOf("-")

+            # is the propertie avaiable without prefix?
             if document.documentElement.style[prop]?
+                Moon.fn._prefixes[prop] = prop
                 return prop

+            # if not, try to find the prefix
+            propCap = Moon.fn.cap(prop)
             for pre in prefixes
-                propertie = pre + Moon.fn.cap(prop)
+                propertie = pre + propCap
                 if document.documentElement.style[propertie]?
+                    Moon.fn._prefixes[prop] = propertie
                     return propertie
-

         # capitalizes the first letter
         cap: (str) ->
@@ -64,7 +74,6 @@

         # defines an animation step. Moon animations must have at least one step
         animate: (args) ->
-            self = this
             animationProps =
                 duration: 0
                 delay: 0
@@ -75,53 +84,52 @@
             for arg, value of args
                 animationProps[arg] = value

-            self._stack.push(animationProps)
-            return self
+            this._stack.push(animationProps)
+            return this

         # play the animation and sets the end callback
         play: (callback) ->
-            self = this
-            self._callback = callback
-            self._play()
-            return self
+            this._callback = callback
+            this._play()
+            return this

-        # "private" play function for intern use
+        # play function for intern use
         _play: ->
-            self = this
-            self._step++
-            anm = self._stack[self._step]
+            this._step++
+            anm = this._stack[this._step]
             if anm?
                 # before animation function
                 anm.beforeAnimation() if typeof anm.beforeAnimation == "function"

                 # apply animation for each element
-                for el in self._collection
-                    el.style[self.getPrefix("transition")] = "#{anm.duration}ms all #{anm.easing} #{anm.delay}ms"
+                for el in this._collection
+                    el.style[this.getPrefix("transition")] = "#{anm.duration}ms all #{anm.easing} #{anm.delay}ms"

                     for key, value of anm
                         if key == "duration" || key == "delay" || key == "easing" || key == "beforeAnimation" || key == "afterAnimation"
                             continue
-                        el.style[self.getPrefix(key)] = value
+                        el.style[this.getPrefix(key)] = value

                 nextTimeout = setTimeout ->
                     # after animation function
                     anm.afterAnimation() if typeof anm.afterAnimation == "function"

                     # continue chained animations
-                    self._play()
+                    this._play()

                     clearTimeout(nextTimeout)
                 , anm.delay + anm.duration

             # no more stacked animations
             else
-                self._callback() if self._callback?
+                this._callback() if this._callback?
+                this.reset()
+            return this

-                # reset Moon animation
-                self._callback = undefined
-                self._step = -1
-                self._stack = []
-
-            return self
+        # reset Moon animation
+        reset: ->
+            this._callback = undefined
+            this._step = -1
+            this._stack = []

     window.Moon = Moon


'httpGet' helper

'httpGet' helper

--- src/data.coffee
+++ src/data.coffee
@@ -35,6 +35,16 @@
   console.log cmd
   child_process.exec cmd, done.resolve
   return done.promise
+
+
+httpGet = (url) ->
+  deferred = Q.defer()
+  request url, (err, res, body) ->
+    if err?
+      deferred.fail(err)
+    else
+      deferred.resolve(body)
+  return deferred.promise

 ensureDir = (path) ->
@@ -79,25 +89,26 @@

 data.build = (region) ->
   console.log("building", region)
-  deferred = Q.defer()
+  bbox = data.REGION[region].bbox
+  dem = null

   data.dem(region)

-  .then (dem) ->
-    bbox = data.REGION[region].bbox
+  .then (rv) ->
+    dem = rv
     q = query(bbox)
     url = "http://overpass-api.de/api/interpreter?data=#{encodeURIComponent(q)}"
     console.log("overpass:", q)
-
-    request url, (err, res, body) ->
-      p = JSON.parse(body)
-      db = compileOsm(bbox, p, dem)
-      ensureDir("build/#{region}")
-      fs.writeFileSync("build/#{region}/data.json", JSON.stringify(db))
-      console.log("done", region)
-      deferred.resolve()
-
-  return deferred.promise
+    httpGet(url)
+
+  .then (body) ->
+    p = JSON.parse(body)
+    compileOsm(bbox, p, dem)
+
+  .then (db) ->
+    ensureDir("build/#{region}")
+    fs.writeFileSync("build/#{region}/data.json", JSON.stringify(db))
+    console.log("done", region)

 compileOsm = (bbox, osm, dem) ->


Activity: new message route handling added

--- client/Social/Activity/routes.coffee
+++ client/Social/Activity/routes.coffee
@@ -18,8 +18,13 @@

   handleChannel = (type, slug) ->
-    {router, appManager} = KD.singletons
+    {appManager} = KD.singletons
     appManager.open 'Activity', (app) -> app.getView().open type, slug
+
+
+  handleNewMessage = (type, slug) ->
+    {appManager} = KD.singletons
+    appManager.open 'Activity', (app) -> app.getView().showNewMessageModal()

   KD.registerRoutes 'Activity',
@@ -31,6 +36,8 @@

     '/:name?/Activity/Post/:slug?' : ({params:{name, slug}, query}) ->
       handleChannel 'post', slug
+
+    '/:name?/Activity/Message/New' : handleNewMessage

     '/:name?/Activity/Message/:slug?' : ({params:{name, slug}, query}) ->
       handleChannel 'message', slug


AppStorage fixes, needs to fix everywhere where we used AppStorage

--- client/app/MainApp/AppStorage.coffee
+++ client/app/MainApp/AppStorage.coffee
@@ -1,5 +1,5 @@

-class AppStorage # extends KDObject
+class AppStorage

   constructor: (appId, version)->
     @_applicationID = appId
@@ -17,20 +17,18 @@
     else
       callback @_storage

-  fetchValue: (key, defaultValue, callback, group = 'bucket')->
+  fetchValue: (key, callback, group = 'bucket')->

     @reset()
     @fetchStorage (storage)=>
-      callback if storage[group]?[key] then storage[group][key] else defaultValue
+      callback if storage[group]?[key] then storage[group][key]

-  getValue: (key, defaultValue, group = 'bucket')->
+  getValue: (key, group = 'bucket')->

-    return defaultValue unless @_storage
-    return if @_storage[group]?[key]? then @_storage[group][key] else defaultValue
+    return unless @_storage
+    return if @_storage[group]?[key]? then @_storage[group][key]

   setValue: (key, value, callback, group = 'bucket')->
-
-    return if @getValue(key) is value

     pack = @zip key, group, value


Destroy all decorations when Highlight Selected has cleared too.

--- lib/minimap-highlight-selected.coffee
+++ lib/minimap-highlight-selected.coffee
@@ -34,6 +34,7 @@
   init: ->
     @decorations = []
     @highlightSelected.onDidAddMarker (marker) => @markerCreated(marker)
+    @highlightSelected.onDidRemoveAllMarkers => @markersDestroyed()

   dispose: ->
     @decorations.forEach (decoration) -> decoration.destroy()
@@ -47,6 +48,10 @@
       {type: 'highlight', class: @highlightSelected.makeClasses()})
     @decorations.push decoration

+  markersDestroyed: =>
+    @decorations.forEach (decoration) -> decoration.destroy()
+    @decorations = []
+
   deactivatePlugin: ->
     return unless @active


hotfix for domain change

hotfix for domain change

--- coffee/heikeji.coffee
+++ coffee/heikeji.coffee
@@ -41,8 +41,8 @@

   # 判断是否是b站, 非b站连接不执行

-  _params = /http:\/\/(www\.bilibili\.tv|bilibili\.kankanews\.com)?\/video\/av([0-9]+)\/(?:index_([0-9]+)\.html)?/.exec(d.URL)
-  _b_url_r = (/http:\/\/(www\.bilibili\.tv|bilibili\.kankanews\.com)/.exec(d.URL))
+  _params = /http:\/\/(www\.bilibili\.com|bilibili\.kankanews\.com)?\/video\/av([0-9]+)\/(?:index_([0-9]+)\.html)?/.exec(d.URL)
+  _b_url_r = (/http:\/\/(www\.bilibili\.com|bilibili\.kankanews\.com)/.exec(d.URL))
   tpModel = !_params && !!_b_url_r
   _bilibili_url = if !_b_url_r then null else _b_url_r[0]


Speed up long output lines so total time is the same

--- coffee/machine.coffee
+++ coffee/machine.coffee
@@ -15,8 +15,8 @@
 # states: OFF, WAITING, RUNNING
 class Machine
   MILLIS_FOR_BOLD = 300
-  MILLIS_FOR_OUTPUT = 300
-  MILLIS_FOR_OUTPUT_LETTER = 100
+  MILLIS_FOR_OUTPUT_DURING = 200
+  MILLIS_FOR_OUTPUT_AFTER = 300
   MILLIS_FOR_UNBOLD = 500
   MILLIS_FOR_SCROLLED_INSTRUCTIONS = 500
   MILLIS_FOR_SCROLLED_INSTRUCTIONS_TENTH = 5
@@ -138,10 +138,11 @@
         $one('div.machine .console').scrollTop =
           $one('div.machine .console').scrollHeight
       if rest == ''
-        @setTimeout callback, MILLIS_FOR_OUTPUT
+        @setTimeout callback, MILLIS_FOR_OUTPUT_AFTER
       else
-        @setTimeout (-> outputNextLetter rest), MILLIS_FOR_OUTPUT_LETTER
+        @setTimeout (-> outputNextLetter rest), millis_for_each_letter
     output = @_executeNextLineGettingOutput()
+    millis_for_each_letter = MILLIS_FOR_OUTPUT_DURING / (output.length || 1)
     outputNextLetter output

   _unboldNextLine: (callback) ->


Actually invoke onPress

Actually invoke onPress

--- src/index.coffee
+++ src/index.coffee
@@ -78,6 +78,8 @@
   handleMouseLeave: -> @setState isInside: false

   handleMouseDown: ->
+    @props.onPress?()
+
     # Listen for a mouse up somewhere else in case the user presses then moves.
     eventlistener.add document, 'mouseup', @handleDocumentMouseUp


Pretty colors in help

Pretty colors in help

--- src/index.coffee
+++ src/index.coffee
@@ -1,5 +1,6 @@
 fs = require 'fs'
 program = require 'commander'
+colors = require 'colors'
 pkg = require '../package.json'

 subcommands = [
@@ -20,13 +21,22 @@
   'uploaded'
 ]

+program.Command.prototype._commandHelp = program.Command.prototype.commandHelp
+program.Command.prototype.commandHelp = ->
+  for cmd in this.commands
+    cmd._name = cmd._name.green
+    cmd._description = cmd._description.blue
+  @_commandHelp()
+
 program.version(pkg.version)
 require("./commands/#{s}") program for s in subcommands
+
 program._parse = program.parse
 program.parse = ->
   program._parse arguments...
   program.help() if program.rawArgs.length < 3

+
 module.exports = program

 module.exports.IEVM = require './ievm'


Export image dimensions along with contents

--- src/index.coffee
+++ src/index.coffee
@@ -10,7 +10,10 @@
         fs.createReadStream(path)
             .pipe(new Png {filterType: 4})
             .on 'parsed', () ->
-                output = 'return [\n'
+                output = 'exports.width = ' + this.width + ';\n'
+                output += 'exports.height = ' + this.height + ';\n'
+
+                output += 'exports.contents = [\n'
                 mapCell =
                     walkable: 0
                     unwalkable: 1
@@ -37,6 +40,7 @@
                         if not (y == this.height - 1 and x == this.width - 1)
                             output += ', '
                     output += '\n'
+
                 output += ']'

                 callback null, {data: output}


Fix drop callback

Fix drop callback

--- src/index.coffee
+++ src/index.coffee
@@ -1,6 +1,6 @@
 module.exports = class TreeView

-  constructor: (container, dropCallback) ->
+  constructor: (container, @dropCallback) ->
     @_treeRoot = document.createElement 'ol'
     @_treeRoot.classList.add 'tree'
     container.appendChild @_treeRoot
@@ -223,7 +223,7 @@

     @_clearDropClasses()

-    reparent = dropCallback?(dropInfo) ? true
+    reparent = @dropCallback?(dropInfo) ? true
     return if ! reparent

     switch dropInfo.where