Auto-reconnect WebSocket when connection drops

--- frontend/app/js/mpd-service.coffee
+++ frontend/app/js/mpd-service.coffee
@@ -2,15 +2,30 @@

 MPD_STATUS = "mpd:status"

-mod.factory "mpdService", ["$rootScope", ($rootScope) ->
+mod.factory "mpdService", ["$rootScope", "$interval", ($rootScope, $interval) ->
+  ctrl = this
+  retrying = null

-  # Open a websicket and wait for incoming messages.
-  ws = new WebSocket("ws://#{location.host}/websocket")
-  ws.onopen = ->
-    console.log "Websocket opened."
+  @connect = ->
+    # Open a websicket and wait for incoming messages.
+    ws = new WebSocket("ws://#{location.host}/websocket")
+    ws.onopen = ->
+      console.log "Websocket opened"
+      retrying && $interval.cancel(retrying)
+      retrying = null

-  # The only type of message is mpd status JSON, for now.
-  ws.onmessage = (message) ->
-    $rootScope.$broadcast MPD_STATUS, JSON.parse(message.data)
+    # The only type of message is mpd status JSON, for now.
+    ws.onmessage = (message) ->
+      $rootScope.$broadcast MPD_STATUS, JSON.parse(message.data)

+    # The only type of message is mpd status JSON, for now.
+    ws.onclose = ->
+      return if retrying
+      console.log "Websocket closed"
+      retrying = $interval ->
+        console.log "Websocket reconnecting"
+        ctrl.connect()
+      , 1000
+
+  @connect()
 ]


Don't follow, preload or up-instant a link with [rel=download]

--- lib/assets/javascripts/unpoly/link.coffee
+++ lib/assets/javascripts/unpoly/link.coffee
@@ -96,11 +96,21 @@
     )

   config = new up.Config ->
-    preloadDelay: 90
-    followSelectors: combineFollowableSelectors(LINKS_WITH_REMOTE_HTML, ATTRIBUTES_SUGGESTING_FOLLOW).concat(LINKS_WITH_LOCAL_HTML)
-    instantSelectors: ['[up-instant]']
-    preloadSelectors: combineFollowableSelectors(LINKS_WITH_REMOTE_HTML, ['[up-preload]'])
-    clickableSelectors: LINKS_WITH_LOCAL_HTML.concat ['[up-emit]', '[up-accept]', '[up-dismiss]', '[up-clickable]']
+    noFollowSelectors = ['[up-follow=false]', '[rel=download]']
+    return {
+      followSelectors: combineFollowableSelectors(LINKS_WITH_REMOTE_HTML, ATTRIBUTES_SUGGESTING_FOLLOW).concat(LINKS_WITH_LOCAL_HTML),
+      noFollowSelectors: noFollowSelectors
+
+      instantSelectors: ['[up-instant]'],
+      noInstantSelectors: ['[up-instant=false]'].concat(noFollowSelectors),
+
+      preloadSelectors: combineFollowableSelectors(LINKS_WITH_REMOTE_HTML, ['[up-preload]']),
+      noPreloadSelectors: ['[up-preload=false]'].concat(noFollowSelectors),
+
+      clickableSelectors: LINKS_WITH_LOCAL_HTML.concat(['[up-emit]', '[up-accept]', '[up-dismiss]', '[up-clickable]']),
+
+      preloadDelay: 90,
+    }

   fullFollowSelector = ->
     config.followSelectors.join(',')
@@ -114,11 +124,14 @@
   fullClickableSelector = ->
     config.clickableSelectors.join(',')

-  isInstant = (linkOrDescendant) ->
-    element = e.closest(linkOrDescendant, fullInstantSelector())
-    # Allow users to configure up.link.config.instantSelectors.push('a')
-    # but opt out individual links with [up-instant=false].
-    return element && !e.matches(element, '[up-instant=false], [up-follow=false]')
+  isFollowDisabled = (link) ->
+    return e.matches(link, config.noFollowSelectors.join(','))
+
+  isPreloadDisabled = (link) ->
+    return e.matches(link, config.noPreloadSelectors.join(','))
+
+  isInstantDisabled = (link) ->
+    return e.matches(link, config.noInstantSelectors.join(','))

   ###**
   @property up.link.config
@@ -401,9 +414,6 @@
     link = up.fragment.get(link)
     return e.matches(link, fullFollowSelector()) && !isFollowDisabled(link)

-  isFollowDisabled = (link) ->
-    return e.matches(link, '[up-follow=false]')
-
   ###**
   Makes sure that the given link will be [followed](/up.follow)
   by Unpoly instead of making a full page load.
@@ -453,6 +463,12 @@
     betterTargetSelector = "a, [up-href], #{up.form.fieldSelector()}"
     betterTarget = e.closest(event.target, betterTargetSelector)
     return (!betterTarget || betterTarget == link) && !isFollowDisabled(link)
+
+  isInstant = (linkOrDescendant) ->
+    element = e.closest(linkOrDescendant, fullInstantSelector())
+    # Allow users to configure up.link.config.instantSelectors.push('a')
+    # but opt out individual links with [up-instant=false].
+    return element && !isInstantDisabled(element)

   ###**
   Provide an `up:click` event that improves on standard click
@@ -911,7 +927,7 @@
   @stable
   ###
   up.compiler fullPreloadSelector, (link) ->
-    unless e.matches(link, '[up-preload=false], [up-follow=false]')
+    unless isPreloadDisabled(link)
       return linkPreloader.observeLink(link)

   up.on 'up:framework:reset', reset


use encodeURIComponent instead of a simple replace

--- server/middlewares/authentication.coffee
+++ server/middlewares/authentication.coffee
@@ -33,6 +33,6 @@
         next()
     else
         url = "/login"
-        url += "?next=#{req.url.replace "?", "%3F"}" unless req.url is '/'
+        url += "?next=#{encodeURIComponent req.url}" unless req.url is '/'
         url += "&#{qs.stringify req.query}" if req.query.length
         res.redirect url


Fix profile volume permission sorting

Fix profile volume permission sorting

--- web/party/profile.coffee
+++ web/party/profile.coffee
@@ -104,7 +104,7 @@
     parties.collaborators = (Party.all[pi] for pi of collaborators)

     stringSort = (a,b) -> +(a > b) || +(a == b) - 1
-    volumeSort = (a,b) -> b.permission - a.permission || stringSort(a.volume.displayName, b.volume.displayName)
+    volumeSort = (a,b) -> b.volume.permission - a.volume.permission || stringSort(a.volume.displayName, b.volume.displayName)
     partySort = (a,b) -> stringSort(a.party.sortname, b.party.sortname)

     volumes.individual.sort(volumeSort)


GameObject.Effect

GameObject.Effect

--- test/tweening.coffee
+++ test/tweening.coffee
@@ -3,6 +3,8 @@
 test "should allow for simple linear tweening", ->
   o = GameObject
     x: 0
+
+

   o.tween 10,
     x: 10


Remove unused require

Remove unused require

--- src/packages/tree-view/src/dialog.coffee
+++ src/packages/tree-view/src/dialog.coffee
@@ -1,4 +1,4 @@
-{View, $$} = require 'space-pen'
+{View} = require 'space-pen'
 Editor = require 'editor'
 fs = require 'fs'
 $ = require 'jquery'


improving comments

improving comments

--- src/app/controllers/main_controller.coffee
+++ src/app/controllers/main_controller.coffee
@@ -1,3 +1,4 @@
 class MainController extends app.controllers.AppController
+	# this code will run automatically, change it if you want to overwrite it
 	# index:()->
 	# 	@render "main", new app.models.MainModel


Fix room id on createDirectRoom

Fix room id on createDirectRoom

--- server/methods/createDirectRoom.coffee
+++ server/methods/createDirectRoom.coffee
@@ -6,7 +6,7 @@
 		if Meteor.user().username is toUsername
 			return

-		roomId = [Meteor.user().username, toUsername].sort().join('')
+		roomId = [Meteor.user().username, toUsername].sort().join(',')

 		userTo = Meteor.users.findOne { username: toUsername }


Fix rcpath, remove trailing whitespaces

Fix rcpath, remove trailing whitespaces

--- kites/sharedHosting/mounter.coffee
+++ kites/sharedHosting/mounter.coffee
@@ -137,19 +137,17 @@
                         callback null,res

   tmpNetrc : (options,callback)->
-
+
     # this method will create .netrc file for curlftpfs

-    #
-    #
-
-    {username, remotehost,remoteuser,remotepass, install} = options
-    rcPath = escapePath path.join config.usersPath, usersname, '.netrc'
+    {username, remotehost, remoteuser, remotepass, install} = options
+    rcPath = escapePath path.join config.usersPath, username, '.netrc'

     unless safeForUser username, rcPath
       console.error "User [#{username}] is trying to make something bad: ", cfg
       callback "You are not authorized to do this."
       return no
+
     if install
       conf = "machine #{remotehost} login #{remoteuser} password #{remotepass}"
       fs.writeFile rcPath, conf, 'utf-8', (err)->
@@ -167,11 +165,6 @@
         else
           console.log info = "[OK] .netrc file #{rcPath} has been removed"
           callback null, info
-
-
-
-
-

   # Safe
   umountDrive : (options, callback)->
@@ -214,7 +207,7 @@
               if err?
                 console.warn "Couldn't remove mountpoint #{options.mountpoint}: #{err.message}"
               else
-                console.log "mountpoint #{options.mountpoint} has been removed"
+                console.log "mountpoint #{options.mountpoint} has been removed"
             @removeMountConf options,(err,res)->
               callback err if err?
               callback null,res


Added test placeholders for @ack, @join, @leave.

--- tests/sockets.coffee
+++ tests/sockets.coffee
@@ -53,4 +53,10 @@
       t.reached 'reached2'
       t.equal 'data2', data.foo, 'bar'

-    c.emit 'shout', foo: 'bar'+    c.emit 'shout', foo: 'bar'
+
+  'server ack': ->
+
+  'server join': ->
+
+  'server leave': ->


List to add events on the model's contents

--- src/scripts/modules/media/nav/nav.coffee
+++ src/scripts/modules/media/nav/nav.coffee
@@ -32,7 +32,7 @@
       super()
       @hideProgress = options.hideProgress

-      @listenTo(@model, 'changePage removeNode moveNode add', @render)
+      @listenTo(@model, 'changePage removeNode moveNode add:contents', @render)

     events:
       'click .next': 'nextPage'


Make feedback link work in service description.

--- src/views/service-map-disclaimers.coffee
+++ src/views/service-map-disclaimers.coffee
@@ -9,6 +9,12 @@
     ServiceMapDisclaimersView: class ServiceMapDisclaimersView extends SMItemView
         template: 'description-of-service'
         className: 'content modal-dialog about'
+        events:
+            'click .uservoice-link': 'openUserVoice'
+        openUserVoice: (ev) ->
+            console.trace()
+            UserVoice = window.UserVoice || [];
+            UserVoice.push ['show', mode: 'contact']
         serializeData: ->
             lang: p13n.getLanguage()
     ServiceMapDisclaimersOverlayView: class ServiceMapDisclaimersOverlayView extends SMItemView


add trigger for touch devices for closing dropdown

--- src/coffee/responsive.breadcrumbs.coffee
+++ src/coffee/responsive.breadcrumbs.coffee
@@ -299,6 +299,28 @@
                                     .removeClass o.opts.holder.hoverClass

                                 o.opts.holderAnimation.onAfterHide(_this) if $.isFunction(o.opts.holderAnimation.onAfterHide)
+            else
+                ($ document.body).on "#{ o.hoverIn }.#{ pluginName }", (e) ->
+                    target    = $ e.target
+                    wrapperId = o.dropdownWrapper.attr 'id'
+
+                    if target.parents(".#{ o.opts.holder.class }").length is 0 and target.parents("##{ wrapperId }").length is 0
+                        o.opts.holderAnimation.onBeforeHide(_this) if $.isFunction(o.opts.holderAnimation.onBeforeHide)
+
+                        o.dropdownWrapper
+                            .stop(true, true)
+                            .fadeOut
+                                duration: o.opts.holderAnimation.hideDuration
+                                easing:   o.opts.holderAnimation.hideEasing
+                                complete: ->
+                                    o.opts.holderAnimation.onHide(_this) if $.isFunction(o.opts.holderAnimation.onHide)
+
+                                    # remove the hover class to the holder element
+                                    o.el
+                                        .children(".#{ pluginName.toLowerCase() }-holder")
+                                        .removeClass o.opts.holder.hoverClass
+
+                                    o.opts.holderAnimation.onAfterHide(_this) if $.isFunction(o.opts.holderAnimation.onAfterHide)

             # bind resize event to the window


Fix 'this' reference of RedmineLoader.fetchAllData().

--- src/coffee/services/redmineLoader.coffee
+++ src/coffee/services/redmineLoader.coffee
@@ -28,7 +28,7 @@
      @param {AccountModel[]} accounts - Account to be fetched data.
      @return {Promise[]<undefined>} To be resolved when all fetching finished.
     ###
-    fetchAllData: (accounts) ->
+    fetchAllData: (accounts) =>
       Log.debug("fetchAllData() start")
       State.isLoadingAllData = true
       for a in accounts


Button: warn developer about the missing @loader

instead of failing silently warn developer about the absence of @loader so that they can fix the calling code

--- src/components/buttons/buttonview.coffee
+++ src/components/buttons/buttonview.coffee
@@ -113,15 +113,23 @@
     @loader.hide()

   showLoader:->
+
+    unless @loader
+      return warn 'KDButtonView::showLoader is called where no loader is set'
+
     {icon, iconOnly} = @getOptions()
     @setClass "loading"
-    @loader?.show()
+    @loader.show()
     @hideIcon() if icon and not iconOnly

   hideLoader:->
+
+    unless @loader
+      return warn 'KDButtonView::hideLoader is called where no loader is set'
+
     {icon, iconOnly} = @getOptions()
     @unsetClass "loading"
-    @loader?.hide()
+    @loader.hide()
     @showIcon() if icon and not iconOnly

   disable:-> @$().attr "disabled", yes


Formatting, add key to chapters

Formatting, add key to chapters

--- src/components/learning-guide/guide.cjsx
+++ src/components/learning-guide/guide.cjsx
@@ -7,8 +7,7 @@
 Section     = require './section'
 ColorKey    = require './color-key'
 ProgressBar = require './progress-bar'
-WeakerSections = require './weaker-sections'
-PracticeButton = require '../learning-guide/practice-button'
+WeakerPanel = require './weaker-panel'
 ChapterSectionType = require './chapter-section-type'

 module.exports = React.createClass
@@ -17,36 +16,14 @@
     router: React.PropTypes.func

   propTypes:
-    courseId:  React.PropTypes.string.isRequired
-    allSections:  React.PropTypes.array.isRequired
-    chapters: React.PropTypes.arrayOf(ChapterSectionType)
-    heading:  React.PropTypes.element
-    onPractice: React.PropTypes.func
-    onReturn:   React.PropTypes.func.isRequired
+    courseId:    React.PropTypes.string.isRequired
+    allSections: React.PropTypes.array.isRequired
+    chapters:    React.PropTypes.arrayOf(ChapterSectionType)
+    heading:     React.PropTypes.element
+    onPractice:  React.PropTypes.func
+    onReturn:    React.PropTypes.func.isRequired
     weakerTitle: React.PropTypes.string.isRequired
     weakerExplanation: React.PropTypes.element
-
-  # renderWeaker: ->
-  #   # sort sections by current level of understanding
-  #   validSections = _.filter(@props.allSections, (s) -> s.clue.sample_size_interpretation isnt 'below')
-  #   sortedSections = _.sortBy(validSections, (s) -> s.clue.value )
-
-  #   # if there are less than 4 sections, use 1/2 of the available ones
-  #   weakStrongCount = Math.min(sortedSections.length / 2, 4)
-
-  #

-  #

-  #       {@props.weakerTitle}
-  #       {@props.weakerExplanation}
-  #       {if @props.onPractice
-  #         <PracticeButton title='Practice All' courseId={@props.courseId} /> }
-  #

-  #

-  #       {for section, i in _.first(sortedSections, weakStrongCount)
-  #
}
-  #

-
-  #

   render: ->
     {courseId} = @props
@@ -61,14 +38,15 @@

-        <WeakerSections {...@props} />
+
+        <WeakerPanel sections={@props.allSections} {...@props} />

         <BS.Row>

Individual Chapters

         </BS.Row>

         {for chapter, i in (@props.chapters or [])
-          <Chapter chapter={chapter} {...@props} />}
+          <Chapter key={i} chapter={chapter} {...@props} />}


Convert a command panel spec to integration-style to prepare for command preview

--- spec/extensions/command-panel-spec.coffee
+++ spec/extensions/command-panel-spec.coffee
@@ -2,13 +2,14 @@
 CommandPanel = require 'command-panel'

 describe "CommandPanel", ->
-  [rootView, editor, commandPanel] = []
+  [rootView, editor, buffer, commandPanel] = []

   beforeEach ->
     rootView = new RootView
     rootView.open(require.resolve 'fixtures/sample.js')
     rootView.enableKeymap()
     editor = rootView.getActiveEditor()
+    buffer = editor.activeEditSession.buffer
     commandPanel = requireExtension('command-panel')

   afterEach ->
@@ -109,13 +110,14 @@
       expect(rootView.find('.command-panel')).not.toExist()

   describe "when return is pressed on the panel's editor", ->
-    it "calls execute", ->
-      spyOn(commandPanel, 'execute')
-      rootView.trigger 'command-panel:toggle'
-      commandPanel.miniEditor.insertText 's/hate/love/g'
-      commandPanel.miniEditor.trigger keydownEvent('enter')
+    describe "if the command has an immediate effect", ->
+      it "executes it immediately on the current buffer", ->
+        rootView.trigger 'command-panel:toggle'
+        commandPanel.miniEditor.insertText ',s/sort/torta/g'
+        commandPanel.miniEditor.trigger keydownEvent('enter')

-      expect(commandPanel.execute).toHaveBeenCalled()
+        expect(buffer.lineForRow(0)).toMatch /quicktorta/
+        expect(buffer.lineForRow(1)).toMatch /var torta/

     describe "if the command is malformed", ->
       it "adds and removes an error class to the command panel and does not close it", ->


Update specs to check more cases

Update specs to check more cases

--- spec/markdown-table-formatter-spec.coffee
+++ spec/markdown-table-formatter-spec.coffee
@@ -2,33 +2,425 @@
 MarkdownTableFormatter = require '../lib/markdown-table-formatter'

 describe "When formatting a table", ->
-    testTable='|Left|Center|Right|\n|:-|:-:|-:|\n|1|2|3|\n'
-    expectedTable = '| Left | Center | Right |\n|:-----|:------:|------:|\n| 1    |   2    |     3 |\n'
-    beforeEach ->
-        waitsForPromise ->
-            atom.packages.activatePackage('markdown-table-formatter')
-
-    it "should load correctly", ->
-        expect(MarkdownTableFormatter).toBeDefined()
-        expect(MarkdownTableFormatter.tableFormatter).toBeDefined()
-
-    it "should match the regex", ->
-        expect(testTable).toMatch(MarkdownTableFormatter.tableFormatter.regex)
-
-    it "should properly format this table", ->
-      MarkdownTableFormatter.tableFormatter.spacePadding = 1
-      MarkdownTableFormatter.tableFormatter.keepFirstAndLastPipes = true
-      table = MarkdownTableFormatter.tableFormatter.regex.exec testTable
-      formatted=MarkdownTableFormatter.tableFormatter.formatTable table
-      expect(formatted).toEqual(expectedTable)
-
-    it "should properly work with editor", ->
-      editor=new TextEditor(atom.workspace)
-      editor.getGrammar().scopeName='source.gfm'
-      editor.setText(testTable+"asd\n\n"+testTable)
-      MarkdownTableFormatter.tableFormatter.spacePadding = 1
-      MarkdownTableFormatter.tableFormatter.keepFirstAndLastPipes = true
-      MarkdownTableFormatter.tableFormatter.autoSelectEntireDocument = true
-      MarkdownTableFormatter.tableFormatter.formatOnSave = false
-      MarkdownTableFormatter.tableFormatter.format editor
-      expect(editor.getText()).toEqual(expectedTable+"asd\n\n"+expectedTable)
+  testTables=[
+    test:
+      "|Left|Center|Right|\n"+
+      "|:-|:-:|-:|\n"+
+      "|1|2|3|\n"
+    expected:
+      "| Left | Center | Right |\n"+
+      "|:-----|:------:|------:|\n"+
+      "| 1    |   2    |     3 |\n"
+  ,
+    test:
+      "| h1 h1| h2 | h3 |\n"+
+      "|-|-|-|\n"+
+      "| data1 | data2 | data3 |\n"
+    expected:
+      "| h1 h1 | h2    | h3    |\n"+
+      "|:------|:------|:------|\n"+
+      "| data1 | data2 | data3 |\n"
+  ,
+    test:
+      "h1 | h2 | h3\n"+
+      "-|-|-\n"+
+      "data-1 | data-2 | data-3\n"
+    expected:
+      "| h1     | h2     | h3     |\n"+
+      "|:-------|:-------|:-------|\n"+
+      "| data-1 | data-2 | data-3 |\n"
+  ,
+    test:
+      "-|-|-\n"+
+      "a|b|c\n"
+    expected:
+      "|:--|:--|:--|\n"+
+      "| a | b | c |\n"
+  ,
+    test:
+      "| Header 1 | Header 2 | Header 3 |\n"+
+      "|----|---|-|\n"+
+      "| data1a | Data is longer than header | 1 |\n"+
+      "| d1b | add a cell|\n"+
+      "|lorem|ipsum|3|\n"+
+      "| | empty outside cells\n"+
+      "| skip| | 5 |\n"+
+      "| six | Morbi purus | 6 |      \n"
+    expected:
+      "| Header 1 | Header 2                   | Header 3 |\n"+
+      "|:---------|:---------------------------|:---------|\n"+
+      "| data1a   | Data is longer than header | 1        |\n"+
+      "| d1b      | add a cell                 |          |\n"+
+      "| lorem    | ipsum                      | 3        |\n"+
+      "|          | empty outside cells        |          |\n"+
+      "| skip     |                            | 5        |\n"+
+      "| six      | Morbi purus                | 6        |\n"
+  ,
+    test:
+      "|teste-3|\n"+
+      "|---|\n"+
+      "|other|\n"
+    expected:
+      "| teste-3 |\n"+
+      "|:--------|\n"+
+      "| other   |\n"
+  ,
+    test:
+      "|outro \n"+
+      "|- \n"+
+      "|teste \n"
+    expected:
+      "| outro |\n"+
+      "|:------|\n"+
+      "| teste |\n"
+  ,
+    test:
+      "outro|\n"+
+      ":-:|\n"+
+      "teste|\n"
+    expected:
+      "| outro |\n"+
+      "|:-----:|\n"+
+      "| teste |\n"
+  ,
+    test:
+      "First Header  | Second Header\n"+
+      "------------- | -------------\n"+
+      "Content Cell  | Content Cell\n"+
+      "Content Cell  | Content Cell\n"
+    expected:
+      "| First Header | Second Header |\n"+
+      "|:-------------|:--------------|\n"+
+      "| Content Cell | Content Cell  |\n"+
+      "| Content Cell | Content Cell  |\n"
+  ,
+    test:
+      "| First Header  | Second Header |\n"+
+      "| ------------- | ------------- |\n"+
+      "| Content Cell  | Content Cell  |\n"+
+      "| Content Cell  | Content Cell  |\n"
+    expected:
+      "| First Header | Second Header |\n"+
+      "|:-------------|:--------------|\n"+
+      "| Content Cell | Content Cell  |\n"+
+      "| Content Cell | Content Cell  |\n"
+  ,
+    test:
+      "| First Header  | Second Header |\n"+
+      "| ------------- | ------------- |\n"+
+      "| Content Cell  | Content Cell  |\n"+
+      "| Content Cell  | Content Cell  |\n"
+    expected:
+      "| First Header | Second Header |\n"+
+      "|:-------------|:--------------|\n"+
+      "| Content Cell | Content Cell  |\n"+
+      "| Content Cell | Content Cell  |\n"
+  ,
+    test:
+      "| Item      | Value |\n"+
+      "| --------- | -----:|\n"+
+      "| Computer  | $1600 |\n"+
+      "| Phone     |   $12 |\n"+
+      "| Pipe      |    $1 |\n"
+    expected:
+      "| Item     | Value |\n"+
+      "|:---------|------:|\n"+
+      "| Computer | $1600 |\n"+
+      "| Phone    |   $12 |\n"+
+      "| Pipe     |    $1 |\n"
+  ,
+    test:
+      "| Function name | Description                    |\n"+
+      "| ------------- | ------------------------------ |\n"+
+      "| `help()`      | Display the help window.       |\n"+
+      "| `destroy()`   | **Destroy your computer!**     |\n"
+    expected:
+      "| Function name | Description                |\n"+
+      "|:--------------|:---------------------------|\n"+
+      "| `help()`      | Display the help window.   |\n"+
+      "| `destroy()`   | **Destroy your computer!** |\n"
+  ,
+    test:
+      "| Left align | Right align | Center align |\n"+
+      "|:-----------|------------:|:------------:|\n"+
+      "| This       |        This |     This     |\n"+
+      "| column     |      column |    column    |\n"+
+      "| will       |        will |     will     |\n"+
+      "| be         |          be |      be      |\n"+
+      "| left       |       right |    center    |\n"+
+      "| aligned    |     aligned |   aligned    |\n"
+    expected:
+      "| Left align | Right align | Center align |\n"+
+      "|:-----------|------------:|:------------:|\n"+
+      "| This       |        This |     This     |\n"+
+      "| column     |      column |    column    |\n"+
+      "| will       |        will |     will     |\n"+
+      "| be         |          be |      be      |\n"+
+      "| left       |       right |    center    |\n"+
+      "| aligned    |     aligned |   aligned    |\n"
+  ,
+    test:
+      "|Left align|Right align|Center align|\n"+
+      "|:---------|----------:|:----------:|\n"+
+      "|This|This|This|\n"+
+      "|column|column|column|\n"+
+      "|will|will|will|\n"+
+      "|be|be|be|\n"+
+      "|left|right|center|\n"+
+      "|aligned|aligned|aligned|\n"
+    expected:
+      "| Left align | Right align | Center align |\n"+
+      "|:-----------|------------:|:------------:|\n"+
+      "| This       |        This |     This     |\n"+
+      "| column     |      column |    column    |\n"+
+      "| will       |        will |     will     |\n"+
+      "| be         |          be |      be      |\n"+
+      "| left       |       right |    center    |\n"+
+      "| aligned    |     aligned |   aligned    |\n"
+  ,
+    test:
+      "First Header  | Second Header\n"+
+      "------------- | -------------\n"+
+      "Content Cell  | Content Cell\n"+
+      "Content Cell  | Content Cell\n"
+    expected:
+      "| First Header | Second Header |\n"+
+      "|:-------------|:--------------|\n"+
+      "| Content Cell | Content Cell  |\n"+
+      "| Content Cell | Content Cell  |\n"
+  ,
+    test:
+      "First Header  | Second Header\n"+
+      "------------- | -------------\n"+
+      "Content Cell  | Content Cell\n"+
+      "Content Cell  | Content Cell\n"
+    expected:
+      "| First Header | Second Header |\n"+
+      "|:-------------|:--------------|\n"+
+      "| Content Cell | Content Cell  |\n"+
+      "| Content Cell | Content Cell  |\n"
+  ,
+    test:
+      "| First Header  | Second Header |\n"+
+      "| ------------- | ------------- |\n"+
+      "| Content Cell  | Content Cell  |\n"+
+      "| Content Cell  | Content Cell  |\n"
+    expected:
+      "| First Header | Second Header |\n"+
+      "|:-------------|:--------------|\n"+
+      "| Content Cell | Content Cell  |\n"+
+      "| Content Cell | Content Cell  |\n"
+  ,
+    test:
+      "| Name | Description          |\n"+
+      "| ------------- | ----------- |\n"+
+      "| Help      | Display the help window.|\n"+
+      "| Close     | Closes a window     |\n"
+    expected:
+      "| Name  | Description              |\n"+
+      "|:------|:-------------------------|\n"+
+      "| Help  | Display the help window. |\n"+
+      "| Close | Closes a window          |\n"
+  ,
+    test:
+      "| Name | Description          |\n"+
+      "| ------------- | ----------- |\n"+
+      "| Help      | ~~Display the~~ help window.|\n"+
+      "| Close     | _Closes_ a window     |\n"
+    expected:
+      "| Name  | Description                  |\n"+
+      "|:------|:-----------------------------|\n"+
+      "| Help  | ~~Display the~~ help window. |\n"+
+      "| Close | _Closes_ a window            |\n"
+  ,
+    test:
+      "| Left-Aligned  | Center Aligned  | Right Aligned |\n"+
+      "| :------------ |:---------------:| -----:|\n"+
+      "| col 3 is      | some wordy text | $1600 |\n"+
+      "| col 2 is      | centered        |   $12 |\n"+
+      "| zebra stripes | are neat        |    $1 |\n"
+    expected:
+      "| Left-Aligned  | Center Aligned  | Right Aligned |\n"+
+      "|:--------------|:---------------:|--------------:|\n"+
+      "| col 3 is      | some wordy text |         $1600 |\n"+
+      "| col 2 is      |    centered     |           $12 |\n"+
+      "| zebra stripes |    are neat     |            $1 |\n"
+  ,
+    test:
+      "| First Header | Second Header |         Third Header |  \n"+
+      "| :----------- | :-----------: | -------------------: |  \n"+
+      "| First row    |      Data     | Very long data entry |  \n"+
+      "| Second row   |    **Cell**   |               *Cell* |  \n"
+    expected:
+      "| First Header | Second Header |         Third Header |\n"+
+      "|:-------------|:-------------:|---------------------:|\n"+
+      "| First row    |     Data      | Very long data entry |\n"+
+      "| Second row   |   **Cell**    |               *Cell* |\n"
+  ,
+    test:
+      "|Coffee||\n"+
+      "|:---|:---|\n"+
+      "|Origin/Name|[prompt:Origin/Name]|\n"+
+      "|Brew method|[list:Brew Method|AeroPress|Chemex|Drip|Espresso|French Press|Pour Over|Other]|\n"+
+      "|Brewer|[prompt:Who brewed it?]|\n"+
+      "|Rating|[list:Rating|★☆☆|★★☆|★★★]|\n"+
+      "|Notes|[prompt:Notes]|\n"
+    expected:
+      "| Coffee      |                                                                                |\n"+
+      "|:------------|:-------------------------------------------------------------------------------|\n"+
+      "| Origin/Name | [prompt:Origin/Name]                                                           |\n"+
+      "| Brew method | [list:Brew Method|AeroPress|Chemex|Drip|Espresso|French Press|Pour Over|Other] |\n"+
+      "| Brewer      | [prompt:Who brewed it?]                                                        |\n"+
+      "| Rating      | [list:Rating|★☆☆|★★☆|★★★]                                                      |\n"+
+      "| Notes       | [prompt:Notes]                                                                 |\n"
+  ]
+  nonTables=[
+    "aaa|dafdas|adfas\n"+
+    "----\\t| ----\\t| ---\n"+
+    "asdf|asadf|sdfas\n"
+
+    "Here’s the original Coffee Log after decoding:\n"+
+    "dayone://post?entry=#coffee #log\n"+
+    "If you wish, you can add a leading and tailing pipe to each line of the table. Use the form that you like. As an illustration, this will give the same result as above:\n"+
+    "Note: A table need at least one pipe on each line for Markdown Extra to parse it correctly. This means that the only way to create a one-column table is to add a leading or a tailing pipe, or both of them, to each line.\n"+
+    "The align HTML attribute is applied to each cell of the concerned column.\n"+
+    "You can apply span-level formatting to the content of each cell using regular Markdown syntax:\n"+
+    "First, let’s talk about what Markdown tables look like. The full explanation can be found at either the PHP Markdown Extra or MultiMarkdown sites, but the nutshell version is this:\n"+
+    "The pipe (|) characters separate the columns. The alignment of each column is determined by the placement of the colons in the separator line. A colon at the left end only (or no colon at all) means left aligned; a colon at the right end only means right aligned; a colon at each end means centered.\n"+
+    "It’s nice to have the text version of the table spaced as I have it above with the pipes stacked on top of one another and the text aligned the way the separator line says. It’s very time consuming to type it in that way, though, so my tables tend to look more like this:\n"+
+    "which is easy to type, but hard to read and edit. So the first command I made is a Python script that takes an ugly text table and makes it pretty. Here’s the code.\n"+
+
+    " 1  #!/usr/bin/python\n"+
+    " 2  \n"+
+    " 3  import sys\n"+
+    " 4  \n"+
+    " 5  def just(string, type, n):\n"+
+    " 6      \"Justify a string to length n according to type.\"\n"+
+    " 7      \n"+
+    " 8      if type == '::':\n"+
+    " 9          return string.center(n)\n"+
+    "10      elif type == '-:':\n"+
+    "11          return string.rjust(n)\n"+
+    "12      elif type == ':-':\n"+
+    "13          return string.ljust(n)\n"+
+    "14      else:\n"+
+    "15          return string\n"+
+    "16  \n"+
+    "17  \n"+
+    "18  def normtable(text):\n"+
+    "19      \"Aligns the vertical bars in a text table.\"\n"+
+    "20      \n"+
+    "21      # Start by turning the text into a list of lines.\n"+
+    "22      lines = text.splitlines()\n"+
+    "23      rows = len(lines)\n"+
+    "24      \n"+
+    "25      # Figure out the cell formatting.\n"+
+    "26      # First, find the separator line.\n"+
+    "27      for i in range(rows):\n"+
+    "28          if set(lines[i]).issubset('|:.-'):\n"+
+    "29              formatline = lines[i]\n"+
+    "30              formatrow = i\n"+
+    "31              break\n"+
+    "32      \n"+
+    "33      # Delete the separator line from the content.\n"+
+    "34      del lines[formatrow]\n"+
+    "35      \n"+
+    "36      # Determine how each column is to be justified. \n"+
+    "37      formatline = formatline.strip('| ')\n"+
+    "38      fstrings = formatline.split('|')\n"+
+    "39      justify = []\n"+
+    "40      for cell in fstrings:\n"+
+    "41          ends = cell[0] + cell[-1]\n"+
+    "42          if ends == '::':\n"+
+    "43              justify.append('::')\n"+
+    "44          elif ends == '-:':\n"+
+    "45              justify.append('-:')\n"+
+    "46          else:\n"+
+    "47              justify.append(':-')\n"+
+    "48      \n"+
+    "49      # Assume the number of columns in the separator line is the number\n"+
+    "50      # for the entire table.\n"+
+    "51      columns = len(justify)\n"+
+    "52      \n"+
+    "53      # Extract the content into a matrix.\n"+
+    "54      content = []\n"+
+    "55      for line in lines:\n"+
+    "56          line = line.strip('| ')\n"+
+    "57          cells = line.split('|')\n"+
+    "58          # Put exactly one space at each end as \"bumpers.\"\n"+
+    "59          linecontent = [ ' ' + x.strip() + ' ' for x in cells ]\n"+
+    "60          content.append(linecontent)\n"+
+    "61      \n"+
+    "62      # Append cells to rows that don't have enough.\n"+
+    "63      rows = len(content)\n"+
+    "64      for i in range(rows):\n"+
+    "65          while len(content[i]) < columns:\n"+
+    "66              content[i].append('')\n"+
+    "67      \n"+
+    "68      # Get the width of the content in each column. The minimum width will\n"+
+    "69      # be 2, because that's the shortest length of a formatting string and\n"+
+    "70      # because that matches an empty column with \"bumper\" spaces.\n"+
+    "71      widths = [2] * columns\n"+
+    "72      for row in content:\n"+
+    "73          for i in range(columns):\n"+
+    "74              widths[i] = max(len(row[i]), widths[i])\n"+
+    "75      \n"+
+    "76      # Add whitespace to make all the columns the same width and \n"+
+    "77      formatted = []\n"+
+    "78      for row in content:\n"+
+    "79          formatted.append('|' + '|'.join([ just(s, t, n) for (s, t, n) in zip(row, justify, widths) ]) + '|')\n"+
+    "80      \n"+
+    "81      # Recreate the format line with the appropriate column widths.\n"+
+    "82      formatline = '|' + '|'.join([ s[0] + '-'*(n-2) + s[-1] for (s, n) in zip(justify, widths) ]) + '|'\n"+
+    "83      \n"+
+    "84      # Insert the formatline back into the table.\n"+
+    "85      formatted.insert(formatrow, formatline)\n"+
+    "86      \n"+
+    "87      # Return the formatted table.\n"+
+    "88      return '\n'.join(formatted)\n"+
+    "89  \n"+
+    "90          \n"+
+    "91  # Read the input, process, and print.\n"+
+    "92  unformatted = sys.stdin.read()   \n"+
+    "93  print normtable(unformatted)\n"
+    ]
+  beforeEach ->
+    waitsForPromise ->
+      atom.packages.activatePackage('markdown-table-formatter')
+
+  it "should load correctly", ->
+    expect(MarkdownTableFormatter).toBeDefined()
+    expect(MarkdownTableFormatter.tableFormatter).toBeDefined()
+
+  it "should match the regex", ->
+    for table in testTables
+      expect(table.test).toMatch(MarkdownTableFormatter.tableFormatter.regex)
+
+  it "should NOT match the regex", ->
+    for table in nonTables
+      expect(table).not.toMatch(MarkdownTableFormatter.tableFormatter.regex)
+
+  it "should properly format this table", ->
+    MarkdownTableFormatter.tableFormatter.spacePadding = 1
+    MarkdownTableFormatter.tableFormatter.keepFirstAndLastPipes = true
+    rx = MarkdownTableFormatter.tableFormatter.regex
+    for table in testTables
+      rx.lastIndex = 0
+      rxtable = rx.exec(table.test)
+      formatted=MarkdownTableFormatter.tableFormatter.formatTable rxtable
+      expect(formatted).toEqual(table.expected)
+
+  it "should properly work with editor", ->
+    editor=new TextEditor(atom.workspace)
+    editor.getGrammar().scopeName='source.gfm'
+    expected=""
+    for table in testTables
+      text = nonTables[Math.floor(Math.random()*nonTables.length)]
+      editor.setText(editor.getText()+"\n"+text+"\n"+table.test)
+      expected+="\n"+text+"\n"+table.expected
+    MarkdownTableFormatter.tableFormatter.spacePadding = 1
+    MarkdownTableFormatter.tableFormatter.keepFirstAndLastPipes = true
+    MarkdownTableFormatter.tableFormatter.autoSelectEntireDocument = true
+    MarkdownTableFormatter.tableFormatter.formatOnSave = false
+    MarkdownTableFormatter.tableFormatter.format editor
+    expect(editor.getText()).toEqual(expected)


Hide modding tab if the user is a bot

Hide modding tab if the user is a bot

--- resources/assets/coffee/react/profile-page/header.coffee
+++ resources/assets/coffee/react/profile-page/header.coffee
@@ -143,12 +143,13 @@
           href: laroute.route('users.show', user: @props.user.id)
           className: 'page-mode-v2__link page-mode-v2__link--active'
           osu.trans 'users.show.header_title.info'
-      li
-        className: 'page-mode-v2__item'
-        a
-          href: laroute.route('users.modding.index', user: @props.user.id)
-          className: 'page-mode-v2__link'
-          osu.trans 'users.beatmapset_activities.title_compact'
+      if !@props.user.is_bot
+        li
+          className: 'page-mode-v2__item'
+          a
+            href: laroute.route('users.modding.index', user: @props.user.id)
+            className: 'page-mode-v2__link'
+            osu.trans 'users.beatmapset_activities.title_compact'

   renderTitle: =>


Add missing key and shorten them

Add missing key and shorten them

--- resources/assets/coffee/react/profile-page/header.coffee
+++ resources/assets/coffee/react/profile-page/header.coffee
@@ -157,13 +157,14 @@

     if @props.user.id != currentUser.id
       blockButton = el BlockButton,
-        key: 'block-button'
+        key: 'block'
         userId: @props.user.id
         wrapperClass: 'simple-menu__item'
         modifiers: ['inline']
       items.push blockButton

       reportButton = el _exported.ReportUser,
+        key: 'report'
         user: @props.user
         wrapperClass: 'simple-menu__item'
         modifiers: ['inline']


avoid long lines in timeout check

avoid long lines in timeout check

--- services/document-updater/app/coffee/RedisManager.coffee
+++ services/document-updater/app/coffee/RedisManager.coffee
@@ -104,7 +104,9 @@
 			# check if request took too long and bail out.  only do this for
 			# get, because it is the first call in each update, so if this
 			# passes we'll assume others have a reasonable chance to succeed.
-			return callback(new Error("redis getDoc exceeded timeout")) if timeSpan > MAX_REDIS_REQUEST_LENGTH
+			if timeSpan > MAX_REDIS_REQUEST_LENGTH
+				error = new Error("redis getDoc exceeded timeout")
+				return callback(error)
 			# check sha1 hash value if present
 			if docLines? and storedHash?
 				computedHash = RedisManager._computeHash(docLines)
@@ -170,7 +172,9 @@
 					catch e
 						return callback(e)
 					timeSpan = timer.done()
-					return callback(new Error("redis getPreviousDocOps exceeded timeout")) if timeSpan > MAX_REDIS_REQUEST_LENGTH
+					if timeSpan > MAX_REDIS_REQUEST_LENGTH
+						error = new Error("redis getPreviousDocOps exceeded timeout")
+						return callback(error)
 					callback null, ops

 	DOC_OPS_TTL: 60 * minutes


update header

update header

--- lib/carte/client/views/header.cjsx
+++ lib/carte/client/views/header.cjsx
@@ -3,6 +3,7 @@
 Edit = require('./edit')
 CardModel = require('../models/card')
 ModalTrigger = require('react-bootstrap/lib/ModalTrigger')
+config = require('../../shared/config.json')

 module.exports = React.createClass
   displayName: 'Header'
@@ -25,6 +26,7 @@

+            {config.title}


:bug: Fix change event never emitted by the CSV form

--- lib/csv-editor-form-element.coffee
+++ lib/csv-editor-form-element.coffee
@@ -198,6 +198,8 @@
       else if radio = radioGroup.querySelector("[id^='#{initial}-']")
         radio.checked = true

+    @initialized = true
+
   attachedCallback: ->
     @attached = true


Fix waitsForStateToUpdate proper like.

Fix waitsForStateToUpdate proper like.

--- spec/text-editor-presenter-spec.coffee
+++ spec/text-editor-presenter-spec.coffee
@@ -90,19 +90,12 @@

     expectNoStateUpdate = (presenter, fn) -> expectStateUpdatedToBe(false, presenter, fn)

-    waitsForStateToUpdateAsync = (presenter, fn) ->
+    waitsForStateToUpdate = (presenter, fn) ->
       waitsFor "presenter state to update", 1000, (done) ->
         disposable = presenter.onDidUpdateState ->
           disposable.dispose()
           process.nextTick(done)
         fn?()
-
-    waitsForStateToUpdate = (presenter, fn) ->
-      waitsFor "presenter state to update", 1000, (done) ->
-        fn?()
-        disposable = presenter.onDidUpdateState ->
-          disposable.dispose()
-          process.nextTick(done)

     tiledContentContract = (stateFn) ->
       it "contains states for tiles that are visible on screen", ->
@@ -1481,7 +1474,7 @@
               marker2 = editor.addMarkerLayer(maintainHistory: true).markBufferRange([[4, 0], [6, 2]], invalidate: 'touch')
               decoration2 = null

-              waitsForStateToUpdateAsync presenter, -> decoration2 = editor.decorateMarker(marker2, type: 'line', class: 'b')
+              waitsForStateToUpdate presenter, -> decoration2 = editor.decorateMarker(marker2, type: 'line', class: 'b')
               runs ->
                 expect(lineStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
                 expect(lineStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a', 'b']
@@ -1489,14 +1482,14 @@
                 expect(lineStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a', 'b']
                 expect(lineStateForScreenRow(presenter, 7).decorationClasses).toBeNull()

-              waitsForStateToUpdateAsync presenter, -> editor.getBuffer().insert([5, 0], 'x')
+              waitsForStateToUpdate presenter, -> editor.getBuffer().insert([5, 0], 'x')
               runs ->
                 expect(marker1.isValid()).toBe false
                 expect(lineStateForScreenRow(presenter, 4).decorationClasses).toBeNull()
                 expect(lineStateForScreenRow(presenter, 5).decorationClasses).toBeNull()
                 expect(lineStateForScreenRow(presenter, 6).decorationClasses).toBeNull()

-              waitsForStateToUpdateAsync presenter, -> editor.undo()
+              waitsForStateToUpdate presenter, -> editor.undo()
               runs ->
                 expect(lineStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
                 expect(lineStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a', 'b']
@@ -1504,7 +1497,7 @@
                 expect(lineStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a', 'b']
                 expect(lineStateForScreenRow(presenter, 7).decorationClasses).toBeNull()

-              waitsForStateToUpdateAsync presenter, -> marker1.setBufferRange([[2, 0], [4, 2]])
+              waitsForStateToUpdate presenter, -> marker1.setBufferRange([[2, 0], [4, 2]])
               runs ->
                 expect(lineStateForScreenRow(presenter, 1).decorationClasses).toBeNull()
                 expect(lineStateForScreenRow(presenter, 2).decorationClasses).toEqual ['a']
@@ -1514,7 +1507,7 @@
                 expect(lineStateForScreenRow(presenter, 6).decorationClasses).toEqual ['b']
                 expect(lineStateForScreenRow(presenter, 7).decorationClasses).toBeNull()

-              waitsForStateToUpdateAsync presenter, -> decoration1.destroy()
+              waitsForStateToUpdate presenter, -> decoration1.destroy()
               runs ->
                 expect(lineStateForScreenRow(presenter, 2).decorationClasses).toBeNull()
                 expect(lineStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
@@ -1523,7 +1516,7 @@
                 expect(lineStateForScreenRow(presenter, 6).decorationClasses).toEqual ['b']
                 expect(lineStateForScreenRow(presenter, 7).decorationClasses).toBeNull()

-              waitsForStateToUpdateAsync presenter, -> marker2.destroy()
+              waitsForStateToUpdate presenter, -> marker2.destroy()
               runs ->
                 expect(lineStateForScreenRow(presenter, 2).decorationClasses).toBeNull()
                 expect(lineStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
@@ -1687,7 +1680,7 @@
           blockDecoration1 = addBlockDecorationBeforeScreenRow(0)
           blockDecoration2 = addBlockDecorationBeforeScreenRow(1)

-          waitsForStateToUpdateAsync presenter, ->
+          waitsForStateToUpdate presenter, ->
             presenter.setBlockDecorationDimensions(blockDecoration1, 0, 30)
             presenter.setBlockDecorationDimensions(blockDecoration2, 0, 10)

@@ -1698,7 +1691,7 @@
             expect(stateForCursor(presenter, 3)).toBeUndefined()
             expect(stateForCursor(presenter, 4)).toBeUndefined()

-          waitsForStateToUpdateAsync presenter, ->
+          waitsForStateToUpdate presenter, ->
             blockDecoration2.destroy()
             editor.setCursorBufferPosition([0, 0])
             editor.insertNewline()
@@ -2157,31 +2150,40 @@
             }

           # becoming empty
-          waitsForStateToUpdate presenter, -> editor.getSelections()[1].clear(autoscroll: false)
+          runs ->
+            editor.getSelections()[1].clear(autoscroll: false)
+          waitsForStateToUpdate presenter
           runs ->
             expectUndefinedStateForSelection(presenter, 1)

           # becoming non-empty
-          waitsForStateToUpdate presenter, -> editor.getSelections()[1].setBufferRange([[2, 4], [2, 6]], autoscroll: false)
+          runs ->
+            editor.getSelections()[1].setBufferRange([[2, 4], [2, 6]], autoscroll: false)
+          waitsForStateToUpdate presenter
           runs ->
             expectValues stateForSelectionInTile(presenter, 1, 2), {
               regions: [{top: 0, left: 4 * 10, width: 2 * 10, height: 10}]
             }

           # moving out of view
-          waitsForStateToUpdate presenter, -> editor.getSelections()[1].setBufferRange([[3, 4], [3, 6]], autoscroll: false)
+          runs ->
+            editor.getSelections()[1].setBufferRange([[3, 4], [3, 6]], autoscroll: false)
+          waitsForStateToUpdate presenter
           runs ->
             expectUndefinedStateForSelection(presenter, 1)

           # adding
-          waitsForStateToUpdate presenter, -> editor.addSelectionForBufferRange([[1, 4], [1, 6]], autoscroll: false)
+          runs -> editor.addSelectionForBufferRange([[1, 4], [1, 6]], autoscroll: false)
+          waitsForStateToUpdate presenter
           runs ->
             expectValues stateForSelectionInTile(presenter, 2, 0), {
               regions: [{top: 10, left: 4 * 10, width: 2 * 10, height: 10}]
             }

           # moving added selection
-          waitsForStateToUpdate presenter, -> editor.getSelections()[2].setBufferRange([[1, 4], [1, 8]], autoscroll: false)
+          runs ->
+            editor.getSelections()[2].setBufferRange([[1, 4], [1, 8]], autoscroll: false)
+          waitsForStateToUpdate presenter

           destroyedSelection = null
           runs ->
@@ -2216,7 +2218,7 @@

           marker = editor.markBufferPosition([2, 2])
           highlight = null
-          waitsForStateToUpdateAsync presenter, ->
+          waitsForStateToUpdate presenter, ->
             highlight = editor.decorateMarker(marker, type: 'highlight', class: 'a')
             marker.setBufferRange([[2, 2], [5, 2]])
             highlight.flash('b', 500)
@@ -2232,7 +2234,7 @@
               flashCount: 1
             }

-          waitsForStateToUpdateAsync presenter, -> highlight.flash('c', 600)
+          waitsForStateToUpdate presenter, -> highlight.flash('c', 600)
           runs ->
             expectValues stateForHighlightInTile(presenter, highlight, 2), {
               flashClass: 'c'
@@ -2479,7 +2481,7 @@
           }

           # Change range
-          waitsForStateToUpdateAsync presenter, -> marker.setBufferRange([[2, 13], [4, 6]])
+          waitsForStateToUpdate presenter, -> marker.setBufferRange([[2, 13], [4, 6]])
           runs ->
             expectValues stateForOverlay(presenter, decoration), {
               item: item
@@ -2487,12 +2489,12 @@
             }

             # Valid -> invalid
-          waitsForStateToUpdateAsync presenter, -> editor.getBuffer().insert([2, 14], 'x')
+          waitsForStateToUpdate presenter, -> editor.getBuffer().insert([2, 14], 'x')
           runs ->
             expect(stateForOverlay(presenter, decoration)).toBeUndefined()

             # Invalid -> valid
-          waitsForStateToUpdateAsync presenter, -> editor.undo()
+          waitsForStateToUpdate presenter, -> editor.undo()
           runs ->
             expectValues stateForOverlay(presenter, decoration), {
               item: item
@@ -2500,7 +2502,7 @@
             }

           # Reverse direction
-          waitsForStateToUpdateAsync presenter, -> marker.setBufferRange([[2, 13], [4, 6]], reversed: true)
+          waitsForStateToUpdate presenter, -> marker.setBufferRange([[2, 13], [4, 6]], reversed: true)
           runs ->
             expectValues stateForOverlay(presenter, decoration), {
               item: item
@@ -2508,13 +2510,13 @@
             }

           # Destroy
-          waitsForStateToUpdateAsync presenter, -> decoration.destroy()
+          waitsForStateToUpdate presenter, -> decoration.destroy()
           runs ->
             expect(stateForOverlay(presenter, decoration)).toBeUndefined()

           # Add
           decoration2 = null
-          waitsForStateToUpdateAsync presenter, -> decoration2 = editor.decorateMarker(marker, {type: 'overlay', item})
+          waitsForStateToUpdate presenter, -> decoration2 = editor.decorateMarker(marker, {type: 'overlay', item})
           runs ->
             expectValues stateForOverlay(presenter, decoration2), {
               item: item
@@ -2979,7 +2981,7 @@
               presenter.setBlockDecorationDimensions(blockDecoration5, 0, 50)
               presenter.setBlockDecorationDimensions(blockDecoration6, 0, 60)

-              waitsForStateToUpdateAsync presenter, -> presenter.setBlockDecorationDimensions(blockDecoration6, 0, 60)
+              waitsForStateToUpdate presenter, -> presenter.setBlockDecorationDimensions(blockDecoration6, 0, 60)
               runs ->
                 expect(lineNumberStateForScreenRow(presenter, 0).blockDecorationsHeight).toBe(10)
                 expect(lineNumberStateForScreenRow(presenter, 1).blockDecorationsHeight).toBe(0)
@@ -2994,7 +2996,7 @@
                 expect(lineNumberStateForScreenRow(presenter, 10).blockDecorationsHeight).toBe(0)
                 expect(lineNumberStateForScreenRow(presenter, 11).blockDecorationsHeight).toBe(60)

-              waitsForStateToUpdateAsync presenter, ->
+              waitsForStateToUpdate presenter, ->
                 blockDecoration1.getMarker().setHeadBufferPosition([1, 0])
                 blockDecoration2.getMarker().setHeadBufferPosition([5, 0])
                 blockDecoration3.getMarker().setHeadBufferPosition([9, 0])
@@ -3013,7 +3015,7 @@
                 expect(lineNumberStateForScreenRow(presenter, 10).blockDecorationsHeight).toBe(0)
                 expect(lineNumberStateForScreenRow(presenter, 11).blockDecorationsHeight).toBe(60)

-              waitsForStateToUpdateAsync presenter, ->
+              waitsForStateToUpdate presenter, ->
                 blockDecoration1.destroy()
                 blockDecoration3.destroy()

@@ -3045,14 +3047,14 @@
                 expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a', 'b']
                 expect(lineNumberStateForScreenRow(presenter, 7).decorationClasses).toBeNull()

-                waitsForStateToUpdateAsync presenter, -> editor.getBuffer().insert([5, 0], 'x')
+                waitsForStateToUpdate presenter, -> editor.getBuffer().insert([5, 0], 'x')
                 runs ->
                   expect(marker1.isValid()).toBe false
                   expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toBeNull()
                   expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toBeNull()
                   expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toBeNull()

-                waitsForStateToUpdateAsync presenter, -> editor.undo()
+                waitsForStateToUpdate presenter, -> editor.undo()
                 runs ->
                   expect(lineNumberStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
                   expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a', 'b']
@@ -3060,7 +3062,7 @@
                   expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a', 'b']
                   expect(lineNumberStateForScreenRow(presenter, 7).decorationClasses).toBeNull()

-                waitsForStateToUpdateAsync presenter, -> marker1.setBufferRange([[2, 0], [4, 2]])
+                waitsForStateToUpdate presenter, -> marker1.setBufferRange([[2, 0], [4, 2]])
                 runs ->
                   expect(lineNumberStateForScreenRow(presenter, 1).decorationClasses).toBeNull()
                   expect(lineNumberStateForScreenRow(presenter, 2).decorationClasses).toEqual ['a']
@@ -3070,7 +3072,7 @@
                   expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toEqual ['b']
                   expect(lineNumberStateForScreenRow(presenter, 7).decorationClasses).toBeNull()

-                waitsForStateToUpdateAsync presenter, -> decoration1.destroy()
+                waitsForStateToUpdate presenter, -> decoration1.destroy()
                 runs ->
                   expect(lineNumberStateForScreenRow(presenter, 2).decorationClasses).toBeNull()
                   expect(lineNumberStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
@@ -3079,7 +3081,7 @@
                   expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toEqual ['b']
                   expect(lineNumberStateForScreenRow(presenter, 7).decorationClasses).toBeNull()

-                waitsForStateToUpdateAsync presenter, -> marker2.destroy()
+                waitsForStateToUpdate presenter, -> marker2.destroy()
                 runs ->
                   expect(lineNumberStateForScreenRow(presenter, 2).decorationClasses).toBeNull()
                   expect(lineNumberStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
@@ -3297,7 +3299,7 @@
             expect(decorationState[decoration3.id].item).toBe decorationItem
             expect(decorationState[decoration3.id].class).toBe 'test-class'

-            waitsForStateToUpdateAsync presenter, -> blockDecoration1.destroy()
+            waitsForStateToUpdate presenter, -> blockDecoration1.destroy()
             runs ->
               decorationState = getContentForGutterWithName(presenter, 'test-gutter')
               expect(decorationState[decoration1.id]).toBeUndefined()
@@ -3416,7 +3418,7 @@
                 gutterName: 'test-gutter-2'
                 class: 'new-test-class'
                 item: decorationItem
-              waitsForStateToUpdateAsync presenter, -> decoration1.setProperties(newDecorationParams)
+              waitsForStateToUpdate presenter, -> decoration1.setProperties(newDecorationParams)

               runs ->
                 decorationState = getContentForGutterWithName(presenter, 'test-gutter')
@@ -3470,7 +3472,7 @@
             marker4 = editor.markBufferRange([[0, 0], [1, 0]])
             decoration4 = null

-            waitsForStateToUpdateAsync presenter, -> decoration4 = editor.decorateMarker(marker4, decorationParams)
+            waitsForStateToUpdate presenter, -> decoration4 = editor.decorateMarker(marker4, decorationParams)

             runs ->
               expectStateUpdate presenter, -> editor.addGutter({name: 'test-gutter-2'})
@@ -3537,7 +3539,7 @@
             blockDecorationsHeight = Math.round(35.8 + 100.3 + 95.2)
             expect(getStylesForGutterWithName(presenter, 'line-number').scrollHeight).toBe(linesHeight + blockDecorationsHeight)

-            waitsForStateToUpdateAsync presenter, -> blockDecoration3.destroy()
+            waitsForStateToUpdate presenter, -> blockDecoration3.destroy()
             runs ->
               blockDecorationsHeight = Math.round(35.8 + 100.3)
               expect(getStylesForGutterWithName(presenter, 'line-number').scrollHeight).toBe(linesHeight + blockDecorationsHeight)


Calculate offset correctly.

Calculate offset correctly.

I am a terrible programmer, as you can see.

--- lib/views/autocomplete_view.coffee
+++ lib/views/autocomplete_view.coffee
@@ -9,10 +9,11 @@
     @addClass('autocomplete popover-list')
     @subscribe editorView, 'dart-tools:autocomplete', =>
       @attach()
+      path = @editor.getPath()
       pos = @editor.getCursorBufferPosition()
-      path = @editor.getPath()
+      offset = @editor.buffer.characterIndexForPosition(pos)

-      @autocompleter.autocomplete path, pos.row * pos.column
+      @autocompleter.autocomplete path, offset

     @subscribe @autocompleter, 'autocomplete', (autocompleteInfo) =>
       results = autocompleteInfo.results
@@ -47,7 +48,7 @@

   getFilterKey: ->
     'completion'
-
+
   viewForItem: (item) ->
     $$ ->
       @li =>


Render: Expand #webgl1 option to query-like hashes

--- engine/render.coffee
+++ engine/render.coffee
@@ -80,7 +80,7 @@
             console.warn "There's already a GL context. Set reinstance to true
                         to change GL flags."

-        if location?.hash != '#webgl1'
+        if not location?.hash.toString().match /(#|\?|&)webgl1\b/
             gl = @canvas.getContext("webgl2", glflags)
         @context.is_webgl2 = @is_webgl2 = gl?
         if not gl


Fixed issue with using an array in figarojs

--- pull_scripts/initial_populate.coffee
+++ pull_scripts/initial_populate.coffee
@@ -65,7 +65,7 @@
           f = action for action in card.actions when action.type == 'updateCard'
           action = f
           try
-            if card.idList in process.env['donelists']
+            if card.idList in process.env['donelists'].split(',')
               finished = true
             closed_date = action.date
             dates.push new Date(closed_date)


toggles fiter mode by default

toggles fiter mode by default

--- app/assets/javascripts/records.js.coffee
+++ app/assets/javascripts/records.js.coffee
@@ -162,4 +162,5 @@
       forceFallback: false,
       uploadMultiple: false
     true
+  $.toggle_records_context 'filter'
   true


fix: app upload analytics

fix: app upload analytics

--- app/components/submission-details.coffee
+++ app/components/submission-details.coffee
@@ -16,9 +16,7 @@

   statusObserver: Ember.observer "submission.status", ->
     status = @get "submission.status"
-    console.log("first")
     if status is ENUMS.SUBMISSION_STATUS.ANALYZING
-      console.log("hello")
       analytics.feature(ENV.csb.feature.applicationUpload, ENV.csb.module.security, ENV.csb.product.appknox)


allows setting parentElement when standalone is true

--- src/scripts/daterangepicker/config.coffee
+++ src/scripts/daterangepicker/config.coffee
@@ -171,10 +171,7 @@
     $(val)

   _parentElement: (val) ->
-    if @standalone()
-      @anchorElement
-    else
-      $(val || 'body')
+    $(val || (if @standalone() then @anchorElement else 'body'))

   _callback: (val) ->
     val


Add support of SOP with variable pad height

--- src/pattern/default/common/copper.coffee
+++ src/pattern/default/common/copper.coffee
@@ -23,6 +23,10 @@
     y = -pitch * (count/4 - 0.5)
     for i in [0..(count/2 - 1)]
       pad.y = y
+      if (numbers[i] is 1) and padParams.height1?
+        pad.height = padParams.height1
+      else
+        pad.height = padParams.height
       if pins[numbers[i]]? then pattern.pad numbers[i], pad
       y += pitch

@@ -31,6 +35,10 @@
     y = -pitch * (count/4 - 0.5)
     for i in [(count/2)..(count - 1)]
       pad.y = y
+      if (numbers[i] is 1) and padParams.height1?
+        pad.height = padParams.height1
+      else
+        pad.height = padParams.height
       if pins[numbers[i]]? then pattern.pad numbers[i], pad
       y += pitch


Minor formatting.

Minor formatting.

--- src/spec/producttypegenerator.spec.coffee
+++ src/spec/producttypegenerator.spec.coffee
@@ -360,7 +360,6 @@

     expectedProductTypeDefinitions = [expectedProductTypeDefinition1, expectedProductTypeDefinition2]

-
     expect(@generator._createProductTypesDefinitions(productTypeDefinitions, attributeDefinitions, masterSKU)).toEqual expectedProductTypeDefinitions


fix a serious race condition with first-time workshet starting, which could cause a lot of confusion and frustration.

--- src/smc-util/syncstring_evaluator.coffee
+++ src/smc-util/syncstring_evaluator.coffee
@@ -51,7 +51,7 @@
         query =
             eval_inputs :
                 string_id : @string._string_id
-                time      : {'>=': misc.server_seconds_ago(30)}
+                time      : {'>=': misc.server_seconds_ago(60)}
                 input     : null
         @_inputs = @string._client.sync_table(query, undefined, 500)
         @_inputs.once('connected', =>cb())
@@ -60,7 +60,7 @@
         query =
             eval_outputs :
                 string_id : @string._string_id
-                time      : {'>=': misc.server_seconds_ago(30)}
+                time      : {'>=': misc.server_seconds_ago(60)}
                 output    : null
         @_outputs = @string._client.sync_table(query, undefined, 500)
         @_outputs.setMaxListeners(100)  # in case of many evaluations at once.
@@ -225,6 +225,15 @@
         @_inputs.on 'change', (keys) =>
             for key in keys
                 @_handle_input_change(key)
+        # CRITICAL: it's very important to handle all the inputs that may have happened just moments before
+        # this object got created.  Why, since the first one is the user trying to frickin' evaluate a cell
+        # in their worksheet to start things running... and they do that usually moments before the worksheet
+        # gets opened on the backend; if we don't do the following, then often this is missed, and great
+        # confusion and frustration ensues.
+        dbg("handle any pending evaluations")
+        @_inputs.get().forEach (val, key) =>
+            @_handle_input_change(key)
+            return

     # Runs only in the project
     _evaluate_using_sage: (input, cb) =>


hub: wrap some proxy server calls in try/except, since they are causing tracebacks that disconnect clients (bad).

--- salvus/hub.coffee
+++ salvus/hub.coffee
@@ -489,11 +489,17 @@
                 proxy = _ws_proxy_servers[t]
                 if not proxy?
                     winston.debug("websocket upgrade: not using cache")
-                    proxy = httpProxy.createProxyServer(ws:true, target:t, timeout:0)
-                    _ws_proxy_servers[t] = proxy
+                    try
+                        proxy = httpProxy.createProxyServer(ws:true, target:t, timeout:0)
+                        _ws_proxy_servers[t] = proxy
+                    catch e
+                        winston.debug("websocket upgrade -- create proxy: ERROR/DEBUG/TODO -- SOMETHING WENT WRONG -- #{e}")
                 else
                     winston.debug("websocket upgrade: using cache")
-                proxy.ws(req, socket, head)
+                try
+                    proxy.ws(req, socket, head)
+                catch e
+                    winston.debug("websocket upgrade -- proxy.ws: ERROR/DEBUG/TODO -- SOMETHING WENT WRONG -- #{e}")


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -17,7 +17,7 @@
         # Remove any previous svg
         $(@node).children("svg").remove();

-        width = @width
+        #width = @width
         height = 400
         radius = Math.min(width, height) / 2
         radiuso = 130


Agent pie: start/end angle pie transition work

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -209,7 +209,11 @@
             if myInstanceDataId = 'resolved' and i = 0
                 console.log('in tween - OLD DATA - ' + myInstanceDataId + '[' + i + ']: ',window.oldPieData[myInstanceDataId][i])
             #console.log('1. in tween - d: ',d)
-            myInterpolater = d3.interpolate(window.oldPieData[myInstanceDataId][i] ? d, d)
+            #myInterpolater = d3.interpolate(window.oldPieData[myInstanceDataId][i] ? d, d)
+            interpolateVal = (window.oldPieData[myInstanceDataId] ? 0)
+            if interpolateVal <> 0 and not window.oldPieData[myInstanceDataId].empty()
+                interpolateVal = window.oldPieData[myInstanceDataId][i]
+            myInterpolater = d3.interpolate(interpolateVal, d)
             #console.log('myInterpolater(0): ',myInterpolater(0))
             window.oldPieData[myInstanceDataId][i] = myInterpolater(0) ? d
             if myInstanceDataId = 'resolved' and i = 0


--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -179,11 +179,11 @@
             #    b = i(t)
             #    return arc b
         #)
-        path.transition().duration(750).attrTween('d', (d,i,a) ->
-            `var i`
-            i = d3.interpolate(@_current, d)
-            @_current = i(0)
+        path.transition().duration(750).attrTween('d', (d) ->
+            ip = d3.interpolate(@_current, d)
+            @_current = ip(0)
             return (t) ->
+                d.endAngle = ip(t)
                 return arc(i(t))
         )
         path.exit()


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -59,7 +59,7 @@
     else
       arcs.append("svg:text").attr("transform", (d, i) ->
         procent_val = Math.round(data[i].value/sum * 100)
-        d.innerRadius = (radius * (100-procent_val)/100) - mFontSize/2 #45  #45=max text size/2
+        d.innerRadius = (radius * (100-procent_val)/100) - mFontSize*2 #45  #45=max text size/2
         d.outerRadius = radius
         "translate(" + arc.centroid(d) + ")")
         .attr('fill', "#fff")


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -258,6 +258,7 @@
         #    .attrTween("d", that.removePieTween)
         #    .remove();
         path.exit()
+        console.log("post-exit")

         labels = labelGroup.selectAll("text")
             .data(data.sort((p1,p2) -> return p1.startAngle - p2.startAngle))
@@ -272,7 +273,7 @@
             .x((d) -> return d.x)
             .y((d) -> return d.y)
             ([]) #create an empty quadtree to hold label positions
-        console.log("labelLayout",labelLayout)
+
         maxLabelWidth = 0
         maxLabelHeight = 0


bugfix if no selector given

bugfix if no selector given

--- modules/gui/lib/ViewContext.coffee
+++ modules/gui/lib/ViewContext.coffee
@@ -101,7 +101,7 @@
                 if typeof selector is 'string'
                     $ selector, parent

-                else
+                else if selector
                     selector.apply @, [parent]

             ##
@@ -131,7 +131,7 @@

                                 @_build done, 0

-                        else if (__parentNode = _sel(@data['parent-selector'], @parent.__root)).length
+                        else if (__parentNode = _sel(@data['parent-selector'], @parent.__root))?.length
                             __parentNode.append ele
                             @_build done, 0


address a race condition in my sqlite usage.

--- salvus/sqlite.coffee
+++ salvus/sqlite.coffee
@@ -136,13 +136,15 @@
                     cb    : (err, n) =>
                         count = n; cb(err)
             (cb) =>
-                if count > 0
+                insert = () =>
                     w = @_where(opts.where)
                     s = @_set(opts.set)
                     @sql
                         query : "UPDATE #{opts.table} #{s.query} #{w.query}"
                         vals  : s.vals.concat(w.vals)
                         cb    : cb
+                if count > 0
+                    insert()
                 else
                     columns     = []
                     vals        = []
@@ -155,7 +157,13 @@
                     @sql
                         query : "INSERT INTO #{opts.table} (#{columns.join(',')}) VALUES (#{vals_holder.join(',')})"
                         vals  : vals
-                        cb    : cb
+                        cb    : (err) =>
+                            if err
+                                # We still have to try this and if it fails -- do to something else
+                                # doing an insert after the count above, then do an update instead.
+                                insert()
+                            else
+                                cb(err)
         ], (err) => opts.cb?(err))

     delete: (opts={}) ->


Fixing a typo on the endpoint

Fixing a typo on the endpoint

--- scripts/crashlytics-notifications.coffee
+++ scripts/crashlytics-notifications.coffee
@@ -22,7 +22,7 @@

 module.exports = (robot) ->

-  robot.router.post "/hubot/crashltics-notify", (req, res) ->
+  robot.router.post "/hubot/crashlytics-notify", (req, res) ->

     @failing ||= []
     query = querystring.parse(url.parse(req.url).query)


Add a test to the dimensionizer for resize

--- app/assets/components/dimensionizer/spec/dimensionizer_spec.coffee
+++ app/assets/components/dimensionizer/spec/dimensionizer_spec.coffee
@@ -28,6 +28,12 @@
       dimension_el = dimensionizer.dimensionizer
       expect(dimension_el).not.toBeNull()

+  describe '#resize', ->
+    it 'registers a resize event and changes the dimensionizer', ->
+      dimensionizer = new roos.Dimensionizer()
+      dimension_el = dimensionizer.dimensionizer
+      expect(dimension_el.text()).toEqual("#{$(window).width()}px")
+
   describe '#dispose', ->
     it 'removes the dimensionizer from the dom', ->
       dimensionizer = new roos.Dimensionizer()


version -> widget_version in remote tab url

--- app/assets/javascripts/uploadcare/widget/tabs/remote-tab.js.coffee
+++ app/assets/javascripts/uploadcare/widget/tabs/remote-tab.js.coffee
@@ -28,7 +28,7 @@
             src =
               "#{@settings.socialBase}/window/#{@windowId}/" +
               "#{service}?lang=#{locale.lang}&public_key=#{@settings.publicKey}" +
-              "&version=#{encodeURIComponent(uploadcare.version)}"
+              "&widget_version=#{encodeURIComponent(uploadcare.version)}"
             @iframe = $('<iframe>')
               .attr('src', src)
               .css


Fix some errors with the project menu when jitsi is selected as videoconference system

--- app/modules/components/project-menu/project-menu.controller.coffee
+++ app/modules/components/project-menu/project-menu.controller.coffee
@@ -95,24 +95,27 @@
         return sectionName

     _videoConferenceUrl: () ->
+        # Get base url
         if @.project.get("videoconferences") == "appear-in"
             baseUrl = "https://appear.in/"
         else if @.project.get("videoconferences") == "talky"
             baseUrl = "https://talky.io/"
         else if @.project.get("videoconferences") == "jitsi"
             baseUrl = "https://meet.jit.si/"
-            url = @.project.get("slug") + "-" + taiga.slugify(@.project.get("videoconferences_extra_data"))
-            url = url.replace(/-/g, "")
-            return baseUrl + url
         else if @.project.get("videoconferences") == "custom"
             return @.project.get("videoconferences_extra_data")
         else
             return ""

+        # Add prefix to the chat room name if exist
         if @.project.get("videoconferences_extra_data")
-            url = @.project.get("slug") + "-" + @.project.get("videoconferences_extra_data")
+            url = @.project.get("slug") + "-" + taiga.slugify(@.project.get("videoconferences_extra_data"))
         else
             url = @.project.get("slug")
+
+        # Some special cases
+        if @.project.get("videoconferences") == "jitsi"
+            url = url.replace(/-/g, "")

         return baseUrl + url


graphic labels prepending with dates if not full month data is loaded

--- app/scripts/pages/usage-dashboard/plays-report/plays-report.coffee
+++ app/scripts/pages/usage-dashboard/plays-report/plays-report.coffee
@@ -48,13 +48,16 @@
 					unless @$.months[monthMark]?
 						@$.months[monthMark] =
 							label: @$filter('date') day.date, 'MMMM, yyyy'
-							dates: day.date
+							dates: []
 							value: 0
 						nMonths++
+					@$.months[monthMark].dates.push day.date
 					@$.months[monthMark].value += parseInt day.value
-				for month in @$.months
+				for k, month of @$.months
 					if month.dates.length isnt month.dates[0].nDaysInMonth()
-						month.label = "(#{month.dates[0].getDate()} - #{}) #{month.label}"
+						firstDate = month.dates[0].getDate()
+						lastDate = month.dates[month.dates.length-1].getDate()
+						month.label = "#{firstDate} #{if firstDate isnt lastDate then '-' + lastDate else ''} #{month.label}"


Update downloadMaterials.coffee

Update downloadMaterials.coffee

--- server/cron/downloadMaterials.coffee
+++ server/cron/downloadMaterials.coffee
@@ -682,9 +682,10 @@
         for a in links
             href = a.href
             key = @getUrlKey(href)
-            regexp = href.match(/^https?:\/\/algoprog.ru(.*)$/)
-            newhref = regexp[1]
-            if newhref == null
+            match = href.match(/^https?:\/\/algoprog.ru(.*)$/)
+            if match
+              newhref = match[1]
+            else
               if not key
                  continue
               if not (key of @urlToMaterial)
@@ -697,6 +698,7 @@
             a.setAttribute("onclick", "window.goto('#{newhref}')();return false;")
         body = document.getElementsByTagName("body")[0]
         material.content = body.innerHTML
+
     correctInternalLinks: ->
         promises = []
         for id, material of @materials


Serialize package states independently of RootView

Previously, package specs needed to deactivate the root view to test
their package serialization. Now, specs can just deactivate and then
reactivate the package, relying on serialization infrastructure that's
independent of the lifecycle of the RootView.

--- spec/fuzzy-finder-spec.coffee
+++ spec/fuzzy-finder-spec.coffee
@@ -143,8 +143,8 @@
           rootView.trigger 'fuzzy-finder:toggle-buffer-finder'
           rootView.open()

-          states = rootView.serialize().packages
-          states = _.map states['fuzzy-finder'], (path, time) -> [ path, time ]
+          atom.deactivatePackage('fuzzy-finder')
+          states = _.map atom.getPackageState('fuzzy-finder'), (path, time) -> [ path, time ]
           states = _.sortBy states, (path, time) -> -time

           paths = [ 'sample-with-tabs.coffee', 'sample.txt', 'sample.js' ]


Fix spec that doesn’t test anything to actually test

--- spec/results-view-spec.coffee
+++ spec/results-view-spec.coffee
@@ -268,7 +268,7 @@
         expect(resultsView.find(".path-details").length).toBe 3

     it "renders all results when core:move-to-bottom is triggered", ->
-      workspaceElement.style.height = '300px'
+      workspaceElement.style.height = '200px'
       projectFindView.findEditor.setText('so')
       projectFindView.confirm()

@@ -277,11 +277,13 @@

       runs ->
         resultsView = getResultsView()
+        expect(resultsView.find("li").length).toBeLessThan resultsView.getPathCount() + resultsView.getMatchCount()

         expect(resultsView.prop('scrollHeight')).toBeGreaterThan resultsView.height()
         previousScrollHeight = resultsView.prop('scrollHeight')
         atom.commands.dispatch resultsView.element, 'core:move-to-bottom'
         expect(resultsView.find("li").length).toBe resultsView.getPathCount() + resultsView.getMatchCount()
+        expect(resultsView.find("li:last")).toHaveClass 'selected'

   describe "opening results", ->
     openHandler = null


tweak colors and height

tweak colors and height

--- source/javascripts/master-map.coffee
+++ source/javascripts/master-map.coffee
@@ -1,12 +1,12 @@
 render = ->
   width       = $('#master-map').width()
-  height      = 1200
+  height      = 1000
   projection  = d3.geo.albersUsa().scale(1).translate [ 0, 0 ]
   path        = d3.geo.path().projection(projection)
   fill        = d3.scale.log().clamp(true).range ['#111', '#00ff00']
   starCount   = 'total'
   zoomGroup   = null
-  colors      = ['#a634f4', '#f1f42f', '#bcf020', '#eeb016', '#ec180c']
+  colors      = ['#a634f4', '#5adacc', '#bcf020', '#eeb016', '#ec180c']

   # Main objects
   recruits = null


added missing spinner service

added missing spinner service

--- src/Session/SignInCtrl.coffee
+++ src/Session/SignInCtrl.coffee
@@ -1,5 +1,5 @@
 angular.module('session').
-controller('SignInCtrl', ($scope, session) ->
+controller('SignInCtrl', ($scope, session, usSpinnerService) ->
   $scope.signInSubmit = ->
     usSpinnerService.spin('main')
     session.signIn($scope.login, $scope.password).then(


Fix display bug on message dests, refs #554

--- client/app/components/mails_input.coffee
+++ client/app/components/mails_input.coffee
@@ -99,6 +99,10 @@
                 @setState unknown: value[1].trim()
             else
                 @setState unknown: event.target.value
+
+        onInput = (event) =>
+            input = @refs.contactInput.getDOMNode()
+            input.cols = input.value.length + 2

         className  = """
            #{@props.className or ''} form-group mail-input #{@props.id}
@@ -153,6 +157,7 @@
                         rows: 1
                         value: @state.unknown
                         onChange: onChange
+                        onInput: onInput
                         placeholder: placeholder
                         'autoComplete': 'off'
                         'spellCheck': 'off'


pass onClick through to icon

pass onClick through to icon

--- client/lanes/components/shared/Icon.cjsx
+++ client/lanes/components/shared/Icon.cjsx
@@ -34,7 +34,7 @@
             'clickable'  : @props.clickable or (@props.tooltip and @props.tooltipProps.trigger is 'click')

         icon =
-            <i style={@props.style} className={classes} />
+            <i style={@props.style} className={classes} onClick={@props.onClick} />

         if @props.tooltip
             props = _.extend({}, DEFAULT_TOOLTIP_PROPS, @props.tooltipProps)


Move Version.coffee to build

Move Version.coffee to build

So watch doesn't trigger

--- framer/Framer.coffee
+++ framer/Framer.coffee
@@ -41,7 +41,7 @@
 Framer.Extras = require "./Extras/Extras"

 # Add version info
-Framer.Version = require "./Version"
+Framer.Version = require "../build/Version"

 Framer.Loop = new Framer.AnimationLoop()
 Utils.domComplete(Framer.Loop.start)


Remove duplicates

Remove duplicates

--- client/app/Applications/WebTerm.kdapplication/AppController.coffee
+++ client/app/Applications/WebTerm.kdapplication/AppController.coffee
@@ -24,11 +24,12 @@
     preCondition :
       condition  : (options, cb)->
         {params} = options
-        (KD.getSingleton 'vmController').fetchDefaultVmName (defaultVmName)=>
+        vmController = KD.getSingleton 'vmController'
+        vmController.fetchDefaultVmName (defaultVmName)->
           vmName = params?.vmName or defaultVmName
           return cb no  unless vmName
-          KD.getSingleton("vmController").info vmName, (err, vm, info)=>
-            cb  if info?.state is 'RUNNING' then yes else no
+          vmController.info vmName, (err, vm, info)->
+            cb  info?.state is 'RUNNING'
       failure    : (options, cb)->
         KD.getSingleton("vmController").askToTurnOn 'WebTerm', cb


Added keyup validation to permission setting input.

--- client/app/MainApp/filetree/itemsubviews/setpermissionsview.coffee
+++ client/app/MainApp/filetree/itemsubviews/setpermissionsview.coffee
@@ -127,7 +127,15 @@

     @addSubView @newModeInput = new KDInputView
       cssClass : "new-mode-input"
-      keyup    : =>
+      validate      :
+        event       : "keyup"
+        rules       :
+          required  : yes
+          maxLength : 3
+        messages    :
+          required  : "File permission mode required"
+          maxLength : "New file permission mode must be 3 chars"
+      keyup         : =>
         value = Encoder.XSSEncode @newModeInput.getValue()
         if value > 99 and value < 778
           @setPermission value, =>


checking job interval

checking job interval

--- server/job.coffee
+++ server/job.coffee
@@ -384,7 +384,7 @@
     # and if legit and being hit on every available machine in the cluster, should trigger alert to start more cluster machines?
     API.log {msg:'Not running more jobs, job max memory reached', _cid: process.env.CID, _appid: process.env.APP_ID, function:'API.job.next'}
   else
-    API.log {msg:'Checking for jobs to run', ignores: {groups:API.job._ignoregroups,ids:API.job._ignoreids}, function:'API.job.next', level:'debug'}
+    API.log {msg:'Checking for jobs to run', ignores: {groups:API.job._ignoregroups,ids:API.job._ignoreids}, function:'API.job.next', level:'all'}
     match = must_not:[{term:{available:false}}] # TODO check this will get matched properly to something where available = false
     match.must_not.push({term: 'group.exact':g}) for g of API.job._ignoregroups
     match.must_not.push({term: '_id':m}) for m of API.job._ignoreids
@@ -393,6 +393,9 @@
     if p?
       if job_processing.get(p._id)? # because job_process is searched, there can be a delay before it reflects deleted jobs, so accept this extra load on ES
         API.job._ignoreids[p._id] = now + API.settings.job?.interval ? 1000
+        future = new Future()
+        Meteor.setTimeout (() -> future.return()), Math.floor((API.settings.job?.interval ? 1000)/3)
+        future.wait()
         return API.job.next()
       else if p.limit?
         lm = job_limit.get p.group


Slightly fix wording

Slightly fix wording

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

Терминология

         Выше я уже ввел некоторую специфическую терминологию, которая используется в олимпиадном программировании, на всякий случай повторю ее тут:

-
Контест — любой набор задач, каким-то образом сгруппированный и выделенный от остальных. В пределах контеста задачи обычно занумерованы (1, 2, 3, ... или A, B, C, ... и т.п.) Это может быть отдельный тур олимпиады, или набор задач к какому-нибудь занятию или на какую-нибудь тему, и т.д. В рамках наших занятий "контестом" мы называем набор задач, доступных по каждой отдельной ссылке с главной страницы курса; слева от таких ссылок обычно значок, похожий на изображение клавиатуры. Например, "Задачи на арифметические операции" — это отдельный контест.

+
Контест — любой набор задач, каким-то образом сгруппированный и выделенный от остальных. В пределах контеста задачи обычно занумерованы (1, 2, 3, ... или A, B, C, ... и т.п.) Это может быть отдельный тур олимпиады, или набор задач к какому-нибудь занятию или на какую-нибудь тему, и т.д. В рамках наших занятий "контестом" мы называем набор задач, доступных по каждой отдельной ссылке со странички соответствующего уровня. Например, "Задачи на арифметические операции" — это отдельный контест.

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

Штрафная попытка (обычно в контексте сводного рейтинга) — попытка, которая не прошла все тесты.

Тест — это отдельный тестовый пример, подготовленный авторами задачи, чтобы на нем проверять ваши программы. Обычно по каждой задаче автор задачи подготавливает от 5 до 60 тестов, и программы, которые вы сдаете на проверку, проверяются по очереди на всех этих тестах. Чтобы попытка считалась успешной, надо, чтобы программа прошла все тесты, т.е. на все тесты дала правильный ответ. (На других олимпиадах могут быть другие правила.)


Simplified logic

Simplified logic

--- core/app/backbone/views/sub_comments/sub_comments_list_view.coffee
+++ core/app/backbone/views/sub_comments/sub_comments_list_view.coffee
@@ -14,24 +14,19 @@
       content: $.trim(@state.text)
       created_by: currentUser.toJSON()

-    return @addModelError() unless model.isValid()
-
-    @props.addToCollection.add(model)
-    model.save {},
-      success: =>
-        @addModelSuccess()
-      error: =>
-        @props.addToCollection.remove(model)
-        @addModelError()
-    console.info "Submitting", @state.text
-
-  addModelError: ->
-    @setState phase: 'new'
-    FactlinkApp.NotificationCenter.error 'Your comment could not be posted, please try again.'
-
-  addModelSuccess: ->
-    @setState phase: 'new'
-    @setState text: ''
+    if model.isValid()
+      @props.addToCollection.add(model)
+      model.save {},
+        success: =>
+          @setState phase: 'new'
+          @setState text: ''
+        error: =>
+          @props.addToCollection.remove(model)
+          @setState phase: 'new'
+          FactlinkApp.NotificationCenter.error 'Your comment could not be posted, please try again.'
+    else
+      @setState phase: 'new'
+      FactlinkApp.NotificationCenter.error 'Your comment is not valid.'

   render: ->
     submit_button =


fix(vm.migrate): handle empty optional sr param

Fixes vatesfr/xo-web#2248

--- src/api/vm.coffee
+++ src/api/vm.coffee
@@ -453,7 +453,7 @@
     throw unauthorized()

   yield @getXapi(vm).migrateVm(vm._xapiId, @getXapi(host), host._xapiId, {
-    sr: @getObject(sr, 'SR')._xapiId
+    sr: sr && @getObject(sr, 'SR')._xapiId
     migrationNetworkId: migrationNetwork?._xapiId
     mapVifsNetworks: mapVifsNetworksXapi,
     mapVdisSrs: mapVdisSrsXapi,


Turbolinks compatiblity

Turbolinks compatiblity

--- vendor/assets/javascripts/modal.js.coffee
+++ vendor/assets/javascripts/modal.js.coffee
@@ -355,3 +355,7 @@
       $('.modal').attr('data-path', path)
                  .attr('data-title', title)
   this
+
+# compatibility with turbolinks
+$(document).on "page:load", ->
+  modals = {}


Added LESS support. Make sure that LESS compiles all files synchronously.

--- src/assets.coffee
+++ src/assets.coffee
@@ -10,7 +10,6 @@
 {parse}       = require 'url'

 libs = {}
-jsCompilers = Snockets.compilers

 module.exports = exports = (options = {}) ->
   return connectAssets if connectAssets
@@ -27,8 +26,6 @@
   options.buildsExpire ?= false
   options.detectChanges ?= true
   options.minifyBuilds ?= true
-  options.pathsOnly ?= false
-  jsCompilers = _.extend jsCompilers, options.jsCompilers || {}

   connectAssets = module.exports.instance = new ConnectAssets options
   connectAssets.createHelpers options
@@ -60,16 +57,15 @@
           if shortRoute[0] is '/' then shortRoute = shortRoute[1..]
       else
         shortRoute = rootDir + '/' + shortRoute
-      if ext and shortRoute.indexOf(ext, shortRoute.length - ext.length) is -1
+      if shortRoute.indexOf(ext, shortRoute.length - ext.length) is -1
         shortRoute += ext
       shortRoute

     context.css = (route) =>
       route = expandRoute route, '.css', context.css.root
       unless route.match REMOTE_PATH
-        route = @options.servePath + @compileCSS route
-      return route if @options.pathsOnly
-      "<link rel='stylesheet' href='#{route}'>"
+        route = @compileCSS route
+      "<link rel='stylesheet' href='#{@options.servePath}#{route}'>"
     context.css.root = 'css'

     context.js = (route) =>
@@ -79,10 +75,8 @@
       else if srcIsRemote
         routes = ["#{@options.src}/#{route}"]
       else
-        routes = (@options.servePath + p for p in @compileJS route)
-
-      return routes if @options.pathsOnly
-      ("<script src='#{r}'></script>" for r in routes).join '\n'
+        routes = @compileJS route
+      ("<script src='#{@options.servePath}#{r}'></script>" for r in routes).join '\n'
     context.js.root = 'js'

     context.img = (route) =>
@@ -92,10 +86,10 @@
       else if srcIsRemote
         route = "#{@options.src}/#{route}"
       else
-        route = @options.servePath + @cacheImg route
-      route
+        route = @cacheImg route
+      "#{@options.servePath}#{route}"
     context.img.root = 'img'
-
+
   # Synchronously lookup image and return the route
   cacheImg: (route) ->
     if !@options.detectChanges and @cachedRoutePaths[route]
@@ -140,12 +134,12 @@
     catch e
       console.error "Can't resolve image path: #{resolvedPath}"
     return "url('#{resolvedPath}')"
-
+
   fixCSSImagePaths: (css) ->
     regex = /url\([^\)]+\)/g
     css = css.replace regex, @resolveImgPath
     return css
-
+
   # Synchronously compile Stylus to CSS (if needed) and return the route
   compileCSS: (route) ->
     if !@options.detectChanges and @cachedRoutePaths[route]
@@ -176,7 +170,7 @@
           else
             mtime = new Date
             @compiledCss[sourcePath] = {data: new Buffer(css), mtime}
-
+
         if alreadyCached and @options.build
           filename = @buildFilenames[sourcePath]
           return "/#{filename}"
@@ -253,21 +247,79 @@
         result = js

       libs.stylus or= require 'stylus'
-      libs.bootstrap or= try require 'bootstrap-stylus' catch e then (-> ->)
       libs.nib or= try require 'nib' catch e then (-> ->)
-      libs.bootstrap or= try require 'bootstrap-stylus' catch e then (-> ->)
       options = @optionsMap[sourcePath] ?=
         filename: sourcePath
       libs.stylus(source, options)
-          .use(libs.bootstrap())
           .use(libs.nib())
-          .use(libs.bootstrap())
           .set('compress', @compress)
           .set('include css', true)
           .render callback
       result

-exports.jsCompilers = jsCompilers
+  less:
+    optionsMap:
+      compress: false
+      optimization: 1
+      silent: false
+      paths: []
+      color: true
+
+    patchLess: (less) ->
+      # Monkey patch importer of LESS to load files synchronously
+      less.Parser.importer = (file, paths, callback) ->
+        paths.unshift "."
+
+        # for (i = 0; i < paths.length; i++)
+        i = 0
+        while i < paths.length
+        # for i in [0..paths.length]
+          try
+            pathname = path.join(paths[i], file)
+            fs.statSync(pathname)
+            break
+          catch e
+            pathname = null
+
+          i++
+
+        if not pathname
+          util.error("file '" + file + "' wasn't found.\n")
+          process.exit(1)
+
+        try
+          data = fs.readFileSync(pathname, 'utf-8')
+        catch e
+          util.error(e);
+
+        new(less.Parser)(
+          paths: [path.dirname(pathname)].concat(paths)
+          filename: pathname
+        ).parse(data, (e, root) ->
+          if (e)
+            less.writeError(e)
+          callback(e, root)
+        )
+
+      less
+
+    compileSync: (sourcePath, source) ->
+      result = ""
+      libs.less or= @patchLess (require 'less')
+      options = @optionsMap
+      options.filename = sourcePath
+      options.paths = [path.dirname(sourcePath)].concat(options.paths)
+
+      callback = (err, tree) ->
+        console.log "FEhler", err if err
+        throw err if err
+        result = tree.toCSS({})
+
+      new libs.less.Parser(options).parse(source, callback)
+      result
+
+exports.jsCompilers = jsCompilers = Snockets.compilers
+
 # ## Regexes
 BEFORE_DOT = /([^.]*)(\..*)?$/

@@ -279,8 +331,8 @@
 getExt = (filePath) ->
   if(lastDotIndex = filePath.lastIndexOf '.') >= 0
     filePath[(lastDotIndex + 1)...]
-  ''
-
+  ''
+
 stripExt = (filePath) ->
   if (lastDotIndex = filePath.lastIndexOf '.') >= 0
     filePath[0...lastDotIndex]
@@ -297,7 +349,7 @@
   pathParts = path.normalize(dir).split '/'
   if path.existsSync dir
     return callback null
-
+
   mkdirRecursive pathParts.slice(0,-1).join('/'), mode, (err) ->
     return callback err if err and err.errno isnt process.EEXIST
     fs.mkdirSync dir, mode
@@ -308,4 +360,4 @@
   hash.update code
   md5Hex = hash.digest 'hex'
   ext = path.extname filename
-  "#{stripExt filename}-#{md5Hex}#{ext}"
+  "#{stripExt filename}-#{md5Hex}#{ext}"


Update badges.coffee

Update badges.coffee

Add Abaddon to the badge list

--- src/badges.coffee
+++ src/badges.coffee
@@ -7,7 +7,7 @@
 # Commands:
 #   hubot I have the <badge> badge - add/remove badges (say don't to remove)
 #   hubot I don't have any badges - remove your badges completely
-#   hubot what badges do I have? - show off your Ingress badgesyou worked hard for them!
+#   hubot what badges do I have? - show off your Ingress badges—you worked hard for them!
 #   hubot what badges does <person> have? - check another agent's badges
 #   hubot list badges - list the badges available
 #   hubot display badges - displays all the badge titles and the badges
@@ -67,6 +67,7 @@
 }

 badgeTypes = {
+  'abaddon': 1,
   'builder': 5,
   'connector': 5,
   'darsana': 1,


Remove memes that don't work anymore with the google images deprecation

--- scripts/memes.coffee
+++ scripts/memes.coffee
@@ -3,7 +3,6 @@
 # Commands:
 #   aribot - for when you want approval
 #   babers way - the most delicious way
-#   hubot capybara - like pug bomb. for capybaras.
 #   hop on - you're gonna get some hop ons
 #   it's happening - ron paul is excited
 #   linux - RMSifies you
@@ -23,10 +22,6 @@

   robot.hear /\bbucks\b/i, (msg) ->
     msg.send 'http://stuff.ebnj.net/funny/moneygun.gif'
-
-  robot.respond /capybara/i, (msg) ->
-    imageMe msg, 'capybara', (url) ->
-      msg.send url

   robot.hear /da+yum/i, (msg) ->
     msg.send 'http://i.imgur.com/vu0AC4m.gif'
@@ -58,9 +53,6 @@
   robot.hear /magic word/i, (msg) ->
     msg.send 'http://24.media.tumblr.com/8566ef54aefe210f0706b8768a62ff5c/tumblr_mldilmBIxz1r7rimro1_400.gif'

-  robot.hear /\bmountain bomb\b/i, (msg) ->
-    mountainMe msg
-
   robot.hear /pull yourself together/i, (msg) ->
     msg.send 'http://i.imgur.com/JNrl4.gif'

@@ -78,29 +70,3 @@

   robot.respond /what's for lunch/i, (msg) ->
     msg.send 'https://docs.google.com/spreadsheets/d/1M-qV4-FBUfQELJaB-gfJ8lfpJQYPlTHeRP4aJZ0Urg8'
-
-# Stolen from google-images.coffee
-imageMe = (msg, query, animated, faces, cb) ->
-  cb = animated if typeof animated == 'function'
-  cb = faces if typeof faces == 'function'
-  q = v: '1.0', rsz: '8', q: query, safe: 'active'
-  q.imgtype = 'animated' if typeof animated is 'boolean' and animated is true
-  q.imgtype = 'face' if typeof faces is 'boolean' and faces is true
-  msg.http('http://ajax.googleapis.com/ajax/services/search/images')
-    .query(q)
-    .get() (err, res, body) ->
-      images = JSON.parse(body)
-      images = images.responseData?.results
-      if images?.length > 0
-        image  = msg.random images
-        cb "#{image.unescapedUrl}#.png"
-      else
-        console.log 'imageMe failure: ' + body
-
-mountainMe = (msg) ->
-  mountains = ['annapurna', 'cho oyu', 'denali', 'eiger north face', 'everest', 'gasherbrum',
-    'k2', 'kangchenjunga', 'lhotse', 'makalu', 'matterhorn', 'mont blanc massif', 'nanga parbat',
-    'shishapangma', 'washington']
-  mountain = msg.random mountains
-  imageMe msg, "#{mountain} mountain", (url) ->
-    msg.send url


Place overlays inside the stacking context of #container.

--- core/app/backbone/modules/overlay.coffee
+++ core/app/backbone/modules/overlay.coffee
@@ -1,6 +1,6 @@
 FactlinkApp.module "Overlay", (Overlay, FactlinkApp, Backbone, Marionette, $, _) ->
   $overlay_elements = $('

')
-    .appendTo('body')
+    .appendTo('#container')

   Overlay.show = ->
     $overlay_elements.fadeIn(300)


make size optional for the multipart string VT

--- src/coffee/transformer.coffee
+++ src/coffee/transformer.coffee
@@ -236,7 +236,9 @@

       util.transformValue(valueTransformers, value, row)
       .then (transformed) ->
-        if transformed.length < size and pad
+        if not size
+          transformed
+        else if transformed.length < size and pad
           _s.pad(transformed, size, pad)
         else if transformed.length is size
           transformed


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/camera.coffee
+++ src/camera.coffee
@@ -14,7 +14,7 @@
     zoom: 1
     transform: Matrix()
     scroll: Point(0, 0)
-    maxAcceleration: 3
+    maxSpeed: Point(Infinity, Infinity)

   currentType = "centered"
   currentObject = null


add `align` property to `PointText`

add `align` property to `PointText`

--- src/drawable/PointText.coffee
+++ src/drawable/PointText.coffee
@@ -28,7 +28,11 @@
 		@_fontSize = options.fontSize
 		@font = "#{ @_fontSize }px #{ @_fontFamily }" or options.font

-		@setContextAlign @align
+	@property 'align',
+		get: -> @_align
+		set: (val) ->
+			@_align = val
+			@setContextAlign @_align

 	@property 'fontFamily',
 		get: -> @_fontFamily


add config-save

add config-save

--- views/akashic-records-checkbox-area.cjsx
+++ views/akashic-records-checkbox-area.cjsx
@@ -123,10 +123,12 @@
     {filterPaneShow} = @state
     filterPaneShow = not filterPaneShow
     @setState {filterPaneShow}
+    config.set "plugin.Akashic.#{@props.contentType}.filterPaneShow", filterPaneShow
   handleStatisticsPaneShow: ->
     {statisticsPaneShow} = @state
     statisticsPaneShow = not statisticsPaneShow
     @setState {statisticsPaneShow}
+    config.set "plugin.Akashic.#{@props.contentType}.statisticsPaneShow", statisticsPaneShow
   handleClickCheckbox: (index) ->
     {rowChooseChecked} = @props
     rowChooseChecked[index] = !rowChooseChecked[index]
@@ -184,6 +186,11 @@
     @setState
       compareArgv: compareArgv
   componentWillMount: ->
+    filterPaneShow = config.get "plugin.Akashic.#{@props.contentType}.filterPaneShow", true
+    statisticsPaneShow = config.get "plugin.Akashic.#{@props.contentType}.statisticsPaneShow", true
+    @setState
+      filterPaneShow: filterPaneShow
+      statisticsPaneShow: statisticsPaneShow
     @refreshSearchResult @state.searchArgv
     @refreshCompareResult @state.compareArgv
   componentWillReceiveProps: (nextProps)->


If we don't find legislation by type/number, return a 404.

--- api/v1/legislation.coffee
+++ api/v1/legislation.coffee
@@ -40,6 +40,11 @@

         return

+      unless legislation?
+        res.send 404
+        res.end()
+        return
+
       res.jsonp(legislation)


fix help line

fix help line

--- server/aeromq.coffee
+++ server/aeromq.coffee
@@ -13,7 +13,7 @@
 parser = new optparse.OptionParser options
 config = {}

-parser.banner = "Usage: " + process.ARGV[1] + " [options]"
+parser.banner = "Usage: aeromq [options]"

 parser.on 'port', (port) ->
     config.port = port