Fix history api call #30

Fix history api call #30

--- src/plugins/history.coffee
+++ src/plugins/history.coffee
@@ -1,17 +1,22 @@
 if not not (window.history and window.history.pushState)
   getImageFromUri = ->
     hash = window.location.hash
-    return unless hash
+
+    if not hash and isOpen
+      opened.close()
+      return

     if isOpen
       item = opened.storage.search hash
-      return opened.select item if item
+      if item
+        opened.select item, false
+        return

     for chocolate in instances
       item = chocolate.storage.search hash
       if item
         opened.close() if isOpen
-        chocolate.open item
+        chocolate.open item, false
         return

   if 'onhashchange' of window
@@ -25,7 +30,7 @@

   pushState = (title, hash) ->
     title = title or ''
-    hash = hash or ''
+    return unless hash

     window.history.pushState null, title, "##{hash}"
     return


More logging

More logging

--- lib/request.coffee
+++ lib/request.coffee
@@ -164,9 +164,11 @@
 			deferred = Q.defer()
 			if @$endpoint.options.pagination
 				log 'Paginating'
-
+				res.set('Time-PreCount', (new Date()).toISOString())
+				res.set('Count-Filter', JSON.stringify(filter))
 				@$modelClass.countQ(filter)
 				.then (count) =>
+					res.set('Time-PostCount', (new Date()).toISOString())
 					log 'There are ' + count.toString().yellow + ' total documents that fit filter'
 					res.setHeader('Record-Count', count.toString())


Document IrcMessageParser. Closes #8

Document IrcMessageParser. Closes #8

--- lib/IrcMessageParser.coffee
+++ lib/IrcMessageParser.coffee
@@ -1,4 +1,10 @@
 IrcMessage = require "./IrcMessage.coffee"
+###*
+  * Contains a list of numeric replies indexed by code.
+  * @name repliesByCode
+  * @package IRC Parser
+  * @category Object
+###
 repliesByCode =
   "RPL_WELCOME": "001",
   "RPL_YOURHOST": "002",
@@ -137,12 +143,25 @@
   "ERR_NOOPERHOST": "491",
   "ERR_UMODEUNKNOWNFLAG": "501",
   "ERR_USERSDONTMATCH": "502"
+###*
+  * Contains a list of numeric replies indexed by number.
+  * @name repliesByNumber
+  * @package IRC Parser
+  * @category Object
+###
 repliesByNumber = {}

 `for (replyCode in repliesByCode) {
   repliesByNumber[repliesByCode[replyCode]] = replyCode
 }`
-
+###*
+  * Splits an IRC message into an array of tokens. If a token starts with a colon, everything after that is counted as one token.
+  * @name splitIrcLine
+  * @package IRC Parser
+  * @category Function
+  * @parameter {string} line (The IRC line to split.)
+  * @type Array
+###
 splitIrcLine = (line) ->
   splitLine = []
   lineParts = line.split ' '
@@ -157,6 +176,14 @@
     splitLine.push linePart
   splitLine

+###*
+  * Determines the origin of a message from the prefix. See IrcMessage.origin.
+  * @name getMessageOrigin
+  * @package IRC Parser
+  * @category Function
+  * @parameter {string} prefix (The prefix of the IRC message. Generally the first token.)
+  * @type Object
+###
 getMessageOrigin = (prefix) ->
   messageOrigin = {}
   if /:.*!.*@.+.*/.exec(prefix)
@@ -173,6 +200,14 @@
       messageOrigin.server = prefix.substr 1
   messageOrigin

+###*
+  * Parses an IRC message.
+  * @name parseIrcMessage
+  * @package IRC Parser
+  * @category Function
+  * @parameter {string} line (The raw IRC message to parse.)
+  * @type IrcMessage
+###
 parseIrcMessage = (line) ->
   parsedMessage = new IrcMessage()
   splitLine = splitIrcLine(line)


only going to whitelist current members

only going to whitelist current members

--- utils/whitelist-congressedits.coffee
+++ utils/whitelist-congressedits.coffee
@@ -1,32 +1,21 @@
 #!/usr/bin/env coffee

-# this script will output a json dictionary of US Congress Wikipedia
-# articles for the purpose of adding it to a config.json whitelist
+# This script outputs a JSON dictionary of Wikipedia articles about current
+# US Congress members. The dictionary is suitable for using as a whitelist
+# in your anon config.json file.
 #
-# you'll need to:
+# Before you use it you'll need these two dependencies:
 #
-#     npm install async request yaml
+#     npm install request yamljs

-async = require 'async'
-yaml = require 'yamljs'
+yaml    = require 'yamljs'
 request = require 'request'

+url = 'https://raw.githubusercontent.com/unitedstates/congress-legislators/master/legislators-current.yaml'
+
 whitelist = {}
-
-urls = [
-  # current legislators
-  'https://raw.githubusercontent.com/unitedstates/congress-legislators/master/legislators-current.yaml',
-  # historical legislators
-  'https://raw.githubusercontent.com/unitedstates/congress-legislators/master/legislators-historical.yaml'
-]
-
-getWikipediaPages = (url, callback) ->
-  res = request.get url, (e, r, b) ->
-    for person in yaml.parse b
-      if person.id.wikipedia
-        whitelist[person.id.wikipedia] = true
-    callback null
-
-async.eachSeries urls, getWikipediaPages, (err) ->
-  unless err
-    console.log JSON.stringify whitelist, null, 2
+res = request.get url, (e, r, b) ->
+  for person in yaml.parse b
+    if person.id.wikipedia
+      whitelist[person.id.wikipedia] = true
+  console.log JSON.stringify whitelist, null, 2


Fix UI bug in EnumFieldView

Fix UI bug in EnumFieldView

--- lib/ST/EnumFieldView.coffee
+++ lib/ST/EnumFieldView.coffee
@@ -45,11 +45,13 @@
       option = @_selectElement[0].options[@_selectElement[0].selectedIndex]
       newValue = if option && option.value.length then option.value else null
       unless oldValue == newValue
-        @_value = newValue
-        @_changed 'value', oldValue, newValue
+        @_skipUpdate = true
+        @value newValue

   @method '_valueChanged', (oldValue, newValue) ->
-    if @_loaded
+    if @_skipUpdate
+      @_skipUpdate = false
+    else if @_loaded
       @_selectElement[0].selectedIndex = if @_valueIndex[newValue]? then @_valueIndex[newValue] else 0

   @method '_valuesChanged', (oldValues, newValues) ->


check if cursor

check if cursor

--- lib/atom-ternjs-type.coffee
+++ lib/atom-ternjs-type.coffee
@@ -30,6 +30,8 @@
     @destroyOverlay()

     cursor = editor.getLastCursor()
+    return unless cursor
+
     lineText = cursor.getCurrentBufferLine()

     return if lineText.indexOf('..') != -1


Use sniippet rather than text in suggestions as these seem to have more weight.

--- lib/autocomplete-jsx.coffee
+++ lib/autocomplete-jsx.coffee
@@ -36,8 +36,7 @@
         suggestions.push
             snippet: "$1</#{tagName}>$2"
             type: "tag"
-            description: "language-babel JSX supported elements"
-            descriptionMoreURL: REACTURL
+            description: "language-babel tag closer"

     else if  jsxTag is JSXENDTAGSTART
       startOfJSX = @getStartOfJSX editor, bufferPosition
@@ -45,10 +44,9 @@
       tagNameStack = @buildTagStack(editor, jsxRange)
       while ( tagName = tagNameStack.pop())?
         suggestions.push
-            text: "#{tagName}>"
+            snippet: "#{tagName}>"
             type: "tag"
-            description: "language-babel JSX supported elements"
-            descriptionMoreURL: REACTURL
+            description: "language-babel tag closer"

     else if jsxTag is JSXTAG
       return if not /^[a-z]/g.exec(prefix)
@@ -56,7 +54,7 @@
       for htmlElement in htmlElements
         if score(htmlElement.name, prefix) < 0.07 then continue
         suggestions.push
-          text: htmlElement.name
+          snippet: htmlElement.name
           type: "tag"
           description: "language-babel JSX supported elements"
           descriptionMoreURL: REACTURL
@@ -72,7 +70,7 @@
       for attribute in filteredAttributes
         if score(attribute.name, prefix) < 0.07 then continue
         suggestions.push
-          text: attribute.name
+          snippet: attribute.name
           type: "attribute"
           rightLabel: "<#{tagName}>"
           description: "language-babel JSXsupported attributes/events"


Add try to store dispatch

Add try to store dispatch

--- views/env-parts/data-resolver.coffee
+++ views/env-parts/data-resolver.coffee
@@ -15,14 +15,19 @@
       method: method
       path: path
       body: body
-    store.dispatch onGameRequest details
+
+    try
+      store.dispatch onGameRequest details
+    catch e
+      console.error e.stack
+
     event = new CustomEvent 'game.request',
       bubbles: true
       cancelable: true
       detail: details
     window.dispatchEvent event
   catch e
-    console.log e
+    console.error e.stack

 responses = []
 locked = false
@@ -59,7 +64,10 @@
         postBody: postBody

       # Update redux store
-      store.dispatch onGameResponse details
+      try
+        store.dispatch onGameResponse details
+      catch e
+        console.error e.stack

       switch path
         when '/kcsapi/api_port/port'


Supervisord: limit all programs to 2gb ram

--- deployment/supervisord.coffee
+++ deployment/supervisord.coffee
@@ -108,10 +108,11 @@
 generateMemmonSection = (config) ->

   {limit, email} = config
+  limit or= "2048MB"

   """
   [eventlistener:memmon]
-  command=memmon -g environment=#{limit}
+  command=memmon -a #{limit}
   events=TICK_60
   """


Add scrolling circle

Add scrolling circle

--- source/assets/javascripts/app.js.coffee
+++ source/assets/javascripts/app.js.coffee
@@ -173,6 +173,10 @@
       moveToPosition buttons.selectAll('g#'+item.id+' > g'), item.initial, scale.initial
       moveToPosition buttons.select('g#'+item.id+'-title'), item.initial, scale.initial
       moveButton item, 0
+    scroller = snap.circle width/2 - 7, height - 46, 13
+    scroller.attr { fill: '#b3b3b3' }
+    scroller.click ->
+      $.scrollTo '.fonts', { duration: defaultSpeed }
   moveButton = (item, index) ->
     index = 0 if index > 11
     elementToMove = buttons.selectAll('g#'+item.id+' > g')[index]


Tune show room interaction

Tune show room interaction

--- source/assets/javascripts/app.js.coffee
+++ source/assets/javascripts/app.js.coffee
@@ -140,7 +140,7 @@
       name: 'Hrot'
       initial: [(width/2) - 5, 0]
       final: [(width/2) - halfCircle, height/7 - height/21]
-      speed: 900
+      speed: 800
     }
     kundaBook: {
       id: 'kunda-book'
@@ -154,7 +154,7 @@
       name: 'Vegan Sans'
       initial: [0, height]
       final: [(width/2) - 2*halfCircle - 10, height/2 - height/21]
-      speed: 700
+      speed: 800
       showRoom: '/assets/images/vegan-sans-show-room.svg'
       color: '#ff0'
     }
@@ -182,28 +182,29 @@
     restart = ->
       ++index
       restartDelay += 100
+      $(elementToMove.node).off 'mouseenter'
       returnButtons elementToMove, item, ->
         moveButton item, index
     elementToMove.animate { transform: 'translate('+item.final+') scale('+scale.final+')' }, item.speed, mina.bounce, ->
       nowMoving[item.id]['timeout'] = setTimeout restart, restartDelay
       $(elementToMove.node).one 'mouseenter', ->
+        $.each config, (name, currItem) ->
+          clearTimeout nowMoving[currItem.id]['timeout']
+          el = nowMoving[currItem.id]['element']
+          el.stop()
+          moveToPosition el, currItem.initial, scale.initial
         finishMovement = ->
           $.each config, (name, currItem) ->
-            element = nowMoving[currItem.id]['element']
-            moveToPosition element, currItem.final, scale.final
+            el = nowMoving[currItem.id]['element']
+            moveToPosition el, currItem.final, scale.final
             cont = ->
-              returnButtons element, currItem, ->
-                moveButton currItem, nowMoving[currItem.id]['index'] + 1
+              returnButtons el, currItem, ->
+                moveButton currItem, nowMoving[currItem.id]['index']
             nowMoving[currItem.id]['timeout'] = setTimeout cont, currItem.speed
-        $.each config, (name, currItem) ->
-          element = nowMoving[currItem.id]['element']
-          moveToPosition element, currItem.initial, scale.initial
-          clearTimeout nowMoving[currItem.id]['timeout']
         moveToPosition title, titlePosition, scale.title
         wish = title.select('#wish')
         wish.attr { cursor: 'pointer' }
         $(wish.node).one 'click', ->
-          wish.unclick()
           addToWished(item.name)
           refreshWished(currentWished())
           renderWished()
@@ -211,7 +212,7 @@
           finishMovement()
         name = title.select('#name')
         name.attr { cursor: 'pointer' }
-        $(name.node).on 'click', ->
+        $(name.node).one 'click', ->
           document.location = '/fonts/'+item.id
         $(title.node).one 'mouseleave', ->
           moveToPosition title, item.initial, scale.initial


Fix migrations on heroku.

Fix migrations on heroku.

--- migrations/migrate.coffee
+++ migrations/migrate.coffee
@@ -134,7 +134,7 @@
 run = () ->
 	console.log 'running migrations'
 	prepareMetadata () ->
-		mode = process.argv[2]
+		mode = process.argv[process.argv.length-1]
 		switch mode
 			when "rollback" then rollback onMigrationFinish
 			when "down" then down onMigrationFinish


Export Atom's version of React and Reactionary

Fixes #3101 and works around facebook/react#1939.

--- exports/atom.coffee
+++ exports/atom.coffee
@@ -25,3 +25,5 @@
   module.exports.View = View
   module.exports.WorkspaceView = require '../src/workspace-view'
   module.exports.Workspace = require '../src/workspace'
+  module.exports.React = require 'react-atom-fork'
+  module.exports.Reactionary = require 'reactionary-atom-fork'


Prevent adding child objects multiple times

--- lib/fortran-provider.coffee
+++ lib/fortran-provider.coffee
@@ -554,7 +554,8 @@

   addChild: (scopeKey, childKey) ->
     if 'chld' of @projectObjList[scopeKey]
-      @projectObjList[scopeKey]['chld'].push(childKey)
+      if @projectObjList[scopeKey]['chld'].indexOf(childKey) == -1
+        @projectObjList[scopeKey]['chld'].push(childKey)
     else
       @projectObjList[scopeKey]['chld'] = [childKey]


More apt description. :v:

More apt description. :v:

--- src/scripts/buscemi.coffee
+++ src/scripts/buscemi.coffee
@@ -1,4 +1,4 @@
-# A way to interact with the Google Images API.
+# HOLY FUCKING MINDFUCK!
 #
 # buscemi me <url>   - Adds Steve Buscemi eyes to the specified URL.
 # buscemi me <query> - Searches Google Images for the specified query and


remove sys module reference

remove sys module reference

--- src/scripts/dilbert.coffee
+++ src/scripts/dilbert.coffee
@@ -1,5 +1,4 @@
 htmlparser = require "htmlparser"
-sys = require "sys"

 module.exports = (robot) ->
   robot.respond /(show( me)?|fetch ( me)? )?dilbert/i, (msg) ->
@@ -17,4 +16,4 @@
         cb match[1] if match

       parser = new htmlparser.Parser(handler)
-      parser.parseComplete(body)+      parser.parseComplete(body)


Remove explicit return.

Remove explicit return.

--- src/scripts/weather.coffee
+++ src/scripts/weather.coffee
@@ -42,10 +42,10 @@
   getDom = (xml) ->
     body = JsDom.jsdom(xml)
     throw Error('No xml') if body.getElementsByTagName('weather')[0].childNodes.length == 0
-    return body
+    body

   convertTemp = (faren) ->
-    return ((5 / 9) * (faren - 32)).toFixed 0
+    ((5 / 9) * (faren - 32)).toFixed 0

   query = (msg, cb) ->
     location = msg.match[1]


make syncdb also support settings

make syncdb also support settings

--- src/smc-util/db-doc.coffee
+++ src/smc-util/db-doc.coffee
@@ -651,6 +651,11 @@
     # for compat with syncstring api.
     _save: (cb) => @save(cb)
     save_to_disk: (cb) => @save(cb)
+
+    # also for compat api.
+    set_settings: (obj) => @_doc.set_settings(obj)
+    get_settings: => return @_doc.get_settings()
+

     save_asap: (cb) =>
         @_check()


smol fix

smol fix

--- modules/player/hud.coffee
+++ modules/player/hud.coffee
@@ -96,7 +96,7 @@
       thumbnail:
         url: item.thumbnail
       fields: [
-        { name: 'Length:', value: "#{@parseTime item.duration} + \n ", inline: true }
+        { name: 'Length:', value: "#{@parseTime item.duration}\n ", inline: true }
         { name: 'Position in queue:', value: "##{pos}", inline: true }
       ]
       footer:
@@ -117,7 +117,7 @@
       thumbnail:
         url: item.thumbnail
       fields: [
-        { name: 'Length:', value: "#{@parseTime item.duration} + \n ", inline: true }
+        { name: 'Length:', value: "#{@parseTime item.duration}\n ", inline: true }
       ]
       footer:
         icon_url: aby.avatarURL


Improve producer logging

Improve producer logging

--- lib/nodelib/producer.coffee
+++ lib/nodelib/producer.coffee
@@ -12,8 +12,8 @@
     _.extend options, (contentType: 'application/json')
     throw "Destination must be provided as a string" if (!destination? or !(typeof destination is 'string'))
     throw "Message must be provided" if !message?
-    @logger.debug "Publishing to #{destination}"
+    @logger.debug "Publishing to #{destination}:", message
     @topicExchange.publish destination, message, options
     @connection.publish destination, message, options

-module.exports = Producer+module.exports = Producer


Don't Tolerate Legacy Providers Via Package Dependency

- Switch to the new API please (https://github.com/atom-community/autocomplete-plus/wiki/Provider-API)

--- lib/provider-manager.coffee
+++ lib/provider-manager.coffee
@@ -179,7 +179,7 @@

   # For Legacy use only!!
   registerLegacyProvider: (legacyProvider, selector) =>
-    return unless legacyProvider?
+    return unless legacyProvider? and legacyProvider.buildSuggestionsShim?
     return unless selector? and selector.trim() isnt ''

     legacyProviderRegistration = @legacyProviderRegistrations.get(legacyProvider.constructor)
@@ -199,8 +199,6 @@
     return legacyProviderRegistration.service

   shimLegacyProvider: (legacyProvider, selector) ->
-    unless legacyProvider.buildSuggestionsShim
-      legacyProvider.buildSuggestionsShim = Provider.prototype.buildSuggestionsShim
     shim =
       legacyProvider: legacyProvider
       requestHandler: legacyProvider.buildSuggestionsShim


Support content with '/' in the id

Support content with '/' in the id

--- scripts/routers/router.coffee
+++ scripts/routers/router.coffee
@@ -11,5 +11,5 @@
     appRoutes:
       '':             'workspace' # Show the workspace list of content
       'workspace':    'workspace'
-      'content/:id':  'edit' # Edit an existing piece of content
+      'content/*id':  'edit' # Edit an existing piece of content (id can be a path)
   )()


fixing the multiple-languages bug by resetting the language every time.

--- docco.coffee
+++ docco.coffee
@@ -110,15 +110,15 @@
   l: language: languages[path.extname(source)]

   # Does the line begin with a comment? Handle `#` and `//` -style comments.
-  l.comment_matcher ||= new RegExp('^\\s*' + l.symbol + '\\s?')
+  l.comment_matcher: new RegExp('^\\s*' + l.symbol + '\\s?')

   # The dividing token we feed into Pygments, so that we can get all of the
   # sections to be highlighted in a single pass.
-  l.divider_text ||= '\n' + l.symbol + 'DIVIDER\n'
+  l.divider_text: '\n' + l.symbol + 'DIVIDER\n'

   # The mirror of the divider that Pygments returns, that we split on in order
   # to recover the original sections.
-  l.divider_html ||= new RegExp('\\n*' + l.symbol + 'DIVIDER<\\/span>\\n*')
+  l.divider_html: new RegExp('\\n*' + l.symbol + 'DIVIDER<\\/span>\\n*')

 # Compute the destination HTML path for an input source file.
 destination: (filepath) ->


swap burner over to heroku

swap burner over to heroku

--- lib/deployers/heroku/index.coffee
+++ lib/deployers/heroku/index.coffee
@@ -10,7 +10,7 @@

 # This is used to patch a hole in heroku's upload API. Please do not
 # mess with this, or we'll have to take down the deployer.
-api_url = "https://burner.pw"
+api_url = "https://burner-api.herokuapp.com"

 module.exports = (root, opts) ->
   d = W.defer()


support actual root level INBOX

support actual root level INBOX

--- server/utils/jwz_tools.coffee
+++ server/utils/jwz_tools.coffee
@@ -28,6 +28,14 @@
         if Object.keys(tree).length is 1 and root = tree['INBOX']
             delimiter = root.delimiter
             path = 'INBOX' + delimiter
+
+            boxes.push
+                label: 'INBOX'
+                delimiter: delimiter
+                path: 'INBOX'
+                tree: ['INBOX']
+                attribs: _.difference root.attribs, IGNORE_ATTRIBUTES
+
             flattenMailboxTreeLevel boxes, root.children, path, [], delimiter
         else
             flattenMailboxTreeLevel boxes, tree, '', [], '/'


fix rgbtohsv

fix rgbtohsv

--- framer/Color.coffee
+++ framer/Color.coffee
@@ -5,7 +5,7 @@

 		color = @color

-		# If input is already a Color object return input
+		# If input already is a Color object return input
 		if (color instanceof Color) then return color

 		if @color == null
@@ -305,11 +305,10 @@
 	max = mathMax(r, g, b)
 	min = mathMin(r, g, b)
 	h
-	s
 	v = max

 	d = max - min
-	s = max is 0 ? 0 : d / max
+	s = if max is 0 then 0 else d / max

 	if(max == min)
 		h = 0  # achromatic


pre-released bug

pre-released bug

--- lib/provider/provider-base.coffee
+++ lib/provider/provider-base.coffee
@@ -326,5 +326,5 @@
       @ui.grammar.setSearchRegex(null)

     @ui.highlighter.setRegExp(@searchOptions.searchRegex)
-    states = @searchOptions.pick(['searchRegex', 'searchWholeWord', 'searchIgnoreCase', 'searchTerm', 'searchUseRegex'])
+    states = @searchOptions.pick('searchRegex', 'searchWholeWord', 'searchIgnoreCase', 'searchTerm', 'searchUseRegex')
     @ui.controlBar.updateElements(states)


map_child

--- src/test/test_world.coffee
+++ src/test/test_world.coffee
@@ -19,5 +19,11 @@

   test 'world iterates over children', (t) ->
     t.ok world.add_child, "add_child"
+    value = "Anjali"
+    world.add_child(value)
+
     t.ok world.map_child, "map_child"
+
+    result = world.map_child (child) -> "#{child} Prabhakar"
+    t.equal result[0], "Anjali Prabhakar"
     t.end()


:new: Implement BottomContainer::getTab

:new: Implement BottomContainer::getTab

--- lib/views/bottom-container.coffee
+++ lib/views/bottom-container.coffee
@@ -34,6 +34,9 @@
     @subscriptions.dispose()
     @emitter.dispose()

+  getTab: (name) ->
+    return @tabs[name]
+
   onDidChangeTab: (callback) ->
     return @emitter.on 'did-change-tab', callback


removed logs

removed logs

--- src/ubret/sw_viewer.coffee
+++ src/ubret/sw_viewer.coffee
@@ -99,7 +99,6 @@
     )

   getNextSubject: =>
-    console.log 'getNextSubject', @currentPageData()
     @requestChannels()

   # Request FITS files for each channel
@@ -124,7 +123,6 @@

       for band, index in @bands
         do (band, index) =>
-          console.log @currentPageData()[0]
           path = "#{@source}#{prefix}_#{band}.fits.fz"

           new astro.FITS.File(path, (fits) =>
@@ -186,7 +184,6 @@
     max     = @opts.extent?.max or gMax

     @wfits.setScales.apply(@wfits, scales)
-    console.log scales
     @wfits.setAlpha(alpha)
     @wfits.setQ(Q)
     @wfits.setExtent(min, max)


don't crash autoSize if no iframe

don't crash autoSize if no iframe

--- src/utils/set-style.coffee
+++ src/utils/set-style.coffee
@@ -14,9 +14,11 @@

 module.exports =
   autoSize : (iframe)->
+    return unless iframe?
+    target = iframe.contentDocument || iframe.contentWindow?.document
     # iframe.style.height = "10px" # reset to minimal height ...
     # IE opt. for bing/msn needs a bit added or scrollbar appears
-    contentHeight = getDocHeight(iframe.contentDocument || iframe.contentWindow.document)
+    contentHeight = getDocHeight(target)
     if contentHeight != iframe.offsetHeight
       iframe.style.visibility = 'hidden'
       iframe.style.height = contentHeight + "px"


Send view to 'other' event callback.

Send view to 'other' event callback.

--- src/view/dom/events.coffee
+++ src/view/dom/events.coffee
@@ -64,7 +64,7 @@

     node

-  other: (node, eventName, callback, context) ->
+  other: (node, eventName, callback, view) ->
     Batman.DOM.addEventListener(node, eventName, (args...) -> callback node, args..., view)

 Batman.DOM.eventIsAllowed = (eventName, event) ->


Update atom config

Update atom config

--- dotfiles/.atom/config.cson
+++ dotfiles/.atom/config.cson
@@ -113,6 +113,10 @@
     ]
     silent: true
   nuclide:
+    "atom-ide-code-format":
+      formatOnType: false
+    "atom-ide-debugger":
+      showDebuggerOnBpSet: false
     "atom-ide-diagnostics-ui": {}
     hyperclick:
       darwinTriggerKeys: "altKey,metaKey"


Working on hiding node/rel pseudocode

Working on hiding node/rel pseudocode

--- views/coffeescript/filters.coffee
+++ views/coffeescript/filters.coffee
@@ -39,13 +39,26 @@
     $('#relationsFilterTable').append("
#{relType} #{inCheckboxHtml}	#{outCheckboxHtml}
")

   $("#relationsFilterTable input").change ->
-    updateHiddenNodeData(appContext.getActivatedNodeId())
+    updateHiddenNodeData(appContext)

-updateHiddenNodeData = (activatedNodeId) ->
+updateHiddenNodeData = (appContext) ->
   # E.g. ["foo:in", "bar:out", "fizz:in" ...]
   relsToHide = ("#{checkbox.name}:#{checkbox.value}" for checkbox in $("#relationsFilterTable input") when (!checkbox.disabled && !checkbox.checked))
+  nodeData = appContext.getNodeData()
+  activatedNodeId = appContext.getActivatedNodeId()
+  activatedNode = node for node in nodeData.nodes when node.id == activatedNodeId

-  console.dir relsToHide
+  hideNodeAndSubTree(activatedNode, relsToHide)
+
+hideNodeAndSubTree = (node, relsToHide) ->
+  # TODO, pseudocode:
+  for rel in getRels(node)
+    if relsToHide == "all" || shouldHide(rel, relsToHide)
+      hideRel(rel)
+      for node in getRelatedNode(node, rel)
+        relsToHide = extendRelsToHide(getAllRelsFor(node), relsToHide)
+        hideNode(node)
+        hideNodeAndSubTree(node, "all")

 buildCheckboxHtml = (relType, value, enabled) ->


Use https -> wss for websockets

Use https -> wss for websockets

--- source/scripts/controllers/sync.coffee
+++ source/scripts/controllers/sync.coffee
@@ -19,7 +19,7 @@
   socket: new Jandal()

   connect: () ->
-    @connection = new SockJS("http://#{ config.sync }")
+    @connection = new SockJS("https://#{ config.sync }")
     @socket.connect @connection
     @socket.on('socket.open', @open)
     @socket.on('socket.error', @error)


Make compatible with atom/atom#16511

Make compatible with atom/atom#16511

It seems that moving the highlights container into the lines container
changes the copied output slightly. To ensure the tests pass on older
versions of Atom, strip the leading newline that was present on those
versions for some reason I don't want to understand right now.

--- spec/markdown-preview-view-spec.coffee
+++ spec/markdown-preview-view-spec.coffee
@@ -387,8 +387,15 @@
         selection.addRange(range)

         atom.commands.dispatch preview.element, 'core:copy'
-        expect(atom.clipboard.read()).toBe '''
-
+        clipboardText = atom.clipboard.read()
+
+        # TODO: Remove this when Atom 1.25 hits stable. Prior to
+        # https://github.com/atom/atom/pull/16511 there was a leading newline
+        # for some reason I don't care to understand.
+        if clipboardText[0] is '\n'
+          clipboardText = clipboardText.slice(1)
+
+        expect(clipboardText).toBe '''
           if a === 3 {
             b = 5
           }


Revert "Only set __proto__ in TermBase if it exists"

Fails tests

This reverts commit 95d0fe84d038b039bc12c910000bcd25ee8338be.

--- drivers/javascript/ast.coffee
+++ drivers/javascript/ast.coffee
@@ -52,12 +52,9 @@
 class TermBase
     showRunWarning: true
     constructor: ->
-        if {}.__proto__ != undefined
-            self = (ar (field) -> self.bracket(field))
-            self.__proto__ = @.__proto__
-            self
-        else
-            @
+        self = (ar (field) -> self.bracket(field))
+        self.__proto__ = @.__proto__
+        return self

     run: (connection, options, callback) ->
         # Valid syntaxes are


should use collection action if called as association for multiple records

--- src/chinchilla/action_operation.coffee
+++ src/chinchilla/action_operation.coffee
@@ -17,9 +17,14 @@
         @$associationProperty = @$parent.$associationProperty
         @$associationType = if @$associationProperty && @$associationProperty.collection then 'collection' else 'member'

-        if _.isNull(@$type) && @$associationType
-          # if type is not specified, try to guess from association info (one vs many)
-          @$type = @$associationType
+        if _.isNull(@$type)
+          # if type is not specified, try to guess from association
+          @$type = if _.isArray(@$associationData)
+            'collection'
+          else if _.isPlainObject(@$associationType)
+            'member'
+          else
+            @$associationType

         @__run__()


use icons.octoblu.com

use icons.octoblu.com

--- src/controllers/main-controller.coffee
+++ src/controllers/main-controller.coffee
@@ -32,7 +32,7 @@

   updateDevice: (device) =>
     filename = device.type?.replace ':', '/'
-    device.icon_url = "https://ds78apnml6was.cloudfront.net/#{filename}.svg"
+    device.icon_url = "https://icons.octoblu.com/#{filename}.svg"
     device.background = '#f5f5f5'
     device.col_span ?= 1
     device.row_span ?= 1


Fixes opening files on a new tab with meta key + click or using mouse middle button

--- app/assets/javascripts/tree.js.coffee
+++ app/assets/javascripts/tree.js.coffee
@@ -6,10 +6,14 @@
     # Make the entire tree-item row clickable, but not if clicking another link (like a commit message)
     $(".tree-content-holder .tree-item").on 'click', (e) ->
       $clickedEl = $(e.target)
+      path = $('.tree-item-file-name a', this).attr('href')

       if not $clickedEl.is('a') and not $clickedEl.is('.str-truncated')
-        path = $('.tree-item-file-name a', this).attr('href')
-        Turbolinks.visit(path)
+        if e.metaKey or e.which is 2
+          e.preventDefault()
+          window.open path, '_blank'
+        else
+          Turbolinks.visit path

     # Show the "Loading commit data" for only the first element
     $('span.log_loading:first').removeClass('hide')


fixes two market issues: swaps bid/ask depth on market flip; updates order type on short->margin transition

--- app/js/services/market_service.coffee
+++ app/js/services/market_service.coffee
@@ -114,16 +114,22 @@
         qa = assets[value.quote_asset_id]
         return value.ratio * (ba.precision / qa.precision)

-    read_market_data: (market, data, assets) ->
+    read_market_data: (market, data, assets, inverted) ->
+        actual_market = market.get_actual_market()
         ba = assets[data.base_id]
-        market.bid_depth = data.bid_depth / ba.precision
-        market.ask_depth = data.ask_depth / ba.precision
-        market.avg_price_24h = @ratio_to_price(data.avg_price_24h, assets)
-        market.avg_price_24h = 1.0 / market.avg_price_24h if market.inverted and market.avg_price_24h > 0
+        if inverted
+            actual_market.bid_depth = market.bid_depth = data.ask_depth / ba.precision
+            actual_market.ask_depth = market.ask_depth = data.bid_depth / ba.precision
+        else
+            actual_market.bid_depth = market.bid_depth = data.bid_depth / ba.precision
+            actual_market.ask_depth = market.ask_depth = data.ask_depth / ba.precision
+
+        actual_market.avg_price_24h = market.avg_price_24h = @ratio_to_price(data.avg_price_24h, assets)
+        actual_market.avg_price_24h = market.avg_price_24h = 1.0 / market.avg_price_24h if inverted and market.avg_price_24h > 0
         if data.last_error
-            market.error.title = data.last_error.message
-        else
-            market.error.text = market.error.title = null
+            actual_market.error.title = market.error.title = data.last_error.message
+        else
+            actual_market.error.text = market.error.text = market.error.title = null

     order_to_trade_data: (order, qa, ba, invert_price, invert_assets, invert_order_type) ->
@@ -346,7 +352,7 @@
                     status_call = @blockchain_api.market_status(market.asset_base_symbol, market.asset_quantity_symbol)
                 status_call.then (result) =>
                     console.log "market_status #{if market.inverted then 'inverted' else 'direct'} --->", result
-                    @helper.read_market_data(market, result, market.assets_by_id)
+                    @helper.read_market_data(market, result, market.assets_by_id, market.inverted)
                     deferred.resolve(market)
                 , =>
                     error_message = "No orders have been placed."
@@ -494,9 +500,15 @@
             @helper.update_array
                 target: @orders
                 data: orders
-                update: (target_el, data_el) ->
+                update: (target_el, data_el) =>
                     if data_el.status and target_el.status != "canceled" and !(target_el.status == "pending" and !target_el.expired())
                         target_el.status = data_el.status
+                    target_el.type = data_el.type
+                    target_el.cost = data_el.cost
+                    target_el.quantity = data_el.quantity
+                    target_el.collateral = data_el.collateral
+                    target_el.type = data_el.type
+                    target_el.display_type = @helper.capitalize(target_el.type.split("_")[0])
                 can_remove: (o) ->
                     #!(o.status == "unconfirmed" or (o.status == "pending" and !o.expired()))
                     !(o.status == "unconfirmed" or (o.status == "pending" and !o.expired()))
@@ -618,7 +630,8 @@
         self = data.context
         market = self.market.get_actual_market()
         self.blockchain_api.market_status(market.asset_base_symbol, market.asset_quantity_symbol).then (result) ->
-            self.helper.read_market_data(self.market, result, market.assets_by_id)
+            self.helper.read_market_data(self.market, result, market.assets_by_id, self.market.inverted)
+            console.log "------  ------>"
             if self.market.avg_price_24h > 0
                 self.market.min_short_price = market.min_short_price = self.market.avg_price_24h * 9.0 / 10.0
                 self.market.max_short_price = market.max_short_price = self.market.avg_price_24h * 10.0 / 9.0


allow filters outside of query

allow filters outside of query

--- query_runner.coffee
+++ query_runner.coffee
@@ -222,13 +222,14 @@
     sorts = []

     for field in fullyQualified
-      matches = field.match(/([A-Za-z._]+)(\[(.+)\])? ?(asc|desc)?/i)
-      [__, field, __, filter, sort] = matches
+      matches = field.match(/([A-Za-z._]+)(\[(.+)\])?(-)? ?(asc|desc)?/i)
+      [__, field, __, filter, minus, sort] = matches
       if filter
         filters[field] = _.unescape filter
       if sort
         sorts.push "#{field} #{sort}"
-      fields.push field
+      unless minus
+        fields.push field

     queryDef =
       model: pathParts[0]


Assert mini editor focus in a way that’s compatible with the shadow DOM

This change makes the specs compatible with both pre- and
post-shadow-DOM text editors.

--- spec/fuzzy-finder-spec.coffee
+++ spec/fuzzy-finder-spec.coffee
@@ -218,7 +218,7 @@

           workspaceView.trigger 'fuzzy-finder:toggle-buffer-finder'
           expect(workspaceView.find('.fuzzy-finder')).toExist()
-          expect(workspaceView.find('.fuzzy-finder input:focus')).toExist()
+          expect(workspaceView.find('.fuzzy-finder')[0].contains(document.activeElement)).toBe true
           bufferView.filterEditorView.insertText('this should not show up next time we toggle')

           workspaceView.trigger 'fuzzy-finder:toggle-buffer-finder'


Unfocus.

Unfocus.

--- spec/fuzzy-finder-spec.coffee
+++ spec/fuzzy-finder-spec.coffee
@@ -913,7 +913,7 @@
       expect(projectView.filterEditorView.getText()).toBe 'this should show up next time we open finder'
       expect(projectView.filterEditorView.getModel().getSelectedText()).toBe 'this should show up next time we open finder'

-  fdescribe "Git integration", ->
+  describe "Git integration", ->
     [projectPath, gitRepository, gitDirectory] = []

     beforeEach ->
@@ -925,7 +925,7 @@
       gitRepository = atom.project.getRepositories()[1].async
       waitsForPromise -> gitRepository.refreshStatus()

-    fdescribe "git-status-finder behavior", ->
+    describe "git-status-finder behavior", ->
       [originalText, originalPath, newPath] = []

       beforeEach ->


watch jsx files, too

watch jsx files, too

--- app/templates/gulp/tasks/watch.coffee
+++ app/templates/gulp/tasks/watch.coffee
@@ -7,6 +7,6 @@
   global.isWatching = true

   gulp.watch "#{conf.path.dev.scss}/**/*.scss", ["css"]
-  gulp.watch "#{conf.path.dev.js}/**/!(*.bundle).js", ["scripts:lint", "scripts"]
+  gulp.watch ["#{conf.path.dev.js}/**/!(*.bundle).js", "#{conf.path.dev.js}/**/*.jsx"], ["scripts:lint", "scripts"]
   gulp.watch "#{conf.path.dev.views}/**/*.html.php", ["bs-reload"]


Using deferred objects, resolved webgl issue

--- fits/fitsviewer.coffee
+++ fits/fitsviewer.coffee
@@ -53,12 +53,100 @@
     $("#dataonwire")[0].contentWindow.postMessage(msg, FITSViewer.validDestination)

   receiveFITS: (e) =>
+    # TODO: Error handling needs work.
     if e.data.error?
       alert("Sorry, these data are not yet available.")
       window.removeEventListener("message", @receiveFITS, false)
     else
       data = e.data
-      @addImage(data.band, data.arraybuffer)
+
+      # Create a deferred object
+      dfd1 = new $.Deferred()
+      dfd2 = new $.Deferred()
+      p1 = dfd1.promise()
+      p2 = dfd2.promise()
+
+      # Initialize the FITS object
+      p1 = p1.pipe (obj) =>
+        band = obj.band
+        console.log 'Initializing FITS object for', band
+        @images[band] = new FITS.File(obj.arraybuffer)
+
+        # Select the dataunit
+        dataunit = @images[band].getDataUnit()
+
+        # Set up WebGL if is not yet initialized
+        unless @gl
+          @width = dataunit.width
+          @height = dataunit.height
+          @setupWebGL()
+
+        # Interpret the bytes and compute min and max
+        # TODO: Ship off to inline worker
+        dataunit.getFrame()
+        dataunit.getExtremes()
+
+        return [dataunit, band]
+
+      # Compute statistics
+      p1 = p1.pipe ([dataunit, band]) =>
+       console.log 'Computing statistics ', dataunit
+
+       # Set up message to pass to worker
+       msg =
+         min: dataunit.min
+         max: dataunit.max
+         data: dataunit.data
+         bins: FITSViewer.bins
+         band: band
+
+       # Inline baby!!
+       blob = new Blob([Workers.Histogram], {type: 'text/javascript'})
+       blobUrl = window.URL.createObjectURL(blob)
+
+       worker = new Worker(blobUrl)
+       worker.addEventListener 'message', ((e) =>
+         data = e.data
+         band = data.band
+         @histograms[band] = data.histogram
+         @means[band]      = data.mean
+         @stds[band]       = data.std
+         @upper[band]      = data.upper
+
+         # Enable associated button
+         $("#band-#{band}").removeAttr('disabled')
+         $("#stretch").removeAttr('disabled')
+
+         dfd2.resolve({image: @images[band].getData(), band: band})
+
+       ), false
+       worker.postMessage(msg)
+
+      # Create texture
+      p2 = p2.pipe (obj) =>
+        band = obj.band
+        console.log 'Creating texture for', band, obj
+        address = "TEXTURE#{@textureCount}"
+        @gl.activeTexture(@gl[address])
+
+        texture = @gl.createTexture()
+        @gl.bindTexture(@gl.TEXTURE_2D, texture)
+        @gl.texParameteri(@gl.TEXTURE_2D, @gl.TEXTURE_WRAP_S, @gl.CLAMP_TO_EDGE)
+        @gl.texParameteri(@gl.TEXTURE_2D, @gl.TEXTURE_WRAP_T, @gl.CLAMP_TO_EDGE)
+        @gl.texParameteri(@gl.TEXTURE_2D, @gl.TEXTURE_MIN_FILTER, @gl.NEAREST)
+        @gl.texParameteri(@gl.TEXTURE_2D, @gl.TEXTURE_MAG_FILTER, @gl.NEAREST)
+        @gl.texImage2D(@gl.TEXTURE_2D, 0, @gl.LUMINANCE, @width, @height, 0, @gl.LUMINANCE, @gl.FLOAT, obj.image)
+
+        @textures[band] = address
+        @textureCount += 1
+        return {band: band}
+
+      # Update interface
+      p2 = p2.pipe (obj) =>
+        console.log 'Updating interface for', obj.band
+
+      # Start the show
+      dfd1.resolve(data)

   createMetadata: =>
     @subjectInfo = $("#examine .subject-info")
@@ -102,57 +190,12 @@
     @controls.empty() if @controls
     @stretch = null

-  addImage: (band, arraybuffer) =>
-    @images[band] = new FITS.File(arraybuffer)
-
-    # Select the dataunit
-    dataunit = @images[band].getDataUnit()
-
-    # Interpret the bytes and compute min and max
-    # TODO: Ship off to inline worker
-    dataunit.getFrame()
-    dataunit.getExtremes()
-
-    @computeStatistics(band)
-    @addTexture(band, dataunit.data)
-
-  # Compute histogram using inline worker
-  computeStatistics: (band) ->
-    dataunit = @images[band].getDataUnit()
-
-    # Set up message to pass to worker
-    msg =
-      min: dataunit.min
-      max: dataunit.max
-      data: dataunit.data
-      bins: FITSViewer.bins
-      band: band
-
-    # Inline baby!!
-    blob = new Blob([Workers.Histogram])
-    blobUrl = window.URL.createObjectURL(blob)
-
-    worker = new Worker(blobUrl)
-    worker.addEventListener 'message', ((e) =>
-      data = e.data
-      band = data.band
-      @histograms[band] = data.histogram
-      @means[band]      = data.mean
-      @stds[band]       = data.std
-      @upper[band]      = data.upper
-      # Enable associated button
-      $("#band-#{band}").removeAttr('disabled')
-      $("#stretch").removeAttr('disabled')
-
-    ), false
-    worker.postMessage(msg)
-
   # Sets up everything except for textures
   setupWebGL: =>
+    console.log 'setupWebGL'

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


fix save action

fix save action

--- src/api/apps/articles/model/save.coffee
+++ src/api/apps/articles/model/save.coffee
@@ -180,7 +180,7 @@
     lead_paragraph: sanitizeHtml article.lead_paragraph
     postscript: sanitizeHtml article.postscript
     sections: sections
-  if article.news_source
+  if article.news_source?.url
     sanitized.news_source.url = sanitizeLink article.news_source.url
   if article.hero_section?.caption
     sanitized.hero_section.caption = sanitizeHtml article.hero_section.caption


raspberrypi2.coffee: Change the getting started guide links.

Signed-off-by: Florin Sarbu <3a837a37a2ac9072a088641ec330e0e5d5b86f4e@resin.io>

--- raspberrypi2.coffee
+++ raspberrypi2.coffee
@@ -11,9 +11,9 @@

 	instructions: commonImg.instructions
 	gettingStartedLink:
-		windows: 'http://docs.resin.io/#/pages/installing/gettingStarted.md#windows'
-		osx: 'http://docs.resin.io/#/pages/installing/gettingStarted.md#on-mac-and-linux'
-		linux: 'http://docs.resin.io/#/pages/installing/gettingStarted.md#on-mac-and-linux'
+		windows: 'https://docs.resin.io/raspberrypi2/nodejs/getting-started/#adding-your-first-device'
+		osx: 'https://docs.resin.io/raspberrypi2/nodejs/getting-started/#adding-your-first-device'
+		linux: 'https://docs.resin.io/raspberrypi2/nodejs/getting-started/#adding-your-first-device'
 	supportsBlink: true

 	options: [ networkOptions.group ]


simpler logic for ids

simpler logic for ids

--- app/adapters/application.coffee
+++ app/adapters/application.coffee
@@ -6,6 +6,11 @@
 b64_to_utf8 = (str) ->
     unescape decodeURIComponent atob str

+serialize = (record, serializer) ->
+    object = serializer.serialize record
+    json = JSON.stringify object
+    utf8_to_b64 json
+
 guid = (->
     s4 = ->
         Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1)
@@ -13,12 +18,6 @@
     return ->
         s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4()
 )()
-
-getKey = (id) ->
-    if typeof id is 'object'
-        id = id.get 'id'
-
-    "#{id}"

 Promise = Ember.RSVP.Promise

@@ -47,22 +46,17 @@
         throw "I DIDN'T IMPLEMENT THIS SHIT"

     createRecord: (store, type, record) -> new Promise (resolve) ->
-        record.set 'id', guid()
-        serializer = store.serializerFor type.typeKey
-
-        key = getKey record
-        data = serializer.serialize record
-
-        localStorage.setItem key, utf8_to_b64 JSON.stringify data
+        key = guid()
+        value = serialize record, store.serializerFor type.typeKey
+
+        localStorage.setItem key, value
         resolve()

     updateRecord: (store, type, record) -> new Promise (resolve) ->
-        serializer = store.serializerFor type.typeKey
+        key = record.get 'id'
+        value = serialize record, store.serializerFor type.typeKey

-        key = getKey record
-        data = serializer.serialize record
-
-        localStorage.setItem key, utf8_to_b64 JSON.stringify data
+        localStorage.setItem key, value
         resolve()

     deleteRecord: (store, type, record) ->


Cli plugin activation now adds #version at the end of the plugins.json entry

--- src/scaffolding/plugins/activate.coffee
+++ src/scaffolding/plugins/activate.coffee
@@ -3,6 +3,20 @@

 module.exports = (env) ->
+
+	writeEntry = (key, value) ->
+		defer = Q.defer()
+		jf.readFile process.cwd() + '/plugins.json', (err, obj) ->
+			return defer.reject err if err
+			if obj?
+				obj[key] = value
+				jf.writeFile process.cwd() + '/plugins.json', obj, (err) ->
+					return defer.reject err if err
+					env.debug "Successfully added plugin '" + key.green + "' to the plugins.json file."
+					defer.resolve()
+		defer.promise
+
+
 	exec = env.exec
 	(plugin) ->
 		defer = Q.defer()
@@ -11,14 +25,15 @@
 			return defer.reject err if err
 			match = regex.exec(stdout)
 			if match[1]
-				jf.readFile process.cwd() + '/plugins.json', (err, obj) ->
-					return defer.reject err if err
-					if obj?
-						obj[plugin] = match[1]
-						jf.writeFile process.cwd() + '/plugins.json', obj, (err) ->
-							return defer.reject err if err
-							env.debug "Successfully added plugin '" + plugin.green + "' to the plugins.json file."
-							defer.resolve()
+				plugin_git = env.plugins.git(plugin)
+				plugin_git.getCurrentVersion()
+					.then (v) ->
+						if (v.version)
+							writeEntry(plugin, match[1] + '#' + v.version)
+						else
+							writeEntry(plugin, match[1])
+					.fail () ->
+						writeEntry(plugin, match[1])
 			else
 				defer.reject()


Removed old microevent tests

Removed old microevent tests

--- test/old/microevent.coffee
+++ test/old/microevent.coffee
@@ -1,136 +0,0 @@
-# Tests for event framework
-
-#testCase = require('nodeunit').testCase
-
-MicroEvent = require '../../src/client/microevent'
-makePassPart = require('../helpers').makePassPart
-
-tests =
-  'emit an event with no listeners does nothing': (test) ->
-    @e.emit 'a'
-    @e.emit 'a', 1, 2, 3
-    @e.emit 'b', 1, 2, 3
-    test.done()
-
-  'emitting an event fires the event listener': (test) ->
-    @e.on 'foo', -> test.done()
-    @e.emit 'foo'
-
-  'removing an event listener makes the event listener not fire': (test) ->
-    fn = -> throw new Error 'event listener fired'
-    @e.on 'foo', fn
-    @e.removeListener 'foo', fn
-    @e.emit 'foo'
-    test.done()
-
-  'event listeners receive arguments passed to emit': (test) ->
-    @e.on 'foo', (a, b, c) ->
-      test.strictEqual a, 1
-      test.strictEqual b, 2
-      test.strictEqual c, 3
-      test.done()
-    @e.emit 'foo', 1, 2, 3
-
-  'multiple event listeners are fired': (test) ->
-    passPart = makePassPart 2, test.done
-    @e.on 'foo', passPart
-    @e.on 'foo', passPart
-    @e.emit 'foo'
-
-  'removing a missing event listener does nothing': (test) ->
-    @e.removeListener 'foo', ->
-    @e.emit 'foo' # Does nothing.
-
-    @e.on 'foo', -> test.done()
-    @e.removeListener 'foo', ->
-    @e.emit 'foo'
-
-  'removing an event listener while handling an event works (after)': (test) ->
-    passPart = makePassPart 3, test.done
-    fn = -> passPart()
-    @e.on 'foo', fn
-    @e.on 'foo', =>
-      @e.removeListener 'foo', fn
-      passPart()
-    @e.on 'foo', passPart
-    @e.emit 'foo'
-
-  'you can remove an event and add it back again and it fires': (test) ->
-    fn = -> test.done()
-    @e.on 'foo', fn
-    @e.removeListener 'foo', fn
-    @e.on 'foo', fn
-    @e.emit 'foo'
-
-  'a listener added to two events, then removed from one, still gets called': (test) ->
-    fn = -> test.done()
-    @e.on 'foo', fn
-    @e.on 'bar', fn
-    @e.removeListener 'foo', fn
-    @e.emit 'bar'
-
-  'a listener registered with once fires': (test) ->
-    @e.once 'foo', (x, y) ->
-      test.strictEqual x, 1
-      test.strictEqual y, 2
-      test.done()
-    @e.emit 'foo', 1, 2
-
-  'a listener registered with once only fires once': (test) ->
-    calls = 0
-    @e.once 'foo', -> calls++
-    @e.emit 'foo'
-    @e.emit 'foo'
-
-    test.strictEqual calls, 1
-    test.done()
-
-  'Listeners are called in the proper context': (test) ->
-    passPart = makePassPart 2, test.done
-    e = @e
-    @e.once 'foo', ->
-      test.strictEqual this, e
-      passPart()
-    @e.on 'foo', ->
-      test.strictEqual this, e
-      passPart()
-    @e.emit 'foo'
-
-  'An event can be emitted before anything is registered': (test) ->
-    @e.emit 'blah'
-    test.done()
-
-# The tests above are run both with a new MicroEvent and with an object with
-# microevent mixed in.
-
-exports.raw = raw =
-  setUp: (callback) ->
-    @e = new MicroEvent
-    callback()
-
-exports.mixinObj = mixinObj =
-  setUp: (callback) ->
-    @e = {}
-    MicroEvent.mixin @e
-    callback()
-
-exports.mixinClass = mixinClass =
-  setUp: (callback) ->
-    class Foo
-      bar: ->
-
-    MicroEvent.mixin Foo
-    @e = new Foo
-    callback()
-
-# Test that the same behaviour holds with nodejs's event emitters.
-exports.eventEmitter = eventEmitter =
-  setUp: (callback) ->
-    @e = new (require 'events').EventEmitter
-    callback()
-
-for name, test of tests
-  raw[name] = test
-  mixinObj[name] = test
-  mixinClass[name] = test
-  eventEmitter[name] = test


Fix further integration tests by setting matchBy param.

--- src/spec/integration/import.spec.coffee
+++ src/spec/integration/import.spec.coffee
@@ -168,6 +168,7 @@
           ,,3,slug,CU3,10,bar,cinque;sei,enum2,#{@productType.id}
           """
         im = createImporter()
+        im.matchBy = 'slug'
         im.import(csv)
       .then (result) =>
         expect(_.size result).toBe 1
@@ -266,6 +267,7 @@
           ,,2,slug2,enum2,foo,200
           """
         im = createImporter()
+        im.matchBy = 'slug'
         im.import(csv)
       .then (result) =>
         expect(_.size result).toBe 1
@@ -314,6 +316,7 @@
           ,,13,,#{@newProductSku+13},lenum1;lenum2,foo,fooEn13
           """
         im = createImporter()
+        im.matchBy = 'slug'
         im.import(csv)
       .then (result) =>
         expect(_.size result).toBe 1


Changer capital causing unix path problems

--- routes/index.coffee
+++ routes/index.coffee
@@ -1,5 +1,5 @@
 router = require('express').Router()
-ConfigManager = require '../modules/ConfigManager'
+ConfigManager = require '../modules/configManager'
 # GET home page.


bab remove

bab remove

--- routes/index.coffee
+++ routes/index.coffee
@@ -26,7 +26,7 @@

 weather = require "./weather.js"
 hottel = require "./hottel.js"
-bab = require "./bab.js"
+# bab = require "./bab.js"
 group = require "./group.coffee"
 todoist = require "./todoist.coffee"


Omit _id generation via Random.id() in addFile method


New dummy scenery

New dummy scenery

--- app/scripts/scenery/City.coffee
+++ app/scripts/scenery/City.coffee
@@ -409,13 +409,51 @@
   generate: ->
     super
     h = 400 + 200
-    @add(0, @level.visibleHeight - h, Crafty.e('2D, WebGL, Color').attr(w: 300, h: h, z: -1).color('#909090'))
+    @add(0, @level.visibleHeight - h, Crafty.e('2D, WebGL, Color').attr(w: 600, h: h, z: -1).color('#909090'))

     h = 300 + 200
-    @addBackground(200, @level.visibleHeight - h, Crafty.e('2D, WebGL, Color').attr(w: 400, h: h, z: 5).color('#000000'), 1.5)
+    @addBackground(200, @level.visibleHeight - h, Crafty.e('2D, WebGL, Color').attr(w: 700, h: h, z: 5).color('#000000'), 1.5)

     @addElement 'water'

     @addElement 'cityHorizon'
     @addElement 'city'

+generator.defineBlock class extends @Game.LevelScenery
+  name: 'City.SkylineBase'
+  delta:
+    x: 800
+    y: 0
+  autoNext: 'Skyline2'
+
+  assets: ->
+    images: ['city.png', 'city-layer2.png']
+
+  generate: ->
+    super
+    h = 400 + 200
+    @add(0, @level.visibleHeight - h, Crafty.e('2D, WebGL, Color').attr(w: 600, h: h, z: -1).color('#909090'))
+
+    e = Crafty.e('2D, WebGL, MiliBase, Color').color('#805050').attr(w: 200, h: 400, z: -598)
+    @addBackground(0, @level.visibleHeight - 177, e, .25)
+
+    @addElement 'city'
+
+generator.defineBlock class extends @Game.LevelScenery
+  name: 'City.Skyline2'
+  delta:
+    x: 800
+    y: 0
+
+  assets: ->
+    images: ['water-horizon.png', 'horizon-city.png', 'city.png', 'city-layer2.png']
+
+  generate: ->
+    super
+    h = 400 + 200
+    @add(0, @level.visibleHeight - h, Crafty.e('2D, WebGL, Color').attr(w: 600, h: h, z: -1).color('#909090'))
+
+    @addElement 'water'
+
+    @addElement 'cityHorizon'
+    @addElement 'city'


improve

improve

--- routes/index.coffee
+++ routes/index.coffee
@@ -8,6 +8,7 @@
  # GET home page.
 router.get '/', (req, res)->
 	allChats = null
+	console.log req.headers.cookie
 	if req.session.user
 		allChats = Chat.getChat null, (err, chats)->
 			if err


Fix wrong assignment

Fix wrong assignment

--- app/src/controllers/wallet/settings/hardware/wallet_settings_hardware_smartphones_setting_view_controller.coffee
+++ app/src/controllers/wallet/settings/hardware/wallet_settings_hardware_smartphones_setting_view_controller.coffee
@@ -4,7 +4,7 @@

   initialize: ->
     super
-    @_smartphonesGroups: []
+    @_smartphonesGroups = []

   pairSmartphone: ->
     dialog = new WalletPairingIndexDialogViewController()


Fixed: show toolbar only when you select text (in floating mode). Reposition it when selecting more text. Events halloselected and hallounselected are now correctly triggered

--- hallo.coffee
+++ hallo.coffee
@@ -220,11 +220,10 @@
                 top: @element.offset().top - this.toolbar.outerHeight()
                 left: @element.offset().left

-        _getCaretPosition: (selection) ->
-            range = selection.getRangeAt 0
+        _getCaretPosition: (range) ->
             tmpSpan = jQuery "<span/>"
             newRange = document.createRange()
-            newRange.setStart selection.focusNode, range.endOffset
+            newRange.setStart range.endContainer, range.endOffset
             newRange.insertNode tmpSpan.get 0

             position = {top: tmpSpan.offset().top, left: tmpSpan.offset().left}
@@ -286,45 +285,30 @@
         _rangesEqual: (r1, r2) ->
             r1.startContainer is r2.startContainer and r1.startOffset is r2.startOffset and r1.endContainer is r2.endContainer and r1.endOffset is r2.endOffset

+        # Check if some text is selected, and if this selection has changed. If it changed,
+        # trigger the "halloselected" event
         _checkSelection: (event) ->
             widget = event.data
-            sel = window.getSelection()
-
-            # TODO: sel.type is not crossbrowser compatible
-            #if sel.type is "Caret"
-            #    if widget.selection
-            #        widget.selection = null
-            #        widget._trigger "unselected", null,
-            #            editable: widget
-            #            originalEvent: event
-            #    return
-
-            selectedRanges = []
-            changed = not widget.section or (sel.rangeCount != widget.selection.length)
-
-            if(sel.rangeCount > 0) #fixing possible chrome error on click on editButton: "Uncaught Error: INDEX_SIZE_ERR: DOM Exception 1"
-                for i in [0..sel.rangeCount]
-                    range = sel.getRangeAt(i).cloneRange()
-                    selectedRanges[i] = range
-
-                    changed = true if not changed and not widget._rangesEqual(range, widget.selection[i])
-                    ++i
-            widget.selection = selectedRanges
-            if changed
+            sel = widget.getSelection()
+
+            if sel.collapsed is true
+                if widget.selection
+                    widget.selection = null
+                    widget._trigger "unselected", null,
+                        editable: widget
+                        originalEvent: event
+                return
+
+            if !widget.selection or not widget._rangesEqual sel, widget.selection
+                widget.selection = sel.cloneRange();
                 widget._trigger "selected", null,
                     editable: widget
-                    selection: sel
-                    ranges: selectedRanges
+                    selection: widget.selection
+                    ranges: [widget.selection]
                     originalEvent: event

         _activated: (event) ->
             widget = event.data
-            #  avoid jumping of the toolbar Todo:: look into jumpy toolbar
-            #if widget.toolbar.html() isnt ""
-                #widget.toolbar.css "top", widget.element.offset().top - widget.toolbar.height()
-                #widget.toolbar.show()
-
-            # add 'inEditMode' class onto the activated element
             jQuery(@).addClass 'inEditMode'
             widget.toolbar.css "width", jQuery(@).width()+26
             widget._trigger "activated", event


expands conditional to display modal to check if user is logged in or not

--- src/desktop/apps/articles/routes.coffee
+++ src/desktop/apps/articles/routes.coffee
@@ -27,7 +27,7 @@
       articles = new Articles result.articles
       user = res.locals.sd.CURRENT_USER
       setupEmailSubscriptions user, (results) ->
-        res.locals.sd.SUBSCRIBED_TO_EDITORIAL = results.editorial
+        res.locals.sd.SUBSCRIBED_TO_EDITORIAL = (results.editorial || !!req.user)
         res.locals.sd.ARTICLES = articles.toJSON()
         res.render 'articles',
           articles: articles


Improved truncation support

Improved truncation support

--- framer/VekterTextLayer.coffee
+++ framer/VekterTextLayer.coffee
@@ -110,13 +110,13 @@
 		firstStyle.setText(text)
 		@inlineStyles = [firstStyle]

-	setTextOverflow: (textOverflow, autoHeight) ->
-		if textOverflow in ["ellipsis", "clip"] and not autoHeight
+	setTextOverflow: (textOverflow, maxLines=1) ->
+		if textOverflow in ["ellipsis", "clip"]
 			@setStyle("overflow", "hidden")

 			multiLineOverflow = textOverflow is "ellipsis"
 			if multiLineOverflow
-				@setStyle("WebkitLineClamp", 1)
+				@setStyle("WebkitLineClamp", maxLines)
 				@setStyle("WebkitBoxOrient", "vertical")
 				@setStyle("display", "-webkit-box")
 			else
@@ -179,17 +179,27 @@
 		for block in @blocks
 			blockDiv = block.createElement()
 			block.element = blockDiv
-			block.setTextOverflow(@textOverflow, @autoHeight)
+			# block.setTextOverflow(@textOverflow, @autoHeight)
 			@element.appendChild blockDiv

+	getText: ->
+		@blocks.map((b) -> b.text).join("\n")
+
 	setText: (text) ->
-		firstBlock = _.first(@blocks)
-		firstBlock.setText(text)
-		@blocks = [firstBlock]
+		values = text.split("\n")
+		@blocks = @blocks.slice(0, values.length)
+		for value, index in values
+			if @blocks[index]?
+				block = @blocks[index]
+			else
+				block = new StyledTextBlock
+					text: value
+					inlineStyles: [_.clone(@blocks[index-1].inlineStyles[0])]
+				@blocks.push(block)
+			block.setText(value)

 	setTextOverflow: (textOverflow) ->
 		@textOverflow = textOverflow
-		@blocks.map (b) => b.setTextOverflow(textOverflow, @autoHeight)

 	setStyle: (style, value) ->
 		@blocks.map (block) -> block.setStyle(style, value)
@@ -203,10 +213,9 @@
 	measure: (currentSize) ->
 		constraints = {}
 		if not @autoWidth
-			constraints.width = currentSize.width
+			constraints.width = currentSize.width * currentSize.multiplier
 		if not @autoHeight
-			constraints.height = currentSize.height
-
+			constraints.height = currentSize.height * currentSize.multiplier
 		m = getMeasureElement(constraints)
 		measuredWidth = 0
 		measuredHeight = 0
@@ -215,13 +224,21 @@
 		for block in @blocks
 			size = block.measure()
 			measuredWidth = Math.max(measuredWidth, size.width)
-			if constraints.height? and (measuredHeight + size.height) > constraints.height
-				fontSize = parseFloat(@getStyle("fontSize", block))
-				lineHeight = parseFloat(@getStyle("lineHeight", block))
-				availableHeight = constraints.height - measuredHeight
-				visibleLines = Math.floor(availableHeight / (fontSize*lineHeight))
-				block.setStyle("WebkitLineClamp", visibleLines)
-				size = block.measure()
+			constrainedHeight = if constraints.height? then constraints.height / currentSize.multiplier else null
+			if  not @autoWidth and
+				@textOverflow? and @textOverflow in ["clip", "ellipsis"] and
+				constrainedHeight? and (measuredHeight + size.height) > constrainedHeight
+					fontSize = parseFloat(@getStyle("fontSize", block))
+					lineHeight = parseFloat(@getStyle("lineHeight", block))
+					availableHeight = constrainedHeight - measuredHeight
+					print lineHeight, availableHeight, fontSize, availableHeight / (fontSize*lineHeight)
+					visibleLines = Math.max(1, Math.floor(availableHeight / (fontSize*lineHeight)))
+					# print visibleLines
+					block.setTextOverflow(@textOverflow, visibleLines)
+					size.height = availableHeight
+					# print constrainedHeight, size
+			else
+					block.setTextOverflow(null)
 			measuredHeight += size.height

 		m.removeChild @element
@@ -321,7 +338,9 @@

 	@define "truncate",
 		get: -> @textOverflow is "ellipsis"
-		set: (truncate) -> @textOverflow = if truncate then "ellipsis" else null
+		set: (truncate) ->
+			@autoSize = false
+			@textOverflow = if truncate then "ellipsis" else null

 	@define "whiteSpace", textProperty(@, "whiteSpace", null, _.isString)
 	@define "direction", textProperty(@, "direction", null, _.isString)
@@ -344,7 +363,7 @@
 		set: (value) -> @direction = value

 	@define "text",
-		get: -> @_styledText.blocks.map((b) -> b.text).join("\n")
+		get: -> @_styledText.getText()
 		set: (value) ->
 			@_styledText.setText(value)
 			@renderText()
@@ -354,4 +373,8 @@
 		return if @__constructor
 		@_styledText.render()
 		@_updateHTMLScale()
-		@size = @_styledText.measure(@size)
+		calculatedSize = @_styledText.measure
+			width: @size.width
+			height: @size.height
+			multiplier: @context.pixelMultiplier
+		@size = calculatedSize


Sync (sort of) class-names with language-gfm #16

--- grammars/markdown.cson
+++ grammars/markdown.cson
@@ -53,7 +53,7 @@
         # end: '(?<!`)(\\1)(?!`)'
         # match: '(?<!`)(`+)[^`]+(\\1)(?!`)'
         match: '(?<!`)(`{1,2})(?!`).+?(?<!`)(\\1)(?!`)'
-        name: 'code.md'
+        name: 'code.raw.markup.md'
         captures:
           1: name: 'punctuation.md'
           2: name: 'punctuation.md'
@@ -68,7 +68,7 @@
   comments:
     patterns: [
       {
-        name: 'comment.block.md'
+        name: 'comment.md'
         begin: '(<!--)'
         end: '(-->)'
         captures:
@@ -85,7 +85,7 @@
   criticmark:
     patterns: [
       {
-        name: 'addition.critic.md'
+        name: 'addition.critic.inserted.markup.md'
         begin: '({\\+\\+)'
         beginCaptures:
           1: name: 'punctuation.md'
@@ -97,7 +97,7 @@
         ]
       }
       {
-        name: 'deletion.critic.md'
+        name: 'deletion.critic.deleted.markup.md'
         begin: '({--)'
         beginCaptures:
           1: name: 'punctuation.md'
@@ -109,7 +109,7 @@
         ]
       }
       {
-        name: 'highlight.critic.md'
+        name: 'highlight.critic.changed.markup.md'
         begin: '({==)'
         beginCaptures:
           1: name: 'punctuation.md'
@@ -121,7 +121,7 @@
         ]
       }
       {
-        name: 'comment.critic.md'
+        name: 'comment.critic.markup.md'
         begin: '({>>)'
         beginCaptures:
           1: name: 'punctuation.md'
@@ -139,7 +139,7 @@
           1: name: 'punctuation.md'
       }
       {
-        name: 'substitution.critic.md'
+        name: 'substitution.critic.inserted.markup.md'
         begin: '({~~)'
         beginCaptures:
           1: name: 'punctuation.md'
@@ -211,7 +211,7 @@

       {
         match: '(?<![\\w|\\\\])([_]{2})(?!\\s)(?m:(.+))(?<![\\s|\\\\])(\\1)(?!\\w)'
-        name: 'strong.emphasis.md'
+        name: 'strong.emphasis.bold.markup.md'
         captures:
           1: name: 'punctuation.md'
           2: patterns: [
@@ -224,7 +224,7 @@
       }
       {
         match: '(?<![\\w|\\\\])([\\*]{2})(?!\\s)(?m:(.+))(?<![\\s|\\\\])(\\1)(?!\\w)'
-        name: 'strong.emphasis.md'
+        name: 'strong.emphasis.bold.markup.md'
         captures:
           1: name: 'punctuation.md'
           2: patterns: [
@@ -238,14 +238,14 @@
       {
         # Interword ** is permitted
         match: '(?<=\\w)([\\*]{2})(?:.+)(?<!\\W)(\\1)'
-        name: 'strong.interword-1.emphasis.md'
+        name: 'strong.emphasis.bold.markup.md'
         captures:
           1: name: 'punctuation.md'
           2: name: 'punctuation.md'
       }
       {
         match: '(?<=\\s|^)([\\*]{2})(?=\\w)(?:.+)(\\1)(?=\\w)'
-        name: 'strong.interword-2.emphasis.md'
+        name: 'strong.emphasis.bold.markup.md'
         captures:
           1: name: 'punctuation.md'
           2: name: 'punctuation.md'
@@ -269,7 +269,7 @@

       {
         match: '(?<![\\w|_|\\\\])([_])(?!\\s|\\1)(?m:(.+))(?<![\\s|\\\\])(\\1)(?!\\w)'
-        name: 'emphasis.md'
+        name: 'emphasis.italic.markup.md'
         captures:
           1: name: 'punctuation.md'
           2: patterns: [
@@ -282,7 +282,7 @@
       }
       {
         match: '(?<![\\w|\\*|\\\\])([\\*])(?!\\s|\\1)(?m:(.+))(?<![\\s|\\\\])(\\1)(?!\\w)'
-        name: 'emphasis.md'
+        name: 'emphasis.italic.markup.md'
         captures:
           1: name: 'punctuation.md'
           2: patterns: [
@@ -297,7 +297,7 @@
         # Interword * is permitted
         # match: '(?<=\\w)([\*])(?m:(.+))(?<!\\W)(\\1)'
         match: '(?<=\\w)([\\*])(?:.+)(?<!\\W)(\\1)'
-        name: 'interword-1.emphasis.md'
+        name: 'emphasis.italic.markup.md'
         captures:
           1: name: 'punctuation.md'
           2: name: 'punctuation.md'
@@ -305,7 +305,7 @@
       {
         # match: '(?<=\\s|^)([\*])(?=\\w)(?m:(.+))(\\1)(?=\\w)'
         match: '(?<=\\s|^)([\\*])(?=\\w)(?:.+)(\\1)(?=\\w)'
-        name: 'interword-2.emphasis.md'
+        name: 'emphasis.italic.markup.md'
         captures:
           1: name: 'punctuation.md'
           2: name: 'punctuation.md'
@@ -369,13 +369,13 @@
       {
         match: '(?<=^|\\s|"|\'|\\(|\\[)((@)(\\w[-\\w:]*))(?=[\\s"\'.,;\\)\\]])'
         captures:
-          1: name: 'username.reference.gfm.md'
+          1: name: 'username.reference.gfm.variable.md'
           2: name: 'punctuation.md'
       }
       {
         match: '(?<=^|\\s|"|\'|\\(|\\[)((#)(\\d+))(?=[\\s"\'\\.,;\\)\\]])'
         captures:
-          1: name: 'issue.reference.gfm.md'
+          1: name: 'issue.reference.gfm.variable.md'
           2: name: 'punctuation.md'
       }
     ]
@@ -390,7 +390,7 @@
       {
         # Empty headings
         match: '^(?:[ ]{0,3})((?:#{1,6})\\s*(?:#*)\\s*)$'
-        name: 'empty.heading.md'
+        name: 'empty.heading.markup.md'
         captures:
           1:
             patterns: [
@@ -450,7 +450,7 @@
         # match for a heading? It seems more logical to test against one match, than
         # to go through all six of them every iteration.
         match: '^((?: {0,3})(?:#{1,6})(?: +)(?:.+))$'
-        name: 'heading.md'
+        name: 'heading.markup.md'
         captures:
           1:
             patterns: [
@@ -580,7 +580,7 @@
         name: 'link.3.md'
         match: '((\\[)([^\\]]*?)(\\]))((\\()(<)(.*)(>)(\\)))'
         captures:
-          1: name: 'text.link.md'
+          1: name: 'text.link.string.md'
           2: name: 'punctuation.md'
           3:
             patterns: [
@@ -590,7 +590,7 @@
           5: name: 'destination.link.md'
           6: name: 'punctuation.md'
           7: name: 'punctuation.md'
-          8: name: 'uri.md'
+          8: name: 'uri.underline.markup.md'
           9: name: 'punctuation.md'
           10: name: 'punctuation.md'
       }
@@ -601,7 +601,7 @@
         name: 'link.2.md'
         match: '((\\[)([^\\]]*?)(\\]))((\\()([^ ]*) ((["\'])(?:.+)(\\9))(\\)))'
         captures:
-          1: name: 'text.link.md'
+          1: name: 'text.link.string.md'
           2: name: 'punctuation.md'
           3:
             patterns: [
@@ -610,8 +610,8 @@
           4: name: 'punctuation.md'
           5: name: 'destination.link.md'
           6: name: 'punctuation.md'
-          7: name: 'uri.md'
-          8: name: 'title.link.md'
+          7: name: 'uri.underline.markup.md'
+          8: name: 'title.link.heading.markup.md'
           9: name: 'punctuation.md'
           10: name: 'punctuation.md'
           11: name: 'punctuation.md'
@@ -621,7 +621,7 @@
         name: 'link.1.md'
         match: '((\\[)([^\\]]*?)(\\]))((\\()(?!<)([^ ]*)(\\)))'
         captures:
-          1: name: 'text.link.md'
+          1: name: 'text.link.string.md'
           2: name: 'punctuation.md'
           3:
             patterns: [
@@ -630,7 +630,7 @@
           4: name: 'punctuation.md'
           5: name: 'destination.link.md'
           6: name: 'punctuation.md'
-          7: name: 'uri.md'
+          7: name: 'uri.underline.markup.md'
           8: name: 'punctuation.md'
       }
     ]
@@ -645,7 +645,7 @@
       {
         # Task list item
         match: '^(?:\\s*)(- \\[ \\])(?: )(.*)$'
-        name: 'task.unordered.list.md'
+        name: 'task.unordered.list.markup.md'
         captures:
           1: { name: 'punctuation.md' }
           2:
@@ -657,7 +657,7 @@
       {
         # Completed task list item
         match: '^(?:\\s*)(- \\[x\\])(?: )(.*)$'
-        name: 'completed.task.unordered.list.md'
+        name: 'completed.task.unordered.list.markup.md'
         captures:
           1: { name: 'punctuation.md' }
           2:
@@ -669,7 +669,7 @@
       {
         # Unordered list
         match: '^(?:\\s*)([-+\\*]){1}(?: )(.*)$'
-        name: 'unordered.list.md'
+        name: 'unordered.list.markup.md'
         captures:
           1: { name: 'punctuation.md' }
           2:
@@ -681,7 +681,7 @@
       {
         # Ordered list
         match: '^(?:\\s*)([0-9]{1,9}[\\.\\)])(?: )(.*)$'
-        name: 'ordered.list.md'
+        name: 'ordered.list.markup.md'
         captures:
           1: { name: 'punctuation.md' }
           2:
@@ -693,14 +693,14 @@
       {
         # Empty unordered lists, for styling purposes
         match: '^(?:\\s*)([-+\\*]){1}$'
-        name: 'empty.unordered.list.md'
+        name: 'empty.unordered.list.markup.md'
         captures:
           1: { name: 'punctuation.md' }
       }
       {
         # Empty ordered lists, for styling purposes
         match: '^(?:\\s*)([0-9]{1,9}[\\.\\)])$'
-        name: 'empty.ordered.list.md'
+        name: 'empty.ordered.list.markup.md'
         captures:
           1: { name: 'punctuation.md' }
       }
@@ -715,7 +715,7 @@
     patterns: [
       {
         match: '^(?: {0,3})(>){1}(?: ){0,1}(.*)$'
-        name: 'quote.md'
+        name: 'quote.markup.md'
         captures:
           1: { name: 'punctuation.md' }
           2:
@@ -732,7 +732,7 @@

   tables:
     patterns: [
-      name: 'table.md'
+      name: 'table.storage.md'
       begin: '^\\|'
       beginCaptures:
         0: name: 'punctuation.md'


New test - AutoVar - invalidation propagation during fetch

--- tests/publish-tests.coffee
+++ tests/publish-tests.coffee
@@ -7,7 +7,64 @@
     "#{Math.floor 10000 * Math.random()}"

-addTest "AutoVar - set fetchInProgress when a dependency is setting fetchInProgress", (test, onComplete) ->
+addTest "AutoVar - invalidation propagation during fetch", (test, onComplete) ->
+    randomId = new ReactiveVar makeId()
+
+    a = J.AutoVar(
+        'a'
+        ->
+            console.log 'compute a'
+            foo = $$.Foo.fetchOne randomId.get()
+            console.log 'a got', foo
+            randomId.get()
+    )
+    b = J.AutoVar(
+        'b'
+        ->
+            console.log 'compute b'
+            ret = a.get()
+            console.log 'b got', ret
+            ret
+    )
+    c = J.AutoVar(
+        'c'
+        ->
+            console.log 'compute c'
+            ret = b.get()
+            console.log 'c got', ret
+            ret
+    )
+
+    runCount1 = 0
+    watcher1 = J.AutoVar(
+        'watcher1'
+        ->
+            runCount1 += 1
+            console.log 'compute watcher1', runCount1
+            cVal = c.get()
+            console.log 'watcher1 got', cVal
+            if runCount1 is 1
+                test.isTrue false, "w1 should have thrown (1)"
+            else if runCount1 is 2
+                test.isTrue cVal?
+                randomId.set makeId()
+                console.log 'Should throw here'
+                newCVal = c.get()
+                test.isTrue false, "Should have thrown"
+                console.log 'newCVal is', newCVal
+            else if runCount1 is 3
+                test.isTrue cVal?
+                a.stop()
+                b.stop()
+                c.stop()
+                watcher1.stop()
+                onComplete()
+            null
+        true
+    )
+
+
+if false then addTest "AutoVar - set fetchInProgress when a dependency is setting fetchInProgress", (test, onComplete) ->
     randomId = new ReactiveVar makeId()

     a = J.AutoVar(


Coffee fix

Coffee fix

--- _includes/coffeescripts/survey.coffee
+++ _includes/coffeescripts/survey.coffee
@@ -50,7 +50,7 @@
             # GA "Event label"
             @.value,
             # GA "Event Value", if integer
-            parseInt(@.value, 10) == if @.value then parseInt(@.value, 10) else 0
+            parseInt(@.value, 10) is if @.value then parseInt(@.value, 10) else 0
           ]

       # Collect data from survey form


Bug fix in form validation

Bug fix in form validation

--- _includes/coffeescripts/survey.coffee
+++ _includes/coffeescripts/survey.coffee
@@ -78,7 +78,6 @@

       # User want to take the survey
       $("#survey-action-ok").on 'click', (event) ->
-        event.preventDefault()
         showSlide 2

       $('#survey-form').on 'submit', (event) ->
@@ -86,14 +85,12 @@

       # User submits the survey
       $('#survey-action-send').on 'click', (event) ->
-        event.preventDefault()
-
         # Validate form
-        $("#survey .warning").remove()
+        $("#survey .warning").removeClass("warning")
         validates = true
         formValues = $("#survey-form").serializeArray()
         $.each formValues, () ->
-          if !@.value
+          if not @.value
             validationAlert @.name
             validates = false
             return
@@ -102,20 +99,17 @@

       # User do not want to take the survey now
       $("#survey-action-no").on 'click', (event) ->
-        event.preventDefault()
         writePersistentCookie "not_now", msecNow()
         writeSessionCookie "selected", false
         $survey.remove()

       # User do not want to take the survey right now, she will be reminded during the session
       $("#survey-action-snooze").on 'click', (event) ->
-        event.preventDefault()
         writeSessionCookie "snooze", msecNow()
         $survey.remove()

       # Close the dialog after the Thank you page
       $("#survey-action-done").on 'click', (event) ->
-        event.preventDefault()
         $survey.remove()

     # Read persisten and session cookies that keep track of the survey


Update translations.

Update translations.

--- app/assets/javascripts/application/i18n.js.coffee
+++ app/assets/javascripts/application/i18n.js.coffee
@@ -154,7 +154,7 @@
     "payment": "Payment"
     "phone_numbers": "Phone Numbers"
     "photo_browser": "Photo Browser"
-    "plist": "PList"
+    "plist": "Plist"
     "prettyfier": "Prettyfier"
     "promises": "Promises"
     "proprietary_api": "Proprietary API"


Use a real uuid in the test to silence DB warnings

--- storage/test/unit-access-service.coffee
+++ storage/test/unit-access-service.coffee
@@ -1,4 +1,5 @@
 {expect} = require 'chai'
+uuid = require 'uuid'
 Promise = require 'bluebird'
 Promise.longStackTraces()

@@ -40,7 +41,7 @@
                 expect(result).to.be.true

             it 'should return false for an invalid token', Promise.coroutine ->
-                result = yield access.check 'foo'
+                result = yield access.check uuid()
                 expect(result).to.be.false

             it 'should return false for a null token', Promise.coroutine ->


Refactor to use settings from the repository

--- helper/settings.coffee
+++ helper/settings.coffee
@@ -9,5 +9,5 @@
   fileLocal = "#{splinters.dir}/#{splinters.name}.local#{splinters.ext}"
   if fs.existsSync(fileLocal)
     settingsLocal = JSON.parse fs.readFileSync fileLocal, {encoding: "UTF-8"}
-    _.deepExtend settings, settingsLocal
+    settings = _.deepExtend settings, settingsLocal
   settings


Actualización del paginador a jQuery 1.6.

Por defecto, espera que una respuesta text/javascript venga con tipo
JSON, y da un error al evaluar la respuesta que en realidad viene como
el trozo de HTML a insertar.

--- app/assets/javascripts/jquery.paginator.js.coffee
+++ app/assets/javascripts/jquery.paginator.js.coffee
@@ -21,6 +21,7 @@

   this.each ->
     $element = jQuery(this)
+    defaults['dataType'] = 'text'
     defaults['success'] = (data) ->
       $element.html(data)
       $element.removeCargando()


Updated .atom configuration

Updated .atom configuration

- new theme settings
- adding gopath to avoid warnings on launch
- adding additional linter setting
- removing comments that get overwritten when config changes

--- home/.atom/config.cson
+++ home/.atom/config.cson
@@ -1,24 +1,27 @@
 'global':
   'exception-reporting':
-    # NOTE - considered sharable, see:
-    # https://github.com/atom/exception-reporting/issues/7
     'userId': '1e725750-c66d-33e4-9fce-f627ac32bde5'
   'release-notes':
     'viewedVersion': '0.94.0'
   'welcome':
     'showOnStartup': false
   'metrics':
-    # NOTE - considered sharable, see:
-    # https://github.com/atom/metrics/issues/18
     'userId': 'c2f08243ecb4a9bc44bd9e46d4f067086d9357e3'
   'editor':
     'fontSize': 11
     'showInvisibles': true
     'showIndentGuide': true
-    'invisibles': {}
   'core':
     'disabledPackages': [
       'metrics'
       'select-rectangle'
     ]
     'useReactEditor': true
+    'themes': [
+      'atom-dark-ui-slim'
+      'atom-dark-syntax'
+    ]
+  'linter':
+    'showErrorInline': true
+  'go-plus':
+    'goPath': '~/.go'


more string interpolation goodness

more string interpolation goodness

--- src/extractors/GoogleSpreadsheet.coffee
+++ src/extractors/GoogleSpreadsheet.coffee
@@ -18,7 +18,7 @@

     download = (sheet) ->
       rawData = ""
-      link = "/feeds/cells/" + config.apiKey + "/" + sheet + "/public/values?alt=json"
+      link = "/feeds/cells/#{config.apiKey}/#{sheet}/public/values?alt=json"
       options =
         host: "spreadsheets.google.com"
         port: 80


Fix class parsing for code

Fix class parsing for code

--- app/assets/javascripts/nembrot.document.js.coffee
+++ app/assets/javascripts/nembrot.document.js.coffee
@@ -178,7 +178,7 @@
   $("#code").toggle()

 load_code = () ->
-  file = '/code/' + $('#main > div').attr('class').replace('s-', '/')
+  file = '/code/' + $('#main > div').attr('class').replace('s-', '/').replace('-', '/')
   $.ajax
     url: file
     cache: true


Flush through the questions in the brain.

--- scripts/chat.coffee
+++ scripts/chat.coffee
@@ -32,7 +32,8 @@
 		robot.logger.info "Loading knowledge"
 		robot.brain.data.questions ?= {}

-		if _.isUndefined(robot.brain.data.questions) then robot.brain.data.questions = initial_question_bank
+		# if _.isUndefined(robot.brain.data.questions) then
+		robot.brain.data.questions = initial_question_bank
 		question_bank = robot.brain.data.questions
 		current_question = null
 		question_ids = Object.keys(question_bank)
@@ -55,7 +56,6 @@
 				question_text = "Thanks for talking to me. "
 			else
 				used_questions.push current_question
-				robot.logger.info used_questions
 				question_ids = _.difference(question_ids, used_questions)
 				question_text = question_bank[current_question].question
 			cb question_text


added ability to pass start date view to date

--- app/assets/javascripts/pt/tags/datetime.js.coffee
+++ app/assets/javascripts/pt/tags/datetime.js.coffee
@@ -32,6 +32,7 @@
           showMeridian: true
           autoclose: true
           minView: 2
+          startView: parseInt(attr.dateStartView) or 2

         $el.data 'ptDateLoaded', true
         $el.datetimepicker 'show'


possibility to specify API_PROTO_PATH or API_CONFIG_PATH as environment variable

--- hubot/scripts/cloud.coffee
+++ hubot/scripts/cloud.coffee
@@ -18,8 +18,8 @@
 ConfigHelper      = require '../modules/config-helper/config-helper'
 UrlParamsResolver = require '../modules/url-params-resolver/url-params-resolver'

-protopath     = '/api/main.proto'
-configpath    = '/api/config.yml'
+protopath     = process.env.API_PROTO_PATH || '/api/main.proto'
+configpath    = process.env.API_CONFIG_PATH || '/api/config.yml'
 host          = process.env.API_HOST + ':' + process.env.API_PORT

 caller        = new Caller       protopath, host


fixed filterRows method

fixed filterRows method

--- app/coffee/drm-tablefilter/drm-tablefilter.coffee
+++ app/coffee/drm-tablefilter/drm-tablefilter.coffee
@@ -19,18 +19,41 @@
             # cache full table
             self.fullRows = @table.find 'tbody tr'

-            self.table.on 'keyup', "input.#{@searchInput}", ->
-                that = $ @
-                input = $.trim(that.val()).toLowerCase()
-                columnNum = that.closest('th').index()
-                self.renderTable columnNum, input
+            self.table.on 'keyup', "input.#{@searchInput}", self.renderTable

-        filterRows: (columnNum, input) =>
-            rows = if input.length is 0 then @fullRows else @fullRows.has "td:eq(#{columnNum}):containsNC(#{input})"
+        filterRows: =>
+            self = @
+            # check other inputs
+            inputs = self.table.find('th').find ".#{self.searchInput}"
+            filterValues = []
+
+            $.each inputs, (key, value) ->
+                that = $ value
+                if $.trim(that.val()).length isnt 0 then filterValues.push value
+                filterValues
+
+            filterValuesLength = filterValues.length
+
+            if filterValuesLength is 0
+                rows = self.fullRows
+            else
+                $.each filterValues, (key, value) ->
+                    that = $ value
+                    input = $.trim(that.val()).toLowerCase()
+                    columnNum = that.closest('th').index()
+
+                    if filterValuesLength is 1
+                        rows = self.fullRows.has "td:eq(#{columnNum}):containsNC(#{input})"
+                    else if key is 0
+                        rows = self.fullRows.has "td:eq(#{columnNum}):containsNC(#{input})"
+                    else
+                        rows = rows.has "td:eq(#{columnNum}):containsNC(#{input})"
+                    rows
+            rows

-        renderTable: (columnNum, input) =>
+        renderTable: =>
+            filteredRows = @filterRows()
             tableBody = @table.find('tbody').empty()
-            filteredRows = @filterRows columnNum, input

             $.each filteredRows, (key, value) ->
                 tableBody.append value


add start/stop feature on cron.coffee

add start/stop feature on cron.coffee

--- scripts/cron.coffee
+++ scripts/cron.coffee
@@ -5,13 +5,20 @@
 #      robot.send {room: "#bot"}, "おはようございます！"

   cronjob = new cronJob(
-    cronTime: "1 * * * * *"     # 実行時間 s m h d w m
+    cronTime: "0 1 0 * * * *"     # 実行時間 s m h d w m
     start:    true              # すぐにcronのjobを実行するか
     timeZone: "Asia/Tokyo"      # タイムゾーン指定
     onTick: ->                  # 時間が来た時に実行する処理
 #      sayHello()
       robot.send {room: "#bot"}, "おはようございます！"
   )
+  robot.respond /start job/i, (msg) ->
+    msg.send "Start job.."
+    job.start()
+
+  robot.respond /stop job/i, (msg) ->
+    msg.send "Stop job.."
+    job.stop()
 #
 #  robot.respond /hello/i, (msg) ->
 #    msg.send 'World!'


help messages in private

help messages in private

--- scripts/help.coffee
+++ scripts/help.coffee
@@ -69,7 +69,7 @@

     emit = cmds.join "\n"

-    msg.send emit
+    robot.send({user: {name: msg.message.user.name}}, emit)

   robot.router.get "/#{robot.name}/help", (req, res) ->
     cmds = robot.helpCommands().map (cmd) ->


Updated treedropdownfields namespaces and snippets

--- completions.cson
+++ completions.cson
@@ -2002,23 +2002,31 @@
   'prefix': 'treedropdownfield'
   'module': 'silverstripe/framework'
   'scope': '.text.html.php'
-  'body': """
-  TreeDropdownField::create(
-  \t\'${1:name}ID\',
-  \t\'${2:${1:title}}\',
-  \t\'${3:SiteTree}\'
-  )${0}
-  """
-  'variations': [
-    {
-      'minVersion': '4.0'
+  'variations': [
+    {
+      'minVersion': '4.0'
+      'body': """
+      TreeDropdownField::create(
+      \t\'${1:name}ID\',
+      \t\'${2:${1:title}}\',
+      \t${3:SiteTree::class}
+      )${0}
+      """
       'useNamespace': [
         'SilverStripe\\Forms\\TreeDropdownField'
-      ]
-    }
-    {
-      'maxVersion': '4.0'
-      'minVersion': '3.0'
+        'SilverStripe\\CMS\\Model\\SiteTree'
+      ]
+    }
+    {
+      'maxVersion': '4.0'
+      'minVersion': '3.0'
+      'body': """
+      TreeDropdownField::create(
+      \t\'${1:name}ID\',
+      \t\'${2:${1:title}}\',
+      \t\'${3:SiteTree}\'
+      )${0}
+      """
     }
   ]

@@ -2026,23 +2034,31 @@
   'prefix': 'treedropdownfield_readonly'
   'module': 'silverstripe/framework'
   'scope': '.text.html.php'
-  'body': """
-  TreeDropdownField_Readonly::create(
-  \t\'${1:name}ID\',
-  \t\'${2:${1:title}}\',
-  \t\'${3:SiteTree}\'
-  )${0}
-  """
-  'variations': [
-    {
-      'minVersion': '4.0'
+  'variations': [
+    {
+      'minVersion': '4.0'
+      'body': """
+      TreeDropdownField_Readonly::create(
+      \t\'${1:name}ID\',
+      \t\'${2:${1:title}}\',
+      \t${3:SiteTree::class}
+      )${0}
+      """
       'useNamespace': [
         'SilverStripe\\Forms\\TreeDropdownField_Readonly'
-      ]
-    }
-    {
-      'maxVersion': '4.0'
-      'minVersion': '3.0'
+        'SilverStripe\\CMS\\Model\\SiteTree'
+      ]
+    }
+    {
+      'maxVersion': '4.0'
+      'minVersion': '3.0'
+      'body': """
+      TreeDropdownField_Readonly::create(
+      \t\'${1:name}ID\',
+      \t\'${2:${1:title}}\',
+      \t\'${3:SiteTree}\'
+      )${0}
+      """
     }
   ]

@@ -2054,7 +2070,7 @@
   DropdownField::create(
   \t\'${1:name}\',
   \t\'${2:${1:title}}\',
-  \t\'${3:datalist}\'
+  \t${3:datalist}
   )${0}
   """
   'variations': [


refined component.coffee docs

refined component.coffee docs

--- component.coffee
+++ component.coffee
@@ -7,19 +7,15 @@
 yaml = require "js-yaml"
 testServer = require "./server"

-read = (fname, encoding="utf8") -> fs.readFileSync(fname, encoding)
-write = (fname, content, encoding="utf8") -> fs.writeFileSync(fname, content, encoding)
+read = (fname, encoding="utf8") ->
+  fs.readFileSync(fname, encoding)
+write = (fname, content, encoding="utf8") ->
+  fs.writeFileSync(fname, content, encoding)
 exists = path.existsSync
 join = path.join
 pathSep = path.normalize("/")
 argv = null
-
-usage = """node $0 [-l] install
-node $0 [-l] [--list] test
-
-`component` is a utility that's used for installing javascript components and
-their dependencies -- imagine that!
-"""
+usage = null

 pkgJson = (dir = ".") ->
   JSON.parse read(join(dir, "package.json"))
@@ -95,7 +91,8 @@

 provide = (pth) ->
   if not exists pth
-    _.reduce [""].concat(pth.split(pathSep)), (soFar, dir) ->
+    start = if pth[0] is pathSep then pathSep else ""
+    _.reduce [start].concat(pth.split(pathSep)), (soFar, dir) ->
       fs.mkdirSync join(soFar, dir) if not exists(join(soFar, dir))
       soFar += (if soFar then pathSep else "") + dir

@@ -177,53 +174,78 @@
   config: (config) ->
     JSON.stringify config, null, "    "

-commands =
-
-  install: () ->
-    components = allComponents()
-    componentsDirName = join testDirectory(), "static/components"
-    provide componentsDirName
-    for component in components
-      src = join(component.path, sourceDirectory(component.path))
-      name = componentName(null, component.json)
-      installTo componentsDirName, argv.link, src, name
-    if isComponent()
-      installTo componentsDirName, argv.link, 'src', componentName()
-
-  test: () ->
-    port = process.env.PORT || 1335
-    host = process.env.HOST || "localhost"
-    components = allComponents()
-    staticDirName = join testDirectory(), "static"
-    return listTests(discoverTests(staticDirName), host, port) if argv.listtests
-    commands.install()
-    tests = discoverTests staticDirName
-    extraHtml = getTestTemplate() + "\n" + stringBundlesAsRequirejsModule()
-    testServer
-      .createServer(staticDirName, getConfig(), extraHtml, getTestMiddleware())
-      .listen(port, host)
-    console.log "serving at http://#{host}:#{port}"
-    console.log "available tests:"
-    listTests tests, host, port
-
-  template: (cmd, args...) ->
-    templateCommands[cmd] getConfig()
-
-  build: () ->
-    commands.install()
-
-    staticDirName = join testDirectory(), "static"
-    provide argv.staticpath
-    wrench.copyDirSyncRecursive staticDirName, argv.staticpath
-
-    config = getConfig()
-    templateObj = _.reduce(templateCommands, ((templateObj, cmd, name) ->
-      templateObj[name] = templateCommands[name](config)
-      return templateObj
-    ), {})
-    provide argv.templatepath
-    contextjsonname = join(argv.templatepath, argv.contextjsonname)
-    write contextjsonname, JSON.stringify(templateObj)
+exports.commands = commands =
+  install:
+    description: """
+    install component dependencies
+    """
+    action: () ->
+      components = allComponents()
+      componentsDirName = join testDirectory(), "static/components"
+      provide componentsDirName
+      for component in components
+        src = join(component.path, sourceDirectory(component.path))
+        name = componentName(null, component.json)
+        installTo componentsDirName, argv.link, src, name
+      if isComponent()
+        installTo componentsDirName, argv.link, 'src', componentName()
+
+  test:
+    description: """
+    start up a test server
+    """
+    action: () ->
+      port = process.env.PORT || 1335
+      host = process.env.HOST || "localhost"
+      components = allComponents()
+      staticDirName = join testDirectory(), "static"
+      if argv.listtests
+        return listTests(discoverTests(staticDirName), host, port)
+      commands.action.install()
+      tests = discoverTests staticDirName
+      extraHtml = getTestTemplate() + "\n" + stringBundlesAsRequirejsModule()
+      testServer
+        .createServer(staticDirName, getConfig(), extraHtml, getTestMiddleware())
+        .listen(port, host)
+      console.log "serving at http://#{host}:#{port}"
+      console.log "available tests:"
+      listTests tests, host, port
+
+  template:
+    description: """
+    output html snippets for things like the require.js path, can be any of:
+    #{(' - ' + cmd + '\n' for cmd of templateCommands).join("")}
+    """.replace(/\n$/, '')
+    action: (cmd, args...) ->
+      templateCommands[cmd] getConfig()
+
+  build:
+    description: """
+    the build command does two things:
+     - if the [staticpath] is given, it will run the `component install`
+       command and then copy the installation directory to [staticpath]
+     - if the [templatepath] is given, it will output a json file named
+       [contextjsonname] to that path containing the keyed info from the
+       `component template` command
+    """
+    action: () ->
+      if argv.staticpath
+        provide argv.staticpath
+
+        commands.action.install()
+
+        staticDirName = join testDirectory(), "static"
+        wrench.copyDirSyncRecursive staticDirName, argv.staticpath
+
+      if argv.templatepath
+        config = getConfig()
+        templateObj = _.reduce(templateCommands, ((templateObj, cmd, name) ->
+          templateObj[name] = templateCommands[name](config)
+          return templateObj
+        ), {})
+        provide argv.templatepath
+        contextjsonname = join(argv.templatepath, argv.contextjsonname)
+        write contextjsonname, JSON.stringify(templateObj)

 commandNames = _.reduce(commands, ((memo, v, k) ->
@@ -232,38 +254,56 @@
   memo
 ), {})

+usage = """#{("node $0 "+cmd+'\n' for cmd of commandNames).join("")}
+`component` is a utility that's used for installing javascript components and
+their dependencies -- imagine that!
+
+commands:
+
+"""
+for name, command of commands
+  usage += "
+  #{name}:\n
+    #{command.description.replace(/\n/g, '\n    ')}\n"
+
 exports.run = () ->
   argv = require("optimist")
     .usage(usage)

-    .boolean(["l", "link"]).alias("l", "link")
-    .describe("l", "install components as symlinks (useful for development)")
-
-    .boolean("listtests")
-    .describe("listtests", "when running the 'test' command, just list tests")
-
-    .string(["t", "templatepath"]).alias("t", "templatepath")
-    .describe("t", "when running 'build', command, specify the templatepath")
-
-    .string(["j", "contextjsonname"]).alias("j", "contextjsonname")
-    .default("contextjsonname", "csi-context.json")
-    .describe("j", "when running 'build', command, specify the name of the context json")
-
-    .string(["s", "staticpath"]).alias("s", "staticpath")
-    .describe("s", "when running 'build', command, specify the staticpath")
-
-    .string(["b", "baseurl"]).alias("b", "baseurl")
-    .default("baseurl", "/static")
-    .describe("b", "when running 'build', command, specify the baseurl")
-
-    .check((argv) ->
-      if argv._[0] is 'build'
-        for arg in ['templatepath', 'staticpath']
-          if not argv[arg]
-            throw Error "'#{arg}' option must be defined for 'build' command"
-    )
+    .options "link",
+      boolean: true
+      alias: "l"
+      describe: "install components as symlinks (useful for development)"
+
+    .options "listtests",
+      boolean: true
+      describe: "(command: test) just list tests"
+
+    .options "templatepath",
+      string: true
+      alias: "t"
+      describe: "(command: build) specify the templatepath"
+
+    .options "contextjsonname",
+      string: true
+      alias: "j"
+      "default": "csi-context.json"
+      describe: "(command: build) specify the name of the context json"
+
+    .options "staticpath",
+      string: true
+      alias: "s"
+      describe: "(command: build) specify the static path (dirname for build)"
+
+    .options "baseurl",
+      string: true
+      alias: "b"
+      "deafault": "/static"
+      describe: "(command: build) specify the baseurl"

     .alias("h", "help")
+
+    .wrap(80)

     .argv

@@ -277,5 +317,5 @@
     require("optimist").showHelp()
     process.exit 1

-  commands[command](argv._[1..]...)
-
+  commands[command].action(argv._[1..]...)
+


'build' command doesn't blow away component inst.

--- component.coffee
+++ component.coffee
@@ -266,16 +266,16 @@
     action: () ->
       commands.install.action()

+      if resolve(argv.staticpath) isnt resolve(defaultStaticpath())
+        log "installing default static path (#{defaultStaticpath()}) to #{argv.staticpath}"
+        wrench.copyDirSyncRecursive defaultStaticpath(), argv.staticpath
+
       if argv.templatepath
         templateObj = templateCommands.all getConfig()
         provide argv.templatepath
         contextjsonname = join(argv.templatepath, argv.contextjsonname)
         log "writing context json to #{contextjsonname}"
         write contextjsonname, JSON.stringify(templateObj)
-
-      if resolve(argv.staticpath) isnt resolve(defaultStaticpath())
-        log "installing default static path (#{defaultStaticpath()}) to #{argv.staticpath}"
-        wrench.copyDirSyncRecursive defaultStaticpath(), argv.staticpath

   completion:
     description: """


fade in date row faster

fade in date row faster

--- scripts/home.coffee
+++ scripts/home.coffee
@@ -9,4 +9,4 @@
     $date = $dateRow.find('#meetupDate')
     shuffler.render($date)

-    $dateRow.fadeIn()
+    $dateRow.fadeIn(300)


2017-02-19 22:32:19

2017-02-19 22:32:19

--- def/menu_win32.cson
+++ def/menu_win32.cson
@@ -12,8 +12,6 @@
 				value: "打开文件夹..."
 			"Add Project Folder…":
 				value: "添加项目文件夹..."
-			"Reopen Last &Item":
-				value: "重新打开上一次项目(&I)"
 			"Open Recent":
 				value: "最近打开的文件..."
 				submenu:
@@ -21,7 +19,7 @@
 						value: "重新打开上次关闭的文件"
 					"Clear List":
 						value: "清除列表"
-            "Reopen Project":
+			"Reopen Project":
 				value: "重新打开项目..."
 				submenu:
 					"Clear Project History":


Include 404 page in build

Include 404 page in build

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -131,6 +131,7 @@
           modules: [{
             name: 'main'
             include: [
+              'cs!pages/404/404'
               'cs!pages/home/home'
               'cs!pages/content/content'
               'cs!pages/search/search'


Add bump task

Add bump task

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -82,4 +82,7 @@
   # register tasks
   grunt.registerTask "build", ["clean", "coffeelint", "coffee", "concat"]
   grunt.registerTask "test", ["build", "shell:jasmine"]
-  grunt.registerTask "coverage", ["build", "shell:coverage"]+  grunt.registerTask "coverage", ["build", "shell:coverage"]
+  grunt.registerTask 'release', 'Release a new version, push it and publish it', (target) ->
+    target = 'patch' unless target
+    grunt.task.run "bump-only:#{target}", 'test', 'bump-commit'


Use regex for e.touches replacement

Use regex for e.touches replacement

Was not firing globally before, this fixes tests.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -126,7 +126,7 @@
           # Convert Zepto's touch class to work for jQuery
           processContent: (content) ->
             content
-              .replace('e.touches', '(e.originalEvent||e).touches')
+              .replace(/e\.touches/g, '(e.originalEvent||e).touches')
               .replace('(Zepto)', '(jQuery)')

         files:


commented test for manual release

commented test for manual release

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -555,7 +555,7 @@
   grunt.registerTask "default",
     # release alchemy
     if releaseFlag
-      [#"test:dist",
+      ["test:dist",
        "build",
        "string-replace", # apply version to alchemy.js
        "bumpBower", # bump bower version