path = require "path"
latexmk = require "./latexmk"

module.exports =
  configDefaults:
    texPath: "$PATH:/usr/texbin"
    outputDirectory: ""
    enableShellEscape: false

  activate: ->
    @initialize()
    atom.workspaceView.command "latex:build", => @build()

  initialize: ->
    envPath = process.env.PATH
    texPath = atom.config.get("latex.texPath")
    texPathSansMarker = texPath?.replace("$PATH", "")
    if texPath? and texPath.length == texPathSansMarker.length
      console.error "latex.texPath MUST contain $PATH at some point"
      return -1

    hasAllPaths = texPathSansMarker?.split(":").every (path) ->
      envPath.indexOf(path) > -1

    process.env.PATH = texPath.replace("$PATH", envPath) unless hasAllPaths

  build: ->
    editor = atom.workspace.activePaneItem
    file = editor?.buffer.file
    unless file?
      return -1

    editor.save() if editor.isModified() # NOTE: Should this be configurable?
    args = latexmk.constructArgs(file.path)
    proc = latexmk.run(args) # TODO: Trigger an event instead of returning proc?

path = require "path"
latexmk = require "./latexmk"

module.exports =
  configDefaults:
    texPath: "$PATH:/usr/texbin"
    outputDirectory: ""
    enableShellEscape: false

  activate: ->
    @initialize()
    atom.workspaceView.command "latex:build", => @build()

  initialize: ->
    atom.config.observe "latex.texPath", => @setPath()

  setPath: ->
    texPath = atom.config.get("latex.texPath")
    unless texPath?.indexOf("$PATH") >= 0
      console.error "latex.texPath MUST contain $PATH at some point"
      return
    @envPath = process.env.PATH unless @envPath?
    process.env.PATH = texPath.replace("$PATH", @envPath)

  build: ->
    editor = atom.workspace.activePaneItem
    file = editor?.buffer.file
    unless file?
      return -1

    editor.save() if editor.isModified() # NOTE: Should this be configurable?
    args = latexmk.constructArgs(file.path)
    proc = latexmk.run(args) # TODO: Trigger an event instead of returning proc?

Simplify the code that initializes process.env.PATH


r = require('require-root')('generic-dht')
TransactionManager = r 'src/transactions/TransactionManager'

describe 'TransactionManager', ()->

  beforeEach ()->
    @responseCb = jasmine.createSpy 'responseCb'
    @errorCb = jasmine.createSpy 'errorCb'
    @manager = new TransactionManager @responseCb, @errorCb

  it 'should not create a new transaction without an address', ()->
    id = @manager.getNewTransactionId()
    expect(id).toBeNull()

  it 'should not create a new transaction without a messageType', ()->
    id = @manager.getNewTransactionId(null, null)
    expect(id).toBeNull()

r = require('require-root')('generic-dht')
TransactionManager = r 'src/transactions/TransactionManager'

describe 'TransactionManager', ()->

  commonBeforeEach = ()->
    @responseCb = jasmine.createSpy 'responseCb'
    @errorCb = jasmine.createSpy 'errorCb'
    @manager = new TransactionManager @responseCb, @errorCb
    @address = 'somewhereonthenet'
    @messageType = 'test'

  beforeEach commonBeforeEach

  it 'should not create a new transaction without an address', ()->
    id = @manager.getNewTransactionId()
    expect(id).toBeNull()

  it 'should not create a new transaction without a messageType', ()->
    id = @manager.getNewTransactionId(null, null)
    expect(id).toBeNull()

Use a common beforeEach in TransactionManager tests


window.Tahi ||= {}

class Tahi.RichEditableElement
  constructor: (@element) ->
    @instance = CKEDITOR.inline @element,
      extraPlugins: 'sharedspace'
      removePlugins: 'floatingspace,resize'
      sharedSpaces:
        top: 'toolbar'
      toolbar: [
        [ 'Format' ]
        [ 'Bold', 'Italic', 'Underline', 'Strike', 'Subscript', 'Superscript', '-', 'RemoveFormat' ]
        [ 'NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', 'Table' ]
        [ 'PasteFromWord' ],
        [ 'Link', 'Unlink' ]
        [ 'Replace', '-', 'Scayt' ]
      ]

    @placeholderText = @element.attributes['placeholder'].value
    @setPlaceholder()
    @instance.on 'focus', => @clearPlaceholder()
    @instance.on 'blur', => @setPlaceholder()

  setPlaceholder: ->
    text = $($.parseHTML(@instance.getData())).text().trim()
    if text == '' || text == @placeholderText
      @instance.element.addClass 'placeholder'
      @instance.setData @placeholderText

  clearPlaceholder: ->
    text = $($.parseHTML(@instance.getData())).text().trim()
    if text == @placeholderText
      @instance.element.removeClass 'placeholder'
      @instance.setData ''

window.Tahi ||= {}

class Tahi.RichEditableElement
  constructor: (@element) ->
    @instance = CKEDITOR.inline @element,
      extraPlugins: 'sharedspace'
      removePlugins: 'floatingspace,resize'
      sharedSpaces:
        top: 'toolbar'
      toolbar: [
        [ 'Format' ]
        [ 'Bold', 'Italic', 'Underline', 'Strike', 'Subscript', 'Superscript', '-', 'RemoveFormat' ]
        [ 'NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', 'Table' ]
        [ 'PasteFromWord' ],
        [ 'Link', 'Unlink' ]
        [ 'Replace', '-', 'Scayt' ]
      ]
      format_tags: 'h2;h3;h4;h5;h6;p;div;pre;address'

    @placeholderText = @element.attributes['placeholder'].value
    @setPlaceholder()
    @instance.on 'focus', => @clearPlaceholder()
    @instance.on 'blur', => @setPlaceholder()

  setPlaceholder: ->
    text = $($.parseHTML(@instance.getData())).text().trim()
    if text == '' || text == @placeholderText
      @instance.element.addClass 'placeholder'
      @instance.setData @placeholderText

  clearPlaceholder: ->
    text = $($.parseHTML(@instance.getData())).text().trim()
    if text == @placeholderText
      @instance.element.removeClass 'placeholder'
      @instance.setData ''

Remove h1 from CKEDITOR format_tags

Remove h1 from CKEDITOR format_tags

[Finishes #61519586]


$ ->
  # Don't run on non-video pages
  return if not $('.videos').length

  # $(window).sausage({page: '.video'
  # , content: (i, page) ->
   # '' + (i + 1) + ". " + page.find('.date').html() + ''
  # })
  every = (milliseconds, callback) => setInterval callback, milliseconds
  nearBottom = () ->
    $(window).scrollTop() > $(document).height() - $(window).height() - 600
  page = 1
  loading = false
  checkPage = every 200, () ->
    if not loading and nearBottom()
      loading = true
      $('.queue').append($('
Loading...
'))
      page++
      $.ajax(window.location.pathname + "?page=#{ page }", {
        dataType: 'script',
        complete: (resp) ->
          if resp.responseText
            $('#loading').replaceWith("
#{ resp.responseText }
")
            affixTags($(".page[data-page='#{ page }'] .video .tagEntry"))
            # $(window).sausage('draw')
            loading = false
          else
            clearInterval(checkPage)
      })

$ ->
  # Don't run on non-video pages
  return if not $('.videos').length

  # $(window).sausage({page: '.video'
  # , content: (i, page) ->
   # '' + (i + 1) + ". " + page.find('.date').html() + ''
  # })
  every = (milliseconds, callback) => setInterval callback, milliseconds
  nearBottom = () ->
    $(window).scrollTop() > $(document).height() - $(window).height() - 600
  page = 1
  loading = false
  checkPage = every 200, () ->
    if not loading and nearBottom()
      loading = true
      $('.queue').append($('
Loading...
'))
      page++
      $.ajax(window.location.pathname + (window.location.search || '?') + "&page=#{ page }", {
        dataType: 'script',
        complete: (resp) ->
          if resp.responseText
            $('#loading').replaceWith("
#{ resp.responseText }
")
            affixTags($(".page[data-page='#{ page }'] .video .tagEntry"))
            # $(window).sausage('draw')
            loading = false
          else
            clearInterval(checkPage)
            $('#loading').remove()
      })

Fix infinite scroll for pages w/ query string params already on them


Template.instancesOfProcurementForm.events
    "click .add-instance-of-procurement": -> @instancesOfProcurementCollection.insert {}

Template.instancesOfProcurementForm.helpers
    "iop": -> @instancesOfProcurementCollection.find()

Template.instancesOfProcurementForm.created = ->
    collection = @data.instancesOfProcurementCollection = new Meteor.Collection null
    collection.insert instance for instance in @data.instancesOfProcurement

Template.instanceOfProcurementForm.events
    "click .remove-instance-of-procurement": (e, template) ->
        collection = template.view.parentView.parentView.parentView.templateInstance().data.instancesOfProcurementCollection
        collection.remove @

Template.instancesOfProcurementForm.events
    "click .add-instance-of-procurement": -> @instancesOfProcurementCollection.insert {}

Template.instancesOfProcurementForm.helpers
    "iop": -> @instancesOfProcurementCollection.find()

Template.instancesOfProcurementForm.created = ->
    collection = @data.instancesOfProcurementCollection = new Meteor.Collection null
    collection.insert instance for instance in @data.instancesOfProcurement

Template.instanceOfProcurementForm.events
    "click .remove-instance-of-procurement": (e, template) ->
        collection = Template.parentData(2).instancesOfProcurementCollection
        collection.remove @

Use the official parentData method to access parents data


class @ReceiptFindMode extends CheckoutMode
  ModeSwitcher.registerEntryPoint("receipt_list", @)

  constructor: ->
    super
    @receiptList = new ReceiptList()

  enter: ->
    super
    refresh = new RefreshButton(=> Api.receipt_list().done(@onResult))
    @cfg.uiRef.body.empty()
    @cfg.uiRef.body.append(refresh.render())
    @cfg.uiRef.body.append(@receiptList.render())
    refresh.refresh()

  glyph: -> "list-alt"
  title: -> "Receipt List"
  subtitle: -> null

  onResult: (receipts) =>
    @receiptList.body.empty()
    for receipt, index in receipts
      row = @receiptList.append(receipt, index + 1)
      row.on("click", @_showReceiptFn(receipt.id))
    if receipts.length == 0
      @receiptList.no_results()

  _showReceiptFn: (receiptId) =>
    () =>
      dialog = new Dialog()
      dialog.addPositive().text("Ok")
      dialog.title.text("Receipt details")

      Api.receipt_get(id: receiptId).then(
        (data) =>
          result = Templates.render("receipt_info", receipt: data)
          dialog.body.append(result)

        (jqXHR) =>
          dialog.body.text(jqXHR.responseText)
      )
      dialog.show()

class @ReceiptFindMode extends CheckoutMode
  ModeSwitcher.registerEntryPoint("receipt_list", @)

  constructor: ->
    super
    @receiptList = new ReceiptList()

  enter: ->
    super
    refresh = new RefreshButton(=> Api.receipt_pending().done(@onResult))
    @cfg.uiRef.body.empty()
    @cfg.uiRef.body.append(refresh.render())
    @cfg.uiRef.body.append(@receiptList.render())
    refresh.refresh()

  glyph: -> "list-alt"
  title: -> "Receipt List"
  subtitle: -> null

  onResult: (receipts) =>
    @receiptList.body.empty()
    for receipt, index in receipts
      row = @receiptList.append(receipt, index + 1)
      row.on("click", @_showReceiptFn(receipt.id))
    if receipts.length == 0
      @receiptList.no_results()

  _showReceiptFn: (receiptId) =>
    () =>
      dialog = new Dialog()
      dialog.addPositive().text("Ok")
      dialog.title.text("Receipt details")

      Api.receipt_get(id: receiptId).then(
        (data) =>
          result = Templates.render("receipt_info", receipt: data)
          dialog.body.append(result)

        (jqXHR) =>
          dialog.body.text(jqXHR.responseText)
      )
      dialog.show()

Fix api function name regression.

Fix api function name regression.

From 7ec2e329da4f16da80d682bc37d9ec8e2f7ce6c7


"*":
  core:
    audioBeep: false
    closeEmptyWindows: false
    disabledPackages: [
      "language-clojure"
      "language-go"
      "language-java"
      "language-perl"
      "language-objective-c"
      "language-property-list"
      "metrics"
      "open-on-github"
      "package-generator"
      "symbols-view"
      "language-toml"
      "spell-check"
      "github"
    ]
    packagesWithKeymapsDisabled: [
      "refactor"
      "es-identifier-highlight"
      "sort-lines"
      "csslint"
    ]
    telemetryConsent: "limited"
  csslint:
    hideOnNoErrors: true
  editor:
    invisibles: {}
    showInvisibles: true
  "exception-reporting":
    userId: "2ab09d56-2ab2-2fc5-7065-f68aca373175"
  "find-and-replace": {}
  linter:
    lintOnChange: false
  "linter-jscs":
    configPath: ".jscsrc"
    onlyConfig: true
  "linter-jshint":
    disableWhenNoJshintrcFileInPath: true
  "linter-ui-default":
    showPanel: true
  "spell-check": {}
  tabs:
    enableVcsColoring: true
  "tree-view":
    hideIgnoredNames: true
    hideVcsIgnoredFiles: true
  welcome:
    showOnStartup: false
  whitespace:
    ignoreWhitespaceOnCurrentLine: false

"*":
  core:
    audioBeep: false
    closeEmptyWindows: false
    disabledPackages: [
      "language-clojure"
      "language-go"
      "language-java"
      "language-perl"
      "language-objective-c"
      "language-property-list"
      "metrics"
      "open-on-github"
      "package-generator"
      "symbols-view"
      "language-toml"
      "spell-check"
      "github"
      "language-sql"
    ]
    packagesWithKeymapsDisabled: [
      "refactor"
      "es-identifier-highlight"
      "sort-lines"
      "csslint"
    ]
    telemetryConsent: "limited"
  csslint:
    hideOnNoErrors: true
  editor:
    invisibles: {}
    showInvisibles: true
  "exception-reporting":
    userId: "2ab09d56-2ab2-2fc5-7065-f68aca373175"
  "find-and-replace": {}
  linter:
    lintOnChange: false
  "linter-jscs":
    configPath: ".jscsrc"
    onlyConfig: true
  "linter-jshint":
    disableWhenNoJshintrcFileInPath: true
  "linter-ui-default":
    showPanel: true
  "spell-check": {}
  tabs:
    enableVcsColoring: true
  "tree-view":
    hideIgnoredNames: true
    hideVcsIgnoredFiles: true
  welcome:
    showOnStartup: false
  whitespace:
    ignoreWhitespaceOnCurrentLine: false

Disable language-sql atom built-in package

I don't think I need it.


#   Copyright 2013 Matt Farmer
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.
module.exports = (jobs) ->
  require('./scrape-sessions')(jobs)
  require('./scrape-representatives')(jobs)
  require('./scrape-vote-list')(jobs)

  jobs.process 'poll', (job, done) ->
    # Queue up jobs that should run on each poll.
    jobs.create('scrape sessions').save()
    jobs.create('scrape representatives').save()
    #jobs.create('scrape committees').save()
    #jobs.create('scrape legislation').save()
    #jobs.create('scrape votes').save()

    # Schedule the next poll.
    # todo

    done()

  # Create initial poll
  jobs.create('poll').save()

#   Copyright 2013 Matt Farmer
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.
module.exports = (jobs) ->
  require('./scrape-sessions')(jobs)
  require('./scrape-representatives')(jobs)
  require('./scrape-vote-list')(jobs)

  jobs.process 'poll', (job, done) ->
    # Queue up jobs that should run on each poll.
    jobs.create('scrape sessions').save()
    #jobs.create('scrape representatives').save()
    #jobs.create('scrape committees').save()
    #jobs.create('scrape legislation').save()
    #jobs.create('scrape votes').save()

    # Schedule the next poll.
    # todo

    done()

  # Create initial poll
  jobs.create('poll').save()

Disable representatives job for now.

Disable representatives job for now.


Sprangular.service "Orders", ($http) ->

  service =
    find: (number) ->
      $http.get("/api/orders/#{number}")
        .then (response) ->
          Sprangular.extend(response.data, Sprangular.Order)

    all: ->
      $http.get("/api/orders")
        .then (response) ->
          Sprangular.extend(response.data, Sprangular.Order)

  service

Sprangular.service "Orders", ($http) ->

  service =
    find: (number) ->
      $http.get("/api/orders/#{number}")
        .then (response) ->
          service._loadOrder(response.data)

    all: ->
      $http.get("/api/orders")
        .then (response) ->
          _.map response.data, (record) ->
            service._loadOrder(record)

    _loadOrder: (data) ->
      order = new Sprangular.Order
      order.load(data)

  service

Refactor loading of order responses

Refactor loading of order responses


noflo = require 'noflo'

class RotateElement extends noflo.Component
  description: 'Change the coordinates of a DOM element'
  constructor: ->
    @element = null
    @inPorts =
      element: new noflo.Port 'object'
      percent: new noflo.Port 'number'

    @inPorts.element.on 'data', (element) =>
      @element = element
    @inPorts.percent.on 'data', (percent) =>
      if @element
        @setRotation @element, percent

  setRotation: (element, percent) ->
    degrees = 360 * percent % 360
    @setVendor element, "transform", "rotate(#{degrees}deg)"

  setVendor: (element, property, value) ->
    propertyCap = property.charAt(0).toUpperCase() + property.substr(1)
    element.style["webkit" + propertyCap] = value
    element.style["moz" + propertyCap] = value
    element.style["ms" + propertyCap] = value
    element.style["o" + propertyCap] = value
    element.style[property] = value

exports.getComponent = -> new RotateElement

noflo = require 'noflo'

class RotateElement extends noflo.Component
  description: 'Change the coordinates of a DOM element'
  constructor: ->
    @element = null
    @inPorts =
      element: new noflo.Port 'object'
      percent: new noflo.Port 'number'
      degrees: new noflo.Port 'number'

    @inPorts.element.on 'data', (element) =>
      @element = element
    @inPorts.percent.on 'data', (percent) =>
      return unless @element
      degrees = 360 * percent % 360
      @setRotation @element, degrees
    @inPorts.degrees.on 'data', (degrees) =>
      return unless @element
      @setRotation @element, degrees

  setRotation: (element, degrees) ->
    @setVendor element, "transform", "rotate(#{degrees}deg)"

  setVendor: (element, property, value) ->
    propertyCap = property.charAt(0).toUpperCase() + property.substr(1)
    element.style["webkit" + propertyCap] = value
    element.style["moz" + propertyCap] = value
    element.style["ms" + propertyCap] = value
    element.style["o" + propertyCap] = value
    element.style[property] = value

exports.getComponent = -> new RotateElement

Allow input also in degrees

Allow input also in degrees


{CompositeDisposable} = require 'atom'

AboutView = null
StatusBarView = null

aboutURI = 'atom://about'

createAboutView = (state) ->
  AboutView ?= require './about-view'
  new AboutView(state)

atom.deserializers.add
  name: 'AboutView'
  deserialize: (state) -> createAboutView(state)

module.exports = About =
  activate: ->
    @subscriptions = new CompositeDisposable
    @updateAvailable = false

    @subscriptions.add atom.workspace.addOpener (uriToOpen) ->
      createAboutView(uri: uriToOpen) if uriToOpen is aboutURI

    @subscriptions.add atom.commands.add 'atom-workspace', 'about:view-release-notes', ->
      require('shell').openExternal('https://atom.io/releases')

    if atom.isReleasedVersion()
      @subscriptions.add atom.commands.add 'atom-workspace', 'window:update-available', =>
        @updateAvailable = true
        @showStatusBarIfNeeded()

  deactivate: ->
    @subscriptions.dispose()
    @statusBarTile?.destroy()

  consumeStatusBar: (statusBar) ->
    @statusBar = statusBar
    @showStatusBarIfNeeded()

  showStatusBarIfNeeded: ->
    return unless @updateAvailable and @statusBar?

    StatusBarView ?= require './about-status-bar'

    @statusBarTile?.destroy()
    @statusBarTile = @statusBar.addRightTile(item: new StatusBarView(), priority: -100)

{CompositeDisposable} = require 'atom'

AboutView = null
StatusBarView = null

# The local storage key for the available update version.
AvailableUpdateVersion = 'about:version-available'

aboutURI = 'atom://about'

createAboutView = (state) ->
  AboutView ?= require './about-view'
  new AboutView(state)

atom.deserializers.add
  name: 'AboutView'
  deserialize: (state) -> createAboutView(state)

module.exports = About =
  activate: ->
    @subscriptions = new CompositeDisposable
    @updateAvailable = false

    @subscriptions.add atom.workspace.addOpener (uriToOpen) ->
      createAboutView(uri: uriToOpen) if uriToOpen is aboutURI

    @subscriptions.add atom.commands.add 'atom-workspace', 'about:view-release-notes', ->
      require('shell').openExternal('https://atom.io/releases')

    availableVersion = localStorage.getItem(AvailableUpdateVersion)
    localStorage.removeItem(AvailableUpdateVersion) if availableVersion is atom.getVersion()

    if atom.isReleasedVersion()
      @subscriptions.add atom.onUpdateAvailable ({releaseVersion}) =>
        localStorage.setItem(AvailableUpdateVersion, releaseVersion)
        @showStatusBarIfNeeded()

  deactivate: ->
    @subscriptions.dispose()
    @statusBarTile?.destroy()

  consumeStatusBar: (statusBar) ->
    @statusBar = statusBar
    @showStatusBarIfNeeded()

  isUpdateAvailable: ->
    availableVersion = localStorage.getItem(AvailableUpdateVersion)
    availableVersion? and availableVersion isnt atom.getVersion()

  showStatusBarIfNeeded: ->
    return unless @isUpdateAvailable() and @statusBar?

    StatusBarView ?= require './about-status-bar'

    @statusBarTile?.destroy()
    @statusBarTile = @statusBar.addRightTile(item: new StatusBarView(), priority: -100)

Store the version available for update.

Store the version available for update.


window.UpdateOnSignInOrOutMixin =
  componentDidMount: ->
    window.currentSession.user().on 'change:username', (-> @forceUpdate()), @

  componentWillUnmount: ->
    window.currentSession.user().off null, null, @

window.UpdateOnSignInOrOutMixin =
  componentDidMount: ->
    window.currentSession.user().on 'change:username', ( ->
      if @isMounted()
        @forceUpdate()
    ), @

  componentWillUnmount: ->
    window.currentSession.user().off null, null, @

Fix UpdateOnSignInOrOutMixin so it doesn't trigger forceUpdate when impossible


ModelEmitter = require './model-emitter'
Route        = require './route'

class App extends ModelEmitter
  constructor: (state = {}) ->
    super
    @_routes = {}
    @views   = []

  addRoute: (path, cb) ->
    unless (route = @_routes[path])?
      route = new Route path

    route.callbacks ?= []
    route.callbacks.push cb

    @_routes[path] = route

  # setup routing
  setupRoutes: ->
    for k, v of @routes
      if Array.isArray v
        for cb in v
          @addRoute k, cb
      else
        @addRoute k, v
    null

  dispatchRoutes: ->
    for _, route of @_routes
      if route.regexp.test location.pathname
        for cb in route.callbacks
          cb()
    null

  route: ->
    @setupRoutes()
    @dispatchRoutes()

module.exports = App

ModelEmitter = require './model-emitter'
Route        = require './route'

class App extends ModelEmitter
  prefix: ''

  constructor: (state = {}) ->
    super
    @_routes = {}
    @views   = []

  addRoute: (path, cb) ->
    unless (route = @_routes[path])?
      route = new Route @prefix + path

    route.callbacks ?= []
    route.callbacks.push cb

    @_routes[path] = route

  # setup routing
  setupRoutes: ->
    for k, v of @routes
      if Array.isArray v
        for cb in v
          @addRoute k, cb
      else
        @addRoute k, v
    null

  dispatchRoutes: ->
    for _, route of @_routes
      if route.regexp.test location.pathname
        for cb in route.callbacks
          cb()
    null

  route: ->
    @setupRoutes()
    @dispatchRoutes()

module.exports = App

Make it possible to prefix routes.

Make it possible to prefix routes.


{$$, EditorView, View} = require 'atom'
marked = require 'marked'

module.exports =

class GitIssueView extends View
  @content: ->
    @div =>
      @h1 'GitHub Issues'
      @ul()
  constructor: (opt) ->
    super
    {@issues} = opt
    issueList = @issues
      .sort (a,b) -> +a.number - +b.number
      .map (issue) ->
        "##{issue.number}: #{issue.title}
#{marked issue.body}
"
    #@setText issueList.join "\n"
    issueList.forEach (issue) =>
      @find('ul').append("
#{issue}
")
  getTitle: -> 'GitHub Issues'
  getUri: -> 'github-issues://list'

{$$, EditorView, View} = require 'atom'
marked = require 'marked'
_ = require 'lodash'

module.exports =

class GitIssueView extends View
  @content: ->
    @section 'class':'padded pane-item', =>
      @h1 'class':'section-heading', 'GitHub Issues'
      @div 'data-element':'issue-list'
  constructor: (opt={}) ->
    super
    console.log opt
    {@issues} = opt
    issueList = @issues
      .sort (a,b) -> +a.number - +b.number
      .map (issue) ->
        "
##{issue.number}: #{issue.title}

#{(marked issue.body) or ''}
"
    issueList.forEach (issue) =>
      @find('[data-element="issue-list"]').append("
#{issue}
")

  getTitle: -> 'GitHub Issues'
  getUri: -> 'github-issues://list'

Improve look of issues display

Improve look of issues display


kd                 = require 'kd'
globals            = require 'globals'
KDCustomHTMLView   = kd.CustomHTMLView

EnvironmentsModal  = require 'app/environment/environmentsmodal'
SidebarMachineList = require './sidebarmachinelist'

module.exports = class SidebarOwnMachinesList extends SidebarMachineList

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

    options.title      ?= 'Your VMs'
    options.hasPlusIcon = yes
    options.cssClass    = 'my-machines'

    super options, data

  viewAppended: ->

    super

    @header.addSubView @unreadCount = new KDCustomHTMLView
      tagName  : 'cite'
      cssClass : 'count hidden'
      partial  : '1'

    kd.singletons.computeController.on 'StackRevisionChecked', (stack) =>

      return  if @isDestroyed # This needs to be investigated ~ GG
                              # We're creating instances of this multiple times
                              # but somehow we're not cleaning up them correctly

      {_revisionStatus} = stack
      if not _revisionStatus?.error? and {status} = _revisionStatus
        @unreadCount.show()  if status?.code > 0

  headerClickHandler: -> new EnvironmentsModal

kd                 = require 'kd'
KDCustomHTMLView   = kd.CustomHTMLView
SidebarMachineList = require './sidebarmachinelist'
curryIn            = require 'app/util/curryIn'

module.exports = class SidebarOwnMachinesList extends SidebarMachineList

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

    options.title      ?= 'Your VMs'
    options.hasPlusIcon = yes

    curryIn options, cssClass: 'my-machines'

    super options, data

  viewAppended: ->

    super

    @header.addSubView @unreadCount = new KDCustomHTMLView
      tagName  : 'cite'
      cssClass : 'count hidden'
      partial  : '1'

Refactor sidebar own machine list class definition


fs = require 'fs'
path = require 'path'

# Finds all test files in the given directory.
class TestsFinder

  constructor: (@directory) ->

  # Returns the name of all files within the given directory that contain tests.
  files: ->
    @_search_directory @directory, []

  # Adds all test files in the current directory and its subdirectories
  # to the given files array.
  _search_directory: (dir, files) ->
    for file in fs.readdirSync(dir)
      continue if @_is_hidden file
      filePath = path.resolve "#{dir}/#{file}"
      stat = fs.statSync filePath
      if stat.isFile()
        continue unless @_is_test_file file
        files.push filePath
      else if stat.isDirectory()
        @_search_directory filePath, files
    files

  # Returns whether the given filesystem object is hidden.
  _is_hidden: (file) ->
    file[0] == '.'

  # Returns whether the file with the given filename contains unit tests.
  _is_test_file: (file) ->

    # Ignore non-test code files.
    return false unless file.match /_(?:test|spec)\.[^.]+$/

    # Ignore non-code files.
    return false unless file.match /(js|coffee)$/

    true

module.exports = TestsFinder

fs = require 'fs'
path = require 'path'

# Finds all test files in the given directory.
class TestsFinder

  constructor: (@directory) ->

  # Returns the name of all files within the given directory that contain tests.
  files: ->
    @_search_directory @directory, []

  # Adds all test files in the current directory and its subdirectories
  # to the given files array.
  _search_directory: (dir, files) ->
    for file in fs.readdirSync(dir)
      continue if @_is_hidden file
      filePath = path.resolve "#{dir}/#{file}"
      stat = fs.statSync filePath
      if stat.isFile()
        continue unless @_is_test_file file
        files.push filePath
      else if stat.isDirectory()
        @_search_directory filePath, files
    files.sort()

  # Returns whether the given filesystem object is hidden.
  _is_hidden: (file) ->
    file[0] == '.'

  # Returns whether the file with the given filename contains unit tests.
  _is_test_file: (file) ->

    # Ignore non-test code files.
    return false unless file.match /_(?:test|spec)\.[^.]+$/

    # Ignore non-code files.
    return false unless file.match /(js|coffee)$/

    true

module.exports = TestsFinder

Return files sorted by name

Return files sorted by name

This makes tests less flaky.


{Range} = require('atom')
helpers = require('./helpers')

module.exports = Validate =

  linter: (linter) ->
    # set undefined to false for backward compatibility
    linter.modifiesBuffer = Boolean(linter.modifiesBuffer)
    unless linter.grammarScopes instanceof Array
      throw new Error("grammarScopes is not an Array. Got: #{linter.grammarScopes}")
    unless linter.lint?
      throw new Error("Missing linter.lint")
    if typeof linter.lint isnt 'function'
      throw new Error("linter.lint isn't a function")
    return true

  messages: (messages) ->
    unless messages instanceof Array
      throw new Error("Expected messages to be array, provided: #{typeof messages}")
    messages.forEach (result) ->
      unless result.type
        throw new Error "Missing type field on Linter Response"
      unless result.html or result.text
        throw new Error "Missing html/text field on Linter Response"
      result.range = Range.fromObject result.range if result.range?
      result.key = JSON.stringify(result)
      result.class = result.type.toLowerCase().replace(' ', '-')
      Validate.messages(result.trace) if result.trace
    return undefined

{Range} = require('atom')
helpers = require('./helpers')

module.exports = Validate =

  linter: (linter) ->
    # set undefined to false for backward compatibility
    linter.modifiesBuffer = Boolean(linter.modifiesBuffer)
    unless linter.grammarScopes instanceof Array
      throw new Error("grammarScopes is not an Array. Got: #{linter.grammarScopes}")
    if linter.lint
      throw new Error("linter.lint isn't a function on provider") if typeof linter.lint isnt 'function'
    else
      throw new Error('Missing linter.lint on provider')
    return true

  messages: (messages) ->
    unless messages instanceof Array
      throw new Error("Expected messages to be array, provided: #{typeof messages}")
    messages.forEach (result) ->
      if result.type
        throw new Error 'Invalid type field on Linter Response' if typeof result.type isnt 'string'
      else
        throw new Error 'Missing type field on Linter Response'
      if result.html
        throw new Error 'Invalid html field on Linter Response' if typeof result.html isnt 'string'
      else if result.text
        throw new Error 'Invalid text field on Linter Response' if typeof result.text isnt 'string'
      else
        throw new Error 'Missing html/text field on Linter Response'
      result.range = Range.fromObject result.range if result.range?
      result.key = JSON.stringify(result)
      result.class = result.type.toLowerCase().replace(' ', '-')
      Validate.messages(result.trace) if result.trace
    return undefined

:new: Enforce more validations on linter and messages


'.workspace':
  'ctrl-9': 'github:toggle-git-panel'
  'ctrl-(': 'github:toggle-git-panel-focus' # ctrl-shift-9

'.github-Panel':
  'cmd-enter': 'github:commit'
  'ctrl-enter': 'github:commit'

'.github-StagingView':
  'left': 'github:focus-diff-view'
  'tab': 'core:focus-next'
  'shift-tab': 'core:focus-previous'

'.github-CommitView-editor atom-text-editor:not([mini])':
  'cmd-enter': 'github:commit'
  'ctrl-enter': 'github:commit'
  'shift-tab': 'core:focus-previous'

'.github-FilePatchView':
  '/': 'github:toggle-patch-selection-mode'
  'tab': 'github:select-next-hunk'
  'shift-tab': 'github:select-previous-hunk'
  'right': 'core:move-right'

'.github-Prompt-input':
  'enter': 'core:confirm'
  'esc': 'tool-panel:unfocus'

'.workspace':
  'ctrl-9': 'github:toggle-git-panel'
  'ctrl-(': 'github:toggle-git-panel-focus' # ctrl-shift-9

'.github-Panel':
  'cmd-enter': 'github:commit'
  'ctrl-enter': 'github:commit'

'.github-StagingView':
  'tab': 'core:focus-next'
  'shift-tab': 'core:focus-previous'

'.github-CommitView-editor atom-text-editor:not([mini])':
  'cmd-enter': 'github:commit'
  'ctrl-enter': 'github:commit'
  'shift-tab': 'core:focus-previous'

'.github-FilePatchView':
  '/': 'github:toggle-patch-selection-mode'
  'tab': 'github:select-next-hunk'
  'shift-tab': 'github:select-previous-hunk'
  'right': 'core:move-right'

'.github-Prompt-input':
  'enter': 'core:confirm'
  'esc': 'tool-panel:unfocus'

Make left dive into diff view

Make left dive into diff view


###
Requires https://github.com/jaspervdj/stylish-haskell
###

"use strict"
Beautifier = require('./beautifier')

module.exports = class Crystal extends Beautifier
  name: "Crystal"
  link: "http://crystal-lang.org"
  isPreInstalled: false

  options: {
    Crystal: false
  }

  beautify: (text, language, options) ->
    # Seems that Crystal dosen't have Windows support yet.
    if @isWindows
      @Promise.reject(@commandNotFoundError(
        'crystal'
        {
        link: "http://crystal-lang.org"
        program: "crystal"
        })
      )
    else
      @run("crystal", [
        'tool',
        'format',
        tempFile = @tempFile("temp", text)
        ], {ignoreReturnCode: true})
        .then(=>
          @readFile(tempFile)
        )

###
Requires https://github.com/jaspervdj/stylish-haskell
###

"use strict"
Beautifier = require('./beautifier')

module.exports = class Crystal extends Beautifier
  name: "Crystal"
  link: "http://crystal-lang.org"
  executables: [
    {
      name: "Crystal"
      cmd: "crystal"
      homepage: "http://crystal-lang.org"
      installation: "https://crystal-lang.org/docs/installation/"
      version: {
        parse: (text) -> text.match(/Crystal (\d+\.\d+\.\d+)/)[1]
      }
    }
  ]

  options: {
    Crystal: false
  }

  beautify: (text, language, options) ->
    @exe("crystal").run([
      'tool',
      'format',
      tempFile = @tempFile("temp", text)
      ], {ignoreReturnCode: true})
      .then(=>
        @readFile(tempFile)
      )

Switch Crystal beautifier to using Executables


Backbone = require 'backbone'
sd = require('sharify').data
{ Markdown } = require 'artsy-backbone-mixins'
insane = require('insane')
_ = require 'underscore'

module.exports = class Page extends Backbone.Model

  _.extend @prototype, Markdown

  urlRoot: "#{sd.API_URL}/api/v1/page"

  sanitizedHtml: (attr) ->
    clean = insane(@get(attr))
    Markdown.mdToHtml.apply { get: -> clean }, [null, sanitize: false]

Backbone = require 'backbone'
sd = require('sharify').data
{ Markdown } = require 'artsy-backbone-mixins'
insane = require('insane')
_ = require 'underscore'

module.exports = class Page extends Backbone.Model

  _.extend @prototype, Markdown

  urlRoot: "#{sd.API_URL}/api/v1/page"

  sanitizedHtml: (attr) ->
    clean = insane(@get(attr), {
      allowedAttributes: {
        a: ["href", "name", "target"],
        img: ["src"],
        h1: ['style'],
        h2: ['style'],
        div: ['style']
      }
    })
    Markdown.mdToHtml.apply { get: -> clean }, [null, sanitize: false]

Make the markdown a bit more customizable with h1, h2, and div


###
# Copyright 2015 ppy Pty. Ltd.
#
# This file is part of osu!web. osu!web is distributed with the hope of
# attracting more community contributions to the core ecosystem of osu!.
#
# osu!web is free software: you can redistribute it and/or modify
# it under the terms of the Affero GNU General Public License version 3
# as published by the Free Software Foundation.
#
# osu!web is distributed WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with osu!web.  If not, see <http://www.gnu.org/licenses/>.
###

class @LandingHero
  constructor: ->
    $(document).on 'turbolinks:load', @landingHeroInitialize

  landingHeroInitialize: ->
    $('.js-landing-hero-slider--new.js-landing-hero-slider')
      .slick
        fade: true
        arrows: false
        autoplay: true
        adaptiveHeight: true
        dots: true
        appendDots: $('.js-landing-header')
      .removeClass 'js-landing-hero-slider--new'

    os = osu.getOS()
    others = osu.otherOS os
    $('.js-download-platform').text Lang.get('home.landing.download.for', {'os': os})
    $('.js-download-other').text Lang.get('home.landing.download.other', {'os1': others[0], 'os2': others[1]})

###
# Copyright 2015 ppy Pty. Ltd.
#
# This file is part of osu!web. osu!web is distributed with the hope of
# attracting more community contributions to the core ecosystem of osu!.
#
# osu!web is free software: you can redistribute it and/or modify
# it under the terms of the Affero GNU General Public License version 3
# as published by the Free Software Foundation.
#
# osu!web is distributed WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with osu!web.  If not, see <http://www.gnu.org/licenses/>.
###

class @LandingHero
  constructor: ->
    $(document).on 'turbolinks:load', @initialize
    $(document).on 'turbolinks:before-cache', @remove

  initialize: =>
    $('.js-landing-hero-slider')
      .slick
        fade: true
        arrows: false
        autoplay: true
        adaptiveHeight: true
        dots: true
        appendDots: $('.js-landing-header')

    os = osu.getOS()
    others = osu.otherOS os
    $('.js-download-platform').text Lang.get('home.landing.download.for', {'os': os})
    $('.js-download-other').text Lang.get('home.landing.download.other', {'os1': others[0], 'os2': others[1]})

  remove: =>
    $('.js-landing-hero-slider').slick('unslick')

Fix slider breakage after navigation

Fix slider breakage after navigation


describe 'error of incorrect pattern type when', ->

  it 'is not an Object or Array', ->
    a.throws (-> ss 'foobar'), 'Error: Given argument type String ("foobar") is incompatible with parameter type PlainObject.'

describe 'error of incorrect pattern type', ->

  it 'when arg is not a PlainObject', ->
    a.throws (-> ss 'foobar'), 'Error: Given argument type String ("foobar") is incompatible with parameter type PlainObject.'
    a.throws (-> ss /blah/), 'Error: Given argument type Object ({}) is incompatible with parameter type PlainObject.'
    a.throws (-> ss 55), 'Error: Given argument type Number (55) is incompatible with parameter type PlainObject.'
    a.throws (-> ss Object.create(null)), 'Error: Given argument type Object ({}) is incompatible with parameter type PlainObject.'

  it 'except if arg is object', ->
    a.doesNotThrow (-> ss { foo: 'bar' })

  it 'except if arg is wilcard', ->
    a.doesNotThrow (-> ss '*')

Enhance pattern-type test with more assertions on cases of invalid patterns


SpellChecker = require 'spellchecker'

module.exports =
  findMisspellings: (text) ->
    wordRegex = /(?:^|[\s\[\]])([a-zA-Z']+)(?=[\s\.\[\]:]|$)/g
    row = 0
    misspellings = []
    for line in text.split('\n')
      while matches = wordRegex.exec(line)
        word = matches[1]
        continue unless SpellChecker.isMisspelled(word)
        startColumn = matches.index + matches[0].length - word.length
        endColumn = startColumn + word.length
        misspellings.push([[row, startColumn], [row, endColumn]])
      row++
    callTaskMethod('misspellingsFound', misspellings)

SpellChecker = require 'spellchecker'

module.exports =
  findMisspellings: (text) ->
    wordRegex = /(?:^|[\s\[\]])([a-zA-Z']+)(?=[\s\.\[\]:,]|$)/g
    row = 0
    misspellings = []
    for line in text.split('\n')
      while matches = wordRegex.exec(line)
        word = matches[1]
        continue unless SpellChecker.isMisspelled(word)
        startColumn = matches.index + matches[0].length - word.length
        endColumn = startColumn + word.length
        misspellings.push([[row, startColumn], [row, endColumn]])
      row++
    callTaskMethod('misspellingsFound', misspellings)

Include comma as a word boundary

Include comma as a word boundary


{exec} = require 'child_process'
path   = require 'path'
fs     = require 'fs'

REFRESH_RATE = 500        # in msecs
RUN_PATH     = path.join __dirname, "run_test.coffee"
seen         = {}         # cache of file names
shouldRun    = true       # if a change in file was detected
canRun       = true       # if tests are running right now, don't run.

runTest = (curr, prev) ->
  if curr.mtime > prev.mtime
    console.log "[beantest] Change in file detected. Rerunning tests...\n"
    shouldRun = true

beantest = (persistent=true) ->
  if shouldRun and canRun
    exec "coffee #{RUN_PATH}", (error, stdout, stderr) ->
      console.log(if error then stderr else stdout)
      canRun = true
    shouldRun = canRun = false
  return unless persistent
  exec 'find . | grep "\\.coffee$"', (error, stdout, stderr) ->
    fileNames = stdout.split('\n')
    for fileName in fileNames
      if fileName not of seen
        seen[fileName] = true
        fs.watchFile fileName, { interval: REFRESH_RATE }, runTest
    setTimeout beantest, REFRESH_RATE

@beantest = beantest

{exec} = require 'child_process'
path   = require 'path'
fs     = require 'fs'

REFRESH_RATE = 500        # in msecs
EXECUTE_WITH = "node"
RUN_PATH     = path.join __dirname, "run_test.js"
unless path.exists RUN_PATH
  EXECUTE_WITH = "coffee"
  RUN_PATH     = path.join __dirname, "run_test.coffee"
seen         = {}         # cache of file names
shouldRun    = true       # if a change in file was detected
canRun       = true       # if tests are running right now, don't run.

runTest = (curr, prev) ->
  if curr.mtime > prev.mtime
    console.log "[beantest] Change in file detected. Rerunning tests...\n"
    shouldRun = true

beantest = (persistent=true) ->
  if shouldRun and canRun
    exec "#{EXECUTE_WITH} #{RUN_PATH}", (error, stdout, stderr) ->
      console.log(if error then stderr else stdout)
      canRun = true
    shouldRun = canRun = false
  return unless persistent
  exec 'find . | grep "\\.coffee$"', (error, stdout, stderr) ->
    fileNames = stdout.split('\n')
    for fileName in fileNames
      if fileName not of seen
        seen[fileName] = true
        fs.watchFile fileName, { interval: REFRESH_RATE }, runTest
    setTimeout beantest, REFRESH_RATE

@beantest = beantest

Use node by default; don't depend on coffee.


#= require jquery
#= require jquery_ujs
#= require foundation
#= require ace/ace
#= require ace/theme-monokai
#= require ace/mode-coffee
#= require ace/mode-handlebars
#= require ace/mode-yaml
#= require ace/mode-haml
#= require tinymce-jquery
#= require_directory .
#= require_self

$ ->
  $(document).foundation()
  collapsible.setup()
  html_edit.setup()
  yml_edit.setup()
  $('.preview-page').click (ev)->
    ev.preventDefault()
    url = $(@).attr('href')
    iframe = $('#preview-modal iframe')
    params =
      body: $('#page_body').val()
      sidebar: $('#page_sidebar').val()
      footer: $('#page_footer').val()
      layout: $('#page_layout').val()
    $.post url, page: params, (response)->
      iframe.attr 'src', url
      $('#preview-modal').foundation('reveal', 'open')
    false

#= require jquery
#= require jquery_ujs
#= require foundation
#= require ace/ace
#= require ace/theme-monokai
#= require ace/mode-coffee
#= require ace/mode-handlebars
#= require ace/mode-yaml
#= require ace/mode-haml
#= require tinymce-jquery
#= require_directory .
#= require_self

$ ->
  $(document).foundation()
  collapsible.setup()
  html_edit.setup()
  yml_edit.setup()
  $('.preview-page').click (ev)->
    ev.preventDefault()
    url = $(@).attr('href')
    iframe = $('#preview-modal iframe')
    params =
      name: $('#page_name').val()
      menu_text: $('#page_menu_text').val()
      title: $('#page_title').val()
      body: $('#page_body').val()
      sidebar: $('#page_sidebar').val()
      footer: $('#page_footer').val()
      layout: $('#page_layout').val()
    $.post url, page: params, (response)->
      iframe.attr 'src', url
      $('#preview-modal').foundation('reveal', 'open')
    false

Add other page fields to preview functionality


# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/

$(document).ready ->
  $(".new").hide()
  $(".view-option").show()
  $(".edit-option").hide()
  $(".options").each ->
    $(this).find(".new").first().show()
    return
  $(".new-option input").focus ->
    event.preventDefault()
    blankInputs = $(this).closest(".options").find(".new.new-option input").filter ->
      return $(this).val() == ""
    blankInputs.first().closest(".new").show()
    $(this).closest(".new").next().show()
    return
  $(".view-option a").click ->
    event.preventDefault()
    $(this).parent().hide()
    $(this).closest(".existing").find(".edit-option").show()
    return
  $(".delete label").click ->
    event.preventDefault()
    $(this).parent().parent().hide()
    blankInputs = $(this).closest(".options").find(".new.new-option input").filter ->
      return $(this).val() == ""
    blankInputs.closest(".new").hide()
    blankInputs.first().closest(".new").show()
    $(this).parent().parent().find("input").val("")
    return
  return

# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/

$(document).ready ->
  $(".new").hide()
  $(".view-option").show()
  $(".edit-option").hide()
  $(".options").each ->
    $(this).find(".new").first().show()
    return
  $(".new-option input").on 'focus', (event) ->
    $(this).closest(".new").next().show()
    event.preventDefault()
    return false
  $(".view-option a").on 'click', (event) ->
    $(this).parent().hide()
    $(this).closest(".existing").find(".edit-option").show()
    event.preventDefault()
    return false
  $(".delete label").on 'click', (event) ->
    $(this).parent().parent().hide()
    $(this).parent().parent().find("input").val("")
    $(this).closest(".options").find(".new").first().show()
    event.preventDefault()
    return false
  return

Fix settings javascript for Firefox compatibility


App.IndexRoute = App.AuthenticatedRoute.extend
  setupController: (controller, model)->
    stateData   = {}
    stateIds = []
    activeState = null

    # Loop thru all room states
    #   find the first joined room to load
    #   start pollers for all joined rooms
    model.forEach (item)=>
      stateId = item.get("id")
      stateIds.push(stateId)

      # This is to make sure the first active room is loaded
      if item.get("joined") == true
        activeState = item if !activeState?
        poller = new App.Poller()
        poller.store  = @store
        poller.setRoom item.get("room")
        poller.start(item.get("room").get("id"))
        stateData[stateId] = {"poller": poller}
      else
        stateData[stateId] = {}

    controller.set("stateIds", stateIds)
    controller.set("stateData", stateData)
    controller.set("activeState", activeState)
    @_super(controller, model)

  model: ->
    @store.find("room_user_state")

  deactivate: ->
    stateData = @controller.get("stateData")
    for stateId, state of stateData
      state.poller.stop() if state.poller

App.IndexRoute = App.AuthenticatedRoute.extend
  setupController: (controller, model)->
    stateData   = {}
    stateIds = []
    activeState = null

    # Loop thru all room states
    #   find the first joined room to load
    #   start pollers for all joined rooms
    model.forEach (item)=>
      stateId = item.get("id")
      stateIds.push(stateId)

      # This is to make sure the first active room is loaded
      if item.get("joined") == true
        activeState = item if !activeState?

        messagePoller = new App.MessagePoller()
        messagePoller.store  = @store
        messagePoller.setRoom item.get("room")
        messagePoller.start()

        usersPoller = new App.UsersPoller()
        usersPoller.store = @store
        usersPoller.setRoom item.get("room")
        usersPoller.start()

        stateData[stateId] =
          messagePoller: messagePoller
          usersPoller: usersPoller
      else
        stateData[stateId] = {}

    controller.set("stateIds", stateIds)
    controller.set("stateData", stateData)
    controller.set("activeState", activeState)
    @_super(controller, model)

  model: ->
    @store.find("room_user_state")

  deactivate: ->
    stateData = @controller.get("stateData")
    for stateId, state of stateData
      state.messagePoller.stop() if state.messagePoller
      state.usersPoller.stop() if state.usersPoller

Add message poller and users poller to state data


###
  Top module for Rubic (for Chrome App)
###

# Compatibility modules
require("compat/es7compat")
require("compat/bbjscompat")

# UI helpers
require("ui/bootbox-promise")

# Load main controller
require("controller/windowcontroller")  # Needed to solve circular dependency
$(->
  # Controller must be initiated after window.onload
  require("app/preferences").initCache().then(->
    require("controller/maincontroller").instance.activate()
  )
)

# Boards
require("board/peridotboard")
require("board/wakayamarbboard")
require("board/grcitrusboard")

# Engines
require("engine/mrubyengine")
require("engine/duktapeengine")
require("engine/luaengine")
require("engine/micropythonengine")

# Editors
require("editor/rubyeditor")
require("editor/mrbviewer")
require("editor/javascripteditor")
require("editor/coffeescripteditor")
require("editor/luaeditor")
require("editor/pythoneditor")
require("editor/yamleditor")

###
  Top module for Rubic (for Chrome App)
###

# Compatibility modules
require("compat/es7compat")
require("compat/bbjscompat")

# UI helpers
require("ui/bootbox-promise")

$(->
  require("app/preferences").initCache().then(->
    # WindowController should be loaded before MainController
    # to solve circular dependency
    require("controller/windowcontroller")

    # Load main controller
    require("controller/maincontroller").instance.activate()

    # Boards
    require("board/peridotboard")
    require("board/wakayamarbboard")
    require("board/grcitrusboard")

    # Engines
    require("engine/mrubyengine")
    require("engine/duktapeengine")
    require("engine/luaengine")
    require("engine/micropythonengine")

    # Editors
    require("editor/rubyeditor")
    require("editor/mrbviewer")
    require("editor/javascripteditor")
    require("editor/coffeescripteditor")
    require("editor/luaeditor")
    require("editor/pythoneditor")
    require("editor/yamleditor")
  )
)

Move class loading after window.onload to show splash quickly


# Description:
#   Hubot enforces the correct time to talk about lunch.
#
# Dependencies:
#   None
#
# Configuration:
#   None
#
# Commands:
#   lunch -> Isn't it a little early to be talking about lunch?
#
#
# Author:
#   arosenb2

module.exports = (robot) ->
  robot.hear /lunch/i, (msg) ->
    if ! isPastLunchTime() then msg.reply "Hey #{msg.message.user.name}, isn't it a bit early to start talking about lunch?"

isPastLunchTime = (time) ->
  return time.getHours() > 12

# Description:
#   Hubot enforces the correct time to talk about lunch.
#
# Dependencies:
#   None
#
# Configuration:
#   None
#
# Commands:
#   lunch -> Isn't it a little early to be talking about lunch?
#
#
# Author:
#   arosenb2

module.exports = (robot) ->
  robot.hear /lunch/i, (msg) ->
    if ! isPastLunchTime() then msg.reply "Hey #{msg.message.user.name}, isn't it a bit early to start talking about lunch?"

isPastLunchTime = ->
  return new Date().getHours() > 12

Use the date object instead of a param

Use the date object instead of a param


{CompositeDisposable} = require 'atom'

module.exports =
  configDefaults:
    directory: 'testdata'

  notationalVelocityView: null

  activate: (state) ->
    # Events subscribed to in atom's system can be easily cleaned up with a
    # CompositeDisposable
    @subscriptions = new CompositeDisposable

    # Register command that toggles this view
    @subscriptions.add atom.commands.add 'atom-workspace',
      'notational-velocity:toggle': => @createView(state).toggle()

  deactivate: ->
    @subscriptions.dispose()
    @notationalVelocityView.destroy()

  serialize: ->
    notationalVelocityViewState: @notationalVelocityView.serialize()

  createView: (state) ->
    unless @notationalVelocityView?
      NotationalVelocityView = require './notational-velocity-view'
      @notationalVelocityView = new NotationalVelocityView(state.notationalVelocityViewState)
    @notationalVelocityView

{CompositeDisposable} = require 'atom'

module.exports =
  config:
    directory:
      type: 'string'
      default: 'testdata'

  notationalVelocityView: null

  activate: (state) ->
    # Events subscribed to in atom's system can be easily cleaned up with a
    # CompositeDisposable
    @subscriptions = new CompositeDisposable

    # Register command that toggles this view
    @subscriptions.add atom.commands.add 'atom-workspace',
      'notational-velocity:toggle': => @createView(state).toggle()

  deactivate: ->
    @subscriptions.dispose()
    @notationalVelocityView.destroy()

  serialize: ->
    notationalVelocityViewState: @notationalVelocityView.serialize()

  createView: (state) ->
    unless @notationalVelocityView?
      NotationalVelocityView = require './notational-velocity-view'
      @notationalVelocityView = new NotationalVelocityView(state.notationalVelocityViewState)
    @notationalVelocityView

Update default config by specifying its type


# Gets added by gulp build.
# base64 = require 'base64binary'

uint8ArrayToInt = (arr) ->
	len = arr.length
	return 0 if len == 0

	return arr[len-1] + (uint8ArrayToInt(arr[...len-1]) << 8)

validate = (key) ->
	# See http://crypto.stackexchange.com/a/5948.
	key = key.replace(/\r?\n/g, '')
	key = /AAAAB3NzaC1yc2E[A-Za-z0-9+\/=]+/.exec(key)?[0]

	if !key?
		return 'Missing header.'

	arr = base64binary.decode(key)

	secondLength = uint8ArrayToInt(arr[11..14])
	lengthSoFar = 4 + 7 + 4 + secondLength
	thirdLength = uint8ArrayToInt(arr[lengthSoFar..lengthSoFar+3])
	lengthSoFar += 4 + thirdLength

	if lengthSoFar != arr.length
		return 'Invalid key length.'

	return true

# Gets added by gulp build.
# base64 = require 'base64binary'

uint8ArrayToInt = (arr) ->
	len = arr.length
	return 0 if len == 0

	return arr[len-1] + (uint8ArrayToInt(arr.subarray(0, len-1)) << 8)

validate = (key) ->
	# See http://crypto.stackexchange.com/a/5948.
	key = key.replace(/\r?\n/g, '')
	key = /AAAAB3NzaC1yc2E[A-Za-z0-9+\/=]+/.exec(key)?[0]

	if !key?
		return 'Missing header.'

	arr = base64binary.decode(key)

	secondLength = uint8ArrayToInt(arr.subarray(11, 15))
	lengthSoFar = 4 + 7 + 4 + secondLength
	thirdLength = uint8ArrayToInt(arr.subarray(lengthSoFar, lengthSoFar+4))
	lengthSoFar += 4 + thirdLength

	if lengthSoFar != arr.length
		return 'Invalid key length.'

	return true

Use subarrays as browsers (chrome at least) don't support slice.

Works correctly in node, also.


sanitize = (email, options = {}) ->

  email = email.trim().toLowerCase()

  return switch
    when checkGmail email then sanitizeGmail email, options
    else email

sanitizeGmail = (email, options = {}) ->

  options.excludeDots or= no
  options.excludePlus or= no

  [local, host]     = email.split '@'
  [username, label...] = local.split '+'

  label = label.join '+'

  # . have no meaning, they're being abused to create fake accounts
  username = excludeDots username  if options.excludeDots

  # + have meaning, but they ultimately belong to user before +
  # so we check for uniqueness before +, but let user save + in email
  username = excludePlus username  if options.excludePlus

  if label and not options.excludePlus
  then local = "#{username}+#{label}"
  else local = username

  return "#{local}@#{host}"

# Special rules for Gmail and Googlemail. Googlemail is for users
# in Germany, Poland and Russia.
checkGmail = (email) -> /^(.)+@(gmail|googlemail).com/.test email

excludeDots = (username) -> username.replace /\./g, ''

excludePlus = (username) ->
  parts[0]  if (parts = username.split '+').length

module.exports = sanitize

sanitize = (email, options = {}) ->

  options.excludeDots or= no
  options.excludePlus or= no

  [local, host]     = email.split '@'
  [username, label...] = local.split '+'

  label = label.join '+'

  # . have no meaning, they're being abused to create fake accounts
  username = excludeDots username  if options.excludeDots

  # + have meaning, but they ultimately belong to user before +
  # so we check for uniqueness before +, but let user save + in email
  username = excludePlus username  if options.excludePlus

  if label and not options.excludePlus
  then local = "#{username}+#{label}"
  else local = username

  return "#{local}@#{host}"

# Special rules for Gmail and Googlemail. Googlemail is for users
# in Germany, Poland and Russia.
checkGmail = (email) -> /^(.)+@(gmail|googlemail).com/.test email

checkKoding = (email) -> /^(.)+@koding.com/.test email

excludeDots = (username) -> username.replace /\./g, ''

excludePlus = (username) ->
  parts[0]  if (parts = username.split '+').length

module.exports = (email, options = {}) ->

  email = email.trim().toLowerCase()

  if checkKoding email
  then email
  else sanitize email, options

Apply email sanitization procedure to emails from all domains except koding.com


# Initializers
$ ->
  # jQuery datepickers (also evaluates dynamically added HTML)
  $(document).on 'focus', '.datepicker:not(.hasDatepicker)', ->
    defaults = dateFormat: 'yy-mm-dd'
    options = $(@).data 'datepicker-options'
    $(@).datepicker $.extend(defaults, options)

  # Clear Filters button
  $('.clear_filters_btn').click ->
    window.location.search = ''

  # Batch Actions dropdown
  $('.dropdown_button').popover()

  # Filter form: don't send any inputs that are empty
  $('.filter_form').submit ->
    $(@).find(':input').filter(-> @value is '').prop 'disabled', true

  # Filter form: for filters that let you choose the query method from
  # a dropdown, apply that choice to the filter input field.
  $('.filter_form_field.select_and_search select').change ->
    $(@).siblings('input').prop name: "q[#{@value}]"

  # In order for index scopes to overflow properly onto the next line, we have
  # to manually set its width based on the width of the batch action button.
  if (batch_actions_selector = $('.table_tools .batch_actions_selector')).length
    batch_actions_selector.next().css
      width: "calc(100% - 10px - #{batch_actions_selector.outerWidth()}px)"
      float: 'right'

# Initializers
$ ->
  # jQuery datepickers (also evaluates dynamically added HTML)
  $(document).on 'focus', '.datepicker:not(.hasDatepicker)', ->
    defaults = dateFormat: 'yy-mm-dd'
    options = $(@).data 'datepicker-options'
    $(@).datepicker $.extend(defaults, options)

  # Clear Filters button
  $('.clear_filters_btn').click ->
    window.location.search = ''

  # Batch Actions dropdown
  $('.dropdown_button').popover()

  # Filter form: don't send any inputs that are empty
  $('.filter_form').submit ->
    $(@).find(':input').filter(-> @value is '').prop 'disabled', true

  # Filter form: for filters that let you choose the query method from
  # a dropdown, apply that choice to the filter input field.
  $('.filter_form_field.select_and_search select').change ->
    $(@).siblings('input').prop name: "q[#{@value}]"

  # In order for index scopes to overflow properly onto the next line, we have
  # to manually set its width based on the width of the batch action button.
  if (batch_actions_selector = $('.table_tools .batch_actions_selector')).length
    batch_actions_selector.next().css
      width: "calc(100% - 10px - #{batch_actions_selector.outerWidth()}px)"
      'float': 'right'

Fix using Javascript reserved keyword

Fix using Javascript reserved keyword


Promise = require 'bluebird'
fs = Promise.promisifyAll require 'fs'
os = require 'os'
crypto = require 'crypto'

# Parses the output of /proc/cpuinfo to find the "Serial : 710abf21" line
# or the hostname if there isn't a serial number (when run in dev mode)
# The uuid is the SHA1 hash of that value.
exports.getDeviceUuid = ->
	fs.readFileAsync('/proc/cpuinfo', 'utf8')
	.then (cpuinfo) ->
		serial = cpuinfo
			.split('\n')
			.filter((line) -> line.indexOf('Serial') isnt -1)[0]
			?.split(':')[1]
			.trim() or os.hostname()

		return crypto.createHash('sha1').update(serial, 'utf8').digest('hex')

# Parses package.json and returns resin-supervisor's version
exports.getSupervisorVersion = ->
	fs.readFileAsync '../package.json', 'utf-8'
	.then (data) ->
		obj = JSON.parse data
		return obj.version

Promise = require 'bluebird'
fs = Promise.promisifyAll require 'fs'
os = require 'os'
crypto = require 'crypto'

# Parses the output of /proc/cpuinfo to find the "Serial : 710abf21" line
# or the hostname if there isn't a serial number (when run in dev mode)
# The uuid is the SHA1 hash of that value.
exports.getDeviceUuid = ->
	fs.readFileAsync('/proc/cpuinfo', 'utf8')
	.then (cpuinfo) ->
		serial = cpuinfo
			.split('\n')
			.filter((line) -> line.indexOf('Serial') isnt -1)[0]
			?.split(':')[1]
			.trim() or os.hostname()

		return crypto.createHash('sha1').update(serial, 'utf8').digest('hex')

# Parses package.json and returns resin-supervisor's version
exports.getSupervisorVersion = ->
	fs.readFileAsync(__dirname + '/../package.json', 'utf-8')
	.then (data) ->
		obj = JSON.parse data
		return obj.version

Use __dirname to make sure the relative path is relative to where we expect.


###
  jquery.turbolinks.js ~ v0.1.0 ~ https://github.com/kossnocorp/jquery.turbolinks

  jQuery plugin for rebind events problem caused by Tubrolinks

  The MIT License

  Copyright (c) 2012 Sasha Koss
###

$ = require?('jquery') || window.jQuery

callbacks = []

ready = ->
  callback() for callback in callbacks

$(ready)

$.fn.ready = (callback) ->
  callbacks.push(callback)

$(document).on('page:change', ready)

###
  jquery.turbolinks.js ~ v0.1.0 ~ https://github.com/kossnocorp/jquery.turbolinks

  jQuery plugin for drop-in fix binded events problem caused by Turbolinks

  The MIT License

  Copyright (c) 2012 Sasha Koss
###

$ = require?('jquery') || window.jQuery

# List for store callbacks passed to `$` or `$.ready`
callbacks = []

# Call each callback in list
ready = ->
  callback() for callback in callbacks

# Bind `ready` to DOM ready event
$(ready)

# Store callbacks in list on `$` and `$.ready`
$.fn.ready = (callback) ->
  callbacks.push(callback)

# Bind `ready` to Tubolinks page change event
$(document).on('page:change', ready)

Update comments in source file

Update comments in source file


# Notifies about Crashlytics crashes via a Crashlytics web hook
#
# Dependencies:
#   "url": ""
#   "querystring": ""
#
# Configuration:
#   Just put this url <HUBOT_URL>:<PORT>/hubot/jenkins-notify?room=<room> to your Jenkins
#   Notification config. See here: https://wiki.jenkins-ci.org/display/JENKINS/Notification+Plugin
#
# Commands:
#   None
#
# URLS:
#   POST /hubot/crashlytics-notify?room=<room>
#
# Authors:
#   bmnick

url = require('url')
querystring = require('querystring')

module.exports = (robot) ->

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

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

    try
      data = req.body

      if data.event == 'verification'
        console.log "verified with Crashlytics"

      if data.event == 'issue_impact_change'
        robot.messageRoom '#publishmobileautomati', 'Stuffs broke!'
        res.writeHead 204, { 'Content-Length': 0}

      res.end()
    catch error
      console.log "jenkins-notify error: #{error}. Data: #{req.body}"
      console.log error.stack

# Notifies about Crashlytics crashes via a Crashlytics web hook
#
# Dependencies:
#   "url": ""
#   "querystring": ""
#
# Configuration:
#   Just put this url <HUBOT_URL>:<PORT>/hubot/jenkins-notify?room=<room> to your Jenkins
#   Notification config. See here: https://wiki.jenkins-ci.org/display/JENKINS/Notification+Plugin
#
# Commands:
#   None
#
# URLS:
#   POST /hubot/crashlytics-notify?room=<room>
#
# Authors:
#   bmnick

url = require('url')
querystring = require('querystring')

module.exports = (robot) ->

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

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

    try
      data = req.body

      if data.event == 'verification'
        console.log "verified with Crashlytics"

      if data.event == 'issue_impact_change'
        console.log 'Got a crash from Crashlytics'
        robot.messageRoom '#publishmobileautomati', 'Stuffs broke!'
        res.writeHead 204, { 'Content-Length': 0}

      res.end()
    catch error
      console.log "jenkins-notify error: #{error}. Data: #{req.body}"
      console.log error.stack

Add logging when receiving crashes

Add logging when receiving crashes


define [
    'underscore'
    'marionette'
    '../base'
    'tpl!templates/query/item.html'
], (_, Marionette, base, templates...) ->

    templates = _.object ['item'], templates

    class EmptyQueryItem extends base.EmptyView
        icon: false

        message: 'You have not yet created any queries or have had any shared with you'

    class QueryItem extends Marionette.ItemView
        className: 'row-fluid'

        template: templates.item

        ui:
            owner: '.owner'
            nonOwner: '.non-owner'

        onRender: ->
            if @model.get('is_owner')
                @ui.nonOwner.hide()
            else
                @ui.owner.hide()

    { EmptyQueryItem, QueryItem }

define [
    'underscore'
    'marionette'
    '../base'
    'tpl!templates/query/item.html'
], (_, Marionette, base, templates...) ->

    templates = _.object ['item'], templates

    class EmptyQueryItem extends base.EmptyView
        icon: false

        message: 'You have not yet created any queries or have had any shared with you'

    class QueryItem extends Marionette.ItemView
        className: 'row-fluid'

        template: templates.item

        ui:
            owner: '.owner'
            nonOwner: '.non-owner'

        events:
            'click .delete-query': 'deleteQuery'

        deleteQuery: ->
            @model.destroy({wait: true})

        onRender: ->
            if @model.get('is_owner')
                @ui.nonOwner.hide()
            else
                @ui.owner.hide()

    { EmptyQueryItem, QueryItem }

Implement delete button for queries in the QueryList


H2O.PredictOutput = (_, _go, prediction) ->
  { frame, model } = prediction

  _plots = signals []

  renderPlot = (title, render) ->
    container = signal null

    render (error, vis) ->
      if error
        debug error
      else
        container vis.element

    _plots.push title: title, plot: container

  switch prediction.__meta.schema_type
    when 'ModelMetricsBinomial'
     renderPlot 'ROC Curve', _.plot (g) ->
       g(
         g.path g.position 'FPR', 'TPR'
         g.line(
           g.position (g.value 1), (g.value 0)
           g.strokeColor g.value 'red'
         )
         g.from _.inspect 'Prediction - Thresholds x Metric Scores', prediction
       )

  for tableName in _.ls prediction
    if table = _.inspect tableName, prediction
      if table.indices.length > 1
        renderPlot tableName, _.plot (g) ->
          g(
            g.select()
            g.from table
          )
      else
        renderPlot tableName, _.plot (g) ->
          g(
            g.select 0
            g.from table
          )

  inspect = ->
    #XXX get this from prediction table
    _.insertAndExecuteCell 'cs', "inspect getPrediction model: #{stringify model.name}, frame: #{stringify frame.name}"

  defer _go

  plots: _plots
  inspect: inspect
  template: 'flow-predict-output'

H2O.PredictOutput = (_, _go, prediction) ->
  { frame, model } = prediction

  _plots = signals []

  renderPlot = (title, render) ->
    container = signal null

    render (error, vis) ->
      if error
        debug error
      else
        container vis.element

    _plots.push title: title, plot: container

  switch prediction.__meta.schema_type
    when 'ModelMetricsBinomial'
     renderPlot 'ROC Curve', _.plot (g) ->
       g(
         g.path g.position 'FPR', 'TPR'
         g.line(
           g.position (g.value 1), (g.value 0)
           g.strokeColor g.value 'red'
         )
         g.from _.inspect 'Prediction - Thresholds x Metric Scores', prediction
         g.domainX_HACK 0, 1
         g.domainY_HACK 0, 1
       )

  for tableName in _.ls prediction
    if table = _.inspect tableName, prediction
      if table.indices.length > 1
        renderPlot tableName, _.plot (g) ->
          g(
            g.select()
            g.from table
          )
      else
        renderPlot tableName, _.plot (g) ->
          g(
            g.select 0
            g.from table
          )

  inspect = ->
    #XXX get this from prediction table
    _.insertAndExecuteCell 'cs', "inspect getPrediction model: #{stringify model.name}, frame: #{stringify frame.name}"

  defer _go

  plots: _plots
  inspect: inspect
  template: 'flow-predict-output'

Fix ROC domains to (0, 1)

Fix ROC domains to (0, 1)


define (require) ->
  Backbone = require('backbone')

  SEARCH_URI = "#{location.protocol}//#{location.hostname}:6543/search"

  return class SearchResults extends Backbone.Model
    url: () -> "#{SEARCH_URI}#{@query}"

    defaults:
      query:
        limits: []
        sort: []
      results:
        items: []
        total: 0

    initialize: (options = {}) ->
      @query = options.query or ''
      @fetch
        success: () => @set('loaded', true)

define (require) ->
  Backbone = require('backbone')
  settings = require('cs!settings')

  SEARCH_URI = "#{location.protocol}//#{settings.cnxarchive.host}:#{settings.cnxarchive.port}/search"

  return class SearchResults extends Backbone.Model
    url: () -> "#{SEARCH_URI}#{@query}"

    defaults:
      query:
        limits: []
        sort: []
      results:
        items: []
        total: 0

    initialize: (options = {}) ->
      @query = options.query or ''
      @fetch
        success: () => @set('loaded', true)

Use cnx host/port settings to create search uri


deckardcain = require('deckardcain')
fury = require('fury')
fury.use(require('fury-adapter-apib-parser'))
fury.use(require('fury-adapter-swagger'))

apiElementsToJson = require('./api-elements-to-json')

createAnnotation = (message, type) ->
  {
    element: 'annotation'
    meta: {classes: [type]}
    content: message
  }

parse = (source, callback) ->
  annotations = []
  mediaType = deckardcain.identify(source)

  unless mediaType
    mediaType = 'text/vnd.apiblueprint'
    annotations.push(createAnnotation('''\
      Could not recognize API description format. \
      Falling back to API Blueprint by default.\
    ''', 'warning'))

  args = {source, mediaType, generateSourceMap: true}
  fury.parse(args, (err, apiElements) ->
    if not (err or apiElements)
      err = new Error('Unexpected parser error occurred.')
    else if err
      # Turning Fury error object into standard JavaScript error
      err = new Error(err.message)

    if apiElements
      apiElements = apiElementsToJson(apiElements)
      apiElements.content = apiElements.content.concat(annotations)
    else
      apiElements = null

    callback(err, {mediaType, apiElements})
  )

module.exports = parse

deckardcain = require('deckardcain')
fury = require('fury')
fury.use(require('fury-adapter-apib-parser'))
fury.use(require('fury-adapter-swagger'))

apiElementsToJson = require('./api-elements-to-json')

createWarning = (message) ->
  annotation = new fury.minim.elements.Annotation(message)
  annotation.classes.push('warning')
  return annotation

parse = (source, callback) ->
  warning = null
  mediaType = deckardcain.identify(source)

  unless mediaType
    mediaType = 'text/vnd.apiblueprint'
    warning = createWarning('''\
      Could not recognize API description format. \
      Falling back to API Blueprint by default.\
    ''')

  args = {source, mediaType, generateSourceMap: true}
  fury.parse(args, (err, apiElements) ->
    if not (err or apiElements)
      err = new Error('Unexpected parser error occurred.')
    else if err
      # Turning Fury error object into standard JavaScript error
      err = new Error(err.message)

    if apiElements
      apiElements.unshift(warning) if warning
      apiElements = apiElementsToJson(apiElements)
    else
      apiElements = null

    callback(err, {mediaType, apiElements})
  )

module.exports = parse

refactor: Use minim to add warning about unrecognized format


fsUtils = require 'fs-utils'
path = require 'path'
_ = require 'underscore'
archive = require 'ls-archive'
File = require 'file'

module.exports=
class ArchiveEditSession
  registerDeserializer(this)

  @activate: ->
    Project = require 'project'
    Project.registerOpener (filePath) ->
      new ArchiveEditSession(filePath) if archive.isPathSupported(filePath)

  @deserialize: ({path}={}) ->
    if fsUtils.isFileSync(path)
      new ArchiveEditSession(path)
    else
      console.warn "Could not build edit session for path '#{path}' because that file no longer exists"

  constructor: (@path) ->
    @file = new File(@path)

  destroy: ->
    @file?.off()

  serialize: ->
    deserializer: 'ArchiveEditSession'
    path: @path

  getViewClass: ->
    require './archive-view'

  getTitle: ->
    if archivePath = @getPath()
      path.basename(archivePath)
    else
      'untitled'

  getUri: -> @path

  getPath: -> @path

  isEqual: (other) ->
    other instanceof ArchiveEditSession and @getUri() is other.getUri()

fsUtils = require 'fs-utils'
path = require 'path'
_ = require 'underscore'
archive = require 'ls-archive'
File = require 'file'

module.exports=
class ArchiveEditSession
  registerDeserializer(this)

  @activate: ->
    Project = require 'project'
    Project.registerOpener (filePath) ->
      new ArchiveEditSession(filePath) if archive.isPathSupported(filePath)

  @deserialize: ({path}={}) ->
    path = project.resolve(path)
    if fsUtils.isFileSync(path)
      new ArchiveEditSession(path)
    else
      console.warn "Could not build archive edit session for path '#{path}' because that file no longer exists"

  constructor: (@path) ->
    @file = new File(@path)

  destroy: ->
    @file?.off()

  serialize: ->
    deserializer: 'ArchiveEditSession'
    path: @getUri()

  getViewClass: ->
    require './archive-view'

  getTitle: ->
    if archivePath = @getPath()
      path.basename(archivePath)
    else
      'untitled'

  getUri: -> project?.relativize(@getPath()) ? @getPath()

  getPath: -> @path

  isEqual: (other) ->
    other instanceof ArchiveEditSession and @getUri() is other.getUri()

Make all edit session uri's relative

Make all edit session uri's relative

This allows them to be collaborated without having absolute paths
in the shared document.


ETahi.SavesDelayed = Ember.Mixin.create
  saveInFlight: false

  saveDelayed: ->
    Ember.run.debounce(@, @saveModel, 100)

  saveModel: ->
    unless @get('saveInFlight')
      @set('saveInFlight', true)
      @get('model').save().then =>
        @set('saveInFlight', false)

  actions:
    saveModel: ->
      @saveDelayed()

ETahi.SavesDelayed = Ember.Mixin.create
  saveInFlight: false

  saveDelayed: ->
    Ember.run.debounce(@, @saveModel, 200)

  saveModel: ->
    unless @get('saveInFlight')
      @set('saveInFlight', true)
      @get('model').save().then =>
        @set('saveInFlight', false)

  actions:
    saveModel: ->
      @saveDelayed()

Increase SavesDelayed debounce to 200ms.

We're trying to eliminate some recurring test failures.
This may change more in the future.


{$$} = require 'space-pen'
SelectList = require 'select-list'
$ = require 'jquery'
_ = require 'underscore'

module.exports =
class CommandPaletteView extends SelectList
  @activate: ->
    new CommandPaletteView

  @viewClass: ->
    "#{super} command-palette overlay from-top"

  filterKey: 'eventDescription'

  previouslyFocusedElement: null
  keyBindings: null

  initialize: ->
    super

    rootView.command 'command-palette:toggle', => @toggle()

  toggle: ->
    if @hasParent()
      @cancel()
    else
      @attach()

  attach: ->
    super

    @keyBindings = _.losslessInvert(keymap.bindingsForElement(@previouslyFocusedElement))

    events = []
    for eventName, eventDescription of _.extend($(window).events(), @previouslyFocusedElement.events())
      events.push({eventName, eventDescription}) if eventDescription

    events = _.sortBy events, (e) -> e.eventDescription

    @setArray(events)
    @appendTo(rootView)
    @miniEditor.focus()

  itemForElement: ({eventName, eventDescription}) ->
    keyBindings = @keyBindings
    $$ ->
      @li class: 'event', 'data-event-name': eventName, =>
        @span eventDescription, class: 'label', title: eventName
        @div class: 'right', =>
          for binding in keyBindings[eventName] ? []
            @kbd binding, class: 'key-binding'

  confirmed: ({eventName}) ->
    @cancel()
    @previouslyFocusedElement.trigger(eventName)

{$$} = require 'space-pen'
SelectList = require 'select-list'
$ = require 'jquery'
_ = require 'underscore'

module.exports =
class CommandPaletteView extends SelectList
  @activate: ->
    new CommandPaletteView

  @viewClass: ->
    "#{super} command-palette overlay from-top"

  filterKey: 'eventDescription'

  keyBindings: null

  initialize: ->
    super

    rootView.command 'command-palette:toggle', => @toggle()

  toggle: ->
    if @hasParent()
      @cancel()
    else
      @attach()

  attach: ->
    super

    @keyBindings = _.losslessInvert(keymap.bindingsForElement(@previouslyFocusedElement))

    events = []
    for eventName, eventDescription of _.extend($(window).events(), @previouslyFocusedElement.events())
      events.push({eventName, eventDescription}) if eventDescription

    events = _.sortBy events, (e) -> e.eventDescription

    @setArray(events)
    @appendTo(rootView)
    @miniEditor.focus()

  itemForElement: ({eventName, eventDescription}) ->
    keyBindings = @keyBindings
    $$ ->
      @li class: 'event', 'data-event-name': eventName, =>
        @span eventDescription, class: 'label', title: eventName
        @div class: 'right', =>
          for binding in keyBindings[eventName] ? []
            @kbd binding, class: 'key-binding'

  confirmed: ({eventName}) ->
    @cancel()
    @previouslyFocusedElement.trigger(eventName)

Remove unneeded initialization of variable from superclass


_ = require "underscore"
Util = require "../util/util"
HasProperties = require "../common/has_properties"

class OpenURL extends HasProperties
  type: 'OpenURL'

  execute: (data_source) ->
    selected = data_source.get("selected")
    if selected['0d'].flag
      indices = selected['0d'].indices
    else if selected['1d'].indices.length > 0
      indices = selected['1d'].indices
    else if selected['2d'].indices.length > 0
      indices = selected['2d'].indices
    else
      indices = []

    for i in indices
      url = Util.replace_placeholders(@get("url"), data_source, i)
      window.open(url)

  defaults: ->
    return _.extend {}, super(), {
      url: 'http://'
    }

module.exports =
  Model: OpenURL

_ = require "underscore"
Util = require "../util/util"
HasProperties = require "../common/has_properties"

class OpenURL extends HasProperties
  type: 'OpenURL'

  execute: (data_source) ->
    selected = data_source.get("selected")
    if selected['0d'].flag
      indices = selected['0d'].indices
    else if selected['1d'].indices.length > 0
      indices = selected['1d'].indices
    else if selected['2d'].indices.length > 0
      indices = selected['2d'].indices
    else
      indices = []

    for i in indices
      url = Util.replace_placeholders(@get("url"), data_source, i)
      window.open(url)
    null

  defaults: ->
    return _.extend {}, super(), {
      url: 'http://'
    }

module.exports =
  Model: OpenURL

Return null from OpenURL.execute() to reduce output size


window.ReactSubComment = React.createBackboneClass
  displayName: 'ReactSubComment'
  propTypes:
    fact_opinionators: React.PropTypes.instanceOf(Opinionators).isRequired
    model: React.PropTypes.instanceOf(SubComment).isRequired

  _save: ->
    @model().saveWithState()

  _content_tag: ->
    if @model().get('formatted_content')
      _span ["comment-content spec-subcomment-content",
        dangerouslySetInnerHTML: {__html: @model().get('formatted_content')}]
    else
      _span ["comment-content spec-subcomment-content"],
        @model().get('content')

  render: ->
    creator = @model().creator()
    created_at = @model().get('created_at')

    _div ["sub-comment"],
      _div ['sub-comment-avatar-container'],
        ReactOpinionatedAvatar
          user: creator
          model: @props.fact_opinionators
          size: 30

      _div ['sub-comment-content-container'],
        _a [" sub-comment-creator", href: creator.link(), rel: "backbone"],
          creator.get('name')
        if created_at
          TimeAgo
            className: "sub-comment-post-time"
            time: @model().get('created_at')

        @_content_tag()

        if @model().get('save_failed') == true
          ReactRetryButton onClick: @_save

        if @model().can_destroy()
          _div ["sub-comment-post-delete"],
            ReactSlidingDeleteButton
              model: @model()
              onDelete: -> @model().destroy wait: true

window.ReactSubComment = React.createBackboneClass
  displayName: 'ReactSubComment'
  propTypes:
    fact_opinionators: React.PropTypes.instanceOf(Opinionators).isRequired
    model: React.PropTypes.instanceOf(SubComment).isRequired

  _save: ->
    @model().saveWithState()

  _content_tag: ->
    if @model().get('formatted_content')
      _span ["comment-content spec-subcomment-content",
        dangerouslySetInnerHTML: {__html: @model().get('formatted_content')}]
    else
      _span ["comment-content spec-subcomment-content"],
        @model().get('content')

  render: ->
    creator = @model().creator()
    created_at = @model().get('created_at')

    _div ["sub-comment"],
      _div ['sub-comment-avatar-container'],
        ReactOpinionatedAvatar
          user: creator
          model: @props.fact_opinionators
          size: 30

      _div ['sub-comment-content-container'],
        _a [" sub-comment-creator", href: creator.link(), rel: "backbone"],
          creator.get('name')
        if created_at
          TimeAgo
            className: "sub-comment-post-time"
            time: @model().get('created_at')

        @_content_tag()

        if @model().get('save_failed') == true
          ReactRetryButton onClick: @_save

        if @model().can_destroy()
          _div ["sub-comment-post-delete"],
            ReactSlidingDeleteButton
              model: @model()
              onDelete: => @model().destroy wait: true

Fix delete button on sub comments

Fix delete button on sub comments


"*":
  "autocomplete-python":
    useKite: false
  core:
    disabledPackages: [
      "metrics"
      "linter-python-pep8"
    ]
    telemetryConsent: "no"
    themes: [
      "one-dark-ui"
      "monokai"
    ]
  editor:
    fontSize: 11
    scrollPastEnd: true
    showIndentGuide: true
    showInvisibles: true
    softWrap: true
    softWrapHangingIndent: 4
    tabLength: 4
  "exception-reporting":
    userId: "1a743eaf-a8a8-050d-ec12-e52437e24ab5"
  "fold-functions":
    autofoldGrammars: [
      "Python"
    ]
    skipAutofoldWhenOnlyOneFunction: true
  "git-diff":
    showIconsInEditorGutter: true
  global:
    whitespace:
      removeTrailingWhitespace: false
  linter:
    errorPanelHeight: 69
  "linter-python-pep8":
    pep8DirToExecutable: "/usr/local/bin/"
  "one-light-ui":
    fontSize: 10
    layoutMode: "Compact"
    tabSizing: "Minimum"
  "package-sync":
    createOnChange: true
    forceOverwrite: true
  welcome:
    showOnStartup: false
  whitespace: {}

"*":
  "autocomplete-python":
    useKite: false
  core:
    disabledPackages: [
      "metrics"
      "linter-python-pep8"
    ]
    telemetryConsent: "no"
    themes: [
      "one-dark-ui"
      "monokai"
    ]
  editor:
    fontSize: 11
    scrollPastEnd: true
    showIndentGuide: true
    showInvisibles: true
    softWrap: true
    softWrapHangingIndent: 4
    tabLength: 4
  "exception-reporting":
    userId: "1a743eaf-a8a8-050d-ec12-e52437e24ab5"
  "fold-functions":
    autofoldGrammars: [
      "Python"
    ]
    skipAutofoldWhenOnlyOneFunction: true
  "git-diff":
    showIconsInEditorGutter: true
  global:
    whitespace:
      removeTrailingWhitespace: false
  linter:
    errorPanelHeight: 69
  "linter-python-pep8":
    pep8DirToExecutable: "/usr/local/bin/"
  "one-light-ui":
    fontSize: 10
    layoutMode: "Compact"
    tabSizing: "Minimum"
  "package-sync":
    createOnChange: true
    forceOverwrite: true
  "tree-view":
    hideVcsIgnoredFiles: true
  welcome:
    showOnStartup: false
  whitespace: {}

Hide vcs-ignored files in Atom

Hide vcs-ignored files in Atom


passport = require 'passport'
debug = require('debug')('meshblu-google-authenticator:routes')
url = require 'url'

class Router
  constructor: (@app) ->

  register: =>
    @app.get  '/', (request, response) => response.status(200).send status: 'online'

    @app.get '/login', @storeCallbackUrl, passport.authenticate 'google', scope: ['profile', 'email']

    @app.get '/oauthcallback', passport.authenticate('google', { failureRedirect: '/login' }), @afterPassportLogin

  afterPassportLogin: (request, response) =>
    {callbackUrl} = request.session
    return response.status(401).send(new Error 'Invalid User') unless request.user
    return response.status(201).send(request.user) unless callbackUrl?
    uriParams = url.parse callbackUrl
    uriParams.query ?= {}
    uriParams.query.uuid = request.user.uuid
    uriParams.query.token = request.user.token
    return response.redirect(url.format uriParams)

  defaultRoute: (request, response) =>
    response.render 'index'

  storeCallbackUrl: (request, response, next) =>
    request.session.callbackUrl = request.query.callbackUrl
    next()

module.exports = Router

passport = require 'passport'
debug = require('debug')('meshblu-google-authenticator:routes')
url = require 'url'

class Router
  constructor: (@app) ->

  register: =>
    @app.get  '/', (request, response) => response.status(200).send status: 'online'

    @app.get '/login', @storeCallbackUrl, passport.authenticate 'google', scope: ['profile', 'email']

    @app.get '/oauthcallback', passport.authenticate('google', { failureRedirect: '/login' }), @afterPassportLogin

  afterPassportLogin: (request, response) =>
    {callbackUrl} = request.cookies
    delete request.cookies.callbackUrl
    return response.status(401).send(new Error 'Invalid User') unless request.user
    return response.status(201).send(request.user) unless callbackUrl?
    uriParams = url.parse callbackUrl
    uriParams.query ?= {}
    uriParams.query.uuid = request.user.uuid
    uriParams.query.token = request.user.token
    return response.redirect(url.format uriParams)

  defaultRoute: (request, response) =>
    response.render 'index'

  storeCallbackUrl: (request, response, next) =>
    if request.query.callbackUrl?
      request.cookies.callbackUrl = request.query.callbackUrl
    else
      delete request.cookies.callbackUrl
    next()

module.exports = Router

Use cookies for callback instead of session


require [
  "handlebars"
], (
  Handlebars
) ->
  helpers =
    highlightMatches: (match) ->
      result = match.wrap (match) ->
        "#{match}"

      new Handlebars.SafeString result

    prettyUrl: (url) ->
      url.replace /^((http|https):\/\/)?(www.)?/, ""

  Handlebars.registerHelper(name, helper) for name, helper of helpers

require [
  "handlebars"
], (
  Handlebars
) ->
  helpers =
    highlightMatches: (match) ->
      result = match.wrap (match) ->
        "#{match}"

      new Handlebars.SafeString result

    prettyUrl: (url) ->
      url.replace(/^((http|https):\/\/)?(www.)?/, "")
         .replace(/\/$/, "")
         .replace(/\.html$/, "")

  Handlebars.registerHelper(name, helper) for name, helper of helpers

Remove trailing slashes and HTML file extension from prettyfied URLs


//= require analytics/virtual_pageview

describe 'VirtualPageview', ->
  element = null

  describe 'given "data-virtual-pageview" anchor link', ->

    beforeEach ->
      element = $('' +
        'John Smith' +
        '')
      $(document.body).append(element)
      new window.VirtualPageview.bindLinks()

    afterEach ->
      element.remove()
      element = null

    describe 'on first click', ->
      it 'dispatches pageview', ->
        spyOn window, 'dispatchPageView'
        $('#a_link').trigger 'click'

        expect(window.dispatchPageView).toHaveBeenCalledWith('/top-3-search-result')

//= require analytics/virtual_pageview

describe 'VirtualPageview', ->
  element = null

  describe 'given "data-virtual-pageview" anchor link', ->

    setUpPage = (page_url) ->
      element = $('' +
        'John Smith' +
        '')
      $(document.body).append(element)
      new window.VirtualPageview.bindLinks()

    afterEach ->
      element.remove()
      element = null

    describe 'with single page url', ->
      beforeEach ->
        setUpPage('/top-3-search-result')

      describe 'on first click', ->
        it 'dispatches pageview', ->
          spyOn window, 'dispatchPageView'
          $('#a_link').trigger 'click'

          expect(window.dispatchPageView).toHaveBeenCalledWith('/top-3-search-result')

Refactor virtual pageview spec to have a setup function


ETahi.ControllerParticipants = Ember.Mixin.create
  needs: ['application']
  currentUser: Ember.computed.alias('controllers.application.currentUser')
  allUsers: (->
    paperId = @get('paper.id') || @get('litePaper.id')
    DS.PromiseObject.create
      promise: $.getJSON("/filtered_users/collaborators/#{paperId}")
  ).property()

  participants: Em.computed.alias('model.participants')

  actions:
    saveNewParticipant: (newParticipantId) ->
      newParticipant = this.store.find 'user', newParticipantId
      newParticipant.then (user)=>
        unless @get('participants').contains user
          @get('participants').pushObject(user)
          @send('saveModel') unless @get('model.isNew')

ETahi.ControllerParticipants = Ember.Mixin.create
  needs: ['application']
  currentUser: Ember.computed.alias('controllers.application.currentUser')
  allUsers: (->
    paperId = @get('paper.id') || @get('litePaper.id')
    DS.PromiseObject.create
      promise: $.getJSON("/filtered_users/collaborators/#{paperId}")
  ).property()

  participants: Em.computed.alias('model.participants')

  actions:
    saveNewParticipant: (newParticipantId) ->
      @store.find('user', newParticipantId).then (user)=>
        @get('participants').addObject(user)
        @send('saveModel') unless @get('model.isNew')

Use addObject instead of pushObject.

Use addObject instead of pushObject.


# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/

$ ->
  animateMetric = $(".metric-box strong").each ->
    $this = $(this)
    starting_point = 0
    $target_count = parseInt($this.text().replace(/\,/g, ''))

    # set the test to 0
    $this.counter = starting_point

    tween = createjs.Tween.get($this).to( {counter: $target_count}, 10000 , createjs.Ease.quintOut)

    tween.addEventListener 'change', (event) ->
      $this.text(Math.round($this.counter).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","))
      return

# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/

$ ->
  animateMetric = $(".metric-box strong").each ->
    $this = $(this)
    starting_point = 0
    $target_count = parseInt($this.text().replace(/\,/g, ''))

    # set the test to 0
    $this.counter = starting_point

    tween = createjs.Tween.get($this).to( {counter: $target_count}, 2000 , createjs.Ease.quintOut)

    tween.addEventListener 'change', (event) ->
      $this.text(Math.round($this.counter).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","))
      return

Make animation duration much shorter

Make animation duration much shorter


{
  namespace,
  utils,
  jQuery: $,
  templates: {tpl}
} = uploadcare

{dragdrop} = uploadcare.widget

namespace 'uploadcare.widget.tabs', (ns) ->
  class ns.FileTab extends ns.BaseSourceTab

    constructor: ->
      super

      @wrap.append tpl 'tab-file', {avalibleTabs: @dialogApi.avalibleTabs}

      @wrap.on 'click', '@uploadcare-dialog-switch-tab', (e) =>
        @dialogApi.switchTab $(e.target).data 'tab'

      @__setupFileButton()
      @__initDragNDrop()

    __initDragNDrop: ->
      dropArea = @wrap.find('@uploadcare-drop-area')
      if utils.abilities.fileDragAndDrop
        dragdrop.receiveDrop (type, data) =>
          @dialogApi.addFiles type, data
        , dropArea
        className = 'draganddrop'
      else
        className = 'no-draganddrop'
      @wrap.addClass "uploadcare-#{className}"

    __setupFileButton: ->
      fileButton = @wrap.find('@uploadcare-dialog-browse-file')
      utils.fileInput fileButton, @settings.multiple, (e) =>
        @dialogApi.addFiles 'event', e

{
  namespace,
  utils,
  jQuery: $,
  templates: {tpl}
} = uploadcare

{dragdrop} = uploadcare.widget

namespace 'uploadcare.widget.tabs', (ns) ->
  class ns.FileTab extends ns.BaseSourceTab

    constructor: ->
      super

      @wrap.append tpl 'tab-file', {avalibleTabs: @dialogApi.avalibleTabs}

      @wrap.on 'click', '@uploadcare-dialog-switch-tab', (e) =>
        @dialogApi.switchTab $(e.target).data 'tab'

      @__setupFileButton()
      @__initDragNDrop()

    __initDragNDrop: ->
      dropArea = @wrap.find('@uploadcare-drop-area')
      if utils.abilities.fileDragAndDrop
        dragdrop.receiveDrop (type, data) =>
          @dialogApi.addFiles type, data
          @dialogApi.switchToPreview()
        , dropArea
        className = 'draganddrop'
      else
        className = 'no-draganddrop'
      @wrap.addClass "uploadcare-#{className}"

    __setupFileButton: ->
      fileButton = @wrap.find('@uploadcare-dialog-browse-file')
      utils.fileInput fileButton, @settings.multiple, (e) =>
        @dialogApi.addFiles 'event', e

Switch to preview after drag and drop

Switch to preview after drag and drop


#= require ../auto_complete/search_list_view

class AutoCompleteSearchUserView extends Backbone.Factlink.StepView
  tagName: "li"

  template: "auto_complete/search_item"

  templateHelpers: ->
    query = @options.query

    highlightedTitle: -> highlightTextInTextAsHtml(query, @username)

  onRender: ->
    @$el.addClass('user-logo') if @model.id == currentUser.id

class window.AutoCompleteSearchUsersView extends AutoCompleteSearchListView
  itemView: AutoCompleteSearchUserView

#= require ../auto_complete/search_list_view

class AutoCompleteSearchUserView extends Backbone.Factlink.StepView
  tagName: "li"

  template: "auto_complete/search_item"

  templateHelpers: ->
    query = @options.query

    highlightedTitle: -> highlightTextInTextAsHtml(query, @name)

  onRender: ->
    @$el.addClass('user-logo') if @model.id == currentUser.id

class window.AutoCompleteSearchUsersView extends AutoCompleteSearchListView
  itemView: AutoCompleteSearchUserView

Use the users name in the auto complete box