TimeEntriesController = require "#{__dirname}/../../controllers/time_entries"
TimeEntry = require "#{__dirname}/../../models/time_entry"

_ = require 'underscore'

handler = (app) ->

  app.get '/time_entries', (req, res) ->
    if typeof req.query.projectId isnt 'undefined'
      TimeEntriesController.getForProject req.query.projectId, (err, time_entries)->
        res.send _.map time_entries, (t) -> (new TimeEntry t).publicObject()
    else
      res.send 404

  app.post '/time_entries', (req, res) ->
    te = new TimeEntry req.body
    if te.validate()
      TimeEntriesController.create te, (err, time_entry)->
        if err
          res.send 400, error: 'some error'
        else
          res.send time_entry.publicObject()
    else
      res.send 400, error: 'some error'

  app.get '/time_entries/:id', (req, res) ->
    TimeEntriesController.getOne req.params.id, (err, time_entry)->
      if err
        res.send 404, error: "#{req.params.id} not found"
      else
        res.send time_entry.publicObject()

  app.delete '/time_entries/:id', (req, res) ->
    TimeEntriesController.deleteOne req.params.id, (err)->
      if err
        res.send 404, error: "#{req.params.id} not found"
      else
        res.send 200

module.exports = handler

TimeEntriesController = require "#{__dirname}/../../controllers/time_entries"
TimeEntry = require "#{__dirname}/../../models/time_entry"

_ = require 'underscore'

handler = (app) ->

  app.get '/time_entries', (req, res) ->
    if typeof req.query.projectId isnt 'undefined'
      TimeEntriesController.getForProject req.query.projectId, (err, time_entries)->
        res.send _.map time_entries, (t) -> (new TimeEntry t).publicObject()
    else
      res.send 404

  app.post '/time_entries', (req, res) ->
    req.body.userId = req.user.id
    te = new TimeEntry req.body
    if te.validate()
      TimeEntriesController.create te, (err, time_entry)->
        if err
          res.send 400, error: 'some error'
        else
          res.send time_entry.publicObject()
    else
      res.send 400, error: 'some error'

  app.get '/time_entries/:id', (req, res) ->
    TimeEntriesController.getOne req.params.id, (err, time_entry)->
      if err
        res.send 404, error: "#{req.params.id} not found"
      else
        res.send time_entry.publicObject()

  app.delete '/time_entries/:id', (req, res) ->
    TimeEntriesController.deleteOne req.params.id, (err)->
      if err
        res.send 404, error: "#{req.params.id} not found"
      else
        res.send 200

module.exports = handler

Use the req.user.id when making time entries


ETahi.FlowManagerRoute = Ember.Route.extend
  model: ->
    @store.find("flow")

  actions:
    chooseNewFlowMangerColumn: ->
      @render('chooseNewFlowManagerColumnOverlay',
        into: 'application'
        outlet: 'overlay'
        controller: 'chooseNewFlowManagerColumnOverlay')

    removeFlow: (flow) ->
      flow.destroyRecord()

    viewCard: (paper, task) ->
      currentType = task.get('type')
      currentType = 'AdHocTask' if currentType == 'Task'
      baseObjectName = (currentType || 'AdHocTask').replace('Task', 'Overlay')
      controller = @controllerFor(baseObjectName)
      controller.set('model', task)
      controller.set('paper', paper)

      @render(baseObjectName,
        into: 'application'
        outlet: 'overlay'
        controller: controller)

ETahi.FlowManagerRoute = Ember.Route.extend
  model: ->
    @store.find("flow")

  actions:
    chooseNewFlowMangerColumn: ->
      @render('chooseNewFlowManagerColumnOverlay',
        into: 'application'
        outlet: 'overlay'
        controller: 'chooseNewFlowManagerColumnOverlay')

    removeFlow: (flow) ->
      flow.destroyRecord()

    viewCard: (paper, task) ->
      redirectParams = ['flow_manager']
      @controllerFor('application').set('overlayRedirect', redirectParams)
      @controllerFor('application').set('overlayBackground', 'paper/manage')
      @transitionTo('paper.task', paper, task)

Change urls for task view in FlowManager


_ = require 'underscore'
Backbone = require 'backbone'
form = require '../../form/utilities.coffee'

module.exports = class StepView extends Backbone.View
  __events__: null

  events: ->
    _.extend @__events__,
      'click .js-nevermind': 'dismiss'

  initialize: ({ @user, @state, @artwork }) -> #

  template: ->
    throw new Error 'no template provided'

  next: (e) ->
    e?.preventDefault()
    @state.next()

  dismiss: (e) ->
    e.preventDefault()
    @state.trigger 'abort'

  autofocus: -> _.defer =>
    $input = form.firstVisibleInput @$el
    form.autofocus $input

  render: ->
    @$el.html @template
      user: @user
      state: @state
      artwork: @artwork
    @postRender()
    @autofocus()
    this

  postRender: -> #

_ = require 'underscore'
Backbone = require 'backbone'
form = require '../../form/utilities.coffee'

module.exports = class StepView extends Backbone.View
  __events__: null

  events: ->
    _.extend @__events__,
      'click .js-nevermind': 'dismiss'

  initialize: ({ @user, @state, @artwork }) ->
    @__setup__()

  template: ->
    throw new Error 'no template provided'

  next: (e) ->
    e?.preventDefault()
    @state.next()

  dismiss: (e) ->
    e.preventDefault()
    @state.trigger 'abort'

  autofocus: -> _.defer =>
    $input = form.firstVisibleInput @$el
    form.autofocus $input

  setup: -> #

  __setup__: ->
    return if @__isSetup__
    @setup()
    @__isSetup__ = true

  render: ->
    @$el.html @template
      user: @user
      state: @state
      artwork: @artwork
    @postRender()
    @autofocus()
    this

  postRender: -> #

Include a setup method that only runs once on initialization


angular.module('doubtfire.projects.states.tutorials', [])

#
# Tasks state for projects
#
.config(($stateProvider) ->
  $stateProvider.state 'projects/tutorials', {
    parent: 'projects/index'
    url: '/tutorials'
    controller: 'ProjectsTutorialsStateCtrl'
    templateUrl: 'projects/states/tutorials/tutorials.tpl.html'
    data:
      task: "Tutorial List"
      pageTitle: "_Home_"
      roleWhitelist: ['Tutor', 'Convenor', 'Admin', 'Student']
   }
)

.controller("ProjectsTutorialsStateCtrl", ($scope, $modal, User, Project, alertService, projectService, analyticsService) ->
  $scope.sortOrder = 'abbreviation'
  $scope.setTutorial = (id) ->
    Project.update(
      { id: $scope.project.project_id, tutorial_id: id }
      (project) ->
        $scope.project.tutorial_id = project.tutorial_id
        $scope.project.tutorial = $scope.unit.tutorialFromId( $scope.project.tutorial_id )
        eventName = if id isnt -1 then "Changed tutorial" else "Withdrew from all tutorials"
        analyticsService.event "Student Project View - Tutorials Tab", eventName
        projectService.updateGroups($scope.project) #can be removed from groups by changing labs
      (response) -> alertService.add("danger", response.data.error, 6000)
    )
)

angular.module('doubtfire.projects.states.tutorials', [])

#
# Tasks state for projects
#
.config(($stateProvider) ->
  $stateProvider.state 'projects/tutorials', {
    parent: 'projects/index'
    url: '/tutorials'
    controller: 'ProjectsTutorialsStateCtrl'
    templateUrl: 'projects/states/tutorials/tutorials.tpl.html'
    data:
      task: "Tutorial List"
      pageTitle: "_Home_"
      roleWhitelist: ['Tutor', 'Convenor', 'Admin', 'Student']
   }
)

.controller("ProjectsTutorialsStateCtrl", ($scope, $modal, User, Project, alertService, projectService, analyticsService) ->
  $scope.sortOrder = 'abbreviation'
  $scope.setTutorial = (id) ->
    Project.update(
      { id: $scope.project.project_id, tutorial_id: id }
      (project) ->
        oldTutorialAbbr = $scope.unit.tutorialFromId($scope.project.tutorial_id)?.abbreviation
        $scope.project.tutorial_id = project.tutorial_id
        $scope.project.tutorial = $scope.unit.tutorialFromId( $scope.project.tutorial_id )
        if id == -1
          eventName = "Withdrew from all tutorials"
          successMsg = "Withdrew from #{oldTutorialAbbr}"
        else
          eventName = "Changed tutorial"
          successMsg = "Enrolled in #{$scope.project.tutorial.abbreviation}"
        analyticsService.event("Student Project View - Tutorials Tab", eventName)
        alertService.add("success", successMsg, 3000)
        projectService.updateGroups($scope.project) #can be removed from groups by changing labs
      (response) -> alertService.add("danger", response.data.error, 6000)
    )
)

ENHANCE: Add success alert messages on changing tutorial


#= require ./store
#= require_tree ./models
#= require_tree ./controllers
#= require_tree ./views
#= require_tree ./helpers
#= require_tree ./templates
#= require_tree ./routes
#= require ./router
#= require_self

<%= application_name.camelize %>.initialize()

Remove App.initialize from coffeescript template


#= require slickgrid
#= require SlickGrid/slick.dataview
#= require SlickGrid/slick.groupitemmetadataprovider

Pancakes.SearchResultsGrid = Ember.View.extend
  columns: []
  classNames: ['grid']
  options: {}

  # Build up the SlickGrid elements.
  #
  # The grid is the only visual element here; the rest are for grouping,
  # sorting, and filtering.  They all fit in at view-level, though.
  didInsertElement: ->
    el = @$()
    groupItemMetadataProvider = new Slick.Data.GroupItemMetadataProvider

    view = new Slick.Data.DataView(
      groupItemMetadataProvider: groupItemMetadataProvider
      inlineFilters: true
    )

    grid = new Slick.Grid el, @get('view'), @get('columns'), @get('options')

    @set 'grid', grid
    @set 'view', view

    view.onRowCountChanged.subscribe (e, args) ->
      grid.updateRowCount()
      grid.render()

    view.onRowsChanged.subscribe (e, args) ->
      grid.invalidateRows(args.rows)
      grid.render()

    grid.autosizeColumns()
    grid.registerPlugin(groupItemMetadataProvider)

  # When this view is destroyed, destroy the grid and view also.
  willDestroyElement: ->
    @get('grid').destroy()
    @set 'grid', null
    @set 'view', null

# vim:ts=2:sw=2:et:tw=78

#= require slickgrid
#= require SlickGrid/slick.dataview
#= require SlickGrid/slick.groupitemmetadataprovider

Pancakes.SearchResultsGrid = Ember.View.extend
  columns: []
  classNames: ['grid']
  options: {}

  # Build up the SlickGrid elements.
  #
  # The grid is the only visual element here; the rest are for grouping,
  # sorting, and filtering.  They all fit in at view-level, though.
  didInsertElement: ->
    el = @$()
    groupItemMetadataProvider = new Slick.Data.GroupItemMetadataProvider

    view = new Slick.Data.DataView(
      groupItemMetadataProvider: groupItemMetadataProvider
      inlineFilters: true
    )

    @set 'view', view

    grid = new Slick.Grid el, @get('view'), @get('columns'), @get('options')

    @set 'grid', grid

    view.onRowCountChanged.subscribe (e, args) ->
      grid.updateRowCount()
      grid.render()

    view.onRowsChanged.subscribe (e, args) ->
      grid.invalidateRows(args.rows)
      grid.render()

    grid.autosizeColumns()
    grid.registerPlugin(groupItemMetadataProvider)

  # When this view is destroyed, destroy the grid and view also.
  willDestroyElement: ->
    @get('grid').destroy()
    @set 'grid', null
    @set 'view', null

# vim:ts=2:sw=2:et:tw=78

Fix an initialization order goof.

Fix an initialization order goof.


# Inject common project-wide [view locals](http://expressjs.com/api.html#app.locals).

{ parse, format } = require 'url'
_ = require 'underscore'
{ NODE_ENV } = require '../../config'

module.exports = (req, res, next) ->
  res.locals.sd.CURRENT_URL = req.url
  res.locals.sd.CURRENT_PATH = parse(req.url).pathname
  res.locals._ = _
  res.locals.homeHref = req.user?.homePath() or '/'

  # Respect do not track headers
  res.locals.doNotTrack = res.locals.sd.DO_NOT_TRACK = req.headers.dnt

  next()

# Inject common project-wide [view locals](http://expressjs.com/api.html#app.locals).

{ parse, format } = require 'url'
_ = require 'underscore'
{ NODE_ENV } = require '../../config'

module.exports = (req, res, next) ->
  res.locals.sd.CURRENT_URL = req.url
  res.locals.sd.CURRENT_PATH = parse(req.url).pathname
  res.locals._ = _
  res.locals.homeHref = req.user?.homePath() or '/'

  # Respect do not track headers
  res.locals.doNotTrack = res.locals.sd.DO_NOT_TRACK = req.headers.dnt || false

  next()

Set do not track to false if it doesn't exist


Update handlebar snippets file type group


App.IndexController = Ember.ArrayController.extend
  needs: ["application"]
  currentUser: Ember.computed.alias("controllers.application.currentUser")
  itemController: "RoomUserStateItem"

  detectMessageType: (msgTxt)->
    if msgTxt.match("\n")
      "paste"
    else
      "text"

  actions:
    postMessage: (msgTxt)->
      msgTxt = msgTxt.trim()
      room = @get("activeState").get("room")
      console.log "To room #{room.get("name")}:", msgTxt
      currentUser = @get("currentUser")
      messageParams =
        roomId: room.get("id")
        body: msgTxt
        type: @detectMessageType(msgTxt)
        createdAt: new Date()
        user: currentUser

      console.log messageParams
      msg = @store.createRecord("message", messageParams)
      successCallback = ->
        console.log "message has been posted"
      errorCallback   = ->
        console.log "error posting message"
      msg.save().then(successCallback, errorCallback)

App.IndexController = Ember.ArrayController.extend
  needs: ["application"]
  currentUser: Ember.computed.alias("controllers.application.currentUser")
  itemController: "RoomUserStateItem"

  detectMessageType: (msgTxt)->
    if msgTxt.match("\n")
      "paste"
    else
      "text"

  actions:
    postMessage: (msgTxt)->
      msgTxt = msgTxt.trim()
      room = @get("activeState").get("room")
      currentUser = @get("currentUser")
      messageParams =
        room: room
        body: msgTxt
        type: @detectMessageType(msgTxt)
        createdAt: new Date()
        user: currentUser

      console.log messageParams
      msg = @store.createRecord("message", messageParams)
      successCallback = ->
        console.log "message has been posted"
      errorCallback   = ->
        console.log "error posting message"
      msg.save().then(successCallback, errorCallback)

Send room instead of roomId

Send room instead of roomId


"*":
  core:
    disabledPackages: [
      "preview-tabs"
      "script"
    ]
    ignoredNames: [
      ".DS_Store"
      ".bundle"
      ".git"
    ]
    projectHome: "/Users/justas/Projects"
  editor:
    fontFamily: "Menlo"
    fontSize: 15
    invisibles: {}
    showIndentGuide: true
  "exception-reporting":
    userId: "a16fb11c-80f5-993c-dac9-27e736ff535e"
  linter:
    showErrorPanel: false
  "linter-rubocop":
    command: "/Users/jpalumickas/.rbenv/shims"
  "linter-scss-lint":
    scssLintExecutablePath: "/Users/jpalumickas/.rbenv/shims/"
  "release-notes":
    viewedVersion: "0.95.0"
  "spell-check":
    grammars: [
      "text.plain"
      "source.gfm"
      "text.git-commit"
    ]
  "tree-view":
    hideIgnoredNames: true
  "vim-mode":
    useClipboardAsDefaultRegister: true
  welcome:
    showOnStartup: false
  whitespace:
    ignoreWhitespaceOnCurrentLine: false

"*":
  core:
    disabledPackages: [
      "preview-tabs"
      "script"
    ]
    ignoredNames: [
      ".DS_Store"
      ".bundle"
      ".git"
    ]
    projectHome: "/Users/justas/Projects"
  editor:
    backUpBeforeSaving: true
    fontFamily: "Menlo"
    fontSize: 15
    invisibles: {}
    showIndentGuide: true
  "exception-reporting":
    userId: "a16fb11c-80f5-993c-dac9-27e736ff535e"
  linter:
    showErrorPanel: false
  "linter-rubocop":
    command: "/Users/jpalumickas/.rbenv/shims"
  "linter-scss-lint":
    scssLintExecutablePath: "/Users/jpalumickas/.rbenv/shims/"
  "release-notes":
    viewedVersion: "0.95.0"
  "spell-check":
    grammars: [
      "text.plain"
      "source.gfm"
      "text.git-commit"
    ]
  "tree-view":
    hideIgnoredNames: true
  "vim-mode":
    useClipboardAsDefaultRegister: true
  welcome:
    showOnStartup: false
  whitespace:
    ignoreWhitespaceOnCurrentLine: false

Add backUpBeforeSaving for Atom editor

Add backUpBeforeSaving for Atom editor


class TimeSlider.Plugin.EOWCS
    constructor: (@options = {})->

        @formatDate = (date) ->
            date = date.toISOString()
            date = date.substring(0, date.length - 5)
            date + "Z"

        callback = (start, end, callback) =>
            request = d3.xhr(WCS.EO.KVP.describeEOCoverageSetURL(@options.url, @options.eoid, { subsetTime: [ @formatDate(start), @formatDate(end) ] }))
            request.get( (error, response) =>
                return [] if error

                datasets = []
                response = WCS.Core.Parse.parse(response.responseXML)
                for coverage in response.coverageDescriptions
                    datasets.push([ new Date(coverage.timePeriod[0]), new Date(coverage.timePeriod[1]) ])

                callback(@options.dataset, datasets)
            )

        return callback

class TimeSlider.Plugin.EOWCS
    constructor: (@options = {})->

        @formatDate = (date) ->
            date = date.toISOString()
            date = date.substring(0, date.length - 5)
            date + "Z"

        callback = (start, end, callback) =>
            request = d3.xhr(WCS.EO.KVP.describeEOCoverageSetURL(@options.url, @options.eoid, { subsetTime: [ @formatDate(start), @formatDate(end) ] }))
            request.get( (error, response) =>
                callback(@options.dataset, []) if error

                datasets = []
                response = WCS.Core.Parse.parse(response.responseXML)
                for coverage in response.coverageDescriptions
                    datasets.push([ new Date(coverage.timePeriod[0]), new Date(coverage.timePeriod[1]) ])

                callback(@options.dataset, datasets)
            )

        return callback

Return an empty list of datasets if the xhr receives an error

Signed-off-by: Marko Locher <9efe9a5a1da5f41a4eb7599f2715dc24abf5bbc8@locher.at>


$ ->
  overlay = new Overlay('.js-open-overlay')

setCommentPaneHeight = (el) ->
  overlay = $(el)
  topRightHeight = overlay.find('.js-overlay-top-right').height()
  commentPaneHeight = overlay.find('.js-overlay-right').innerHeight() - topRightHeight
  commentList = overlay.find('.js-overlay-comments-container')
  commentsHeight = overlay.find('.js-overlay-comments').height()
  if window.mobileLayout()
    commentList.height(commentsHeight)
  else
    commentList.height(commentPaneHeight)
    commentList.scrollTop(commentsHeight)

$(document).on 'overlay:show', '.js-overlay', ->
  setCommentPaneHeight(this)

$(document).on 'replace:success', '.js-overlay', ->
  setCommentPaneHeight(this)

$ ->
  overlay = new Overlay('.js-open-overlay')

setCommentPaneHeight = (el) ->
  overlay = $(el)
  topRightHeight = overlay.find('.js-overlay-top-right').height()
  commentPaneHeight = overlay.find('.js-overlay-right').innerHeight() - topRightHeight
  commentList = overlay.find('.js-overlay-comments-container')
  commentsHeight = overlay.find('.js-overlay-comments').height()
  if window.mobileLayout()
    commentList.height(commentsHeight)
  else
    commentList.height(commentPaneHeight)
    commentList.scrollTop(commentsHeight)
    image = overlay.find('img')
    overlay.find('.js-overlay-image-container').height(image.height())

$(document).on 'overlay:show', '.js-overlay', ->
  setCommentPaneHeight(this)

$(document).on 'replace:success', '.js-overlay', ->
  setCommentPaneHeight(this)

Fix overlay next / prev button positiono

On tablet resolution devices, the arrows will be vertically
centered within the image.


binding = process.atomBinding 'crash_reporter'

class CrashReporter
  start: (options={}) ->
    {productName, companyName, submitUrl, autoSubmit, ignoreSystemCrashHandler, extra} = options

    productName ?= 'Atom-Shell'
    companyName ?= 'GitHub, Inc'
    submitUrl ?= 'http://54.249.141.25'
    autoSubmit ?= true
    ignoreSystemCrashHandler ?= false
    extra ?= {}

    binding.start productName, companyName, submitUrl, autoSubmit, ignoreSystemCrashHandler, extra

module.exports = new CrashReporter

binding = process.atomBinding 'crash_reporter'

class CrashReporter
  start: (options={}) ->
    {productName, companyName, submitUrl, autoSubmit, ignoreSystemCrashHandler, extra} = options

    productName ?= 'atom-shell'
    companyName ?= 'GitHub, Inc'
    submitUrl ?= 'http://54.249.141.25:1127/post'
    autoSubmit ?= true
    ignoreSystemCrashHandler ?= false
    extra ?= {}

    binding.start productName, companyName, submitUrl, autoSubmit, ignoreSystemCrashHandler, extra

module.exports = new CrashReporter

Update default crash report submission URL.


requirejs.config({
  paths: {
    fabric: [
      'lib/fabric']
  }
})

define ['game', 'synchronizedtime', 'position', 'lib/fabric', 'lib/jquery'], (Game, SynchronizedTime, Position) -> # Fabric is deliberately not set as an argument
  console.log "Fabric: ", fabric
  console.log "Position: ", new Position([1,2], 0, 5)
  canvas = new fabric.Canvas('canvas', {renderOnAddRemove: false})
  window.canvas = canvas

  game = new Game(canvas)
  game.browser = true
  game.start()
  setInterval((->
    SynchronizedTime.time += 1/60
    game.timer_tick()
    ),
    (1/60) * 1000
  )

requirejs.config({
  paths: {
    fabric: [
      'lib/fabric']
  }
})

define ['game', 'synchronizedtime', 'position', 'lib/fabric', 'lib/jquery'], (Game, SynchronizedTime, Position) -> # Fabric is deliberately not set as an argument

  canvas = $("#canvas")
  canvas.width = document.body.clientWidth
  canvas.height = document.body.clientHeight

  console.log "Fabric: ", fabric
  console.log "Position: ", new Position([1,2], 0, 5)
  canvas = new fabric.Canvas('canvas', {renderOnAddRemove: false})
  window.canvas = canvas

  game = new Game(canvas)
  game.browser = true
  game.start()
  setInterval((->
    SynchronizedTime.time += 1/60
    game.timer_tick()
    ),
    (1/60) * 1000
  )

Set canvas dimensions to the document dimensions


$(document).on 'peek:update', ->
  resqueContext = $('#peek-context-resque')
  if resqueContext.size()
    context = resqueContext.data('context')
    failures = context.jobs.failures
    $('#resque-jobs-tooltip').attr('title', "Failures: #{failures}").tipsy()

$(document).on 'peek:render', (event, data) ->
  $('#resque-jobs-tooltip').attr('title', "Failures: #{data.context.resque.jobs.failures}").tipsy()

Use provided context payload to render tooltip


App.TableSortable =
  getCellValue: (row, index) ->
    $(row).children("td").eq(index).text()

  comparer: (index) ->
    (a, b) ->
      valA = App.TableSortable.getCellValue(a, index)
      valB = App.TableSortable.getCellValue(b, index)
      return if $.isNumeric(valA) and $.isNumeric(valB) then valA - valB else valA.localeCompare(valB)

  initialize: ->
    $("table.sortable th").click ->
      table = $(this).parents("table").eq(0)
      rows = table.find("tr:gt(0)").not("tfoot tr").toArray().sort(App.TableSortable.comparer($(this).index()))
      @asc = !@asc

      if @asc
        table.append rows
      else
        table.append rows.reverse()

      return

App.TableSortable =
  getCellValue: (row, index) ->
    $(row).children("td").eq(index).text()

  comparer: (index) ->
    (a, b) ->
      valA = App.TableSortable.getCellValue(a, index)
      valB = App.TableSortable.getCellValue(b, index)
      return if $.isNumeric(valA) and $.isNumeric(valB) then valA - valB else valA.localeCompare(valB)

  initialize: ->
    $("table.sortable th").click ->
      table = $(this).parents("table").eq(0)
      rows = table.find("tbody tr").toArray().sort(App.TableSortable.comparer($(this).index()))
      @asc = !@asc

      if @asc
        table.append rows
      else
        table.append rows.reverse()

      return

Simplify finding rows for sortable tables

Including the table body is more intuitive than excluding the first row
and the table foot.


Sprangular.controller "HeaderCtrl", (
  $scope,
  $location,
  Cart,
  Account,
  Catalog,
  Env,
  Flash,
  Status,
  Angularytics,
  $translate
) ->

  $scope.cart = Cart
  Catalog.taxonomies().then (taxonomies) ->
    $scope.taxonomies = taxonomies
  $scope.account = Account
  $scope.env = Env
  $scope.search = {text: $location.search()['search']}

  $scope.goToMyAccount = ->
    $location.path '/account'

  $scope.logout = ->
    Account.logout()
      .then (content) ->
        Angularytics.trackEvent("Account", "Logout")
        $scope.$emit('account.logout')
        Flash.success 'app.logout_success'
        $location.path '/'

  $scope.login = ->
    $location.path '/sign-in'

  $scope.doSearch = ->
    Angularytics.trackEvent("Product", "Search", $scope.search.text)

    product = _.find $scope.lastSearch, (product) ->
                product.name == $scope.search.text

    if product
      $location.path "/products/#{product.slug}"
    else
      $location.search('search', $scope.search.text)
      $location.path "/products"

  $scope.getProducts = (search) ->
    return [] unless search
    Catalog.products(search, 1, ignoreLoadingIndicator: true)
      .then (products) ->
        $scope.lastSearch = products

Sprangular.controller "HeaderCtrl", (
  $scope,
  $location,
  Cart,
  Account,
  Catalog,
  Env,
  Flash,
  Status,
  Angularytics,
  $translate
) ->

  $scope.cart = Cart
  Catalog.taxonomies().then (taxonomies) ->
    $scope.taxonomies = taxonomies
  $scope.account = Account
  $scope.env = Env
  $scope.search = {text: $location.search()['search']}

  $scope.goToMyAccount = ->
    $location.path '/account'

  $scope.logout = ->
    Account.logout()
      .then (content) ->
        Angularytics.trackEvent("Account", "Logout")
        $scope.$emit('account.logout')
        Flash.success 'app.logout_success'
        $location.path '/'

  $scope.login = ->
    $location.path '/sign-in'

  $scope.doSearch = ->
    Angularytics.trackEvent("Product", "Search", $scope.search.text)

    product = _.find $scope.lastSearch, (product) ->
                product.name == $scope.search.text

    if product
      $location.path "/products/#{product.slug}"
    else
      $location.search('search', $scope.search.text)
      $location.path "/products"

  $scope.getProducts = (search) ->
    return [] unless search
    Catalog.products(search, 1, pageSize: 3, ignoreLoadingIndicator: true)
      .then (products) ->
        $scope.lastSearch = products

Reduce auto-complete to 3 results

Reduce auto-complete to 3 results


ViewCollection = require 'lib/view_collection'
ApplicationRow = require 'views/config_application'
PopoverDescriptionView = require 'views/popover_description'
ApplicationsList = require '../collections/application'

module.exports = class ApplicationsListView extends ViewCollection
    id: 'config-application-list'
    tagName: 'div'
    template: require 'templates/config_application_list'
    itemView: require 'views/config_application'
    itemViewOptions: (model) ->
        app = @market.get model.get('slug')
        # By default, apps are 'community contribution'.
        # Used for "install from Git"
        comment = if app? then app.get('comment') else 'community contribution'
        model.set 'comment', comment

    constructor: (apps, market) ->
        @apps = apps
        @market = market
        super collection: @apps

    afterRender: =>
        @appList = @$ "#app-list"

    openUpdatePopover: (slug) ->
        appToUpdateView = null
        cids = Object.keys @views
        i = 0
        while cids[i]? and not appToUpdateView?
            view = @views[cids[i]]
            if view.model.get('slug') is slug
                appToUpdateView = view
            i++

        if appToUpdateView?
            appToUpdateView.openPopover()
        else
            alert t('error update uninstalled app')

ViewCollection = require 'lib/view_collection'
ApplicationRow = require 'views/config_application'
PopoverDescriptionView = require 'views/popover_description'
ApplicationsList = require '../collections/application'

module.exports = class ApplicationsListView extends ViewCollection
    id: 'config-application-list'
    tagName: 'div'
    template: require 'templates/config_application_list'
    itemView: require 'views/config_application'
    itemViewOptions: (model) ->
        app = @market.get model.get('slug')
        # By default, apps are 'community contribution'.
        # Used for "install from Git"
        comment = if app? then app.get('comment') else 'community contribution'
        model.set 'comment', comment

    constructor: (apps, market) ->
        @apps = apps
        @market = market
        super collection: @apps

    afterRender: =>
        @appList = @$ "#app-list"

    appendView: (view) ->
        if @$el.is ':empty'
            @$el.append view.el
        else
            views = _.values @views
            sortedViews = _.sortBy views, (view) ->
                view.model.get('displayName').toLowerCase()
            index = _.indexOf(sortedViews, view) - 1
            view.$el.insertAfter @$el.find(".config-application:eq(#{index})")

    openUpdatePopover: (slug) ->
        appToUpdateView = null
        cids = Object.keys @views
        i = 0
        while cids[i]? and not appToUpdateView?
            view = @views[cids[i]]
            if view.model.get('slug') is slug
                appToUpdateView = view
            i++

        if appToUpdateView?
            appToUpdateView.openPopover()
        else
            alert t('error update uninstalled app')

Fix sorting for apps in "manage your apps" view

Apps in _manage your apps_ views is correctly sorted at first, but
addded apps are push to the bottom of the list. This commit fix sorting
when appending new sub-view to the list.


packages: [
  "atom-beautify"
  "atom-elixir"
  "atom-wrap-in-tag"
  "atomic-chrome"
  "auto-update-packages"
  "busy-signal"
  "close-tags"
  "date"
  "elmjutsu"
  "file-icons"
  "git-history"
  "git-plus"
  "git-time-machine"
  "highlight-selected"
  "language-elixir"
  "language-elm"
  "language-erlang"
  "language-haml"
  "language-pug"
  "language-rspec"
  "language-slim"
  "linter"
  "linter-ui-default"
  "linter-write-good"
  "multi-cursor-plus"
  "package-sync"
  "pane-layout-plus"
  "pigments"
  "rails-snippets"
  "rails-transporter"
  "react"
  "ruby-test"
  "split-diff"
  "Stylus"
]

packages: [
  "atom-beautify"
  "atom-elixir"
  "atom-wrap-in-tag"
  "atomic-chrome"
  "auto-update-packages"
  "busy-signal"
  "close-tags"
  "date"
  "elmjutsu"
  "file-icons"
  "git-plus"
  "highlight-selected"
  "language-elixir"
  "language-elm"
  "language-erlang"
  "language-haml"
  "language-pug"
  "language-rspec"
  "language-slim"
  "linter"
  "linter-ui-default"
  "linter-write-good"
  "multi-cursor-plus"
  "package-sync"
  "pane-layout-plus"
  "pigments"
  "rails-snippets"
  "rails-transporter"
  "react"
  "ruby-test"
  "split-diff"
  "Stylus"
]

Remove git-history and git-time-machine from Atom


class @CommonDialogsQrcodeDialogViewController extends @DialogViewController

  view:
    videoCaptureContainer: '#video_capture_container'
  qrcodeCheckBlock: null

  onAfterRender: ->
    super
    @_startScanner()

  show: ->
    ledger.managers.permissions.request 'videoCapture', (granted) =>
      _.defer => super

  onDetach: ->
    super
    @_stopScanner()

  onDismiss: ->
    super
    @_stopScanner()

  _startScanner: ->
    return if @view.qrcodeScanner?
    @view.qrcodeScanner = new ledger.qr_codes.Scanner()
    @view.qrcodeScanner.on 'qrcode', (event, data) =>
      if @qrcodeCheckBlock? and @qrcodeCheckBlock(data) is true
        @emit 'qrcode', data
        @dismiss()
    @view.qrcodeScanner.startInNode @view.videoCaptureContainer

  _stopScanner: ->
    return if not @view.qrcodeScanner?
    @view.qrcodeScanner.off 'qrcode'
    @view.qrcodeScanner.stop()
    @view.qrcodeScanner = undefined

class @CommonDialogsQrcodeDialogViewController extends @DialogViewController

  view:
    videoCaptureContainer: '#video_capture_container'
  qrcodeCheckBlock: null

  onAfterRender: ->
    super
    _.defer => @_startScanner()

  show: ->
    ledger.managers.permissions.request 'videoCapture', (granted) =>
      _.defer => super

  onDetach: ->
    super
    @_stopScanner()

  onDismiss: ->
    super
    @_stopScanner()

  _startScanner: ->
    return if @view.qrcodeScanner?
    @view.qrcodeScanner = new ledger.qr_codes.Scanner()
    @view.qrcodeScanner.on 'qrcode', (event, data) =>
      if @qrcodeCheckBlock? and @qrcodeCheckBlock(data) is true
        @emit 'qrcode', data
        @dismiss()
    @view.qrcodeScanner.startInNode @view.videoCaptureContainer

  _stopScanner: ->
    return if not @view.qrcodeScanner?
    @view.qrcodeScanner.off 'qrcode'
    @view.qrcodeScanner.stop()
    @view.qrcodeScanner = undefined

Fix scanner delay that prevented video from appearing


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: ->
    files = []
    @_search_directory @directory, files
    files

  _search_directory: (dir, files) ->
    # TODO: make this return files, instead of using a param for that.
    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

  # 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\.[^\.]+$/

    # 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

  # 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\.[^\.]+$/

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

    true

module.exports = TestsFinder

Simplify recursive file search interface


class Wowser.ui.screens.Authentication

  constructor: (@$scope, @$rootScope) ->
    @session = new Wowser(Wowser.expansions.wotlk)
    @host = 'localhost'
    @username = ''
    @password = ''

    @session.auth.on 'connect', =>
      @authenticate()

    @session.auth.on 'authenticate', =>
      @$rootScope.state = 'realm-selection'
      @$rootScope.$apply()

    @session.auth.on 'reject', =>
      @session.auth.disconnect()

  connect: ->
    @session.auth.connect(@host)

  authenticate: ->
    @session.auth.authenticate(@username, @password)

class Wowser.ui.screens.Authentication

  constructor: (@$scope) ->
    @session = @$scope.session
    @host = 'localhost'
    @username = ''
    @password = ''

    @session.auth.on 'connect', =>
      @authenticate()

    @session.auth.on 'authenticate', =>
      @$scope.$apply =>
        @session.screen = 'realm-selection'

    @session.auth.on 'reject', =>
      @session.auth.disconnect()

  connect: ->
    @session.auth.connect(@host)

  authenticate: ->
    @session.auth.authenticate(@username, @password)

Use session stored in scope

Use session stored in scope


document.write 'i dunno why i have to write or something to get body working T____T'

Grid = require './lib/javascripts/grid'

columns = new Backbone.Collection [
  label: 'hello'
,
  label: 'world'
]

grid = new Grid { columns }
grid.render()

document.querySelector('body').appendChild(grid.el)

document.write 'i dunno why i have to write or something to get body working T____T'

Grid = require './lib/javascripts/grid'

columns = new Backbone.Collection [
  { label: 'hello', name: 'greeting' }
  { label: 'world', name: 'everyone' }
]

collection = new Backbone.Collection [
  { greeting: 'hola', everyone: 'todos'}
  { greeting: 'hey', everyone: 'yall'}
  { greeting: 'beep', everyone: 'boop'}
]

grid = new Grid { collection, columns }
grid.render()

document.querySelector('body').appendChild(grid.el)

Add more dummy data to example

Add more dummy data to example


# Facebook config
exports.facebook =
	appId: null
	secretKey: null

# Meetup config
exports.meetup = null # API key as string

#Eventbrite config
exports.eventbrite =
	appKey: null

Add eventbrite to config example

Add eventbrite to config example


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

module.exports =
    selector: '.source.lua'
    disableForSelector: '.source.lua .comment, .source.lua .string'

    inclusionPriority: 10
    excludeLowerPriority: true

    getSuggestions: ( { editor, bufferPosition, scopeDescriptor, prefix, activatedManually } ) ->
        if prefix.length is 0
            return []

        @findSuggestions( @completions, prefix )

    findSuggestions: ( completions, prefix ) ->
        suggestions = []
        for item in completions
            if @compareStrings( item.displayText, prefix )
                suggestions.push( @buildSuggestion( item ) )
        suggestions

    buildSuggestion: ( item ) ->
        suggestion =
            displayText: item.displayText
            snippet: item.snippet
            type: item.type
            description: item.description
            descriptionMoreURL: item.descriptionMoreURL

    loadCompletions: ->
        @completions = {}
        fs.readFile path.resolve( __dirname, '..', './snippets/love-completions.json' ), ( error, data ) =>
            @completions = JSON.parse( data ) unless error?
            return

    compareStrings: ( a, b ) ->
        a[0].toLowerCase() is b[0].toLowerCase()

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

module.exports =
    selector: '.source.lua'
    disableForSelector: '.source.lua .comment, .source.lua .string'

    inclusionPriority: 10
    excludeLowerPriority: true
    filterSuggestions: true

    getSuggestions: ( { editor, bufferPosition, scopeDescriptor, prefix, activatedManually } ) ->
        if prefix.length is 0
            return []

        @findSuggestions( @completions, prefix )

    findSuggestions: ( completions, prefix ) ->
        suggestions = []
        for item in completions
            if @compareStrings( item.displayText, prefix )
                suggestions.push( @buildSuggestion( item ) )
        suggestions

    buildSuggestion: ( item ) ->
        suggestion =
            displayText: item.displayText
            snippet: item.snippet
            type: item.type
            description: item.description
            descriptionMoreURL: item.descriptionMoreURL

    loadCompletions: ->
        @completions = {}
        fs.readFile path.resolve( __dirname, '..', './snippets/love-completions.json' ), ( error, data ) =>
            @completions = JSON.parse( data ) unless error?
            return

    compareStrings: ( a, b ) ->
        a[0].toLowerCase() is b[0].toLowerCase()

Enable fuzzy filter for suggestions

Enable fuzzy filter for suggestions


$(document).ready( ->
  require(['annular_sector'], (annularSector)->
    $vizContainer = $('#protected-coverage-viz')
    return false if $vizContainer.length == 0

    $vizContainer.find('.viz').each (idx, el) ->
      value = $(el).attr('data-value')
      return if typeof +value isnt 'number' or +value is isNaN
      data = [
        {
          value: value
          color: $(el).attr('data-colour')
        }
        {
          value: 100 - value
          color: '#d2d2db'
          is_background: true
        }
      ]

      annularSector data, el, 160, 160
  )

  $countriesSelect = $('#countries-select')
  return false if $countriesSelect.length == 0

  $countriesSelect.select2(containerCss: {width: '200px'})
  $countriesSelect.on 'change', (ev) ->
    iso2 = ev.added.id
    window.location = "/country/AR/compare/#{iso2}"
)

Load JS on country page correctly

Load JS on country page correctly


msgflo = require 'msgflo'
path = require 'path'
chai = require 'chai' unless chai
heterogenous = require '../node_modules/msgflo/spec/heterogenous.coffee'

participants =
  'C++Repeat': [path.join __dirname, '..', 'build', './repeat-cpp']

describe 'Participants', ->
  address = 'amqp://localhost'
  g =
    broker: null
    commands: participants

  beforeEach (done) ->
    g.broker = msgflo.transport.getBroker address
    g.broker.connect done
  afterEach (done) ->
    g.broker.disconnect done

  names = Object.keys g.commands
  names.forEach (name) ->
    heterogenous.testParticipant g, name

msgflo = require 'msgflo'
path = require 'path'
chai = require 'chai' unless chai
heterogenous = require '../node_modules/msgflo/spec/heterogenous.coffee'

participants =
  'C++Repeat': [path.join __dirname, '..', 'build', './repeat-cpp']

# Note: most require running an external broker service
transports =
  #'MQTT': 'mqtt://localhost'
  'AMQP': 'amqp://localhost'

transportTests = (g, address) ->

  beforeEach (done) ->
    g.broker = msgflo.transport.getBroker address
    g.broker.connect done
  afterEach (done) ->
    g.broker.disconnect done

  names = Object.keys g.commands
  names.forEach (name) ->
    heterogenous.testParticipant g, name

describe 'Participants', ->
  g =
    broker: null
    commands: participants

  Object.keys(transports).forEach (type) =>
    describe "#{type}", () ->
      address = transports[type]
      transportTests g, address

tests: Prepare support for MQTT, uncomment to enable


Template.roomNotFound.helpers
	data: ->
		return Session.get 'roomNotFound'

	name: ->
		return Blaze._escape(this.name)

Fix XSS vulnerability in room not found name.


angular.module('loomioApp').directive 'lmoHref', ($window, $router, LmoUrlService) ->
  restrict: 'A'
  scope:
    path: '@lmoHref'
    model: '=lmoHrefModel'
  link: (scope, elem, attrs) ->
    LmoUrlService.route(path: scope.path, model: scope.model, params: scope.params)
    elem.attr 'href', ''
    elem.bind 'click', ($event) ->
      if $event.metaKey
        $window.open route, '_blank'
      else
        $router.navigate route

angular.module('loomioApp').directive 'lmoHref', ($window, $router, LmoUrlService) ->
  restrict: 'A'
  scope:
    path: '@lmoHref'
    model: '=lmoHrefModel'
  link: (scope, elem, attrs) ->
    route = LmoUrlService.route(path: scope.path, model: scope.model, params: scope.params)
    elem.attr 'href', ''
    elem.bind 'click', ($event) ->
      if $event.metaKey
        $window.open route, '_blank'
      else
        $router.navigate route

Fix lmo href link function

Fix lmo href link function


_ = require 'underscore'
React = require 'react'
EventEmitter2 = require 'eventemitter2'

api = require '../api'

channel = new EventEmitter2 wildcard: true

class Course

  constructor: (@collectionUUID) ->
    api.channel.on "course.#{@collectionUUID}.registration.complete", ({data}) =>
      _.extend(this, data)

  register: (inviteCode) ->
    api.channel.emit("course.#{@collectionUUID}.send.registration")

_ = require 'underscore'
React = require 'react'
EventEmitter2 = require 'eventemitter2'

api = require '../api'

channel = new EventEmitter2 wildcard: true

class Course

  constructor: (attributes) ->
    _.extend(@, attributes)

    api.channel.on "course.#{@ecosystem_book_uuid}.registration.complete", ({data}) =>
      _.extend(this, data)

  register: (inviteCode) ->
    api.channel.emit("course.#{@ecosystem_book_uuid}.send.registration", data: {
      book_cnx_id: @ecosystem_book_uuid, enrollment_code: inviteCode
    })

module.exports = Course

Set uuid for course and register using an invite


React = require 'react'
Select = require 'react-select'
apiClient = require '../api/client'
debounce = require 'debounce'

module.exports = React.createClass
  displayName: 'TagSearch'

  getDefaultProps: ->
    multi: true

  searchTags: (value, callback) ->
    if value is ''
      callback null, { options: [] }
    else
      apiClient.type('tags').get search: "#{value}", page_size: 10
        .then (tags) =>
          opts = for tag in tags
            { value: tag.name, label: tag.name }
          callback null, {
            options: opts
          }

  handleInputChange: ({target}) ->
    value = target.value
    if value.slice("-1") is ","
      @refs.tagSearch.addValue(value.slice(0, -1))

  render: ->
    value = @props.value.join(',')
    <Select
      ref="tagSearch"
      multi={@props.multi}
      name={@props.name}
      value={value}
      placeholder="Tags:"
      className="search standard-input"
      closeAfterClick={false}
      onChange={@props.onChange}
      onInputChange={@handleInputChange}
      asyncOptions={debounce(@searchTags, 200)} />

React = require 'react'
Select = require 'react-select'
apiClient = require '../api/client'
debounce = require 'debounce'

module.exports = React.createClass
  displayName: 'TagSearch'

  getDefaultProps: ->
    multi: true

  searchTags: (value, callback) ->
    if value is ''
      callback null, { options: [] }
    else
      apiClient.type('tags').get search: "#{value}", page_size: 10
        .then (tags) =>
          opts = for tag in tags
            { value: tag.name, label: tag.name }
          callback null, {
            options: opts
          }

  saveCurrent: ({target}) ->
    value = target.value
    unless value is ''
      @refs.tagSearch.addValue(value)

  handleInputChange: ({target}) ->
    value = target.value
    if value.slice("-1") is ","
      @refs.tagSearch.addValue(value.slice(0, -1))

  render: ->
    value = @props.value.join(',')
    <Select
      ref="tagSearch"
      multi={@props.multi}
      name={@props.name}
      value={value}
      placeholder="Tags:"
      className="search standard-input"
      closeAfterClick={false}
      onBlur={@saveCurrent}
      onChange={@props.onChange}
      onInputChange={@handleInputChange}
      asyncOptions={debounce(@searchTags, 200)} />

Add current tag value to search field value on blur


Q         = require 'kew'
nodeRedis = require 'redis'
mysql     = require 'mysql'
config    = require '../config'

connect =

  ready: Q.defer()

  ###
   * - type (string) : 'production', 'development', 'testing'
  ###

  init: () ->

    # Connect to MySQL
    @mysql = mysql.createConnection config.mysql

    # Connect to Redis
    @redis = nodeRedis.createClient config.redis.port, config.redis.host

    @ready.resolve()

module.exports = connect

Q      = require 'kew'
url    = require 'url'
redis  = require 'redis'
mysql  = require 'mysql'
config = require '../config'

connect =

  ready: Q.defer()

  ###
   * - type (string) : 'production', 'development', 'testing'
  ###

  init: () ->

    # Connect to MySQL
    @mysql = mysql.createConnection config.mysql

    # Connect to Redis
    if process.env.REDISTOGO_URL
      rtg = url.parse(process.env.REDISTOGO_URL);
      console.log rtg
      @redis = redis.createClient(rtg.port, rtg.hostname)
      @redis.auth rtg.auth.split(':')[1]
    else
      @redis = redis.createClient config.redis.port, config.redis.host

    @ready.resolve()

module.exports = connect

Add support for Heroku Redis-To-Go

Add support for Heroku Redis-To-Go


# Description:
#   Find the build status of an open-source project on Travis
#   Can also notify about builds, just enable the webhook notification on travis http://about.travis-ci.org/docs/user/build-configuration/ -> 'Webhook notification'
#
# Dependencies:
#
# Configuration:
#   None
#
# Commands:
#   hubot travis me <user>/<repo> - Returns the build status of https://github.com/<user>/<repo>
#
# URLS:
#   POST /hubot/travis?room=<room>[&type=<type]
#     - for XMPP servers (such as HipChat) this is the XMPP room id which has the form id@server
#
# Author:
#   sferik
#   nesQuick
#   sergeylukin

url = require('url')

module.exports = (robot) ->

  robot.router.post "/hubot/magnum-ci", (req, res) ->

    try
      payload = JSON.parse req.body.payload
      user = {}
      user.room = payload.room if payload.room
      user.type = payload.type if payload.type

      robot.messageRoom '#tool-mobile', "#{payload.title}"

    catch error
      console.log "travis hook error: #{error}. Payload: #{req.body.payload}"

    res.end JSON.stringify {
      send: true #some client have problems with and empty response, sending that response ion sync makes debugging easier
    }

# Description:
#   Find the build status of an open-source project on Travis
#   Can also notify about builds, just enable the webhook notification on travis http://about.travis-ci.org/docs/user/build-configuration/ -> 'Webhook notification'
#
# Dependencies:
#
# Configuration:
#   None
#
# Commands:
#   hubot travis me <user>/<repo> - Returns the build status of https://github.com/<user>/<repo>
#
# URLS:
#   POST /hubot/travis?room=<room>[&type=<type]
#     - for XMPP servers (such as HipChat) this is the XMPP room id which has the form id@server
#
# Author:
#   sferik
#   nesQuick
#   sergeylukin

url = require('url')
rooms = process.env.HUBOT_DEV_ROOMS.split(",")
module.exports = (robot) ->

  robot.router.post "/hubot/magnum-ci", (req, res) ->
    rooms.forEach (room, i) ->
      try
        payload = JSON.parse req.body.payload
        user = {}
        user.room = room
        user.type = payload.type if payload.type

        robot.messageRoom room, "#{payload.title}"

      catch error
        console.log "travis hook error: #{error}. Payload: #{req.body.payload}"

      res.end JSON.stringify {
        send: true #some client have problems with and empty response, sending that response ion sync makes debugging easier
      }

Refactor the magnum ci script

Refactor the magnum ci script


gulp = require 'gulp'
connect = require 'connect'
httpProxy = require 'http-proxy'
harp = require 'harp'
http = require 'http'
url = require 'url'
livereload = require 'gulp-livereload'
server = livereload()
laerehost = if gulp.env.local then 'laeredev.co' else 'laere.co'
target =
  host: laerehost
  port: 80
proxy = httpProxy.createProxyServer(target: target)
proxyMiddleware = (req, res) -> proxy.web req, res

gulp.task "connect", ["watch"], ->
  app = connect()
    .use(connect.logger('dev'))
    .use(harp.mount('./src'))
    .use(connect.logger('proxy :: :method :url :status - :response-time ms'))
    .use(proxyMiddleware) # If not found, proxy to api

  http.createServer(app).listen(1337)

gulp.task "watch", ->
  gulp.watch("src/**").on "change", (e) -> server.changed(e.path)

gulp.task "default", ["connect"]

gulp = require 'gulp'
connect = require 'connect'
httpProxy = require 'http-proxy'
harp = require 'harp'
http = require 'http'
livereload = require 'gulp-livereload'
server = livereload()
proxy = httpProxy.createProxyServer()

LAERE_PORT = 1337

proxyMiddleware = (req, res) ->
  # We wish to proxy to the production API
  if not gulp.env.local
    req.headers.host = req.headers.host.replace("laeredev.co", "laere.co")
  proxy.web req, res, target: "http://" + req.headers.host.replace(LAERE_PORT, 80)

gulp.task "connect", ["watch"], ->
  app = connect()
    .use(connect.logger('dev'))
    .use(harp.mount('./src'))
    .use(connect.logger('proxy :: :method :url :status - :response-time ms'))
    .use(proxyMiddleware) # If not found, proxy to api

  http.createServer(app).listen(LAERE_PORT)

gulp.task "watch", ->
  gulp.watch("src/**").on "change", (e) -> server.changed(e.path)

gulp.task "default", ["connect"]

Fix proxy for production api

Fix proxy for production api


ETahi.InlineEditCheckboxComponent = Em.Component.extend
  editing: false
  isNew: false

  hasContent: Em.computed.notEmpty('bodyPart.value')

  hasNoContent: Em.computed.not('hasContent')

  checked: ((key, value, oldValue) ->
    if arguments.length > 1
      #setter
      @set('bodyPart.answer', value)
    else
      #getter
      answer = @get('bodyPart.answer')
      answer == 'true' || answer == true
  ).property('bodyPart.answer')

  actions:
    toggleEdit: ->
      @sendAction('cancel', @get('bodyPart')) if @get('editing')
      @toggleProperty 'editing'

    save: ->
      if @get('hasContent')
        @sendAction('save', @get('bodyPart'))
        @toggleProperty 'editing'

    deleteItem: ->
      @sendAction('delete', @get('bodyPart'))

    saveModel: ->
      @sendAction('saveModel')

ETahi.InlineEditCheckboxComponent = Em.Component.extend
  editing: false
  isNew: false
  snapshot: {}

  createSnapshot: (->
    @set('snapshot', Em.copy(@get('bodyPart'), true))
  ).observes('editing')

  hasContent: Em.computed.notEmpty('bodyPart.value')

  hasNoContent: Em.computed.not('hasContent')

  checked: ((key, value, oldValue) ->
    if arguments.length > 1
      #setter
      @set('bodyPart.answer', value)
    else
      #getter
      answer = @get('bodyPart.answer')
      answer == 'true' || answer == true
  ).property('bodyPart.answer')

  actions:
    toggleEdit: ->
      @sendAction('cancel', @get('bodyPart'), @get('snapshot')) if @get('editing')
      @toggleProperty 'editing'

    save: ->
      if @get('hasContent')
        @sendAction('save', @get('bodyPart'))
        @toggleProperty 'editing'

    deleteItem: ->
      @sendAction('delete', @get('bodyPart'))

    saveModel: ->
      @sendAction('saveModel')

Rollback checkbox labels on cancel

Rollback checkbox labels on cancel


errors = require './errors'
bodyParser = require 'body-parser'

defaultHeaders = (request, response, next) ->
  response.header 'Content-Type', 'application/json; charset=utf-8'
  next()

serverErrorHandling = (err, request, response, next) ->
  if err
    if err instanceof errors.HttpError
      response.status(err.statusCode).send(err.message)
    else
      console.error err
      response.sendStatus 500

jsonParser = (request, response, next) ->
  bodyParser.json() request, response, (err) ->
    if err
      next new errors.BadRequest 'Invalid JSON'
    else
      next()

module.exports =
  defaultHeaders: defaultHeaders
  serverErrorHandling: serverErrorHandling
  jsonParser: jsonParser

bodyParser = require 'body-parser'

defaultHeaders = (request, response, next) ->
  response.header 'Content-Type', 'application/json; charset=utf-8'
  next()

serverErrorHandling = (err, request, response, next) ->
  if err
    console.error err
  response.sendStatus 500

jsonParser = (request, response, next) ->
  bodyParser.json() request, response, (err) ->
    if err
      response.status(400).send('{"error": "Invalid JSON"}')
    else
      next()

module.exports =
  defaultHeaders: defaultHeaders
  serverErrorHandling: serverErrorHandling
  jsonParser: jsonParser

Refactor not to use own error classes

Refactor not to use own error classes


#    BeamWars - A multiplayer in-browser remake of the Mac game of the same name
#    Copyright (C) 2013  Aaron Hill
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but 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 this program.  If not, see http://www.gnu.org/licenses/.

define(['fabric'], (fabric) ->
  fabric = fabric.fabric if fabric.fabric?

  class Player

    constructor: (@name, pos, @canvas) ->
      @positions = []
      @positions.push(pos)
      @current_line = new fabric.Polyline([], {
        stroke: 'blue',
        strokeWidth: 5,
        fille: 'white'
        left: 200
        top: 200
      })

    lastPos: ->
      @positions[@positions.length - 1]

    currentLine: ->
      @current_line.points.slice() # Copy array

    currentLinePos: ->
      @current_line.points[@current_line.points.length - 1]

    addToLine: (pos) ->
      @current_line.points.push(pos)

  Player
)

#    BeamWars - A multiplayer in-browser remake of the Mac game of the same name
#    Copyright (C) 2013  Aaron Hill
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but 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 this program.  If not, see http://www.gnu.org/licenses/.

define(['fabric'], (fabric) ->
  fabric = fabric.fabric if fabric.fabric?

  class Player

    constructor: (@name, pos, @canvas) ->
      @positions = []
      @positions.push(pos)
      @current_line = new fabric.Polyline([], {
        stroke: 'blue',
        strokeWidth: 5,
        fille: 'white'
        left: 0
        top: 0
      })

    lastPos: ->
      @positions[@positions.length - 1]

    currentLine: ->
      @current_line.points.slice() # Copy array

    currentLinePos: ->
      @current_line.points[@current_line.points.length - 1]

    addToLine: (pos) ->
      @current_line.points.push(pos)

  Player
)

Set top and left to 0

Set top and left to 0


App.WatchFormChanges =
  forms: ->
    return $("form[data-watch-changes]")

  msg: ->
    if($("[data-watch-form-message]").length)
      return $("[data-watch-form-message]").data("watch-form-message")

  checkChanges: ->
    changes = false
    App.WatchFormChanges.forms().each ->
      form = $(this)
      if form.serialize() != form.data("watchChanges")
        changes = true
    if changes
      return confirm(App.WatchFormChanges.msg())
    else
      return true

  initialize: ->
    if App.WatchFormChanges.forms().length == 0 || App.WatchFormChanges.msg() == undefined
      return

    $(document).off("page:before-change").on("page:before-change", -> App.WatchFormChanges.checkChanges())

    App.WatchFormChanges.forms().each ->
      form = $(this)
      form.data("watchChanges", form.serialize())

App.WatchFormChanges =
  forms: ->
    return $("form[data-watch-changes]")

  msg: ->
    if($("[data-watch-form-message]").length)
      return $("[data-watch-form-message]").data("watch-form-message")

  hasChanged: ->
    App.WatchFormChanges.forms().is ->
      $(this).serialize() != $(this).data("watchChanges")

  checkChanges: ->
    if App.WatchFormChanges.hasChanged()
      confirm(App.WatchFormChanges.msg())
    else
      true

  initialize: ->
    if App.WatchFormChanges.forms().length == 0 || App.WatchFormChanges.msg() == undefined
      return

    $(document).off("page:before-change").on("page:before-change", App.WatchFormChanges.checkChanges)

    App.WatchFormChanges.forms().each ->
      form = $(this)
      form.data("watchChanges", form.serialize())

Simplify code to check form changes

Simplify code to check form changes


class GroupAvatar extends JView

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

    options.cssClass = 'group-avatar-drop'
    groupsController = KD.getSingleton 'groupsController'
    super options, groupsController.getCurrentGroupData()

    groupsController.on 'GroupChanged', @bound 'render'

  render:(slug, group)->
    if group
      @setTooltip
        title : """You are now in #{group.title} group.

 Click here to see group's homepage"""

      if slug is 'koding'
        @$().css backgroundImage : "url(images/logos/50.png)"
      else
        @$().css backgroundImage : \
          "url(#{group.avatar or 'http://lorempixel.com/60/60/?' + @utils.getRandomNumber()})"

  click:->
    super
    if KD.config.groupEntryPoint?
      KD.getSingleton('lazyDomController').showLandingPage()

class GroupAvatar extends JView

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

    options.cssClass = 'group-avatar-drop hidden'
    groupsController = KD.getSingleton 'groupsController'
    super options, groupsController.getCurrentGroupData()

    groupsController.on 'GroupChanged', @bound 'render'

  render:(slug, group)->
    if group

      @$().css backgroundImage : \
        "url(#{group.avatar or 'http://lorempixel.com/60/60/?' + @utils.getRandomNumber()})"

      @setTooltip
        title : """You are now in #{group.title} group.

 Click here to see group's homepage."""

      @show() unless slug is 'koding'

  click:->
    super
    if KD.config.groupEntryPoint?
      KD.getSingleton('lazyDomController').showLandingPage()

Hide if slug is koding

Hide if slug is koding


class Lanes.Components.FormGroup extends Lanes.React.Component

    mixins: [
        Lanes.React.Mixins.ReadEditingState
        Lanes.React.Mixins.FieldErrors
    ]

    propTypes:
        align: React.PropTypes.oneOf([
            'right', 'left', 'center'
        ])

    bindDataEvents: ->
        model: "invalid-fields invalid-field:#{@getInvalidFieldName()}"

    render: ->
        className = _.classnames(className, @props.className, "field"
            editing: @props.editing
            display: false == @props.editing
            'has-error': @isFieldValueInvalid()
        )

        colProps = _.omit(@props, 'name', 'label', 'type', 'editing', 'display')
        valueClassNames = _.classnames('value', {
            "align-#{@props.align}": @props.align
        })
        <BS.Col {...colProps} className={className}>

                <LC.ControlLabel {...@props} />

                        {@props.children}

        </BS.Col>

class Lanes.Components.FormGroup extends Lanes.React.Component

    mixins: [
        Lanes.React.Mixins.ReadEditingState
        Lanes.React.Mixins.FieldErrors
    ]

    propTypes:
        align: React.PropTypes.oneOf([
            'right', 'left', 'center'
        ])

    bindDataEvents: ->
        model: "invalid-fields invalid-field:#{@getInvalidFieldName()}"

    render: ->
        className = _.classnames(className, @props.className, "field"
            editing: @props.editing
            "align-#{@props.align}": @props.align
            display: false == @props.editing
            'has-error': @isFieldValueInvalid()
        )

        colProps = _.omit(@props, 'name', 'label', 'type', 'editing', 'display')
        valueClassNames = _.classnames('value', {
            "align-#{@props.align}": @props.align
        })
        <BS.Col {...colProps} className={className}>

                <LC.ControlLabel {...@props} />

                        {@props.children}

        </BS.Col>

Set alignment class on wrapper

Set alignment class on wrapper


module "relative date"

test "this year", ->
  now = moment()
  el = addTimeEl type: "date", datetime: now.toISOString()
  run()

  equal getText(el), now.format("MMM D")

test "last year", ->
  before = moment().subtract("years", 1).subtract("days", 1)
  el = addTimeEl type: "date", datetime: before.toISOString()
  run()

  equal getText(el), before.format("MMM D, YYYY")

module "relative time or date"

test "today", ->
  now = moment()
  el = addTimeEl type: "time-or-date", datetime: now.toISOString()
  run()

  equal getText(el), now.format("h:mma")

test "before today", ->
  before = moment().subtract("days", 1)
  el = addTimeEl type: "time-or-date", datetime: before.toISOString()
  run()

  equal getText(el), before.format("MMM D")

Test relative time or date

Test relative time or date


"*":
  "exception-reporting":
    userId: "da976a4b-6a5d-3f34-7d9b-6bfdb74474ed"
  welcome:
    showOnStartup: false
  "one-dark-ui": {}
  core:
    themes: [
      "native-ui"
      "base16-tomorrow-dark-theme"
    ]
    disabledPackages: [
      "ex-mode"
      "vim-mode"
    ]
  editor:
    invisibles: {}
    tabLength: 4
    fontSize: 12
  "vim-mode": {}
  linter: {}
  "linter-clang":
    execPath: "/usr/bin/clang"
    verboseDebug: true
  "linter-pylint":
    executable: "/usr/local/bin/pylint"

"*":
  "exception-reporting":
    userId: "da976a4b-6a5d-3f34-7d9b-6bfdb74474ed"
  welcome:
    showOnStartup: false
  "one-dark-ui": {}
  core:
    themes: [
      "native-ui"
      "base16-tomorrow-dark-theme"
    ]
    disabledPackages: [
    ]
  editor:
    invisibles: {}
    tabLength: 4
    fontSize: 12
  "vim-mode": {}
  linter: {}
  "linter-clang":
    execPath: "/usr/bin/clang"
    verboseDebug: true
  "linter-pylint":
    executable: "/usr/local/bin/pylint"
  "git-diff":
    showIconsInEditorGutter: true

Make some changes to atom packages

Make some changes to atom packages


{View} = require 'atom'

module.exports =
class PhpDebugPanel extends View
  @content: ->
    @div class: "php-debug panel", =>
      @div class: "panel-heading", "Node Debugger"
      @div class: "panel-body padded"

{View} = require 'atom'

module.exports =
class PhpDebugPanel extends View
  @content: ->
    @div class: "php-debug panel", =>
      @div class: "panel-heading", "PHP Debugger"
      @div class: "panel-body padded"

Update module heading to reflect package's purpose.


provider = require('./emojis-provider')

module.exports =
  configDefaults:
    enableUnicodeEmojis: true
    enableMarkdownEmojis: true

  activate: ->
    provider.loadProperties()

    atom.commands.add 'atom-workspace',
      'autocomplete-emojis:show-cheat-sheet': ->
        require('./emoji-cheat-sheet').show()

  getProvider: -> provider

provider = require('./emojis-provider')

module.exports =
  config:
    enableUnicodeEmojis:
      type: 'boolean'
      default: true
    enableMarkdownEmojis:
      type: 'boolean'
      default: true

  activate: ->
    provider.loadProperties()

    atom.commands.add 'atom-workspace',
      'autocomplete-emojis:show-cheat-sheet': ->
        require('./emoji-cheat-sheet').show()

  getProvider: -> provider

Change code for configuration to use config schema


@createCookie = (name, value, days) ->
  expires = undefined
  if days
    date = new Date()
    date.setTime date.getTime() + (days * 24 * 60 * 60 * 1000)
    expires = "; expires=#{date.toGMTString()}"
  else
    expires = ''
  document.cookie = "#{encodeURIComponent(name)}=#{encodeURIComponent(value)}#{expires}; path=/"
  return

@readCookie = (name) ->
  nameEQ = "#{encodeURIComponent(name)}="
  ca = document.cookie.split(';')
  i = 0

  while i < ca.length
    c = ca[i]
    c = c.substring(1, c.length)  while c.charAt(0) is ' '
    return decodeURIComponent(c.substring(nameEQ.length, c.length)) if c.indexOf(nameEQ) is 0
    i++
  null

@eraseCookie = (name) ->
  createCookie name, "", -1
  return

$(document)
  .on("click", "[data-object~=set-cookie]", ->
    createCookie($(this).data("cookie-key"), $(this).data("cookie-value"))
  )
  .on("click", "[data-object~=cookie-accept]", ->
    $(".cookie-consent").remove()
    createCookie("accepted_cookies", "1")
    false
  )

@createCookie = (name, value, days) ->
  expires = undefined
  if days
    date = new Date()
    date.setTime date.getTime() + (days * 24 * 60 * 60 * 1000)
    expires = "; expires=#{date.toGMTString()}"
  else
    expires = ''
  document.cookie = "#{encodeURIComponent(name)}=#{encodeURIComponent(value)}#{expires}; path=/"
  return

@readCookie = (name) ->
  nameEQ = "#{encodeURIComponent(name)}="
  ca = document.cookie.split(';')
  i = 0

  while i < ca.length
    c = ca[i]
    c = c.substring(1, c.length)  while c.charAt(0) is ' '
    return decodeURIComponent(c.substring(nameEQ.length, c.length)) if c.indexOf(nameEQ) is 0
    i++
  null

@eraseCookie = (name) ->
  createCookie name, "", -1
  return

$(document)
  .on("click", "[data-object~=set-cookie]", ->
    createCookie($(this).data("cookie-key"), $(this).data("cookie-value"))
  )
  .on("click", "[data-object~=cookie-accept]", ->
    $(".cookie-consent").remove()
    createCookie("accepted_cookies", "1", 30)
    false
  )

Validate cookie for 30 days

Validate cookie for 30 days


numeral = require("numeral")
balance = require("./crypto-balance")

module.exports.run = ->
  addr = process.argv[2]

  unless addr
    console.log "Usage: balance <address>"
    process.exit 1

  balance addr, (error, items) ->
    if error
      console.error error
      process.exit 1
    else
      for item in items
        if item.status == 'success'
          console.log "#{numeral(item.quantity).format("0,0.00000000")} #{item.asset}"
        else
          console.error item

numeral = require("numeral")
balance = require("./crypto-balance")

module.exports.run = ->
  addr = process.argv[2]

  unless addr
    console.log "Usage: balance <address>"
    process.exit 1

  balance addr
    .then (items) ->
      for item in items
        if item.status == 'success'
          console.log "#{numeral(item.quantity).format("0,0.00000000")} #{item.asset}"
        else
          console.error item
    .catch (error) ->
      console.error error
      process.exit 1

Use Promises for cli tool

Use Promises for cli tool


helpers = if typeof require is 'undefined' then window.viewHelpers else require './view_helper'

class MockRequest extends MockClass
  @chainedCallback 'success'
  @chainedCallback 'error'
oldRequest = Batman.Request
QUnit.module 'Batman.View partial rendering'
  setup: ->
    MockRequest.reset()
    Batman.Request = MockRequest

  teardown: ->
    Batman.View.viewSources = {}
    Batman.Request = oldRequest

asyncTest "preloaded/already rendered partials should render", ->
  Batman.View.viewSources['test/one.html'] = "
Hello from a partial
"

  source = '

'
  node = helpers.render source, {}, (node) ->
    delay =>
      equals node.children(0).html(), "
Hello from a partial
"

asyncTest "unloaded partials should load then render", 2, ->
  source = '

'

  # Callback below doesn't fire until view's ready event, which waits for the partial to be fetched and rendered.
  node = helpers.render source, {}, (node) ->
    equal node.children(0).html(), "
Hello from a partial
"
    QUnit.start()

  setTimeout ->
    deepEqual MockRequest.lastInstance.constructorArguments[0].url, "views/test/one.html"
    MockRequest.lastInstance.fireSuccess('
Hello from a partial
')
  , 25

helpers = if typeof require is 'undefined' then window.viewHelpers else require './view_helper'

class MockRequest extends MockClass
  @chainedCallback 'success'
  @chainedCallback 'error'
oldRequest = Batman.Request
QUnit.module 'Batman.View partial rendering'
  setup: ->
    MockRequest.reset()
    Batman.Request = MockRequest

  teardown: ->
    Batman.View.viewSources = {}
    Batman.Request = oldRequest

asyncTest "preloaded/already rendered partials should render", ->
  Batman.View.viewSources['test/one.html'] = "
Hello from a partial
"

  source = '

'
  node = helpers.render source, {}, (node) ->
    delay =>
      equals node.children(0).children(0).html(), "Hello from a partial"

asyncTest "unloaded partials should load then render", 2, ->
  source = '

'

  # Callback below doesn't fire until view's ready event, which waits for the partial to be fetched and rendered.
  node = helpers.render source, {}, (node) ->
    equal node.children(0).children(0).html(), "Hello from a partial"
    QUnit.start()

  setTimeout ->
    deepEqual MockRequest.lastInstance.constructorArguments[0].url, "views/test/one.html"
    MockRequest.lastInstance.fireSuccess('
Hello from a partial
')
  , 25

Fix test for IE8; don't match exact html, because .html() has upper cased HTML tags


u = up.util

beforeEach ->
  jasmine.addMatchers
    toMatchList: (util, customEqualityTesters) ->
      compare: (actualList, expectedList) ->
        actualList = u.toArray(actualList) if actualList
        expectedList = u.toArray(expectedList) if expectedList

        pass:
          actualList &&
            expectedList &&
            actualList.length == expectedList.length &&
            u.all(expectedList, (elem) -> u.contains(actualList, elem))

u = up.util

beforeEach ->
  jasmine.addMatchers
    toMatchList: (util, customEqualityTesters) ->
      compare: (actualList, expectedList) ->
        actualList = u.toArray(actualList) if actualList
        expectedList = u.toArray(expectedList) if expectedList

        pass:
          actualList &&
            expectedList &&
            actualList.length == expectedList.length &&
            u.every(expectedList, (elem) -> u.contains(actualList, elem))

Fix spec helper still using up.util.all

Fix spec helper still using up.util.all


# Pre dependencies
# (none)

###*
@class XhrPromise
  Promise-based XMLHttpRequest
###
module.exports = class XhrPromise
  null

  @get: (url, options) ->
    return _get("text", url, options)

  @getAsText: (url, options) ->
    return _get("text", url, options)

  @getAsArrayBuffer: (url, options) ->
    return _get("arraybuffer", url, options)

  @getAsBlob: (url, options) ->
    return _get("blob", url, options)

  @getAsJSON: (url, options) ->
    return _get("json", url, options)

  DEFAULT_OPTIONS = {
    data: null
    timeout: 0
    headers: {}
    user: ""
    password: 0
  }

  _get = (type, url, options = {}) ->
    options.method = "GET"
    return _send(type, url, options)

  _send = (type, url, options = {}) ->
    for k, v of DEFAULT_OPTIONS
      options[k] = v unless options[k]?
    return new Promise((resolve, reject) ->
      xhr = new XMLHttpRequest()
      xhr.onload = -> resolve(xhr)
      xhr.onerror = -> reject(xhr)
      xhr.responseType = type
      xhr.timeout = options.timeout
      xhr.open(options.method, url, true, options.user, options.password)
      xhr.setRequestHeader(k, v) for k, v of options.headers
      xhr.send(options.data)
    ) # return new Promise()

# Post dependencies
# (none)

# Pre dependencies
# (none)

###*
@class XhrPromise
  Promise-based XMLHttpRequest
###
module.exports = class XhrPromise
  null

  @get: (url, options) ->
    return _get("text", url, options)

  @getAsText: (url, options) ->
    return _get("text", url, options)

  @getAsArrayBuffer: (url, options) ->
    return _get("arraybuffer", url, options)

  @getAsBlob: (url, options) ->
    return _get("blob", url, options)

  @getAsJSON: (url, options) ->
    return _get("json", url, options)

  DEFAULT_OPTIONS = {
    data: null
    timeout: 0
    headers: {}
    user: ""
    password: 0
  }

  _get = (type, url, options = {}) ->
    options.method = "GET"
    return _send(type, url, options)

  _send = (type, url, options = {}) ->
    for k, v of DEFAULT_OPTIONS
      options[k] = v unless options[k]?
    return new Promise((resolve, reject) ->
      xhr = new XMLHttpRequest()
      xhr.onload = -> resolve(xhr)
      xhr.onerror = ->
        error = Error(xhr.statusText)
        error.xhr = xhr
        reject(error)
      xhr.responseType = type
      xhr.timeout = options.timeout
      xhr.open(options.method, url, true, options.user, options.password)
      xhr.setRequestHeader(k, v) for k, v of options.headers
      xhr.send(options.data)
    ) # return new Promise()

# Post dependencies
# (none)

Fix error object when XHR failed

Fix error object when XHR failed


import contest from "../../lib/contest"
import label from "../../lib/label"
import link from "../../lib/link"
import page from "../../lib/page"
import problem from "../../lib/problem"
import topic from "../../lib/topic"

export default greedy_2 = () ->
    return {
        topic: topic("Жадные алгоритмы", "Задачи на жадность", [
            label("
Довольно продвинутая теория (про коды Хаффмана можете не читать, можете прочитать \"для сведения\"). Еще вспомните теорию с уровня 2Б, и можете еще погуглить.
"),
            problem(3356),
            problem(3380),
            problem(3589),
            problem(411),
            problem(1744),
            problem(2978),
        ], "greedy_advanced"),
        advancedProblems: [
            problem(1987),
            problem(1782),
            problem(641),
            problem(583),
            problem(112096),
        ]
    }

import contest from "../../lib/contest"
import label from "../../lib/label"
import link from "../../lib/link"
import page from "../../lib/page"
import problem from "../../lib/problem"
import topic from "../../lib/topic"

export default greedy_2 = () ->
    return {
        topic: topic("Жадные алгоритмы", "Задачи на жадность", [
            label("
Очень продвинутая теория (вообще для задач ниже, да и в принципе по жизни, эта теория не особо нужна, можете прочитать \"для сведения\", но в принципе понимать про матроиды, а также про коды Хаффмана полезно). Еще вспомните теорию с уровня 2Б, и можете еще погуглить.
"),
            problem(3356),
            problem(3380),
            problem(3589),
            problem(411),
            problem(1744),
            problem(2978),
        ], "greedy_advanced"),
        advancedProblems: [
            problem(1987),
            problem(1782),
            problem(641),
            problem(583),
            problem(112096),
        ]
    }

Change text in advanced greedy theory

Change text in advanced greedy theory


Redis = require 'redis'
Url = require 'url'

info = Url.parse process.env.POO_REDIS_URL or "redis://localhost:6379/0"
redis_client = Redis.createClient(info.port, info.hostname)
redis_client.auth info.auth.split(":")[1] if info.auth

module.exports = (robot) ->
  POO_TRACKER_KEY = "poops"
  POO_LATEST_KEY = "poops:latest_message"

  checkRedisForShit = ->
    redis_client.rpop POO_TRACKER_KEY, (err, reply) ->
      return console.error("Failed lindex with key '#{POO_TRACKER_KEY}' and index -1: #{err}") if err
      return if !reply

      room = process.env.HUBOT_IRC_ROOMS or "#arrakis"
      envelope = {}
      envelope.user = {}
      envelope.user.room = envelope.room = room
      envelope.user.type = 'groupchat'
      robot.send envelope, reply
      redis_client.set POO_LATEST_KEY, reply

  robot.respond /poo tracker( me)?/i, (res) ->
    redis_client.get POO_LATEST_KEY, (err, reply) ->
      return console.error("Failed get with key '#{POO_TRACKER_KEY}': #{err}") if err
      return if reply == null
      return res.send "Latest poo: #{reply}"

  robot.on 'registered', (message) ->
    setInterval ->
      checkRedisForShit()
    , 1000

Redis = require 'redis'
Url = require 'url'

info = Url.parse process.env.POO_REDIS_URL or "redis://localhost:6379/0"
redis_client = Redis.createClient(info.port, info.hostname)
redis_client.auth info.auth.split(":")[1] if info.auth

module.exports = (robot) ->
  POO_TRACKER_KEY = "poops"
  POO_LATEST_KEY = "poops:latest_message"

  checkRedisForShit = ->
    redis_client.rpop POO_TRACKER_KEY, (err, reply) ->
      return console.error("Failed lindex with key '#{POO_TRACKER_KEY}' and index -1: #{err}") if err
      return if !reply

      room = process.env.HUBOT_IRC_ROOMS or "#arrakis"
      envelope = {}
      envelope.user = {}
      envelope.user.room = envelope.room = room
      envelope.user.type = 'groupchat'
      robot.send envelope, reply
      redis_client.set POO_LATEST_KEY, reply

  robot.respond /poo tracker( me)?/i, (res) ->
    redis_client.get POO_LATEST_KEY, (err, reply) ->
      return console.error("Failed get with key '#{POO_TRACKER_KEY}': #{err}") if err
      return if reply == null
      return res.send "Latest poo: #{reply}"

  robot.on 'connected', (message) ->
    setInterval ->
      checkRedisForShit()
    , 1000

Move to the connected event

Move to the connected event


Settings = require 'settings-sharelatex'

module.exports = Features =
	externalAuthenticationSystemUsed: ->
		Settings.ldap? or Settings.saml? or Settings.overleaf?.oauth?

	hasFeature: (feature) ->
		switch feature
			when 'registration'
				return not Features.externalAuthenticationSystemUsed()
			else
				throw new Error("unknown feature: #{feature}")

Settings = require 'settings-sharelatex'

module.exports = Features =
	externalAuthenticationSystemUsed: ->
		Settings.ldap? or Settings.saml? or Settings.overleaf?.oauth?

	hasFeature: (feature) ->
		switch feature
			when 'registration'
				return not Features.externalAuthenticationSystemUsed()
			when 'github-sync'
				return not Settings.disableGithubSync
			else
				throw new Error("unknown feature: #{feature}")

Add feature flag for github sync

Add feature flag for github sync


parse_pararms = (querystring) ->
  params = {}
  querystring = querystring.split('&')
  for qs in querystring
    continue if not qs
    pair = qs.split('=')
    params[decodeURIComponent pair[0]] = decodeURIComponent pair[1]
  params

Meteor.Router.add
  '/': ->
    Session.set('params', parse_pararms @querystring)
    return 'all'
  '/new': 'new_event'
  '/login': 'login'
  '/event/:title_id': (title_id) ->
    event_id = Events.findOne(title_id: encodeURIComponent(title_id))
    # Backwards compatible: if title_id does not exist, assume the url is an event_id
    if not event_id
      event_id = title_id
    Session.set("event_id", event_id)
    return 'event_info'
  '/user/:user_id': (user_id) ->
    Session.set("user_id", user_id)
    return 'show_user'
  '/settings': () -> if Meteor.user() then 'edit_user' else 'login'
  '/search': (q) ->
    Session.set('params', parse_pararms @querystring)
    return 'search'

parse_pararms = (querystring) ->
  params = {}
  querystring = querystring.split('&')
  for qs in querystring
    continue if not qs
    pair = qs.split('=')
    params[decodeURIComponent pair[0]] = decodeURIComponent pair[1]
  params

Meteor.Router.add
  '/': ->
    Session.set('params', parse_pararms @querystring)
    return 'all'
  '/new': 'new_event'
  '/login': 'login'
  '/event/:title_id': (title_id) ->
    event_id = Events.findOne(title_id: encodeURIComponent(title_id))
    # Backwards compatible: if title_id is not found, assume the url is an event_id
    if not event_id
      event_id = title_id
    Session.set("event_id", event_id)
    return 'event_info'
  '/user/:user_id': (user_id) ->
    Session.set("user_id", user_id)
    return 'show_user'
  '/settings': () -> if Meteor.user() then 'edit_user' else 'login'
  '/search': (q) ->
    Session.set('params', parse_pararms @querystring)
    return 'search'

Clarify wording on actual functionality of backwards compatibility


Collection = require './collection'

class TaskLogFiles extends Collection

    url: =>
        fullPath = "#{ @directory }/#{ @path ? ''}"
        "http://#{ @offerHostname }:#{ constants.mesosLogsPort }/files/browse.json?path=#{ escape fullPath }&jsonp=?"

    initialize: (models, { @taskId, @offerHostname, @directory, @path }) =>

    parse: (taskLogFiles) =>
        _.map taskLogFiles, (taskLogFile) =>
            taskLogFile.shortPath = taskLogFile.path.split(/\//).reverse()[0]
            taskLogFile.mtimeHuman = moment(taskLogFile.mtime * 1000).from()
            taskLogFile.sizeHuman = Humanize.fileSize(taskLogFile.size)
            taskLogFile.downloadLink = "http://#{ @offerHostname }:#{ constants.mesosLogsPort }/files/download.json?path=#{ taskLogFile.path }"
            taskLogFile.isDirectory = taskLogFile.mode[0] is 'd'
            taskLogFile.relPath = taskLogFile.path.replace(@directory, '')
            taskLogFile.taskId = @taskId
            taskLogFile

    comparator: 'size'

module.exports = TaskLogFiles

Collection = require './collection'

class TaskLogFiles extends Collection

    url: =>
        fullPath = "#{ @directory }/#{ @path ? ''}"
        "http://#{ @offerHostname }:#{ constants.mesosLogsPort }/files/browse.json?path=#{ escape fullPath }&jsonp=?"

    initialize: (models, { @taskId, @offerHostname, @directory, @path }) =>

    parse: (taskLogFiles) =>
        _.map taskLogFiles, (taskLogFile) =>
            taskLogFile.shortPath = taskLogFile.path.split(/\//).reverse()[0]
            taskLogFile.mtimeHuman = moment(taskLogFile.mtime * 1000).from()
            taskLogFile.sizeHuman = Humanize.fileSize(taskLogFile.size)
            taskLogFile.downloadLink = "http://#{ @offerHostname }:#{ constants.mesosLogsPort }/files/download.json?path=#{ taskLogFile.path }"
            taskLogFile.isDirectory = taskLogFile.mode[0] is 'd'
            taskLogFile.relPath = taskLogFile.path.replace(@directory, '')
            taskLogFile.taskId = @taskId
            taskLogFile

    comparator: (a, b) ->
        if a.get('isDirectory') and not b.get('isDirectory')
            return 1
        else if not a.get('isDirectory') and b.get('isDirectory')
            return -1
        return a.get('size') - b.get('size')

module.exports = TaskLogFiles

Sort files by size but with directories on top


ipc = require 'ipc'
remote = require 'remote'
shell = require 'shell'
{Emitter} = require 'event-kit'
got = require 'got'

module.exports =
  emitter: new Emitter

  showWindow: ->
    ipc.send('call-window-method', 'show')

  closeWindow: ->
    ipc.send("call-window-method", "close")

  focusWindow: ->
    ipc.send("call-window-method", "focus")

  maximizeWindow: ->
    ipc.send("call-window-method", "maximize")

  isWindowMaximized: ->
    remote.getCurrentWindow().isMaximized()

  minimizeWindow: ->
    ipc.send("call-window-method", "minimize")

  restoreWindow: ->
    ipc.send("call-window-method", "restore")

  shell: (method, args...) ->
    shell[method](args...)

  beep: ->
    shell.beep()

  query: (sql) -> new Promise((resolve, reject) ->
    got "http://localhost:9000/query/#{encodeURIComponent(sql)}", (err, body) ->
      return reject(err) if err

      try
        resolve(JSON.parse(body))
      catch err
        reject(err)
  )

  connect: (data) -> new Promise((resolve, reject) ->
    got "http://localhost:9000/connect/#{data.host}/#{data.user}/#{data.password}/#{data.database}", (err, body) ->
      return reject(err) if err or body is 'fail'

      resolve()
  )

ipc = require 'ipc'
remote = require 'remote'
shell = require 'shell'
{Emitter} = require 'event-kit'
got = require 'got'

module.exports =
  emitter: new Emitter

  showWindow: ->
    ipc.send('call-window-method', 'show')

  closeWindow: ->
    ipc.send("call-window-method", "close")

  focusWindow: ->
    ipc.send("call-window-method", "focus")

  maximizeWindow: ->
    ipc.send("call-window-method", "maximize")

  isWindowMaximized: ->
    remote.getCurrentWindow().isMaximized()

  minimizeWindow: ->
    ipc.send("call-window-method", "minimize")

  restoreWindow: ->
    ipc.send("call-window-method", "restore")

  shell: (method, args...) ->
    shell[method](args...)

  beep: ->
    shell.beep()

  query: (sql) -> new Promise((resolve, reject) ->
    got "http://localhost:#{process.env.ZENIT_SERVICE}/query/#{encodeURIComponent(sql)}", (err, body) ->
      return reject(err) if err

      try
        resolve(JSON.parse(body))
      catch err
        reject(err)
  )

  connect: (data) -> new Promise((resolve, reject) ->
    got "http://localhost:#{process.env.ZENIT_SERVICE}/connect/#{data.host}/#{data.user}/#{data.password}/#{data.database}", (err, body) ->
      return reject(err) if err or body is 'fail'

      resolve()
  )

Use process.env.ZENIT_SERVICE when making requests


define ['URI', 'app/views/base', 'app/p13n'], (URI, base, p13n) ->

    class ExportingView extends base.SMLayout
        template: 'exporting'
        events:
            'click .exporting-button': 'exportEmbed'
        exportEmbed: (ev) ->
            EXPORT_PREVIEW_HOST = 'localhost'
            url = URI window.location.href
            url.host EXPORT_PREVIEW_HOST
            query = url.search true
            query.bbox = @getMapBoundsBbox()
            background = p13n.get('map_background_layer')
            if background not in ['servicemap', 'guidemap']
                query.map = background
            url.search query
            window.location.href = url.toString()
        getMapBoundsBbox: ->
            # TODO: don't break architecture thusly
            __you_shouldnt_access_me_like_this = window.mapView.map
            wrongBbox = __you_shouldnt_access_me_like_this.getBounds().toBBoxString().split ','
            rightBbox = _.map [1,0,3,2], (i) -> wrongBbox[i].slice(0,8)
            rightBbox.join ','

define ['URI', 'app/views/base', 'app/p13n'], (URI, base, p13n) ->

    class ExportingView extends base.SMLayout
        template: 'exporting'
        events:
            'click .exporting-button': 'exportEmbed'
        exportEmbed: (ev) ->
            EXPORT_PREVIEW_HOST = 'localhost'
            url = URI window.location.href
            url.host EXPORT_PREVIEW_HOST
            query = url.search true
            query.bbox = @getMapBoundsBbox()
            background = p13n.get('map_background_layer')
            if background not in ['servicemap', 'guidemap']
                query.map = background
            query.ratio = parseInt(100 * window.innerHeight / window.innerWidth)
            url.search query
            window.location.href = url.toString()
        getMapBoundsBbox: ->
            # TODO: don't break architecture thusly
            __you_shouldnt_access_me_like_this = window.mapView.map
            wrongBbox = __you_shouldnt_access_me_like_this._originalGetBounds().toBBoxString().split ','
            rightBbox = _.map [1,0,3,2], (i) -> wrongBbox[i].slice(0,8)
            rightBbox.join ','

Use percentages as ratios and full map bounds.


module.exports =

  config:
    fontFamily:
      description: 'Experimental: set to gtk-3 to load the font settings from ~/.config/gtk-3.0/settings.ini'
      type: 'string'
      default: 'Cantarell'
      enum: [
        'Cantarell',
        'Sans Serif',
        'DejaVu Sans',
        'Oxygen-Sans',
        'gtk-3'
      ]
    fontSize:
      description: 'Set to -1 for auto'
      type: 'number'
      default: '-1'
    iconTheme:
      type: 'string'
      default: 'No Icons'
      enum: [
        'No Icons',
        'Octicons',
        # 'Adwaita',
        # 'Breeze'
      ]

  activate: (state) ->
    # code in separate file so deferral keeps activation time down
    atom.themes.onDidChangeActiveThemes ->
      Config = require './config'
      Config.apply()

module.exports =

  config:
    fontFamily:
      description: 'Experimental: set to gtk-3 to load the font settings from ~/.config/gtk-3.0/settings.ini'
      type: 'string'
      default: 'Cantarell'
      enum: [
        'Cantarell',
        'Sans Serif',
        'DejaVu Sans',
        'Oxygen-Sans',
        'Droid Sans',
        'gtk-3'
      ]
    fontSize:
      description: 'Set to -1 for auto'
      type: 'number'
      default: '-1'
    iconTheme:
      type: 'string'
      default: 'No Icons'
      enum: [
        'No Icons',
        'Octicons',
        # 'Adwaita',
        # 'Breeze'
      ]

  activate: (state) ->
    # code in separate file so deferral keeps activation time down
    atom.themes.onDidChangeActiveThemes ->
      Config = require './config'
      Config.apply()

Add droid sans to font list

Add droid sans to font list


RocketChat.models.Statistics = new class extends RocketChat.models._Base
	constructor: ->
		@_initModel 'statistics'

	# FIND ONE
	findOneById: (_id, options) ->
		query =
			_id: _id

		return @findOne query, options

	findLast: ->
		query = {}
		options = sort: createdAt: -1
		return @find(query, options).fetch()?[0]

RocketChat.models.Statistics = new class extends RocketChat.models._Base
	constructor: ->
		@_initModel 'statistics'

		@tryEnsureIndex { 'createdAt': 1 }

	# FIND ONE
	findOneById: (_id, options) ->
		query =
			_id: _id

		return @findOne query, options

	findLast: ->
		options =
			sort:
				createdAt: -1
			limit: 1
		return @find({}, options).fetch()?[0]

Improve query and add index to statistics collection

Fixes #2747


Marbles.Views.NavigationActive = class NavigationActiveView extends Marbles.View
  @view_name: 'navigation_active'

  @buildMappingRegexp: (mapping) ->
    new RegExp("#{mapping.replace("*", ".*?")}")

  initialize: ->
    @active_class = Marbles.DOM.attr(@el, 'data-active-class')
    @active_selector = Marbles.DOM.attr(@el, 'data-active-selector')

    @buildActiveMapping()
    @markActiveItem()

    Marbles.history.on 'route', (router, name, args) =>
      @markActiveItem()

  buildActiveMapping: =>
    @active_mapping = []
    for el in Marbles.DOM.querySelectorAll(@active_selector, @el)
      continue unless mapping = Marbles.DOM.attr(el, 'data-match-url')
      reg = @constructor.buildMappingRegexp(mapping)
      @active_mapping.push([reg, el])
    @active_mapping = _.sortBy(@active_mapping, ( (item) => item[0].source.length * -1 ))

  markActiveItem: =>
    path = window.location.pathname
    matched = false
    for item in @active_mapping
      [reg, el] = item
      if !matched && reg.test(path)
        matched = true
        Marbles.DOM.addClass(el, @active_class)
      else
        Marbles.DOM.removeClass(el, @active_class)

Marbles.Views.NavigationActive = class NavigationActiveView extends Marbles.View
  @view_name: 'navigation_active'

  @buildMappingRegexp: (mapping) ->
    new RegExp("^#{mapping.replace("*", ".*?")}$")

  initialize: ->
    @active_class = Marbles.DOM.attr(@el, 'data-active-class')
    @active_selector = Marbles.DOM.attr(@el, 'data-active-selector')

    @buildActiveMapping()
    @markActiveItem()

    Marbles.history.on 'route', (router, name, args) =>
      @markActiveItem()

  buildActiveMapping: =>
    @active_mapping = []
    for el in Marbles.DOM.querySelectorAll(@active_selector, @el)
      continue unless mapping = Marbles.DOM.attr(el, 'data-match-url')
      reg = @constructor.buildMappingRegexp(mapping)
      @active_mapping.push([reg, el])
    @active_mapping = _.sortBy(@active_mapping, ( (item) => item[0].source.length * -1 ))

  markActiveItem: =>
    path = window.location.pathname
    matched = false
    for item in @active_mapping
      [reg, el] = item
      if !matched && reg.test(path)
        matched = true
        Marbles.DOM.addClass(el, @active_class)
      else
        Marbles.DOM.removeClass(el, @active_class)

Fix nav item route matching

Fix nav item route matching


# Navigation
(($) ->
  circle = $.sammy("body", ->

    # Page
    class Page
      constructor: (@name, @title) ->

      render: ->
        document.title = "Circle - " + @title
        $("body").attr("id",@name).html HAML['header'](renderContext)
        $("body").append HAML[@name](renderContext)
        $("body").append HAML['footer'](renderContext)

      load: (show) ->
        self = this
        $.getScript "assets/views/outer/#{@name}/#{@name}.hamlc", ->
          self.render() if show?

      display: ->
        if HAML? and HAML[@name]?
          @render()
        else
          @load(true)

    # Pages
    home = new Page("home", "Continuous Integration made easy")
    about = new Page("about", "About Us")

    # Navigation
    @get "/", (context) -> home.display()
    @get "/#about", (context) -> about.display()

  )

  # Run the application
  $ -> circle.run "/"

) jQuery

# Placeholder Polyfill
$(window).load ->
  $("input, textarea").placeholder()

# Navigation
(($) ->
  circle = $.sammy("body", ->

    # Page
    class Page
      constructor: (@name, @title) ->

      render: ->
        document.title = "Circle - " + @title
        $("body").attr("id",@name).html HAML['header'](renderContext)
        $("body").append HAML[@name](renderContext)
        $("body").append HAML['footer'](renderContext)
        @polyfill() if @polyfill?

      load: (show) ->
        self = this
        $.getScript "assets/views/outer/#{@name}/#{@name}.hamlc", ->
          self.render() if show?

      display: ->
        if HAML? and HAML[@name]?
          @render()
        else
          @load(true)

    # Pages
    home = new Page("home", "Continuous Integration made easy")
    about = new Page("about", "About Us")

    # Navigation
    @get "/", (context) -> home.display()
    @get "/about", (context) -> about.display()

    # Polyfill Detection
    home.polyfill = ->
      if !Modernizr.input.placeholder
        $("input, textarea").placeholder()
    )

  # Run the application
  $ -> circle.run "/"

) jQuery

Use polyfill method for specific pages

Use polyfill method for specific pages


u = up.util
e = up.element
$ = jQuery

beforeEach ->
  jasmine.addMatchers
    toHaveOpacity: (util, customEqualityTesters) ->
      compare: (element, expectedOpacity, tolerance = 0.0) ->
        element = e.get(element)
        actualOpacity = e.styleNumber(element, 'opacity')
        result = {}
        result.pass =  Math.abs(expectedOpacity - actualOpacity) <= tolerance
        unless result.pass
          result.message = u.sprintf("Expected %o to have opacity %o, but it was %o (tolerance ±%o)", element, expectedOpacity, actualOpacity, tolernace)
        return result

u = up.util
e = up.element
$ = jQuery

beforeEach ->
  jasmine.addMatchers
    toHaveOpacity: (util, customEqualityTesters) ->
      compare: (element, expectedOpacity, tolerance = 0.0) ->
        element = e.get(element)
        actualOpacity = e.styleNumber(element, 'opacity')
        result = {}
        result.pass =  Math.abs(expectedOpacity - actualOpacity) <= tolerance
        unless result.pass
          result.message = u.sprintf("Expected %o to have opacity %o, but it was %o (tolerance ±%o)", element, expectedOpacity, actualOpacity, tolerance)
        return result

Fix typo in haveOpacity() matcher (thanks @foobear)