import {mongoCallbacksCount} from '../mongo/MongooseCallbackManager'
import {GROUPS} from '../../client/lib/informaticsGroups'
import {START_SUBMITS_DATE} from '../api/dashboard'
import send from '../metrics/graphite'
import notify from '../metrics/notify'
import Result from "../models/result"

sendGraphite = () ->
    queries =
        ok: {ok: 1, lastSubmitTime: {$gt: START_SUBMITS_DATE}, findMistake: null, activated: true},
        ps: {ps: 1}
    metrics = {}
    for key, query of queries
        query.total = 1
        for group, _ of GROUPS
            query["userList"] = group
            metrics["#{key}.#{group}"] = (await Result.find(query)).length
    await send(metrics)

sendWebSocketsCount2Graphite = () ->
    metrics = {"websockets": mongoCallbacksCount()}
    await send(metrics)

sendWarnings = () ->
    endDate = new Date(new Date() - 5 * 60 * 1000)
    query = {ps: 1, lastSubmitTime: {$lt: endDate}, total: 1}
    submits = await Result.find(query)
    count = submits.length
    if count > 0
        notify "#{count} решений в статусе PS"

export default sendMetrics = () ->
    await sendWarnings()
    await sendGraphite()
    await sendWebSocketsCount2Graphite()

import {GROUPS} from '../../client/lib/informaticsGroups'

import {START_SUBMITS_DATE} from '../api/dashboard'

import send from '../metrics/graphite'
import notify from '../metrics/notify'

import FindMistake from '../models/FindMistake'
import Result from "../models/result"

import {mongoCallbacksCount} from '../mongo/MongooseCallbackManager'

sendGraphite = () ->
    queries =
        ok: {ok: 1, lastSubmitTime: {$gt: START_SUBMITS_DATE}, findMistake: null, activated: true},
        ps: {ps: 1}
    metrics = {}
    for key, query of queries
        query.total = 1
        for group, _ of GROUPS
            query["userList"] = group
            metrics["#{key}.#{group}"] = (await Result.find(query)).length
    await send(metrics)

sendWebSocketsCount2Graphite = () ->
    metrics = {"websockets": mongoCallbacksCount()}
    await send(metrics)

sendPendingFindMistakes = () ->
    metrics = {"pendingFindMistakes": await FindMistake.findNotApprovedCount()}
    await send(metrics)

sendWarnings = () ->
    endDate = new Date(new Date() - 5 * 60 * 1000)
    query = {ps: 1, lastSubmitTime: {$lt: endDate}, total: 1}
    submits = await Result.find(query)
    count = submits.length
    if count > 0
        notify "#{count} решений в статусе PS"

export default sendMetrics = () ->
    await sendWarnings()
    await sendGraphite()
    await sendWebSocketsCount2Graphite()
    await sendPendingFindMistakes()

Send pending find mistakes metric to graphite


EnterDialog = require './enter-dialog'
{Stacktrace} = require './stacktrace'
{StacktraceView} = require './stacktrace-view'
{NavigationView} = require './navigation-view'
editorDecorator = require './editor-decorator'

module.exports =

  activate: (state) ->
    atom.workspaceView.command 'stacktrace:paste', ->
      atom.workspaceView.appendToTop new EnterDialog()

    atom.workspaceView.command 'stacktrace:from-selection', ->
      selections = atom.workspace.getActiveEditor()?.getSelections()
      text = (s.getText() for s in (selections or [])).join ''
      atom.emit 'stacktrace:accept-trace', trace: text

    atom.workspaceView.command 'stacktrace:caller', ->
      NavigationView.current()?.navigateToCaller()

    atom.workspaceView.command 'stacktrace:follow-call', ->
      NavigationView.current()?.navigateToCalled()

    atom.workspace.eachEditor editorDecorator
    @activeChanged = Stacktrace.on 'active-changed', ->
      editorDecorator(e) for e in atom.workspace.getEditors()

    @navigationView = new NavigationView
    atom.workspaceView.appendToBottom @navigationView

    StacktraceView.registerIn(atom.workspace)

    @acceptTrace = atom.on 'stacktrace:accept-trace', ({trace}) =>
      for trace in Stacktrace.parse(trace)
        trace.register()
        atom.workspace.open trace.getUrl()

  deactivate: ->
    @navigationView.remove()

    @activeChanged.off()
    @acceptTrace.off()

EnterDialog = require './enter-dialog'
{Stacktrace} = require './stacktrace'
{StacktraceView} = require './stacktrace-view'
{NavigationView} = require './navigation-view'
editorDecorator = require './editor-decorator'

module.exports =

  activate: (state) ->
    atom.workspaceView.command 'stacktrace:paste', ->
      atom.workspaceView.appendToTop new EnterDialog()

    atom.workspaceView.command 'stacktrace:from-selection', ->
      selections = atom.workspace.getActiveEditor()?.getSelections()
      text = (s.getText() for s in (selections or [])).join ''
      atom.emit 'stacktrace:accept-trace', trace: text

    atom.workspaceView.command 'stacktrace:to-caller', ->
      NavigationView.current()?.navigateToCaller()

    atom.workspaceView.command 'stacktrace:follow-call', ->
      NavigationView.current()?.navigateToCalled()

    atom.workspace.eachEditor editorDecorator
    @activeChanged = Stacktrace.on 'active-changed', ->
      editorDecorator(e) for e in atom.workspace.getEditors()

    @navigationView = new NavigationView
    atom.workspaceView.appendToBottom @navigationView

    StacktraceView.registerIn(atom.workspace)

    @acceptTrace = atom.on 'stacktrace:accept-trace', ({trace}) =>
      for trace in Stacktrace.parse(trace)
        trace.register()
        atom.workspace.open trace.getUrl()

  deactivate: ->
    @navigationView.remove()

    @activeChanged.off()
    @acceptTrace.off()

Rename the `caller` command `to-caller`.


# spec/javascripts/foobar_spec.js.coffee
describe "foobar", ->
  it 'works', -> expect(1 + 1).toEqual(2);

describe "wanted failure", ->
  it 'fails', -> expect(1 + 1).toEqual(42);

Add failing test to verify Travis works.


window.Tahi ||= {}

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

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

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

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

window.Tahi ||= {}

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

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

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

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

Remove items from the CKEDITOR toolbar

Remove items from the CKEDITOR toolbar

[Finishes #61435398]


class Labels
  constructor: ->
    form = $('.label-form')
    @setupLabelForm(form)
    @cleanBinding()
    @addBinding()
    @updateColorPreview

  addBinding: ->
    $(document).on 'click', '.suggest-colors a', @setSuggestedColor
    $(document).on 'input', 'input#label_color', @updateColorPreview

  cleanBinding: ->
    $(document).off 'click', '.suggest-colors a'
    $(document).off 'input', 'input#label_color'

  # Initializes the form to disable the save button if no color or title is entered
  setupLabelForm: (form) ->
    disableButtonIfAnyEmptyField form, '.form-control', form.find('.js-save-button')

  # Updates the the preview color with the hex-color input
  updateColorPreview: =>
    previewColor = $('input#label_color').val()
    $('div.label-color-preview').css('background-color', previewColor)

  # Updates the preview color with a click on a suggested color
  setSuggestedColor: (e) =>
    color = $(e.currentTarget).data('color')
    $('input#label_color').val(color)
    @updateColorPreview()
    # Notify the form, that color has changed
    $('.label-form').trigger('keyup')
    e.preventDefault()

@Labels = Labels

class Labels
  constructor: ->
    form = $('.label-form')
    @setupLabelForm(form)
    @cleanBinding()
    @addBinding()
    @updateColorPreview()

  addBinding: ->
    $(document).on 'click', '.suggest-colors a', @setSuggestedColor
    $(document).on 'input', 'input#label_color', @updateColorPreview

  cleanBinding: ->
    $(document).off 'click', '.suggest-colors a'
    $(document).off 'input', 'input#label_color'

  # Initializes the form to disable the save button if no color or title is entered
  setupLabelForm: (form) ->
    disableButtonIfAnyEmptyField form, '.form-control', form.find('.js-save-button')

  # Updates the the preview color with the hex-color input
  updateColorPreview: =>
    previewColor = $('input#label_color').val()
    $('div.label-color-preview').css('background-color', previewColor)

  # Updates the preview color with a click on a suggested color
  setSuggestedColor: (e) =>
    color = $(e.currentTarget).data('color')
    $('input#label_color').val(color)
    @updateColorPreview()
    # Notify the form, that color has changed
    $('.label-form').trigger('keyup')
    e.preventDefault()

@Labels = Labels

Call updateColorPreview in Labels constructor


'.workspace':
  'ctrl-M': 'markdown-preview:toggle'

'.platform-darwin .markdown-preview':
  'cmd-+': 'markdown-preview:zoom-in'
  'cmd-=': 'markdown-preview:zoom-in'
  'cmd--': 'markdown-preview:zoom-out'
  'cmd-_': 'markdown-preview:zoom-out'
  'cmd-0': 'markdown-preview:reset-zoom'

'.platform-win32 .markdown-preview, .platform-linux .markdown-preview':
  'ctrl-+': 'markdown-preview:zoom-in'
  'ctrl-=': 'markdown-preview:zoom-in'
  'ctrl--': 'markdown-preview:zoom-out'
  'ctrl-_': 'markdown-preview:zoom-out'
  'ctrl-0': 'markdown-preview:reset-zoom'

'.workspace .editor':
  'ctrl-M': 'markdown-preview:toggle'

'.platform-darwin .markdown-preview':
  'cmd-+': 'markdown-preview:zoom-in'
  'cmd-=': 'markdown-preview:zoom-in'
  'cmd--': 'markdown-preview:zoom-out'
  'cmd-_': 'markdown-preview:zoom-out'
  'cmd-0': 'markdown-preview:reset-zoom'

'.platform-win32 .markdown-preview, .platform-linux .markdown-preview':
  'ctrl-+': 'markdown-preview:zoom-in'
  'ctrl-=': 'markdown-preview:zoom-in'
  'ctrl--': 'markdown-preview:zoom-out'
  'ctrl-_': 'markdown-preview:zoom-out'
  'ctrl-0': 'markdown-preview:reset-zoom'

Support preview toggle when focus is on editor


"*":
  core:
    disabledPackages: [
      "wrap-guide"
      "activate-power-mode"
    ]
  editor: {}
  "exception-reporting":
    userId: "79967b20-c933-87c9-85b8-ea6b20c3a648"
  "linter-puppet-lint":
    oldVersion: false
    skip140Chars: true
    skip80Chars: true
  minimap:
    plugins:
      cursorline: true
      cursorlineDecorationsZIndex: 0
      "find-and-replace": true
      "find-and-replaceDecorationsZIndex": 0
      "git-diff": true
      "git-diffDecorationsZIndex": 0
      linter: true
      linterDecorationsZIndex: 0
      "minimap-autohide": true
      "minimap-autohideDecorationsZIndex": 0
  sunset:
    daytime_syntax_theme: "one-light-syntax"
    daytime_ui_theme: "one-light-ui"
    has_been_configured: true
    nighttime_syntax_theme: "one-dark-syntax"
    nighttime_ui_theme: "one-dark-ui"
    when_does_it_get_dark: 1400
    when_does_it_get_light: 700
  welcome:
    showOnStartup: false

"*":
  core:
    disabledPackages: [
      "wrap-guide"
      "activate-power-mode"
    ]
    themes: [
      "one-light-ui"
      "one-light-syntax"
    ]
  editor: {}
  "exception-reporting":
    userId: "79967b20-c933-87c9-85b8-ea6b20c3a648"
  "linter-puppet-lint":
    oldVersion: false
    skip140Chars: true
    skip80Chars: true
  minimap:
    plugins:
      cursorline: true
      cursorlineDecorationsZIndex: 0
      "find-and-replace": true
      "find-and-replaceDecorationsZIndex": 0
      "git-diff": true
      "git-diffDecorationsZIndex": 0
      linter: true
      linterDecorationsZIndex: 0
      "minimap-autohide": true
      "minimap-autohideDecorationsZIndex": 0
  sunset:
    daytime_syntax_theme: "one-light-syntax"
    daytime_ui_theme: "one-light-ui"
    has_been_configured: true
    nighttime_syntax_theme: "one-dark-syntax"
    nighttime_ui_theme: "one-dark-ui"
    when_does_it_get_dark: 1830
    when_does_it_get_light: 700
  welcome:
    showOnStartup: false

Change time for atom dark theme

Change time for atom dark theme


class ResultViewModel
  constructor: (@htmlArray) ->
    @html = ko.computed () =>
      @htmlArray().join("")

  afterListviewRender: (element, data) ->
    # サムネイル表示時のレンダリングがうまくいかないので
    # 自前でクラスを設定する。
    $(element).addClass("ui-li-has-thumb")
    $(element).find("img").addClass("ui-li-thumb")
    # refresh しないと説明文が画面に収まらない場合がある
    $("#preview").listview("refresh")

  showPhoto: (listItem) =>
    $.mobile.changePage "#preview-page", { transition: "slide" }
    imgObject = $(listItem).find("img").clone()
    imgObject.removeClass("ui-li-thumb")
    imgObject.removeAttr("height")
    imgObject.width("100%")
    $("#img-preview").html(imgObject)

  launchApp: (url) =>
    $.mobile.changePage "#main"
    window.location = url + encodeURIComponent(@html())

  insertToMoblogger: () =>
    this.launchApp("moblogger://append?text=")

  launchMobloggerAndCopy: () =>
    this.launchApp("moblogger://pboard?text=")

  insertToRowline: () =>
    this.launchApp("rowline:///set?loc=bottom&view=lines&text=")

  sendToTextHandler: () =>
    this.launchApp("myscripts://run?title=TextHandler&text=")

  insertToTextwell: () =>
    T( 'add', {
        text: @html()
    })

class ResultViewModel
  constructor: (@htmlArray) ->
    @html = ko.computed () =>
      @htmlArray().join("")

  afterListviewRender: (element, data) ->
    # サムネイル表示時のレンダリングがうまくいかないので
    # 自前でクラスを設定する。
    $(element).addClass("ui-li-has-thumb")
    $(element).find("img").addClass("ui-li-thumb")
    # refresh しないと説明文が画面に収まらない場合がある
    $("#preview").listview("refresh")

  showPhoto: (listItem) =>
    $.mobile.changePage "#preview-page", { transition: "slide" }
    imgObject = $(listItem).find("img").clone()
    imgObject.removeClass("ui-li-thumb")
    imgObject.removeAttr("height")
    imgObject.width("100%")
    $("#img-preview").html(imgObject)

  launchApp: (url) =>
    $.mobile.changePage "#main"
    window.location = url + encodeURIComponent(@html())

  insertToMoblogger: () =>
    this.launchApp("moblogger://append?text=")

  launchMobloggerAndCopy: () =>
    this.launchApp("moblogger://pboard?text=")

  insertToRowline: () =>
    this.launchApp("rowline:///set?loc=bottom&view=lines&text=")

  sendToTextHandler: () =>
    this.launchApp("myscripts://run?title=TextHandler&text=")

  insertToTextwell: () =>
    this.launchApp("textwell:///add?text=")

Fix can't insert text to TextWell

Fix can't insert text to TextWell


angular.module("admin.enterprises")
  .controller "enterpriseCtrl", ($scope, Enterprise, PaymentMethods, ShippingMethods) ->
    $scope.Enterprise = Enterprise.enterprise
    $scope.PaymentMethods = PaymentMethods.paymentMethods
    $scope.ShippingMethods = ShippingMethods.shippingMethods

    for PaymentMethod in $scope.PaymentMethods
      PaymentMethod.selected = if PaymentMethod.id in $scope.Enterprise.payment_method_ids then true else false

    $scope.paymentMethodsColor = ->
      if $scope.PaymentMethods.length > 0
        if $scope.selectedPaymentMethodsCount() > 0 then "blue" else "red"
      else
        "red"

    $scope.selectedPaymentMethodsCount = ->
      $scope.PaymentMethods.reduce (count, PaymentMethod) ->
        count++ if PaymentMethod.selected
        count
      , 0

    for ShippingMethod in $scope.ShippingMethods
      ShippingMethod.selected = if ShippingMethod.id in $scope.Enterprise.shipping_method_ids then true else false

    $scope.shippingMethodsColor = ->
      if $scope.ShippingMethods.length > 0
        if $scope.selectedShippingMethodsCount() > 0 then "blue" else "red"
      else
        "red"

    $scope.selectedShippingMethodsCount = ->
      $scope.ShippingMethods.reduce (count, ShippingMethod) ->
        count++ if ShippingMethod.selected
        count
      , 0

angular.module("admin.enterprises")
  .controller "enterpriseCtrl", ($scope, Enterprise, PaymentMethods, ShippingMethods) ->
    $scope.Enterprise = Enterprise.enterprise
    $scope.PaymentMethods = PaymentMethods.paymentMethods
    $scope.ShippingMethods = ShippingMethods.shippingMethods

    for payment_method in $scope.PaymentMethods
      payment_method.selected = payment_method.id in $scope.Enterprise.payment_method_ids

    $scope.paymentMethodsColor = ->
      if $scope.PaymentMethods.length > 0
        if $scope.selectedPaymentMethodsCount() > 0 then "blue" else "red"
      else
        "red"

    $scope.selectedPaymentMethodsCount = ->
      $scope.PaymentMethods.reduce (count, payment_method) ->
        count++ if payment_method.selected
        count
      , 0

    for shipping_method in $scope.ShippingMethods
      shipping_method.selected = shipping_method.id in $scope.Enterprise.shipping_method_ids

    $scope.shippingMethodsColor = ->
      if $scope.ShippingMethods.length > 0
        if $scope.selectedShippingMethodsCount() > 0 then "blue" else "red"
      else
        "red"

    $scope.selectedShippingMethodsCount = ->
      $scope.ShippingMethods.reduce (count, shipping_method) ->
        count++ if shipping_method.selected
        count
      , 0

Refactor admin enterpriseCtrl - variable naming, if x then true else false antipattern


path = require 'path'
colors = require 'colors'
CSON = require 'season'
config = require './config'
Command = require './command'

module.exports =
class Publisher extends Command
  userConfigPath: null
  atomNpmPath: null

  constructor: ->
    @userConfigPath = config.getUserConfigPath()
    @atomNpmPath = require.resolve('npm/bin/npm-cli')

  run: (options) ->
    process.stdout.write 'Publishing '
    try
      {name, version} = CSON.readFileSync(CSON.resolve('package')) ? {}
      process.stdout.write "#{name}@#{version} "
    catch e
    publishArgs = ['--userconfig', @userConfigPath, 'publish']
    @fork @atomNpmPath, publishArgs, (code, stderr='') =>
      if code is 0
        process.stdout.write '\u2713\n'.green
        options.callback()
      else
        process.stdout.write '\u2717\n'.red
        options.callback(stderr.red)

path = require 'path'
colors = require 'colors'
CSON = require 'season'
config = require './config'
Command = require './command'

module.exports =
class Publisher extends Command
  userConfigPath: null
  atomNpmPath: null

  constructor: ->
    @userConfigPath = config.getUserConfigPath()
    @atomNpmPath = require.resolve('npm/bin/npm-cli')

  versionPackage: (version, callback) ->
    process.stdout.write 'Preparing and tagging a new version '
    versionArgs = ['version', version, '-m', 'Prepare %s release']
    @fork @atomNpmPath, versionArgs, (code, stderr='', stdout='') ->
      if code is 0
        process.stdout.write '\u2713\n'.green
        callback()
      else
        process.stdout.write '\u2717\n'.red
        callback("#{stdout}\n#{stderr}".red)

  publishPackage: (options) ->
    process.stdout.write 'Publishing '
    try
      {name, version} = CSON.readFileSync(CSON.resolve('package')) ? {}
      process.stdout.write "#{name}@#{version} "

    publishArgs = ['--userconfig', @userConfigPath, 'publish']
    @fork @atomNpmPath, publishArgs, (code, stderr='', stdout='') ->
      if code is 0
        process.stdout.write '\u2713\n'.green
        options.callback()
      else
        process.stdout.write '\u2717\n'.red
        options.callback("#{stdout}\n#{stderr}".red)

  run: (options) ->
    version = options.commandArgs.shift()
    if version
      @versionPackage version, (error) =>
        if error?
          options.callback(error)
        else
          @publishPackage(options)
    else
      @publishPackage()

Support bumping version from apm publish

Now you can just do `apm publish minor` to bump and tag a minor
release and then publish it.


describe "CMS", ->
  beforeEach ->
    CMS.unbind()

  it "should initialize Models", ->
    expect(CMS.Models).toBeDefined()

  it "should initialize Views", ->
    expect(CMS.Views).toBeDefined()

describe "main helper", ->
  beforeEach ->
    @previousAjaxSettings = $.extend(true, {}, $.ajaxSettings)
    window.stubCookies["csrftoken"] = "stubCSRFToken"
    $(document).ready()

  afterEach ->
    $.ajaxSettings = @previousAjaxSettings

  it "turn on Backbone emulateHTTP", ->
    expect(Backbone.emulateHTTP).toBeTruthy()

  it "setup AJAX CSRF token", ->
    expect($.ajaxSettings.headers["X-CSRFToken"]).toEqual("stubCSRFToken")

describe "AJAX Errors", ->
  tpl = readFixtures('system-feedback.underscore')

  beforeEach ->
    setFixtures($("<script>", {id: "system-feedback-tpl", type: "text/template"}).text(tpl))
    appendSetFixtures(sandbox({id: "page-notification"}))
    @requests = requests = []
    @xhr = sinon.useFakeXMLHttpRequest()
    @xhr.onCreate = (xhr) -> requests.push(xhr)

  afterEach ->
    @xhr.restore()

  it "successful AJAX request does not pop an error notification", ->
    expect($("#page-notification")).toBeEmpty()
    $.ajax("/test")
    expect($("#page-notification")).toBeEmpty()
    @requests[0].respond(200)
    expect($("#page-notification")).toBeEmpty()

  it "AJAX request with error should pop an error notification", ->
    $.ajax("/test")
    @requests[0].respond(500)
    expect($("#page-notification")).not.toBeEmpty()
    expect($("#page-notification")).toContain('div.wrapper-notification-error')

  it "can override AJAX request with error so it does not pop an error notification", ->
    $.ajax
      url: "/test"
      notifyOnError: false
    @requests[0].respond(500)
    expect($("#page-notification")).toBeEmpty()

Add Jasmine tests to cover AJAX error notifications


routes  = require './routes.js'
bodyParser = require 'body-parser'
path = require 'path'
express = require 'express'
app = express()
models = require('mongoose').models
outline = require '../outline.js'
utils = require './utils.js'

# Default parsers
app.use express.static(path.resolve(outline.dist))
app.use bodyParser.urlencoded({ extended: false })
app.use bodyParser.json();

utils.allowCORS(app);

# Setup routes
for route in routes
	for name, config of route
		console.log "name : #{config.auth}"
		url = name.split('.')[0];
		if config.auth in ['true', 'optional']
			app[config.method](url, (req, res, next) -> utils.setupAuthData req, res, next, config)
		app[config.method](url, (req, res, next) -> utils.rejectBadRequest(req, res, next, config))
		app[config.method](url, config.fn);

app.get '*/', (req, res) -> res.sendFile path.resolve("#{__dirname}/../#{outline.dist}/#{req.path}")
app.get '/', (req, res) -> res.sendFile path.resolve("#{__dirname}/../#{outline.dist}/index.html")

module.exports = app;

routes  = require './routes.js'
bodyParser = require 'body-parser'
path = require 'path'
express = require 'express'
app = express()
models = require('mongoose').models
outline = require '../outline.js'
utils = require './utils.js'

# Default parsers
app.use express.static(path.resolve(outline.dist))
app.use bodyParser.urlencoded({ extended: false })
app.use bodyParser.json();

utils.allowCORS(app);

# Setup routes
for route in routes
	for name, config of route
		console.log "#{name}"
		url = name.split('.')[0];
		if config.auth in ['true', 'optional']
			app[config.method](url, (req, res, next) -> utils.setupAuthData req, res, next, config)
		app[config.method](url, (req, res, next) -> utils.rejectBadRequest(req, res, next, config))
		app[config.method](url, config.fn);

app.get '*/', (req, res) -> res.sendFile path.resolve("#{__dirname}/../#{outline.dist}/#{req.path}")
app.get '/', (req, res) -> res.sendFile path.resolve("#{__dirname}/../#{outline.dist}/index.html")

module.exports = app;

Fix list of routes on startup

Fix list of routes on startup


window.UsersComponent = class UsersComponent
  @initialize: ->
    @addEventListeners()

  @addEventListeners: ->
    @userDetailsTooltip()

  @userDetailsTooltip: ->
    tooltipClass = '.information-tooltip'
    $("#{tooltipClass}-trigger").toggle( (ev) ->
      ev.preventDefault()
      $("#{tooltipClass}-trigger").not(@).siblings(tooltipClass).hide()
      $(@).html(' close')
      $(@).siblings(tooltipClass).show()
    , (ev) ->
      ev.preventDefault()
      $(@).html(' details')
      $(@).siblings(tooltipClass).hide()
    )

$(document).ready -> UsersComponent.initialize()

window.UsersComponent = class UsersComponent
  @initialize: ->
    @addEventListeners()

  @addEventListeners: ->
    @userDetailsTooltip()

  @userDetailsTooltip: ->
    tooltipClass = '.information-tooltip'
    $(document).on('click', "#{tooltipClass}-trigger", (ev) ->
      ev.preventDefault()
      if ($(@).find('.fa-align-left').length)
        $("#{tooltipClass}-trigger").not(@).siblings(tooltipClass).hide()
        $("#{tooltipClass}-trigger").not(@).html(' details')
        $(@).html(' close')
        $(@).siblings(tooltipClass).show()
      else
        $(@).html(' details')
        $(@).siblings(tooltipClass).hide()
    )

$(document).ready -> UsersComponent.initialize()

Make the details link for info working dynamically


Helpers = require '../lib/helpers'

describe "The Results Validation Helper", ->
  it "should throw an exception when nothing is passed.", ->
    expect( -> Helpers.validateResults()).toThrow()
  it "should throw an exception when a String is passed.", ->
    expect( -> Helpers.validateResults('String')).toThrow()

describe "The Linter Validation Helper", ->
  it "should throw an exception when grammarScopes is not an Array.", ->
    linter = {
      grammarScopes: 'not an array'
      lint: ->
    }
    expect( -> Helpers.validateLinter(linter)).toThrow()

  it "should throw an exception when lint is missing.", ->
    linter = {
      grammarScopes: []
    }
    expect( -> Helpers.validateLinter(linter)).toThrow()

  it "should throw an exception when a lint is not a function.", ->
    linter = {
      grammarScopes: []
      lint: 'not a function'
    }
    expect( -> Helpers.validateLinter(linter)).toThrow()

  it "should return true when everything validates.", ->
    linter = {
      grammarScopes: []
      lint: ->
    }
    expect(Helpers.validateLinter(linter)).toEqual(true)

Helpers = require '../lib/helpers'

describe "The Results Validation Helper", ->
  it "should throw an exception when nothing is passed.", ->
    expect( -> Helpers.validateResults()).toThrow()
  it "should throw an exception when a String is passed.", ->
    expect( -> Helpers.validateResults('String')).toThrow()
  it "should throw an exception when a result's type is missing.", ->
    results = [{}]
    expect( -> Helpers.validateResults(results)).toThrow()
  it "should return the results when validated.", ->
    results = [{type: 'Type'}]
    expect(Helpers.validateResults(results)).toEqual(results)

describe "The Linter Validation Helper", ->
  it "should throw an exception when grammarScopes is not an Array.", ->
    linter = {
      grammarScopes: 'not an array'
      lint: ->
    }
    expect( -> Helpers.validateLinter(linter)).toThrow()
  it "should throw an exception when lint is missing.", ->
    linter = {
      grammarScopes: []
    }
    expect( -> Helpers.validateLinter(linter)).toThrow()
  it "should throw an exception when a lint is not a function.", ->
    linter = {
      grammarScopes: []
      lint: 'not a function'
    }
    expect( -> Helpers.validateLinter(linter)).toThrow()
  it "should return true when everything validates.", ->
    linter = {
      grammarScopes: []
      lint: ->
    }
    expect(Helpers.validateLinter(linter)).toEqual(true)

Add specs for Results Validation.

Add specs for Results Validation.


Bookshelf = require('bookshelf')
config = require('../knex_config').database

Bookshelf.PG = PG = Bookshelf.initialize(config)

Team = PG.Model.extend
  tableName: 'teams'
  hasTimestamps: ['created_at', 'updated_at']

  toJSON: -> {
      id: @id
      name: @get('name')
      generation: @get('generation')
      pokemon: JSON.parse(@get('contents'))
    }

Teams = PG.Collection.extend
  model: Team

module.exports = {Team, Teams}

Bookshelf = require('bookshelf')
{_} = require('underscore')
config = require('../knex_config').database

Bookshelf.PG = PG = Bookshelf.initialize(config)

Team = PG.Model.extend
  tableName: 'teams'
  hasTimestamps: ['created_at', 'updated_at']

  toJSON: ->
    contents = @get('contents')
    {
      id: @id
      name: @get('name')
      generation: @get('generation')
      # Postgres 9.2+ support the JSON datatype. Other versions/DBs do not.
      # So if the JSON data type is supported, then loading will load as JSON.
      pokemon: (if _.isObject(contents) then contents else JSON.parse(contents))
    }

Teams = PG.Collection.extend
  model: Team

module.exports = {Team, Teams}

Support JSON datatype a bit better.

Sim: Support JSON datatype a bit better.


class window.SubchannelItemView extends Backbone.Marionette.ItemView
  tagName: "li"
  events:
    click: "clickHandler"
    "click .close": "destroySubchannel"

  template: "subchannels/_subchannel_item"
  initialize: ->
    @model.bind "destroy", @close, this

  destroySubchannel: (e) ->
    @model.destroy()  if confirm("Are you sure you want to remove this channel from your channel?")
    e.stopPropagation()
    false

  onRender: ->
    @$('i.close').hide() unless currentChannel.user().get('id') == currentUser.get('id')

  clickHandler: (e) ->
    return  if e.metaKey or e.ctrlKey or e.altKey
    mp_track "Channel: Click on subchannel",
      channel_id: currentChannel.id
      subchannel_id: @model.id

    Backbone.history.navigate @model.get("created_by").username + "/channels/" + @model.id, true
    e.preventDefault()
    false

class window.SubchannelItemView extends Backbone.Marionette.ItemView
  tagName: "li"
  events:
    click: "clickHandler"
    "click .close": "destroySubchannel"

  template: "subchannels/_subchannel_item"
  initialize: ->
    @model.bind "destroy", @close, this

  destroySubchannel: (e) ->
    @model.destroy()  if confirm("Are you sure you want to remove this channel from your channel?")
    e.stopPropagation()
    false

  onRender: ->
    @$('i.close').hide() unless currentChannel.user().get('id') == currentUser.get('id')

  clickHandler: (e) ->
    mp_track "Channel: Click on subchannel",
      channel_id: currentChannel.id
      subchannel_id: @model.id

    @defaultClickHandler e

Use defaultClickHandler to navigate to subChannel


Mark = require './mark'

class FanMark extends Mark
  type: 'fan'
  source: null
  distance: 0
  angle: 0
  spread: 0

  constructor: ->
    super

    @set
      source: @source || {x: 0, y: 0}
      distance: @distance
      angle: @angle
      spread: @spread

  'set distance': (value) -> Math.max value, 10

  'set spread': (value) -> Math.min 90, Math.max 1, value

  toJSON: ->
    {@type, @source, @distance, @angle, @spread}

module.exports = FanMark

Mark = require './mark'

class FanMark extends Mark
  type: 'fan'
  source: null
  distance: 0
  angle: 0
  spread: 0

  constructor: ->
    super

    @set
      source: @source || {x: 0, y: 0}
      distance: @distance
      angle: @angle
      spread: @spread

  'set distance': (value) -> Math.max value, 10

  'set spread': (value) -> Math.min 179.99, Math.max 1, value

  toJSON: ->
    {@type, @source, @distance, @angle, @spread}

module.exports = FanMark

Remove 90 degree limit from fan tool

Remove 90 degree limit from fan tool


# For some odd reason, this isn't working. There's no reason why it shouldn't. I
# think either Heroku is messing up my config, or I need to flip a setting in
# Heroku somewhere.
#
# Instead, I'm requiring HTTPS through CloudFlare page rules, a hacky but
# effective solution
#config = require '../config'
#
#exports.forceHTTPS = (req, res, next) ->
#  # redirect to HTTPS in production, take care of Heroku's special https
#  # handling
#  if not (req.secure or (req.header('x-forwarded-proto') == 'https')) and
#      config.nodeEnv == 'production'
#    res.redirect "https://#{req.headers.host}#{req.url}"
#  else
#    next()

exports.app = (req, res, next) ->
  res.render 'app',
    loggedIn: req.isAuthenticated()
    user: req.user

# For some odd reason, this isn't working. There's no reason why it shouldn't. I
# think either Heroku is messing up my config, or I need to flip a setting in
# Heroku somewhere.
#
# Instead, I'm requiring HTTPS through CloudFlare page rules, a hacky but
# effective solution
#config = require '../config'
#
#exports.forceHTTPS = (req, res, next) ->
#  # redirect to HTTPS in production, take care of Heroku's special https
#  # handling
#  if not (req.secure or (req.header('x-forwarded-proto') == 'https')) and
#      config.nodeEnv == 'production'
#    res.redirect "https://#{req.headers.host}#{req.url}"
#  else
#    next()

models = require '../models'

exports.app = (req, res, next) ->
  key = req.params.key
  if key
    models.Queue.findById key
      .then (queue) ->
        if queue
          res.render 'app',
            loggedIn: req.isAuthenticated()
            user: req.user
        else
          ex = new Error 'Queue not found'
          ex.status = 404
          next ex
      .error ->
        ex = new Error 'Error finding queue'
        ex.status = 500
        next ex
  else
    res.render 'app',
      loggedIn: req.isAuthenticated()
      user: req.user

Handle 404's better for queues

Handle 404's better for queues


App.Votes =

  hoverize: (selector, delegateTo) ->
    $(selector).on "mouseenter", delegateTo, ->
      $(this).find("div.anonymous-votes").show()
      $(this).find("div.organizations-votes").show()
      $(this).find("div.not-logged").show()
    $(selector).on "mouseleave", delegateTo, ->
      $(this).find("div.anonymous-votes").hide()
      $(this).find("div.organizations-votes").hide()
      $(this).find("div.not-logged").hide()

  initialize: ->
    App.Votes.hoverize $("#debates"), "div.votes"
    App.Votes.hoverize $("#proposals, #featured-proposals"), "div.supports"
    false

App.Votes =

  hoverize: (selector, delegateTo) ->
    $(selector).on "mouseenter", delegateTo, ->
      $(this).find("div.anonymous-votes").show()
      $(this).find("div.organizations-votes").show()
      $(this).find("div.not-logged").show()
    $(selector).on "mouseleave", delegateTo, ->
      $(this).find("div.anonymous-votes").hide()
      $(this).find("div.organizations-votes").hide()
      $(this).find("div.not-logged").hide()

  initialize: ->
    App.Votes.hoverize $("#debates, .debate-show"), "div.votes"
    App.Votes.hoverize $("#proposals, #featured-proposals, .proposal-show"), "div.supports"
    false

Fix broken tests for resources show

Fix broken tests for resources show


#= require ../../node_modules/share/node_modules/browserchannel/dist/bcsocket-uncompressed

#= require ../../node_modules/share/webclient/share.uncompressed
#= require ../../node_modules/share/webclient/json.uncompressed

module = angular.module "plunker.share", []

module.service "share", [ () ->

]

#= require ./../../node_modules/share/node_modules/browserchannel/dist/bcsocket-uncompressed

#= require ./../../node_modules/share/webclient/share.uncompressed
#= require ./../../node_modules/share/webclient/json.uncompressed

Remove angular service not used

Remove angular service not used


module.exports =
  configDefaults:
    executableDir: null

  activate: ->
    console.log 'activate linter-python-flake8'

module.exports =
  configDefaults:
    executableDir: null
    maxLineLength: 79
    ignoreErrorCodes: []

  activate: ->
    console.log 'activate linter-python-flake8'

Set defaults for maxLineLength, ignoreErrorCodes

Specified a default max line length of 79 chars (PEP8 default) and report all error codes by default


module.exports =
  config:
    jshintExecutablePath:
      default: ''
      type: 'string'
      description: 'Leave empty to use bundled'
  provideLinter: ->
    jshintPath = require('path').join(__dirname, '..', 'node_modules', '.bin', 'jshint')
    helpers = require('atom-linter')
    reporter = require('jshint-json') # a string path
    provider =
      grammarScopes: ['source.js', 'source.js.jsx']
      scope: 'file'
      lintOnFly: true
      lint: (textEditor) ->
        exePath = atom.config.get('linter-jshint.jshintExecutablePath') or jshintPath
        filePath = textEditor.getPath()
        text = textEditor.getText()
        parameters = ['--reporter', reporter, '--extract', 'auto', '--filename', filePath, '-']
        return helpers.exec(exePath, parameters, {stdin: text}).then (output) ->
          return JSON.parse(output).result.filter((entry) -> entry.error.id).map (entry) ->
            error = entry.error
            pointStart = [error.line - 1, error.character - 1]
            pointEnd = [error.line - 1, error.character]
            return {
              type: error.id.substr(1, error.id.length - 2)
              text: "#{error.code} - #{error.reason}"
              filePath
              range: [pointStart, pointEnd]
            }

module.exports =
  config:
    jshintExecutablePath:
      default: ''
      type: 'string'
      description: 'Leave empty to use bundled'
  provideLinter: ->
    jshintPath = require('path').join(__dirname, '..', 'node_modules', '.bin', 'jshint')
    helpers = require('atom-linter')
    reporter = require('jshint-json') # a string path
    provider =
      grammarScopes: ['source.js', 'source.js.jsx']
      scope: 'file'
      lintOnFly: true
      lint: (textEditor) ->
        exePath = atom.config.get('linter-jshint.jshintExecutablePath') || jshintPath
        filePath = textEditor.getPath()
        text = textEditor.getText()
        parameters = ['--reporter', reporter, '--extract', 'auto', '--filename', filePath, '-']
        return helpers.exec(exePath, parameters, {stdin: text}).then (output) ->
          return JSON.parse(output).result.filter((entry) -> entry.error.id).map (entry) ->
            error = entry.error
            pointStart = [error.line - 1, error.character - 1]
            pointEnd = [error.line - 1, error.character]
            return {
              type: error.id.substr(1, error.id.length - 2)
              text: "#{error.code} - #{error.reason}"
              filePath
              range: [pointStart, pointEnd]
            }

Use || instead of or

:art: Use || instead of or


{CompositeDisposable} = require 'atom'

module.exports =
  config: require './config'

  activate: (state) ->
    @transpiler ?= new (require './transpiler')
    # track any file save ( buffer save) events and transpile if babel
    @disposable = new CompositeDisposable
    @disposable.add atom.project.onDidChangePaths =>
      @transpiler.stopUnusedTasks()
    @disposable.add  atom.workspace.observeTextEditors (textEditor) =>
      @disposable.add textEditor.onDidSave (event) =>
        grammar = textEditor.getGrammar()
        return if grammar.packageName isnt 'language-babel'
        @transpiler.transpile(event.path, textEditor)

  deactivate: ->
    if @disposable?
      @disposable.dispose()
    @transpiler.stopAllTranspilerTask()

{CompositeDisposable} = require 'atom'

module.exports =
  config: require './config'

  activate: (state) ->
    @transpiler ?= new (require './transpiler')
    # track any file save events and transpile if babel
    @disposable = new CompositeDisposable
    @textEditors = {}

    @disposable.add atom.project.onDidChangePaths =>
      @transpiler.stopUnusedTasks()

    @disposable.add atom.workspace.observeTextEditors (textEditor) =>
      @textEditors[textEditor.id] = new CompositeDisposable
      @textEditors[textEditor.id].add textEditor.onDidSave (event) =>
        if atom.workspace.getActiveTextEditor().id is textEditor.id and
        textEditor.getGrammar().packageName is 'language-babel'
          @transpiler.transpile(event.path, textEditor)
      @textEditors[textEditor.id].add textEditor.onDidDestroy () =>
        @textEditors[textEditor.id].dispose()
        delete @textEditors[textEditor.id]

  deactivate: ->
    @disposable.dispose()
    for id, disposeable of @textEditors
      disposeable.dispose()
    @transpiler.stopAllTranspilerTask()

Improve handling of TextEditor subscriptions and only save on ActiveTextEditors.


connect = require('connect')
utils = connect.utils
forbiddenURLChars= /\/([._][^/]*)|_$/

###
todo: // detect if URL is of a directory and, if so, bring up the _index
if (stat.isDirectory())
  if (!redirect) return @next();
  @res.statusCode = 301;
  @res.setHeader('Location', url.pathname + '/');
  @res.end('Redirecting to ' + url.pathname + '/');
  return;
}
###

exports = module.exports = action= ()->
  actions= @meta._actions

  if forbiddenURLChars.test(@pathinfo.url.pathname)
    @res.statusCode = 403;
    @res.end('Forbidden URL');
    return;

  urlExtension = @pathinfo.path.extension
  if urlExtension
    aue= @meta._allowed_url_extensions
    unless aue.set
      s= {}
      s['.'+key]=true for key in aue
      aue.set= s
    unless aue.set[urlExtension]
      @res.statusCode = 415;
      @res.end('Unsupported Media Type');
      return;

  actionList= actions[urlExtension] ? actions['*']
  @next() unless actionList
  i= -1
  pass= ()=>
    i+= 1
    if (actionList.length > i)
      actionList[i].call(this,pass)
    else
      @next()
  pass()

connect = require('connect')
utils = connect.utils
forbiddenURLChars= /\/([._][^/]*)|_$/

###
todo: // detect if URL is of a directory and, if so, bring up the _index
if (stat.isDirectory())
  if (!redirect) return @next();
  @res.statusCode = 301;
  @res.setHeader('Location', url.pathname + '/');
  @res.end('Redirecting to ' + url.pathname + '/');
  return;
}
###

exports = module.exports = action= ()->
  actions= @meta._actions

  if forbiddenURLChars.test(@pathinfo.url.pathname)
    @res.statusCode = 403;
    @res.end('Forbidden URL');
    return;

  urlExtension = @pathinfo.path.extension
  if urlExtension && @meta._allowed_url_extensions
    aue= @meta._allowed_url_extensions
    aue.regexp ?= new RegExp('((\.' + aue.join(')|(\.') + '))$')
    unless aue.regexp.test(urlExtension)
      @res.statusCode = 415;
      @res.end('Unsupported Media Type');
      return;

  actionList= actions[urlExtension] ? actions['*']
  @next() unless actionList
  i= -1
  pass= ()=>
    i+= 1
    if (actionList.length > i)
      actionList[i].call(this,pass)
    else
      @next()
  pass()

Test for allowed extensions by converting list to regexp and testing.


app = require 'app'
fs = require 'fs'
path = require 'path'
protocol = require 'protocol'

# Private: Handles requests with 'atom' protocol.
#
# It's created by {AtomApplication} upon instantiation, and is used to create a
# custom resource loader by adding the 'atom' custom protocol.
module.exports =
class AtomProtocolHandler
  constructor: (@resourcePath) ->
    @loadPaths = [
      path.join(@resourcePath, 'node_modules')
      path.join(app.getHomeDir(), '.atom', 'packages')
    ]

    @registerAtomProtocol()

  # Private: Creates the 'atom' custom protocol handler.
  registerAtomProtocol: ->
    protocol.registerProtocol 'atom', (request) =>
      relativePath = path.normalize(request.url.substr(7))
      for loadPath in @loadPaths
        filePath = path.join(loadPath, relativePath)
        break if fs.statSyncNoException(filePath)?
      return new protocol.RequestFileJob(filePath)

app = require 'app'
fs = require 'fs'
path = require 'path'
protocol = require 'protocol'

# Private: Handles requests with 'atom' protocol.
#
# It's created by {AtomApplication} upon instantiation, and is used to create a
# custom resource loader by adding the 'atom' custom protocol.
module.exports =
class AtomProtocolHandler
  constructor: (@resourcePath) ->
    @loadPaths = [
      path.join(@resourcePath, 'node_modules')
      path.join(app.getHomeDir(), '.atom', 'packages')
      path.join(app.getHomeDir(), '.atom', 'dev', 'packages')
    ]

    @registerAtomProtocol()

  # Private: Creates the 'atom' custom protocol handler.
  registerAtomProtocol: ->
    protocol.registerProtocol 'atom', (request) =>
      relativePath = path.normalize(request.url.substr(7))
      for loadPath in @loadPaths
        filePath = path.join(loadPath, relativePath)
        break if fs.statSyncNoException(filePath)?
      return new protocol.RequestFileJob(filePath)

Add ~/.atom/dev/packages to load paths

Add ~/.atom/dev/packages to load paths


I18n = (id, args...) ->
  m = chrome.i18n.getMessage(id, [args...])
  return escapeHtml(m) if m != ""
  m = id.replace(/(.)([A-Z][a-z])/g, (m, p1, p2) -> "#{p1} #{p2.toLowerCase()}")
  m = m.replace(/_/g, ' ')
  escapeHtml(m)

$(->
  pat = /__MSG_([A-Za-z0-9]+)__/
  $("a,button").each((i, v) ->
    e = $(v)
    title = e.attr?("title")
    if title
      newTitle = title.replace(pat, (m, p1) -> I18n(p1))
      e.attr("title", newTitle) if title != newTitle
    html = e.html()
    return true unless html
    newHtml = html.replace(pat, (m, p1) -> I18n(p1))
    e.html(newHtml) if html != newHtml
  )
)

I18n = (id_or_object, args...) ->
  switch typeof id_or_object
    when "string"
      m = chrome.i18n.getMessage(id_or_object, [args...])
      return escapeHtml(m) if m != ""
      m = id_or_object.replace(
        /(.)([A-Z][a-z])/g,
        (m, p1, p2) -> "#{p1} #{p2.toLowerCase()}"
      )
      m = m.replace(/_/g, ' ')
      escapeHtml(m)
    when "object"
      m = id_or_object[chrome.i18n.getUILanguage()]
      return m if m
      id_or_object["en"]
    else
      undefined

$(->
  pat = /__MSG_([A-Za-z0-9]+)__/
  $("a,button").each((i, v) ->
    e = $(v)
    title = e.attr?("title")
    if title
      newTitle = title.replace(pat, (m, p1) -> I18n(p1))
      e.attr("title", newTitle) if title != newTitle
    html = e.html()
    return true unless html
    newHtml = html.replace(pat, (m, p1) -> I18n(p1))
    e.html(newHtml) if html != newHtml
  )
)

Add multi-language object ({en: "hoge"}) support


httpProxy = require 'http-proxy'
http = require 'http'
service = require './service'

DEFAULT_CONFIG =
    dir: "/Users/adam/.wow"
    timeout: 20000

class ServiceManager
    constructor: (config) ->
        @config = config or DEFAULT_CONFIG
        @services = {}
    getService: (name) =>
        if @services[name]?
            return @services[name]
        else
            service = new service.Service(this, name)
            @services[name] = service
            return service
    getProxy: =>
        myProxy = httpProxy.createServer (req, res, proxy) =>
            service = @getService(req.headers.host)
            service.start ->
                proxy.proxyRequest req, res,
                    host: 'localhost'
                    port: service.port
        return myProxy
    listen: (port) =>
        @getProxy().listen(port)

exports.ServiceManager = ServiceManager

httpProxy = require 'http-proxy'
http = require 'http'
path = require 'path'
service = require './service'

HOME = process.env.HOME or process.env.HOMEPATH or process.env.USERPROFILE

DEFAULT_CONFIG =
    dir: path.join HOME, ".wow"
    timeout: 20000

class ServiceManager
    constructor: (config) ->
        @config = config or DEFAULT_CONFIG
        @services = {}
    getService: (name) =>
        if @services[name]?
            return @services[name]
        else
            service = new service.Service(this, name)
            @services[name] = service
            return service
    getProxy: =>
        myProxy = httpProxy.createServer (req, res, proxy) =>
            service = @getService(req.headers.host)
            service.start ->
                proxy.proxyRequest req, res,
                    host: 'localhost'
                    port: service.port
        return myProxy
    listen: (port) =>
        @getProxy().listen(port)

exports.ServiceManager = ServiceManager

Remove hardcoded home directory path

Remove hardcoded home directory path


app = require 'app'
delegate = require 'atom_delegate'
path = require 'path'
Window = require 'window'

# All opened windows.
windows = []

# Quit when all windows are closed.
app.on 'window-all-closed', ->
  app.quit()

delegate.browserMainParts.preMainMessageLoopRun = ->
  win = new Window width: 800, height: 600, show: false
  win.loadUrl "file://#{__dirname}/../static/index.html"

  win.on 'destroyed', ->
    windows.splice windows.indexOf(win), 1

  windows.push win
  win.show()

app = require 'app'
delegate = require 'atom_delegate'
path = require 'path'
Window = require 'window'

resourcePath = path.dirname(__dirname)

# All opened windows.
windows = []

# Quit when all windows are closed.
app.on 'window-all-closed', ->
  app.quit()

openWindowWithParams = (pairs) ->
  win = new Window width: 800, height: 600, show: false, title: 'Atom'

  windows.push win
  win.on 'destroyed', ->
    windows.splice windows.indexOf(win), 1

  url = "file://#{resourcePath}/static/index.html"
  separator = '?'
  for pair in pairs
    url += "#{separator}#{pair.name}=#{pair.param}"
    separator = '&' if separator is '?'

  win.loadUrl url
  win.show()

delegate.browserMainParts.preMainMessageLoopRun = ->
  openWindowWithParams [
    {name: 'bootstrapScript', param: 'window-bootstrap'},
    {name: 'resourcePath', param: resourcePath},
  ]

Set bootstrapScript and resourcePath params in URL.


Q = require 'q'
moment = require('moment')

HeadlineService = require('../services/headline')

TYPE_SOURCE_MAP =
  esri:
    name: 'Environment Agency - Abu Dhabi'
    url: 'http://www.ead.ae'
  worldBank:
    name: 'World Bank Database'
    url: 'http://data.worldbank.org'

DATE_FORMAT = 'D MMM YYYY'

module.exports = class IndicatorPresenter

  constructor: (@indicator) ->

  populateHeadlineRangesFromHeadlines: (headlines) ->
    @indicator.headlineRanges = {}
    xAxis = @indicator.indicatorDefinition?.xAxis

    if xAxis?
      newest = headlines[0][xAxis].toString()
      oldest = headlines[headlines.length - 1][xAxis].toString()

      @indicator.headlineRanges =
        oldest: moment(oldest).format(DATE_FORMAT)
        newest: moment(newest).format(DATE_FORMAT)

  populateIsUpToDate: ->
    deferred = Q.defer()

    if @indicator.narrativeRecency?
      @indicator.isUpToDate = HeadlineService.narrativeRecencyTextIsUpToDate(
        @indicator.narrativeRecency
      )
      deferred.resolve()
    else
      new HeadlineService(@indicator).calculateRecencyOfHeadline().then((narrativeRecency)=>
        @indicator.isUpToDate = HeadlineService.narrativeRecencyTextIsUpToDate(
          narrativeRecency
        )
        deferred.resolve()
      ).fail(deferred.reject)

    return deferred.promise

  populateNarrativeRecency: ->
    deferred = Q.defer()

    new HeadlineService(@indicator).calculateRecencyOfHeadline().then((recency)=>
      @indicator.narrativeRecency = recency
      deferred.resolve()
    ).fail(deferred.reject)

    return deferred.promise

Q = require 'q'
moment = require('moment')

HeadlineService = require('../services/headline')

DATE_FORMAT = 'D MMM YYYY'

module.exports = class IndicatorPresenter

  constructor: (@indicator) ->

  populateHeadlineRangesFromHeadlines: (headlines) ->
    @indicator.headlineRanges = {}
    xAxis = @indicator.indicatorDefinition?.xAxis

    if xAxis?
      newest = headlines[0][xAxis].toString()
      oldest = headlines[headlines.length - 1][xAxis].toString()

      @indicator.headlineRanges =
        oldest: moment(oldest).format(DATE_FORMAT)
        newest: moment(newest).format(DATE_FORMAT)

  populateIsUpToDate: ->
    deferred = Q.defer()

    if @indicator.narrativeRecency?
      @indicator.isUpToDate = HeadlineService.narrativeRecencyTextIsUpToDate(
        @indicator.narrativeRecency
      )
      deferred.resolve()
    else
      new HeadlineService(@indicator).calculateRecencyOfHeadline().then((narrativeRecency)=>
        @indicator.isUpToDate = HeadlineService.narrativeRecencyTextIsUpToDate(
          narrativeRecency
        )
        deferred.resolve()
      ).fail(deferred.reject)

    return deferred.promise

  populateNarrativeRecency: ->
    deferred = Q.defer()

    new HeadlineService(@indicator).calculateRecencyOfHeadline().then((recency)=>
      @indicator.narrativeRecency = recency
      deferred.resolve()
    ).fail(deferred.reject)

    return deferred.promise

Remove unused type -> source map

Remove unused type -> source map


Batman.mixin Batman.Filters,

  timeText: (str_time)->
    str_time = str_time.split('+')
    moment.locale('de')
    now = moment.utc()
    start = moment(str_time[0], "HH-mm").add(str_time[1], 'minutes')
    "#{start.from(now)} (+#{str_time[1]})"

class Dashing.List extends Dashing.Widget
  ready: ->
    if @get('unordered')
      $(@node).find('ol').remove()
    else
      $(@node).find('ul').remove()

Batman.mixin Batman.Filters,

  timeText: (str_time)->
    str_time = str_time.split('+')
    moment.locale('de')
    now = moment.utc()
    start = moment(str_time[0], "HH-mm").add(str_time[1], 'minutes')
    if str_time[1]
      "#{start.from(now)} (+#{str_time[1]})"
    else
      "#{start.from(now)} (+?)"

class Dashing.List extends Dashing.Widget
  ready: ->
    if @get('unordered')
      $(@node).find('ol').remove()
    else
      $(@node).find('ul').remove()

Hide rnv delay if undefined

Hide rnv delay if undefined


mkdirp = Meteor.npmRequire 'mkdirp'
Busboy = Meteor.npmRequire "busboy"
fs = Npm.require 'fs'
os = Npm.require 'os'
path = Npm.require 'path'

Router.route '/image/:roomId',
  name: 'image'
  where: 'server'
  onBeforeAction: (req, res, next) ->
    {roomId} = @params
    filenames = []
    if req.method is 'POST'
      busboy = new Busboy headers: req.headers
      busboy.on 'file', (fieldname, file, filename, encoding, mimetype) ->
        dir = path.join Meteor.settings.imageDir, id
        try
          mkdirp.sync dir
          saveTo = path.join dir, filename
          file.pipe fs.createWriteStream saveTo
          filenames.push saveTo
        catch error
          res.statusCode = 500
          next()
      busboy.on 'finish', ->
        if filenames.length
          res.statusCode = 204
        else
          res.statusCode = 400
        next()
      req.pipe busboy
    else
      next()
.post ->
  @response.end()

Router.route '/image/:roomId/:file',
  where: 'server'
.get ->
  {roomId, file} = @params
  imagePath = path.join Meteor.settings.imageDir, id, file
  if fs.existsSync imagePath
    image = fs.readFileSync imagePath
    @response.writeHead 200,
      'Content-Type': 'image/jpg'
    @response.write image
  else
    @response.statusCode = 500
  @response.end()

mkdirp = Meteor.npmRequire 'mkdirp'
Busboy = Meteor.npmRequire "busboy"
fs = Npm.require 'fs'
os = Npm.require 'os'
path = Npm.require 'path'

Router.route '/image/:roomId',
  name: 'image'
  where: 'server'
  onBeforeAction: (req, res, next) ->
    {roomId} = @params
    filenames = []
    if req.method is 'POST'
      busboy = new Busboy headers: req.headers
      busboy.on 'file', (fieldname, file, filename, encoding, mimetype) ->
        dir = path.join Meteor.settings.imageDir, roomId
        try
          mkdirp.sync dir
          saveTo = path.join dir, filename
          file.pipe fs.createWriteStream saveTo
          filenames.push saveTo
        catch error
          res.statusCode = 500
          next()
      busboy.on 'finish', ->
        if filenames.length
          res.statusCode = 204
        else
          res.statusCode = 400
        next()
      req.pipe busboy
    else
      next()
.post ->
  @response.end()

Router.route '/image/:roomId/:file',
  where: 'server'
.get ->
  {roomId, file} = @params
  imagePath = path.join Meteor.settings.imageDir, roomId, file
  if fs.existsSync imagePath
    image = fs.readFileSync imagePath
    @response.writeHead 200,
      'Content-Type': 'image/jpg'
    @response.write image
  else
    @response.statusCode = 500
  @response.end()

Fix variable names since changing parameter names.


# Get the name of the favorites collection for a project
module?.exports = (project) ->
  #--> Naming Convention: "Favorites for #{project_id}"
  #    Prevents global uniqueness validation conflict under project scope
  name = "Favorites"
  name += " (#{project.id})" if project?
  name

# Get the name of the favorites collection for a project
module?.exports = (project) ->
  #--> Naming Convention: "Favorites for #{project_id}"
  #    Prevents global uniqueness validation conflict under project scope
  name = "Favorites"
  name += " (#{project.slug})" if project?
  name

Use slug for favorites naming

Use slug for favorites naming

- Keeps collection name unique, but is more descriptive
- Closes #1357


op = require '../../../tasks/package/operators'

module.exports = (config) ->
  config.tasks.package.merge
    operatorsMap:
      'annotate:class': op.annotateClass
      'annotate:file': op.annotateFile
      'join': op.join
      'compile': op.compile
      'uglify': op.uglify
      'create:directory': op.createDirectory
      'strip:requires': op.stripRequires
      'exports:package': op.exportsToPackage
      'saveToFile': op.saveToFile

op = require '../../../tasks/package/operators'

module.exports = (config) ->
  config.tasks.package.merge
    operatorsMap:
      'annotate:class': op.annotateClass
      'annotate:file': op.annotateFile
      'compile': op.compile
      'create:directory': op.createDirectory
      'create:file': op.saveToFile
      'exports:package': op.exportsToPackage
      'join': op.join
      'strip:requires': op.stripRequires
      'uglify': op.uglify

Rename saveToFile operator as create:file


module.exports =
class CallbacksRegistry
  constructor: ->
    @nextId = 0
    @callbacks = {}

  add: (callback) ->
    @callbacks[++@nextId] = callback
    @nextId

  get: (id) ->
    @callbacks[id]

  call: (id, args...) ->
    @get(id).call global, args...

  apply: (id, args...) ->
    @get(id).apply global, args...

  remove: (id) ->
    delete @callbacks[id]

module.exports =
class CallbacksRegistry
  constructor: ->
    @emptyFunc = -> throw new Error "Browser trying to call a non-exist callback
      in renderer, this usually happens when renderer code forgot to release
      a callback installed on objects in browser when renderer was going to be
      unloaded or released."
    @callbacks = {}

  add: (callback) ->
    id = Math.random().toString()
    @callbacks[id] = callback
    id

  get: (id) ->
    @callbacks[id] ? ->

  call: (id, args...) ->
    @get(id).call global, args...

  apply: (id, args...) ->
    @get(id).apply global, args...

  remove: (id) ->
    delete @callbacks[id]

Use random number as id in CallbacksRegistry.

It's very possible that the callbacks got GCed before the render view is
closed (like page getting refreshed), so we should not let browser call
the wrong callback, instead we should throw error whenever a callback is
not found.


Controller = Ember.Controller.extend
  name: 'profile'

  needs: ['currentUser', 'accounts', 'account']
  userBinding: 'controllers.currentUser'
  accountBinding: 'controllers.account'

  activate: (action, params) ->
    this["view#{$.camelize(action)}"]()

  viewHooks: ->
    @connectTab('hooks')
    @get('controllers.account').reloadHooks()

  viewUser: ->
    @connectTab('user')

  connectTab: (tab) ->
    @set('tab', tab)

  billingUrl: (->
    id = if @get('account.type') == 'user' then 'user' else @get('account.login')
    "#{@get('config').billing_endpoint}/subscriptions/#{id}"
  ).property('account.login', 'account.type')

Travis.ProfileController = Controller

Controller = Ember.Controller.extend
  name: 'profile'

  needs: ['currentUser', 'accounts', 'account']
  userBinding: 'controllers.currentUser'
  accountBinding: 'controllers.account'

  activate: (action, params) ->
    this["view_#{action}".camelize()]()

  viewHooks: ->
    @connectTab('hooks')
    @get('controllers.account').reloadHooks()

  viewUser: ->
    @connectTab('user')

  connectTab: (tab) ->
    @set('tab', tab)

  billingUrl: (->
    id = if @get('account.type') == 'user' then 'user' else @get('account.login')
    "#{@get('config').billing_endpoint}/subscriptions/#{id}"
  ).property('account.login', 'account.type')

Travis.ProfileController = Controller

Use String.camelize instead of $.camelize


Twitter = require 'twitter'

client = new Twitter
  consumer_key: process.env.HUBOT_TWITTER_CONSUMER_KEY,
  consumer_secret: process.env.HUBOT_TWITTER_CONSUMER_SECRET,
  access_token_key: process.env.HUBOT_TWITTER_ACCESS_TOKEN_KEY,
  access_token_secret: process.env.HUBOT_TWITTER_ACCESS_TOKEN_SECRET,

# I give you, a pre-configured twitter client,
# So you don't have to.
module.exports =
  client: client

  # A helper for the general case, posts `body`
  # as a tweet, and calls you back with it, as well
  # as a pre-built URL
  tweet: (body, cb=(err, tweet, url)) =>
    params = status: body
    client.post 'statuses/update', params, (error, tweet, response) ->
      return cb(error)  if error

      # LUCKY US
      myself = tweet.user.screen_name
      tid = tweet.id_str
      url = "https://twitter.com/#{myself}/status/#{tid}"
      return cb(tweet, url)

Twitter = require 'twitter'

client = new Twitter
  consumer_key: process.env.HUBOT_TWITTER_CONSUMER_KEY,
  consumer_secret: process.env.HUBOT_TWITTER_CONSUMER_SECRET,
  access_token_key: process.env.HUBOT_TWITTER_ACCESS_TOKEN_KEY,
  access_token_secret: process.env.HUBOT_TWITTER_ACCESS_TOKEN_SECRET,

# I give you, a pre-configured twitter client,
# So you don't have to.
module.exports =
  client: client

  # A helper for the general case, posts `body`
  # as a tweet, and calls you back with it, as well
  # as a pre-built URL.
  #
  # Callback invoked as `cb(err, tweet, url)`
  tweet: (body, cb=(->)) =>
    params = status: body
    client.post 'statuses/update', params, (error, tweet, response) ->
      return cb(error) if error

      # LUCKY US
      myself = tweet.user.screen_name
      tid = tweet.id_str
      url = "https://twitter.com/#{myself}/status/#{tid}"
      return cb(undefined, tweet, url)

Fix the header and success path

Fix the header and success path


class app.views.BasePage extends app.View
  constructor: (@el, @entry) -> super

  deactivate: ->
    if super
      @highlightNodes = []

  render: (content, fromCache = false) ->
    @highlightNodes = []
    @previousTiming = null
    @addClass "_#{@entry.doc.type}" unless @constructor.className
    @html content
    @prepare?() unless fromCache
    @activate()
    @delay @afterRender if @afterRender
    if @highlightNodes.length > 0
      $.requestAnimationFrame => $.requestAnimationFrame(@paintCode)
    return

  highlightCode: (el, language) ->
    return unless language
    language = "language-#{language}"
    if $.isCollection(el)
      for e in el
        e.classList.add(language)
        @highlightNodes.push(e)
    else if el
      el.classList.add(language)
      @highlightNodes.push(el)
    return

  paintCode: (timing) =>
    if @previousTiming
      if Math.round(1000 / (timing - @previousTiming)) > 50 # fps
        @nodesPerFrame = Math.round(Math.min(@nodesPerFrame * 1.25, 50))
      else
        @nodesPerFrame = Math.round(Math.max(@nodesPerFrame * .8, 10))
    else
      @nodesPerFrame = 10
    Prism.highlightElement(el) for el in @highlightNodes.splice(0, @nodesPerFrame)
    $.requestAnimationFrame(@paintCode) if @highlightNodes.length > 0
    @previousTiming = timing
    return

class app.views.BasePage extends app.View
  constructor: (@el, @entry) -> super

  deactivate: ->
    if super
      @highlightNodes = []

  render: (content, fromCache = false) ->
    @highlightNodes = []
    @previousTiming = null
    @addClass "_#{@entry.doc.type}" unless @constructor.className
    @html content
    @prepare?() unless fromCache
    @activate()
    @delay @afterRender if @afterRender
    if @highlightNodes.length > 0
      $.requestAnimationFrame => $.requestAnimationFrame(@paintCode)
    return

  highlightCode: (el, language) ->
    return unless language
    language = "language-#{language}"
    if $.isCollection(el)
      for e in el
        e.classList.add(language)
        @highlightNodes.push(e)
    else if el
      el.classList.add(language)
      @highlightNodes.push(el)
    return

  paintCode: (timing) =>
    if @previousTiming
      if Math.round(1000 / (timing - @previousTiming)) > 50 # fps
        @nodesPerFrame = Math.round(Math.min(@nodesPerFrame * 1.25, 50))
      else
        @nodesPerFrame = Math.round(Math.max(@nodesPerFrame * .8, 10))
    else
      @nodesPerFrame = 10

    for el in @highlightNodes.splice(0, @nodesPerFrame)
      $.remove(clipEl) if clipEl = el.lastElementChild
      Prism.highlightElement(el)
      $.append(el, clipEl) if clipEl

    $.requestAnimationFrame(@paintCode) if @highlightNodes.length > 0
    @previousTiming = timing
    return

Fix copy-to-clipboard links in highlighted code blocks


$.fn.equalHeights = () ->
    max = -1
    $(this).each ->
        h = $(this).height()
        max = (if h > max then h else max)
        $(this).css "min-height": max

$.fn.equalHeights = () ->
    max = -1
    $(this).each ->
        h = $(this).outerHeight()
        max = (if h > max then h else max)
        $(this).css "min-height": max

Use calculated outerHeight for equalHeight plugin


module.exports = ->
  # Project configuration
  @initConfig
    pkg: @file.readJSON 'package.json'

    # BDD tests on Node.js
    mochaTest:
      nodejs:
        src: ['spec/*.coffee']
        options:
          reporter: 'spec'

    # Coding standards
    coffeelint:
      components:
        files:
          src: ['spec/*.coffee', 'src/*.coffee', 'src/runtimes/*.coffee']
        options:
          max_line_length:
            value: 80
            level: 'warn'

    # Protocol tests
    shell:
      msgflo:
        command: 'node bin/msgflo'
        options:
          async: true
      fbp_test:
        command: 'fbp-test --colors'

  # Grunt plugins used for building

  # Grunt plugins used for testing
  @loadNpmTasks 'grunt-mocha-test'
  @loadNpmTasks 'grunt-coffeelint'
  @loadNpmTasks 'grunt-shell-spawn'

  # Our local tasks
  @registerTask 'fbp-test', [
    'shell:msgflo'
    'shell:fbp_test'
    'shell:msgflo:kill'
  ]

  @registerTask 'build', 'Build the chosen target platform', (target = 'all') =>
    # nothing

  @registerTask 'test', 'Build and run automated tests', (target = 'all') =>
    @task.run 'coffeelint'
    @task.run 'build'
    @task.run 'mochaTest'
#    @task.run 'fbp-test'

  @registerTask 'default', ['test']

module.exports = ->
  # Project configuration
  @initConfig
    pkg: @file.readJSON 'package.json'

    # BDD tests on Node.js
    mochaTest:
      nodejs:
        src: ['spec/*.coffee']
        options:
          reporter: 'spec'

    # Coding standards
    coffeelint:
      components:
        files:
          src: ['spec/*.coffee', 'src/*.coffee', 'src/runtimes/*.coffee']
        options:
          max_line_length:
            value: 100
            level: 'warn'

    # Protocol tests
    shell:
      msgflo:
        command: 'node bin/msgflo'
        options:
          async: true
      fbp_test:
        command: 'fbp-test --colors'

  # Grunt plugins used for building

  # Grunt plugins used for testing
  @loadNpmTasks 'grunt-mocha-test'
  @loadNpmTasks 'grunt-coffeelint'
  @loadNpmTasks 'grunt-shell-spawn'

  # Our local tasks
  @registerTask 'fbp-test', [
    'shell:msgflo'
    'shell:fbp_test'
    'shell:msgflo:kill'
  ]

  @registerTask 'build', 'Build the chosen target platform', (target = 'all') =>
    # nothing

  @registerTask 'test', 'Build and run automated tests', (target = 'all') =>
    @task.run 'coffeelint'
    @task.run 'build'
    @task.run 'mochaTest'
#    @task.run 'fbp-test'

  @registerTask 'default', ['test']

Bump line length limit to 100

Grunt: Bump line length limit to 100


# I love this program.
#
# Atom will evaluate this file each time a new window is opened. It is run
# after packages are loaded/activated and after the previous editor state
# has been restored.
#
# An example hack to log to the console when each text editor is saved.
#
# atom.workspace.observeTextEditors (editor) ->
#   editor.onDidSave ->
#     console.log "Saved! #{editor.getPath()}"

# Hide scope-related notices shortly after displaying them
atom.workspace.notificationManager.onDidAddNotification (popup) ->
	if /^\s*Scopes at Cursor/.test(popup.message)
		setTimeout (->
			popup.dismiss()
		), atom.config.get("popupDismissDelay") || 1000

# Crude debugging method to see what events we can hook into
prot = atom.emitter.constructor.prototype
emit = prot.emit
global.traceEmissions = (active) ->
	if active
		prot.emit = () ->
			console.log arguments
			emit.apply @, arguments
	else
		prot.emit = emit

Add a crude debugging method for Atom package development


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

roundUpBy = (value, round_to) ->
  return round_to * Math.ceil(value / round_to)

formatNumber = (number) ->
  Math.ceil(number).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")

animateNumber = (element, target_count, duration) ->
  jQuery(counter: 0).animate { counter: target_count },
    duration: duration
    easing: "easeInOutQuint"
    step: ->
      element.text formatNumber(@counter)
    complete: ->
      jQuery(counter: target_count).animate { counter: (40 + target_count) },
        duration: 400
        easing: "easeOutQuint"
        step: ->
          element.text formatNumber(@counter)
          return
      return
  return

$ ->
  metricsInview = new (Waypoint.Inview)(
    element: $('.metric-box')[0]
    enter: (direction) ->
      $(".metric-box strong").each (index) ->
        $this = $(this)
        $target_count = parseInt($this.text().replace(/\,/g, ''))
        animateNumber($this, $target_count, 300 + index * 200)
      # only run this once
      this.destroy()
  )

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

roundUpBy = (value, round_to) ->
  return round_to * Math.ceil(value / round_to)

formatNumber = (number) ->
  Math.ceil(number).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")

animateNumber = (element, target_count, duration) ->
  jQuery(counter: 0).animate { counter: target_count - 40 },
    duration: duration
    easing: "easeInOutQuint"
    step: ->
      element.text formatNumber(@counter)
    complete: ->
      jQuery(counter: target_count - 40).animate { counter: (target_count) },
        duration: 400
        easing: "easeOutQuint"
        step: ->
          element.text formatNumber(@counter)
          return
      return
  return

$ ->
  metricsInview = new (Waypoint.Inview)(
    element: $('.metric-box')[0]
    enter: (direction) ->
      $(".metric-box strong").each (index) ->
        $this = $(this)
        $target_count = parseInt($this.text().replace(/\,/g, ''))
        animateNumber($this, $target_count, 300 + index * 200)
      # only run this once
      this.destroy()
  )

Make the final count the original number


app = require 'core/application'
AuthModal = require 'views/core/AuthModal'
RootView = require 'views/core/RootView'
template = require 'templates/sales-view'

module.exports = class SalesView extends RootView
  id: 'sales-view'
  template: template

  events:
    'click .btn-contact-us': 'onClickContactUs'
    'click .btn-create-account': 'onClickSignup'
    'click .btn-login-account': 'onClickLogin'

  getTitle: ->
    'CodeCombat'

  onClickContactUs: (e) ->
    app.router.navigate '/teachers/freetrial', trigger: true

  onClickLogin: (e) ->
    @openModalView new AuthModal(mode: 'login') if me.get('anonymous')
    window.tracker?.trackEvent 'Started Login', category: 'Sales', label: 'Sales Login'

  onClickSignup: (e) ->
    @openModalView new AuthModal() if me.get('anonymous')
    window.tracker?.trackEvent 'Started Signup', category: 'Sales', label: 'Sales Create'

  logoutRedirectURL: false

app = require 'core/application'
AuthModal = require 'views/core/AuthModal'
RootView = require 'views/core/RootView'
template = require 'templates/sales-view'

module.exports = class SalesView extends RootView
  id: 'sales-view'
  template: template

  events:
    'click .btn-contact-us': 'onClickContactUs'
    'click .btn-create-account': 'onClickSignup'
    'click .btn-login-account': 'onClickLogin'
    'click #down-arrow': 'onClickDownArrow'

  getTitle: ->
    'CodeCombat'

  onClickContactUs: (e) ->
    app.router.navigate '/teachers/freetrial', trigger: true

  onClickLogin: (e) ->
    @openModalView new AuthModal(mode: 'login') if me.get('anonymous')
    window.tracker?.trackEvent 'Started Login', category: 'Sales', label: 'Sales Login'

  onClickSignup: (e) ->
    @openModalView new AuthModal() if me.get('anonymous')
    window.tracker?.trackEvent 'Started Signup', category: 'Sales', label: 'Sales Create'

  logoutRedirectURL: false

  onClickDownArrow: (e) ->
    $('#page-container').animate({
      scrollTop: $('[name="' + $(e.target).closest('a').attr('href').substr(1) + '"]').offset().top
    }, 300)
    false

Add smooth down arrow navigation to /schools


class Dashing.Lecturelist extends Dashing.Widget

  ready: ->

  onData: (data) ->
    i = 0
    node = $(@get('node'))
    if data.items.length == 0
      @set 'empty', true
    while i < data.items.length
      capacity = data.items[i].capacity
      tickets = data.items[i].tickets
      people = ""
      x = capacity - tickets
      while x--
        people += ""
      while tickets--
        people += ""
      data.items[i].people = people

      i++

class Dashing.Lecturelist extends Dashing.Widget

  ready: ->
    @currentIndex = 0
    @items = $(@node).find('li')
    @nextItem()
    @startCarousel()

  onData: (data) ->
    @currentIndex = 0
    i = 0
    if data.items.length == 0
      @set 'empty', true
    while i < data.items.length
      capacity = data.items[i].capacity
      tickets = data.items[i].tickets
      people = ""
      x = capacity - tickets
      while x--
        people += ""
      while tickets--
        people += ""
      data.items[i].people = people

      i++

  startCarousel: ->
    @items.hide()
    setInterval(@nextItem, 10000)

  nextItem: =>
    items = @get('items')
    if items
      console.log(items[@currentIndex])
      $(items[@currentIndex]).fadeOut =>
        @currentIndex = (@currentIndex + 1) % items.length
        $(items[@currentIndex]).fadeIn()

Make upcoming lectures into a scrolly fadey inny outy carousel


fs = require 'fs'

require 'window'

measure 'spec suite require time', ->
  fsUtils = require 'fs-utils'
  path = require 'path'
  require 'spec-helper'

  requireSpecs = (directoryPath, specType) ->
    for specPath in fsUtils.listTreeSync(path.join(directoryPath, 'spec')) when /-spec\.coffee$/.test specPath
      require specPath

  setSpecType = (specType) ->
    for spec in jasmine.getEnv().currentRunner().specs() when not spec.specType?
      spec.specType = specType

  # Run core specs
  requireSpecs(window.resourcePath)
  setSpecType('core')

  # Run bundled package specs
  if fsUtils.isDirectorySync(config.nodeModulesDirPath)
    for packageName in fs.readdirSync(config.nodeModulesDirPath)
      packagePath = path.join(config.nodeModulesDirPath, packageName)
      requireSpecs(packagePath, 'bundled') if atom.isInternalPackage(packagePath)
    setSpecType('bundled')

  # Run user package specs
  if fsUtils.isDirectorySync(config.userPackagesDirPath)
    for packageName in fs.readdirSync(config.userPackagesDirPath)
      requireSpecs(path.join(config.userPackagesDirPath, packageName))
    setSpecType('user')

require 'window'

measure 'spec suite require time', ->
  fs = require 'fs'
  fsUtils = require 'fs-utils'
  path = require 'path'
  _ = require 'underscore'
  require 'spec-helper'

  requireSpecs = (directoryPath, specType) ->
    for specPath in fsUtils.listTreeSync(path.join(directoryPath, 'spec')) when /-spec\.coffee$/.test specPath
      require specPath

  setSpecType = (specType) ->
    for spec in jasmine.getEnv().currentRunner().specs() when not spec.specType?
      spec.specType = specType

  # Run core specs
  requireSpecs(window.resourcePath)
  setSpecType('core')

  fixturesPackagesPath = fsUtils.resolveOnLoadPath('fixtures/packages')
  packagePaths = atom.getAvailablePackageNames().map (packageName) -> atom.resolvePackagePath(packageName)
  packagePaths = _.groupBy packagePaths, (packagePath) ->
    if packagePath.indexOf("#{fixturesPackagesPath}#{path.sep}") is 0
      'fixtures'
    else if packagePath.indexOf("#{window.resourcePath}#{path.sep}") is 0
      'bundled'
    else
      'user'

  # Run bundled package specs
  requireSpecs(packagePath) for packagePath in packagePaths.bundled
  setSpecType('bundled')

  # Run user package specs
  requireSpecs(packagePath) for packagePath in packagePaths.user
  setSpecType('user')

Allow user package specs to override bundled package specs


class @VoteController extends RouteController

  waitOn: ->
    q = {}
    if filterType = @params.filterType
      q.type = filterType
    if filterTag = @params.filterTag
      q.tags = filterTag
    Meteor.subscribe('proposals-min', q)

  after: -> document.title = "Vote | Reversim Summit 2014"

  tempalte: 'vote'

  data: ->
    speakers = User.allSpeakers()
    user = User.current()
    speakers = if user and (user.admin() or user.moderator()) then sort(speakers) else _.shuffle(speakers)
    return {
      page: 'vote'
      speakers: speakers
      filterType: @params.filterType
      filterTag: @params.filterTag
    }

sort = (speakers) ->
  _.sortBy(speakers, (speaker) ->
    - sum(speaker.proposals().map((p) -> p.voteCount()))
  )

sum = (arr) -> _.reduce(arr, (sum, num) -> sum + num)

class @VoteController extends RouteController

  waitOn: ->
    q = {}
    if filterType = @params.filterType
      q.type = filterType
    if filterTag = @params.filterTag
      q.tags = filterTag
    Meteor.subscribe('proposals-min', q)

  after: -> document.title = "Vote | Reversim Summit 2014"

  tempalte: 'vote'

  data: ->
    speakers = User.allSpeakers()
    if speakers and speakers.length > 0
      user = User.current()
      speakers = if user and (user.admin() or user.moderator()) then sort(speakers) else _.shuffle(speakers)
    return {
      page: 'vote'
      speakers: speakers
      filterType: @params.filterType
      filterTag: @params.filterTag
    }

sort = (speakers) ->
  _.sortBy(speakers, (speaker) ->
    - sum(speaker.proposals().map((p) -> p.voteCount()))
  )

sum = (arr) -> _.reduce(arr, (sum, num) -> sum + num)

Fix for spakers array that's not ready yet


class VendorView extends KDView

  constructor:->

    super cssClass: 'vendor'

    @vendorController = new KDListViewController
      selection    : yes
      viewOptions  :
        cssClass   : 'vendor-list'
        wrapper    : yes
        itemClass  : VendorItemView
    , items        : [
      { name : "Koding",       view : new VendorKoding }
      { name : "Amazon",       view : new VendorAmazon }
      { name : "DigitalOcean", view : new VendorDigitalOcean }
      { name : "EngineYard",   view : new VendorEngineyard }
    ]

  viewAppended:->

    @mainView = new KDTabView
      cssClass : "vendor-mainview"
      hideHandleContainer : yes

    @vendorListView = new KDView

    @vendorListView.addSubView new KDHeaderView
      title : "Vendors"
      type : "medium"

    @vendorListView.addSubView @vendorController.getView()

    @addSubView @messagesSplit = new SplitViewWithOlderSiblings
      sizes     : ["200px",null]
      views     : [@vendorListView, @mainView]
      cssClass  : "vendor-split"
      resizable : no

    # Add vendor views to mainview
    for vendor in @vendorController.itemsOrdered
      @mainView.addPane vendor.getData().view

    # Add Welcome pane
    @mainView.addPane new VendorWelcomeView

    @vendorController.on "ItemSelectionPerformed", (controller, item)=>
      {view} = item.items.first.getData()
      @mainView.showPane view

class VendorView extends KDView

  constructor:->

    super cssClass: 'vendor'

    @vendorController = new KDListViewController
      selection    : yes
      viewOptions  :
        cssClass   : 'vendor-list'
        wrapper    : yes
        itemClass  : VendorItemView
    , items        : [
      { name : "Koding",       view : new VendorKoding }
      { name : "Google",       view : new VendorGoogle }
      { name : "Amazon",       view : new VendorAmazon }
      { name : "DigitalOcean", view : new VendorDigitalOcean }
      { name : "EngineYard",   view : new VendorEngineyard }
    ]

  viewAppended:->

    @mainView = new KDTabView
      cssClass : "vendor-mainview"
      hideHandleContainer : yes

    @vendorListView = new KDView

    @vendorListView.addSubView new KDHeaderView
      title : "Vendors"
      type : "medium"

    @vendorListView.addSubView @vendorController.getView()

    @addSubView @messagesSplit = new SplitViewWithOlderSiblings
      sizes     : ["200px",null]
      views     : [@vendorListView, @mainView]
      cssClass  : "vendor-split"
      resizable : no

    # Add vendor views to mainview
    for vendor in @vendorController.itemsOrdered
      @mainView.addPane vendor.getData().view

    # Add Welcome pane
    @mainView.addPane new VendorWelcomeView

    @vendorController.on "ItemSelectionPerformed", (controller, item)=>
      {view} = item.items.first.getData()
      @mainView.showPane view

Add VendorGoogle to vendor list

VendorView: Add VendorGoogle to vendor list


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.replace(new RegExp("^#{TentStatus.config.PATH_PREFIX || ''}"), '')
    path = path.replace(/^\/?/, '/') # ensure path begins with a /
    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 selected when path prefix

Fix nav selected when path prefix


## Browser policy to allow many external sources, but *not* frames.
#BrowserPolicy.content.allowOriginForAll 'http://meteor.local'
BrowserPolicy.content.allowImageOrigin '*'
BrowserPolicy.content.allowMediaOrigin '*'
BrowserPolicy.content.allowFontOrigin '*'
BrowserPolicy.content.allowStyleOrigin '*'
BrowserPolicy.content.allowConnectOrigin '*'

## Allow blob source for images, as needed by pdf.js
BrowserPolicy.content.allowImageOrigin 'blob:'

Fix PDFs with embedded images via BrowserPolicy

pdf.js embeds blob: objects into <img> tags (when the PDF has embedded
images), and we need to explicitly allow this.


{Wrappers} = require "#{__dirname}/wrappers"

class BeanBuilder

  basePath: null
  suffix: null
  createObjectCallback: null
  useSingletons: false

  instances: null

  constructor: ({@basePath, @suffix, @createObjectCallback, @useSingletons}) ->
    if @suffix[0] is '_'
      @suffix = @suffix[1..]
    @instances = {}
    @useSingletons = @useSingletons is true

  build: (config) ->
    throw new Error 'No task defined.' unless config
    suffixLength = @suffix.length
    config.type = Wrappers.underscored(config.type ? 'default')
    config.type = "#{config.type}_#{@suffix}" unless config.type[-suffixLength..] is "_#{@suffix}"
    className = Wrappers.classify config.type
    if (@useSingletons or config.singleton) and @instances[className]
      instance = @instances[className]
      instance.reconfigure config if instance.reconfigure config
      return instance
    filePath = "#{@basePath}/#{config.type}"
    requiredScope = require filePath
    Clazz = requiredScope[className]
    throw new Error "Type #{className} was not found in file #{filePath}." unless Clazz
    instance = if typeof @createObjectCallback is 'function'
      @createObjectCallback Clazz, config
    else
      new Clazz
    # Shadow copy of this instance for singleton usage.
    if (@useSingletons or config.singleton)
      @instances[className] = instance
    instance

exports.BeanBuilder = BeanBuilder

{Wrappers} = require "#{__dirname}/wrappers"

class BeanBuilder

  basePath: null
  suffix: null
  createObjectCallback: null
  useSingletons: false

  instances: null

  constructor: ({@basePath, @suffix, @createObjectCallback, @useSingletons}) ->
    if @suffix[0] is '_'
      @suffix = @suffix[1..]
    @instances = {}
    @useSingletons = @useSingletons is true

  build: (config) ->
    throw new Error 'No task defined.' unless config
    suffixLength = @suffix.length
    config.type = Wrappers.underscored(config.type ? 'default')
    config.type = "#{config.type}_#{@suffix}" unless config.type[-suffixLength..] is "_#{@suffix}"
    className = Wrappers.classify config.type
    if (@useSingletons or config.singleton) and @instances[className]
      instance = @instances[className]
      instance.reconfigure config if instance.reconfigure config
      return instance
    filePath = "#{@basePath}/#{config.type}"
    requiredScope = require filePath
    Clazz = requiredScope[className]
    throw new Error "Type #{className} was not found in file #{filePath}." unless Clazz
    instance = if typeof @createObjectCallback is 'function'
      @createObjectCallback Clazz, config
    else
      new Clazz
    instance._idType = config.type
    # Shadow copy of this instance for singleton usage.
    if (@useSingletons or config.singleton)
      @instances[className] = instance
    instance

exports.BeanBuilder = BeanBuilder

Add a generic idType to each built instance. Perhaps removed later.


Yossarian.EventArtistView = Ember.View.extend
  tagName: 'img'
  attributeBindings: ['src', 'title']

  preload: ->
    @set('src', @get('content.image.image.large.url'))
    @set('title', @get('content.name'))

Yossarian.EventArtistsView = Ember.CollectionView.extend
  tagName: 'div'
  content: []
  currentItem: null
  itemViewClass: Yossarian.EventArtistView

  didInsertElement: ->
    views = @get('childViews')

    view.preload() for view in views[0..2]

    for view in views[3..(views.length - 1)]
      ((view) -> setTimeout (-> view.preload()), 3000)(view)

    setTimeout (=> @initilizeSlider()), 100

  initilizeSlider: ->
    @$().nivoSlider
      effect: 'slideInRight'
      animSpeed: 400
      pauseTime: Math.random() * (20000 - 3000) + 3000
      directionNav: false
      controlNav: false
      pauseOnHover: true
      startSlide: Math.round(Math.random() * 2)

Yossarian.EventArtistView = Ember.View.extend
  tagName: 'img'
  attributeBindings: ['src', 'title']

  preload: ->
    @set('src', @get('content.image.image.large.url'))
    @set('title', @get('content.name'))

Yossarian.EventArtistsView = Ember.CollectionView.extend
  tagName: 'div'
  content: []
  nextSlideView: null
  itemViewClass: Yossarian.EventArtistView

  didInsertElement: ->
    views = @get('childViews')

    view.preload() for view in views[0..2]

    @set('nextSlideView', views[3])

    setTimeout (=> @initilizeSlider()), 100

  initilizeSlider: ->
    @$().nivoSlider
      effect: 'slideInRight'
      animSpeed: 400
      pauseTime: Math.random() * (20000 - 3000) + 3000
      directionNav: false
      controlNav: false
      pauseOnHover: true
      startSlide: Math.round(Math.random() * 2)
      afterChange: => @preloadNextSlideView()

  preloadNextSlideView: ->
    views = @get('childViews')
    index = views.indexOf(@get('nextSlideView')) + 1

    return if index >= views.length

    @set('nextSlideView', views[index])

  nextSlideViewDidChange: (->
    @get('nextSlideView').preload()
  ).observes('nextSlideView')

Add more inteligent image preloading

Add more inteligent image preloading


React = require 'react'
BS = require 'react-bootstrap'
_ = require 'underscore'
{StepPanel} = require '../../helpers/policies'

{CardBody} = require '../pinned-header-footer-card/sections'
AsyncButton = require '../buttons/async-button'
{TaskStore} = require '../../flux/task'

{TaskStepStore} = require '../../flux/task-step'

module.exports =

  renderContinueButton: ->
    return null if @hideContinueButton?()
    isWaiting = TaskStepStore.isLoading(@props.id)
    isSaving = TaskStepStore.isSaving(@props.id)
    isFailed = TaskStepStore.isFailed(@props.id)
    # if this is the last step completed and the view is read-only,
    # then you cannot continue, and this will override @isContinueEnabled
    cannotContinue = not StepPanel.canContinue(@props.id)

    <AsyncButton
      bsStyle='primary'
      className='continue'
      onClick={@onContinue}
      disabled={not @isContinueEnabled?() or cannotContinue}
      isWaiting={isWaiting}
      isFailed={isFailed}
      >
      {@continueButtonText?() or 'Continue'}
    </AsyncButton>

  render: ->
    {pinned, courseId, id, taskId, review} = @props

    # from StepFooterMixin
    footer = @renderFooter({stepId: id, taskId, courseId, review})
    <CardBody className='task-step' footer={footer} pinned={pinned}>
      {@renderBody()}
      {@renderGroup?()}
    </CardBody>

React = require 'react'
BS = require 'react-bootstrap'
_ = require 'underscore'
{StepPanel} = require '../../helpers/policies'

{CardBody} = require '../pinned-header-footer-card/sections'
AsyncButton = require '../buttons/async-button'
{TaskStore} = require '../../flux/task'

{TaskStepStore} = require '../../flux/task-step'

module.exports =

  renderContinueButton: ->
    return null if @hideContinueButton?()

    # if this is the last step completed and the view is read-only,
    # then you cannot continue, and this will override @isContinueEnabled
    waitingText = switch
      when TaskStepStore.isLoading(@props.id) then "Loading…"
      when TaskStepStore.isSaving(@props.id)  then "Saving…"
      else null

    cannotContinue = not StepPanel.canContinue(@props.id) or not @isContinueEnabled?()

    <AsyncButton
      bsStyle='primary'
      className='continue'
      onClick={@onContinue}
      disabled={cannotContinue}
      isWaiting={!!waitingText}
      waitingText={waitingText}
      isFailed={TaskStepStore.isFailed(@props.id)}
      >
      {@continueButtonText?() or 'Continue'}
    </AsyncButton>

  render: ->
    {pinned, courseId, id, taskId, review} = @props

    # from StepFooterMixin
    footer = @renderFooter({stepId: id, taskId, courseId, review})
    <CardBody className='task-step' footer={footer} pinned={pinned}>
      {@renderBody()}
      {@renderGroup?()}
    </CardBody>

Disable continue button when saving

Disable continue button when saving

A task step should not be continued while it's state is saving or
loading. The earlier code was checking the isSaving state, but wasn't
passing it onto the AsyncButton.


define (require) ->
  router = require('cs!router')
  analytics = require('cs!helpers/handlers/analytics')
  BaseView = require('cs!helpers/backbone/views/base')
  template = require('hbs!./nav-template')
  require('less!./nav')

  return class MediaNavView extends BaseView
    template: template
    templateHelpers:
      _hideProgress: () -> @hideProgress

    initialize: (options) ->
      super()
      @listenTo(@model, 'change:page change:pages', @render) if @model
      @hideProgress = options.hideProgress

    events:
      'click .next': 'nextPage'
      'click .back': 'previousPage'

    nextPage: () ->
      @navigate(@model.nextPage())

    previousPage: () ->
      @navigate(@model.previousPage())

    navigate: (page) ->
      y = window.pageYOffset || document.documentElement.scrollTop

      if y > 700
        window.scrollTo(0, 300)

      route = "/content/#{router.current().params[0]}:#{page}" # Deterimine the new route
      router.navigate(route) # Update browser URL to reflect the new route
      analytics.send() # Send the analytics information for the new route

define (require) ->
  $ = require('jquery')
  router = require('cs!router')
  analytics = require('cs!helpers/handlers/analytics')
  BaseView = require('cs!helpers/backbone/views/base')
  template = require('hbs!./nav-template')
  require('less!./nav')

  return class MediaNavView extends BaseView
    template: template
    templateHelpers:
      _hideProgress: () -> @hideProgress

    initialize: (options) ->
      super()
      @listenTo(@model, 'change:page change:pages', @render) if @model
      @hideProgress = options.hideProgress

    events:
      'click .next': 'nextPage'
      'click .back': 'previousPage'

    nextPage: () ->
      @navigate(@model.nextPage())

    previousPage: () ->
      @navigate(@model.previousPage())

    navigate: (page) ->
      maxY = $('.media-header').offset().top
      y = window.pageYOffset || document.documentElement.scrollTop

      if y > maxY
        window.scrollTo(0, maxY)

      route = "/content/#{router.current().params[0]}:#{page}" # Deterimine the new route
      router.navigate(route) # Update browser URL to reflect the new route
      analytics.send() # Send the analytics information for the new route

Determine when to scroll content back to top dynamically