Use flex in MainView

Use flex in MainView

--- views/components/main/index.cjsx
+++ views/components/main/index.cjsx
@@ -1,5 +1,5 @@
 path = require 'path-extra'
-{layout, ROOT, $, $$, React, ReactBootstrap} = window
+{ROOT, $, $$, React, ReactBootstrap} = window
 {Grid, Col} = ReactBootstrap
 {TeitokuPanel, MissionPanel, NdockPanel, KdockPanel, TaskPanel} = require './parts'
 module.exports =
@@ -9,11 +9,10 @@
   description: '概览面板，提供基本的概览界面'
   reactClass: React.createClass
     getInitialState: ->
-      xs: if layout == 'horizonal' then 6 else 3
+      layout: window.layout
     handleChangeLayout: (e) ->
-      {layout} = e.detail
       @setState
-        xs: if layout == 'horizonal' then 6 else 3
+        layout: e.detail.layout
     componentDidMount: ->
       window.addEventListener 'layout.change', @handleChangeLayout
     componentWillUnmount: ->
@@ -22,18 +21,24 @@

         <link rel="stylesheet" href={path.join(path.relative(ROOT, __dirname), 'assets', 'main.css')} />
         <TeitokuPanel ref="teitokuPanel" />
-          <Grid className="panel-container">
-            <Col xs={@state.xs} className="panel-col ndock-panel" ref="ndockPanel">
-              <NdockPanel />
-            </Col>
-            <Col xs={@state.xs} className="panel-col kdock-panel" ref="kdockPanel">
-              <KdockPanel />
-            </Col>
-            <Col xs={@state.xs} className="panel-col mission-panel" ref="missionPanel">
-              <MissionPanel />
-            </Col>
-            <Col xs={@state.xs} className="panel-col task-panel" ref="taskPanel">
-              <TaskPanel />
-            </Col>
-          </Grid>
+        {
+          if @state.layout == 'horizonal'
+            [
+

+                <NdockPanel style={flex: 1} />
+                <KdockPanel style={flex: 1} />
+
,
+

+                <MissionPanel style={flex: 1} />
+                <TaskPanel style={flex: 1} />
+

+            ]
+          else
+

+              <NdockPanel style={flex: 1} />
+              <KdockPanel style={flex: 1} />
+              <MissionPanel style={flex: 1} />
+              <TaskPanel style={flex: 1} />
+

+        }


Fix ship panel countdown, fix https://github.com/yudachi/poi/issues/130, fix useless interval

--- views/components/ship/index.cjsx
+++ views/components/ship/index.cjsx
@@ -182,6 +182,10 @@
       countdown = decks.map (deck) ->
         getCondCountdown deck
       timeDelta = [0, 0, 0, 0]
+      if countdown[0] > 0 || countdown[1] > 0 || countdown[2] > 0 || countdown[3] > 0
+        @interval = setInterval @updateCountdown, 1000 if !@interval?
+      else
+        @interval = clearInterval @interval if @interval?
       @setState
         names: names
         decks: decks
@@ -191,19 +195,22 @@
         countdown: countdown
     updateCountdown: ->
       {countdown, states} = @state
+      flag = true
       for i in [0..3]
         if countdown[i] - timeDelta[i] > 0
+          flag = false
           timeDelta[i] += 1
           # Use DOM operation instead of React for performance
-          $("#ShipView #deck-condition-countdown-#{i}").innerHTML = resolveTime(countdown[i] - timeDelta[i])
+          $("#ShipView #deck-condition-countdown-#{i}-#{@componentId}").innerHTML = resolveTime(countdown[i] - timeDelta[i])
           if countdown[i] == timeDelta[i] and states[i] < 4
             notify "#{@state.names[i]} 疲劳回复完成", {icon: join(ROOT, 'assets', 'img', 'operation', 'sortie.png')}
+      @interval = clearInterval @interval if flag
     componentDidMount: ->
       window.addEventListener 'game.response', @handleResponse
-      setInterval @updateCountdown, 1000
+      @componentId = Math.ceil(Date.now() * Math.random())
     componentWillUnmount: ->
       window.removeEventListener 'game.response', @handleResponse
-      clearInterval @updateCountdown, 1000
+      @interval = clearInterval @interval if @interval?
     render: ->

         <link rel="stylesheet" href={join(relative(ROOT, __dirname), 'assets', 'ship.css')} />
@@ -239,7 +246,7 @@
                     </OverlayTrigger>
                   </Col>
                   <Col xs={4}>
-                    回复：{resolveTime @state.countdown[i]}
+                    回复：{resolveTime @state.countdown[i]}
                   </Col>
                 </Grid>
               </Alert>


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -98,6 +98,29 @@

     return result

+  ###*
+  Create a new color. The constructor is very flexible. It accepts arrays of r, g, b
+  values, hex strings, hsl strings, and other Color objects If no arguments are given,
+  defaults to transparent.
+
+

+  color = Color()
+
+  color.toString()
+  # => 'rgba(0, 0, 0, 0)'
+
+  color = Color('Sky Blue')
+
+  color.toHex()
+  # => '#75bbfd'
+
+

+
+  @name Color
+  @param {Array|Number|String|Color} args Either an Array or r, g, b values,
+  a sequence of numbers defining r, g, b values, a hex or hsl string, or another Color object
+  @constructor
+  ###
   Color = (args...) ->
     parsedColor =
       switch args.length


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -1671,5 +1671,5 @@
   ]

   names.each (element) ->
-    lookup[element[1].toLowerCase().replace(/ /g, "")] = Color(element[0])
+    lookup[element[1].toLowerCase().replace(/ /g, "")] = parseHex(element[0])
 )()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -117,7 +117,6 @@
   and even the named colors from the xkcd survey: http://blog.xkcd.com/2010/05/03/color-survey-results/.
   If no arguments are given, defaults to transparent.

-
       individualRgb = Color(23, 56, 49, 0.4)

       arrayRgb = Color([59, 100, 230])


Fix build

Fix build

--- server/calculations/calculateLevel.coffee
+++ server/calculations/calculateLevel.coffee
@@ -1,7 +1,7 @@
 import Table from '../models/table'
 import Result from '../models/result'
 import logger from '../log'
-import isContestRequired from '../lib/isContestRequired'
+import isContestRequired from '../../client/lib/isContestRequired'

 export default calculateLevel = (user, baseLevel, lastDate) ->
     for bigLevel in [1..10]


Adding RequestProcessor in sdk exports

Adding RequestProcessor in sdk exports

--- src/index.coffee
+++ src/index.coffee
@@ -1,10 +1,14 @@
 __ = require "lodash/fp"
 requireDirectory = require "require-directory"

-module.exports =
-  Builder: require "./processor.builder"
-  Observers: requireDirectory module, "./observers", {
+requireDir = (path) ->
+  requireDirectory module, path, {
     recurse: false
     exclude: /spec/
     rename: __.compose __.upperFirst, __.camelCase
   }
+
+module.exports =
+  Builder: require "./processor.builder"
+  Observers: requireDir "./observers"
+  Processors: requireDir "./processors"


Use key "username" (not "user") for loginWithUsername

--- src/index.coffee
+++ src/index.coffee
@@ -92,7 +92,7 @@
       return cb err, res

 loginWithUsername = (ddp, username, password, options..., cb) ->
-   attemptLogin ddp, {user: username}, password, options[0], cb
+   attemptLogin ddp, {username: username}, password, options[0], cb

 loginWithEmail = (ddp, email, password, options..., cb) ->
    attemptLogin ddp, {email: email}, password, options[0], cb


fix(index): wait for i18n messages to be parsed

Closes #32.

--- src/index.coffee
+++ src/index.coffee
@@ -25,10 +25,13 @@

     m.runBeforeDom? settings.get m._definedKeys

-  # Defer this to not do too much at the same time and freeze the browser.
+
+# Defer this to not do too much at the same time and freeze the browser.
+i18nReady = new Promise ( resolve ) ->
   setTimeout ->
     require "./i18n/en"
     require "./i18n/ru"
+    resolve()

 domReady = new Promise ( resolve ) ->
@@ -37,7 +40,8 @@
 # Local settings are fetched faster than the DOM with a high level of certainty,
 # but it's still not guaranteed that they'll be ready before the DOM.
 # So we need to wait for both here to be sure, not only for DOM.
-Promise.all [ settingsReady, domReady ]
+# i18n messages are generally ready long before DOM, but not always. See #32.
+Promise.all [ settingsReady, domReady, i18nReady ]
 .then ->

   i18n.setLanguage if window.vk.lang is 0 then "ru" else "en"


Remove meaningless opt defaults

Remove meaningless opt defaults

--- src/index.coffee
+++ src/index.coffee
@@ -37,9 +37,6 @@

   opts.backgroundShapes ?= []
   opts.watermarkImage ?= null
-
-  opts.pickerWidth = 60
-  opts.optionsHeight = 60

   unless 'tools' of opts
     opts.tools = [


default logPath to use proc name

default logPath to use proc name

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

 convert = (proc) ->
-  proc = extend {}, defaults, proc
+  proc = extend {}, defaults(proc.name), proc
   proc.spawnOpts.cwd ?= process.cwd()

   (callback) ->
@@ -44,9 +44,9 @@
   else
     func

-defaults =
+defaults = (procName) ->
   port: 0 # get random available port
-  logFilePath: './log/process.log' # TODO: use proc name
+  logFilePath: "./log/#{procName}.log"
   spawnOpts: {}
   verifyInterval: 100
   verifyTimeout: 3000


Better check for voters and commenters

Better check for voters and commenters

--- src/index.coffee
+++ src/index.coffee
@@ -206,6 +206,20 @@
     res.render 'home', title: "Products You May Have Missed"

 router.get '/posts', userRequired, clientOnlyToken, (req, res, next) ->
+
+  voters = (post) ->
+    _.pluck(post.votes, "user_id")
+  commenters = (post) ->
+    commentersInArray = (comments) ->
+      _.union(_.flatten(_.map(comments, commentersInComment)))
+    commentersInComment = (comment) ->
+      _.union [comment.user_id], commentersInArray(comment.child_comments)
+    commentersInArray post.comments
+  votedFor = (user, post) ->
+    voters(post).indexOf(user.id) != -1
+  commentedOn = (user, post) ->
+    commenters(post).indexOf(user.id) != -1
+
   async.waterfall [
     (callback) ->
       async.parallel [
@@ -250,7 +264,7 @@
                   else
                     console.log "#{Date.now() - mdfpstart} to download posts for #{day}"
                     posts = _.filter posts, (post) ->
-                      !(post?.current_user?.voted_for_post || post?.current_user?.commented_on_post)
+                      !(votedFor(req.user, post) || commentedOn(req.user, post))
                     callback null, posts
             ], callback
           daysAgoToSFDay = (i) ->
@@ -271,11 +285,11 @@
         inputses = []
         for post in posts
           inputs =
-            relatedPostUpvotes: _.filter(post.related_posts, (related) -> related?.current_user?.voted_for_post).length
-            relatedPostComments: _.filter(post.related_posts, (related) -> related?.current_user?.commented_on_post).length
+            relatedPostUpvotes: _.filter(post.related_posts, (related) -> votedFor(req.user, related)).length
+            relatedPostComments: _.filter(post.related_posts, (related) -> commentedOn(req.user, related)).length
             followingHunters: _.filter([post.user], (user) -> followings.indexOf(user.id) != -1).length
-            followingUpvotes: _.filter(post.votes, (vote) -> followings.indexOf(vote.user_id) != -1).length
-            followingComments: 0
+            followingUpvotes: _.intersection(followings, voters(post)).length
+            followingComments: _.intersection(followings, commenters(post)).length
             totalUpvotes: post.votes_count
             totalComments: post.comments_count
           inputses.push inputs


prepare for new version

prepare for new version

--- src/index.coffee
+++ src/index.coffee
@@ -2,94 +2,64 @@

 class Oscilloscope

-  # TODO: resize canvas without loosing context options.
-  constructor: (@canvas, options = { }) ->
-    return new Oscilloscope @canvas, options unless this instanceof Oscilloscope
-
-    @signals = []
+  constructor: (source, options = { }) ->
+    return new Oscilloscope source, options unless this instanceof Oscilloscope
+
     @drawRequest = 0

-    # reusable time-domain buffer (waveform data)
-    @timeDomain = new Uint8Array(options.buffer || 1024) # range 32 to 32768
-
-    # setup canvas drawing context
-    @drawContext = @canvas.getContext("2d")
-
-    @drawContext.lineWidth = options.stroke || 2
-    #TODO: can we just multiply right now.
-    @drawContext.shadowBlur = (options.glow || 0) * 100
-
-    # TODO: we may want to avoid this flip in the future.
-    # flip the context to mimic a regular cartesian coordinate system
-    @drawContext.translate 0, @canvas.height
-    @drawContext.scale 1, -1
-
-  # add source signal to the oscilloscope
-  addSignal: (source, color) ->
     unless source instanceof AudioNode
       throw new Error "Signal must be an AudioNode"

     if source instanceof AnalyserNode
       debug "add signal analyser"
-      analyser = source
+      @analyser = source
     else
       debug "add signal source"
-      analyser = source.context.createAnalyser()
-      source.connect analyser
+      @analyser = source.context.createAnalyser()
+      source.connect @analyser

-    # TODO: existing analysers will get fftSize overidden?
-    analyser.fftSize = @timeDomain.length
+    @analyser.fftSize = options.fftSize if options.fftSize;  # range 32 to 32768

-    @signals.push
-      analyser: analyser
-      color: color or "#ffffff"
+    @timeDomain = new Uint8Array(@analyser.frequencyBinCount)
+
+    console.log('fft', @analyser.fftSize)
+    console.log('bincount', @analyser.frequencyBinCount)

-    # auto-start when adding the first signal
-    if @signals.length == 1
-      @start()

-  # TODO: implement this i guess. (auto-stop?)
-  removeSignal: (analyser) ->
-    debug "remove signal"
-    throw new Error "Not implemented."
-
-  # begin signal animation
-  start: ->
+  # todo: make auto start/stop (after babel)
+  # begin default signal animation
+  animate: ->
     return if @drawRequest
     debug "start animation"
     @drawRequest = window.requestAnimationFrame @_drawSignal

-  # stop signal animation
+  # todo: stop signal animation
   stop: ->
     debug "stop animation"
     cancelAnimationFrame @drawRequest
     @drawRequest = 0
-    @drawContext.clearRect 0, 0, @canvas.width, @canvas.height
+    #@drawContext.clearRect 0, 0, @canvas.width, @canvas.height

   # draw signal animation
-  _drawSignal: =>
-    @drawContext.clearRect 0, 0, @canvas.width, @canvas.height
+  draw: (ctx, x0, y0, width, height) =>
+    # TODO: maybe cache values
+    x0 = x0 || 0
+    y0 = y0 || 0
+    width = width || (ctx.canvas.width - x0)
+    height = height || (ctx.canvas.height - y0)

-    step = @canvas.width / @timeDomain.length
-    scopeHeight = @canvas.height / @signals.length
+    step = width / @timeDomain.length

-    for signal, index in @signals
-      @drawContext.beginPath()
+    @analyser.getByteTimeDomainData @timeDomain

-      signal.analyser.getByteTimeDomainData @timeDomain
+    ctx.beginPath()
+    # drawing loop (skipping every second record)
+    for value, i in @timeDomain by 2
+      percent = value / 256
+      x = x0 + i * step
+      y = y0 + height * percent
+      ctx.lineTo x, y

-      # drawing loop (skipping every second record)
-      for value, i in @timeDomain by 2
-        # TODO: recheck this (255/256) and that
-        percent = value / 255
-        x = i * step
-        y = (scopeHeight * percent) + (scopeHeight * index)
-        @drawContext.lineTo x, y
-
-      @drawContext.strokeStyle = signal.color
-      @drawContext.shadowColor = signal.color
-      @drawContext.stroke()
-
-    @drawRequest = window.requestAnimationFrame @_drawSignal
+    ctx.stroke()

 module.exports = Oscilloscope


Some performance fixes

Some performance fixes

--- src/index.coffee
+++ src/index.coffee
@@ -13,6 +13,33 @@
 JSON_TYPE = "application/json"

 router = express.Router()
+
+cache = {}
+cacheGet = (url, token, headers, callback) ->
+  key = "#{token}|#{url}"
+  entry = null
+  if cache[key]
+    entry = cache[key]
+    console.dir entry
+    if entry.etag
+      headers["If-None-Match"] = entry.etag
+  web.get url, headers, (err, response, body) ->
+    if err
+      callback err
+    else if response.statusCode == 304
+      callback null, entry.body
+    else if response.statusCode != 200
+      callback new Error("Bad status code #{response.statusCode} getting posts: #{body}")
+    else
+      key = "#{token}|#{url}"
+      entry =
+        url: url
+        token: token
+        etag: response.headers.etag
+        body: body
+      cache[key] = entry
+      console.dir entry
+      callback null, body

 sortPosts = (db, client, agentID, posts, user, token, callback) ->
   assert.ok _.isObject(db), "#{db} is not an object"
@@ -24,7 +51,13 @@

   async.waterfall [
     (callback) ->
-      db.read "UserFollowing", user.id, callback
+      db.read "UserFollowing", user.id, (err, following) ->
+        if err and err.name == "NoSuchThingError"
+          callback null, []
+        else if err
+          callback err
+        else
+          callback null, following
     (followings, callback) ->
       scorePost = (post, callback) ->
         inputs =
@@ -42,7 +75,7 @@
           else
             post.score = outputs.score
             callback null, post
-      async.map posts, scorePost, (err, scored) ->
+      async.mapLimit posts, 4, scorePost, (err, scored) ->
         if err
           callback err
         else
@@ -149,11 +182,9 @@
           "Accept": JSON_TYPE
           "Authorization": "Bearer #{token}"
         url = "https://api.producthunt.com/v1/posts"
-        web.get url, headers, (err, response, body) ->
+        cacheGet url, token, headers, (err, body) ->
           if err
             callback err
-          else if response.statusCode != 200
-            callback new Error("Bad status code #{response.statusCode} getting posts: #{body}")
           else
             results = JSON.parse(body)
             now = Date.now()
@@ -167,11 +198,9 @@
             "Accept": JSON_TYPE
             "Authorization": "Bearer #{token}"
           url = "https://api.producthunt.com/v1/posts/#{post.id}"
-          web.get url, headers, (err, response, body) ->
+          cacheGet url, token, headers, (err, body) ->
             if err
               callback err
-            else if response.statusCode != 200
-              callback new Error("Bad status code #{response.statusCode} getting posts: #{body}")
             else
               results = JSON.parse(body)
               callback null, results.post


update menu name

update menu name

--- menus/speck-runner.cson
+++ menus/speck-runner.cson
@@ -10,7 +10,7 @@
   {
     'label': 'Packages'
     'submenu': [
-      'label': 'speck-runner'
+      'label': 'Speck Runner'
       'submenu': [
         {
           'label': 'Run'


test: add ttl and value swap test

test: add ttl and value swap test

--- _src/test/mocha_test.coffee
+++ _src/test/mocha_test.coffee
@@ -1252,6 +1252,11 @@
 				'foo'.should.eql localCache.fetch( 'key', 100, state.func )
 				'foo'.should.eql localCache.get( 'key' )
 				return
+
+		context 'when ttl is omitted', () ->
+		  it 'swap ttl and value', () ->
+			  'foo'.should.eql localCache.fetch( 'key', state.func )
+				return

 	describe "Issues", () ->
 		describe("#151 - cannot set null", () ->


framerate gets a facelift

framerate gets a facelift

--- lib/assets/javascripts/jax/builtin/models/framerate.js.coffee
+++ lib/assets/javascripts/jax/builtin/models/framerate.js.coffee
@@ -19,7 +19,7 @@
 pixel coordinates instead of world units and is displayed with an
 orthographic (essentially 2D) projection.

-The framerate is capable of two modes of operation
+The framerate is capable of two modes of operation:

 * EMA - calculates an exponential moving average of the framerate based on
   the `ema` option, producing a smooth line on the graph, allowing you to
@@ -33,19 +33,26 @@
   the JavaScript garbage collector is run.

 You can also get the current framerate any time after the framerate has been
-added to the world by calling `fps` or `ups`.
+added to the world by calling `fps`.

 ###
 class Jax.Framerate extends Jax.Model
   drawPath = (ctx, points, height, current)->
     ctx.beginPath()
-    ctx.moveTo 0, height - points[0] * height
+    ctx.moveTo 0, clamp height - points[0] * height, 0, height
     for i in [0..current]
-      ctx.lineTo i, height - points[i] * height
+      ctx.lineTo i, clamp height - points[i] * height, 0, height
     ctx.stroke()
     ctx.closePath()
+
+  clamp = (val, min, max) ->
+    if val < min then min
+    else if val > max then max
+    else val

   constructor: (options = {}) ->
+    options.stroke or= "rgba(0, 0, 0, 255)"
+    options.fill or= "rgba(255, 0, 255, 255)"
     options.width or= 128
     options.height or= 64
     # Instead of disabling depth tests, we'll just position it in ortho
@@ -56,31 +63,30 @@
     options.castShadow or= false
     options.receiveShadow or= false
     options.illuminated or= false
+    options.fontHeight or= 12
     super options

     @canvas = document.createElement 'canvas'
     @canvas.width  = @width
     @canvas.height = @height
     @ctx = @canvas.getContext '2d'
-    @ctx.font      = @font or= "10pt Arial"
-
-    @fps_points = []
-    @ups_points = []
+    @ctx.font      = @font or= "#{@fontHeight}px Arial"
+
+    @history = []
     @current = -1
-    @max_fps = @max_ups = 100
+    @max_fps = 100
     @ema = 30 if @ema is undefined

     # cache some variables for use during update
     @ema_exponent = 2 / (@ema + 1)
-    @_max_data_width = @width / 3
-    @_fps_label_left = 0
-    @_ups_label_left = @width / 3 + @width / 6
-    @_one_quarter_height = @height * 0.25
-    @_two_quarter_height = @height * 0.5
+    @_max_data_width       = @width / 3
+    @_fps_label_left       = 0
+    @_one_quarter_height   = @height * 0.25
+    @_two_quarter_height   = @height * 0.5
     @_three_quarter_height = @height * 0.75
-    @_12_pcnt_height = @height * 0.12
-    @_marker_offset = @width - @_12_pcnt_height
-    @_max_queue_size = Math.round(@width - @_12_pcnt_height)
+    @_12_pcnt_height       = @height * 0.12
+    @_marker_offset        = @width - @_12_pcnt_height
+    @_max_queue_size       = Math.round(@width - @_12_pcnt_height)

     @glTex = new Jax.Texture
       width: @width
@@ -88,58 +94,65 @@
       mag_filter: GL_LINEAR
       min_filter: GL_LINEAR
       flip_y: true
+      wrap_s: GL_CLAMP_TO_EDGE
+      wrap_t: GL_CLAMP_TO_EDGE

     @glTex.image = @canvas
     @mesh = new Jax.Mesh.Quad
       width: @width
       height: @height
-      color: [1, 1, 1, 0.5]
-      material: new Jax.Material(layers: [ { type: 'Position' }, { type: 'Texture', instance: @glTex } ])
-
+      color: [1, 1, 1, 1]
+      transparent: true
+      material: new Jax.Material.Custom
+        layers: [
+          { type: 'Position' },
+          { type: 'VertexColor' },
+          { type: 'Texture', instance: @glTex }
+        ]
+
   render: (context, material) ->
     @fps = context.getFramesPerSecond()
-    fps_pcnt = @fps / @max_fps
+    fpsPcnt = @fps / @max_fps

-    if fps_pcnt == Number.POSITIVE_INFINITY then fps_pcnt = 1000
+    if @fps is Number.POSITIVE_INFINITY then @fps = fpsPcnt = 0

     if @current == @_max_queue_size
       for i in [0..@current]
-        @fps_points[i] = @fps_points[i+1]
+        @history[i] = @history[i+1]
     else @current++

     if @ema
       # calculate exponential moving average
       if @current == 0
-        fema = 0
-        uema = 0
+        curEMA = 0
       else
-        fema = @fps_points[@current-1]
-      @fps_points[@current] = (fps_pcnt * @ema_exponent) + (fema * (1 - @ema_exponent))
+        curEMA = @history[@current-1]
+      @history[@current] = curPcnt = (fpsPcnt * @ema_exponent) + (curEMA * (1 - @ema_exponent))
     else
-      @fps_points[@current] = fps_pcnt
+      @history[@current] = curPcnt = fpsPcnt

     # clear the graph
     @ctx.clearRect 0, 0, @width, @height
-    @ctx.fillStyle = "rgba(64, 64, 64, 0.7)"
-    @ctx.fillRect 0, 0, @width, @height
-    @ctx.textBaseline = "middle"
-    @ctx.fillStyle = "rgba(0, 32, 0, 1)"
-    @ctx.fillText "25", @_marker_offset, @_three_quarter_height, @_12_pcnt_height
-    @ctx.fillStyle = "rgba(0, 32, 0, 1)" # why do I have to do this again?
-    @ctx.fillText "50", @_marker_offset, @_two_quarter_height, @_12_pcnt_height
-    @ctx.fillStyle = "rgba(0, 32, 0, 1)"
-    @ctx.fillText "75", @_marker_offset, @_one_quarter_height, @_12_pcnt_height

     if !@ema or @current >= @ema
-      # FPS or FPS EMA
-      @ctx.strokeStyle = "red"
-      drawPath @ctx, @fps_points, @height, @current
-
-      @ctx.textBaseline = "bottom"
-      @ctx.fillStyle = "red"
-      @ctx.fillText "#{Math.round @fps} FPS", @_fps_label_left, @height, @_max_data_width
+      @ctx.strokeStyle = @fill
+      drawPath @ctx, @history, @height, @current
+
+      @ctx.textBaseline = "center"
+
+      x = @width - @fontHeight * 4.25
+      y = clamp @height - (curPcnt * @height + 5), @fontHeight, @height
+
+      @ctx.strokeStyle = @stroke
+      @ctx.strokeText "#{Math.round @fps} FPS", x, y
+
+      @ctx.fillStyle = @fill
+      @ctx.fillText "#{Math.round @fps} FPS", x, y
     else
-      @ctx.fillStyle = "rgba(0, 0, 0, 1)"
+      @ctx.strokeStyle = @stroke
+      @ctx.fillText "Gathering data...", 10, @height / 2, @width - 20
+
+      @ctx.fillStyle = "rgba(128, 128, 128, 255)"
       @ctx.fillText "Gathering data...", 10, @height / 2, @width - 20

     @glTex.refresh context


Fix remove paired secure screen, bad implementation

--- app/src/m2fa/paired_secure_screen.coffee
+++ app/src/m2fa/paired_secure_screen.coffee
@@ -115,8 +115,8 @@
   @removePairedSecureScreensFromSyncedStore: (screens, callback) -> @removePairedSecureScreensFromStore(ledger.storage.sync, screens, callback)

   @removePairedSecureScreensByUuidFromStore: (store, uuid, callback = _.noop) ->
-    @getScreensByUuidFromStore store, uuid, (screens, error) ->
+    @getScreensByUuidFromStore store, uuid, (screens, error) =>
       return callback(null, error) if error?
-      @removePairedSecureScreensByUuidFromStore(store, screens, callback)
+      @removePairedSecureScreensFromStore(store, screens, callback)

   @removePairedSecureScreensByUuidFromSyncedStore: (uuid, callback = _.noop) -> @removePairedSecureScreensByUuidFromStore(ledger.storage.sync, uuid, callback)


bug fix

bug fix

--- app/assets/javascripts/downloads.js.coffee
+++ app/assets/javascripts/downloads.js.coffee
@@ -2,16 +2,16 @@
   # model
   codes = []

-  load_codes ->
+  load_codes = ->
     codes = JSON.parse(localStorage.getItem("codes")) || []

-  save_codes ->
+  save_codes = ->
     localStorage.setItem("codes", JSON.stringify(codes))

   # View
   render = ->
     tbody = $("#rows")
-    tbody.empty
+    tbody.empty()
     for code in codes
       tr = $("

")
       tbody.append(tr)
@@ -29,8 +29,8 @@
     $("#code_field").val("")
     if /^\d\d\d\d$/.test(code)
       codes.push(code)
-      save_codes
-      render
+      save_codes()
+      render()
     else
       alert("コードは4桁の整数で入力してください")

@@ -40,8 +40,8 @@
       if codes[i] == code
         codes.splice(i, 1)
         break
-    save_codes
-    render
+    save_codes()
+    render()

   # コード追加イベントハンドラ
   $("#add_code").on "click", add_code
@@ -49,7 +49,7 @@
     alert(ev)

   # initialize
-  load_codes
-  render
+  load_codes()
+  render()


create a dummy model field so that a validation group is always enabled

Without a validation group, every validation is turned on, so every field is validated. This makes steps that don't have fields (ex: they only have buttons) fail because they don't send any model fields. This fixes that by creating a dummy model field when it doesn't see any already there.

--- app/assets/javascripts/hat-trick.js.coffee
+++ app/assets/javascripts/hat-trick.js.coffee
@@ -13,6 +13,7 @@
     this.enableFormwizard()
     this.setupButtonsForCurrentStep()
     this.setCurrentStepField()
+    this.createDummyModelField() unless this.currentStepHasModelFields()
     this.bindEvents()

   linkClass: "_ht_link"
@@ -297,9 +298,19 @@
         stepId = currentStep.fieldset
         this.buttons[stepId] = currentStep.buttons

+  modelName: ->
+    hatTrick.model['__name__']
+
+  createDummyModelField: ->
+    this.setHiddenInput "#{this.modelName()}[_dummy]", "1"
+
+  currentStepHasModelFields: ->
+    this.currentStep().find("input[name^='#{this.modelName()}[']").length > 0
+
   updateStepFromMetadata: ->
     if hatTrick.metadata?.currentStep?
       this.setButtonMetadataForCurrentStep()
+      this.createDummyModelField() unless this.currentStepHasModelFields()

       currentStep = hatTrick.metadata.currentStep
       if currentStep.repeatOf?


Fix: Prevent multiple menus..

Fix: Prevent multiple menus..

--- app/assets/javascripts/web/_site.js.coffee
+++ app/assets/javascripts/web/_site.js.coffee
@@ -1352,11 +1352,7 @@

   preloadedImageIds: []

-  linkActionsTimer: null
-
-  linkCacheTimer: null
-
-  linkMenuTimer: null
+  linkActions: {}

   ONE_HOUR: 60 * 60 * 1000

@@ -2261,22 +2257,31 @@
       $(document).on 'mouseenter mouseleave', 'body:not(.touch) .entry-final-content a', (event) ->
         link = $(@)
         if link.text().trim().length > 0 && !$(@).has('.mejs__container').length > 0 && !link.closest(".system-content").length
-          clearTimeout(feedbin.linkActionsTimer)
-          clearTimeout(feedbin.linkCacheTimer)
-          clearTimeout(feedbin.linkMenuTimer)
+          url = link.attr('href')
+
+          unless url of feedbin.linkActions
+            feedbin.linkActions[url] =
+              linkActionsTimer: null
+              linkCacheTimer: null
+              linkMenuTimer: null
+
+          clearTimeout(feedbin.linkActions[url].linkActionsTimer)
+          clearTimeout(feedbin.linkActions[url].linkCacheTimer)
+          clearTimeout(feedbin.linkActions[url].linkMenuTimer)

           if event.type == "mouseleave"
-            feedbin.linkMenuTimer = setTimeout ( ->
+            feedbin.linkActions[url].linkMenuTimer = setTimeout ( ->
               $('.entry-final-content a [data-behavior~=link_actions]').remove()
+              delete feedbin.linkActions[url]
             ), 350

           if event.type == "mouseenter"
-            feedbin.linkCacheTimer = setTimeout ( ->
+            feedbin.linkActions[url].linkCacheTimer = setTimeout ( ->
               form = $("[data-behavior~=extract_cache_form]")
-              $("#url", form).val(link.attr('href'))
+              $("#url", form).val(url)
               form.submit()
             ), 100
-            feedbin.linkActionsTimer = setTimeout ( ->
+            feedbin.linkActions[url].linkActionsTimer = setTimeout ( ->
               actionsVisible = link.find('[data-behavior~=link_actions]').length > 0
               if !actionsVisible
                 contents = $('[data-behavior~=link_actions]').clone()


[backport] Fixed more analytics bugs

[backport] Fixed more analytics bugs

--- app/coffee/modules/common/analytics.coffee
+++ app/coffee/modules/common/analytics.coffee
@@ -144,20 +144,6 @@
             position++
         @.trackEvent("ecommerce", "list-plans")

-    addEcClickProduct: (plan) ->
-        return if not @.initialized
-        return if not @win.ga
-
-        @win.ga('ec:addProduct', {
-            'id': plan.plan_id,
-            'name': plan.name,
-            'category': "plans",
-            'quantity': 1,
-            'position': 1,
-        })
-        @win.ga('ec:setAction','click')
-        @.trackEvent("ecommerce", "add-click", plan.name, plan.plan_id)
-
     ecAddToCart: (plan_id, plan_name, plan_price) ->
         return if not @.initialized
         return if not @win.ga
@@ -185,13 +171,13 @@
             'quantity': 1,
             'position': 1,
         })
-        @.addEcStep("confirm-plan")
+        @.ecStep("confirm-plan")

     ecPurchase: (plan_id, plan_name, plan_price) ->
         return if not @.initialized
         return if not @win.ga

-        @.addEcStep("plan-changed")
+        @.ecStep("plan-changed")

         @win.ga('ec:addProduct', {
             'id': plan_id,


Add working routes.

Add working routes.

--- javascript/inner.coffee
+++ javascript/inner.coffee
@@ -96,4 +96,13 @@

 window.dashboardViewModel = new DashboardViewModel()
-ko.applyBindings window.dashboardViewModel+ko.applyBindings window.dashboardViewModel
+
+Sammy('.container-fluid', () ->
+  @get('/', =>)
+
+  @get('/gh/:username/:project', (cx) ->
+    alert(cx.params.username)
+    alert(cx.params.project)
+  )
+).run('/')


Fix an error in get service of the issues resource

--- app/coffee/modules/resources/issues.coffee
+++ app/coffee/modules/resources/issues.coffee
@@ -26,7 +26,8 @@
     service = {}

     service.get = (projectId, issueId) ->
-        return $repo.queryOne("issues", issueId)
+        params = {project: projectId}
+        return $repo.queryOne("issues", issueId, params)

     service.list = (projectId, filters) ->
         params = {project: projectId}


StaticPage: Add contentType parameter to opening and closing tags methods

--- server/lib/crawler/staticpages/feed.coffee
+++ server/lib/crawler/staticpages/feed.coffee
@@ -8,7 +8,7 @@

 createFeed = (models, options, callback)->
   {JAccount, SocialChannel} = models
-  {page, channelId, client, route} = options
+  {page, channelId, client, route, contentType} = options

   return callback "channelId not set"  unless channelId

@@ -32,8 +32,8 @@
       options.page = page
       buildContent models, result.messageList, options, (err, pageContent) ->
         return callback err  if err
-        schemaorgTagsOpening = getSchemaOpeningTags()
-        schemaorgTagsClosing = getSchemaClosingTags()
+        schemaorgTagsOpening = getSchemaOpeningTags contentType
+        schemaorgTagsClosing = getSchemaClosingTags contentType

         content = schemaorgTagsOpening + pageContent + schemaorgTagsClosing

@@ -124,17 +124,31 @@
   queue.pageContent += createTagNode tag
   queue.next()

-getSchemaOpeningTags = =>
-  return """
-

+getSchemaOpeningTags = (contentType) ->
+  openingTag = ""
+  title = ""
+  switch contentType
+    when "post"
+      title = "activities"
+      openingTag += """
"""
+    when "topic"
+      title = "topics"
+
+  openingTag +=
+      """

             <meta itemprop="mainContentOfPage" content="true"/>
-
Latest activities

+
Latest #{title}

             <meta itemprop="itemListOrder" content="Descending" />
       """

-getSchemaClosingTags = =>
-  return closingTags = "

"
+  return openingTag
+
+getSchemaClosingTags = (contentType) ->
+  closingTag = "
"
+  closingTag += "
"  if contentType is "post"
+
+  return closingTag

 createTagNode = (tag)->
   tagContent = ""


Updates spec

Updates spec

--- apps/partner/test/client/overview.coffee
+++ apps/partner/test/client/overview.coffee
@@ -73,7 +73,7 @@
     describe '#renderArtistsGrid', ->

       it 'fetches all the partner artists and renders them in grid', ->
-        @partnerArtists.fetchUntilEnd = (options) =>
+        @partnerArtists.fetchUntilEndInParallel = (options) =>
           @partnerArtists.add @pas
           options.success?()
         @view.initializeArtists()
@@ -99,7 +99,7 @@
         )

         @partnerArtists = new PartnerArtists()
-        @partnerArtists.fetchUntilEnd = (options) => options?.success?()
+        @partnerArtists.fetchUntilEndInParallel = (options) => options?.success?()
         @PartnerArtistsCollection = sinon.stub()
         @PartnerArtistsCollection.returns @partnerArtists
         mod.__set__ 'PartnerArtists', @PartnerArtistsCollection


chore(Gruntfile): Fixed grunt bump not tagging changelog correctly

--- misc/grunt/tasks.coffee
+++ misc/grunt/tasks.coffee
@@ -134,9 +134,12 @@

       pkg         = grunt.config.get("pkg")
       pkg.version = newVersion
+      grunt.config "pkg", pkg
       grunt.file.write "package.json", JSON.stringify(pkg, null, "  "), encoding: "utf8"

       grunt.task.run "changelog"
+
+      done()

     if version?
       writeAndChangelog version[1..]
@@ -152,8 +155,6 @@

         writeAndChangelog data[1..]

-        done()
-
   ###
   @name changelog
   @description


Add zendesk link and hide error details by default

--- src/concept-coach/error-notification.cjsx
+++ src/concept-coach/error-notification.cjsx
@@ -7,7 +7,7 @@
 ErrorNotification = React.createClass

   getInitialState: ->
-    error: false
+    error: false, isShowingDetails: false

   componentWillMount: ->
     api.channel.on 'error', @onError
@@ -26,17 +26,35 @@
         )
     @setState(errors: errors)

+  toggleDetails: ->
+    @setState(isShowingDetails: not @state.isShowingDetails)
+
   onHide: ->
     @setState(errors: false)
+
+  renderDetails: ->
+    <BS.Panel header="Error Details">
+

+        {for error, i in @state.errors
+
{error}
}
+

+    </BS.Panel>

   render: ->
     return null unless @state.errors
     <BS.Modal className='errors' onRequestHide={@onHide} title="Server Error">

-

-          {for error in @state.errors
-
{error}
}
-

+
Server error encountered

+

+          An unexpected error has occured.  Please
+          visit  our support site  so we can help to diagnose and correct the issue.
+

+        <BS.Button onClick={@toggleDetails}>
+          {if @state.isShowingDetails then "Hide" else "Show"} Details
+        </BS.Button>
+        {@renderDetails() if @state.isShowingDetails}

         <BS.Button className='ok' bsStyle='primary' onClick={@onHide}>OK</BS.Button>


Fix enabling the input for the msg when inviting people to a space

refs #1769

--- app/assets/javascripts/app/join_requests/invite.js.coffee
+++ app/assets/javascripts/app/join_requests/invite.js.coffee
@@ -36,6 +36,9 @@
         results: (data, page) -> # parse the results into the format expected by Select2.
           results: data

+# Enable the message unless there is an option to add people and it is selected.
+# Covers the case when there's no option to add people, when the 'invite people' is the default.
 enableDisableMessage = ->
-  typeInvite = $('#type_invite')
-  $('#join_request_comment').enable(typeInvite.is(":checked"))
+  typeAdd = $('#type_add')
+  selected = !(typeAdd.is(":visible") && typeAdd.is(":checked"))
+  $('#join_request_comment').enable(selected)


Validate Trade form's % of your total overseas trade adds up to 100

--- app/assets/javascripts/frontend/form-validation.js.coffee
+++ app/assets/javascripts/frontend/form-validation.js.coffee
@@ -351,6 +351,21 @@
       @appendMessage(question, "You need to request or upload at least 2 letters of support")
       @addErrorClass(question)

+  validateGoodsServicesPercentage: (question) ->
+    totalOverseasTradeInputs = question.find(".js-by-trade-goods-and-services-amount .show-question input[type='text']")
+    totalOverseasTradePercentage = 0
+    missingOverseasTradeValue = false
+    totalOverseasTradeInputs.each ->
+      if $(this).val().toString().trim().length
+        totalOverseasTradePercentage += parseInt $(this).val()
+      else
+        missingOverseasTradeValue = true
+    if !missingOverseasTradeValue
+      if totalOverseasTradePercentage != 100
+        @log_this(question, "validateGoodsServicesPercentage", "% of your total overseas trade should add up to 100")
+        @appendMessage(question, "% of your total overseas trade should add up to 100")
+        @addErrorClass(question)
+
   # It's for easy debug of validation errors
   # As it really tricky to find out the validation which blocks form
   # and do not display any error massage on form
@@ -435,6 +450,10 @@
         # console.log "validateSupportLetters"
         @validateSupportLetters(question)

+      if question.find(".js-by-trade-goods-and-services-amount").length
+        # console.log "validateGoodsServicesPercentage"
+        @validateGoodsServicesPercentage(question)
+
       #console.log @validates

     return @validates


fixing add kudo

fixing add kudo

--- app/assets/javascripts/routes/application_route.js.coffee
+++ app/assets/javascripts/routes/application_route.js.coffee
@@ -27,7 +27,7 @@
         if data.status isnt 'error'
           currentUserCon.decrementKudos kudoNum
           newKudo = Sks.KudoReceived.createRecord value: kudoNum, comment: kudoComment
-          #user.get('kudoReceived').pushObject(newKudo)
+          user.get('kudoReceiveds').pushObject(newKudo)
           showFlash 'alert-success', 'You\'ve added a kudo!'
         else
           showFlash 'alert-error', 'Oops! An error occured!'


one more try

one more try

--- app/controllers/home.coffee
+++ app/controllers/home.coffee
@@ -16,16 +16,19 @@
   router.get '/current', (req, res) ->
     res.json
       frames: [
-        index: 0
-        text:  req.param("zip-code")
-        icon: "i120"
-        ,
-        index: 1
-        text:  req.param("zip-code")
-        icon: "i120"
-        ,
-        c : req.header("accept")
-        d : req.headers
-        e : req.params
+        {
+          index: 0
+          text: req.param("zip-code")
+          icon: "i120"
+        },
+        {
+          index: 1
+          text:  req.param("zip-code")
+          icon: "i120"
+
+          c: req.header("accept")
+          d: req.headers
+          e: req.params
+        }
       ]


debug info

debug info

--- app/controllers/home.coffee
+++ app/controllers/home.coffee
@@ -17,4 +17,6 @@
     res.json
       a : 1
       b : req.param("zip-code")
+      c : req.header("accept")
+      d : req


Notes should end up in the right places if the notebook id changes

--- app/controllers/sync.coffee
+++ app/controllers/sync.coffee
@@ -363,6 +363,20 @@
               # reindex
               resultantindex = indexer(resultant)

+    # Er, well if notebook name id changes we have to change note ids
+    # @consindo is an idiot. Please send him angry messages.
+    # This code is also annoying because it's not a keyvalue pair. That's what happens if you loop try to dry.
+    # So, we'll build an index
+    notebooknamechangesindex = {}
+    for item in namechanges.Notebook
+      notebooknamechangesindex[item[0]] = item[1]
+
+    # Simple, right? Now we loop throught the notes and change the ids
+    for k, v of resultant.Note
+      if notebooknamechangesindex[v.notebook] isnt undefined
+        v.notebook = notebooknamechangesindex[v.notebook]
+        resultant.Note[k] = v
+
     # All the changes from the client should be added at this point
     # Now we detect the differences between the server copy and our client.


Add group to start thread modal launched from group page

--- lineman/app/components/discussion_form/discussion_form.coffee
+++ lineman/app/components/discussion_form/discussion_form.coffee
@@ -3,7 +3,7 @@
   controller: ($scope, $controller, $location, discussion, CurrentUser, Records, AbilityService, FormService, KeyEventService) ->
     $scope.discussion = discussion.clone()

-    if $scope.discussion.isNew() and !$scope.discussion.groupId?
+    if $scope.discussion.isNew()
       $scope.showGroupSelect = true

     $scope.$on 'modal.closing', (event) ->


Don't show the loading section if we 404 on the /users API call.

This sets a an 'error' callback for the $.ajax request to /users that
forces the invitation form to redraw when the request fails.

When the invitation form redraws and sees that github_users and
github_users_not_following are null and loading_github_users is false,
it doesn't draw anything.

Partially fixes #2070.

--- js/inner/project.coffee
+++ js/inner/project.coffee
@@ -246,11 +246,15 @@
             success: (results) =>
               github_users ((new CI.inner.GithubUser result) for result in results)
               @loading_github_users false
+            error: () =>
+              github_users null
+              @loading_github_users false
         github_users()

     @github_users_not_following = @komp
       deferEvaluation: true
-      read: => (user for user in @github_users() when not user.following())
+      read: => if @github_users()
+        (user for user in @github_users() when not user.following())

   @sidebarSort: (l, r) ->
     if l.followed() and r.followed() and l.latest_build()? and r.latest_build()?


Start of documentation

Start of documentation

--- app/scripts/lib/script_modules/core.coffee
+++ app/scripts/lib/script_modules/core.coffee
@@ -5,16 +5,35 @@
   _verify: (sequence) ->
     throw new Error('sequence mismatch') unless sequence is @currentSequence

+  # Runs a sequence of steps:
+  # example:
+  #   @sequence(
+  #     @moveTo(x: 30)
+  #     @moveTo(y: 50)
+  #   )
   sequence: (tasks...) ->
     (sequence) =>
       @_verify(sequence)
       WhenJS.sequence(tasks, sequence)

+  # Runs steps in parallel, and completes when the last branch has finished
+  # example:
+  #   @parallel(
+  #     @placeSquad Game.Scripts.EnemyType1
+  #     @placeSquad Game.Scripts.EnemyType2
+  #   )
   parallel: (tasks...) ->
     (sequence) =>
       @_verify(sequence)
       WhenJS.parallel(tasks, sequence)

+  # Executes step conditionally
+  # example:
+  #   @if((=> Math.random() > 0.5),
+  #     @sequence(...)
+  #   # else
+  #     @sequence(...)
+  #   )
   if: (condition, block, elseBlock) ->
     (sequence) =>
       @_verify(sequence)


Remove a console.log

Remove a console.log

--- services/web/public/coffee/directives/expandableTextArea.coffee
+++ services/web/public/coffee/directives/expandableTextArea.coffee
@@ -5,7 +5,6 @@
 		restrict: "A"
 		link: (scope, el) ->
 			resetHeight = () ->
-				console.log el.val()
 				el.css("height", "auto")
 				el.css("height", el.prop("scrollHeight"))


use domain for api url in js

use domain for api url in js

--- app/assets/javascripts/application.coffee
+++ app/assets/javascripts/application.coffee
@@ -35,7 +35,7 @@
 #= require blank
 #= require timesheet

-window.ApiBasePath = "//api.#{window.location.host}"
+window.ApiBasePath = "//api.#{document.domain}"

 $(document).on 'show.bs.collapse', '.navbar-collapse', ->
   $('.navbar-collapse.in').not(this).collapse('hide')


Fixed comments bug.

Fixed comments bug.

Signed-off-by: Philip Vieira <3ba3974e96044dcde818e6310ed799abe10671d4@vallin.se>

--- app/assets/javascripts/articles.js.coffee
+++ app/assets/javascripts/articles.js.coffee
@@ -1,10 +1,11 @@
 #= require helpers/comments

+$(document).bind 'show_articles.load', (e,obj) =>
+
+  $('form#new_comment').commentForm()
+  $('#article_content').BetterGrow()
+
 $(document).bind 'new_articles.load edit_articles.load', (e,obj) =>
-
-  $('form#new_comment').commentForm()
-
-  $('#article_content').BetterGrow()

   $('#article_tag_tokens').tokenInput "/tags/search.json",
       crossDomain: false


Capped to project end date for calculation of completion rate for burndown

--- app/assets/javascripts/burndown.js.coffee
+++ app/assets/javascripts/burndown.js.coffee
@@ -104,7 +104,8 @@
 projectedCompletionData = (project) ->
   startDate   = moment(project.project_template.start_date)
   endDate     = moment(project.project_template.end_date)
-  currentWeek = moment().diff(startDate, 'weeks')
+  currentDate = if moment() > endDate then endDate else moment()
+  currentWeek = currentDate.diff(startDate, 'weeks')
   cutOffDate  = if moment() > endDate then endDate else moment()
   endWeek     = cutOffDate.diff startDate, 'weeks'


Fix to remove delete button at create comment modal.

--- app/assets/javascripts/undersky.js.coffee
+++ app/assets/javascripts/undersky.js.coffee
@@ -280,6 +280,7 @@
         if comments_data.size() > 0
           comments = $('

')
           comments.append(comments_data.clone())
+          comments.find('.comments-button.delete-comment').remove()
           form.append(comments)
         container.append(form)
         container.modal(show: true)


Fix bugs in file progress

Fix bugs in file progress

--- src/model.coffee
+++ src/model.coffee
@@ -264,14 +264,16 @@
   # Execute a file command with the given parameters
   fileExec: (request, rel, params, progress, callback) ->
     if typeof params is 'function'
-      callback = progressEvent
-      progressEvent = params
+      callback = progress
+      progress = params
       params = undefined
+
     progressHook = (xhr, data) ->
       if data instanceof FormData
-        xhr.upload.addEventListener 'progress', (e) ->
+        xhr.upload.onprogress = (e) ->
           if e.lengthComputable
             progress {loaded: e.loaded, total: e.total}
+
     request.hook progressHook
     @_request(request, 'post', rel, params, 'items')
     .callback callback


change borders to div elements

change borders to div elements

--- app/lib/canvas-graph.coffee
+++ app/lib/canvas-graph.coffee
@@ -80,18 +80,21 @@
     @element.className = "mark"

     @element.innerHTML = """
+

-

-

+

+

+

+

+

+

     """

     @element.style.left = @pointerXInElement(e) + "px"
     @element.style.position = 'absolute'
     @element.style.top = e.target.offsetTop + "px"
-    @element.style.height = @canvas.height - 15 + 'px' # subtract vertical borders
+    @element.style.height = @canvas.height + 'px'
     @element.style.backgroundColor = 'rgba(255,0,0,.5)'
-    @element.style.border =  '2px solid red'
-    @element.style.borderTop =  '13px solid red'
     @element.style.pointerEvents = 'auto'
     @element.style.textAlign = 'center'


add comment

add comment

--- app/lib/canvas-graph.coffee
+++ app/lib/canvas-graph.coffee
@@ -179,7 +179,7 @@
           y = -y + @canvas.height # flip y-values
           # @ctx.beginPath()
           @ctx.fillStyle = "rgba(252, 69, 65, 1.0)" #"#fc4541"
-          continue if x-sliderOffset < 60
+          continue if x-sliderOffset < 60 # skip if overlap with y-axis
           @ctx.fillRect(x-sliderOffset,y,2,2)

   plotPoints: (xMin = @smallestX, xMax = @largestX, yMin = @smallestY, yMax = @largestY) ->


ignore negative (transit-like) outliers

ignore negative (transit-like) outliers

--- app/lib/canvas-graph.coffee
+++ app/lib/canvas-graph.coffee
@@ -138,7 +138,7 @@
     std = @std(data.y)

     for y, i in [data.y...]
-      continue if Math.sqrt( Math.pow( y - mean, 2 ) ) > nsigma * std # skip outlier
+      continue if (y - mean) > (nsigma * std) # skip outlier
       data_new.x.push data.x[i]
       data_new.y.push data.y[i]


add lightcurve highlighting function

add lightcurve highlighting function

--- app/lib/canvas-graph.coffee
+++ app/lib/canvas-graph.coffee
@@ -102,6 +102,15 @@
       sum = sum + value
     return sum / data.length

+  highlightCurve: (xLeft,xRight) ->
+    for i in [0...@dataLength]
+      if @data.x[i] >= xLeft and @data.x[i] <= xRight
+        x = ((+@data.x[i]-@xMin)/(@xMax-@xMin)) * @canvas.width
+        y = ((+@data.y[i]-@yMin)/(@yMax-@yMin)) * @canvas.height
+        y = -y + @canvas.height # flip y-values
+        @ctx.fillStyle = "#fc4541"
+        @ctx.fillRect(x,y,2,2)
+
   plotPoints: (xMin = @smallestX, xMax = @largestX, yMin = @smallestY, yMax = @largestY) ->
     @xMin = xMin
     @xMax = xMax
@@ -128,6 +137,9 @@
         mark.element.style.width = (scaledMax-scaledMin) + "px"
         mark.element.style.left = (scaledMin) + "px"
         mark.save(scaledMin, scaledMax)
+
+    # @highlightCurve(10,14) # test
+

     @scale = (@largestX - @smallestX) / (@xMax - @xMin)


only handle error thrown by the eventSourcable

Otherwise we wrap the whole saving / publishing
process which results in errors even worse.

--- source/server/infrastructure/router.coffee
+++ source/server/infrastructure/router.coffee
@@ -54,12 +54,12 @@
       @eventBus.subscribeTo @initializingMessage, (event) =>
         @log.info("#{this}: Creating new #{@eventSourceable} with event
                   #{event.typeName()}\n", event)
-        @_handleDomainErrors -> @repository.save new @eventSourceable(event)
+        @repository.save(@_handleDomainErrors -> new @eventSourceable(event))
     else if @initializingMessage.isSubclassOf(Space.messaging.Command)
       @commandBus.registerHandler @initializingMessage, (cmd) =>
         @log.info("#{this}: Creating new #{@eventSourceable} with command
                   #{cmd.typeName()}\n", cmd)
-        @_handleDomainErrors -> @repository.save new @eventSourceable(cmd)
+        @repository.save(@_handleDomainErrors -> new @eventSourceable(cmd))

   _routeEventToEventSourceable: (eventType) ->
     @eventBus.subscribeTo eventType, @_genericEventHandler
@@ -75,7 +75,7 @@
                        #{@eventSourceable}<#{correlationId}>\n"), event)
     eventSourceable = @repository.find @eventSourceable, correlationId
     throw Router.ERRORS.cannotHandleMessage(event) if !eventSourceable?
-    @_handleDomainErrors -> @repository.save eventSourceable.handle(event)
+    @repository.save(@_handleDomainErrors -> eventSourceable.handle(event))

   _genericCommandHandler: (command) =>
     if not command? then return
@@ -83,13 +83,13 @@
                        #{@eventSourceable}<#{command.targetId}>"), command)
     eventSourceable = @repository.find @eventSourceable, command.targetId
     throw Router.ERRORS.cannotHandleMessage(command) if !eventSourceable?
-    @_handleDomainErrors -> @repository.save eventSourceable.handle(command)
+    @repository.save(@_handleDomainErrors -> eventSourceable.handle(command))

   _logMsg: (message) -> "#{@configuration.appId}: #{this}: #{message}"

   _handleDomainErrors: (fn) ->
     try
-      fn.call(this)
+      return fn.call(this)
     catch error
       this.publish(new Space.domain.Exception({
         thrower: @eventSourceable.toString(),


whitespace

whitespace

--- src/monad.coffee
+++ src/monad.coffee
@@ -5,7 +5,7 @@

 # Monads are awesome!
 TMonad = (value) -> Trait
-  unit : Trait.required                               # Constructable!
+  unit : Trait.required                              # Constructable!
   bind : (fn) -> fn.call this, value                  # Bindable!
   toString : () ->                                    # Stringable!
     if value and typeof value.toString == 'function'


Fixed schema loading.

Fixed schema loading.

--- app/models/CocoModel.coffee
+++ app/models/CocoModel.coffee
@@ -23,8 +23,7 @@
     if @constructor.schema?.loaded
       @addSchemaDefaults()
     else
-      {me} = require 'lib/auth'
-      @loadSchema() if me?.loaded
+      @loadSchema()
     @once 'sync', @onLoaded, @
     @saveBackup = _.debounce(@saveBackup, 500)


Fixed error with Serial Monitor test

Fixed error with Serial Monitor test

--- spec/views/serial-monitor-view-spec.coffee
+++ spec/views/serial-monitor-view-spec.coffee
@@ -97,10 +97,13 @@
         expect(@serialMonitorView.connectButton.text()).toEqual('Disconnect')
         expect(@serialMonitorView.input.hiddenInput.attr('disabled')).toBeUndefined()

+        spyOn console, 'error'
         @serialMonitorView.port.emit 'error'

         expect(@serialMonitorView.connectButton.text()).toEqual('Connect')
+        expect(console.error).toHaveBeenCalled()

+        jasmine.unspy console, 'error'
         @serialMonitorView.close()

     it 'checks serial communication', ->


updated keymaps to API1.0 selectors

updated keymaps to API1.0 selectors

--- keymaps/timekeeper.cson
+++ keymaps/timekeeper.cson
@@ -7,7 +7,7 @@

 # For more detailed documentation see
 # https://atom.io/docs/latest/advanced/keymaps
-'.workspace':
+'atom-workspace':
     # Timekeeper Timer Actions
     'ctrl-alt-s': 'timekeeper:start'
     'ctrl-alt-p': 'timekeeper:pause'
@@ -16,4 +16,4 @@
     'ctrl-alt-a': 'timekeeper:abort'

     # Timekeeper UI Actions
-    'ctrl-alt-d': 'timekeeper:dashboard'+    'ctrl-alt-d': 'timekeeper:dashboard'


Keep date picker active after a refresh if necessary

--- client/app/components/toolbar_messageslist_filters.coffee
+++ client/app/components/toolbar_messageslist_filters.coffee
@@ -66,11 +66,16 @@
         if window.location.href.indexOf('flag') isnt -1
             filter = window.location.href.replace(/.*\/flag\//gi, '')
             @props.filter = filter.replace(/\/.*/gi, '')
+
         dateFiltered = @props.queryParams.before isnt '-' and
                        @props.queryParams.before isnt '1970-01-01T00:00:00.000Z' and
                        @props.queryParams.before isnt undefined and
                        @props.queryParams.after isnt undefined and
                        @props.queryParams.after isnt '-'
+
+        if window.location.href.indexOf('before') isnt -1
+            dateFiltered = true
+
         div
             role:            'group'
             className:       'filters'
@@ -115,6 +120,5 @@
                 active: dateFiltered
                 onDateFilter: @onDateFilter

-
     toggleExpandState: ->
         @setState expanded: not @state.expanded


Code refactoring

Code refactoring

--- client/ide/lib/views/tabview/ideapplicationtabview.coffee
+++ client/ide/lib/views/tabview/ideapplicationtabview.coffee
@@ -85,17 +85,15 @@
           cssClass  : "solid green medium"
           title     : "Save and Close"
           callback  : =>
-            if file.path.indexOf("localfile:") is 0
-              file.once "fs.saveAs.finished", =>
-                @removePane_ pane
-                @parent.handleCloseSplitView pane
+            if file.isDummyFile()
               @willClose = yes
               ace.requestSaveAs()
             else
               ace.requestSave()
-              file.once "fs.save.finished", =>
-                @removePane_ pane
-                @parent.handleCloseSplitView pane
+
+            file.once [ 'fs.saveAs.finished', 'fs.save.finished' ], =>
+              @removePane_ pane
+              @parent.handleCloseSplitView pane

             @askForSaveModal.destroy()
         "DontSave"  :


roles are now displayed sorted according to stored user preferences

--- client/tournament-volunteers/tournament-volunteers.coffee
+++ client/tournament-volunteers/tournament-volunteers.coffee
@@ -1,46 +1,67 @@
+setSignupId = ->
+  vId = Meteor.userId()
+  tId = Session.get('active-tournament').tournamentId
+  signup = TournamentVolunteers.findOne { tournamentId: tId, volunteerId: vId }
+  Session.set 'signup-id', signup._id
+
 setActiveRole = ->
   id = $('#role option:selected').val()
   name = $('#role option:selected').text()
   Session.set 'active-role-id', id
   Session.set 'active-role-name', name

+getRolePreferences = ->
+  signupId = Session.get 'signup-id'
+  tournament = TournamentVolunteers.findOne { _id: signupId }#, { fields: preferences: 1 }
+  tournament.preferences
+
 associateVolunteerWithTournament = ->
-	vId = Meteor.userId()
-	tId = Session.get('active-tournament').tournamentId
-	found = TournamentVolunteers.findOne { tournamentId: tId, volunteerId: vId }
-	unless vId and found
-		TournamentVolunteers.insert { tournamentId: tId, volunteerId: vId }
+  setSignupId()
+  signup = Session.get 'signup-id'
+  unless signup
+    TournamentVolunteers.insert { tournamentId: tId, volunteerId: vId }

 saveRolePreferences = (prefs) ->
-	vId = Meteor.userId()
-	tId = Session.get('active-tournament').tournamentId
-	signupId = TournamentVolunteers.findOne({ tournamentId: tId, volunteerId: vId })._id
-	TournamentVolunteers.update { _id: signupId }, { $set: preferences: prefs}, $upsert: 1
+  signupId = Session.get 'signup-id'
+  TournamentVolunteers.update { _id: signupId }, { $set: preferences: prefs}, $upsert: 1
+
+Template.tournamentVolunteerSignup.created = ->
+  associateVolunteerWithTournament()
+
+Template.tournamentVolunteerSignup.rendered = ->
+  $('#sortableRoles').disableSelection()
+  $('#sortableRoles').sortable
+    forcePlaceholderSize: true
+    stop: (evnt, ui) ->
+      preferences = $('#sortableRoles').sortable('toArray').slice(0, 4)
+      saveRolePreferences preferences

 Template.tournamentVolunteerSignup.days = ->
-	id = Session.get('active-tournament').tournamentid
-	tournament = Tournaments.findOne id, fields: days: 1
-	days = tournament.days
-	formattedDays = for day in days
-		dayOfWeek: moment(day).format 'ddd'
-		dayOfMonth: moment(day).format 'do'
+  id = Session.get('active-tournament').tournamentId
+  tournament = Tournaments.findOne id, fields: days: 1
+  days = tournament.days
+  formattedDays = for day in days
+    dayOfWeek: moment(day).format 'ddd'
+    dayOfMonth: moment(day).format 'do'

 Template.tournamentVolunteerSignup.roles = ->
-	id = Session.get('active-tournament').tournamentId
-	tournament = Tournaments.findOne id, fields: roles: 1
-	tournament.roles
-
-Template.tournamentVolunteerSignup.rendered = ->
-	associateVolunteerWithTournament()
-	$('#sortableRoles').disableSelection()
-	$('#sortableRoles').sortable
-		forcePlaceholderSize: true
-		stop: (evnt, ui) ->
-			preferences = $('#sortableRoles').sortable('toArray').slice(0, 4)
-			saveRolePreferences preferences
+  sortedRoles = []
+  rolePrefs = getRolePreferences()
+  id = Session.get('active-tournament').tournamentId
+  tournament = Tournaments.findOne id, fields: roles: 1
+  for role in rolePrefs
+    pref = _.find tournament.roles, (item) ->
+      if item.roleId is role and !item.found
+        item.found = true
+        item
+    sortedRoles.push pref
+  for role in tournament.roles
+    unless role.found
+      sortedRoles.push role
+  sortedRoles

 Template.tournamentVolunteerSignup.activeRoleName = ->
-	Session.get 'active-role-name'
+  Session.get 'active-role-name'

 Template.tournamentVolunteerSignup.markSelectedRole = ->
   if this.roleName is Session.get 'active-role-name'
@@ -89,8 +110,8 @@
     days: shiftDays

 Template.tournamentVolunteerSignup.events
-	'change #role': (evnt, template) ->
-		setActiveRole()
+  'change #role': (evnt, template) ->
+    setActiveRole()


some additions to formviewwithfields

some additions to formviewwithfields

--- client/Framework/components/forms/KDFormViewWithFields.coffee
+++ client/Framework/components/forms/KDFormViewWithFields.coffee
@@ -14,7 +14,7 @@
     for key,option of options
       option.title or= key
       option
-
+
   createFields:(fields)->
     @addSubView @createField fieldData for fieldData in fields

@@ -23,30 +23,41 @@
     buttons.forEach (buttonOptions)=>
       @buttonField.addSubView button = @createButton buttonOptions
       @buttons[buttonOptions.title] = button
-
+
   createField:(data, field)->
-    {itemClass,title} = data
-    itemClass or= KDInputView
-    field or= new KDView cssClass : "formline #{data.name}"
+    {itemClass, title} = data
+    itemClass     or= KDInputView
+    data.cssClass or= ""
+    field or= new KDView cssClass : "formline #{data.name} #{data.cssClass}"
     field.addSubView label = data.label = @createLabel(data) if data.label
     field.addSubView input = @createInput itemClass,data
+    if data.hint
+      field.addSubView hint  = new KDCustomHTMLView
+        partial  : data.hint
+        tagName  : "cite"
+        cssClass : "hint"
     @fields[title] = field
     if data.nextElement
       for key, next of data.nextElement
         next.title or= key
         @createField next, field
-
+
     return field
-
+
   createLabel:(data)->
-    return new KDLabelView(title : data.label)
-
+    return new KDLabelView
+      title    : data.label
+      cssClass : @utils.slugify data.label
+
   createInput:(itemClass,options)->
     @inputs[options.title] = input = new itemClass options
     return input

   createButton:(options)->
-    return button = new KDButtonView options
+    options.itemClass or= KDButtonView
+    o = $.extend {}, options
+    delete o.itemClass
+    button = new options.itemClass o

 # new KDFormViewWithFields
@@ -64,10 +75,10 @@
 #       title           : "Reset"
 #       style           : "modal-clean-red"
 #       type            : "reset"
-#   callback            : (formOutput)->
+#   callback            : (formOutput)->
 #     log formOutput,"  ::::::"
 #   fields              :
-#     Title             :
+#     Title             :
 #       label           : "Title:"
 #       type            : "text"
 #       name            : "title"
@@ -77,7 +88,7 @@
 #           required    : yes
 #         messages      :
 #           required    : "topic name is required!"
-#     Zikkko            :
+#     Zikkko            :
 #       label           : "Zikkko"
 #       type            : "textarea"
 #       name            : "zikko"
@@ -87,4 +98,3 @@
 #           type        : "text"
 #           name        : "lulu"
 #           placeholder : "lulu..."
-


Client: Activity: Added ChannelDropContainer to PublicChatPane and deleted unnecessary lines

--- client/activity/lib/components/channelthreadpane/index.coffee
+++ client/activity/lib/components/channelthreadpane/index.coffee
@@ -13,6 +13,7 @@
 showNotification             = require 'app/util/showNotification'
 CollaborationComingSoonModal = require 'activity/components/collaborationcomingsoonmodal'
 StartVideoCallLink           = require 'activity/components/common/startvideocalllink'
+ChannelDropContainer         = require 'activity/components/channeldropcontainer'

 module.exports = class ChannelThreadPane extends React.Component
@@ -78,25 +79,9 @@
     showNotification 'Coming soon...', type: 'main'

-  getDropTargetClassNames: -> classnames
-    'ChannelThreadPane-dropContainer': yes
-    'hidden': not @state.showDropTarget
-
-
   onClose: ->

     @setState isComingSoonModalOpen: no
-
-
-  renderDropSection: ->
-
-

-
Drop VMs here
 to start collaborating

-

   renderHeader: ->
@@ -118,7 +103,11 @@
         isOpen={@state.isComingSoonModalOpen}/>

-        {@renderDropSection()}
+        <ChannelDropContainer
+          onDrop={@bound 'onDrop'}
+          onDragOver={@bound 'onDragOver'}
+          onDragLeave={@bound 'onDragLeave'}
+          showDropTarget={@state.showDropTarget}/>

           {@renderHeader()}
           <StartVideoCallLink onStart={@bound 'onStart'}/>


InvitationInputView: Added serialize method.

--- client/admin/lib/views/invitations/invitationinputview.coffee
+++ client/admin/lib/views/invitations/invitationinputview.coffee
@@ -43,3 +43,13 @@
         tagName  : 'span'
         cssClass : 'cancel icon'
         click    : => @destroy()
+
+
+  serialize: ->
+
+    return {
+      email     : @email.getValue()
+      firstName : @firstName.getValue()
+      lastName  : @lastName.getValue()
+    }
+


Minor formatting.

Minor formatting.

--- src/spec/producttypegenerator.spec.coffee
+++ src/spec/producttypegenerator.spec.coffee
@@ -364,9 +364,6 @@
     expect(@generator._createProductTypesDefinitions(productTypeDefinitions, attributeDefinitions, masterSKU)).toEqual expectedProductTypeDefinitions

-
-
-
   it 'should return attribute definition for attribute masterSKU', ->

     expectedAttributeDefinition =


Koding Provider: if 't2.medium' is in allowedInstances for user's current plan do not hesitate to use it

--- workers/social/lib/social/models/computeproviders/koding.coffee
+++ workers/social/lib/social/models/computeproviders/koding.coffee
@@ -129,6 +129,9 @@
             instance_type : "t2.micro"
             storage_size  : storage
             alwaysOn      : no
+
+          if 't2.medium' in userPlan.allowedInstances
+            meta.instance_type = 't2.medium'

           callback null, {
             meta, label, credential: client.r.user.username


Fixing the case where a single char is provided from breaking the algorithm - Also making the algorithm more verbose, for the sake of readability

--- lib/algorhythmic.coffee
+++ lib/algorhythmic.coffee
@@ -6,10 +6,16 @@
     return newVal

   compute: (arr) ->
-    arr.reduce (p, n, i) =>
-      p = parseInt p.charCodeAt('0') unless i > 1
-      n = parseInt n.charCodeAt('0')
-      return @getNum(p + n)
+    if arr.length == 1
+      char = @getCharInt(arr[0])
+      return @getNum(char)
+
+    arr.reduce (previousChar, currentChar, i) =>
+      previousChar = @getCharInt(previousChar) unless i > 1
+      currentChar  = @getCharInt(currentChar)
+      return @getNum(previousChar + currentChar)
+
+  getCharInt: (char) -> parseInt char.charCodeAt('0') or 0

   convert: (string) ->
     str = string.replace(/\.(png|jpg|gif|)$/g, "")


Use skinny arrows for command handlers

Use skinny arrows for command handlers

This ensures this refers to the event element

Closes #64

--- lib/autocomplete.coffee
+++ lib/autocomplete.coffee
@@ -31,9 +31,9 @@
     @deactivationDisposables.add atom.commands.add 'atom-text-editor:not([mini])',
       'autocomplete:toggle': ->
         getAutocompleteView(this)?.toggle()
-      'autocomplete:next': =>
+      'autocomplete:next': ->
         getAutocompleteView(this)?.selectNextItemView()
-      'autocomplete:previous': =>
+      'autocomplete:previous': ->
         getAutocompleteView(this)?.selectPreviousItemView()

   deactivate: ->


rewritten some parts

rewritten some parts

--- lib/block-cursor.coffee
+++ lib/block-cursor.coffee
@@ -1,6 +1,5 @@
 'use strict'
 {CompositeDisposable} = require 'atom'
-_ = require 'underscore'

 class BlockCursor
   cursorStyle = null
@@ -26,9 +25,9 @@
       type: 'color'
       default: 'transparent'
     blinkInterval:
-      description: 'Interval of the cursor blink - the period between primaryColor and secondaryColor - in milliseconds. Set to 0 to disable blinking.'
+      description: 'Cursor blinking interval in milliseconds. Set to 0 to disable blinking. Doesn\'t work for mini editors, except for disabling cursor blinking'
       type: 'integer'
-      default: 400
+      default: 800
       minimum: 0
     pulseDuration:
       description: 'Duration of the pulse transition in milliseconds, set to 0 to disable pulse'
@@ -48,55 +47,59 @@

   activate: ->
     @subs = new CompositeDisposable()
-    @subs.add atom.config.observe 'block-cursor.cursorType', @applyCursorType.bind @
-    @subs.add atom.config.observe 'block-cursor.primaryColor', @applyPrimaryColor.bind @
-    @subs.add atom.config.observe 'block-cursor.secondaryColor', @applySecondaryColor.bind @
-    @subs.add atom.config.observe 'block-cursor.blinkInterval', @applyBlinkInterval.bind @
-    @subs.add atom.config.observe 'block-cursor.pulseDuration', @applyPulseDuration.bind @
-    @subs.add atom.config.observe 'block-cursor.cursorThickness', @applyCursorThickness.bind @
+    @subs.add [
+      atom.config.observe 'block-cursor.cursorType', @applyCursorType
+      atom.config.observe 'block-cursor.primaryColor', @applyPrimaryColor
+      atom.config.observe 'block-cursor.secondaryColor', @applySecondaryColor
+      atom.config.observe 'block-cursor.blinkInterval', @applyBlinkInterval.bind @
+      atom.config.observe 'block-cursor.pulseDuration', @applyPulseDuration
+      atom.config.observe 'block-cursor.cursorThickness', @applyCursorThickness
+    ]
     atom.config.set 'block-cursor.zzzpreview', 'The quick brown fox jumps over the lazy dog'
-    @editorSub = null

   deactivate: ->
     @subs.dispose()
-    @editorSub?.dispose?()
     if cursorStyle?
       cursorStyle.parentNode.removeChild cursorStyle
       cursorStyle = null

-  applyCursorType: (cursorTypeName) ->
+  applyCursorType: (cursorTypeName) =>
     cursorType = cursorTypeMap[cursorTypeName]
     workspaceView = atom.views.getView atom.workspace
     workspaceView.className = workspaceView.className.replace /block-cursor-(block|bordered-box|i-beam|underline)/, ''
-    workspaceView.classList.add "block-cursor-#{cursorType}"
+    workspaceView.className += " block-cursor-#{cursorType}"

-  applyPrimaryColor: (color) ->
+  applyPrimaryColor: (color) =>
     color = color.toRGBAString?() or @toRGBAString color
     @updateStylesheet primarySelector, 'background-color', color
     @updateStylesheet primarySelector, 'border-color', color

-  applySecondaryColor: (color) ->
+  applySecondaryColor: (color) =>
     color = color.toRGBAString?() or @toRGBAString color
     @updateStylesheet secondarySelector, 'background-color', color
     @updateStylesheet secondarySelector, 'border-color', color

-  applyBlinkInterval: (interval) ->
-    @editorSub?.dispose?()
-    @editorSub = atom.workspace.observeTextEditors (editor) ->
-      editorPresenter = atom.views.getView(editor).component.presenter
-      editorPresenter.cursorBlinkPeriod = interval * 2
-      editorPresenter.stopBlinkingCursors(true)
-      if interval > 0
-        editorPresenter.startBlinkingCursorsAfterDelay = do ->
-          _.debounce(editorPresenter.startBlinkingCursors, editorPresenter.getCursorBlinkResumeDelay())
-      else
-        atom.config.set 'block-cursor.secondaryColor', atom.config.get 'block-cursor.primaryColor'
-        editorPresenter.startBlinkingCursorsAfterDelay = ->
+  applyBlinkInterval: do ->
+    sub = null

-  applyPulseDuration: (duration) ->
+    (interval) ->
+      sub?.dispose?()
+      sub = atom.workspace.observeTextEditors (editor) ->
+        editorPresenter = atom.views.getView(editor).component.presenter
+        editorPresenter.stopBlinkingCursors true
+        if interval > 0
+          editorPresenter.cursorBlinkPeriod = interval
+          atom.config.set 'block-cursor.secondaryColor', 'transparent'
+        else
+          editorPresenter.cursorBlinkPeriod = -1 + Math.pow 2, 31
+          atom.config.set 'block-cursor.secondaryColor', atom.config.get 'block-cursor.primaryColor'
+        editorPresenter.startBlinkingCursors()
+      @subs.add sub
+
+  applyPulseDuration: (duration) =>
     @updateStylesheet primarySelector, 'transition-duration', "#{duration}ms"

-  applyCursorThickness: (thickness) ->
+  applyCursorThickness: (thickness) =>
     @updateStylesheet primarySelector, 'border-width', "#{thickness}px"

   getCursorStyle: ->


Add '=' and '_' to the list of allowed var characters

--- lib/editor-utils.coffee
+++ lib/editor-utils.coffee
@@ -202,4 +202,4 @@

   # Determines if a cursor is within a range of text of a var and returns the text
   getClojureVarUnderCursor: (editor)->
-    editor.getWordUnderCursor wordRegex: /[a-zA-Z0-9\-.$!?\/><*]+/
+    editor.getWordUnderCursor wordRegex: /[a-zA-Z0-9\-.$!?\/><*=_]+/


Removed unnecessary blank lines and added improvements for fancy box

--- app/assets/javascript/fancy_login/login_register_fancybox.coffee
+++ app/assets/javascript/fancy_login/login_register_fancybox.coffee
@@ -11,17 +11,14 @@
     @attachSwitchLoginRegisterHandler()

     # Add tracking on signout link
-    $(document).on "click", ".signout", (e)=>
+    $(document).on "click", ".signout", (e) =>
       # Track mixpanel events for logout links
       if $(e.currentTarget).data("event")?
         mixingpanel_tracker.track $(e.currentTarget).data('event'),
                                   $(e.currentTarget).data('extraProps')

-
-
     #since .not-logged-in can be applied to a link or a button
-    $(document).on "click", ".not-logged-in", (e)=>
-
+    $(document).on "click", ".not-logged-in", (e) =>
       # Track mixpanel events for login links
       if $(e.currentTarget).data("event")?
         mixingpanel_tracker.track $(e.currentTarget).data('event'),
@@ -33,12 +30,11 @@
       e.preventDefault()
       @clear()

-
-    $('a.loginbox-close').click (e)->
+    $('a.loginbox-close').click (e) ->
       e.preventDefault();
       $.fancybox.close();

-    $('a.btn-facebook').click (e)=>
+    $('a.btn-facebook').click (e) =>
       e.preventDefault()
       @clearFacebookError()

@@ -58,20 +54,20 @@
         ,scope: 'email'
       return false

-    $('a.btn-google').click (e)=>
+    $('a.btn-google').click (e) =>
       e.preventDefault()
       @showFancyBoxMessage('¡Ops! Esta función todavía no está disponible.', false)
       return false

-    $("#loginbox input").keyup (e)=>
+    $("#loginbox input").keyup (e) =>
       if e.keyCode == 27 # close dialog on <Esc>
         $.fancybox.close()
         return
       else if e.keyCode == 13 # don't clear error on <Enter>
         return
       @clearField e
+
     $('#login_form').submit (e) =>
-
       mixingpanel_tracker.track "Login", {"action":"signin", "location":"login fancybox", "url": document.URL }
       @formSubmit e, $('#login_form').data('url')

@@ -82,27 +78,23 @@
   blocked: false,

   show_into_fancybox: () =>
-
     @clear()

-    $('.not-logged-in').fancybox({
-      closeBtn: false,
-      enableEscapeButton:true,
-
-      padding:0,
-      # autoSize:false,
-      # width:'100%',
-      height: 'auto',
-
-      # Open/close methods are custom defined in shared...
-      openMethod : 'dropIn',
-      openSpeed : 300,
-      closeMethod : 'dropOut',
+    $('.not-logged-in').fancybox
+      href: '#loginbox'
+      closeBtn: false
+      enableEscapeButton:true
+      padding:0
+      autoSize:false
+      width: 270
+      height: 'auto'
+      autoHeight: true
+      openMethod : 'dropIn'
+      openSpeed : 300
+      closeMethod : 'dropOut'
       closeSpeed : 200
-
-      fitToView: true, # images won't be scaled to fit to browser's height
-      maxWidth: "500px", # images won't exceed the browser's width
-
+      fitToView: true  # images won't be scaled to fit to browser's height
+      maxWidth: "500px" # images won't exceed the browser's width
       beforeShow: ()->
         if $(@element).hasClass('not-registered')
           $("#loginbox-sign-in").css("display","none")
@@ -111,25 +103,17 @@
         else
           $("#loginbox-sign-up").css("display","none")
           $("#loginbox-sign-in").css("display","block")
-      href: '#loginbox'
-
-    }); #which div to show as fancybox
-
-
-

   attachSwitchLoginRegisterHandler: ()->
     $(".js-switch-action").on "click", (e) =>
       @clearFancyBoxError()
       target = $(e.target).attr("href")
       if target is "#signup"
-        $("#loginbox-sign-in").slideUp ()->
+        $("#loginbox-sign-in").slideUp ->
           $("#loginbox-sign-up").slideDown()
       else if target is "#signin"
-        $("#loginbox-sign-up").slideUp ()->
+        $("#loginbox-sign-up").slideUp ->
           $("#loginbox-sign-in").slideDown()
-
-

   @block: =>
     @blocked = true
@@ -160,7 +144,7 @@

   # Login form submit VALIDATION SHOULD HAVE BEEN DONE USING jQVALIDATOR!!! :(
   # This form submit is used both for login and registration
-  formSubmit: (e, url)=>
+  formSubmit: (e, url) =>
       e.preventDefault()
       if @constructor.blocked
         return false


Improved layout format.

Improved layout format.

--- app/assets/javascripts/app/controllers/ticket_overview.js.coffee
+++ app/assets/javascripts/app/controllers/ticket_overview.js.coffee
@@ -68,14 +68,13 @@
     # init fetch via ajax, all other updates on time via websockets
     else
       @ajax(
-        id:    'ticket_overview_' + @key,
-        type:  'GET',
-        url:   @apiPath + '/ticket_overviews',
-        data:  {
-          view:       @view,
-          view_mode:  @view_mode,
-        }
-        processData: true,
+        id:   'ticket_overview_' + @key
+        type: 'GET'
+        url:  @apiPath + '/ticket_overviews'
+        data:
+          view:      @view
+          view_mode: @view_mode
+        processData: true
         success: (data) =>
           data.ajax = true
           @load(data)
@@ -125,8 +124,6 @@
     @render()

   render: ->
-
-

     # if customer and no ticket exists, show the following message only
     if !@ticket_list_show[0] && @isRole('Customer')
@@ -160,10 +157,10 @@
     if @isRole('Customer')
       view_modes = []
     html = App.view('agent_ticket_view/content')(
-      overview:    @overview
-      view_modes:  view_modes
-      checkbox:    checkbox
-      edit:        edit
+      overview:   @overview
+      view_modes: view_modes
+      checkbox:   checkbox
+      edit:       edit
     )
     html = $(html)
 #    html.find('li').removeClass('active')
@@ -556,19 +553,19 @@
         },
         {
           value:  'last_contact'
-          name:   'Last Contact'
+          name:   'Last Contact Time'
         },
         {
           value:  'last_contact_agent'
-          name:   'Last Contact Agent'
+          name:   'Last Contact Agent Time'
         },
         {
           value:  'last_contact_customer'
-          name:   'Last Contact Customer'
+          name:   'Last Contact Customer Time'
         },
         {
           value:  'first_response'
-          name:   'First Response'
+          name:   'First Response Time'
         },
         {
           value:  'close_time'
@@ -576,7 +573,11 @@
         },
         {
           value:  'escalation_time'
-          name:   'Escalation'
+          name:   'Escalation Time'
+        },
+        {
+          value:  'pending_time'
+          name:   'Pending Reminder Time'
         },
         {
           value:  'article_count'


deal with state saving and filtering correctly; update api

--- app/assets/javascripts/effective_datatables/initialize.js.coffee
+++ app/assets/javascripts/effective_datatables/initialize.js.coffee
@@ -4,13 +4,13 @@
       datatable = $(this)

       init_options =
-        bServerSide: true
-        bProcessing: true
+        serverSide: true
+        processing: true
         stateSave: true
-        bAutoWidth: false
+        autoWidth: false
         deferRender: true
         order: datatable.data('default-order')
-        sAjaxSource: datatable.data('source')
+        ajax: datatable.data('source')
         pagingType: 'simple_numbers'
         lengthMenu: [[10, 25, 50, 100, 250, 1000, -1], [10, 25, 50, 100, 250, 1000, 'All']]
         fnServerParams: (aoData, a, b) ->
@@ -61,5 +61,8 @@

 $ -> initializeDataTables()
 $(document).on 'page:change', -> initializeDataTables()
-
-$(document).on 'click', '[data-effective-datatables-reset-filters]', (event) -> window.location.reload()
+resetTable = (event) ->
+  $('table[data-effective-datatables-table]').DataTable().state.clear()
+  window.location.reload()
+
+$(document).on 'click', '[data-effective-datatables-reset-filters]', resetTable


set new question sort order to questionCount + 1, write questionsCount method

--- app/assets/javascripts/hanuman/controllers/survey_step.js.coffee
+++ app/assets/javascripts/hanuman/controllers/survey_step.js.coffee
@@ -3,6 +3,10 @@

   question_text: null
   selected_answer_type_id: null
+
+  questionsCount: (->
+    return @get('questions').get('length')
+  ).property()

   actions:
     newQuestion: ->
@@ -13,7 +17,7 @@
       question = @store.createRecord('question',
         question_text: @get('question_text')
         survey_step: @get('model')
-        sort_order: 100
+        sort_order: @get('questionsCount') + 1
       )
       question.set('answer_type', @get('selectedAnswerType'))
       question.save().then (question) ->


Update fill color

Update fill color

--- app/assets/javascripts/uploadcare/widget/tabs/preview-tab.coffee
+++ app/assets/javascripts/uploadcare/widget/tabs/preview-tab.coffee
@@ -53,7 +53,7 @@
           if @__state != 'image'
             src = info.originalUrl
             # 1162x684 is 1.5 size of conteiner
-            src += "-/preview/1162x693/-/setfill/efefef/-/format/jpeg/-/progressive/yes/"
+            src += "-/preview/1162x693/-/setfill/ffffff/-/format/jpeg/-/progressive/yes/"
             imgInfo = info.originalImageInfo
             @__setState('image', {src, name: info.name})
             @initImage([imgInfo.width, imgInfo.height], info.cdnUrlModifiers)
@@ -76,7 +76,7 @@

       utils.image.drawFileToCanvas(
-        blob, 1550, 924, '#efefef', @settings.imagePreviewMaxSize
+        blob, 1550, 924, '#ffffff', @settings.imagePreviewMaxSize
       )
       .done (canvas, size) =>
         utils.canvasToBlob canvas, 'image/jpeg', 0.95,


require ink 0.11.6

require ink 0.11.6

--- lib/julia-client.coffee
+++ lib/julia-client.coffee
@@ -8,7 +8,7 @@
 semver = require 'semver'

 # TODO: Update me when tagging a new relase:
-INK_VERSION_COMPAT  = "^0.11.2"
+INK_VERSION_COMPAT  = "^0.11.6"

 INK_LINK            = '[`ink`](https://github.com/JunoLab/atom-ink)'
 LANGUAGE_JULIA_LINK = '[`language-julia`](https://github.com/JuliaEditorSupport/atom-language-julia)'


:new: Add empty render in linter-views

:new: Add empty render in linter-views

--- lib/linter-views.coffee
+++ lib/linter-views.coffee
@@ -4,6 +4,8 @@

 class LinterViews
   constructor: (@linter)->
+    @messages = []
+
     @bottomTabFile = new BottomTabFile()
     @bottomTabProject = new BottomTabProject()
     @bottomStatus = new BottomStatus()
@@ -15,6 +17,10 @@
     # Set default tab to File
     @scope = 'file'
     @bottomTabFile.active = true
+
+  # This message is called in editor-linter.coffee
+  render: ->
+

   # This method is called when we get the status-bar service
   attachBottom: (statusBar)->


:art: Make activeness and tab filter work again

--- lib/linter-views.coffee
+++ lib/linter-views.coffee
@@ -194,7 +194,7 @@
     @setPanelVisibility(true)
     activeEditor = atom.workspace.getActiveTextEditor()
     @messages.forEach (message) =>
-      if @scope is 'file' then return unless message.currentFile
+      if @scope is 'File' then return unless message.currentFile
       if message.currentFile and message.range #Add the decorations to the current TextEditor
         @markers.push marker = activeEditor.markBufferRange message.range, {invalidate: 'never'}
         activeEditor.decorateMarker(
@@ -204,10 +204,10 @@
           marker, type: 'highlight', class: "linter-highlight #{message.class}"
         )

-      if @scope is 'line'
+      if @scope is 'Line'
         return if @lineMessages.indexOf(message) is -1

-      Element = Message.fromMessage(message, addPath: @scope is 'project', cloneNode: true)
+      Element = Message.fromMessage(message, addPath: @scope is 'Project', cloneNode: true)

       @panel.appendChild Element
     @updateBubble()
@@ -221,7 +221,7 @@

   # This method is called in render, and classifies the messages according to scope
   extractMessages: (Gen, counts) ->
-    isProject = @scope is 'project'
+    isProject = @scope is 'Project'
     activeEditor = atom.workspace.getActiveTextEditor()
     activeFile = activeEditor?.getPath()
     Gen.forEach (Entry) =>


:art: Use set instead of array for visibleTabKeys

--- lib/linter-views.coffee
+++ lib/linter-views.coffee
@@ -34,7 +34,7 @@
     visibleTabs = @getVisibleTabKeys()

     @scope = atom.config.get('linter.defaultErrorTab', 'File')?.toLowerCase()
-    if visibleTabs.indexOf(@scope) is -1
+    if not visibleTabs.has(@scope)
       @scope = visibleTabs[0]

     @tabs.forEach (tab, key) =>
@@ -46,7 +46,7 @@
   getMessages: ->
     @messages

-# consumed in views/panel
+  # consumed in views/panel
   setPanelVisibility: (Status) ->
     if Status
       @panelWorkspace.show() unless @panelWorkspace.isVisible()
@@ -194,11 +194,11 @@
     @tabs.entries().find (tab) -> tab.active

   getVisibleTabKeys: ->
-    return [
-      'line'    if atom.config.get('linter.showErrorTabLine')
-      'file'    if atom.config.get('linter.showErrorTabFile')
-      'project' if atom.config.get('linter.showErrorTabProject')
-    ].filter (key) -> key
+    toReturn = new Set
+    toReturn.add 'line' if atom.config.get('linter.showErrorTabLine')
+    toReturn.add 'file' if atom.config.get('linter.showErrorTabFile')
+    toReturn.add 'project' if atom.config.get('linter.showErrorTabProject')
+    toReturn

   removeBubble: ->
     return unless @bubble


Fixes typo in minimap view update

Fixes typo in minimap view update

--- lib/minimap-view.coffee
+++ lib/minimap-view.coffee
@@ -193,8 +193,7 @@
       @updateMinimapView()
     else
       @detachFromPaneView() if @parent().length is 1
-      @paneView.removeClass('with-minimap') unless activeView instanceof EditorView
-
+      @paneView.addClass('with-minimap') if activeView instanceof EditorView

   onMouseWheel: (e) =>
     return if @isClicked


:bug: Fix deprecated subscription to `item-removed`

--- lib/minimap-view.coffee
+++ lib/minimap-view.coffee
@@ -1,6 +1,7 @@
 {$, View, EditorView} = require 'atom'
 Debug = require 'prolix'
 Delegato = require 'delegato'
+{CompositeDisposable} = require 'event-kit'

 MinimapEditorView = require './minimap-editor-view'
 MinimapIndicator = require './minimap-indicator'
@@ -38,6 +39,8 @@

     @paneView.addClass('with-minimap')

+    @subscriptions = new CompositeDisposable
+
     super

     @computeScale()
@@ -64,7 +67,7 @@
     @obsPane = @paneView.model.observeActiveItem @onActiveItemChanged

     # Fix items movin to another pane.
-    @subscribe @paneView.model, 'item-removed', (item) -> item.off? '.minimap'
+    @subscriptions.add @paneView.model.onDidRemoveItem (item) -> item.off? '.minimap'

     @subscribe @miniEditorView, 'minimap:updated', @updateMinimapSize
     @subscribe @miniEditorView, 'minimap:scaleChanged', =>


:lipstick: Force maxWidth unit

:lipstick: Force maxWidth unit

--- lib/minimap-view.coffee
+++ lib/minimap-view.coffee
@@ -285,7 +285,7 @@

     size = atom.config.get('editor.preferredLineLength')
     if size
-      maxWidth = size * @getCharWidth()
+      maxWidth = (size * @getCharWidth()) + 'px'

       @css maxWidth: maxWidth
       @editorView.find('.editor-contents').css paddingRight: maxWidth


remove unnecessary minimapScroll variable

--- lib/minimap-view.coffee
+++ lib/minimap-view.coffee
@@ -30,7 +30,6 @@
     @scaleY = @scaleX * 0.8
     @minimapScale = @scale(@scaleX, @scaleY)
     @miniScrollView = @miniEditorView.scrollView
-    @minimapScroll = 0
     @transform @miniWrapper[0], @minimapScale
     # dragging's status
     @isPressed = false


Restore addsCount when prev state is provided

--- lib/recent-files.coffee
+++ lib/recent-files.coffee
@@ -12,13 +12,13 @@
       data: @_files
     }

-  constructor: (@_files) ->
+  constructor: (prevFiles = {}) ->
     @_addsCount = 0
-    @_files ||= {} # path => integer (addsCount value at the time of path being added, used for sorting)
+    @_files = {} # path => integer (addsCount value at the time of path being added, used for sorting)
     @_maxFilesToRemember = 1000000

-    for path in @_openPaths()
-      @_addPath(path)
+    @_addPath(path) for path of prevFiles
+    @_addPath(path) for path in @_openPaths()
     @_removeOverflow()

   setMaxFilesToRemember: (newValue) ->


Fix bug - activate item on tab click

Fix bug - activate item on tab click

Pending tab was not closing when a permanent tab was clicked

--- lib/tab-bar-view.coffee
+++ lib/tab-bar-view.coffee
@@ -373,7 +373,7 @@
       tab.classList.add('right-clicked')
       event.preventDefault()
     else if event.which is 1 and not event.target.classList.contains('close-icon')
-      @pane.setActiveItem(tab.item)
+      @pane.activateItem(tab.item)
       tab.item.confirmPendingState?()
       setImmediate => @pane.activate()
     else if event.which is 2


Use querySelector to find active tab

Use querySelector to find active tab

--- lib/tab-bar-view.coffee
+++ lib/tab-bar-view.coffee
@@ -119,7 +119,7 @@

   setActiveTab: (tabView) ->
     if tabView? and not tabView.classList.contains('active')
-      @find(".tab.active").removeClass('active')
+      @element.querySelector('.tab.active')?.classList.remove('active')
       tabView.classList.add('active')

   updateActiveTab: ->


Allow dragging of tab when there is only one tab.

The tabs should be able to be draggale out of the application at any
time, and if there is only one tab it should not be able to be dropped
in the same window.

--- lib/tab-bar-view.coffee
+++ lib/tab-bar-view.coffee
@@ -77,11 +77,8 @@
     (@paneContainer.getPanes().length > 1) or (@pane.getItems().length > 1)

   onDragStart: (event) =>
-    unless @shouldAllowDrag(event)
-      event.preventDefault()
-      return
-
-    event.originalEvent.dataTransfer.setData 'atom-event', 'true'
+    if @shouldAllowDrag()
+      event.originalEvent.dataTransfer.setData 'atom-event', 'true'

     el = $(event.target).closest('.sortable')
     el.addClass 'is-dragging'


nicer code

nicer code

--- lib/util/tooltip.coffee
+++ lib/util/tooltip.coffee
@@ -95,9 +95,9 @@

   positionOverlay: ->
     bounding = @parent.getBoundingClientRect()
-    @view.style.bottom   = document.documentElement.clientHeight - bounding.top + 'px'
-    switch @position
+    @view.style.bottom = document.documentElement.clientHeight - bounding.top + 'px'
+    @view.style.left = switch @position
       when 'left'
-        @view.style.left = bounding.left + 'px'
+        bounding.left + 'px'
       when 'right'
-        @view.style.left = bounding.left + bounding.width -  @view.offsetWidth + 'px'
+        bounding.left + bounding.width - @view.offsetWidth + 'px'


add 輸送船団護衛を強化せよ

add 輸送船団護衛を強化せよ

--- assets/data/quest_goal.cson
+++ assets/data/quest_goal.cson
@@ -289,6 +289,13 @@
     description: "演習勝利"
     required: 7
     init: 0
+424:
+  type: 3
+  "mission_success":
+    description: "遠征"
+    required: 4
+    mission: ['海上護衛任務']
+    init: 0
 ###
 # Quarterly
 ###


hotfix

hotfix

--- kit/nofile_builder.coffee
+++ kit/nofile_builder.coffee
@@ -18,7 +18,7 @@
             tasks:
                 'setup': [
                     'Setup project.',
-                    ->
+                    =>
                         setup = kit.require './kit/setup', @opts.dirname
                         setup()
                 ]


haraheに平均予算を表示する

haraheに平均予算を表示する

--- scripts/harahe.coffee
+++ scripts/harahe.coffee
@@ -31,11 +31,13 @@

           client.get req, (data, response) ->
             parseString data, (err, result) ->
+              formatBudget = (budget) -> if budget then "#{budget}円" else "不明"
               items = result['response']['rest'][0]
               msg.send """
               #{items['name']}

               カテゴリ: #{items['category']}
+              平均予算: #{formatBudget(items['budget'])}
               住所: #{items['address']}
               #{items['url']}
               """


jupyter: add some tooltips

jupyter: add some tooltips

--- src/smc-webapp/jupyter/top-buttonbar.cjsx
+++ src/smc-webapp/jupyter/top-buttonbar.cjsx
@@ -103,10 +103,10 @@

     render_group_zoom: ->
         <ButtonGroup>
-            <Button onClick={=>@props.actions.zoom(-1); @focus()}>
+            <Button onClick={=>@props.actions.zoom(-1); @focus()} title='Zoom out (make text smaller)'>
                 <Icon name='font' style={fontSize:'7pt'}/>
             </Button>
-            <Button onClick={=>@props.actions.zoom(1); @focus()}>
+            <Button onClick={=>@props.actions.zoom(1); @focus()}  title='Zoom in (make text larger)'>
                 <Icon name='font' style={fontSize:'11pt'}/>
             </Button>
         </ButtonGroup>
@@ -114,12 +114,14 @@
     render_group_save_timetravel: ->
         <ButtonGroup>
             <Button
+                title    = 'Save file to disk'
                 bsStyle  = "success"
                 onClick  = {=>@props.actions.save(); @focus()}
                 disabled = {not @props.has_unsaved_changes}>
                 <Icon name='save'/> Save
             </Button>
             <Button
+                title   = 'Show complete edit history'
                 bsStyle = "info"
                 onClick = {=>@props.actions.show_history_viewer()}>
                 <Icon name='history'/> TimeTravel


Improve tests

Improve tests

--- test/default-network.coffee
+++ test/default-network.coffee
@@ -1,15 +1,26 @@
 expect = require('chai').expect
-net = require('../src/default-network')
+defaultNetwork = require('../src/default-network')
+net = require('net')
+os = require('os')

 describe 'default-network', ->
-  data = null
-  before (done) ->
-    net.get (error, result) ->
-      return throw error if error
-      data = result
-      console.log data
-      done()
-  it 'returns an object', ->
-    expect(data).to.be.an.any('object')
-  it "The object includes keys 'defaultInterface' and 'defaultGateway'", ->
-    expect(data).to.include.keys('defaultInterface', 'defaultGateway')
+  describe 'get()', ->
+    data = null
+    before (done) ->
+      defaultNetwork.get (error, result) ->
+        return throw error if error
+        data = result
+        done()
+    it 'returns an data object', ->
+      expect(data).to.be.an.any('object')
+    describe 'data', ->
+      it "includes 'defaultInterface'", ->
+        expect(data).to.include.keys('defaultInterface')
+      it "includes 'defaultGateway'", ->
+        expect(data).to.include.keys('defaultGateway')
+      describe 'defaultInterface', ->
+        it "is included in os.networkInterfaces()", ->
+          expect(os.networkInterfaces()).to.include.keys(data.defaultInterface)
+      describe 'defaultGateway', ->
+        it "is IP", ->
+          expect(net.isIP(data.defaultGateway)).to.be.not.zero


fix provider shema (description => desc)

Signed-off-by: Arnaud Richard <791baf8974538c563f287130527dd623cb0e836d@webshell.io>

--- test/provider_schema.coffee
+++ test/provider_schema.coffee
@@ -27,7 +27,7 @@
 		"url":
 			description: "The base absolute url to use in sub-parts"
 			type: "string"
-		"description":
+		"desc":
 			description: "Description of the API provider"
 			type: "string"
 		"oauth1":


Add animateToNext

Add animateToNext

--- framer/Layer.coffee
+++ framer/Layer.coffee
@@ -934,8 +934,18 @@
 			animationFinished()
 		animation

+	animateToNext: (stateNames=[],options) ->
+		if not Array.isArray(stateNames)
+			if not options? and typeof stateNames is 'object'
+				options = stateNames
+				stateNames = []
+			else
+				stateNames = Utils.arrayFromArguments arguments
+				options = {}
+		nextState = @_stateMachine.next(stateNames)
+		@animateTo nextState, options
+
 	animations: ->
-
 		# Current running animations on this layer
 		_.filter @_context.animations, (animation) =>
 			animation.options.layer is @


Add some examples for hints

Add some examples for hints

--- framer/Utils.coffee
+++ framer/Utils.coffee
@@ -865,6 +865,13 @@

 	frame

+Utils.frameInFrame = (frameA, frameB) ->
+
+	for point in Utils.pointsFromFrame(frameA)
+		return false unless Utils.pointInFrame(point, frameB)
+
+	return true
+
 Utils.framePointForOrigin = (frame, originX, originY) ->
 	frame =
 		x: frame.x + (originX * frame.width)


Add docs link support for visual test helpers.

--- tests/js/base-visual.coffee
+++ tests/js/base-visual.coffee
@@ -24,6 +24,7 @@
       # - Build test template vars.
       vars = _.pick config, 'label', 'className', 'footerHtml'
       vars.footerHtml ?= '' unless config.asFragments
+      vars.docsUrl = $('body > header [data-rel=docs]').attr('href') + "##{config.anchorName}"
       vars.html = _.template config.template, config.vars
       # - Render test to get test context (or fragments).
       $container = if config.asFragments then $('body') else $('#main')
@@ -61,7 +62,10 @@
   $.fn.renderVisualTest.defaults =
     template: _.template """
               <section class="<%- className %> visual-test">
-                <%- label %>
+
+                  <%- label %>
+                  <a data-rel="docs" href="<%= docsUrl %>">docs
+
                 <%= html %>
                 <%= footerHtml %>