Make wesabe.canvas.geometry.Point CommonJS-compatible.

--- application/chrome/content/wesabe/canvas/geometry/Point.coffee
+++ application/chrome/content/wesabe/canvas/geometry/Point.coffee
@@ -1,32 +1,30 @@
-wesabe.provide('canvas.geometry.Point')
+Colorizer = require 'util/Colorizer'

-class wesabe.canvas.geometry.Point
-  constructor: (x, y) ->
-    @x = x
-    @y = y
+class Point
+  constructor: (@x, @y) ->

   @__defineGetter__ 'ZeroPoint', ->
-    new this(0, 0)
+    new this 0, 0

-  this::__defineGetter__ 'nearestPixel',
-    new this.constructor(Math.round(@x, @y))
+  @::__defineGetter__ 'nearestPixel',
+    new @constructor Math.round(@x), Math.round(@y)

   withOffset: (x, y) ->
     if not y?
-      y = x.y || x.height
-      x = x.x || x.width
+      y = x.y or x.height
+      x = x.x or x.width

-    new this.constructor(@x + x, @y + y)
+    new @constructor @x + x, @y + y

   clone: ->
-    new this.constructor(@x, @y)
+    new @constructor @x, @y

   inspect: (refs, color, tainted) ->
-    s = new wesabe.util.Colorizer()
+    s = new Colorizer()
     s.disabled = !color
     s
       .yellow('#<')
-      .bold(this.constructor?.__module__?.name || 'Object')
+      .bold(@constructor?.__module__?.name || 'Object')
       .print(' ')
       .yellow('{')
       .print(@x)
@@ -35,3 +33,6 @@
       .yellow('}')
       .yellow('>')
       .toString()
+
+
+module.exports = Point


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/string_extensions.coffee
+++ test/string_extensions.coffee
@@ -5,4 +5,8 @@

   equals '{"val": "a string"}'.parse().val, "a string", "even parsing objects works"

-
+test "String#constantize", ->
+  equals "String".constantize(), String, "look up a constant"
+  equals "Math".constantize(), Math, "look up a constant"
+  equals "Number".constantize(), Number, "look up a constant"
+


Hides the row in notes body when empty of emojis

--- app/assets/javascripts/awards_handler.coffee
+++ app/assets/javascripts/awards_handler.coffee
@@ -94,6 +94,11 @@
     isntNoteBody = $emojiBtn.closest('.note-body').length is 0
     counter = $('.js-counter', $emojiBtn)
     counterNumber = parseInt(counter.text())
+
+    if !isntNoteBody
+      console.log $emojiBtn.get(0)
+      # If this is a note body, we just hide the award emoji row like the initial state
+      $emojiBtn.closest('.js-awards-block').addClass 'hidden'

     if counterNumber > 1
       counter.text(counterNumber - 1)


change controllers callback

change controllers callback

--- app/assets/javascripts/controllers.js.coffee
+++ app/assets/javascripts/controllers.js.coffee
@@ -1,6 +1,6 @@
-class Controllers
+class @Controllers

-  @startPollingPlayers: (@game) ->
+  @startPollingPlayers: (@callback = ->) ->
     return if @game.state isnt Game.STATES.WAITING_FOR_PLAYERS

     setTimeout @lookForPlayers, 1000
@@ -12,12 +12,8 @@

     for gamepad, index in gamepads
       if gamepad
-        @game.addPlayer index, gamepad
+        @callback index, gamepad

     console.log gamepads[0], gamepads[1]

-    #console.log gamepads
-    setTimeout @lookForPlayers, 1000
-
-
-window.Controllers = Controllers+    setTimeout @lookForPlayers, 1000


small spacing edit

small spacing edit

--- app/assets/javascripts/health_data.js.coffee
+++ app/assets/javascripts/health_data.js.coffee
@@ -41,6 +41,4 @@

   $("#completion_message").addClass("hidden")
   $("form#check-in .question").addClass("hidden")
-  $("form#check-in .question[data-question-id='" + prev_question_id + "'").removeClass('hidden')
-
-
+  $("form#check-in .question[data-question-id='" + prev_question_id + "'").removeClass('hidden')


add note about PLY firefox support

add note about PLY firefox support

--- doc/input/builtin-meshes.coffee
+++ doc/input/builtin-meshes.coffee
@@ -130,6 +130,8 @@
 The mesh will be scaled to a size of `size` GL units. The default size is 1.0.

 The Ajax call will be a GET request by default. You can change this by passing the `method` option.
+
+**NOTE:** Loading PLY files requires `DataView`, which is not implemented in Firefox until version 15. As of this writing, the latest release version is 14, so you'll need to use a nightly build, or use Chrome.
 ###
 @world.addObject new Jax.Model
   position: [0, -1, 0]


Ignore negative numbers in purchase courses view

--- app/views/courses/PurchaseCoursesView.coffee
+++ app/views/courses/PurchaseCoursesView.coffee
@@ -26,7 +26,7 @@
   getPrice: -> @pricePerStudent * @numberOfStudents

   onInputStudentsInput: ->
-    @numberOfStudents = parseInt(@$('#students-input').val()) or 0
+    @numberOfStudents = Math.max(parseInt(@$('#students-input').val()) or 0, 0)
     @updatePrice()

   updatePrice: ->
@@ -54,7 +54,6 @@
   onStripeReceivedToken: (e) ->
     @state = 'purchasing'
     @render?()
-    console.log 'e', e

     data =
       maxRedeemers: @numberOfStudents


Ready for the new datatable.

Ready for the new datatable.

--- admin/app/scripts/main.coffee
+++ admin/app/scripts/main.coffee
@@ -84,13 +84,26 @@

 class Dataset
   constructor: (@view, hit='')->
-    @title = ko.observable('')
+    @title = ko.observable()
+    @username = ko.observable()
+    @subjects = ko.observable()
+    @locations = ko.observable()
+    @tags = ko.observable()
+    @url =  ko.observable()
+    @publisher = ko.observable()
+    @dates = ko.observable()
+    @resourceType = ko.observable()
+    @frequency = ko.observable()
+    @geographic_granularity = ko.observable()
     if hit
-      @title(hit._source.title)
-      @type =  ko.observable(hit._source.type)
-      @url =  ko.observable(hit._source.url)
       @id =  ko.observable(hit._id)
+      @fromSource(hit._source)
     @isSelected = ko.observable(false)
+
+  fromSource: (source)=>
+    @title = ko.observable(source.title)
+    @url =  ko.observable(source.url)
+    @type =  ko.observable(source.type)

   clickHandler: ()=>
     @view.selectDataset(@)


remove useless update friends

remove useless update friends

--- src/service.coffee
+++ src/service.coffee
@@ -88,12 +88,6 @@
                 cb err, body
         )

-    # updateFriends:() ->
-    #     result = JSON.parse(data)
-    #     if result.retcode != 0
-    #         console.log 'invalid data', result.retcode
-    #         return
-
     friendList:(cb) ->
         rValue =
             h: 'hello'


Added WIP solution for setting up bulk fields with placeholders.

--- src/Forms.coffee
+++ src/Forms.coffee
@@ -52,6 +52,17 @@
           doc
         update: (docId, modifier, template) ->
           console.debug('before update', docId, modifier)
+          # Remove the items in $unset which don't exist as fields in the form.
+          $unset = modifier.$unset
+          if Object.keys($unset).length >= 0
+            schemaInputs = Form.getSchemaInputs(template)
+            fieldIds = []
+            _.each schemaInputs, (input, key) ->
+              fieldIds.push(key) if $(input).length > 0
+            remFieldIds = _.difference(Object.keys($unset), fieldIds)
+            _.each remFieldIds, (fieldId) ->
+              delete $unset[fieldId]
+          console.log('modifier', modifier)
           modifier

     if formArgs.hooks?
@@ -171,6 +182,9 @@
       @autorun (c) ->
         helpMode = Session.get 'helpMode'
         if helpMode then addPopups() else removePopups()
+
+      # if Form.isBulk()
+      #   Form.setUpBulkFields()

       formArgs.onRender?.apply(@, arguments)

@@ -320,6 +334,33 @@
         field.optional = true
       new SimpleSchema(schemaArgs)

+    Form.setUpBulkFields = (template) ->
+      template = getTemplate(template)
+
+      values = Form.getBulkValues()
+
+      schemaInputs = Form.getSchemaInputs(template)
+      _.each schemaInputs, (input, key) ->
+        $input = $(input.node)
+        value = Objects.getModifierProperty(values, key)
+        if Setter.isDefined(value)
+          placeholder = ''
+        else
+
+        $input.attr('placeholder', placeholder)
+
+    Form.getSampleValues = (paramId, template) ->
+      template = getTemplate(template)
+      docs = Form.getDocs()
+      values = []
+      count = 0
+      _.some docs, (doc) ->
+        value = Objects.getModifierProperty(doc, paramId)
+        if Setter.isDefined(value)
+          values.push(value)
+          count++
+        return count >= 3
+
     return Form

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


HOTFIX for #966 - prevent search breaking error due to missing event

--- app/assets/javascripts/toggle_advanced_search/presenter.coffee
+++ app/assets/javascripts/toggle_advanced_search/presenter.coffee
@@ -23,7 +23,7 @@

   ### Private Methods (ue) ###

-  _toggleState: (event) ->
+  _toggleState: (event = {}) ->
     form = $('.filter-form')
     target = $(event.target).data 'target'


Removed unused variable

Removed unused variable

--- core/app/backbone/models/fact.coffee
+++ core/app/backbone/models/fact.coffee
@@ -38,4 +38,3 @@
       can_destroy: @can_destroy()
       fact_url_host: @factUrlHost()
       fact_url_title: @get('fact_title') || @factUrlHost()
-      is_deletable_from_channel: @collection?.channel?.is_mine()


corr_w_scatter: If 1 group, plot gray points

--- corr_w_scatter/corr_w_scatter.coffee
+++ corr_w_scatter/corr_w_scatter.coffee
@@ -103,7 +103,9 @@

   # colors for scatterplot
   nGroup = d3.max(data.group)
-  if nGroup <= 3
+  if nGroup == 1
+    colors = [ d3.rgb(150, 150, 150) ]
+  else if nGroup <= 3
     colors = ["crimson", "green", "darkslateblue"]
   else
     if nGroup <= 10


Improve Atom config

Improve Atom config

--- dotfiles/atom/.atom/config.cson
+++ dotfiles/atom/.atom/config.cson
@@ -41,7 +41,7 @@
     atomicSoftTabs: false
     fontFamily: "Fira Code"
     fontSize: 13
-    preferredLineLength: 100
+    preferredLineLength: 110
     softWrap: true
     softWrapAtPreferredLineLength: true
     softWrapHangingIndent: 2


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -5,11 +5,11 @@
     false if !colorString || colorString == 'transparent'

     bits = rgbParser.exec(colorString)
-      return [
-        toHex(bits[1])
-        toHex(bits[2])
-        toHex(bits[3])
-      ].join('').toUpperCase()
+    return [
+      toHex(bits[1])
+      toHex(bits[2])
+      toHex(bits[3])
+    ].join('').toUpperCase()

   window.Color = (color) ->
     parsedColor = null


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -100,8 +100,9 @@

   ###*
   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.
+  values, hex strings, hsl strings, other Color objects, 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.

   color = Color()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -89,7 +89,7 @@
     else if args.length == 1 && Object.isArray(args.first())
       arr = args.first()

-      rgbMap = arr.slice(0, 3).map (channel) ->
+      rgbMap = arr.splice(0, 3).map (channel) ->
         parseFloat channel

       alpha = if arr[0]? then parseFloat arr[0] else 1.0


Mark methods as essential

Mark methods as essential

--- src/color.coffee
+++ src/color.coffee
@@ -1,10 +1,17 @@
 _ = require 'underscore-plus'
 ParsedColor = require 'color'

-# Public: A simple Color class returned from `atom.config.get` when the value at
-# the key path is of type 'color'.
+# Essential: A simple Color class returned from `atom.config.get` when the value
+# at the key path is of type 'color'.
 module.exports =
 class Color
+  # Essential: Parse a {String} or {Object} into a {Color}
+  #
+  # * `value` - A {String} such as `'white'`, `#ff00ff`, or
+  #             `'rgba(255, 15, 60, .75)'` or an {Object} with `red`, `green`,
+  #             `blue`, and `alpha` properties.
+  #
+  # Returns a {Color} or `null` if it cannot be parsed.
   @parse: (value) ->
     return null if _.isArray(value)
     return null if _.isFunction(value)
@@ -45,11 +52,11 @@
         enumerable: true
         configurable: false

-  # Public: Returns a {String} in the form `'#abcdef'`
+  # Esssential: Returns a {String} in the form `'#abcdef'`
   toHexString: ->
     "##{numberToHexString(@red)}#{numberToHexString(@green)}#{numberToHexString(@blue)}"

-  # Public: Returns a {String} in the form `'rgba(25, 50, 75, .9)'`
+  # Esssential: Returns a {String} in the form `'rgba(25, 50, 75, .9)'`
   toRGBAString: ->
     "rgba(#{@red}, #{@green}, #{@blue}, #{@alpha})"


Added documentation about menu generation

--- src/docco.coffee
+++ src/docco.coffee
@@ -10,7 +10,9 @@
 #     docco src/*.coffee
 #
 # ...will generate linked HTML documentation for the named source files, saving
-# it into a `docs` folder.
+# it into a `docs` folder. One HTML file per input source file will be generated.
+# For each call to docco, if there are multiple files, a menu will be generated to
+# navigate between files.  This will contain only the files for the current invocation.
 #
 # The [source for Docco](http://github.com/jashkenas/docco) is available on GitHub,
 # and released under the MIT license.


fix tests

fix tests

--- exercises/luhn/luhn.spec.coffee
+++ exercises/luhn/luhn.spec.coffee
@@ -4,68 +4,68 @@

   it 'single digit strings can not be valid', ->
     luhn = new Luhn('1')
-    expect(luhn.valid).toEqual(false)
+    expect(luhn.valid).toBe false

   xit 'a single zero is invalid', ->
     luhn = new Luhn('0')
-    expect(luhn.valid).toEqual(false)
+    expect(luhn.valid).toBe false

   xit 'a simple valid SIN that remains valid if reversed', ->
     luhn = new Luhn('059')
-    expect(luhn.valid).toEqual(true)
+    expect(luhn.valid).toBe true

   xit 'a simple valid SIN that becomes invalid if reversed', ->
     luhn = new Luhn('59')
-    expect(luhn.valid).toEqual(true)
+    expect(luhn.valid).toBe true

   xit 'a valid Canadian SIN', ->
     luhn = new Luhn('055 444 286')
-    expect(luhn.valid).toEqual(true)
+    expect(luhn.valid).toBe true

   xit 'invalid credit card', ->
     luhn = new Luhn('8273 1232 7352 0569')
-    expect(luhn.valid).toEqual(false)
+    expect(luhn.valid).toBe false

   xit 'invalid credit card', ->
     luhn = new Luhn('8273 1232 7352 0569')
-    expect(luhn.valid).toEqual(false)
+    expect(luhn.valid).toBe false

   xit 'valid number with an even number of digits', ->
     luhn = new Luhn('095 245 88')
-    expect(luhn.valid).toEqual(true)
+    expect(luhn.valid).toBe true

   xit 'valid number with an odd number of spaces', ->
     luhn = new Luhn('234 567 891 234')
-    expect(luhn.valid).toEqual(true)
+    expect(luhn.valid).toBe true

   xit 'valid strings with a non-digit added at the end become invalid', ->
     luhn = new Luhn('059a')
-    expect(luhn.valid).toEqual(false)
+    expect(luhn.valid).toBe false

   xit 'valid strings with punctuation included become invalid', ->
     luhn = new Luhn('055-444-285')
-    expect(luhn.valid).toEqual(false)
+    expect(luhn.valid).toBe false

   xit 'valid strings with symbols included become invalid', ->
     luhn = new Luhn('055# 444$ 285')
-    expect(luhn.valid).toEqual(false)
+    expect(luhn.valid).toBe false

   xit 'single zero with space is invalid', ->
     luhn = new Luhn(' 0')
-    expect(luhn.valid).toEqual(false)
+    expect(luhn.valid).toBe false

   xit 'more than a single zero is valid', ->
     luhn = new Luhn('0000 0')
-    expect(luhn.valid).toEqual(true)
+    expect(luhn.valid).toBe true

   xit 'input digit 9 is correctly converted to output digit 9', ->
     luhn = new Luhn('091')
-    expect(luhn.valid).toEqual(true)
+    expect(luhn.valid).toBe true

   xit 'using ascii value for non-doubled non-digit isn\'t allowed', ->
     luhn = new Luhn('055b 444 285')
-    expect(luhn.valid).toEqual(false)
+    expect(luhn.valid).toBe false

   xit 'using ascii value for doubled non-digit isn\'t allowed', ->
     luhn = new Luhn(':9')
-    expect(luhn.valid).toEqual(false)
+    expect(luhn.valid).toBe false


Label the sliders

Label the sliders

--- src/reactGUI/ColorWell.coffee
+++ src/reactGUI/ColorWell.coffee
@@ -221,23 +221,16 @@
     )

   renderPicker: ->
-    {div} = React.DOM
+    {div, label} = React.DOM
     return null unless @state.isPickerVisible

-    renderTransparentCell = =>
-      (div \
-        {className: 'color-row', key: 0, style: {height: 20}},
-        (div \
-          {
-            className: classSet(
-              'color-cell transparent-cell': true,
-              'selected': @state.color == 'transparent'
-            )
-            onClick: => @setColor('transparent')
-          },
-          'transparent'
-        )
-      )
+    renderLabel = (text) =>
+      (div {
+        className: 'color-row label', key: text, style: {
+          lineHeight: '20px'
+          height: 16
+        }
+      }, text)

     rows = []
     rows.push ({hue: 0, sat: 0, light: i, alpha: @state.alpha} for i in [0..100] by 10)
@@ -248,10 +241,12 @@

     (div {className: 'color-picker-popup'},
       #renderTransparentCell(),
+      renderLabel("alpha")
       (Slider {
         initialValue: @state.alpha,
         onChange: (newValue) => @setAlpha(newValue)
       }),
+      renderLabel("saturation")
       (Slider {
         initialValue: @state.sat, max: 100,
         onChange: (newValue) => @setSat(newValue)


fix my path

fix my path

--- lib/models/device.coffee
+++ lib/models/device.coffee
@@ -8,7 +8,7 @@
     @devices = dependencies.database?.devices ? require('../database').devices
     @getGeo = dependencies.getGeo ? require('../getGeo')
     @generateToken = dependencies.generateToken ? require('../generateToken')
-    @clearCache = dependencies.clearCache ? require './clearCache'
+    @clearCache = dependencies.clearCache ? require '../clearCache'
     @set attributes
     {@uuid} = attributes


Klient: return Promise.resolve at the end

--- client/Main/kite/kites/klient.coffee
+++ client/Main/kite/kites/klient.coffee
@@ -34,10 +34,11 @@
   init: ->

     @connect()
-    Promise.resolve()

     unless @terminalSessions.length
       @fetchTerminalSessions()
+
+    Promise.resolve()

   # setTransport is used to override the setTransport method in KodingKite


client: require whoami in place

client: require whoami in place

Signed-off-by: Umut Sirin <4055abbc0e40a1254e715b952ccd414607e0b365@gmail.com>

--- client/app/lib/maincontroller.coffee
+++ client/app/lib/maincontroller.coffee
@@ -1,12 +1,11 @@
 kd                             = require 'kd'
 kookies                        = require 'kookies'
 globals                        = require 'globals'
-remote                         = require('./remote')
+remote                         = require './remote'
 checkGuestUser                 = require './util/checkGuestUser'
 getGroup                       = require './util/getGroup'
 setPreferredDomain             = require './util/setPreferredDomain'
 isLoggedIn                     = require './util/isLoggedIn'
-whoami                         = require './util/whoami'
 checkFlag                      = require './util/checkFlag'
 expireClientId                 = require './util/expireClientId'
 AppStorageController           = require './appstoragecontroller'
@@ -311,7 +310,7 @@

     kd.impersonate = require './util/impersonate'
     kd.remote      = remote
-    kd.whoami      = whoami
+    kd.whoami      = require './util/whoami'

   tellChatlioWidget: (method, options, callback = kd.noop) ->


billing: store loggedin member email

billing: store loggedin member email

--- client/home/lib/billing/index.coffee
+++ client/home/lib/billing/index.coffee
@@ -2,7 +2,7 @@
 HomeTeamBillingPaymentInformation = require './hometeambillingpaymentinformation'
 HomeTeamBillingCreditCard         = require './hometeambillingcreditcard'
 HomeTeamBillingPlansList = require './hometeambillingplanslist'
-
+AppFlux = require 'app/flux'
 PaymentFlux = require 'app/flux/payment'

 SECTIONS =
@@ -33,6 +33,8 @@

     mainController.ready =>

+      AppFlux.actions.user.loadLoggedInUserEmail()
+
       { actions } = PaymentFlux(reactor)

       actions.loadStripeClient().then =>


Deal with change sets that are not arrays

--- client/lanes/models/ChangeSet.coffee
+++ client/lanes/models/ChangeSet.coffee
@@ -23,7 +23,7 @@
     value: ->
         set = {}
         for field, change of @update
-            set[field] = change[1]
+            set[field] = if _.isArray(change) then _.last(change) else change
         set

 class ChangeSetCollection extends Lanes.Models.BasicCollection


Don't pre-load package panel views upfront

--- lib/settings-view.coffee
+++ lib/settings-view.coffee
@@ -61,7 +61,6 @@
     @addCorePanel 'Updates', 'cloud-download', => new UpdatesPanel(@packageManager)
     @addCorePanel 'Install', 'plus', => new InstallPanel(@packageManager)

-    @addPackagePanel(pack) for pack in @getPackages()
     @showPanel(@panelToShow) if @panelToShow
     @showPanel('Settings') unless @activePanelName
     @sidebar.width(@sidebar.width()) if @isOnDom()
@@ -108,10 +107,6 @@
         @a class: "icon icon-#{iconName}", name
     @menuSeparator.before(panelMenuItem)
     @addPanel(name, panelMenuItem, panel)
-
-  addPackagePanel: (pack) ->
-    @addPanel pack.name, null, =>
-      new PackageDetailView(pack, @packageManager)

   addPanel: (name, panelMenuItem, panelCreateCallback) ->
     @panelCreateCallbacks ?= {}


Add command binding for copy/paste

Add command binding for copy/paste

--- lib/table-element.coffee
+++ lib/table-element.coffee
@@ -1158,6 +1158,8 @@
     e.stopImmediatePropagation()
     e.preventDefault()
   'core:confirm': -> @startCellEdit()
+  'core:copy': -> @tableEditor.copySelectedCells()
+  'core:paste': -> @tableEditor.pasteClipboard()
   'core:undo': -> @tableEditor.undo()
   'core:redo': -> @tableEditor.redo()
   'core:move-left': -> @moveLeft()


Fixed master test page issue

Fixed master test page issue

Fixed issue where master test page would read attempt to read .ds file
from the timbits directory. Master test page is now constructed using
timbits from from @box. Removed body text from error.

--- src/timbits.coffee
+++ src/timbits.coffee
@@ -41,28 +41,21 @@
 		@server.use express.errorHandler()

 	# route help page
-	@server.get '/', (req, res) ->
-		res.redirect '/timbits/help'
-
 	@server.get '/timbits/help', (req, res) =>
 		res.statusCode = 404
 		fs.readFile "#{config.home}/views/help.coffee", (err, data) ->
 			throw err if err
 			res.send ck.render(data.toString(), context: @box)

-	# router master test page
+	# route master test page
 	@server.get '/timbits/test', (req, res) =>
-		path = "#{config.home}/timbits"
-		fs.readdir path, (err, files) =>
-			throw err if err
-			res.setHeader 'Content-Type', 'text/html; charset=UTF-8'
-			master = ''
-			pending = files.length
-			for file in files
-				file = file.split '.'
-				request {uri: "http://#{server.address}:#{server.port}/#{file[0]}/test" }, (error, response, body) ->
-					master += body
-					res.end master unless --pending
+		res.setHeader 'Content-Type', 'text/html; charset=UTF-8'
+		master = ''
+		pending = Object.keys(@box).length
+		for k,v of @box
+			request {uri: "http://#{server.address}:#{server.port}/#{k}/test" }, (error, response, body) ->
+				master += body
+				res.end master unless --pending

 	# automagically load timbits found in the ./timbits folder
 	path = "#{config.home}/timbits"
@@ -210,7 +203,7 @@
 		testRequest = (type, uri, callback) ->
 			# Execute request for uri and store result
 			request {uri: "#{uri}" }, (error, response, body) ->
-				results.tests.push { type: type, uri: uri, status: response.statusCode, error: (if error? then error else (if body? then body else '')) }
+				results.tests.push { type: type, uri: uri, status: response.statusCode, error: (if error? then error else '') }
 				callback()

 		testRunQueries = (callback) ->


Fills in closing an open expression feature

--- javascripts/lib/math/expression_manipulation.coffee
+++ javascripts/lib/math/expression_manipulation.coffee
@@ -108,11 +108,12 @@
     class_mixer(OpenSubExpressionManipulation)

-    class CloseOpenSubExpressionManipulation
-      invoke: (expression)->
-        _ImplicitMultiplication.build().
-          onNumeric(expression).
-          append(comps.expression.build().open())
+    class CloseSubExpressionManipulation
+      invoke: (expression)->
+        last = expression.last()
+        expression.replaceLast(last.close())
+    class_mixer(CloseSubExpressionManipulation)
+

     class LeftParenthesisExpressionManipulation
       invoke: (expression)->
@@ -206,6 +207,7 @@
       subtraction: SubtractionExpressionManipulation
       negate_last: NegationExpressionManipulation
       open_sub_expression: OpenSubExpressionManipulation
+      close_sub_expression: CloseSubExpressionManipulation
       left_parenthesis: LeftParenthesisExpressionManipulation
       right_parenthesis: RightParenthesisExpressionManipulation
       division: DivisionExpressionManipulation


add function signatures to the grammar cson

--- grammars/language-idris.cson
+++ grammars/language-idris.cson
@@ -151,6 +151,9 @@
         [
           include: '#type_signature'
         ]
+    }
+    {
+      include: '#function_signature'
     }
     {
       name: 'keyword.operator.idris'
@@ -259,6 +262,26 @@
           include: '#language_const'
         }
       ]
+  function_signature:
+    name: 'meta.function.type-signature.idris'
+    begin: "(([\\w']+)|\\(([|!%$+\\-.,=</>:]+)\\))\\s*(:)(?!:)"
+    beginCaptures:
+      2:
+        name: 'entity.name.function.idris'
+      3:
+        name: 'entity.name.function.idris'
+      4:
+        name: 'keyword.operator.colon.idris'
+    end: "(;|(?<=[^\\s>])\\s*(?!->)\\s*$)"
+    patterns:
+      [
+        {
+          include: '#type_signature'
+        }
+      ]
+    comment: """The end patterm is a bit tricky. It's either ';' or something, at the end of the line,
+      but not '->', because a type signature can be multiline. Though, it doesn't help, if you
+      break the signature before arrows."""
   language_const:
     patterns:
       [


JCredential: add new permission to allow admins to update each others credentials if they are in use in group stack templates [completes #104168730]

--- workers/social/lib/social/models/computeproviders/credential.coffee
+++ workers/social/lib/social/models/computeproviders/credential.coffee
@@ -24,6 +24,7 @@
     softDelete        : yes

     permissions       :
+      'modify credential' : []
       'create credential' : ['member']
       'update credential' : ['member']
       'list credentials'  : ['member']
@@ -217,6 +218,7 @@
   fetchUsers: permit

     advanced: [
+      { permission: 'modify credential' }
       { permission: 'update credential', validateWith: Validators.own }
     ]

@@ -295,6 +297,7 @@
   shareWith$: permit

     advanced: [
+      { permission: 'modify credential' }
       { permission: 'update credential', validateWith: Validators.own }
     ]

@@ -355,6 +358,7 @@
   fetchData$: permit

     advanced: [
+      { permission: 'modify credential' }
       { permission: 'update credential', validateWith: Validators.own }
     ]

@@ -366,6 +370,7 @@
   update$: permit

     advanced: [
+      { permission: 'modify credential' }
       { permission: 'update credential', validateWith: Validators.own }
     ]

@@ -394,6 +399,7 @@
   isBootstrapped: permit

     advanced: [
+      { permission: 'modify credential' }
       { permission: 'update credential', validateWith: Validators.own }
     ]


JCredential: shadow: show … instead of stars * and shadow rest

--- workers/social/lib/social/models/computeproviders/credential.coffee
+++ workers/social/lib/social/models/computeproviders/credential.coffee
@@ -493,9 +493,10 @@
   # Poor man's shadow function ~ GG
   shadowed = (c) ->
     return ''  unless c
-    c = c[0...(Math.min  c.length, 30)]
-    r = (c) -> Math.ceil c.length / 1.5
-    return "*#{Array(r c).join '*'}#{c[(r c)..]}"
+    return Array(30).join '*'  if c.length > 100
+    c = c[0...x = (Math.min  c.length, 100)]
+    r = Math.ceil c.length / 3
+    return "#{c[..r]}..."

   fetchData: (client, options, callback) ->


Expect to receive edges at runtime in spec-compliant format

--- spec/RuntimeMiddleware.coffee
+++ spec/RuntimeMiddleware.coffee
@@ -115,7 +115,7 @@
       send actionIn, action, payload
   describe 'receiving a context:edges action', ->
     it 'should send selected edges to the runtime', (done) ->
-      expectedEdges = [
+      sentEdges = [
         from:
           node: 'Foo'
           port: 'out'
@@ -123,7 +123,15 @@
           node: 'Bar'
           port: 'in'
       ]
-      send actionIn, 'context:edges', expectedEdges,
+      expectedEdges = [
+        src:
+          node: 'Foo'
+          port: 'out'
+        tgt:
+          node: 'Bar'
+          port: 'in'
+      ]
+      send actionIn, 'context:edges', sentEdges,
         graphs: [
           name: 'foo'
         ]


Fix positioning and blacklist specs

Fix positioning and blacklist specs

--- spec/autocomplete-spec.coffee
+++ spec/autocomplete-spec.coffee
@@ -16,6 +16,7 @@

     # Set to live completion
     atom.config.set "autocomplete-plus.enableAutoActivation", true
+    atom.config.set "autocomplete-plus.fileBlacklist", ".*, *.md"

     # Set the completion delay
     completionDelay = 100
@@ -300,7 +301,7 @@

             # Check position
             cursorPixelPosition = editorView.pixelPositionForScreenPosition editor.getCursorScreenPosition()
-            expect(autocomplete.position().top).toBe cursorPixelPosition.top + editorView.lineHeight
+            expect(parseInt autocomplete.css("top")).toBe cursorPixelPosition.top + editorView.lineHeight
             expect(autocomplete.position().left).toBe cursorPixelPosition.left

         describe "when the autocomplete view does not fit below the cursor", ->
@@ -313,8 +314,7 @@

             # Check position
             cursorPixelPosition = editorView.pixelPositionForScreenPosition editor.getCursorScreenPosition()
-            autocompleteBottom = autocomplete.position().top + autocomplete.outerHeight()
-            expect(autocompleteBottom).toBe cursorPixelPosition.top
+            expect(parseInt autocomplete.css("top")).toBe cursorPixelPosition.top
             expect(autocomplete.position().left).toBe cursorPixelPosition.left

       describe ".cancel()", ->


Let's try waiting this way.

Let's try waiting this way.

--- spec/fuzzy-finder-spec.coffee
+++ spec/fuzzy-finder-spec.coffee
@@ -953,7 +953,8 @@
         dispatchCommand('toggle-git-status-finder')
         expect(atom.workspace.panelForItem(gitStatusView).isVisible()).toBe true

-        waitForPathsToDisplay gitStatusView
+        waitsFor ->
+          gitStatusView.find('.file').length == 4

         runs ->
           expect(gitStatusView.find('.status.status-modified').length).toBe 1


:white_check_mark: for TextObject AnyPair

--- spec/text-objects-spec.coffee
+++ spec/text-objects-spec.coffee
@@ -221,13 +221,13 @@
     # I don't like old behavior, that was not in Vim and furthermore, this is counter intuitive.
     # Simply selecting area between quote is that normal user expects.
     # it "applies operators inside the next string in operator-pending mode (if not in a string)", ->
+
     it "[Changed behavior] applies operators inside area between quote", ->
       set
         cursor: [0, 26]
       ensure "di'",
         text: "''here' ' and over here"
         cursor: [0, 1]
-# ' something in here and in '' ' and over here"

     it "makes no change if past the last string on a line", ->
       set
@@ -424,9 +424,6 @@
         cursor: [0, 29]
         classListContains: 'normal-mode'
         classListNotContains: 'operator-pending-mode'
-
-  # [TODO]
-  describe "pair-any text object", ->

   describe "the 'a\"' text object", ->
     originalText = '" something in here and in "here" "'
@@ -578,3 +575,65 @@
         ensure 'vae', selectedText: text
         ensure 'escape', selectedText: ''
         ensure 'jjvae', selectedText: text
+
+  describe "the pair-any text object", ->
+    beforeEach ->
+      set
+        text: """
+          .... "abc" ....
+          .... 'abc' ....
+          .... `abc` ....
+          .... {abc} ....
+          .... <abc> ....
+          .... >abc< ....
+          .... [abc] ....
+          .... (abc) ....
+          """
+        cursor: [0, 7]
+
+    it "applies operators any innner-pair and repeatable", ->
+      ensure 'dis',
+        text: """
+          .... "" ....
+          .... 'abc' ....
+          .... `abc` ....
+          .... {abc} ....
+          .... <abc> ....
+          .... >abc< ....
+          .... [abc] ....
+          .... (abc) ....
+          """
+      ensure 'j.j.j.j.j.j.j.',
+        text: """
+          .... "" ....
+          .... '' ....
+          .... `` ....
+          .... {} ....
+          .... <> ....
+          .... >< ....
+          .... [] ....
+          .... () ....
+          """
+    it "applies operators any a-pair and repeatable", ->
+      ensure 'das',
+        text: """
+          ....  ....
+          .... 'abc' ....
+          .... `abc` ....
+          .... {abc} ....
+          .... <abc> ....
+          .... >abc< ....
+          .... [abc] ....
+          .... (abc) ....
+          """
+      ensure 'j.j.j.j.j.j.j.',
+        text: """
+          ....  ....
+          ....  ....
+          ....  ....
+          ....  ....
+          ....  ....
+          ....  ....
+          ....  ....
+          ....  ....
+          """


:racehorse: webkitMatchesSelector -> querySelector

--- src/selector-validator.coffee
+++ src/selector-validator.coffee
@@ -10,7 +10,9 @@

   testElement ?= document.createElement('div')
   try
-    testElement.webkitMatchesSelector(selector)
+    # querySelector appears to be faster than webkitMatchesSelector
+    # http://jsperf.com/query-vs-matches
+    testElement.querySelector(selector)
     selectorCache[selector] = true
     true
   catch selectorError


* use isObject instead of typeof

* use isObject instead of typeof

--- src/index.coffee
+++ src/index.coffee
@@ -29,7 +29,7 @@
         result = Config.loadSync(aPath, aOptions)
       return result
     @path = aPath
-    @options = aOptions if typeof aOptions == 'object'
+    @options = aOptions if isObject aOptions

   load: (aPath, aOptions, done)->
     if isObject(aPath)


Don't use a command to create the debug user.

Just always create them if we're in debugging mode.

--- src/index.coffee
+++ src/index.coffee
@@ -46,12 +46,10 @@
     theCircle.withActiveMatch msg, (m) -> m.chooseMove msg

   if process.env.HUBOT_DEBUG?
-    robot.respond /dhammer setup/i, (msg) ->
-      user = robot.brain.userForId '2',
-        name: 'someone'
-        room: 'thechalkcircle'
-      theCircle.getChallenger(user)
-      msg.reply "Ready."
+    someone = robot.brain.userForId '2',
+      name: 'someone'
+      room: 'thechalkcircle'
+    theCircle.getChallenger(someone)

     robot.respond /dhammer accept/i, (msg) ->
       user = robot.brain.userForName 'someone'


Add 2-way Dropbox sync & support for share links.

--- src/index.coffee
+++ src/index.coffee
@@ -95,6 +95,97 @@
             else
                 return false

+    class DropboxView extends SyncView
+        constructor: (syncMaster, path) ->
+            super(syncMaster)
+            @path = path
+            @lastProcessingTime = new Date()
+
+            spy.processDropbox = @processDropbox
+
+            setInterval(@heartbeat, 100)
+
+        onDataUpdated: (source, version, data) =>
+            slog "DropboxView.onDataUpdated()"
+            # @requestProcessing()
+            @dirty = true
+
+        heartbeat: () =>
+            now = new Date()
+            stale = (now - @lastProcessingTime) > (5 * 1000)
+            if (@dirty or stale) and not @processing
+                @lastProcessingTime = now
+                processDropbox()
+
+        processDropbox: () =>
+
+            finishProcessing = (done) =>
+                @processing = false
+                if done
+                    @dirty = false
+
+            slog 'processDropbox'
+            @startTime = new Date()
+
+            # Check Dropbox for changes
+            @processing = true
+            promise1 = loadDropboxPath(@path)
+            promise1.catch rejectCb
+            promise1.then (data) =>
+                slog.report = (title) =>
+                    slog title
+                    slog "syncView:  ", @version, @data
+                    slog "syncMaster:", @syncMaster.version, @syncMaster.data
+                    slog "Dropbox:   ", '0', data.text
+                    slog "Time:      ", (new Date() - @startTime)  / 1000
+
+                slog.report('BEFORE:')
+                # Only changed on syncMaster
+                if @data is data.text and @data isnt @syncMaster.data
+                    slog "Update syncView and Dropbox. (Only changed on SyncMaster)"
+                    @data = @syncMaster.data
+                    @version = @syncMaster.version
+
+                    # Update Dropbox
+                    promise2 = dbx.filesUpload options =
+                        path: path
+                        contents: @data
+                        mode:
+                            '.tag': 'update'
+                            update: data.rev
+                    promise2.then (metaData) =>
+                        data.text = @data
+                        slog.report('AFTER:')
+
+                        done = @data is @syncMaster.data and
+                               @data is data.text and
+                               @syncMaster.data is data.text
+
+                        finishProcessing(done)
+
+                # Only changed on Dropbox
+                else if @data is @syncMaster.data and @data isnt data.text
+                    slog "Update syncView and SyncMaster. (Only changed on Dropbox)"
+                    @data = data.text
+                    # Update syncMaster
+                    @syncMaster.update(this, @data)
+
+                    slog.report('AFTER:')
+                    done = @data is @syncMaster.data and
+                           @data is data.text and
+                           @syncMaster.data is data.text
+
+                    finishProcessing(done)
+
+                else if @data isnt @syncMaster.data and @data isnt data.text
+                    slog.report('CONFLICT!!\nAFTER:')
+                    finishProcessing(true)
+                else
+                    slog.report('No changes.\nAFTER:')
+                    finishProcessing(true)
+
+
+
     class DocView extends SyncView
         constructor: (syncMaster, doc) ->
             super(syncMaster)
@@ -122,6 +213,7 @@
         onDataUpdated: (source, version, data) =>
             if super(source, version, data)
                 @outline.reloadSerialization(@data)
+

     panel = new DockPanel()
     panel.id = 'main'
@@ -244,52 +336,59 @@
             window.location = makeAuthenticationUrl('CHOOSE')

     loadDropboxPath = (path) ->
-        options =
-            path: path
-        dropboxApiReady = not dropboxAccessDenied
-        if dropboxApiReady
-            p1 = dbx.filesGetMetadata options
-            p1.then (metaData) =>
-                # source: http://stackoverflow.com/questions/190852
-                fileExtension = (fname) ->
-                    fname.substr((~-fname.lastIndexOf(".") >>> 0) + 1)
-
-                if metaData['.tag'] is 'folder'
-                    log "ERROR: #{path} is a folder. (Not supported)"
-                    history.pushState(null, null, "#BLANK")
-                    dropboxApiReady = false
-
-                extension = fileExtension(metaData.name).toLowerCase()
-                if extension not in textExtensions
-                    log "ERROR: File #{path} is not a text file (based on file extension)."
-                    history.pushState(null, null, "#BLANK")
-                    dropboxApiReady = false
-                    spy.name = metaData.name
-
-                if dropboxApiReady
-                    p2 = dbx.filesDownload options
-
-                    p2.then (fileData) =>
-                        reader = new FileReader()
-
-                        reader.addEventListener 'loadend', () ->
-                            # reader.result contains the contents of blob as a typed array
-                            stringResult = new TextDecoder('utf8').decode(reader.result)
-                            doc.setValue(stringResult)
-
-                        reader.readAsArrayBuffer(fileData.fileBlob)
-
-            p1.catch (error) =>
-                slog error
-                spy.error = error
-                switch error.status
-                    when 409  # Path not found
-                        log "ERROR: File #{path} not found on Dropbox."
+        promise = new Promise (resolve, reject) ->
+
+            options =
+                path: path
+            dropboxApiReady = not dropboxAccessDenied
+            if dropboxApiReady
+                p1 = dbx.filesGetMetadata options
+                p1.then (metaData) =>
+                    # source: http://stackoverflow.com/questions/190852
+                    fileExtension = (fname) ->
+                        fname.substr((~-fname.lastIndexOf(".") >>> 0) + 1)
+
+                    if metaData['.tag'] is 'folder'
+                        log "ERROR: #{path} is a folder. (Not supported)"
                         history.pushState(null, null, "#BLANK")
-                    else
-                        ensureDropboxToken(path).then () ->
-                            log "ERROR: #{error.error} (Status: #{error.status})"
+                        dropboxApiReady = false
+
+                    extension = fileExtension(metaData.name).toLowerCase()
+                    if extension not in textExtensions
+                        log "ERROR: File #{path} is not a text file (based on file extension)."
+                        history.pushState(null, null, "#BLANK")
+                        dropboxApiReady = false
+                        spy.name = metaData.name
+
+                    if dropboxApiReady
+                        p2 = dbx.filesDownload options
+
+                        p2.then (fileData) =>
+                            reader = new FileReader()
+
+                            reader.addEventListener 'loadend', () ->
+                                # reader.result contains the contents of blob as a typed array
+                                stringResult = new TextDecoder('utf8').decode(reader.result)
+
+                                data =
+                                    text: stringResult
+                                    rev: metaData.rev
+
+                                resolve(data)
+
+                            reader.readAsArrayBuffer(fileData.fileBlob)
+
+                p1.catch (error) =>
+                    slog error
+                    spy.error = error
+                    switch error.status
+                        when 409  # Path not found
+                            log "ERROR: File #{path} not found on Dropbox."
                             history.pushState(null, null, "#BLANK")
+                        else
+                            ensureDropboxToken(path).then () ->
+                                log "ERROR: #{error.error} (Status: #{error.status})"
+                                history.pushState(null, null, "#BLANK")

     if path is '/' or path is ''
         path = 'WELCOME'
@@ -325,7 +424,16 @@
                         log "ERROR: #{error.error} (Status: #{error.status})"
                         history.pushState(null, null, "#BLANK")
             else
-                loadDropboxPath(path)
+                promise = loadDropboxPath(path)
+                promise.then (data) ->
+                    doc.setValue(data.text)
+                    dropboxView = new DropboxView(syncMaster, path)
+                    dropboxView.data = data.text
+                    dropboxView.rev = data.rev
+
+                    spy.dropboxView = dropboxView
+                promise.catch (error) ->
+                    slog 'ERROR'


rename class

rename class

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

 # Kinesis
-class Kinesis
+class KinesisWrapper

   constructor: (options)->

@@ -41,6 +41,8 @@

         throw err if err

+        console.log 'get stream info...'
+
         async.map data.StreamDescription.Shards, (shard, cb)=>
           @kinesis.getShardIterator {
               ShardId: shard.ShardId
@@ -48,7 +50,9 @@
               StreamName: data.StreamDescription.StreamName
             }, (err, data)=>
               cb(err, data.ShardIterator)
-        ,(err, shardIterators)=>
+        , (err, shardIterators)=>
+
+          console.log 'get shards...'

           @shards = shardIterators.map (i)=>
             return new KinesisShard(@kinesis, i)
@@ -150,4 +154,4 @@
     @setup()

-module.exports = new Kinesis({})
+module.exports = new KinesisWrapper({})


Fix underline

Fix underline

--- src/index.coffee
+++ src/index.coffee
@@ -1,5 +1,5 @@
 # react-controlfacades
-# =================
+# ====================

 CFCheckbox = require './CFCheckbox'


fix server workers

fix server workers

--- src/index.coffee
+++ src/index.coffee
@@ -45,7 +45,7 @@
     useSw ?= navigator.serviceWorker and window.location.protocol isnt 'http:'
     if useSw
       # only use service workers if current page has one
-      @ready = new Promise (resolve, reject) ->
+      @ready = new Promise (resolve, reject) =>
         readyTimeout = setTimeout resolve, SW_CONNECT_TIMEOUT_MS

         navigator.serviceWorker.ready


Fixed problem with livereload

Fixed problem with livereload

--- src/index.coffee
+++ src/index.coffee
@@ -142,10 +142,11 @@
     app
   reload: ->
     es.map (file, callback) ->
-      if @livereload and typeof lr == "object"
-        lr.changed body:
-          files: file.path
-      callback null, file
+      apps.forEach (app) =>
+        if app.livereload and typeof lr == "object"
+          lr.changed body:
+            files: file.path
+        callback null, file
   lr: lr
   serverClose: ->
     apps.forEach((app) -> do app.server.close)


Only get new post IDs once per minute

Only get new post IDs once per minute

--- src/index.coffee
+++ src/index.coffee
@@ -21,6 +21,28 @@
   maxAge: 1000 * 60 * 60 * 24 * 30

 cache = LRU options
+
+lastPosts = null
+
+getPostIDs = (token, callback) ->
+  if lastPosts and (Date.now() - lastPosts.date < 1000 * 60)
+    callback null, lastPosts.ids
+  else
+    headers =
+      "Accept": JSON_TYPE
+      "Authorization": "Bearer #{token}"
+    url = "https://api.producthunt.com/v1/posts"
+    cacheGet url, token, headers, (err, body) ->
+      if err
+        callback err
+      else
+        results = JSON.parse(body)
+        posts = _.map results.posts, (post) ->
+          _.pick post, ["id"]
+        lastPosts =
+          date: Date.now()
+          ids: posts
+        callback null, posts

 cacheGet = (url, token, headers, callback) ->
   key = "#{token}|#{url}"
@@ -157,20 +179,14 @@
               else
                 callback null, following
           (callback) ->
-            token = req.token
-            headers =
-              "Accept": JSON_TYPE
-              "Authorization": "Bearer #{token}"
-            url = "https://api.producthunt.com/v1/posts"
-            cacheGet url, token, headers, (err, body) ->
+            getPostIDs req.token, (err, posts) ->
               if err
                 callback err
               else
-                results = JSON.parse(body)
                 now = Date.now()
                 console.log "#{now - start} (#{now - last}) to get posts"
                 last = now
-                callback null, results.posts
+                callback null, posts
         ], callback
       (results, callback) ->
         [followings, posts] = results


Update Spellbook.Classes.Dispatcher

Update Spellbook.Classes.Dispatcher

--- components/classes/dispatcher.coffee
+++ components/classes/dispatcher.coffee
@@ -2,7 +2,7 @@
 #
 #   Dispatcher
 #   -> Run JS events based on current page
-#   Credit: https://github.com/gitlabhq/gitlabhq/blob/master/app/assets/javascripts/dispatcher.js.coffee
+#      Credit: https://github.com/gitlabhq/gitlabhq/blob/master/app/assets/javascripts/dispatcher.js.coffee
 #
 # *************************************
 #


try to make anonymous sign up clearer

try to make anonymous sign up clearer

--- src/smc-webapp/r_account.cjsx
+++ src/smc-webapp/r_account.cjsx
@@ -156,6 +156,7 @@
         email_address : rtypes.string
         redux         : rtypes.object
         disabled      : rtypes.bool
+        is_anonymous  : rtypes.bool

     getInitialState: ->
         state      : 'view'   # view --> edit --> saving --> view or edit
@@ -248,7 +249,9 @@
             <Saving />

     button_label: ->
-        if @props.email_address
+        if @props.is_anonymous
+            return "Sign up using an email address and password"
+        else if @props.email_address
             return "Change email address"
         else
             return "Set email address and password"
@@ -689,6 +692,7 @@
                 redux         = {@props.redux}
                 ref           = 'email_address'
                 maxLength     = {254}
+                is_anonymous = {@props.is_anonymous}
                 disabled  = {@props.is_anonymous and not @state.terms_checkbox}
                 />


make side chat background white

make side chat background white

--- src/smc-webapp/side_chat.cjsx
+++ src/smc-webapp/side_chat.cjsx
@@ -433,8 +433,9 @@
             overflowX    : "hidden"
             width        : "380%"
             height       : "#{@props.max_height}px"
-            margin       : "0px 0px 0px 13px"
+            margin       : "0px 0px 0px 15px"
             padding      : "0"
+            background   : "white"

         <div>
             <Row>
@@ -454,7 +455,7 @@
                 </Col>
             </Row>
             <Row>
-                <Col xs={2} style={padding:'0px 2px 0px 2px', marginLeft: "13px", width:"60%"}>
+                <Col xs={2} style={padding:'0px 2px 0px 2px', marginLeft: "15px", width:"60%"}>
                     <form>
                         <FormGroup>
                             <FormControl


made test code more concise

made test code more concise

--- qunit/hebrewDateTest.coffee
+++ qunit/hebrewDateTest.coffee
@@ -28,13 +28,15 @@

 edgeTestMultiDayChag = (name, date, length, otherOccasions = {}) ->
   otherOccasions.all ?= []
+  otherOccasions.during ?= []
+  otherOccasions.around ?= []
   before = new Date(date)
   before.setDate(before.getDate() - 1)
-  assertNotChag(name, before, (otherOccasions.before ? []).concat(otherOccasions.all))
+  assertNotChag(name, before, (otherOccasions.before ? []).concat(otherOccasions.all, otherOccasions.around))
   for i in [1..length]
-    assertIsChag(name, new Date(date), (otherOccasions["#{i}"] ? []).concat(otherOccasions.all))
+    assertIsChag(name, new Date(date), (otherOccasions["#{i}"] ? []).concat(otherOccasions.all, otherOccasions.during))
     date.setDate(date.getDate() + 1)
-  assertNotChag(name, date, (otherOccasions.after ? []).concat(otherOccasions['all']))
+  assertNotChag(name, date, (otherOccasions.after ? []).concat(otherOccasions.all, otherOccasions.around))

 shabbatTest = ->
   edgeTestOneDayChag('Shabbat', new Date(2014,6,19))
@@ -58,8 +60,8 @@
   edgeTestMultiDayChag('Shabuot', new Date(2015,4,24),2, before:['Shabbat'])

 roshHashanaTest = ->
-  edgeTestMultiDayChag('RoshHashana', new Date(2011,8,29),2, '1': ['10DaysOfTeshuba'], '2': ['10DaysOfTeshuba'], after: ['Shabbat', '10DaysOfTeshuba'])
-  edgeTestMultiDayChag('RoshHashana', new Date(2015,8,14),2, '1': ['10DaysOfTeshuba'], '2': ['10DaysOfTeshuba'], after: ['10DaysOfTeshuba'])
+  edgeTestMultiDayChag('RoshHashana', new Date(2011,8,29),2, during: ['10DaysOfTeshuba'], after: ['Shabbat', '10DaysOfTeshuba'])
+  edgeTestMultiDayChag('RoshHashana', new Date(2015,8,14),2, during: ['10DaysOfTeshuba'], after: ['10DaysOfTeshuba'])

 tenDaysOfTeshubaTest = ->
   edgeTestMultiDayChag('10DaysOfTeshuba', new Date(2011,8,29),10,'1': ['RoshHashana'], '2': ['RoshHashana'], '3': ['Shabbat'], '10': ['Shabbat', 'YomKippur'])


Update locales.coffee

Update locales.coffee

Added German translation

--- www_src/locales.coffee
+++ www_src/locales.coffee
@@ -7,5 +7,9 @@
 		title: "Оцените приложение"
 		message: "Нам очень важно знать ваше мнение, пожалуйста оцените приложение. \nЭто займет всего пару минут. \nСпасибо!"
 		buttonLabels: ["Оценить сейчас", "Напомнить позже", "Нет, спасибо"]
+	de:
+		title: "Anwendung bewerten"
+		message: "Konnten Sie nicht eine Minute spenden, um diese Anwendung zu bewerten, wenn es Ihnen gefallen hat. \nDanke!"
+		buttonLabels: ["Jetzt bewerten", "Später erinnern", "Nein, danke"]

-module.exports = AppRateLocales+module.exports = AppRateLocales


Fixed UX / tooltip for referral URL

Fixed UX / tooltip for referral URL

--- client/account/lib/views/referral/referralcustomviews.coffee
+++ client/account/lib/views/referral/referralcustomviews.coffee
@@ -74,9 +74,7 @@
     shareBox: ({title, subtitle}) =>

       container = @views.container 'share-box'
-
-      tooltipVisible = no
-
+
       kmt = globals.keymapType
       firstKeyChar = if kmt.charAt(0).toUpperCase() is 'W' then 'ctrl' else '&#8984'
       secondKeyChar = '&#x0043'
@@ -90,18 +88,16 @@
         view          :
           partial     : getReferralUrl nick()
           cssClass    : "text link"
-          mouseenter  : ->
-            if tooltipVisible not yes then @tooltip.destroy()
-          mouseleave  : ->
-            if tooltipVisible not yes then @tooltip.destroy()
           click       : ->
             referralUrl = @getElement()
             @utils.selectText referralUrl
             @setTooltip
               title     : "#{firstKeyChar.toUpperCase()} + #{secondKeyChar} to copy"
               placement : "above"
+              sticky    : yes
             @tooltip.show()
-            tooltipVisible = yes
+            @tooltip.once 'ReceivedClickElsewhere', =>
+              @tooltip.destroy()

       return container


Ace: Fix for .yml extension.

Ace: Fix for .yml extension.

--- client/app/Applications/Ace.kdapplication/acesettings.coffee
+++ client/app/Applications/Ace.kdapplication/acesettings.coffee
@@ -158,7 +158,7 @@
     typescript  : ["Typescript"   , "ts"]
     xml         : ["XML"          , "xml|rdf|rss|wsdl|xslt|atom|mathml|mml|xul|xbl"]
     xquery      : ["XQuery"       , "xq"]
-    yaml        : ["YAML"         , "yaml"]
+    yaml        : ["YAML"         , "yaml|yml"]
     objectivec  : ["Objective C"  , "__dummy__"]

   getSyntaxOptions : ->


Fixing issues with TimeboxScrollable

Fixing issues with TimeboxScrollable

--- iterationplanningboard/TimeboxScrollableSpec.coffee
+++ iterationplanningboard/TimeboxScrollableSpec.coffee
@@ -133,13 +133,13 @@

   it 'should filter newly added column after scrolling forwards', ->
     @createBoard().then =>
-      filterSpy = @spy @cardboard, 'locallyFilter'
+      filterSpy = @spy @cardboard, 'applyLocalFilters'
       @scrollForwards().then =>
         expect(filterSpy.callCount).toBe 1

   it 'should filter newly added column after scrolling backwards', ->
     @createBoard().then =>
-      filterSpy = @spy @cardboard, 'locallyFilter'
+      filterSpy = @spy @cardboard, 'applyLocalFilters'
       @scrollBackwards().then =>
         expect(filterSpy.callCount).toBe 1


add disable_yum_conf to test.coffee.sample

--- test.sample.coffee
+++ test.sample.coffee
@@ -16,6 +16,7 @@
   disable_system_tmpfs: true
   disable_system_user: true
   disable_tools_repo: true
+  disable_yum_conf: true
   docker: # eg `docker-machine create --driver virtualbox nikita`
     machine: 'nikita'
   krb5:


Added test for #10 (for-loop with reversal)

--- test/blocks.coffee
+++ test/blocks.coffee
@@ -24,3 +24,6 @@

     assigns = {'array': [ 1, 1, 1, 1] }
     render('1','{%for item in array%}{%ifchanged%}{{item}}{% endifchanged %}{%endfor%}',assigns)
+
+  test_reverse: renderTest (render, assert) ->
+    render('321', '{% for item in array reversed %}{{ item }}{% endfor %}', array: [ 1, 2, 3 ])


Remove the Arnold emoji since it's not displaying

--- scripts/arnie-quotes.coffee
+++ scripts/arnie-quotes.coffee
@@ -14,17 +14,17 @@
 odds  = [1...100]

 arnie_quotes = [
-    'GET TO THE CHOPPA! :arnold:',
-    'Your clothes, give them to me, now! :arnold:',
-    'Hasta La Vista, Baby! :arnold:',
-    'DDDAAANNNAAAA! :arnold:',
-    'You are one ugly mother******. :arnold:',
-    'It`s not a tumor! :arnold:',
-    'When I said you should screw yourself. I didn`t mean it literally. :arnold:',
-    'Can you hurry up. My horse is getting tired. :arnold:',
-    'Are these all your lunches? You mean you eat other peoples` lunches? STOP IT!! :arnold:',
-    'I`m the party pooper. :arnold:',
-    'Who is your daddy and what does he do? :arnold:'
+    'GET TO THE CHOPPA!',
+    'Your clothes, give them to me, now!',
+    'Hasta La Vista, Baby!',
+    'DDDAAANNNAAAA!',
+    'You are one ugly mother******.',
+    'It`s not a tumor!',
+    'When I said you should screw yourself. I didn`t mean it literally.',
+    'Can you hurry up. My horse is getting tired.',
+    'Are these all your lunches? You mean you eat other peoples` lunches? STOP IT!!',
+    'I`m the party pooper.',
+    'Who is your daddy and what does he do?'
 ]

 module.exports = (robot) ->


Fix conflict

Fix conflict

--- server/app/bongo/models/bucket.coffee
+++ server/app/bongo/models/bucket.coffee
@@ -135,11 +135,7 @@

           bucket.save (err)->
             if err then callback err
-<<<<<<< HEAD
-            else addIt bucket, anchor, item, callback
-=======
             else addIt bucket, anchor, item, groupName, callback
->>>>>>> d34ab7c62cda6fb11dfdbb6000eb7c8a21587315

   getPopulator =(items..., callback)->
     -> ObjectRef.populate items, (err, populated)-> callback err, populated


Add more logs

Add more logs

--- server/testSystems/Informatics.coffee
+++ server/testSystems/Informatics.coffee
@@ -168,6 +168,7 @@
         informaticsProblemId = @_informaticsProblemId(problemId)
         success = false
         for i in [1..8]
+            logger.info "Try submit #{user}, #{problem}, attempt #{i}"
             try
                 oldSubmits = await Submit.findByUserAndProblem(user.informaticsId, problemId)
                 try
@@ -176,14 +177,14 @@
                 finally
                     await downloadSubmits.runForUser(user.informaticsId, 5, 1)
             catch e
-                logger.info "Error submitting", e.message
+                logger.info "Error submitting #{user} #{problem} attempt #{i}: ", e.message
             newSubmits = await Submit.findByUserAndProblem(user.informaticsId, problemId)
             if oldSubmits.length != newSubmits.length
-                logger.info "However, submit appeared after downloadSubmits"
+                logger.info "However, submit #{user} #{problem} appeared after downloadSubmits from attempt #{i}"
                 success = true
                 break
         if not success
-            throw "Can't submit"
+            throw "Can't submit #{user} #{problem}"

     registerUser: (user) ->
         logger.info "Moving user #{user._id} to unknown group"


Increase informatics timeout

Increase informatics timeout

--- server/testSystems/Informatics.coffee
+++ server/testSystems/Informatics.coffee
@@ -17,7 +17,7 @@

 REQUESTS_LIMIT = 1
 UNKNOWN_GROUP = '7647'
-TIMEOUT = 1000
+TIMEOUT = 1000 * 6
 _requests = 0
 _promises = []


Client and server must speak the same language

--- src/xmpp/pubsub_client.coffee
+++ src/xmpp/pubsub_client.coffee
@@ -97,7 +97,12 @@
     requestIq: ->
         pubsubEl = new xmpp.Element('iq', type: @iqType()).
             c('pubsub', xmlns: @xmlns)
-        pubsubEl.cnode(@pubsubChild().root())
+        child = @pubsubChild()
+        if child instanceof Array
+            for el in child
+                pubsubEl.cnode el.root()
+        else
+            pubsubEl.cnode child.root()
         if @opts.actor
             actorEl = pubsubEl.c('actor', xmlns: NS.BUDDYCLOUD_V1)
             actorEl.attrs.type ?= @opts.actorType
@@ -160,13 +165,23 @@
         'set'

     pubsubChild: ->
-        new xmpp.Element('subscribe', node: @opts.node, jid: @opts.actor)
+        els = [new xmpp.Element('subscribe', node: @opts.node, jid: @opts.actor)]
+        if @opts.temporary? and @opts.temporary
+            els.push new xmpp.Element('options', node: @opts.node, jid: @opts.actor)
+                .c('x', xmlns: NS.DATA)
+                .c('field', var: 'FORM_TYPE', type: 'hidden')
+                .c('value').t('http://jabber.org/protocol/pubsub#subscribe_options').up().up()
+                .c('field', var: 'pubsub#expire')
+                .c('value').t('presence')
+                .root()
+        return els

     decodeReplyEl: (el) ->
         if el.is('subscription', @xmlns) and
            el.attrs.node is @opts.node
             @results.user ?= el.attrs.jid or @opts.actor
             @results.subscription ?= el.attrs.subscription or 'subscribed'
+            @results.temporary ?= el.attrs.temporary? and el.attrs.temporary is '1'

     localPushData: ->
         if @results.subscription is 'subscribed'
@@ -176,6 +191,7 @@
                 user: @results.user
                 listener: @opts.sender
                 subscription: @results.subscription
+                temporary: @results.temporary
             }]
         else
             []


Don't use the event target, but the closest link

--- core/app/assets/javascripts/base/account_popup_opener.coffee
+++ core/app/assets/javascripts/base/account_popup_opener.coffee
@@ -8,7 +8,7 @@
   popup_window.focus()

 $('html').on 'click', '.js-accounts-popup-link', (e) ->
-  showPopup($(e.target).attr("href"))
+  showPopup($(e.target).closest('.js-accounts-popup-link').attr("href"))
   e.stopPropagation()
   e.preventDefault()


Fixed string interpolation

Fixed string interpolation

--- core/app/backbone/collections/ndp_evidence_collection.coffee
+++ core/app/backbone/collections/ndp_evidence_collection.coffee
@@ -13,4 +13,4 @@
   comparator: (item) -> - item.get('impact')

   url: ->
-    '/facts/#{@fact.id}/interactors'
+    "/facts/#{@fact.id}/interactors"


fixed favicon in evidence_bottom

fixed favicon in evidence_bottom

--- core/app/backbone/views/evidence/evidence_bottom_view.coffee
+++ core/app/backbone/views/evidence/evidence_bottom_view.coffee
@@ -19,7 +19,8 @@
     showFactInfo: ->
       @fact_base?.scroll_to_link?
     fact_url_host: ->
-      new Backbone.Factlink.Url(@fact_url).host() if @fact_url?
+      if @fact_base?.fact_url?
+        new Backbone.Factlink.Url(@fact_base?.fact_url).host()

   onRender: ->
     @bindTo @model, 'change:sub_comments_count', @updateSubCommentsLink, @


Preset for key/value form fields. Close #398

--- core/htdocs_source/src/pods/oxisection-form/component.coffee
+++ core/htdocs_source/src/pods/oxisection-form/component.coffee
@@ -23,6 +23,11 @@
                     fields.insertAt index+i, clone
             if clonable.name not in names
                 names.push clonable.name
+
+        for field in fields
+            if field.value and typeof field.value is "object"
+                field.name = field.value.key
+                field.value = field.value.value

         for name in names
             clones = (f for f in fields when f.name is name)


fixes bar chart ordering (#269)

fixes bar chart ordering (#269)

--- src/viz/types/helpers/graph/includes/axes.coffee
+++ src/viz/types/helpers/graph/includes/axes.coffee
@@ -135,17 +135,23 @@
         sort = vars.order.sort.value
         agg = vars.order.agg.value or vars.aggs.value[sortKey] or "max"
         aggType = typeof agg
-        valueNest = d3.nest()
-          .key (d) -> fetchValue vars, d, vars[axis].value
-          .rollup (leaves) ->
-            if aggType is "string"
-              d3[agg] leaves, (d) -> fetchValue vars, d, sortKey
-            else if aggType is "function"
-              agg leaves, sortKey
-          .entries vars.axes.dataset
-        valueNest.sort (a, b) ->
-          if sort is "desc" then b.values - a.values else a.values - b.values
-        valueNest.reduce (arr, v) ->
+        counts = values.reduce (obj, val) ->
+          obj[val] = []
+          obj
+        , {}
+        for d in vars.axes.dataset
+          for v in d.values
+            group = fetchValue vars, v, vars[axis].value
+            counts[group].push fetchValue vars, v, sortKey
+        for k, v of counts
+          if aggType is "string"
+            counts[k] = d3[agg] v
+          else if aggType is "function"
+            counts[k] = agg v, sortKey
+        counts = d3.entries(counts)
+        counts.sort (a, b) ->
+          if sort is "desc" then b.value - a.value else a.value - b.value
+        counts.reduce (arr, v) ->
           arr.push v.key
           arr
         , []


replace QuantitiesQuestion#update with dumber js stuff

--- app/assets/javascripts/components/questions/quantities_question.cjsx
+++ app/assets/javascripts/components/questions/quantities_question.cjsx
@@ -40,9 +40,14 @@
   update: (type, answer) =>
     return unless 0 <= answer < 100

-    @props.answer?.selected ||= {}
-    @props.answer?.selected[type] = answer
-    QuestionnaireStore.selectAnswer(@props.data.id, @props.answer?.selected)
+    selectedAnswers = {
+      live: @props.answer?.selected?.live,
+      dead: @props.answer?.selected?.dead,
+      body_parts: @props.answer?.selected?.body_parts
+    }
+
+    selectedAnswers[type] = answer
+    QuestionnaireStore.selectAnswer(@props.data.id, selectedAnswers)

   valueFor: (type) => parseInt(@props.answer?.selected?[type] || 0)


show/hide Preview and Add Content buttons in content editor based on if rows exist

--- app/assets/javascripts/tenon/features/tenon_content/editor.js.coffee
+++ app/assets/javascripts/tenon/features/tenon_content/editor.js.coffee
@@ -28,7 +28,7 @@
     $editable.next('input[type=hidden]').val($editable.html())

   _updateButtons: =>
-    if $('.tn-tc-row').length == 0
+    if $('.tn-tc-row:visible').length == 0
       $('#tn-tc-add-content').show()
       $('#tn-tc-pop-out').hide()
       console.log 'no content rows'


Remove CommonJS-unfriendly package file for wesabe.canvas.geometry.

--- application/chrome/content/wesabe/canvas/geometry/__package__.coffee
+++ application/chrome/content/wesabe/canvas/geometry/__package__.coffee
@@ -1,4 +0,0 @@
-wesabe.require('canvas.geometry.Rect')
-wesabe.require('canvas.geometry.Point')
-wesabe.require('canvas.geometry.Size')
-wesabe.require('canvas.geometry.ImageData')


add all available days according to prod

--- app/assets/javascripts/components/_header.js.cjsx
+++ app/assets/javascripts/components/_header.js.cjsx
@@ -23,15 +23,20 @@

   DAYS: [
     {name: 'All Days', selected: true, value: 'All'}
-    {name: 'March 31', selected: false, value: 0}
-    {name: 'April 1', selected: false, value: 1}
-    {name: 'April 2', selected: false, value: 2}
-    {name: 'April 3', selected: false, value: 3}
-    {name: 'April 4', selected: false, value: 4}
-    {name: 'April 5', selected: false, value: 5}
-    {name: 'April 6', selected: false, value: 6}
-    {name: 'April 7', selected: false, value: 7}
-
+    {name: 'Mar 31', selected: false, value: 0}
+    {name: 'Apr 1', selected: false, value: 1}
+    {name: 'Apr 2', selected: false, value: 2}
+    {name: 'Apr 3', selected: false, value: 3}
+    {name: 'Apr 4', selected: false, value: 4}
+    {name: 'Apr 5', selected: false, value: 5}
+    {name: 'Apr 6', selected: false, value: 6}
+    {name: 'Apr 7', selected: false, value: 7}
+    {name: 'Apr 8', selected: false, value: 8}
+    {name: 'Apr 9', selected: false, value: 9}
+    {name: 'Apr 10', selected: false, value: 10}
+    {name: 'Apr 11', selected: false, value: 11}
+    {name: 'Apr 12', selected: false, value: 12}
+    {name: 'Apr 13', selected: false, value: 13}
   ]

   home: (event) ->


Trim Quantity#format

Trim Quantity#format

Prevents unit-less values from terminating with a blank space.

--- app/assets/javascripts/lib/models/quantity.coffee
+++ app/assets/javascripts/lib/models/quantity.coffee
@@ -172,7 +172,7 @@
       if opts.strip_insignificant_zeros and opts.precision is 0 and @value is 0
         opts.precision = 1

-    "#{ I18n.toNumber(@value, opts) } #{ @localizedUnit() }"
+    "#{ I18n.toNumber(@value, opts) } #{ @localizedUnit() }".trim()

   localizedUnit: ->
     if @unit.base.i18n


Log jQuery version at startup.

Log jQuery version at startup.

--- app/assets/javascripts/roofpig/document.js.coffee
+++ app/assets/javascripts/roofpig/document.js.coffee
@@ -1,21 +1,22 @@
 $(document).ready ->
-  canvas_works = !!window.CanvasRenderingContext2D
+  console.log("jQuery version", $.fn.jquery)

-  webgl_works = (->
+  webgl_browser = (->
     try
       return !!window.WebGLRenderingContext and !!document.createElement("canvas").getContext("experimental-webgl")
     catch e
       return false
   )()

-  if canvas_works
-    log_error("No WebGL support in this browser. Falling back on regular Canvas.") unless webgl_works
+  canvas_browser = !!window.CanvasRenderingContext2D
+  if canvas_browser
+    log_error("No WebGL support in this browser. Falling back on regular Canvas.") unless webgl_browser
   else
     log_error("No Canvas support in this browser. Giving up.")

   for roofpig_div in $('.roofpig')
-    new Display($(roofpig_div), webgl_works, canvas_works).animate()
+    new Display($(roofpig_div), webgl_browser, canvas_browser).animate()

   $('.roofpig').click ->
     display = Display.instances[$(this).data('dpid')]


fix json url

fix json url

--- app/assets/javascripts/tournaments/show.js.coffee
+++ app/assets/javascripts/tournaments/show.js.coffee
@@ -1,4 +1,6 @@
-Update = (tournament) ->
+json_url = window.location.href.replace(/\.[^/]*$/,'')+".json"
+
+update = (tournament) ->
 	here = tournament["players"].length
 	needed = (tournament["min_teams_per_match"] * tournament["min_players_per_team"])
 	pct_complete = here / needed
@@ -21,10 +23,10 @@

 	# Do it all again
 	setTimeout (->
-		$.ajax(url: window.location.href.replace(/\..*/,'')+".json").done Update
+		$.ajax(url: json_url).done update
 		return
 	), 2000

 # Now kick off the whole process
 window.onload = ->
-  $.ajax(url: window.location.href.replace(/\..*/,'')+".json").done Update
+  $.ajax(url: json_url).done update


wording tweak

wording tweak

Former-commit-id: 3f6d96a35cbb0d1e1a0352a33931b355d6b1174b [formerly 1e5ae63f6ae52a150aa6f0e7250f0c029bc80d04] [formerly 5a134ca0ed1aec57c31e625859a0a021accc5ea8 [formerly 50064ce361873ce98125a8f9c2c6c91b2410133a]] [formerly 6d6f9ff23584a5c7151ddc1c188447079f9a01bc [formerly 25881e2d9303be143c23a02b236505bd29d1c8e1] [formerly 7da426460e56e5521a1c89bb902e1cc2d7c36d6e [formerly 7da426460e56e5521a1c89bb902e1cc2d7c36d6e [formerly 7879f21d3f254b9debbe56fd617c77cc5ab249d6]]]]
Former-commit-id: 9c1461dedae52bddfdcda7b4995fa689e6eecee3 [formerly 3f1f942df0f3baa63d6f4d6dbdc23f36c06cc045] [formerly b6a8158311900c0cfde17be1848cfc4383060cc2 [formerly 5bf931646519ed665f52d3bb35e029ee0f75d5a8]]
Former-commit-id: e1dfb860de0a7a87ba5d750d37c46d3adee3d79d [formerly 77eb9a9f2607cb71746c67227ad1b5fd8faac8b1]
Former-commit-id: b81fecfaa85636ce28ab92cd6d9e79fc276fdd92
Former-commit-id: df313c5144bae5924e6308fc73c1b3d6410c5f99

--- @client/admin.coffee
+++ @client/admin.coffee
@@ -303,10 +303,10 @@
     current_user = fetch '/current_user'

     roles = [
-      {name: 'editor', label: 'Editors', description: 'Can modify the description of the proposal, as well as write, comment, and opine.', icon: 'fa-edit', wildcard: {label: 'Any registered user who can access can edit', default: false}},
-      {name: 'writer', label: 'Writers', description: 'Can write pro and con points that are shared with others. Any writer can comment and opine.', icon: 'fa-th-list', wildcard: {label: 'Any registered user who can access can write', default: true}},
-      {name: 'commenter', label: 'Commenters', description: 'Can comment on shared pro and con points.', icon: 'fa-comment', wildcard: {label: 'Any registered user who can access can comment', default: true}},
-      {name: 'opiner', label: 'Opiners', description: 'Can contribute their opinions of this proposal. But not original content.', icon: 'fa-bar-chart', wildcard: {label: 'Any registered user who can access can opine', default: true}},
+      {name: 'editor', label: 'Editors', description: 'Can modify the description of the proposal, as well as write, comment, and opine.', icon: 'fa-edit', wildcard: {label: 'Any registered user who can observe can edit', default: false}},
+      {name: 'writer', label: 'Writers', description: 'Can write pro and con points that are shared with others. Any writer can comment and opine.', icon: 'fa-th-list', wildcard: {label: 'Any registered user who can observe can write', default: true}},
+      {name: 'commenter', label: 'Commenters', description: 'Can comment on shared pro and con points.', icon: 'fa-comment', wildcard: {label: 'Any registered user who can observe can comment', default: true}},
+      {name: 'opiner', label: 'Opiners', description: 'Can contribute their opinions of this proposal. But not original content.', icon: 'fa-bar-chart', wildcard: {label: 'Any registered user who can observe can opine', default: true}},
       {name: 'observer', label: 'Observers', description: 'Can access this proposal. But that\'s it.', icon: 'fa-eye', wildcard: {label: "Public. Anyone can view", default: true}}
     ]


Make it so that the code doesn't throw jshint error.

--- app/assets/volume/slot.coffee
+++ app/assets/volume/slot.coffee
@@ -62,7 +62,7 @@

      # Now that everything is sorted, let's separate out the first element
      # of the array, which should be the closest.
-     [closest, rest...] = smallPlacements
+     [closest]= smallPlacements

      # "snap" the element by setting position to the closest
      dragPosition = closest.position


Remove initializer from service blueprint

--- blueprints/service/files/app/initializers/__name__-service.coffee
+++ blueprints/service/files/app/initializers/__name__-service.coffee
@@ -1,6 +0,0 @@
-<%= classifiedModuleName %>Initializer =
-  name: '<%= dasherizedModuleName %>-service'
-  initialize: (container, app) ->
-    app.inject 'route', '<%= camelizedModuleName %>Service', 'service:<%= dasherizedModuleName %>'
-
-`export default <%= classifiedModuleName %>Initializer`


add correct routing to poll_common_preview

--- client/vue/components/poll_common_preview/poll_common_preview.coffee
+++ client/vue/components/poll_common_preview/poll_common_preview.coffee
@@ -1,15 +1,17 @@
-Session = require 'shared/services/session'
+Session       = require 'shared/services/session'
+LmoUrlService = require 'shared/services/lmo_url_service'

 module.exports =
   props:
     poll: Object
     displayGroupName: String
   methods:
+    urlFor: (model) -> LmoUrlService.route(model: model)
     showGroupName: ->
       @displayGroupName && @poll.group()
   template:
     """
-
+
       <poll-common-chart-preview :poll="poll"></poll-common-chart-preview>


More explicit "if"

More explicit "if"

--- core/app/assets/javascripts/lib/prevent_scroll_propagation.coffee
+++ core/app/assets/javascripts/lib/prevent_scroll_propagation.coffee
@@ -21,4 +21,6 @@
       return if useNativeBehaviour($el)

       event.stopPropagation() # allow nesting of scrollable containers
-      event.preventDefault() if preventScroll($el, delta)
+
+      if preventScroll($el, delta)
+        event.preventDefault()


Use !! instead of || false

Use !! instead of || false

--- core/app/backbone/views/buttons/react_sliding_share_button.coffee
+++ core/app/backbone/views/buttons/react_sliding_share_button.coffee
@@ -56,7 +56,7 @@
   displayName: 'ReactSlidingShareButton'

   getInitialState: ->
-    opened: @props.initialOpened || false
+    opened: !!@props.initialOpened

   _toggleButton: -> @setState opened: !@state.opened


add some basic message filtering

add some basic message filtering

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -9,4 +9,12 @@

 window.sseSource      = new EventSource('/events')
 window.onbeforeunload = -> window.sseSource.close()
-sseSource.onmessage   = (event) -> console.log(JSON.parse event.data)
+messageRegex          = /^(New|DTMF|PeerStatus|Originate|Masquerade|Rename|Bridge|Hangup|SoftHangup)/
+
+sseSource.onmessage = (event) ->
+  data = JSON.parse(event.data)
+
+  if data.from
+    console.log('>> Call from:', data.from, 'To:', data.to)
+  else if messageRegex.test(data.name)
+    console.log(data.name, data.headers)


Handle posting emoji only vote +1 and -1 comments.

And remove mutual votes.

--- app/assets/javascripts/awards_handler.coffee
+++ app/assets/javascripts/awards_handler.coffee
@@ -25,26 +25,9 @@

     e.preventDefault()

-    $emojiBtn = $(e.currentTarget)
-    $addAwardBtn = $('.js-add-award.is-active')
-    $votesBlock = $($addAwardBtn.closest('.js-award-holder').data('target'))
-
-    if $addAwardBtn.length is 0
-      $votesBlock = $emojiBtn.closest('.js-awards-block')
-    else if $votesBlock.length is 0
-      $votesBlock = $addAwardBtn.closest('.js-awards-block')
-
-    $votesBlock.addClass 'js-awards-block'
-    awardUrl = $votesBlock.data 'award-url'
-    emoji = $emojiBtn.find('.icon').data('emoji')
-
-    if emoji in [ 'thumbsup', 'thumbsdown' ]
-      mutualVote = if emoji is 'thumbsup' then 'thumbsdown' else 'thumbsup'
-
-      isAlreadyVoted = $("[data-emoji=#{mutualVote}]").parent().hasClass 'active'
-      @addAward awardUrl, mutualVote if isAlreadyVoted
-
-    @addAward awardUrl, emoji
+    emoji = $(e.currentTarget).find('.icon').data 'emoji'
+    @getVotesBlock().addClass 'js-awards-block'
+    @addAward @getAwardUrl(), emoji

   showEmojiMenu: ($addBtn) ->
@@ -105,16 +88,21 @@

     $menu.css(css)

-  addAward: (awardUrl, emoji) ->
+
+  addAward: (awardUrl, emoji, checkMutuality = yes) ->
+
     emoji = @normilizeEmojiName(emoji)
     @postEmoji awardUrl, emoji, =>
-      @addAwardToEmojiBar(emoji)
+      @addAwardToEmojiBar(emoji, checkMutuality)

       $('.js-awards-block-current').removeClass 'js-awards-block-current'

-    $(".emoji-menu").removeClass "is-visible"
-
-  addAwardToEmojiBar: (emoji) ->
+    $('.emoji-menu').removeClass 'is-visible'
+
+
+  addAwardToEmojiBar: (emoji, checkForMutuality = yes) ->
+
+    @checkMutuality emoji  if checkForMutuality
     @addEmojiToFrequentlyUsedList(emoji)

     emoji = @normilizeEmojiName(emoji)
@@ -130,6 +118,24 @@
         @addMeToUserList(emoji)
     else
       @createEmoji(emoji)
+
+
+  getVotesBlock: -> return $ '.awards.js-awards-block'
+
+
+  getAwardUrl: -> @getVotesBlock().data 'award-url'
+
+
+  checkMutuality: (emoji) ->
+
+    awardUrl = @getAwardUrl()
+
+    if emoji in [ 'thumbsup', 'thumbsdown' ]
+      mutualVote = if emoji is 'thumbsup' then 'thumbsdown' else 'thumbsup'
+
+      isAlreadyVoted = $("[data-emoji=#{mutualVote}]").parent().hasClass 'active'
+      @addAward awardUrl, mutualVote, no if isAlreadyVoted
+

   isActive: ($emojiBtn) ->
     $emojiBtn.hasClass("active")


fix js

fix js

--- app/assets/javascripts/time_sheets.js.coffee
+++ app/assets/javascripts/time_sheets.js.coffee
@@ -3,16 +3,12 @@
 $(document).on 'ajax:error', '.reveal-modal form', (xhr, status, error) ->
   console.log xhr, status, error

-$(document).on 'ajax:success', '.reveal-modal form', (data, status, xhr) ->
+$(document).on 'ajax:complete', '.reveal-modal form', (xhr, status) ->
   $(this).foundation('reveal', 'close')
-  window.location.reload()

 $(document).on 'click', '.stop-timer', ->
   unless $('.stop-timer').hasClass 'disabled'
     $('.stop-timer i').removeClass('icon-spin')
-
-$(document).on 'ajax:complete', '.stop-timer', (xhr, status) ->
-  window.location.reload()

 $(document).on 'click', '.unhider', ->
   $('.' + $(this).data('hide-class')).hide()
@@ -20,15 +16,13 @@
   false

 $(document).on 'keyup', '.reveal-modal.time form #time_entry_end_time, .reveal-modal.time form #time_entry_start_time', ->
-  form_id = "#" + $(this).parents('form').attr('id')
-  startEl = $("#{form_id} #time_entry_start_time")
-  endEl   = $("#{form_id} #time_entry_end_time")
+  startEl = $("#time_entry_start_time")
+  endEl   = $("#time_entry_end_time")

-  unless $("#{form_id} #time_entry_submit").val() == I18n.t('time_entries.form.save')
-    if endEl.val()
-      $("#{form_id} #time_entry_submit").val(I18n.t('time_entries.form.add_entry'))
-    else
-      $("#{form_id} #time_entry_submit").val(I18n.t('time_entries.form.start_timer'))
+  if endEl.val()
+    $("#time_entry_submit").val(I18n.t('time_entries.form.save'))
+  else
+    $("#time_entry_submit").val(I18n.t('time_entries.form.start_timer'))

   startValue = $.fn.timepicker.parseTime startEl.val()
   endValue   = $.fn.timepicker.parseTime endEl.val()
@@ -41,8 +35,7 @@
   else
     value = ""

-  $("#{form_id} .time-difference").html(value)
-
+  $(".time-difference").html(value)

 $ ->
   # Do not put event listeners inside here (they will add up through turbolinks)


String#extension

String#extension

--- test/string_extensions.coffee
+++ test/string_extensions.coffee
@@ -19,7 +19,7 @@
   equals "README".extension(), ""
   equals "README.md".extension(), "md"

-  equals "jquery.min.js".extension(), "md"
+  equals "jquery.min.js".extension(), "js"

 test "#humanize", ->
   equals "employee_salary".humanize(), "Employee salary"


Client: Activity: Feed: formattng

Client: Activity: Feed: formattng

--- client/activity/lib/components/activitysharepopup/index.coffee
+++ client/activity/lib/components/activitysharepopup/index.coffee
@@ -17,7 +17,7 @@
     isOpened : no
     gplus    : { enabled : yes }
     facebook : { enabled : yes }
-    twitter  : { enabled : yes, text : '' }
+    twitter  : { enabled : yes, text  : '' }
     linkedin : { enabled : yes, title : 'Koding.com'}


Client: Activity: Feed: Updated classnames

--- client/activity/lib/components/comments/commentlistitem.coffee
+++ client/activity/lib/components/comments/commentlistitem.coffee
@@ -67,12 +67,23 @@
     hasValue = yes  if value.trim()
     @setState { hasValue: hasValue, commentValue: value }

+
+  getClassNames: ->
+
+    { comment } = @props
+
+    classnames
+      'CommentListItem' : yes
+      'editing'         : comment.get '__isEditing'
+      'edited'          : comment.get('createdAt') isnt comment.get('updatedAt')
+
+
   render: ->

     { comment } = @props
     return null  unless comment

-

+

         <ProfileLinkContainer origin={comment.get('account').toJS()}>
           <Avatar className='FeedItem-Avatar' width={35} height={35} />


Classroom: Implemented goToChapter.

Classroom: Implemented goToChapter.

--- client/app/Applications/Classroom.kdapplication/AppView.coffee
+++ client/app/Applications/Classroom.kdapplication/AppView.coffee
@@ -53,13 +53,26 @@
       @coursesView.noEnrolledCourse.show()

   goToCourse: (courseName, callback = noop) ->
-    @readFileContent "/#{courseName}.kdcourse/manifest.json", (manifest) =>
+    @readFileContent "/#{courseName}.kdcourse/manifest.json", (@manifest) =>
       manifest.startWithSplashView = yes
       @createCoursesView manifest
       callback()

-  goToChapter: ->
-    log "handle go to chapter"
+  goToChapter: (courseName, chapter) ->
+    if @manifest
+      @handleGoToChapter chapter
+    else
+      @readFileContent "/#{courseName}.kdcourse/manifest.json", (@manifest) =>
+        @handleGoToChapter chapter
+
+  handleGoToChapter: (chapterIndex) ->
+    courseManifest   = @manifest
+    {chapters}       = courseManifest
+    return unless chapters
+
+    configFilePath = chapters[chapterIndex].resourcesPath
+    @readFileContent "/#{courseManifest.name}.kdcourse/#{configFilePath}", (config) =>
+      new ClassroomChapterView { container: @ }, { config, courseManifest }

   createCoursesView: (manifest) ->
     @addSubView new ClassroomCourseView { delegate: this }, manifest


Classroom: Template changes.

Classroom: Template changes.

--- client/app/Applications/Classroom.kdapplication/AppView.coffee
+++ client/app/Applications/Classroom.kdapplication/AppView.coffee
@@ -2,16 +2,14 @@

   constructor: (options = {}, data) ->

+    options.cssClass = "classroom"
+
     super options, data

-    @addSubView @container = new KDView
-      cssClass : "classroom"
       partial  : """
-

-

-
<classroom />

-
Start learning, teaching and sharing together.
 It's your classroom and it's online and it's free!

-
-

+

+
<classroom />

+
Start learning, teaching and sharing together.
 It's your classroom and it's online and it's free!

+

       """


Client: Sidebar: Moved renderInvitationWidget logic to sideBar component

--- client/app/lib/components/sidebarmachineslistitem/index.coffee
+++ client/app/lib/components/sidebarmachineslistitem/index.coffee
@@ -256,3 +256,4 @@

 React.Component.include.call SidebarMachinesListItem, [KDReactorMixin]

+


profile: logging removed

profile: logging removed

--- client/landing/app/MainApp/lazy/staticprofilecontroller.coffee
+++ client/landing/app/MainApp/lazy/staticprofilecontroller.coffee
@@ -134,6 +134,7 @@
       @addStaticLogic()
       @emit 'StaticProfileNavLinkClicked', 'CBlogPostActivity', @staticListWrapper, @staticController, =>
         @showWrapper @staticListWrapper
+

     @on 'StaticProfileNavLinkClicked', (facets,wrapper,controller,callback=->)=>
       @profileLoadingBar.setClass 'active'
@@ -426,7 +427,6 @@

     if @profileUser
       @profileUser.fetchAbout (err,about)=>
-        log arguments
         if err
           log err
         else


fix satisfaction survey

fix satisfaction survey

--- client/src/indicators/directives/client-form/controller.coffee
+++ client/src/indicators/directives/client-form/controller.coffee
@@ -4,9 +4,8 @@
   loadingToast
   Sprint
 ) ->
-  if _.isArray $scope.sprint?.indicators?.satisfactionSurvey
-    for question, index in $scope.sprint.indicators.satisfactionSurvey
-      $scope.template[index].answer = question.answer
+  _.forEach $scope.sprint.indicators.satisfactionSurvey, (question, index) ->
+    $scope.template[index].answer = question.answer

   $scope.save = ->
     loadingToast.show()


Only return the "worst" contact point for simplicity

--- app/js/physics.coffee
+++ app/js/physics.coffee
@@ -160,6 +160,14 @@
       if depth >= 0
         contacts.push new Contact(point, minAxis, depth)

+    # For simplicity sake, only return the "deepest" contact point. Eventually
+    # the physics engine will need to track more than one contact and update
+    # them as each contact is resolved.
+    if contacts[1] && contacts[1].depth > contacts[0].depth
+      contacts = [contacts[1]]
+    else
+      contacts = [contacts[0]]
+
     @debug.contacts = contacts

     contacts


Handle case where editor has no path, fixes #11


SimpleBinding impl added

SimpleBinding impl added

--- impl/base/level1/binding.coffee
+++ impl/base/level1/binding.coffee
@@ -85,15 +85,33 @@
 			@child?.destroy()
 			return

+	isSimpleBinding = (binding) ->
+		binding.length is 1 and not isArray(binding[0][0])
+
+	class SimpleBinding
+		constructor: (@obj, @prop, binding) ->
+			item = obj._ref or obj
+			target = @target = binding[0]
+			handlerName = signal.getHandlerName "#{target[1]}Changed"
+			target[0][handlerName]? @update, @
+
+			Object.preventExtensions @
+
+			if item._isReady
+				@update()
+			else
+				item.onReady @update, @
+
+		update: ->
+			@obj[@prop] = @target[0][@target[1]]
+			return
+
+		destroy: ->
+			handlerName = signal.getHandlerName "#{@target[1]}Changed"
+			@target[0][handlerName].disconnect @update, @
+			return
+
 	class Binding
-		@prepare = (arr, item) ->
-			for elem, i in arr
-				if isArray elem
-					Binding.prepare elem, item
-				else if elem is 'this'
-					arr[i] = item
-			null
-
 		@getHash = do ->
 			argI = 0

@@ -133,10 +151,8 @@
 				args.push "return #{hash};"
 				cache[hash] = Function.apply null, args

-		# TODO: implement fast bindings [[item, prop]]
 		constructor: (@obj, @prop, binding, @extraResultFunc=null) ->
 			item = obj._ref or obj
-			Binding.prepare binding, item

 			# properties
 			@func = Binding.getFunc binding
@@ -149,7 +165,7 @@
 			# connections
 			connections = @connections = []
 			for elem in binding
-				if isArray elem
+				if isArray(elem)
 					connections.push new Connection @, elem[0], elem[1]

 			# update
@@ -221,5 +237,8 @@
 		data.bindings[prop]?.destroy()

 		if binding?
-			data.bindings[prop] = new Binding @, prop, binding, extraResultFunc
+			if not extraResultFunc and isSimpleBinding(binding)
+				data.bindings[prop] = new SimpleBinding @, prop, binding
+			else
+				data.bindings[prop] = new Binding @, prop, binding, extraResultFunc
 		return


Follow mixin

Follow mixin

--- test/follow.coffee
+++ test/follow.coffee
@@ -16,7 +16,7 @@
   enemy.include(Follow)
   enemy.follow(player)

-  ok enemy.I.velocity.equal(Point(1, 0)), 'enemy should '
+  ok enemy.I.velocity.equal(Point(1, 0)), 'enemy should head '

   fastEnemy = GameObject
     x: 50


Fix broken specs due to keybindings change

--- spec/extensions/command-panel-spec.coffee
+++ spec/extensions/command-panel-spec.coffee
@@ -108,7 +108,7 @@
     it "closes the command panel", ->
       rootView.trigger 'command-panel:toggle'
       expect(rootView.find('.command-panel').view()).toBe commandPanel
-      commandPanel.miniEditor.trigger keydownEvent('escape')
+      commandPanel.miniEditor.hiddenInput.trigger keydownEvent('escape')
       expect(rootView.find('.command-panel')).not.toExist()

   describe "when return is pressed on the panel's editor", ->
@@ -116,7 +116,7 @@
       spyOn(commandPanel, 'execute')
       rootView.trigger 'command-panel:toggle'
       commandPanel.miniEditor.insertText 's/hate/love/g'
-      commandPanel.miniEditor.trigger keydownEvent('enter')
+      commandPanel.miniEditor.hiddenInput.trigger keydownEvent('enter')

       expect(commandPanel.execute).toHaveBeenCalled()

@@ -125,7 +125,7 @@
         rootView.trigger 'command-panel:toggle'
         commandPanel.miniEditor.insertText 'garbage-command!!'

-        commandPanel.miniEditor.trigger keydownEvent('enter')
+        commandPanel.miniEditor.hiddenInput.trigger keydownEvent('enter')
         expect(commandPanel.parent()).toExist()
         expect(commandPanel).toHaveClass 'error'


ignore some test

ignore some test

--- spec/javascripts/todos_spec.js.coffee
+++ spec/javascripts/todos_spec.js.coffee
@@ -8,5 +8,5 @@
     expect(t.Views).not.toBeNull()
     expect(t.Views.Todos).not.toBeNull()
     expect(t.Routers).not.toBeNull()
-  it 'can init', () ->
-    t.init()
+#  it 'can init', () ->
+#    t.init()


Added tests.

Added tests.

* Tests from @epidemian, thanks!

--- test/parser.coffee
+++ test/parser.coffee
@@ -13,3 +13,35 @@
   test 'deeply nested expressions', ->
     @shouldParse '((((((((((((((((((((0))))))))))))))))))))'
     @shouldParse '++++++++++++++++++++0'
+
+  test '#142 inconsistently indented object literal', ->
+    inconsistently =
+      indented:
+               object:
+                literal: yes
+    eq inconsistently.indented.object.literal, yes
+
+  test 'inconsistently indented if statement', ->
+    f = (b) ->
+      if b
+        'if'
+      else
+           'else'
+    eq 'if', f yes
+    eq 'else', f no
+
+  test 'inconsistent object literal dedent', ->
+    @shouldNotParse '''
+      obj =
+           foo: 5
+        bar: 6
+    '''
+
+  test 'inconsistent if statement dedent', ->
+    @shouldNotParse '''
+      f = ->
+          if something
+            'yup'
+        else
+          'nope'
+    '''


ResourceListController: fetchResources from current group

--- client/admin/lib/views/resources/resourcelistcontroller.coffee
+++ client/admin/lib/views/resources/resourcelistcontroller.coffee
@@ -11,7 +11,7 @@
   constructor: (options = {}, data) ->

     options = kd.utils.extend {}, options,
-      limit               : 30
+      limit               : 10
       noItemFoundText     : 'No resource found!'
       useCustomScrollView : yes
       startWithLazyLoader : yes
@@ -52,9 +52,21 @@

   fetch: (query, callback, options = {}) ->

-    callback null, [
-      {title: 'WIP Resource'}
-    ]
+    { groupsController } = kd.singletons
+
+    options.limit ?= @getOption 'limit'
+    options.sort   = '_id' : -1
+
+    group = groupsController.getCurrentGroup()
+    group.fetchResources query, options, (err, resources) =>
+
+      if err
+        @hideLazyLoader()
+        showError err, \
+          KodingError : "Failed to fetch data, try again later."
+        return
+
+      callback err, resources

   loadItems: (query) ->


myteam/teamsettings: minor change and add TeamName classname

--- client/home/lib/myteam/components/hometeamsettings/view.coffee
+++ client/home/lib/myteam/components/hometeamsettings/view.coffee
@@ -86,7 +86,9 @@
   encode = if title then title else ''
   value = teamName or Encoder.htmlDecode encode

-  <fieldset className='half'>
+  className = if canEdit then 'half' else 'half TeamName'
+
+  <fieldset className=className>
     <label>Team Name</label>
     <TeamNameInputArea canEdit={canEdit} value={value} callback={callback} />
   </fieldset>
@@ -94,21 +96,13 @@

 TeamNameInputArea = ({ canEdit, value, callback }) ->

-  if canEdit
-    <input
-      type='text'
-      name='title'
-      value={value}
-      className='kdinput text js-teamName'
-      onChange={callback} />
-  else
-    <input
-      type='text'
-      name='title'
-      value={value}
-      disabled={not canEdit}
-      className='kdinput text js-teamName'
-      onChange={callback} />
+  <input
+    type='text'
+    name='title'
+    value={value}
+    disabled={not canEdit}
+    className='kdinput text js-teamName'
+    onChange={callback} />

 GenericButton = ({ className, title, callback }) ->


Don't fail when there are no failtimes

Don't fail when there are no failtimes

--- resources/assets/coffee/react/beatmapset-page/success-rate.coffee
+++ resources/assets/coffee/react/beatmapset-page/success-rate.coffee
@@ -29,6 +29,8 @@
     $(window).off '.beatmapSetPageSuccessRate'

   _renderChart: ->
+    return unless @props.failtimes.length > 0
+
     data = @props.failtimes.map (m) -> m.data

     max = Math.max d3.max(data[0]), d3.max(data[1])


新增模块 添加标题

新增模块 添加标题

--- lib/package/package-info-view.coffee
+++ lib/package/package-info-view.coffee
@@ -14,7 +14,9 @@
         # icon
         @div class: 'col-xs-3', =>
           @img class: 'icon', click: 'onClickIcon', outlet: 'icon'
-          @subview 'title', new EditorView(mini: true, placeholderText: 'Title'), class: 'title'
+          @div class: "form-group", =>
+            @label 'Title'
+            @subview 'title', new EditorView(mini: true, placeholderText: 'Title'), class: 'title'
         # info
         @div class: 'col-xs-9', =>
           @div class: "form-group", =>


1.完善组件上传功能。组件能够上传至对应的变色龙模块中

1.完善组件上传功能。组件能够上传至对应的变色龙模块中

--- lib/package/package-list-view.coffee
+++ lib/package/package-list-view.coffee
@@ -5,6 +5,7 @@
 fs = require 'fs'
 request = require 'request'
 Q = require 'q'
+# encrypt = require './sandbox/sandboxLite'

 readdir = Q.denodeify fs.readdir
 stat = Q.denodeify fs.stat
@@ -100,7 +101,8 @@
     @list.html ''
     PackageCell = require './package-cell-template'
     for module in modules
-      if(module.package)
+      _package = module.package
+      if(_package && _package.build && _package.version)
         cell = new PackageCell(module)
         cell.on 'upload', (cell, module)=>
           @upload(cell, module)
@@ -108,6 +110,8 @@

     atom.workspaceView.append this

+    #   @encrypt module.path, (path.join atom.project.path, 'encrypt', module.package.identifier)
+    # .then (result) =>
   upload: (cell, module)->
     cell.changeState 'upload'

@@ -116,6 +120,7 @@
       throw new Error('login fail') unless result.result is 'true'
       console.log "upload"
       zip = new AdmZip()
+      # zip.addLocalFolder path.join(atom.project.path, 'encrypt', module.package.identifier)
       zip.addLocalFolder module.path
       @uploadAttach zip.toBuffer()
     .then (result) =>
@@ -123,9 +128,13 @@
       throw new Error('upload fail') unless result.result is 'success'
       @validateAttach(result.id).then (nResult) -> _.extend(nResult, boundle: result.id)
     .then (result) =>
+      console.log "fetchWidgetId"
+      @fetchWidgetIdByIdentifier(module.package.identifier).then (nResult) ->
+        _.extend(result, widget_id: nResult.id)
+    .then (result) =>
       console.log "new module"
       throw new Error('validate fail') unless result.result is 'success'
-      @newModule _.extend result, {widget_id: '548698920cf2da4e8927bab5'}
+      @newModule _.extend result, {widget_id: result.widget_id}
     .then (result) ->
       cell.changeState 'normal' if result.result is 'success'
     .catch (err) ->
@@ -140,6 +149,9 @@
       return error err if err
       success JSON.parse(body) if success

+  fetchWidgetIdByIdentifier: (id, success, error) ->
+    r = request_get "http://115.28.1.119:18860/mam/api/mam/widget/checkIdentity?identify=#{id}"
+
   newModule: (data, success, error) ->

     Q.Promise (resolve, reject, notify) ->
@@ -179,5 +191,11 @@
         filename: 'upload.zip'
         contentType: 'application/zip'

+  encrypt: (sourceDir, targetDir) ->
+
+    Q.Promise (resolve, reject, notify) ->
+      encrypt sourceDir, targetDir, 'foreveross', (err)=>
+        if err then reject(err) else resolve()
+
   destroy: ->
     @detach()


Fix notification class, clear on hover

Fix notification class, clear on hover

--- components/notifications_menu/client/notifications_view.coffee
+++ components/notifications_menu/client/notifications_view.coffee
@@ -17,15 +17,19 @@

   clearNotifications: (e)->
     @notifications.markRead()
-    @$('.user-avatar').removeClass 'has-notifications'
+    @unsetNotifications()
+
+  openDropdown: ->
+    super
+    @clearNotifications()

   maybeSetNotifications: ->
     if (count = @notifications.getNumberUnread()) > 0
-      @$('.notifications-menu__count').addClass '.notifications-menu__count--has-notifications'
+      @$('.notifications-menu__count').addClass 'notifications-menu__count--has-notifications'
       @$('.notifications-menu__count').text count

   unsetNotifications: ->
-    @$('.user-avatar, .dropdown__link--notifications').removeClass '.notifications-menu__count--has-notifications'
+    @$('.notifications-menu__count').removeClass 'notifications-menu__count--has-notifications'
     @$('.notifications-menu__count').text "0"

   renderFeed: ->