Unwrap methods definition loop in Camera

--- src/scenes/camera.coffee
+++ src/scenes/camera.coffee
@@ -1,12 +1,6 @@
 namespace('agt.scenes')

 {Rectangle, Range} = agt.geom
-
-CAMERA_SETTERS_MAP =
-  x: 'moveToX'
-  y: 'moveToY'
-  width: 'resizeToWidth'
-  height: 'resizeToHeight'

 class agt.scenes.Camera
   @include agt.mixins.Emitter
@@ -17,17 +11,42 @@
                 @zoomRange=new Range(-Infinity, Infinity),
                 @silent=false) ->

-  Object.keys(CAMERA_SETTERS_MAP).forEach (key) ->
-    setter = CAMERA_SETTERS_MAP[key]

-    Camera.accessor key, {
-      get: -> @screen[key]
-      set: (value) -> @[setter](value)
-    }
+  @accessor 'x',
+    get: -> @screen.x
+    set: (value) ->
+      @screen.x = value
+      @fireCameraChange()

-    Camera::[setter] = (value) ->
-      @screen[key] = value
-      @dispatch('changed', this) unless @inBatch
+  @accessor 'y',
+    get: -> @screen.y
+    set: (value) ->
+      @screen.y = value
+      @fireCameraChange()
+
+  @accessor 'width',
+    get: -> @screen.width
+    set: (value) ->
+      @screen.width = value
+      @fireCameraChange()
+
+  @accessor 'height',
+    get: -> @screen.height
+    set: (value) ->
+      @screen.height = value
+      @fireCameraChange()
+
+  @accessor 'rotation',
+    get: -> @screen.rotation
+    set: (value) ->
+      @screen.rotateAroundCenter(rotation - @screen.rotation)
+      @fireCameraChange()
+
+  @accessor 'zoom',
+    get: -> @_zoom
+    set: (value) ->
+      if @zoomRange.surround(value)
+        @_zoom = value

   update: (props={}) ->
     @inBatch = true
@@ -35,6 +54,8 @@
     for key, value of props
       @[key] = value
       changed ||= true
-
+
     @dispatch('changed', this) if changed
     @inBatch = false
+
+  fireCameraChange: -> @dispatch('changed', this) unless @inBatch


make role script honor the hipchat @ syntax

--- src/scripts/roles.coffee
+++ src/scripts/roles.coffee
@@ -13,7 +13,7 @@
   getAmbiguousUserText = (users) ->
     "Be more specific, I know #{users.length} people named like that: #{(user.name for user in users).join(", ")}"

-  robot.respond /who is ([\w .-]+)\?*$/i, (msg) ->
+  robot.respond /who is @?([\w .-]+)\?*$/i, (msg) ->
     name = msg.match[1]

     if name is "you"
@@ -34,7 +34,7 @@
       else
         msg.send "#{name}? Never heard of 'em"

-  robot.respond /([\w .-_]+) is (["'\w: -_]+)[.!]*$/i, (msg) ->
+  robot.respond /@?([\w .-_]+) is (["'\w: -_]+)[.!]*$/i, (msg) ->
     name    = msg.match[1]
     newRole = msg.match[2].trim()

@@ -58,7 +58,7 @@
         else
           msg.send "I don't know anything about #{name}."

-  robot.respond /([\w .-_]+) is not (["'\w: -_]+)[.!]*$/i, (msg) ->
+  robot.respond /@?([\w .-_]+) is not (["'\w: -_]+)[.!]*$/i, (msg) ->
     name    = msg.match[1]
     newRole = msg.match[2].trim()


Fixed one misconfigured test in options manager

--- test/options-managerTest.coffee
+++ test/options-managerTest.coffee
@@ -169,7 +169,7 @@
 		it should return default values and warn user', ->
 			option = 'logLevels'
 			overrideOptions =
-				logLevels: ['unknown']
+				logLevels: 'unknown'
 			optionsManager.shared.options = overrideOptions
 			spy = sinon.spy(optionsManager, 'returnOptionsAndWarning')
 			returnedValues = ['warning', 'error', 'info']


Subscribe to file path if the editor is gone

Even if the editor that the preview tab was tracking has been destroyed,
we can still display the rendered markdown by checking its file
contents.  Do that instead of instantly destroying the pane.

Closes atom/atom#86582

--- lib/markdown-preview-view.coffee
+++ lib/markdown-preview-view.coffee
@@ -65,9 +65,7 @@
         @handleEvents()
         @renderMarkdown()
       else
-        # The editor this preview was created for has been closed so close
-        # this preview since a preview cannot be rendered without an editor
-        atom.workspace?.paneForItem(this)?.destroyItem(this)
+        @subscribeToFilePath(@filePath)

     if atom.workspace?
       resolve()


empty string test

empty string test

--- test/string-to-json-test.coffee
+++ test/string-to-json-test.coffee
@@ -6,7 +6,9 @@
   return text.split('\n')

 describe 'StringToJson', ->
-  stj = new StringToJson sampleText
+  stj = undefined
+  before ->
+    stj = new StringToJson sampleText

   describe '#constructor', ->

@@ -42,3 +44,13 @@
           for k,v of stj.generate()
             lineNumber = parseInt(k.split('-')[0])
             assert.equal lines[lineNumber], v
+
+    describe 'in case when empty string is provided to constructor', ->
+      before ->
+        stj = new StringToJson ''
+
+      it 'should generate empty hash', ->
+        assert.isTrue Object.keys(stj.generate()).length == 0
+
+
+


Add test case for removing event listeners

--- spec/BaconSpec.coffee
+++ spec/BaconSpec.coffee
@@ -26,12 +26,11 @@
 describe "Bacon.fromEventTarget", ->
   it "should create EventStream from DOM object", ->
     emitter = new EventEmitter()
-    element =
-      addEventListener: (event, handler) ->
-        emitter.on event, handler
-        emitter.emit 'click', "x"
-      removeEventListener: (event, handler) ->
-        emitter.removeListener(event, handler)
+    emitter.on "newListener", ->
+      runs ->
+        emitter.emit "click", "x"
+
+    element = toEventTarget emitter

     expectStreamEvents(
       -> Bacon.fromEventTarget(element, "click").take(1)
@@ -48,6 +47,19 @@
       -> Bacon.fromEventTarget(emitter, "data").take(1)
       ["x"]
     )
+
+  it "should clean up event listeners from EventEmitter", ->
+    emitter = new EventEmitter()
+    Bacon.fromEventTarget(emitter, "data").take(1).subscribe ->
+    emitter.emit "data", "x"
+    expect(emitter.listeners("data").length).toEqual(0)
+
+  it "should clean up event listeners from DOM object", ->
+    emitter = new EventEmitter()
+    element = toEventTarget emitter
+    dispose = Bacon.fromEventTarget(element, "click").subscribe ->
+    dispose()
+    expect(emitter.listeners("click").length).toEqual(0)

 describe "EventStream.filter", ->
   it "should filter values", ->
@@ -593,3 +605,8 @@
   filter hasValue, xs
 hasValue = (x) ->
   toValue(x) != "<error>"
+
+# Wrap EventEmitter as EventTarget
+toEventTarget = (emitter) ->
+  addEventListener: (event, handler) -> emitter.addListener(event, handler)
+  removeEventListener: (event, handler) -> emitter.removeListener(event, handler)


misc: timestamping console.log

misc: timestamping console.log

--- src/smc-util/misc.coffee
+++ src/smc-util/misc.coffee
@@ -67,6 +67,8 @@
 if process?.env?.SMC_TEST
     # in test mode we *do* want exception to get thrown below when type checks fails
     TEST_MODE = true
+
+exports.RUNNING_IN_NODE = process?.title == 'node'

 # startswith(s, x) is true if s starts with the string x or any of the strings in x.
 exports.startswith = (s, x) ->
@@ -1293,6 +1295,7 @@
 exports.weeks_before        = (d, tm)  -> exports.days_before(7*d, tm)
 exports.months_before       = (d, tm)  -> exports.days_before(30.5*d, tm)

+
 # Round the given number to 1 decimal place
 exports.round1 = round1 = (num) ->
     Math.round(num * 10) / 10
@@ -1301,6 +1304,17 @@
 exports.round2 = round2 = (num) ->
     # padding to fix floating point issue (see http://stackoverflow.com/questions/11832914/round-to-at-most-2-decimal-places-in-javascript)
     Math.round((num + 0.00001) * 100) / 100
+
+exports.seconds2hms = seconds2hms = (secs) ->
+    s = round2(secs % 60)
+    m = Math.floor(secs / 60) % 60
+    h = Math.floor(secs / 60 / 60)
+    if h == 0 and m == 0
+        return "#{s}s"
+    if h > 0
+        return "#{h}h#{m}m#{s}s"
+    if m > 0
+        return "#{m}m#{s}s"

 # returns the number parsed from the input text, or undefined if invalid
 # rounds to the nearest 0.01 if round_number is true (default : true)
@@ -1456,3 +1470,15 @@
         return ""

 exports.emoticons = exports.to_human_list(exports.smiley_strings())
+
+smc_logger_timestamp = smc_logger_timestamp_last = smc_start_time = new Date().getTime() / 1000.0
+
+exports.log = (msg) ->
+    smc_logger_timestamp = new Date().getTime() / 1000.0
+    t  = seconds2hms(smc_logger_timestamp - smc_start_time)
+    dt = seconds2hms(smc_logger_timestamp - smc_logger_timestamp_last)
+    console.log_original("[#{t} Δ #{dt}] #{msg}")
+    smc_logger_timestamp_last = smc_logger_timestamp
+
+window.console.log_original = window.console.log
+window.console.log = exports.log


Deprecate style sheet events on ThemeManager

They are now supplanted by methods on the StyleManager located at
`atom.styles`.

--- src/theme-manager.coffee
+++ src/theme-manager.coffee
@@ -64,39 +64,43 @@
   onDidReloadAll: (callback) ->
     @emitter.on 'did-reload-all', callback

-  # Essential: Invoke `callback` when a stylesheet has been added to the dom.
+  # Deprecated: Invoke `callback` when a stylesheet has been added to the dom.
   #
   # * `callback` {Function}
   #   * `stylesheet` {StyleSheet} the style node
   #
   # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
   onDidAddStylesheet: (callback) ->
+    Grim.deprecate("Use atom.styles.onDidAddStyleElement instead")
     @emitter.on 'did-add-stylesheet', callback

-  # Essential: Invoke `callback` when a stylesheet has been removed from the dom.
+  # Deprecated: Invoke `callback` when a stylesheet has been removed from the dom.
   #
   # * `callback` {Function}
   #   * `stylesheet` {StyleSheet} the style node
   #
   # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
   onDidRemoveStylesheet: (callback) ->
+    Grim.deprecate("Use atom.styles.onDidRemoveStyleElement instead")
     @emitter.on 'did-remove-stylesheet', callback

-  # Essential: Invoke `callback` when a stylesheet has been updated.
+  # Deprecated: Invoke `callback` when a stylesheet has been updated.
   #
   # * `callback` {Function}
   #   * `stylesheet` {StyleSheet} the style node
   #
   # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
   onDidUpdateStylesheet: (callback) ->
+    Grim.deprecate("Use atom.styles.onDidUpdateStyleElement instead")
     @emitter.on 'did-update-stylesheet', callback

-  # Essential: Invoke `callback` when any stylesheet has been updated, added, or removed.
+  # Deprecated: Invoke `callback` when any stylesheet has been updated, added, or removed.
   #
   # * `callback` {Function}
   #
   # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
   onDidChangeStylesheets: (callback) ->
+    Grim.deprecate("Use atom.styles.onDidAdd/RemoveStyleElement instead")
     @emitter.on 'did-change-stylesheets', callback

   on: (eventName) ->


Add Gauges

Add Gauges

--- libraries/metrics/metrics.coffee
+++ libraries/metrics/metrics.coffee
@@ -12,6 +12,7 @@
 buildGlobalKey = (key)-> "#{name}.global.#{key}"

 counters = {}
+gauges = {}

 destructors = []

@@ -37,10 +38,9 @@
 	inc : (key, sampleRate = 1)->
 		statsd.increment buildKey(key), sampleRate
 		if !counters[key]
-			console.log("No Metric")
 			counters[key] = new prom.Counter({
 		  		name: key,
-				help: key #https://prometheus.io/docs/instrumenting/writing_exporters/#help-strings this is probably wrong
+				help: key,
 				labelNames: ['name','host']
 			})
 		counters[key].inc({name: name, host: hostname})
@@ -63,9 +63,23 @@

 	gauge : (key, value, sampleRate = 1)->
 		statsd.gauge buildKey(key), value, sampleRate
+		if !gauges[key]
+			gauges[key] = new prom.Gauge({
+		  		name: key,
+				help: key,
+				labelNames: ['name','host']
+			})
+		gauges[key].set({name: name, host: hostname},value)

 	globalGauge: (key, value, sampleRate = 1)->
 		statsd.gauge buildGlobalKey(key), value, sampleRate
+		if !gauges[key]
+			gauges[key] = new prom.Gauge({
+		  		name: key,
+				help: key,
+				labelNames: ['name','host']
+			})
+		gauges[key].set({name: name},value)

 	mongodb: require "./mongodb"
 	http: require "./http"


better work with owners

better work with owners

--- library/exposure/pinpoint.coffee
+++ library/exposure/pinpoint.coffee
@@ -102,7 +102,7 @@
             return unless (try moved.length or null) > 0
             try logger.info pe, moved.length, this.service
             $(nod).data owners: go(nod) for nod in moved
-            go(nod).push this for nod in moved or Array()
+            go(n).push @ for n in moved unless @ in go(n)
             @emit "pp-parented", selector, renderer, moved
             _.each moved, (n) => renderer.call @, n, go n

@@ -125,7 +125,7 @@
             return unless (try moved.length or null) > 0
             try logger.info pe, moved.length, this.service
             $(nod).data owners: go(nod) for nod in moved
-            go(nod).push this for nod in moved or Array()
+            go(n).push @ for n in moved unless @ in go(n)
             @emit "pp-vanished", selector, renderer, moved
             _.each moved, (n) => renderer.call @, n, go n

@@ -148,6 +148,6 @@
             return unless (try added.length or null) > 0
             try logger.info pe, added.length, this.service
             $(nod).data owners: go(nod) for nod in added
-            go(nod).push this for nod in added or Array()
+            go(n).push @ for n in added unless @ in go(n)
             @emit "pp-pinpoint", selector, renderer, added
             _.each added, (n) => renderer.call @, n, go n


Name changing

Name changing

--- library/membrane/validate.coffee
+++ library/membrane/validate.coffee
@@ -139,14 +139,14 @@
         assert _.isObject(request), notRequest
         @validateValues request.params, (failure, results) ->
             signature = [results, request, response]
-            return @parametersFailed signature... if failure
+            return @abnormalParameters signature... if failure
             return continuation.bind(this) failure, results

     # This method is a default implementation of the renderer that
     # will be called when the validation has failed. You can easily
     # override it in either your service or in an external compound.
     # By default it renders JSON object with errors mapped to params.
-    parametersFailed: (results, request, response) ->
+    abnormalParameters: (results, request, response) ->
         notRequest = "a #{request} is not a request"
         notResponse = "a #{response} is not a respnonse"
         assert _.isObject(response), notResponse
@@ -193,14 +193,14 @@
         assert _.isObject(request), notRequest
         @validateValues request.headers, (failure, results) ->
             signature = [results, request, response]
-            return @headersFailed signature... if failure
+            return @abnormalHeaders signature... if failure
             return continuation.bind(this) failure, results

     # This method is a default implementation of the renderer that
     # will be called when the validation has failed. You can easily
     # override it in either your service or in an external compound.
     # By default it renders JSON object with errors mapped to headers.
-    headersFailed: (results, request, response) ->
+    abnormalHeaders: (results, request, response) ->
         notRequest = "a #{request} is not a request"
         notResponse = "a #{response} is not a respnonse"
         assert _.isObject(response), notResponse


minor changes to the abstract widget

minor changes to the abstract widget

--- library/semantic/abstract.coffee
+++ library/semantic/abstract.coffee
@@ -69,25 +69,24 @@
     # marking it with the proper identifications and preparing an
     # internal APIs and shorthands for convenient access to some
     # of the most basic functionality related to a visual widget.
-    constructor: (container, reference, payload) ->
+    # The reference has to be a bare (without a dot) HTML class.
+    constructor: (@container, @reference, payload) ->
         msg = "Constructing widget %s with reference %s"
         ptf = "no valid template-function for an element"
         noContainer = "no valid container object supplied"
         noReference = "no valid reference string supplied"
         noPayload = "something wrong with payload function"
         try payload = (-> return undefined) unless payload
-        assert _.isObject(container or null), noContainer
-        assert _.isString(reference or null), noReference
+        assert _.isObject(@container or null), noContainer
+        assert _.isString(@reference or null), noReference
         assert _.isFunction(payload or null), noPayload
         assert @show = => this.element.show() # shortcut
         assert @hide = => this.element.hide() # shortcut
-        assert @container = container # store container
-        assert @reference = reference # store reference
         assert _.isFunction(@constructor::element), ptf
         @element = $ renderable(@constructor::element) @
-        assert @element.addClass "semantic-ui-widget"
-        assert @element.appendTo this.container or 0
+        @element.addClass "semantic-widget", @reference
+        @element.appendTo (try @container or undefined)
+        assert identify = try @constructor.identify()
+        assert ref = @reference.toString().bold or 0
         payload.call this, @element, @reference or 0
-        assert identify = try @constructor.identify()
-        assert ref = reference.toString().bold or no
         logger.debug msg, identify.bold, ref.bold


cleanup test template

cleanup test template

--- test/eslogger_commands_test.coffee
+++ test/eslogger_commands_test.coffee
@@ -13,11 +13,6 @@

 describe 'eslogger_commands', ->

-  hubotEmit = (e, data, tempo = 40) ->
-    beforeEach (done) ->
-      room.robot.emit e, data
-      setTimeout (done), tempo
-
   hubotHear = (message, userName = 'momo', tempo = 40) ->
     beforeEach (done) ->
       room.user.say userName, message
@@ -50,10 +45,6 @@
     room.robot.brain.userForId 'user', {
       name: 'user'
     }
-    room.robot.brain.userForId 'user_with_email', {
-      name: 'user_with_email',
-      email_address: 'user@example.com'
-    }

     room.receive = (userName, message) ->
       new Promise (resolve) =>


fix test file error

fix test file error

--- test/integration/ipfs_tests.coffee
+++ test/integration/ipfs_tests.coffee
@@ -36,8 +36,13 @@
   it 'exists', ->
     should.exist adaptor

-# TODO: talk to API of disposable IPFS node, or start a real one
-xit 'can fetch an IPFS tree', ->
+  it 'can add a file', ->
+    @timeout 10000
+    adaptor.put content: "Plz add me!\n"
+      .should.eventually.deep.equal ['Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP']
+
+  # TODO: talk to API of disposable IPFS node, or start a real one
+  xit 'can fetch an IPFS tree', ->
     @timeout 30000
     result = run "#{ipfsNode.exec} init", relaxed: true #quiet: true,
     result = run "#{ipfsNode.exec} add -r -q #{path.join __dirname, '../fixtures/a'} | tail -n 1"#, quiet: true
@@ -54,8 +59,3 @@
         "QmfAHGP6WXEEsK75JbnXdQkzXojvmrHLSyqBMxdcndQASU -> QmSFxnK675wQ9Kc1uqWKyJUaNxvSc2BP5DbXCD3x93oq61"
         "QmfAHGP6WXEEsK75JbnXdQkzXojvmrHLSyqBMxdcndQASU -> QmdytmR4wULMd3SLo6ePF4s3WcRHWcpnJZ7bHhoj3QB13v"
       ]
-
-  it 'can add a file', ->
-    @timeout 10000
-    adaptor.put content: "Plz add me!\n"
-      .should.eventually.deep.equal ['Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP']


Add minimum height/width to gravatar to prevent too small scaled images


8 tests that check inferred values from design

--- test/tests/TextLayerTest.coffee
+++ test/tests/TextLayerTest.coffee
@@ -530,6 +530,38 @@
 				layerA.style.padding.should.equal "20px"

 	describe "Fonts", ->
+		it "should infer fontWeight from design", ->
+			text = new TextLayer({ "name": "text", "backgroundColor": null, "width": 162, "x": 107, "styledText": { "blocks": [{ "inlineStyles": [{ "startIndex": 0, "endIndex": 4, "css": { "fontSize": "80px", "WebkitTextFillColor": "rgb(0, 0, 0)", "whiteSpace": "pre", "letterSpacing": "0px", "lineHeight": "1.2", "tabSize": 4, "fontFamily": "\".SFNSDisplay-Heavy\", \"SFProDisplay-Heavy\", \"SFUIDisplay-Heavy\", \".SFUIDisplay-Heavy\", sans-serif" } }], "text": "Test" }] }, "height": 96, "constraintValues": { "left": null, "height": 96, "centerAnchorX": 0.5013333333333333, "width": 162, "top": null, "centerAnchorY": 0.50074962518740629 }, "blending": "normal", "autoSize": true, "y": 286 });
+			text.fontWeight.should.equal 800
+
+		it "should infer fontFamily from design", ->
+			text = new TextLayer({ "name": "text", "backgroundColor": null, "width": 162, "x": 107, "styledText": { "blocks": [{ "inlineStyles": [{ "startIndex": 0, "endIndex": 4, "css": { "fontSize": "80px", "WebkitTextFillColor": "rgb(0, 0, 0)", "whiteSpace": "pre", "letterSpacing": "0px", "lineHeight": "1.2", "tabSize": 4, "fontFamily": "\".SFNSDisplay-Heavy\", \"SFProDisplay-Heavy\", \"SFUIDisplay-Heavy\", \".SFUIDisplay-Heavy\", sans-serif" } }], "text": "Test" }] }, "height": 96, "constraintValues": { "left": null, "height": 96, "centerAnchorX": 0.5013333333333333, "width": 162, "top": null, "centerAnchorY": 0.50074962518740629 }, "blending": "normal", "autoSize": true, "y": 286 });
+			text.fontFamily.should.equal "-apple-system"
+
+		it "should infer letterSpacing from design", ->
+			text = new TextLayer({ "name": "text", "backgroundColor": null, "width": 162, "x": 107, "styledText": { "blocks": [{ "inlineStyles": [{ "startIndex": 0, "endIndex": 4, "css": { "fontSize": "80px", "WebkitTextFillColor": "rgb(0, 0, 0)", "whiteSpace": "pre", "letterSpacing": "0px", "lineHeight": "1.2", "tabSize": 4, "fontFamily": "\".SFNSDisplay-Heavy\", \"SFProDisplay-Heavy\", \"SFUIDisplay-Heavy\", \".SFUIDisplay-Heavy\", sans-serif" } }], "text": "Test" }] }, "height": 96, "constraintValues": { "left": null, "height": 96, "centerAnchorX": 0.5013333333333333, "width": 162, "top": null, "centerAnchorY": 0.50074962518740629 }, "blending": "normal", "autoSize": true, "y": 286 });
+			text.letterSpacing.should.equal 0
+
+		it "should infer textAlign from design", ->
+			text = new TextLayer({ "name": "text", "backgroundColor": null, "width": 162, "x": 107, "styledText": { "blocks": [{ "inlineStyles": [{ "startIndex": 0, "endIndex": 4, "css": { "fontSize": "80px", "WebkitTextFillColor": "rgb(0, 0, 0)", "whiteSpace": "pre", "letterSpacing": "0px", "lineHeight": "1.2", "tabSize": 4, "fontFamily": "\".SFNSDisplay-Heavy\", \"SFProDisplay-Heavy\", \"SFUIDisplay-Heavy\", \".SFUIDisplay-Heavy\", sans-serif" } }], "text": "Test" }] }, "height": 96, "constraintValues": { "left": null, "height": 96, "centerAnchorX": 0.5013333333333333, "width": 162, "top": null, "centerAnchorY": 0.50074962518740629 }, "blending": "normal", "autoSize": true, "y": 286 });
+			text.textAlign.should.equal "auto"
+
+		it "should infer fontWeight from design", ->
+			text = new TextLayer({"name":"text","backgroundColor":null,"width":70,"styledText":{"blocks":[{"inlineStyles":[{"startIndex":0,"endIndex":3,"css":{"fontSize":"40px","WebkitTextFillColor":"rgb(0, 0, 0)","whiteSpace":"pre","fontWeight":400,"letterSpacing":"0px","tabSize":4,"fontFamily":"\"Roboto-Regular\", \"Roboto\", sans-serif","lineHeight":"1.2"}}],"text":"Ben"}],"alignment":"center"},"height":48,"constraintValues":{"height":48,"width":70},"blending":"normal","autoSize":true});
+			text.fontWeight.should.equal 400
+
+		it "should infer fontFamily from design", ->
+			text = new TextLayer({"name":"text","backgroundColor":null,"width":70,"styledText":{"blocks":[{"inlineStyles":[{"startIndex":0,"endIndex":3,"css":{"fontSize":"40px","WebkitTextFillColor":"rgb(0, 0, 0)","whiteSpace":"pre","fontWeight":400,"letterSpacing":"0px","tabSize":4,"fontFamily":"\"Roboto-Regular\", \"Roboto\", sans-serif","lineHeight":"1.2"}}],"text":"Ben"}],"alignment":"center"},"height":48,"constraintValues":{"height":48,"width":70},"blending":"normal","autoSize":true});
+			text.fontFamily.should.equal "Roboto"
+
+		it "should infer letterSpacing from design", ->
+			text = new TextLayer({"name":"text","backgroundColor":null,"width":70,"styledText":{"blocks":[{"inlineStyles":[{"startIndex":0,"endIndex":3,"css":{"fontSize":"40px","WebkitTextFillColor":"rgb(0, 0, 0)","whiteSpace":"pre","fontWeight":400,"letterSpacing":"0px","tabSize":4,"fontFamily":"\"Roboto-Regular\", \"Roboto\", sans-serif","lineHeight":"1.2"}}],"text":"Ben"}],"alignment":"center"},"height":48,"constraintValues":{"height":48,"width":70},"blending":"normal","autoSize":true});
+			text.letterSpacing.should.equal 0
+
+		it "should infer textAlign from design", ->
+			text = new TextLayer({"name":"text","backgroundColor":null,"width":70,"styledText":{"blocks":[{"inlineStyles":[{"startIndex":0,"endIndex":3,"css":{"fontSize":"40px","WebkitTextFillColor":"rgb(0, 0, 0)","whiteSpace":"pre","fontWeight":400,"letterSpacing":"0px","tabSize":4,"fontFamily":"\"Roboto-Regular\", \"Roboto\", sans-serif","lineHeight":"1.2"}}],"text":"Ben"}],"alignment":"center"},"height":48,"constraintValues":{"height":48,"width":70},"blending":"normal","autoSize":true});
+			text.textAlign.should.equal "center"
+
 		it "sets the weight if the font property is set", ->
 			l = new TextLayer
 				font: {fontFamily: "Raleway", fontWeight: 800}


div fix

div fix

--- coffee/lib/formatters.coffee
+++ coffee/lib/formatters.coffee
@@ -85,7 +85,7 @@
     IMG_PREVIEW_FORMATTER:
       [/([^<]+)<\/a>/g
       , (_m, link, ext, alt) ->
-        "<div class=\"preview\""
+        "<div class=\"preview\"
"
       ]

     markdown: (raw) ->


Added Greek letters that I forgot about.


Move report form error to getInitialState

--- app/talk/comment-report-form.cjsx
+++ app/talk/comment-report-form.cjsx
@@ -9,6 +9,8 @@

   propTypes:
     comment: React.PropTypes.object
+
+  getInitialState: ->
     error: ''

   onSubmit: (e) ->


polls feed

polls feed

--- apps/root/client/feed_view.coffee
+++ apps/root/client/feed_view.coffee
@@ -1,4 +1,6 @@
+_ = require 'underscore'
 Backbone = require 'backbone'
+Poller = require 'backbone-poller'
 sd = require("sharify").data
 mediator = require '../../../lib/mediator.coffee'
 InfiniteView = require '../../../components/pagination/infinite_view.coffee'
@@ -6,28 +8,41 @@
 BlockCollectionView = require '../../../components/block_collection/client/block_collection_view.coffee'
 BlockView = require '../../../components/block_collection/client/block_view.coffee'
 User = require '../../../models/user.coffee'
+
+
 feedTemplate = -> require('../../../components/feed/templates/feed.jade') arguments...

 module.exports = class FeedView extends Backbone.View
+  count: 0

   initialize: ->
     mediator.trigger 'load:start'

+    @collection.fetch success: @initialFetch
     @collection.on "sync", @render, @
-    @collection.fetch
-      success: =>
-        @setSharedBlocks()
-        mediator.trigger 'load:stop'
-
-        if sd.FEED_TYPE is 'primary'
-          # notifications api updates to be paginated :/
-          @infinite_view = new InfiniteView
-            context: @$el
-            collection: @collection
-            itemSelector: @$el
-

     @current_user = new User sd.CURRENT_USER
+
+  initialFetch: =>
+    @setSharedBlocks()
+    @startPolling()
+
+    mediator.trigger 'load:stop'
+
+    _.defer => @count = mediator.shared.blocks.length
+
+    if sd.FEED_TYPE is 'primary'
+      # notifications api updates to be paginated :/
+      @infinite_view = new InfiniteView
+        context: @$el
+        collection: @collection
+        itemSelector: @$el
+
+  startPolling: ->
+    poller = Poller.get(@collection,
+      delay: [3000]
+      delayed: 10000
+    ).start()

   render: ->
     @$el.html feedTemplate(feed: @collection.models, user: @current_user)
@@ -36,8 +51,14 @@

     @$('.grid__block').each @initBlockView

+  updateTitle: (diff) =>
+    window.document.title = "Arena (#{diff})"
+
   setSharedBlocks: ->
-    mediator.shared.blocks = new Blocks @collection.getAllItems()
+    blocks = new Blocks @collection.getAllItems()
+    if mediator.shared.blocks?
+      @updateTitle(diff) if (diff = blocks.length - mediator.shared.blocks.length)
+    mediator.shared.blocks = blocks

   initBlockView: (index, el) =>
     $block = $(el)


Use 'no' instead of 'false' for Expectation.called value.

--- src/TinyMockJS.coffee
+++ src/TinyMockJS.coffee
@@ -137,7 +137,7 @@
     @_args = []
     @_returns = undefined
     @_throws = undefined
-    @_called = false
+    @_called = no
     @_install_mock_method()
     all_expectations.push(@)


avoid accumulating

avoid accumulating

--- src/attributes.coffee
+++ src/attributes.coffee
@@ -55,6 +55,7 @@
   each_opcode: (fn) ->
     for i in [0..@code_len] when i of @opcodes
       fn(i, @opcodes[i])
+    return

   get_attribute: (name) ->
     for attr in @attrs then if attr.name is name then return attr


DiaScene: drawConnectionLine improvements

--- lib/components/dia/diascene.coffee
+++ lib/components/dia/diascene.coffee
@@ -257,18 +257,15 @@
   updateScene: _.throttle ->

     @cleanup @realCanvas
-    @drawConnectionLine connection for connection in @connections
-    @drawConnectionLine connection for connection in @fakeConnections
-  , 300
-
-  drawConnectionLine:({source, target, options})->
-
-    return unless source or target
-
-    options    or= {}
-    activeColor  = @getOption 'lineColorActive'
-    lineDashes   = @getOption 'lineDashes'
-    lineColor    = @getOption 'lineColor'
+
+
+
+  drawConnectionLine: ({ source, target, options = {} }) ->
+
+    return  unless source or target
+
+    { lineColorActive, lineDashes, transfers = []
+      lineColor, lineWidth, curveDistance } = @getOptions()

     @realContext.beginPath()

@@ -276,10 +273,10 @@
                 else if target.dia in @activeDias then target

     if activeDia
-      lineColor  = options.lineColor  or \
-                   (activeDia.dia.getOption 'colorTag') or activeColor
-      lineDashes = options.lineDashes or \
-                   (activeDia.dia.getOption 'lineDashes') or lineDashes
+      lineColor  = options.lineColor  ? \
+                   (activeDia.dia.getOption 'colorTag') ? lineColorActive
+      lineDashes = options.lineDashes ? \
+                   (activeDia.dia.getOption 'lineDashes') ? lineDashes

     sJoint = source.dia.getJointPos source.joint
     tJoint = target.dia.getJointPos target.joint
@@ -294,10 +291,10 @@
     @realContext.strokeStyle = lineColor
     @realContext.setLineDash lineDashes  if lineDashes.length > 0

-    @realContext.moveTo sJoint.x, sJoint.y
-
-    cd = @getOption 'curveDistance'
-    [sx, sy, tx, ty] = [0, 0, 0, 0]
+    @realContext.moveTo s.x, s.y
+
+    cd = curveDistance
+    [ sx, sy, tx, ty ] = [ 0, 0, 0, 0 ]
     if source.joint in ["top", "bottom"]
       sy = if source.joint is "top" then -cd else cd
     else if source.joint in ["left", "right"]
@@ -310,11 +307,12 @@
     @realContext.bezierCurveTo(sJoint.x + sx, sJoint.y + sy, \
                                tJoint.x + tx, tJoint.y + ty, \
                                tJoint.x, tJoint.y)
-
-    @realContext.lineWidth = @getOption 'lineWidth'
+    @realContext.lineWidth = lineWidth
+
     @realContext.stroke()

-  addFakeConnection:(connection)->
+  addFakeConnection: (connection) ->
+
     @drawConnectionLine connection
     @fakeConnections.push connection


de serialize

de serialize

--- lib/decoration-example-view.coffee
+++ lib/decoration-example-view.coffee
@@ -188,10 +188,8 @@
   # takes in serialized_markers
   deserialize_markers = (serialized_markers) ->
     marker_arr = []
-    serialized_markers.map (object) ->
-      marker_arr.push(createDecoration(object[0], object[1] ))
-
-    return marker_arr
+    serialized_markers.map (serialized_highlight) ->
+      @createHighlight(serialized_highlight.color, Range.deserialize(serialized_highlight.range))


Fixed parsing problem and fixed last section not getting parsed.

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

 commentRegexStr = "\\s*(?:@(\\w+))?(?:\\s*(.*))?"
 commentRegex    = new RegExp commentRegexStr
-argsRegex       = /\{([\w\|]+)}\s([\w\d_-]+)\s?(.*)/
+argsRegex       = /\{([\w\|\s]+)}\s([\w\d_-]+)\s?(.*)/
 returnRegex     = /\{([\w\|]+)}\s?(.*)/
 NEW_LINE        = /\n\r?/
 cwd             = process.cwd()
@@ -101,10 +101,10 @@
 # @param {Object} object Section object with information
 # @param {String} key Name of the tag
 # @param {String} value Value to be set
-# @param {Object} options Tag definitions
+# @param {Object} options Tag definitions (default {})
 # @returns {Number|String} Return the length of the array or value
 #
-_setAttribute = (object, key, value, options) ->
+_setAttribute = (object, key, value, options = {}) ->
   if options.hasMultiple? and options.hasMultiple
     object[key] ?= []
     object[key].push value
@@ -131,6 +131,21 @@
   currentSection = {}
   argObject      = {}

+  #
+  # @chalk function
+  # @function
+  # @private
+  # @name _getMultiLineKey
+  # @description
+  # multiLineKey has value a.b when used in multiple line argument
+  # description. This function get the correct key based on the index
+  #
+  _getMultiLineKey = (index) ->
+    keys  = multiLineKey.split "."
+    index = keys.length + index if index < 0
+    return "" if keys.length is 0 or index < 0 or index > keys.length
+    keys[index]
+
   for line in code.split(NEW_LINE)
     # Check for starting and ending comment block
     if (match = line.match lang.blockRegex)
@@ -146,13 +161,11 @@

       if key?
         if multiLineKey and not _(argObject).isEmpty()
-          do (multiLineKey, currentSection, argObject, definitions) ->
-            keys = multiLineKey.split "."
-            _setAttribute(currentSection,
-              keys[0],
-              argObject,
-              definitions[keys[0]]
-            )
+          _setAttribute(currentSection,
+            _getMultiLineKey(0),
+            argObject,
+            definitions[_getMultiLineKey(0)]
+          )

           argObject = {}

@@ -225,28 +238,24 @@
       # When key doesn't exist and multi line key is set,
       # add the value to the original key
       if multiLineKey and value?
-        do (argObject, multiLineKey, value, currentSection, definitions) ->
-          object = if _(argObject).isEmpty() then currentSection else argObject
-          keys   = multiLineKey.split(".")
-          value += "  \n" if value
-          _setAttribute(object,
-            keys[keys.length - 1],
-            value,
-            definitions[keys[keys.length - 1]]
-          )
+        object = if _(argObject).isEmpty() then currentSection else argObject
+        value += "  \n" if value
+        _setAttribute(object,
+          _getMultiLineKey(-1),
+          value,
+          definitions[_getMultiLineKey(-1)]
+        )

     else
       if hasComment and not _(currentSection).isEmpty()

         # Check if there is remaining argObject to be cleared
         if multiLineKey and not _(argObject).isEmpty()
-          do (multiLineKey, currentSection, argObject, definitions) ->
-            keys = multiLineKey.split "."
-            _setAttribute(currentSection,
-              keys[0],
-              argObject,
-              definitions[keys[0]]
-            )
+          _setAttribute(currentSection,
+            _getMultiLineKey(0),
+            argObject,
+            definitions[_getMultiLineKey(0)]
+          )

           multiLineKey = ""
           argObject    = {}
@@ -255,6 +264,11 @@
         currentSection = {}

       hasComment = false
+
+  # Push the last section if there was no new line at the
+  # end of the file
+  if not _(currentSection).isEmpty()
+    allSections.push currentSection

   return allSections


Load fixture items synchronously

Load fixture items synchronously

--- lib/symfio/plugins/fixtures.coffee
+++ lib/symfio/plugins/fixtures.coffee
@@ -80,7 +80,7 @@
                             item = new model data
                             item.save callback

-                        async.forEach fixture, itemWorker, ->
+                        async.forEachSeries fixture, itemWorker, ->
                             callback()
             ], callback


Kai ももうちょっとコード整理した

Kai ももうちょっとコード整理した

デフォルトのパス変更

--- src/coffee/kai.coffee
+++ src/coffee/kai.coffee
@@ -6,13 +6,15 @@
   Kai =()->
     throw new Error('インスタンス化できません')

+  Kai.version = 0.8
+
   Kai.host = ''

   Kai.ASSET_DIR = 'assets'

   Kai.CSS_DIR = 'css'

-  Kai.SCRIPT_DIR = 'js'
+  Kai.SCRIPT_DIR = 'scripts'

   Kai.IMAGE_DIR = 'images'

@@ -31,20 +33,28 @@

   Kai.RELATIVE = 'relative'

+  # 結合済みのパスが云々
+  # Kai.useCache = false
+  # _cache = {}
+
+
   ###Kai.init###
   ##初期化
   Kai.init=(options)->
+    if not options
+      return
     loc = window.location
-    Kai.ASSET_DIR = if options.packageRoot? then options.packageRoot else 'assets'
-    Kai.CSS_DIR = if options.css? then options.css else 'css'
-    Kai.SCRIPT_DIR = if options.js? then options.js else 'js'
-    Kai.IMAGE_DIR = if options.images? then options.images else 'images'
-    Kai.DATA_DIR = if options.data? then options.data else 'data'
-    Kai.PC_DIR = if options.pc? then options.pc else 'pc'
-    Kai.SP_DIR = if options.sp? then options.sp else 'sp'
-    Kai.TABLET_DIR = if options.tablet then options.tablet else 'tablet'
-    Kai.host = if options.host? then options.host else loc.host
-    Kai.root = if options.root? then options.root else '/'
+    owner = Object.prototype.hasOwnProperty
+    Kai.ASSET_DIR = if owner.call(options, 'packageRoot') then options.packageRoot else 'assets'
+    Kai.CSS_DIR = if owner.call(options, 'css') then options.css else 'css'
+    Kai.SCRIPT_DIR = if owner.call(options, 'scripts') then options.scripts else 'scripts'
+    Kai.IMAGE_DIR = if owner.call(options, 'images') then options.images else 'images'
+    Kai.DATA_DIR = if owner.call(options, 'data') then options.data else 'data'
+    Kai.PC_DIR = if owner.call(options, 'pc') then options.pc else 'pc'
+    Kai.SP_DIR = if owner.call(options, 'sp') then options.sp else 'sp'
+    Kai.TABLET_DIR = if owner.call(options, 'tablet') then options.tablet else 'tablet'
+    Kai.host = if owner.call(options, 'host') then options.host else loc.host
+    Kai.root = if owner.call(options, 'root') then options.root else '/'
     return

   ###Kai.GET_CSS_PATH###
@@ -109,9 +119,9 @@
     if Kai.root?
       fragment = fragment.replace(Kai.root, '')
     level = fragment.split('/').length - 1
-
+
     result = _GET_PATH(asset, device)
-
+
     i = 0
     while i < level
       result = '../' + result


Unset routing information after sending Login7 data.

--- src/connection.coffee
+++ src/connection.coffee
@@ -487,10 +487,6 @@
       host: @routingData?.server || @config.server
       port: @routingData?.port || port

-    # Unset routing information so we reuse the initial config on the next
-    # connection attempt again.
-    @routingData = undefined
-
     if @config.options.localAddress
       connectOpts.localAddress = @config.options.localAddress

@@ -626,6 +622,10 @@
       tdsVersion: @config.options.tdsVersion
       initDbFatal: not @config.options.fallbackToDefaultDb
       readOnlyIntent: @config.options.readOnlyIntent
+
+    # Unset routing information so we reuse the initial config on the next
+    # connection attempt again.
+    @routingData = undefined

     payload = new Login7Payload(loginData)
     @messageIo.sendMessage(TYPE.LOGIN7, payload.data)


add readOnly, textModeAtStart, fix enablePaletteAtStart & setEditorState

--- src/controller.coffee
+++ src/controller.coffee
@@ -139,6 +139,7 @@
   # ## The Editor Class
   exports.Editor = class Editor
     constructor: (@wrapperElement, @options) ->
+      @readOnly = false
       @paletteGroups = @options.palette
       @showPaletteInTextMode = @options.showPaletteInTextMode ? false
       @paletteEnabled = @options.enablePaletteAtStart ? true
@@ -302,9 +303,11 @@
       @redrawMain()
       @rebuildPalette()

-      # If we were given an unrecognized mode, flip into text mode
-      unless @mode?
-        @setEditorState false
+      # If we were given an unrecognized mode or asked to start in text mode,
+      # flip into text mode here
+      blockMode = @mode? && !@options.textModeAtStart
+      # Always call @setEditorState to ensure palette is positioned properly
+      @setEditorState blockMode

       return this

@@ -320,6 +323,13 @@

     getMode: ->
       @options.mode
+
+    setReadOnly: (readOnly) ->
+      @readOnly = readOnly
+      @aceEditor.setReadOnly readOnly
+
+    getReadOnly: ->
+      @readOnly

     # ## Foundational Resize
     # At the editor core, we will need to resize
@@ -622,6 +632,8 @@
                     point.y - gbr.top + @scrollOffsets.palette.y)

   Editor::trackerPointIsInElement = (point, element) ->
+    if @readOnly
+      return false
     if not element.offsetParent?
       return false
     gbr = element.getBoundingClientRect()
@@ -645,6 +657,9 @@
   # Simple function for going through a linked-list block
   # and seeing what the innermost child is that we hit.
   Editor::hitTest = (point, block) ->
+    if @readOnly
+      return null
+
     head = block.start; seek = block.end

     until head is seek
@@ -1904,8 +1919,11 @@
   escapeString = (str) ->
     str[0] + str[1...-1].replace(/(\'|\"|\n)/g, '\\$1') + str[str.length - 1]

-  # Convenince function for setting the text input
+  # Convenience function for setting the text input
   Editor::setTextInputFocus = (focus, selectionStart = null, selectionEnd = null) ->
+    if @readOnly
+      return
+
     if focus?.id of @extraMarks
       delete @extraMarks[focus?.id]

@@ -2819,6 +2837,8 @@
       @redrawMain()

   hook 'keydown', 0, (event, state) ->
+    if @readOnly
+      return
     if event.which isnt BACKSPACE_KEY
       return
     if state.capturedBackspace
@@ -2863,6 +2883,8 @@
     @handwrittenBlocks = []

   hook 'keydown', 0, (event, state) ->
+    if @readOnly
+      return
     if event.which is ENTER_KEY
       if not @textFocus? and not event.shiftKey
         @setTextInputFocus null
@@ -2895,6 +2917,8 @@
         @setTextInputFocus null; @redrawMain()

   hook 'keyup', 0, (event, state) ->
+    if @readOnly
+      return
     # prevents routing the initial enter keypress to a new handwritten
     # block by focusing the block only after the enter key is released.
     if event.which is ENTER_KEY
@@ -3826,10 +3850,13 @@
     if useBlocks
       @setValue @getAceValue()

-      @dropletElement.style.top =
+      @dropletElement.style.top = '0px'
+      if @paletteEnabled
         @paletteWrapper.style.top = @paletteWrapper.style.left = '0px'
-
-      @dropletElement.style.left = "#{@paletteWrapper.offsetWidth}px"
+        @dropletElement.style.left = "#{@paletteWrapper.offsetWidth}px"
+      else
+        @paletteWrapper.style.top = @paletteWrapper.style.left = '-9999px'
+        @dropletElement.style.left = '0px'

       @aceElement.style.top = @aceElement.style.left = '-9999px'
       @currentlyUsingBlocks = true
@@ -3842,6 +3869,8 @@
       @resizeBlockMode(); @redrawMain()

     else
+      paletteVisibleInNewState = @paletteEnabled and @showPaletteInTextMode
+
       oldScrollTop = @aceEditor.session.getScrollTop()

       @setAceValue @getValue()
@@ -3849,9 +3878,18 @@

       @aceEditor.session.setScrollTop oldScrollTop

-      @dropletElement.style.top = @dropletElement.style.left =
+      @dropletElement.style.top = @dropletElement.style.left = '-9999px'
+      if paletteVisibleInNewState
+        @paletteWrapper.style.top = @paletteWrapper.style.left = '0px'
+      else
         @paletteWrapper.style.top = @paletteWrapper.style.left = '-9999px'
-      @aceElement.style.top = @aceElement.style.left = '0px'
+
+      @aceElement.style.top = '0px'
+      if paletteVisibleInNewState
+        @aceElement.style.left = "#{@paletteWrapper.offsetWidth}px"
+      else
+        @aceElement.style.left = '0px'
+
       @currentlyUsingBlocks = false

       @lineNumberWrapper.style.display = 'none'
@@ -4234,6 +4272,8 @@
         pressedXKey = false

     @copyPasteInput.addEventListener 'input', =>
+      if @readOnly
+        return
       if pressedVKey
         try
           str = @copyPasteInput.value; minIndent = Infinity


Refs #68; Began work on the option method for charts.

--- src/core/chart.coffee
+++ src/core/chart.coffee
@@ -46,6 +46,13 @@
     width: 320
     height: 240

+  trigger_options = (instance, scope, prefix) ->
+    for key, value of scope
+      if Epoch.isObject(value)
+        trigger_options(instance, value, prefix + '.' + key)
+      else
+        instance.trigger "option:#{prefix.substr(1)}.#{key}"
+
   # Creates a new base chart.
   # @param [Object] options Options to set for this chart.
   # @option options [Integer] width Sets an explicit width for the visualization (optional).
@@ -64,6 +71,45 @@
     else
       @width = defaults.width unless @width?
       @height = defaults.height unless @height?
+
+  # Gets and sets chart options after initialization. When using this as a setter, if an
+  # option actually changes value then an event option:NAME will be triggered
+  # where NAME is the name of the option.
+  #
+  # This method can be called in four different ways:
+  #

+  #
chart.option() - Returns a an object containing all of the options for the chart.

+  #
chart.option(key) - Returns a specific option. Use . to access nested options.

+  #
chart.option(key, value) - Sets an option and triggers the associated event.

+  #
chart.option(object) - Allows for the setting of multiple options at once.

+  #

+  option: (key, value) ->
+    return Epoch.Util.defaults({}, @options) if arguments.length == 0
+
+    if Epoch.isObject(key)
+      @options = Epoch.Util.defaults(key, @options)
+      trigger_options @, key, ''
+
+    if key? and value?
+      parts = key.split('.')
+      scope = @options
+      while parts.length and scope?
+        subkey = parts.shift()
+        if parts.length == 0
+          scope[subkey] = value
+          @trigger "option:#{key}"
+          return
+        scope = scope[subkey]
+
+    if key?
+      parts = key.split('.')
+      scope = @options
+      while parts.length and scope?
+        subkey = parts.shift()
+        scope = scope[subkey]
+      return scope
+
+

   # Determines if the chart is currently visible in a document.
   # @return [Boolean] True if the chart is visible, false otherwise.


Skip bad subjects

Skip bad subjects

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -389,14 +389,13 @@

   loadFrame: (image, src, attempts = 1) ->
     loadImage(src).then (img) =>
-      # Apparently long stings of AAAAA... mean there was some kinda problem.
-      # TODO: Figure out why this happens. Seems like it's random and only in IE.
       if attempts < 10 and !!~img.src.indexOf (new Array 100).join 'A'
-        # console?.log "Error loading #{src} (#{attempts})"
-        setTimeout => # Allow the transport frame to clean up, then try again.
+        setTimeout =>
           @loadFrame image, src, attempts + 1
+      else if attempts >= 10
+        console.log("Gave up on #{src}")
+        Subject.next()
       else
-        # console?.log "Loaded #{src} after #{attempts} attempts"
         image.attr 'xlink:href', img.src

   onClickFourUp: ->


my candidate scenarios

my candidate scenarios

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -571,8 +571,28 @@
   submit: (e) =>

     # Add annotated subject to My Candidates Talk collection
+
+    # Conditionally push to My Candidates collection
     if _.keys(@annotations).length > 0
-      @trigger 'addToTalk', @talkIds['My Candidates'], @classification.subject.zooniverse_id
+       @trigger 'addToTalk', @talkIds['My Candidates'], @classification.subject.zooniverse_id
+
+    # # Excuse this terrible code.
+    # # Case 1 Not training subject and annotated
+    # if not @isTrainingSubject and _.keys(@annotations).length > 0
+    #   @trigger 'addToTalk', @talkIds['My Candidates'], @classification.subject.zooniverse_id
+    #
+    # # Case 2 Simulation present but not marked
+    # if @isTrainingSubject
+    #   if @classification.subject.metadata.training[0].type in ['lensing cluster', 'lensed quasar', 'lensed galaxy']
+    #     if _.keys(@annotations).length > 0
+    #       if not @isLensMarked
+    #         @trigger 'addToTalk', @talkIds['My Candidates'], @classification.subject.zooniverse_id
+    #
+    # # Case 3 Simulation present and marked
+    # if @isTrainingSubject
+    #   if @classification.subject.metadata.training[0].type in ['lensing cluster', 'lensed quasar', 'lensed galaxy']
+    #     if _.keys(@annotations).length > 1
+    #       @trigger 'addToTalk', @talkIds['My Candidates'], @classification.subject.zooniverse_id

     # Process annotations and push to API
     for index, annotation of @annotations


Mouse interactions

Mouse interactions

--- app/controllers/fitsviewer.coffee
+++ app/controllers/fitsviewer.coffee
@@ -102,8 +102,8 @@

     # TODO: Set this dynamically
     [@width, @height] = [424, 424]
-    canvas  = WebGL.setupCanvas(@container, @width, @height)
-    @gl     = WebGL.create3DContext(canvas)
+    @canvas = WebGL.setupCanvas(@container, @width, @height)
+    @gl     = WebGL.create3DContext(@canvas)
     @ext    = @gl.getExtension('OES_texture_float')

     unless @ext
@@ -152,6 +152,53 @@
     @minScale = 1 / (FITSViewer.viewportWidth * FITSViewer.viewportWidth)
     @maxScale = 2
     @drag = false
+
+    @canvas.onmousedown = (e) =>
+      @drag = true
+
+      @xOldOffset = @xOffset
+      @yOldOffset = @yOffset
+      @xMouseDown = e.clientX
+      @yMouseDown = e.clientY
+
+    @canvas.onmouseup = (e) =>
+      @drag = false
+
+      # Prevents a NaN from being sent to the GPU
+      return null unless @xMouseDown?
+
+      xDelta = e.clientX - @xMouseDown
+      yDelta = e.clientY - @yMouseDown
+      @xOffset = @xOldOffset + (xDelta / @canvas.width / @scale * 2.0)
+      @yOffset = @yOldOffset - (yDelta / @canvas.height / @scale * 2.0)
+      @drawScene()
+
+    @canvas.onmousemove = (e) =>
+      xDelta = -1 * (@canvas.width / 2 - e.offsetX) / @canvas.width / @scale * 2.0
+      yDelta = (@canvas.height / 2 - e.offsetY) / @canvas.height / @scale * 2.0
+
+      x = ((-1 * (@xOffset + 0.5)) + xDelta) + 0.5 << 0
+      y = ((-1 * (@yOffset + 0.5)) + yDelta) + 0.5 << 0
+
+      return unless @drag
+
+      xDelta = e.clientX - @xMouseDown
+      yDelta = e.clientY - @yMouseDown
+
+      @xOffset = @xOldOffset + (xDelta / @canvas.width / @scale * 2.0)
+      @yOffset = @yOldOffset - (yDelta / @canvas.height / @scale * 2.0)
+
+      @drawScene()
+
+    @canvas.onmouseout = (e) =>
+      @drag = false
+
+    @canvas.onmouseover = (e) =>
+      @drag = false
+
+    # Listen for the mouse wheel
+    @canvas.addEventListener('mousewheel', @wheelHandler, false)
+    @canvas.addEventListener('DOMMouseScroll', @wheelHandler, false)

   addTexture: (band, data) =>
     address = "TEXTURE#{@textureCount}"
@@ -205,5 +252,16 @@
     @gl.useProgram(@program)

     @drawScene()
+
+  wheelHandler: (e) =>
+    e.preventDefault()
+    e.stopPropagation()
+    factor = if e.shiftKey then 1.01 else 1.1
+    @scale *= if (e.detail or e.wheelDelta) < 0 then factor else 1 / factor
+
+    # Probably not the most efficient way to do this ...
+    @scale = if @scale > @maxScale then @maxScale else @scale
+    @scale = if @scale < @minScale then @minScale else @scale
+    @drawScene()

 module.exports = FITSViewer


send directly to delegate tab if account is a delegate

--- app/js/controllers/account.coffee
+++ app/js/controllers/account.coffee
@@ -41,6 +41,7 @@
         $scope.account_name = acct.name
         Wallet.set_current_account(acct) if acct.is_my_account
         if $scope.account.delegate_info
+            $state.go "account.priceFeed" # send to delegate info tab if delegate
             Blockchain.get_asset(0).then (asset_type) ->
                 $scope.account.delegate_info.pay_balance_asset = Utils.asset($scope.account.delegate_info.pay_balance, asset_type)


fix for issue #364 - number of delegates selected increased to 10000

--- app/js/services/blockchain.coffee
+++ app/js/services/blockchain.coffee
@@ -173,7 +173,7 @@
     refresh_delegates: ->
         # TODO: delegates paginator is needed
         @avg_act_del_pay_rate=0
-        @q.all({dels: @blockchain_api.list_delegates(0, 1000), config: @get_info()}).then (results) =>
+        @q.all({dels: @blockchain_api.list_delegates(0, 10000), config: @get_info()}).then (results) =>
             for i in [0 ... results.config.delegate_num]
                 @active_delegates[i] = @populate_delegate(results.dels[i], true)
                 @id_delegates[results.dels[i].id] = results.dels[i]


Template test for four_thirds images

Template test for four_thirds images

--- apps/artists/test/template.coffee
+++ apps/artists/test/template.coffee
@@ -28,7 +28,9 @@
         benv.expose { $: benv.require 'jquery' }

         @artistCollection = new Artists(
-          _.times(2, -> new Artist(fabricate('artist')))
+          _.times(2, ->
+            new Artist(fabricate('artist', { image_versions: ['four_thirds', 'tall'] }))
+          )
         )

         @featuredLinksCollection = new Items(
@@ -79,3 +81,6 @@
       $links = @$template.find('.avant-garde-jump-link')
       $links.length.should.equal 2
       $links.first().text().should.equal fabricate('gene').name
+
+    it 'uses four_thirds images', ->
+      @$template.find('.afg-artist img').attr('src').should.include 'four_thirds'


Add missing import to logger module.

Add missing import to logger module.

--- src/lib/logger.coffee
+++ src/lib/logger.coffee
@@ -1,6 +1,7 @@
 Docker = require 'dockerode'
 PUBNUB = require 'pubnub'
 Promise = require 'bluebird'
+es = require 'event-stream'

 initialised = new Promise (resolve) ->
 	exports.init = (config) ->


Redirect history operations in renderer to browser

--- atom/renderer/lib/override.coffee
+++ atom/renderer/lib/override.coffee
@@ -74,6 +74,12 @@
 window.prompt = ->
   throw new Error('prompt() is and will not be supported.')

+# Forward history operations to browser.
+window.history.back = ->
+  remote.getCurrentWebContents().goBack()
+window.history.forward = ->
+  remote.getCurrentWebContents().goForward()
+
 window.opener =
   postMessage: (message, targetOrigin='*') ->
     ipc.send 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_OPENER_POSTMESSAGE', message, targetOrigin


fix calling function

fix calling function

--- scripts/chat-logger.coffee
+++ scripts/chat-logger.coffee
@@ -93,7 +93,7 @@
     msg.reply date_url(msg.envelope.room, now.getUTCFullYear(), now.getUTCMonth() + 1, now.getUTCDate())

   robot.respond /log\s+yesterday/, (msg) ->
-    yest = new Date(Date.yest() - 1000 * 60 * 60 * 24)
+    yest = new Date(Date.now() - 1000 * 60 * 60 * 24)
     msg.reply date_url(msg.envelope.room, yest.getUTCFullYear(), yest.getUTCMonth() + 1, yest.getUTCDate())

   robot.respond /log\s+([1-9]\d{3})?\/([1-9]\d{0,1})\/([1-9]\d{0,1})/, (msg) ->


Fix feed item listing.

Fix feed item listing.

--- scripts/chat-logger.coffee
+++ scripts/chat-logger.coffee
@@ -210,7 +210,7 @@
               if result.length == 0 or _.last(_.last result).date - msg.date >= FEED_DIVIDE_THRESHOLD
                 result.push [msg]
               else
-                _.first(result).unshift msg
+                _.last(result).unshift msg

               throw "break" if result.length > ITEM_COUNT
             , null
@@ -231,7 +231,7 @@
         data.push msg for msg in src_data
     chat_data()[room] = new_logs

-    result.reverse().slice(0, ITEM_COUNT).map (v) ->
+    result.slice(0, ITEM_COUNT).map (v) ->
       d = new Date _.first(v).date

       title: d.toUTCString()


terminal rollback

terminal rollback

--- core/KDWindowController.coffee
+++ core/KDWindowController.coffee
@@ -15,37 +15,6 @@
     @scrollingEnabled = yes
     @bindEvents()
     @setWindowProperties()
-<<<<<<< HEAD
-=======
-
-    @layers = layers = []
-    document.body.addEventListener 'mousedown', (e)=>
-      $('.twipsy').remove() # temporary for beta
-      @setDragInAction no # for cases when dragleave doesn't fire
-      lastLayer = layers[layers.length-1]
-
-      if lastLayer and $(e.target).closest(lastLayer?.$()).length is 0
-        lastLayer.propagateEvent (KDEventType: 'ReceivedClickElsewhere'), e
-    , yes
-
-    document.body.addEventListener 'mouseup', (e)=>
-      @propagateEvent (KDEventType: 'ReceivedMouseUpElsewhere'), e
-    , yes
-
-    window.onbeforeunload = (event) =>
-      # event.preventDefault();
-      # event.stopImmediatePropagation();
-      # event.stopPropagation();
-
-      event or= window.event
-      msg = "Please make sure that you saved all your work."
-
-      event.returnValue = msg if event # For IE and Firefox prior to version 4
-
-      return msg unless window.location.hostname is "localhost"
-
-
->>>>>>> 61023c8c8d984971181cd539bc49241df2437de3
     super

   addLayer: (layer)->


force reload kludge

force reload kludge

--- core/KDWindowController.coffee
+++ core/KDWindowController.coffee
@@ -115,6 +115,12 @@

     window.addEventListener 'beforeunload', @bound "beforeUnload"

+    # TODO: this is a kludge we needed.  sorry for this.  Move it someplace better C.T.
+    @utils.wait 15000, =>
+      KD.remote.api.JSystemStatus.on 'forceReload', =>
+        window.removeEventListener 'beforeunload', @bound 'beforeUnload'
+        location.reload()
+
     @utils.repeat 1000, do (cookie = $.cookie 'clientId') => =>
       if cookie? and cookie isnt $.cookie 'clientId'
         window.removeEventListener 'beforeunload', @bound 'beforeUnload'


finish skeleton with basic commands

finish skeleton with basic commands

--- scripts/pagerv2_commands.coffee
+++ scripts/pagerv2_commands.coffee
@@ -40,6 +40,15 @@
 #   hubot pd note <#,#,#> <note> - create a note for incidents <#,#,#>
 #   hubot pd notes <#>           - read notes for incident <#>
 #
+#   hubot pd [who is] oncall        - tells who is currently on call
+#   hubot pd [who is] next [oncall] - tells who is next on call
+#   hubot pd escalation             - tells who is involved in the escalation process
+#
+#   hubot pd maintenances           - lists currently active maintenances
+#   hubot pd stfu|down [for] <duration> [because <reason>] - creates a maintenance
+#   hubot pd up|end|back <maintenance> - ends <maintenance>
+#
+#
 # Author:
 #   mose

@@ -185,3 +194,42 @@
     [ _, incident ] = res.match
     res.send "Not yet implemented"
     res.finish()
+
+# TODO
+#   hubot pd [who is] oncall     - tells who is currently on call
+  robot.respond /pd (?:who(?: is|'s) )?(?:on ?call)\s+$/, (res) ->
+    res.send "Not yet implemented"
+    res.finish()
+
+# TODO
+#   hubot pd [who is] next [oncall] - tells who is next on call
+  robot.respond /pd (?:who(?: is|'s) )?next(?: on ?call)?\s+$/, (res) ->
+    res.send "Not yet implemented"
+    res.finish()
+
+# TODO
+#   hubot pd escalation          - tells who is involved in the escalation process
+  robot.respond /pd escalation\s+$/, (res) ->
+    res.send "Not yet implemented"
+    res.finish()
+
+# TODO
+#   hubot pd maintenances           - lists currently active maintenances
+  robot.respond /pd maintenances?\s+$/, (res) ->
+    res.send "Not yet implemented"
+    res.finish()
+
+# TODO
+#   hubot pd stfu|down [for] <duration> [because <reason>] - creates a maintenance
+  robot.respond /pd (?:stfu|down)(?: for)?\s*([0-9]+)?(?: min(?:utes)?)?(?: because (.+))?\s+$/,
+    (res) ->
+    [ _, duration ] = res.match
+    res.send "Not yet implemented"
+    res.finish()
+
+# TODO
+#   hubot pd up|end|back <maintenance> - ends <maintenance>
+  robot.respond /pd (?:up|back|end) ([A-Z0-9]+)\s+$/, (res) ->
+    [ _, maintenance ] = res.match
+    res.send "Not yet implemented"
+    res.finish()


Tests: Activity: PostWith: Fixed embed image.

--- tests/src/activity/postwith.coffee
+++ tests/src/activity/postwith.coffee
@@ -30,7 +30,7 @@
     # and renders it as a link, but if we continue typing it understands that
     # it is an image
     image    = 'http://placehold.it/200x100 hello world!'
-    selector = activitySelector + ' .activity-content-wrapper .embed-image-view img'
+    selector = activitySelector + ' .activity-content-wrapper .link-embed-box img'

     browser
       .click                  '[testpath="public-feed-link/Activity/Topic/public"]'


Newhome: beatified render html (kodinghome.coffee)

--- workers/social/lib/social/render/loggedout/kodinghome.coffee
+++ workers/social/lib/social/render/loggedout/kodinghome.coffee
@@ -30,144 +30,264 @@

       #{getHomeIntro()}
-

-

-

-

-

-

-

-

-

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-

-

  Activity

Topics

Members

Groups

Develop

0  Apps

 Docs / Jobs

Login

Register

-

-

-

-

-
guest-60.localhost

-

-
-

-  Drop files here!
-  /home/guest-60/Uploads
-

-
-

-

<canvas width="20" height="20"></canvas><canvas width="20" height="20"></canvas>
-
There is no attached VM

-

-

-

-

-

-

-

-

-

-

-     Koding for You
-

-

-    You have great ideas.  You want to meet brilliant minds, and bring those ideas to life.  You want to start simple.  Maybe soon you'll have a 10 person team, commanding 100s of servers.
-

-

-    You want to learn Python, Java, C, Go, Nodejs, HTML, CSS or Javascript or any other. Community will help you along the way.
-

-

-

-

-     Koding for Developers
-

-

-    You can have an amazing VM that is better than your laptop.  It's connected to internet 100x faster.  You can share it with anyone you wish. Clone git repos.  Test and iterate on your code without breaking your setup.
-

-

-    It's free. Koding is your new localhost, in the cloud.
-

-

-

-

-     Koding for Education
-

-

-    Have a group where your students enjoy the resources you provide to them. Have it private, invite-only, let them share, collaborate and submit their assignments together.10 students, or 10,000. Just 1 or 100s of computers.
-

-

-    Koding is your new classroom.
-

-

-

-

-     Koding for Business
-

-

-    When you hire someone, let them be in your environment in 5 minutes, collaborating with others, contributing code without sharing ssh keys, passwords. Stop cc'ing your team, stop looking for emails.
-

-

-    Koding is your new workspace.
-

-

YouDevelopersEducationBusiness

-

-

-
Simple Pricing

-
Try it and see if it's really as cool as we say

-

-
-      Your first VM
-      Free
-
-
-      Each additional VM
-      $5 / Month
-
-

-

-    Always on* $25 / Month
-    Extra RAM $10 / GB / Month

-    Extra Disk Space $1 / GB / Month
-    Firewall / Backend Builder $5 / Per VM / Month
-

-  Contact us for Education and Business pricing
-

-
-

-

-      © 2013 Koding, Inc.
-      Terms
-      Privacy
-

-

-      Status
-      API
-      Blog
-      About
-

-

-

+

+

+

+

+

+

+

+
+

+

+

+

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+

+

+
+
+                    Activity
+
+

+

+
+                    Topics
+
+

+

+
+                    Members
+
+

+

+
+                    Groups
+
+

+

+
+                    Develop
+
+

+

+
+                    0
+                     Apps
+
+

+

+

+

+

+
+
+                    Docs /
+                    Jobs
+
+

+

+

+

+

+
+                    Login
+
+

+

+
+                    Register
+
+

+

+

+

+
+

+

+
+

+

+
+

+

+

+

+

+
guest-60.localhost

+

+
+

+

+

+                    Drop files here!
+                    /home/guest-60/Uploads
+

+

+

+
+

+

+

+

+
+
+                        <canvas width="20" height="20"></canvas>
+                        <canvas width="20" height="20"></canvas>
+
+
+
There is no attached VM

+

+

+

+

+

-

-

-

+

+

+

+

+

+

+

+

+

+                           Koding for You
+

+

+                          You have great ideas.  You want to meet brilliant minds, and bring those ideas to life.  You want to start simple.  Maybe soon you'll have a 10 person team, commanding 100s of servers.
+

+

+                          You want to learn Python, Java, C, Go, Nodejs, HTML, CSS or Javascript or any other. Community will help you along the way.
+

+

+

+

+

+

+

+                           Koding for Developers
+

+

+                          You can have an amazing VM that is better than your laptop.  It's connected to internet 100x faster.  You can share it with anyone you wish. Clone git repos.  Test and iterate on your code without breaking your setup.
+

+

+                          It's free. Koding is your new localhost, in the cloud.
+

+

+

+

+

+

+

+                           Koding for Education
+

+

+                          Have a group where your students enjoy the resources you provide to them. Have it private, invite-only, let them share, collaborate and submit their assignments together.10 students, or 10,000. Just 1 or 100s of computers.
+

+

+                          Koding is your new classroom.
+

+

+

+

+

+

+

+                           Koding for Business
+

+

+                          When you hire someone, let them be in your environment in 5 minutes, collaborating with others, contributing code without sharing ssh keys, passwords. Stop cc'ing your team, stop looking for emails.
+

+

+                          Koding is your new workspace.
+

+

+

+
+
+                        You
+
+
+                        Developers
+
+
+                        Education
+
+
+                        Business
+
+
+

+

+

+
Simple Pricing

+
Try it and see if it's really as cool as we say

+

+
+                      Your first VM
+                      Free
+
+
+                      Each additional VM
+                      $5 / Month
+
+

+

+                    Always on* $25 / Month
+                    Extra RAM $10 / GB / Month

+                    Extra Disk Space $1 / GB / Month
+                    Firewall / Backend Builder $5 / Per VM / Month
+

+                  Contact us for Education and Business pricing
+

+
+

+

+                      © 2013 Koding, Inc.
+                      Terms
+                      Privacy
+

+

+                      Status
+                      API
+                      Blog
+                      About
+

+

+
+

+

+

+

+

   #{KONFIG.getConfigScriptTag { roles: ['guest'], permissions: [] } }


refactoring : timezone management in model

--- server/controllers/index.coffee
+++ server/controllers/index.coffee
@@ -1,36 +1,23 @@
 time = require('time')
 async = require('async')
 i18n = require('cozy-i18n-helper')
-
-convertAlarmDate = (alarm, timezone) ->
-    timezonedDate = new time.Date(alarm.trigg, 'UTC')
-    timezonedDate.setTimezone(timezone)
-    alarm.trigg = timezonedDate.toString().slice(0, 24)
-    return alarm
-
-convertEventDate = (evt, timezone) ->
-    # Convert dates from UTC to user timezone
-    timezonedDate = new time.Date(evt.start, 'UTC')
-    timezonedDate.setTimezone(timezone)
-    evt.start = timezonedDate.toString().slice(0, 24)
-    timezonedDate = new time.Date(evt.end, 'UTC')
-    timezonedDate.setTimezone(timezone)
-    evt.end = timezonedDate.toString().slice(0, 24)
-    return evt
+Alarm = require '../models/alarm'
+Event = require '../models/event'
+User  = require '../models/user'

 module.exports.index = (req, res) ->
     async.parallel [
         (cb) => Alarm.all (err, alarms) =>
             return cb err if err
             for alarm, index in alarms
-                alarms[index] = convertAlarmDate alarm, User.timezone
+                alarms[index] = alarm.timezoned User.timezone

             cb null, alarms

         (cb) => Event.all (err, events) =>
             return cb err if err
             for evt, index in events
-                events[index] = convertEventDate evt, User.timezone
+                events[index] = evt.timezoned User.timezone

             cb null, events


Handle case where stopword file !exists

Handle case where stopword file !exists

Hit an error where unfluff tried to read a file that didn't exist; now the code should default to English. Would welcome a cleaner approach.

--- src/stopwords.coffee
+++ src/stopwords.coffee
@@ -4,11 +4,17 @@

 cache = {}

+getFilePath = (language) ->
+  path.join(__dirname, "..", "data", "stopwords", "stopwords-#{language}.txt")
+
 # Given a language, loads a list of stop words for that language
 # and then returns which of those words exist in the given content
 module.exports = stopwords = (content, language = 'en') ->
-  filePath = path.join(__dirname, "..", "data", "stopwords", "stopwords-#{language}.txt")
-
+  filePath = getFilePath(language)
+
+  if !fs.existsSync(filePath)
+    filePath = getFilePath('en')
+
   if cache.hasOwnProperty(language)
     stopWords = cache[language]
   else


in coppermine import, use relative urls

in coppermine import, use relative urls

--- scripts/import_coppermine.coffee
+++ scripts/import_coppermine.coffee
@@ -107,12 +107,7 @@
         description: copperminePicture.caption ? ''
         media: [
           {
-            src: "http://kuvat.aniki.fi/albums/#{copperminePicture.filepath}normal_#{copperminePicture.filename}",
-            width: 600 # TODO
-            height: 400 # TODO
-          }
-          {
-            src: "http://kuvat.aniki.fi/albums/#{copperminePicture.filepath}#{copperminePicture.filename}",
+            src: "/albums/#{copperminePicture.filepath}#{copperminePicture.filename}",
             width: 6000 # TODO
             height: 4000 # TODO
             original: true


Implement a changeHeader method for updating channel headers

--- src/matteruser.coffee
+++ src/matteruser.coffee
@@ -174,5 +174,11 @@

         @client.customMessage(msg, msg.channel_id)

+    changeHeader: (channel_id, header) ->
+        return unless channel_id?
+        return unless header?
+
+        @client.setChannelHeader(channel_id, header)
+
 exports.use = (robot) ->
     new Matteruser robot


Fix typo

Fix typo

--- server/api/setupApi.coffee
+++ server/api/setupApi.coffee
@@ -352,8 +352,7 @@
             await checkin.upsert()
         res.json({ok: "OK"})

-    app.post '/api/
-/:userId/:groupName', ensureLoggedIn, wrap (req, res) ->
+    app.post '/api/moveUserToGroup/:userId/:groupName', ensureLoggedIn, wrap (req, res) ->
         if not req.user?.admin
             res.status(403).send('No permissions')
             return


Debugging

Debugging

--- src/tableflip.coffee
+++ src/tableflip.coffee
@@ -1,5 +1,5 @@
 # Description:
-#   Say Hi to Hubot.
+#   Flip a table
 #
 # Dependencies:
 #   None
@@ -8,14 +8,14 @@
 #   None
 #
 # Commands:
-#   hubot hello - "hello!"
-#   hubot orly - "yarly"
+#   hubot tableflip phrase - Flips the phrase
 #
 # Author:
-#   tombell
+#   joe ennever

 module.exports = (robot) ->
-  robot.respond /tableflip (.*)?/, (msg) ->
+  robot.respond /tableflip(.*)?/, (msg) ->
+    console.log("got here #{msg}")
     flipped = null
     if msg.match[2]
       flipped = flip(msg.match[2])


tfRaise does not cooperate well with tikzpicture, it seems; use yshift instead FTTB

--- src/mkts-table.coffee
+++ src/mkts-table.coffee
@@ -444,11 +444,9 @@
   yield tex "% ==========================================================================================================\n"
   yield tex "% Beginning of MKTS Table (layout: #{rpr layout_name})\n"
   ### TAINT undocumented kludge ###
-  if me.nosamepage
-    yield texr 'ð1003', "\\setlength{\\fboxsep}{0mm}"
-  else
-    yield texr 'ð1003', "\\begin{mktsSamepage}\\setlength{\\fboxsep}{0mm}"
-  yield texr 'ð1003', "{\\tfRaise{1.35}\\begin{tikzpicture}[ overlay, yshift = 0mm, yscale = -1, line cap = rect ]"
+  yield texr 'ð1003', "\\begin{mktsSamepage}" unless me.nosamepage
+  yield texr 'ð1003', "{\\setlength{\\fboxsep}{0mm}\\begin{tikzpicture}[ overlay, yshift = 5mm, yscale = -1, line cap = rect ]"
+  # yield texr 'ð1003', "{\\setlength{\\fboxsep}{0mm}\\mktstfRaise{1.35}\\begin{tikzpicture}[ overlay, yshift = 0mm, yscale = -1, line cap = rect ]"
   yield texr 'ð1004', "\\tikzset{x=#{me._tmp.unitwidth_txt}};\\tikzset{y=#{me._tmp.unitheight_txt}};"
   yield return

@@ -463,10 +461,7 @@
   #.........................................................................................................
   yield tex "~\\par% End of MKTS Table (layout: #{rpr layout_name})\n"
   ### TAINT undocumented kludge ###
-  if me.nosamepage
-    null
-  else
-    yield texr 'ð1005', "\\end{mktsSamepage}"
+  yield texr 'ð1005', "\\end{mktsSamepage}" unless me.nosamepage
   yield tex "% ==========================================================================================================\n"
   yield return


Fix bat path

Fix bat path

--- src/tools/bat.coffee
+++ src/tools/bat.coffee
@@ -7,4 +7,4 @@
   mkpath path = fs.BuildPath install2, folder
   path = fs.BuildPath path, PACKAGE.name + '.bat'
   fs.CreateTextFile path, true
-  .WriteLine "@cscript //NoLogo \"%0\\..\\..\\#{PACKAGE.name}.js\" %*"
+  .WriteLine "@cscript //NoLogo \"%~dp0..\\#{PACKAGE.name}.js\" %*"


need additional condition for safe span unwrap

--- src/normalizer.coffee
+++ src/normalizer.coffee
@@ -81,7 +81,10 @@
         attributes = DOM.getAttributes(node)
         if node.tagName == DOM.DEFAULT_INLNE_TAG
           # Remove unneeded SPANs
-          if _.keys(attributes).length == 0 and (node.parentNode != lineNode or DOM.isElement(node.firstChild))
+          hasAttributes = _.keys(attributes).length > 0
+          exposeTextNode = DOM.isTextNode(node.firstChild) and (DOM.isElement(node.previousSibling) or DOM.isElement(node.nextSibling))
+          exposeToLine = node.parentNode == lineNode and DOM.isTextNode(node.firstChild)
+          if !hasAttributes and !exposeTextNode and !exposeToLine
             nodes.push(node.nextSibling)
             DOM.unwrap(node)
             continue


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/color.coffee
+++ test/color.coffee
@@ -36,9 +36,9 @@
   equals color.b(), 255

   color = Color('#3ef')
-  equals color.r(), 44
-  equals color.g(), 39
-  equals color.b(), 11
+  equals color.r(), 51
+  equals color.g(), 238
+  equals color.b(), 255

 test "Color should know what Fuzzy Wuzzy Brown is", ->
   color = Color("Fuzzy Wuzzy Brown")


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/color.coffee
+++ test/color.coffee
@@ -174,6 +174,17 @@
     b: -> 210
     a: -> 0.3

+test "should parse 549E00", ->
+  color = Color('#549E00')
+
+test "should be able to lighten", ->
+  color = Color('#f00').lighten(0.1)
+
+  equals color.r(), 255
+  equals color.g(), 51
+  equals color.b(), 51
+  equals color.a(), 1
+
 test "should equal colors with the same rbga values", ->
   color1 = Color(4, 20, 100)
   color2 = Color('rgb(4, 20, 100)')


sorting options in `coffee --help`

sorting options in `coffee --help`

--- command.coffee
+++ command.coffee
@@ -26,22 +26,22 @@

 # The list of all the valid option flags that `coffee` knows how to handle.
 SWITCHES = [
+  ['-b', '--bare',            'compile without the top-level function wrapper']
   ['-c', '--compile',         'compile to JavaScript and save as .js files']
+  ['-e', '--eval',            'compile a string from the command line']
+  ['-h', '--help',            'display this help message']
   ['-i', '--interactive',     'run an interactive CoffeeScript REPL']
-  ['-o', '--output [DIR]',    'set the directory for compiled JavaScript']
   ['-j', '--join [FILE]',     'concatenate the scripts before compiling']
-  ['-w', '--watch',           'watch scripts for changes and rerun commands']
-  ['-p', '--print',           'print the compiled JavaScript to stdout']
   ['-l', '--lint',            'pipe the compiled JavaScript through JavaScript Lint']
-  ['-s', '--stdio',           'listen for and compile scripts over stdio']
-  ['-e', '--eval',            'compile a string from the command line']
-  ['-r', '--require [FILE*]', 'require a library before executing your script']
-  ['-b', '--bare',            'compile without the top-level function wrapper']
-  ['-t', '--tokens',          'print the tokens that the lexer produces']
   ['-n', '--nodes',           'print the parse tree that Jison produces']
   [      '--nodejs [ARGS]',   'pass options through to the "node" binary']
+  ['-o', '--output [DIR]',    'set the directory for compiled JavaScript']
+  ['-p', '--print',           'print the compiled JavaScript to stdout']
+  ['-r', '--require [FILE*]', 'require a library before executing your script']
+  ['-s', '--stdio',           'listen for and compile scripts over stdio']
+  ['-t', '--tokens',          'print the tokens that the lexer produces']
   ['-v', '--version',         'display CoffeeScript version']
-  ['-h', '--help',            'display this help message']
+  ['-w', '--watch',           'watch scripts for changes and rerun commands']
 ]

 # Top-level objects shared by all the functions.


fix query translate on collections so that newest does not override other search options

--- server/api/lib/collection.coffee
+++ server/api/lib/collection.coffee
@@ -374,12 +374,12 @@
       q = '*' if q is ''
       qry.query.filtered.query.bool.must.push query_string: query: q
   if opts?
+    opts = {random:true} if opts is 'random'
     opts = {size:opts} if typeof opts is 'number'
     opts = {newest: true} if opts is true
     if opts.newest is true
       delete opts.newest
-      opts = {sort: {createdAt:{order:'desc'}}}
-    opts = {random:true} if opts is 'random'
+      opts.sort = {createdAt:{order:'desc'}}
     delete opts._ # delete anything that may have come from query params but are not handled by ES
     if opts.random
       if typeof qry is 'string'


RACK_ENV=development

--- src/pow/server.coffee
+++ src/pow/server.coffee
@@ -5,6 +5,8 @@
 sys     = require 'sys'

 idle = 1000 * 60 * 15
+
+process.env['RACK_ENV'] = 'development'

 exports.Server = class Server
   constructor: (@configuration) ->


[AngularJS FS] bug fix infinite loop when  run tests

--- Projects/Web/AngularJS-FeatureScaffolding/gulp/gulpfile.coffee
+++ Projects/Web/AngularJS-FeatureScaffolding/gulp/gulpfile.coffee
@@ -67,7 +67,7 @@
       sourceFiles : 'src/**/*.{sass, scss}'
       mainSassFile: "src/main/styles/app.sass"
     html:
-      sourceFiles : 'src/**/*.html'
+      sourceFiles : ['src/**/*.html', '!src/**/*.js.html', '!src/**/index.html']
     img:
       sourceFiles : 'src/main/img/**/*.{jpeg, jpg, png}'
     resourcesFiles: 'src/main/resources/**/*'
@@ -271,6 +271,7 @@
   gulp.watch(paths.source.coffee.sourceFiles).on('change', (e) ->
     buildAppScripts()
       .on 'end', ->
+        runAppTestsFunction()
         gutil.log( gutil.colors.red('[CoffeeWatcher] ') + gutil.colors.magenta( _.last(e.path.split('/')) ) + ' was changed' )
   )

@@ -280,7 +281,7 @@
         gutil.log( gutil.colors.red('[HtmlWatcher] ') + gutil.colors.magenta( _.last(e.path.split('/')) ) + ' was changed' )
   )

-  gulp.watch(paths.source.html.sourceFiles).on('change', (e) ->
+  gulp.watch(paths.source.indexFile).on('change', (e) ->
     copyIndexToDistFolder()
       .on 'end', ->
         gutil.log( gutil.colors.red('[IndexFileWatcher] ') + gutil.colors.magenta( _.last(e.path.split('/')) ) + ' was changed' )
@@ -399,14 +400,17 @@

 # RunAppTests
 # ======================
-gulp.task 'runAppTests', ['buildSpecScripts'], () ->
+runAppTestsFunction = () ->
   gulp.src(paths.spec.js.sourceFiles)
     .pipe( karmaPlugin
       configFile: 'karma.conf.js'
-      action    : 'watch'
+      action    : 'run'
     )
     .on 'error', (err) ->
       console.log 'runAppTests: ' + err
+
+gulp.task 'runAppTests', [], runAppTestsFunction
+

@@ -428,6 +432,7 @@
 gulp.task 'copyResourcesToDevFolder'     , ['cleanDev'                ], copyResourcesToDevFolder
 gulp.task 'copyImgToDevFolder'           , ['cleanDev'                ], copyImgToDevFolder
 gulp.task 'copyIndexToDistFolder'        , ['copyResourcesToDevFolder'], copyIndexToDistFolder
+gulp.task 'runDevTests'                  , ['buildAppScripts', 'buildVendorsScripts'], runAppTestsFunction

@@ -444,6 +449,7 @@
   'copyResourcesToDevFolder'
   'copyImgToDevFolder'
   'copyIndexToDistFolder'
+  'runDevTests'
 ]

@@ -486,8 +492,7 @@

 # Test tasks
 # =======================
-gulp.task 'buildSpecScripts' , [], buildAppScripts
-gulp.task 'test'          , [ 'runAppTests']
+gulp.task 'test'          , ['runAppTests']
 # gulp.task 'testAndWatch'  , ['test', 'watchSpecs']


Additional diagnostics

Additional diagnostics

--- server/lib/download.coffee
+++ server/lib/download.coffee
@@ -35,8 +35,8 @@
             addStats("ok")
             return page
         catch e
-            logger.info "Error downloading " + href + " " + i + " will re-download"
-            logger.info e.message
+            logger.info "Error downloading " + href + " " + i + " will re-download", e.message
+            logger.info e, e.stack
             await sleep(delay)
             delay *= 2
     addStats("fail")


Test errors for capital letters

Test errors for capital letters

Closes #35.

--- test/error.coffee
+++ test/error.coffee
@@ -6,6 +6,7 @@
   it 'throws an error when passed a bad language in the function', ->
     humanizingWith = (options) ->
       return -> humanizeDuration(10000, options)
+    assert.throws humanizingWith(language: 'EN'), Error
     assert.throws humanizingWith(language: 'bad language'), Error
     assert.throws humanizingWith(language: ''), Error
     assert.throws humanizingWith(language: null), Error
@@ -15,6 +16,7 @@
     humanizing = (options) ->
       return -> h(10000, options)
     assert.throws humanizing(), Error
+    assert.throws humanizing(language: 'EN'), Error
     assert.throws humanizing(language: 'bad language'), Error
     assert.throws humanizing(language: ''), Error
     assert.throws humanizing(language: null), Error


Update config.coffee

Update config.coffee

--- frontend/config.coffee
+++ frontend/config.coffee
@@ -6,7 +6,7 @@
     definition: false
     wrapper: false
   paths:
-    public: '../_public/www'
+    public: '../release/www'
   files:
     javascripts:
       joinTo:


Use new middleware to add/remove watchers from campaigns.

--- server/routes/index.coffee
+++ server/routes/index.coffee
@@ -98,6 +98,8 @@
   app.get('/db/campaign/:handle/patches', mw.patchable.patches(Campaign))
   app.post('/db/campaign/:handle/patch', mw.auth.checkLoggedIn(), mw.patchable.postPatch(Campaign, 'campaign'))
   app.get('/db/campaign/-/overworld', mw.campaigns.fetchOverworld)
+  app.post('/db/campaign/:handle/watchers', mw.patchable.joinWatchers(Campaign))
+  app.delete('/db/campaign/:handle/watchers', mw.patchable.leaveWatchers(Campaign))

   app.post('/db/classroom', mw.classrooms.post)
   app.get('/db/classroom', mw.classrooms.fetchByCode, mw.classrooms.getByOwner)


Workspace: Added amIHost method to check user joined a session or not.

--- client/app/CommonViews/workspace/panel/collaborativepanel.coffee
+++ client/app/CommonViews/workspace/panel/collaborativepanel.coffee
@@ -20,10 +20,11 @@
     PaneClass              = @getPaneClass paneOptions.type
     paneOptions.delegate   = @
     paneOptions.sessionKey = @getOptions().sessionKeys[@panes.length]  if @getOptions().sessionKeys
-    isJoinedASession       = !!paneOptions.sessionKey
+    isJoinedASession       = !!paneOptions.sessionKey and not @amIHost paneOptions.sessionKey

-    if isJoinedASession and paneOptions.type is "terminal"
-      PaneClass = CollaborativeClientTerminalPane
+    if isJoinedASession then log "#{KD.nick()} is joined a session"
+    else log "#{KD.nick()} created a session"
+

     pane = new PaneClass paneOptions

@@ -31,6 +32,10 @@
     @panes.push pane
     @emit "NewPaneCreated", pane

+  amIHost: (sessionKey) ->
+    return  no unless sessionKey
+    [sessionOwner] = sessionKey.split ":"
+    return sessionOwner == KD.nick()

 CollaborativePanel::EditorPaneClass   = CollaborativeEditorPane
 CollaborativePanel::TerminalPaneClass = CollaborativeTerminalPane


Ace: Emitted file content related events.

--- client/landing/app/Applications/Ace.kdapplication/AppView.coffee
+++ client/landing/app/Applications/Ace.kdapplication/AppView.coffee
@@ -65,6 +65,15 @@
         @openSaveDialog()
       else
         @getData().emit "file.requests.save", contents
+
+    @ace.on "FileContentChanged", =>
+      @getActiveTabHandle().setClass "modified"
+
+    @ace.on "FileContentSynced", =>
+      @getActiveTabHandle().unsetClass "modified"
+
+  getActiveTabHandle: ->
+    return  @getDelegate().tabView.getActivePane().tabHandle

   preview: ->
     {vmName, path} = @getData()


Undo wrongly commited file

Undo wrongly commited file

--- server/startup/cron.coffee
+++ server/startup/cron.coffee
@@ -13,7 +13,6 @@
 			job: ->
 				statistics = RocketChat.statistics.save()
 				statistics.host = Meteor.absoluteUrl()
-				# statistics.environment = 'tutum' if process.env.TUTUM_REDIS_HOST and Meteor.absoluteUrl() is *.rocket.chat
 				unless RocketChat.settings.get 'Statistics_opt_out'
 					console.log 'Sending statistics data to Rocket.Chat'
 					HTTP.post 'https://rocket.chat/stats',


don't use random keys

don't use random keys

--- app/assets/javascripts/components/activity/activity_table.cjsx
+++ app/assets/javascripts/components/activity/activity_table.cjsx
@@ -44,9 +44,9 @@

     drawers = @state.activity.map (revision) ->
       courses = revision.courses.map (course) ->
-
{course.title}

+
{course.title}

-

+

Article is active in


use yaku

use yaku

--- test/index.coffee
+++ test/index.coffee
@@ -9,7 +9,7 @@
         assert content.indexOf(id) > 0

 describe "basic request with Promise", ->
-    request.Promise = Promise or require 'yaku'
+    request.Promise = require 'yaku'

     it "request with http", ->
         buildP 'http://www.baidu.com/', 'bdstatic'
@@ -38,7 +38,7 @@
         build "www.baidu.com", 'bdstatic', done

 describe "request options", ->
-    request.Promise = Promise or require 'yaku'
+    request.Promise = require 'yaku'

     it "use object", ->
         request {host: "www.baidu.com", protocol: "http"}
@@ -50,7 +50,7 @@
             assert content.indexOf('bdstatic') > 0

 describe "advanced requests", ->
-    request.Promise = Promise or require 'yaku'
+    request.Promise = require 'yaku'

     it "decode gb2312, handle gzip", ->
         buildP "http://www.qq.com/", '腾讯'


fix css bug

fix css bug

--- app/assets/javascripts/jax/dev/views/controller_list.js.coffee
+++ app/assets/javascripts/jax/dev/views/controller_list.js.coffee
@@ -18,7 +18,7 @@
       @minify()

   expand: ->
-    @$("a.minify").css 'background-position', '-16px -192px'
+    @$("a.minify .icon").css 'background-position', '-16px -192px'
     @$("li.header").css 'border-bottom-right-radius', '0px'
     if @minified
       curHeight = @$el.height()
@@ -64,6 +64,6 @@

   render: =>
     @$el.empty()
-    @$el.append "
 Controllers
"
+    @$el.append "
 Controllers
"
     @collection.each @add
     true


Removed cornerstone-source.coffee

Removed cornerstone-source.coffee

--- source/cornerstone-source.coffee
+++ source/cornerstone-source.coffee
@@ -1,3 +0,0 @@
-#=require object_extensions
-
-#= require_tree .


cleanup

cleanup

--- src/tex-writer.coffee
+++ src/tex-writer.coffee
@@ -635,23 +635,21 @@

 #-----------------------------------------------------------------------------------------------------------
 @MKTX.BLOCK.$paragraph_2 = ( S ) =>
-  within_paragraph  = no
-  seen_text_event   = no
+  within_paragraph  = false
+  seen_text_event   = false
   collector         = []
-  close_paragraph   = no
-  #.........................................................................................................
-  ### TAINT S.is_first_par, S.within_ul could / should be local variables ###
+  close_paragraph   = false
+  within_noindent   = false
+  is_first_par      = true
+  #.........................................................................................................
   return $ ( event, send ) =>
     # debug '73632', jr event
-    if select event, '(', [ 'h', 'multi-columns', 'blockquote', ],  true then S.is_first_par  = true
-    if select event, ')', [ 'blockquote', ],                        true then S.is_first_par  = true
-    if select event, '(', [ 'ul', ],                                true then S.within_ul     = true
-    if select event, ')', [ 'ul', ],                                true then S.within_ul     = false
-    #.......................................................................................................
-    ### TAINT doesn't use `select`? ###
-    debug '77633', ( jr event ), S.is_first_par, S.within_ul
-    if select event, ')', [ 'ul', ],                               true
-      urge '77768', ( jr event ), S.is_first_par, S.within_ul
+    if select event, '(', [ 'h', 'multi-columns', 'blockquote', ],  true then is_first_par    = true
+    if select event, ')', [ 'blockquote', ],                        true then is_first_par    = true
+    if select event, '.', [ 'hr', 'hr2' ],                          true then is_first_par    = true
+    if select event, '(', [ 'ul', 'keep-lines', ],                  true then within_noindent = true
+    if select event, ')', [ 'ul', 'keep-lines', ],                  true then within_noindent = false
+    #.......................................................................................................
     if select event, '~', 'start-paragraph', true
       within_paragraph  = yes
       seen_text_event   = no
@@ -691,10 +689,10 @@
           #.................................................................................................
           ### Check whether we're typesetting the first text portion after a headline, the start of a
           blockquote or similar and send additional material as needed: ###
-          has_indent        = not S.is_first_par
-          S.is_first_par    = false
+          has_indent        = not is_first_par
+          is_first_par    = false
           #.................................................................................................
-          if S.within_ul
+          if within_noindent
             send [ 'tex', "%% no indent within list\n" ]
             # null
           else if has_indent
@@ -1906,9 +1904,7 @@
     options:              @options
     layout_info:          layout_info
     bare:                 settings[ 'bare' ] ? no
-    is_first_par:         true
     paragraph_nr:         0
-    within_ul:            false
   #.........................................................................................................
   md_readstream       = MD_READER.create_md_read_tee md_source
   tex_writestream     = @create_tex_write_tee S


simplify FNCR handling

simplify FNCR handling


Use template instance's version of autorun

--- client/views/widget/latest.coffee
+++ client/views/widget/latest.coffee
@@ -1,7 +1,7 @@
 Template.blogLatest.rendered = ->
   num = if @data?.num then @data.num else 3

-  Deps.autorun ->
+  @autorun ->
     Meteor.subscribe 'posts', num