_  = require('lodash')
browsers = require('../browsers')

remoteReporters = ['dots']
remoteReporters.push('saucelabs') if process.env.TRAVIS_BRANCH == 'master'

remoteKarma = _.reduce(browsers, (memo, config, browser) ->
  memo[browser] =
    browsers: [browser]
    browserDisconnectTimeout: 10000
    browserDisconnectTolerance: 2
    browserNoActivityTimeout: 60000
    reporters: remoteReporters
  return memo
, {})

module.exports = (grunt) ->
  grunt.config('karma', _.extend(remoteKarma,
    options:
      configFile: 'config/karma.js'
      files: [
        'node_modules/jquery/dist/jquery.js'
        "http://localhost:#{grunt.config('port')}/test/quill.js"

        'test/fixtures/unit.html'
        'test/helpers/inject.coffee'
        'test/helpers/matchers.coffee'

        { pattern: 'test/fixtures/*.css', included: false }

        # We dont do **/*.coffee to control order of tests
        'test/unit/lib/*.coffee'
        'test/unit/core/*.coffee'
        'test/unit/modules/*.coffee'
        'test/unit/themes/*.coffee'
      ]
    coverage:
      browserNoActivityTimeout: 30000
      browsers: ['PhantomJS']
      reporters: ['coverage']
    local:
      browsers: ['Chrome', 'Firefox', 'Safari']
    server:
      autoWatch: true
      browsers: []
      singleRun: false
    test:
      browsers: ['PhantomJS']
  ))

_  = require('lodash')
browsers = require('../browsers')

remoteReporters = ['dots']
remoteReporters.push('saucelabs') if process.env.TRAVIS_BRANCH == 'master'

remoteKarma = _.reduce(browsers, (memo, config, browser) ->
  memo[browser] =
    browsers: [browser]
    browserDisconnectTimeout: 10000
    browserDisconnectTolerance: 2
    browserNoActivityTimeout: 60000
    reporters: remoteReporters
  return memo
, {})

module.exports = (grunt) ->
  grunt.config('karma', _.extend(remoteKarma,
    options:
      configFile: 'config/karma.js'
      files: [
        'node_modules/jquery/dist/jquery.js'
        "http://localhost:#{grunt.config('port')}/test/quill.js"

        'test/fixtures/unit.html'
        'test/helpers/inject.coffee'
        'test/helpers/matchers.coffee'

        { pattern: 'test/fixtures/*.css', included: false }

        # We dont do **/*.coffee to control order of tests
        'test/unit/lib/*.coffee'
        'test/unit/core/*.coffee'
        'test/unit/modules/*.coffee'
        'test/unit/themes/*.coffee'
      ]
    coverage:
      browserNoActivityTimeout: 30000
      browsers: ['PhantomJS']
      reporters: ['coverage']
    local:
      browsers: ['Chrome', 'Firefox', 'Safari']
    server:
      autoWatch: true
      browsers: []
      singleRun: false
    test:
      browsers: ['Chrome']
  ))

Switch default browser to Chrome until Phantom 2.0

Current version does not have function.bind


# noop.pw
moment = require 'moment'
{renderVita} = require './demi'
global.vita = renderVita './riptide/vita.yaml'
global._now = moment()
global.DEBUG = '-p' not in global.process.argv

module.exports = config:
  paths:
    watched: ['riptide']

  plugins:
    autoReload:
      enabled: yes
    coffeelint:
      pattern: /^riptide\/.*\.(coffee)$/
      useCoffeelintJson: yes
    jaded:
      staticPatterns: /^riptide\/markup\/([\d\w]*)\.jade$/
      globals: ['DEBUG', 'vita', '_now']
    closurecompiler:
      compilationLevel: 'SIMPLE'
    typeset:
      tweaks: disable: ['ligatures']
    stylus:
      plugins: [
        'jeet'
        'axis'
      ]

  conventions:
    ignored: [
      /node_modules\/jade/
      /(templates|partials)\/.+\.jade$/
    ]

  hooks:
    onCompile: ->
      global.vita = renderVita './riptide/vita.yaml'

  npm:
    enabled: yes

  modules:
    nameCleaner: (path) ->
      path
        .replace /^riptide\//, ''
        .replace /\.coffee/, ''

  files:
    javascripts:
      joinTo: 'js/app.js'
    stylesheets:
      joinTo: 'css/app.css'

  server:
    command: "node_modules/.bin/http-server -c-1 -p #{process.env.PORT or 8080}"

# noop.pw
moment = require 'moment'
{renderVita} = require './demi'
global.vita = renderVita './riptide/vita.yaml'
global._now = moment()
global.DEBUG = '-p' not in global.process.argv

module.exports = config:
  paths:
    watched: ['riptide']

  plugins:
    autoReload:
      enabled: yes
    coffeelint:
      pattern: /^riptide\/.*\.(coffee)$/
      useCoffeelintJson: yes
    jaded:
      staticPatterns: /^riptide\/markup\/([\d\w]*)\.jade$/
      globals: ['DEBUG', 'vita', '_now']
    closurecompiler:
      compilationLevel: 'SIMPLE'
    typeset:
      tweaks: disable: ['ligatures']
    stylus:
      plugins: [
        'jeet'
        'axis'
      ]

  conventions:
    ignored: [
      /node_modules\/jade/
      /(templates|partials)\/.+\.jade$/
    ]

  hooks:
    onCompile: ->
      global.vita = renderVita './riptide/vita.yaml'

  npm:
    enabled: yes
    styles:
      'normalize.css': ['normalize.css']

  modules:
    nameCleaner: (path) ->
      path
        .replace /^riptide\//, ''
        .replace /\.coffee/, ''

  files:
    javascripts:
      joinTo: 'js/app.js'
    stylesheets:
      joinTo: 'css/app.css'

  server:
    command: "node_modules/.bin/http-server -c-1 -p #{process.env.PORT or 8080}"

Update brunch to also include normalize.css in build


window.keymap.bindKeys '*'
  'meta-:': 'command-panel:toggle'

window.keymap.bindKeys '.command-panel .editor',
  escape: 'command-panel:toggle'
  enter: 'command-panel:execute'

window.keymap.bindKeys '.editor',
  'meta-g': 'command-panel:repeat-relative-address'
  'meta-e': 'command-panel:set-selection-as-regex-address'
  'meta-f': 'command-panel:find-in-file'

window.keymap.bindKeys '*'
  'meta-:': 'command-panel:toggle'

window.keymap.bindKeys '.command-panel .editor',
  escape: 'command-panel:toggle'
  enter: 'command-panel:execute'

window.keymap.bindKeys '.editor',
  'meta-g': 'command-panel:repeat-relative-address'
  'meta-G': 'command-panel:repeat-relative-address-in-reverse'
  'meta-e': 'command-panel:set-selection-as-regex-address'
  'meta-f': 'command-panel:find-in-file'

Implement meta-G (redo search in reverse)


Buffer = require 'buffer'
fs = require 'fs'
require 'benchmark-helper'

describe "Editor", ->
  editor = null

  beforeEach ->
    window.rootViewParentSelector = '#jasmine-content'
    window.startup()
    editor = rootView.editor

  afterEach ->
    window.shutdown()

  benchmark "inserting and deleting a character in an empty file", ->
    editor.insertText('x')
    editor.backspace()

  describe "when editing a ~300 line CoffeeScript file", ->
    beforeEach ->
      editor.setBuffer new Buffer(require.resolve('fixtures/medium.coffee'))

    benchmark "inserting and deleting a character", ->
      editor.insertText('x')
      editor.backspace()

    benchmark "inserting and deleting a character that causes massive token changed", ->
      editor.insertText('"')
      editor.backspace()

Buffer = require 'buffer'
fs = require 'fs'
require 'benchmark-helper'

describe "Editor", ->
  editor = null

  beforeEach ->
    window.rootViewParentSelector = '#jasmine-content'
    window.startup()
    editor = rootView.editor

  afterEach ->
    window.shutdown()

  benchmark "inserting and deleting a character in an empty file", ->
    editor.insertText('x')
    editor.backspace()

  describe "when editing a ~300 line CoffeeScript file", ->
    beforeEach ->
      editor.setBuffer new Buffer(require.resolve('fixtures/medium.coffee'))

    describe "when the cursor is at the beginning of the file", ->
      benchmark "inserting and deleting a character at the beginning of the file", ->
        editor.insertText('x')
        editor.backspace()

      benchmark "inserting and deleting a character that causes massive re-highlighting", ->
        editor.insertText('"')
        editor.backspace()

    describe "when the cursor is at the end of the file", ->
      beforeEach ->
        editor.setCursorScreenPosition([Infinity, Infinity])

      benchmark "inserting and deleting a character", ->
        editor.insertText('"')
        editor.backspace()

Add benchmark for inserting at the end of a file


$('.rating').raty
  path: '/assets'
  score: ->
    $(this).attr('data-score')
  click: (score, _) ->
    $.ajax
      type: 'post',
      url: $(this).attr('data-uri'),
      data:
        picture:
          score: score
    $(this).find('img').unbind('click')

$('.rating').raty
  path: '/assets'
  score: ->
    $(this).attr('data-score')
  click: (score, _) ->
    self = $(this)
    url  = self.attr('data-uri')

    self.find('img').unbind('click')

    $.ajax
      type: 'post'
      url: url
      data:
        picture:
          score: score
      success: ->
        $.ajax
          type: 'get'
          url: url
          success: (data) ->
            self.raty('score', data)
            self.raty('readOnly', true)

Update score and deactivate rating properly.


_ = require 'underscore-plus'
RubyMotionAutocompleteView = require './rubymotion-autocomplete-view'

module.exports =
  autocompleteViews: []
  editorSubscription: null
  snippetPrefixes: []

  activate: (state) ->
    @editorSubscription = atom.workspaceView.eachEditorView (editor) =>
      if editor.attached and not editor.mini
        autocompleteView = new RubyMotionAutocompleteView(editor)
        autocompleteView.snippetPrefixes = @snippetPrefixes
        editor.on 'editor:will-be-removed', =>
          autocompleteView.remove() unless autocompleteView.hasParent()
          _.remove(@autocompleteViews, autocompleteView)
        @autocompleteViews.push(autocompleteView)

    atom.packages.on 'snippets:loaded', =>
      @snippetPrefixes = @collectSnippets()
      @autocompleteViews.forEach (v) => v.snippetPrefixes = @snippetPrefixes

  collectSnippets: ->
    snippets = atom.syntax.propertiesForScope([".source.rubymotion"], "snippets")
    keys = []
    for item in snippets
      keys.push _.keys(item.snippets)
    _.uniq(_.flatten(keys)).sort (word1, word2) ->
      word1.toLowerCase().localeCompare(word2.toLowerCase())

  deactivate: ->
    @editorSubscription?.off()
    @editorSubscription = null
    @autocompleteViews.forEach (autocompleteView) -> autocompleteView.remove()
    @autocompleteViews = []

  serialize: ->

_ = require 'underscore-plus'
RubyMotionAutocompleteView = require './rubymotion-autocomplete-view'
Snippet = require(atom.packages.resolvePackagePath('snippets') + '/lib/snippet')
Snippets = require(atom.packages.resolvePackagePath('snippets') + '/lib/snippets')

module.exports =
  autocompleteViews: []
  editorSubscription: null
  snippetPrefixes: []

  activate: (state) ->
    @editorSubscription = atom.workspaceView.eachEditorView (editor) =>
      if editor.attached and not editor.mini
        autocompleteView = new RubyMotionAutocompleteView(editor)
        autocompleteView.snippetPrefixes = @snippetPrefixes
        editor.on 'editor:will-be-removed', =>
          autocompleteView.remove() unless autocompleteView.hasParent()
          _.remove(@autocompleteViews, autocompleteView)
        @autocompleteViews.push(autocompleteView)

    @collectSnippets (prefixes) =>
      @snippetPrefixes = prefixes
      @autocompleteViews.forEach (v) => v.snippetPrefixes = @snippetPrefixes

  collectSnippets: (callback) ->
    path = atom.packages.getActivePackage('RubyMotion').path + '/snippets/cocoatouch'
    Snippets.loadSnippetsDirectory path, ->
      snippets = atom.syntax.propertiesForScope([".source.rubymotion"], "snippets")
      keys = []
      for item in snippets
        keys.push _.keys(item.snippets)
      keys = _.uniq(_.flatten(keys)).sort (word1, word2) ->
        word1.toLowerCase().localeCompare(word2.toLowerCase())
      callback keys

  deactivate: ->
    @editorSubscription?.off()
    @editorSubscription = null
    @autocompleteViews.forEach (autocompleteView) -> autocompleteView.remove()
    @autocompleteViews = []

  serialize: ->

Load snippets for cocoatouch after loading


describe "tabs", ->
  beforeEach ->
    # run all Blacklight.onload functions
    Blacklight.activate()

  describe "dashboard tabs", ->
    beforeEach ->
      # setup the tabs like the homepage featured and recent tabs
      setFixtures '

Featured Works

Recently Uploaded

'

    describe "tabNavigation", ->

      it "It sets the first tab to active", ->
        expect($('#homeTabs li').attr('class')).toBeUndefined()
        tabNavigation();
        expect($('#homeTabs li').attr('class')).toBe('active')

  describe "dashboard tabs", ->
    beforeEach ->
      # setup the tabs like the my listing on the dashboards
      setFixtures '

                     You are currently listing your works .  You have 1 works

                       My Works

                       My Collections

'

    describe "tabNavigation", ->

      it "It does not error", ->
        tabNavigation();

describe "tabs", ->
  beforeEach ->
    # run all Blacklight.onload functions
    Blacklight.activate()

  describe "dashboard tabs", ->
    beforeEach ->
      # setup the tabs like the homepage featured and recent tabs
      setFixtures '

Featured Works

Recently Uploaded

'

    describe "tabNavigation", ->

      it "It sets the first tab to active", ->
        expect($('#homeTabs a:first').attr('class')).toBeUndefined()
        tabNavigation();
        expect($('#homeTabs a:first').attr('class')).toBe('active')

  describe "dashboard tabs", ->
    beforeEach ->
      # setup the tabs like the my listing on the dashboards
      setFixtures '

                     You are currently listing your works .  You have 1 works

                       My Works

                       My Collections

'

    describe "tabNavigation", ->

      it "It does not error", ->
        tabNavigation();

Fix selector in tabs spec

Fix selector in tabs spec


Point = require 'point'
ChildProcess = require 'child-process'

module.exports =
class TagGenerator

  constructor: (@path, @callback) ->

  parsePrefix: (section = "") ->
    if section.indexOf('class:') is 0
      section.substring(6)
    else if section.indexOf('namespace:') is 0
      section.substring(10)
    else if section.indexOf('file:') is 0
      section.substring(5)
    else if section.indexOf('signature:') is 0
      section.substring(10)
    else
      section

  parseTagLine: (line) ->
    sections = line.split('\t')
    return null if sections.length < 4

    label = sections[0]
    line = parseInt(sections[2]) - 1
    if sections.length > 5
      if prefix = @parsePrefix(sections[4])
        label = "#{prefix}::#{label}"
      if suffix = @parsePrefix(sections[5])
        label = "#{label}#{suffix}"
    else
      if suffix = @parsePrefix(sections[4])
        label = "#{label}#{suffix}"

    tag =
      position: new Point(line, 0)
      name: label

    return tag

  generate: ->
    options =
      bufferLines: true
      stdout: (data) =>
        lines = data.split('\n')
        for line in lines
          tag = @parseTagLine(line)
          @callback(tag) if tag
    command = "#{require.resolve('ctags')} --fields=+KS -nf - #{@path}"
    ChildProcess.exec(command, options)

Point = require 'point'
ChildProcess = require 'child-process'

module.exports =
class TagGenerator

  constructor: (@path, @callback) ->

  parsePrefix: (section = "") ->
    if section.indexOf('class:') is 0
      section.substring(6)
    else if section.indexOf('namespace:') is 0
      section.substring(10)
    else if section.indexOf('file:') is 0
      section.substring(5)
    else if section.indexOf('signature:') is 0
      section.substring(10)
    else if section.indexOf('struct:') is 0
      section.substring(7)
    else
      section

  parseTagLine: (line) ->
    sections = line.split('\t')
    return null if sections.length < 4

    label = sections[0]
    line = parseInt(sections[2]) - 1
    if sections.length > 5
      if prefix = @parsePrefix(sections[4])
        label = "#{prefix}::#{label}"
      if suffix = @parsePrefix(sections[5])
        label = "#{label}#{suffix}"
    else
      if suffix = @parsePrefix(sections[4])
        label = "#{label}#{suffix}"

    tag =
      position: new Point(line, 0)
      name: label

    return tag

  generate: ->
    options =
      bufferLines: true
      stdout: (data) =>
        lines = data.split('\n')
        for line in lines
          tag = @parseTagLine(line)
          @callback(tag) if tag
    command = "#{require.resolve('ctags')} --fields=+KS -nf - #{@path}"
    ChildProcess.exec(command, options)

Trim struct: prefix in display output

Trim struct: prefix in display output


class @Calendar
  options =
    month: "short"
    day: "numeric"
    year: "numeric"

  constructor: (timestamps, starting_year, starting_month, calendar_activities_path) ->
    cal = new CalHeatMap()
    cal.init
      itemName: ["contribution"]
      data: timestamps
      start: new Date(starting_year, starting_month)
      domainLabelFormat: "%b"
      id: "cal-heatmap"
      domain: "month"
      subDomain: "day"
      range: 12
      tooltip: true
      label:
        position: "top"
      legend: [
        0
        10
        20
        30
      ]
      legendCellPadding: 3
      onClick: (date, count) ->
        formated_date = date.getFullYear() + "-" + (date.getMonth()+1) + "-" + date.getDate()
        $.ajax
          url: calendar_activities_path
          data:
            date: formated_date
          cache: false
          dataType: "html"
          success: (data) ->
            $(".user-calendar-activities").html data

class @Calendar
  options =
    month: "short"
    day: "numeric"
    year: "numeric"

  constructor: (timestamps, starting_year, starting_month, calendar_activities_path) ->
    cal = new CalHeatMap()
    cal.init
      itemName: ["contribution"]
      data: timestamps
      start: new Date(starting_year, starting_month)
      domainLabelFormat: "%b"
      id: "cal-heatmap"
      domain: "month"
      subDomain: "day"
      range: 12
      tooltip: true
      label:
        position: "top"
      legend: [
        0
        10
        20
        30
      ]
      legendCellPadding: 3
      cellSize: $('.user-calendar').width() / 80
      onClick: (date, count) ->
        formated_date = date.getFullYear() + "-" + (date.getMonth()+1) + "-" + date.getDate()
        $.ajax
          url: calendar_activities_path
          data:
            date: formated_date
          cache: false
          dataType: "html"
          success: (data) ->
            $(".user-calendar-activities").html data

Make commit calendar size depend on window size

Signed-off-by: Dmitriy Zaporozhets <be23d75b156792e5acab51b196a2deb155d35d6a@gmail.com>


Q = require('q')
mongoose = require('mongoose')

log = require("#{__dirname}/../../log")

retrieve = require("#{__dirname}/../../retrieve")

Schema = mongoose.Schema
ObjectId = Schema.Types.ObjectId

BookSchema = new Schema
  isbn:
    type: String
    index:
      unique: true
    required: true
    trim: true
  title:
    type: String
    trim: true
  author:
    type: String
    trim: true
  prices: [{
    value: Number
    date: Date
  }]
  amazon:
    url: String
    image: String

BookSchema.statics.findByIsbn = (isbn) ->
  @findOne(isbn: isbn).exec()

BookSchema.statics.fetchFromAmazon = (book) ->
  retrieve.fetchFromAmazon(book.isbn)
  .then ({title, value, author, url, image}) =>
    log.verbose "received #{book.isbn}, now updating as #{title}"
    @findOneAndUpdate {isbn: book.isbn},
      title: title
      author: author
      amazon:
        url: url
        image: image
      $push:
        prices:
          value: value
          date: Date.now()
    .exec()

module.exports = mongoose.model('Book', BookSchema)

Q = require('q')
mongoose = require('mongoose')

log = require("#{__dirname}/../../log")

retrieve = require("#{__dirname}/../../retrieve")

Schema = mongoose.Schema
ObjectId = Schema.Types.ObjectId

BookSchema = new Schema
  isbn:
    type: String
    index:
      unique: true
    required: true
    trim: true
  title:
    type: String
    trim: true
  author:
    type: String
    trim: true
  prices: [{
    value: Number
    date: Date
  }]
  amazon:
    url: String
    image: String

BookSchema.statics.findByIsbn = (isbn) ->
  @findOne(isbn: isbn).exec()

BookSchema.statics.fetchFromAmazon = (book) ->
  retrieve.fetchFromAmazon(book.isbn)
  .then ({title, value, author, url, image}) =>
    log.verbose "received #{book.isbn}, now updating as #{title}"
    @findOneAndUpdate {isbn: book.isbn},
      title: title
      author: author or ''
      amazon:
        url: url
        image: image
      $push:
        prices:
          value: value
          date: Date.now()
    .exec()

module.exports = mongoose.model('Book', BookSchema)

Fix Update Book Without Author

Fix Update Book Without Author


_ = require "underscore"
Transform = require "./transform"

class Jitter extends Transform.Model
  initialize: (attrs, options) ->
    super(attrs, options)

  defaults: ->
    return _.extend({}, super(), {
      interval: 1
    })

  compute: (x) ->
    # Apply the transform to a single value
    return(x + ((Math.random() - 0.5) * @get('interval')))

  v_compute: (xs) ->
    # Apply the tranform to a vector of values
    result = new Float64Array(xs.length)
    for x, idx in xs
      result[idx] = this.compute(x)
    return result

module.exports =
  Model: Jitter

_ = require "underscore"
Transform = require "./transform"

class Jitter extends Transform.Model
  initialize: (attrs, options) ->
    super(attrs, options)

  defaults: ->
    return _.extend {}, super(), {
      interval: [ p.Number, 1]
    }

  compute: (x) ->
    # Apply the transform to a single value
    return(x + ((Math.random() - 0.5) * @get('interval')))

  v_compute: (xs) ->
    # Apply the tranform to a vector of values
    result = new Float64Array(xs.length)
    for x, idx in xs
      result[idx] = this.compute(x)
    return result

module.exports =
  Model: Jitter

Convert to using newer property initializers in the JS for Jitter


class window.Comment extends Backbone.Model

  templateHelpers: =>
    creator: @creator

  creator: -> new User(@get('created_by'))

  can_destroy: -> @creator().get('id') == currentUser.get('id')

class window.Comment extends Backbone.Model

  templateHelpers: =>
    creator: @creator

  creator: -> new User(@get('created_by')).toJSON()

  can_destroy: -> @creator().get('id') == currentUser.get('id')

Create JSON of creator in Comment templateHelper


noflo = require "noflo"
_s = require "underscore.string"
{ CacheStorage } = require "../lib/cache_storage"

class Cache extends noflo.Component

  description: _s.clean "save incoming IPs and send the saved IPs to
  port 'out' upon any data IP from 'ready'"

  constructor: ->
    @key = null
    @cache = new CacheStorage

    @inPorts =
      in: new noflo.Port
      ready: new noflo.Port
      key: new noflo.Port
      size: new noflo.Port
    @outPorts =
      out: new noflo.Port

    @inPorts.key.on "data", (@key) =>

    @inPorts.size.on "data", (size) =>
      @cache.size = size

    @inPorts.ready.on "data", (data) =>
      @cache.flushCache @outPorts.out, @key
      @outPorts.out.disconnect()
      delete @cache[@key]

    @inPorts.ready.on "disconnect", =>
      @key = null

    @inPorts.in.on "connect", =>
      @cache.connect(@key)

    @inPorts.in.on "begingroup", (group) =>
      @cache.beginGroup(group, @key)

    @inPorts.in.on "data", (data) =>
      @cache.data(data, @key)

    @inPorts.in.on "endgroup", (group) =>
      @cache.endGroup(group, @key)

    @inPorts.in.on "disconnect", =>
      @cache.disconnect(@key)
      @key = null

exports.getComponent = -> new Cache

noflo = require "noflo"
_s = require "underscore.string"
{ CacheStorage } = require "../lib/cache_storage"

class Cache extends noflo.Component

  description: _s.clean "save incoming IPs and send the saved IPs to
  port 'out' upon any data IP from 'ready'"

  constructor: ->
    @key = null
    @cache = new CacheStorage

    @inPorts =
      in: new noflo.Port
      ready: new noflo.Port
      key: new noflo.ArrayPort
      size: new noflo.Port
    @outPorts =
      out: new noflo.Port

    @inPorts.key.on "data", (@key) =>

    @inPorts.size.on "data", (size) =>
      @cache.size = size

    @inPorts.ready.on "data", (data) =>
      @cache.flushCache @outPorts.out, @key
      @outPorts.out.disconnect()
      delete @cache[@key]

    @inPorts.ready.on "disconnect", =>
      @key = null

    @inPorts.in.on "connect", =>
      @cache.connect(@key)

    @inPorts.in.on "begingroup", (group) =>
      @cache.beginGroup(group, @key)

    @inPorts.in.on "data", (data) =>
      @cache.data(data, @key)

    @inPorts.in.on "endgroup", (group) =>
      @cache.endGroup(group, @key)

    @inPorts.in.on "disconnect", =>
      @cache.disconnect(@key)
      @key = null

exports.getComponent = -> new Cache

Use ArrayPort to allow keying for 'in' and 'ready'


define [
  'analytics/compiled/BaseData'
  'timezone'
], (BaseData, tz) ->

  ##
  # Loads the participation data for the student and course. Exposes the data
  # as the 'pageViews' and 'participations' properties once loaded.
  class ParticipationData extends BaseData
    constructor: (student) ->
      course = student.get('course')
      super "/api/v1/courses/#{course.get 'id'}/analytics/users/#{student.get 'id'}/activity"

    populate: (data) ->
      @bins = []

      # maintain one unique bin per date, order of insertion into @bins
      # unimportant
      binMap = {}
      binFor = (date) =>
        if !binMap[date]?
          binMap[date] =
            date: date
            views: 0
            participations: 0
            participation_events: []
          @bins.push binMap[date]
        binMap[date]

      # sort the page view data to the appropriate bins
      for date, views of data.page_views
        # this date is the hour for the bin
        bin = binFor(Date.parse date)
        bin.views += views

      # sort the participation date to the appropriate bins
      for event in data.participations
        event.createdAt = Date.parse event.created_at
        # bin to the hour corresponding to event.createdAt, so that all
        # participations fall in the same bin as their respective page views.
        bin = binFor(tz.parse(tz.format(event.createdAt, '%F %H:00:00')))
        bin.participation_events.push event
        bin.participations += 1

define [
  'analytics/compiled/BaseData'
  'timezone'
], (BaseData, tz) ->

  ##
  # Loads the participation data for the student and course. Exposes the data
  # as the 'pageViews' and 'participations' properties once loaded.
  class ParticipationData extends BaseData
    constructor: (student) ->
      course = student.get('course')
      super "/api/v1/courses/#{course.get 'id'}/analytics/users/#{student.get 'id'}/activity"

    populate: (data) ->
      @bins = []

      # maintain one unique bin per date, order of insertion into @bins
      # unimportant
      binMap = {}
      binFor = (date) =>
        if !binMap[date]?
          binMap[date] =
            date: date
            views: 0
            participations: 0
            participation_events: []
          @bins.push binMap[date]
        binMap[date]

      # sort the page view data to the appropriate bins
      for date, views of data.page_views
        # this date is the day for the bin
        bin = binFor(Date.parse date)
        bin.views += views

      # sort the participation date to the appropriate bins
      for event in data.participations
        event.createdAt = Date.parse event.created_at
        # bin to the day corresponding to event.createdAt, so that all
        # participations fall in the same bin as their respective page views.
        bin = binFor(new Date(event.createdAt.setHours(0,0,0,0)))
        bin.participation_events.push event
        bin.participations += 1

Fix merging of participation and page views

the binning of page views and participation from the api into dates is
fixed.

closes ADMIN-1299

note: there was a change in this file from binning by day -> hour to fix
a bug that looks like was a timezone issue. See CNVS-16464 and
https://gerrit.instructure.com/#/c/44111/.  I believe this sort of
addressed the issue at the time, but was not correct.

test plan:
  - have a course with a student with page views and participation data
    on the same day (you could run @jeremy's fake_analytics.rb script to
    generate randomish data and tweak the results to get dupes)
  - go to /courses/:couse_id/analytics/users/:user_id
  - click the toggle to switch to table view
  > expect the activity by date table to have combined the views and
    participation into the same date. There should be no duplicate
    dates in the table.

Change-Id: Iff76a43c0a7c0a1d3dd5082a6bbde18407916f32
Reviewed-on: https://gerrit.instructure.com/161479
Tested-by: Jenkins
Reviewed-by: Mysti Sadler <431d062aff1e8f25416b3a7b732fe6effdb03f9a@instructure.com>
Reviewed-by: Jeremy Stanley <b3f594e10a9edcf5413cf1190121d45078c62290@instructure.com>
Reviewed-by: Carl Kibler <7c2b40129dd885a147d5b2dda56dda016b9a8f57@instructure.com>
QA-Review: Jeremy Stanley <b3f594e10a9edcf5413cf1190121d45078c62290@instructure.com>
Product-Review: Ed Schiebel <0ca8a83847a5889cf05cb219c0ab09f6b646243b@instructure.com>


{View} = require 'space-pen'
$ = require 'jquery'
Git = require 'git'
fs = require 'fs'

module.exports =
class FileView extends View

  @content: ({file} = {}) ->
    @li class: 'file entry', =>
      @span file.getBaseName(), class: 'name', outlet: 'fileName'
      @span '', class: 'highlight'

  file: null

  initialize: ({@file, @project} = {}) ->
    extension = fs.extension(@getPath())
    if fs.isReadmePath(@getPath())
      @fileName.addClass('readme-icon')
    else if fs.isCompressedExtension(extension)
      @fileName.addClass('compressed-icon')
    else if fs.isImageExtension(extension)
      @fileName.addClass('image-icon')
    else if fs.isPdfExtension(extension)
      @fileName.addClass('pdf-icon')
    else if fs.isBinaryExtension(extension)
      @fileName.addClass('binary-icon')
    else
      @fileName.addClass('text-icon')

    if git?
      git.on 'status-changed', (path, status) =>
        @updateStatus() if path is @getPath()
      git.on 'statuses-changed', =>
        @updateStatus()

    @updateStatus()

  updateStatus: ->
    @removeClass('ignored modified new')
    return unless git?

    path = @getPath()
    if git.isPathIgnored(path)
      @addClass('ignored')
    else
      status = git.statuses[path]
      if git.isStatusModified(status)
        @addClass('modified')
      else if git.isStatusNew(status)
        @addClass('new')

  getPath: ->
    @file.path

{View} = require 'space-pen'
$ = require 'jquery'
Git = require 'git'
fs = require 'fs'

module.exports =
class FileView extends View

  @content: ({file} = {}) ->
    @li class: 'file entry', =>
      @span file.getBaseName(), class: 'name', outlet: 'fileName'
      @span '', class: 'highlight'

  file: null

  initialize: ({@file, @project} = {}) ->
    extension = fs.extension(@getPath())
    if fs.isReadmePath(@getPath())
      @fileName.addClass('readme-icon')
    else if fs.isCompressedExtension(extension)
      @fileName.addClass('compressed-icon')
    else if fs.isImageExtension(extension)
      @fileName.addClass('image-icon')
    else if fs.isPdfExtension(extension)
      @fileName.addClass('pdf-icon')
    else if fs.isBinaryExtension(extension)
      @fileName.addClass('binary-icon')
    else
      @fileName.addClass('text-icon')

    if git?
      @subscribe git, 'status-changed', (path, status) =>
        @updateStatus() if path is @getPath()
      @subscribe git, 'statuses-changed', =>
        @updateStatus()

    @updateStatus()

  updateStatus: ->
    @removeClass('ignored modified new')
    return unless git?

    path = @getPath()
    if git.isPathIgnored(path)
      @addClass('ignored')
    else
      status = git.statuses[path]
      if git.isStatusModified(status)
        @addClass('modified')
      else if git.isStatusNew(status)
        @addClass('new')

  getPath: ->
    @file.path

Use subscribe instead of on

Use subscribe instead of on


class EyeTribe
  @Tracker = require('./tracker')
  @version = require('./version')

  @loop = (config, callback)->
    if typeof config == 'function'
      [callback, config] = [config, {}]
    @loopTracker = new @Tracker config
      .loop callback

module.exports = EyeTribe

class EyeTribe
  @Tracker = require('./tracker')
  @version = require('./version')
  @Protocol = require('./protocol')
  @GazeData = require('./gazedata')
  @Point2D = require('./point2d')
  @_ = require('underscore')

  @loop = (config, callback)->
    if typeof config == 'function'
      [callback, config] = [config, {}]
    @loopTracker = new @Tracker config
      .loop callback

module.exports = EyeTribe

Add references to internal modules as member variables of class EyeTribe


module.exports.pad = (n, size) ->
    n = n.toString()
    pad = ''
    size = size - n.length
    for i in [0 .. size]
        pad += ' '
    n + pad

module.exports.flatten = (arr, ret) ->
  ret ?= []
  len = arr.length
  for i in [0 .. len]
    if Array.isArray arr[i]
      exports.flatten arr[i], ret
    else
      ret.push arr[i]
  ret

path = require 'path'

module.exports =
    pad: (n, size) ->
        n = n.toString()
        pad = ''
        size = size - n.length
        for i in [0 .. size]
            pad += ' '
        n + pad
    flatten: (arr, ret) ->
        ret ?= []
        for i in [0 ... arr.length]
            if Array.isArray arr[i]
                @flatten arr[i], ret
            else
                ret.push arr[i]
        ret
    # Discovery the project root directory or return null if undiscoverable
    workspace: () ->
        dirs = require('module')._nodeModulePaths process.cwd()
        for dir in dirs
            if path.existsSync(dir) || path.existsSync(path.normalize(dir + '/../package.json'))
                return path.normalize dir + '/..'

Fix flatten function (bug introduce by coffee migration)


noflo = require 'noflo'
fbpSpec = require 'fbp-spec'

exports.getComponent = ->
  c = new noflo.Component
  c.inPorts.add 'in',
    datatype: 'object'
    description: 'Project'
  c.inPorts.add 'runtime',
    datatype: 'object'
    required: yes
  c.outPorts.add 'context',
    datatype: 'object'
  c.outPorts.add 'error',
    datatype: 'object'

  noflo.helpers.WirePattern c,
    in: 'in'
    params: ['runtime']
    out: 'context'
    async: true
  , (project, groups, out, callback) ->
    unless project
      do callback
      return
    unless c.params.runtime.isConnected()
      do callback
      return
    unless project.specs?.length
      do callback
      return
    specs = project.specs.filter (s) -> s.language is 'yaml'
    unless specs.length
      do callback
      return

    suites = []
    for s in specs
      suites = suites.concat fbpSpec.testsuite.loadYAML s.code
    unless suites.length
      do callback
      return

    # Send out the initial suites
    out.send
      suites: suites

    runner = new fbpSpec.runner.Runner c.params.runtime
    fbpSpec.runner.runAll runner, suites, ->
      # Send updated suite state
      out.send
        suites: suites
    , (err) ->
      return callback err if err
      do callback

noflo = require 'noflo'
fbpSpec = require 'fbp-spec'

exports.getComponent = ->
  c = new noflo.Component
  c.inPorts.add 'in',
    datatype: 'object'
    description: 'Project'
  c.inPorts.add 'runtime',
    datatype: 'object'
    required: yes
  c.outPorts.add 'context',
    datatype: 'object'
  c.outPorts.add 'error',
    datatype: 'object'

  noflo.helpers.WirePattern c,
    in: 'in'
    params: ['runtime']
    out: 'context'
    async: true
  , (project, groups, out, callback) ->
    unless project
      do callback
      return
    unless c.params.runtime.isConnected()
      do callback
      return
    unless project.specs?.length
      do callback
      return
    specs = project.specs.filter (s) -> s.language is 'yaml'
    unless specs.length
      do callback
      return

    suites = []
    for s in specs
      continue unless s.code.length
      try
        suite = fbpSpec.testsuite.loadYAML s.code
      catch e
        console.log e
        continue
      suites = suites.concat suite
    unless suites.length
      do callback
      return

    # Send out the initial suites
    out.send
      suites: suites

    runner = new fbpSpec.runner.Runner c.params.runtime
    fbpSpec.runner.runAll runner, suites, ->
      # Send updated suite state
      out.send
        suites: suites
    , (err) ->
      return callback err if err
      do callback

Deal with YAML parse errors

Deal with YAML parse errors


$(document).ready ->
  return unless $('#sc_memo_edit_form').length > 0

  $('.js-edit-memo').on('click', (ev) =>
    ev.preventDefault()
    originalValue = $('#memo-edit-row').data('original-value')
    $('#sc_memo_edit_form').find("[name='store_credit[memo]']").val(originalValue)
    $('#memo-display-row').addClass('hidden')
    $('#memo-edit-row').removeClass('hidden')
  )

  $('.js-save-memo').on('click', (ev) ->
    ev.preventDefault()
    $.ajax(
      $('#sc_memo_edit_form').attr('url'), {
        data: $('#sc_memo_edit_form').serialize(),
        dataType: 'json',
        method: 'put',
        complete: (xhr, status) =>
          if xhr.status is 200
            newValue = $('#sc_memo_edit_form').find("[name='store_credit[memo]']").val()
            $('#memo-edit-row').data('original-value', newValue)
            $('#memo-display-row').find('.js-memo-text').text(newValue)
            hideEditMemoForm()
            show_flash('success', xhr.responseJSON.message)
          else if xhr.status is 400
            show_flash('error', xhr.responseJSON.message)
      }
    )
  )

  $('.js-cancel-memo').on('click', (ev) =>
    ev.preventDefault()
    hideEditMemoForm()
  )

  hideEditMemoForm = ->
    $('#memo-edit-row').addClass('hidden')
    $('#memo-display-row').removeClass('hidden')

$(document).ready ->
  return unless $('#sc_memo_edit_form').length > 0

  $('.js-edit-memo').on('click', (ev) =>
    ev.preventDefault()
    originalValue = $('#memo-edit-row').data('original-value')
    $('#sc_memo_edit_form').find("[name='store_credit[memo]']").val(originalValue)
    $('#memo-display-row').addClass('hidden')
    $('#memo-edit-row').removeClass('hidden')
  )

  $('.js-save-memo').on('click', (ev) ->
    ev.preventDefault()
    Spree.ajax(
      $('#sc_memo_edit_form').attr('url'), {
        data: $('#sc_memo_edit_form').serialize(),
        dataType: 'json',
        method: 'put',
        success: ( (response) =>
          newValue = $('#sc_memo_edit_form').find("[name='store_credit[memo]']").val()
          $('#memo-edit-row').data('original-value', newValue)
          $('#memo-display-row').find('.js-memo-text').text(newValue)
          hideEditMemoForm()
          show_flash('success', response.message)
        ),
        error: ( (xhr, status) =>
          show_flash('error', xhr.responseJSON.message)
        )
      }
    )
  )

  $('.js-cancel-memo').on('click', (ev) =>
    ev.preventDefault()
    hideEditMemoForm()
  )

  hideEditMemoForm = ->
    $('#memo-edit-row').addClass('hidden')
    $('#memo-display-row').removeClass('hidden')

Switch to Spree.ajax & success / error callbacks


TentStatus.Routers.posts = new class PostsRouter extends TentStatus.Router
  routerKey: 'posts'

  routes:
    ""                 : "root"
    "profile"          : "myProfile"
    "posts"            : "index"
    "posts/:entity/:post_id" : "conversation"
    "posts/:post_id" : "conversation"

  index: =>
    unless TentStatus.config.current_entity.hostname == TentStatus.config.domain_entity.hostname
      @profile(encodeURIComponent(TentStatus.domain_entity))
      return
    @view = new TentStatus.Views.Posts
    @setCurrentAction 'index', =>
      @view.render()

  root: => @index(arguments...)

  conversation: (entity, post_id) =>
    unless post_id
      [post_id, entity] = [entity, TentStatus.config.domain_entity]
    else
      entity = new HTTP.URI decodeURIComponent(entity)
    @view = new TentStatus.Views.Conversation entity: entity, post_id: post_id

  myProfile: =>
    @profile(TentStatus.current_entity)

  profile: (entity) =>
    @view = new TentStatus.Views.Profile entity: entity

TentStatus.Routers.posts = new class PostsRouter extends TentStatus.Router
  routerKey: 'posts'

  routes:
    ""                 : "root"
    "profile"          : "myProfile"
    "posts"            : "index"
    "posts/:entity/:post_id" : "conversation"
    "posts/:post_id" : "conversation"

  index: =>
    if !TentStatus.authenticated or (TentStatus.config.current_entity.hostname != TentStatus.config.domain_entity.hostname)
      @profile(encodeURIComponent(TentStatus.domain_entity))
      return
    @view = new TentStatus.Views.Posts
    @setCurrentAction 'index', =>
      @view.render()

  root: => @index(arguments...)

  conversation: (entity, post_id) =>
    unless post_id
      [post_id, entity] = [entity, TentStatus.config.domain_entity]
    else
      entity = new HTTP.URI decodeURIComponent(entity)
    @view = new TentStatus.Views.Conversation entity: entity, post_id: post_id

  myProfile: =>
    @profile(TentStatus.current_entity)

  profile: (entity) =>
    @view = new TentStatus.Views.Profile entity: entity

Fix home page when not logged in

Fix home page when not logged in


{RootView} = require 'atom'
shell = require 'shell'

describe "link package", ->
  [editor] = []

  beforeEach ->
    atom.activatePackage('javascript-tmbundle', sync: true)
    atom.activatePackage('hyperlink-helper-tmbundle', sync: true)
    window.rootView = new RootView
    rootView.openSync('sample.js')
    atom.activatePackage('link')
    rootView.attachToDom()
    editor = rootView.getActiveView()
    editor.insertText("// http://github.com\n")

  describe "when the cursor is on a link", ->
    it "opens the link using the 'open' command", ->
      spyOn(shell, 'openExternal')
      editor.trigger('link:open')
      expect(shell.openExternal).not.toHaveBeenCalled()

      editor.setCursorBufferPosition([0,5])
      editor.trigger('link:open')

      expect(shell.openExternal).toHaveBeenCalled()
      expect(shell.openExternal.argsForCall[0][0]).toBe "http://github.com"

{RootView} = require 'atom'
shell = require 'shell'

describe "link package", ->
  [editor] = []

  beforeEach ->
    atom.activatePackage('language-javascript', sync: true)
    atom.activatePackage('language-hyperlink', sync: true)
    window.rootView = new RootView
    rootView.openSync('sample.js')
    atom.activatePackage('link')
    rootView.attachToDom()
    editor = rootView.getActiveView()
    editor.insertText("// http://github.com\n")

  describe "when the cursor is on a link", ->
    it "opens the link using the 'open' command", ->
      spyOn(shell, 'openExternal')
      editor.trigger('link:open')
      expect(shell.openExternal).not.toHaveBeenCalled()

      editor.setCursorBufferPosition([0,5])
      editor.trigger('link:open')

      expect(shell.openExternal).toHaveBeenCalled()
      expect(shell.openExternal.argsForCall[0][0]).toBe "http://github.com"

Update packages for new naming scheme

Update packages for new naming scheme


game.ToastEntity = me.ObjectEntity.extend(

  init: (x, y, settings) ->

    settings.image = 'toast'
    settings.spritewidth = '12'
    settings.spriteheight = '12'

    @parent x, y, settings

    @maxVelocity = 5
    @gravity = 0

    targetVector = new me.Vector2d(settings.target.x - x, settings.target.y - y)
    targetVector.normalize()
    targetVector.scale(new me.Vector2d(@maxVelocity, @maxVelocity))
    @setVelocity(targetVector.x, targetVector.y)

  update: ->
    @vel.x += @accel.x * me.timer.tick
    @vel.y += @accel.y * me.timer.tick
    @computeVelocity(@vel)

    @updateMovement()

)

game.ToastEntity = me.ObjectEntity.extend(

  init: (x, y, settings) ->

    settings.image = 'toast'
    settings.spritewidth = '12'
    settings.spriteheight = '12'

    @parent x, y, settings

    @maxVelocity = 5
    @gravity = 0

    targetVector = new me.Vector2d(settings.target.x - x, settings.target.y - y)
    targetVector.normalize()
    targetVector.scale(new me.Vector2d(@maxVelocity, @maxVelocity))
    @setVelocity(targetVector.x, targetVector.y)

  update: ->
    @vel.x += @accel.x * me.timer.tick
    @vel.y += @accel.y * me.timer.tick
    @computeVelocity(@vel)
    res = @updateMovement()
    @checkForEnvCollision(res)

  checkForEnvCollision: (res) ->
    me.game.remove(@) if res.x != 0 || res.y != 0

)

Remove bullets when collide with env.

Remove bullets when collide with env.


define [
  "base"
], (App) ->
  App.directive "toggleSwitch", () ->
    restrict: "E"
    scope:
      description: "@"
      labelFalse: "@"
      labelTrue: "@"
      ngModel: "="
    template: """
  <fieldset class="toggle-switch">
    <legend class="sr-only">{{description}}</legend>

    <input
      type="radio"
      name="editor-mode"
      class="toggle-switch-input"
      id="toggle-switch-false-{{$id}}"
      ng-value="false"
      ng-model="ngModel"
    >
    {{labelFalse}}

    <input
      type="radio"
      class="toggle-switch-input"
      name="editor-mode"
      id="toggle-switch-true-{{$id}}"
      ng-value="true"
      ng-model="ngModel"
    >
    {{labelTrue}}

  </fieldset>
"""

define [
  "base"
], (App) ->
  App.directive "toggleSwitch", () ->
    restrict: "E"
    scope:
      description: "@"
      labelFalse: "@"
      labelTrue: "@"
      ngModel: "="
    template: """
  <fieldset class="toggle-switch">
    <legend class="sr-only">{{description}}</legend>

    <input
      type="radio"
      name="toggle-switch-{{$id}}"
      class="toggle-switch-input"
      id="toggle-switch-false-{{$id}}"
      ng-value="false"
      ng-model="ngModel"
    >
    {{labelFalse}}

    <input
      type="radio"
      class="toggle-switch-input"
      name="toggle-switch-{{$id}}"
      id="toggle-switch-true-{{$id}}"
      ng-value="true"
      ng-model="ngModel"
    >
    {{labelTrue}}

  </fieldset>
"""

Make sure each pair of radio buttons gets a unique name attribute in the toggle switch directive.


React             = require 'react'
EnrollButton      = require '../students/enroll_button'

InlineUsers = React.createClass(
  displayName: 'InlineUsers'
  render: ->
    key = @props.title + '_' + @props.role
    user_list = @props.users.map (user) ->
      if user.real_name?
        "#{user.real_name} (#{user.wiki_id}#{if user.email? then " / " + user.email else ""})"
      else
        user.wiki_id

    user_list = user_list.join(', ')
    user_list = if user_list.length > 0 then user_list else 'None'
    inline_list = {@props.title}: {user_list} if @props.users.length > 0 || @props.editable
    allowed = @props.role != 4 || (@props.current_user.role == 4 || @props.current_user.admin)
    button = <EnrollButton {...@props} users={@props.users} role={@props.role} key={key} inline=true allowed={allowed} show={@props.editable && allowed} />

{inline_list}{button}

)

module.exports = InlineUsers

React             = require 'react'
EnrollButton      = require '../students/enroll_button'

InlineUsers = React.createClass(
  displayName: 'InlineUsers'
  render: ->
    key = @props.title + '_' + @props.role
    user_list = @props.users.map (user) ->
      link = "https://en.wikipedia.org/wiki/User:#{user.wiki_id}"
      if user.real_name?
        displayName = "#{user.real_name} (#{user.wiki_id}#{if user.email? then " / " + user.email else ""})"
      else
        displayName = user.wiki_id
      {displayName}

    # This is feels really hacky, but it works.
    last_user_index = user_list.length - 1
    inline_user_list = ([user, ', '] for user, index in user_list when index != last_user_index)
    inline_user_list.push user_list[last_user_index]

    inline_user_list = if user_list.length > 0 then inline_user_list else 'None'
    if @props.users.length > 0 || @props.editable
      inline_list = {@props.title}: {inline_user_list}
    allowed = @props.role != 4 || (@props.current_user.role == 4 || @props.current_user.admin)
    button = <EnrollButton {...@props} users={@props.users} role={@props.role} key={key} inline=true allowed={allowed} show={@props.editable && allowed} />

{inline_list}{button}

)

module.exports = InlineUsers

Make each username in InlineUsers link to the userpage


assert = require('chai').assert
_ = require('underscore')
sinon = require('sinon')
Indicator = require('../../models/indicator')
fs = require 'fs'

suite('Indicator')

test(".find reads the definition from definitions/indicators.json
and returns an indicator with the correct attributes for that ID", (done)->
  definitions = [
    {id: 1, type: 'esri'},
    {id: 5, type: 'standard'}
  ]
  readFileStub = sinon.stub(fs, 'readFile', (filename, callback) ->
    callback(null, JSON.stringify(definitions))
  )

  Indicator.find(5).then((indicator) ->
    try
      assert.isTrue readFileStub.calledWith('./definitions/indicators.json'),
        "Expected find to read the definitions file"

      assert.property indicator, 'type',
        "Expected the type property from the JSON to be populated on indicator model"

      assert.strictEqual indicator.type, 'standard',
        "Expected the type property to be populated correctly from the definition"

      done()
    catch err
      done(err)
    finally
      readFileStub.restore()
  ).fail(done)
)

test(".query loads and formats the data based on its source", ->
  indicator = new Indicator(
    source: "gdoc"
  )

  gotData = {some: 'data'}
  getDataStub = sinon.stub(indicator, 'getDataFrom', ->
    Q.fcall(-> gotData)
  )

  formatDataFrom = sinon.stub(indicator, 'formatDataFrom', ->
    Q.fcall(-> {})
  )

  assert.isTrue(
    getDataStub.calledWith(indicator.source),
    "Expected getDataFrom to be called with the indicator source"
  )

  assert.isTrue(
    formatDataFrom.calledWith(indicator.source, gotData),
    "Expected formatDataFrom to be called with the indicator source and fetched data"
  )
)

Add tests for new get/format data methods


resourceRequest = require './resource-request.coffee'
virtual = require './virtual-resource.coffee'

resources = {}

# TODO : Implement cache invalidation on a periodic basis here
exports.combine = ({resources, req, separator, contentType, prefix}) ->
  id = resources.length + (resources.href for resource in resources).join ':'

  if not resource = resources[id]
    # Create a new resource and load async
    resource = resources[id] = {
      id
      content: {href: resource.href, complete: false} for resource in resources
      deferred: []
    }

    checkComplete = ->
      # Determine if there are any incomplete operations
      for content in resource.content
        if not content.complete
          return false

      resource.content = (content.data for content in resource.content).join ''

      for deferred in resource.deferred
        deferred resource.content

    for content in resource.content
      do (content) ->
        chunks = []
        resourceRequest.get(
          content.href,
          req,
          (err, res) ->
            if err
              console.log "Unable to load combined resource #{content.href}", err
              content.data = ''
              content.complete = true
            else
              res.on 'data', (chunk) ->
                chunks.push chunk
              res.on 'end', ->
                content.data = chunks.join('') + (separator ? '')
                content.complete = true
                checkComplete()
        )

    resource.href = virtual.create
      content: (callback) ->
        resource.deferred.push callback
        checkComplete()
      contentType: contentType
      prefix: prefix

  # TODO Figure out a better way to map to the virtual middleware
  '/virtual/' + resource.href

resourceRequest = require './resource-request.coffee'
virtual = require './virtual-resource.coffee'

resources = {}

# TODO : Implement cache invalidation on a periodic basis here
exports.combine = ({resources, req, separator, contentType, prefix}) ->
  id = resources.length + (resource.href for resource in resources).join ':'

  if not resource = resources[id]
    # Create a new resource and load async
    resource = resources[id] = {
      id
      content: {href: resource.href, complete: false} for resource in resources
      deferred: []
    }

    checkComplete = ->
      # Determine if there are any incomplete operations
      for content in resource.content
        if not content.complete
          return false

      resource.content = (content.data for content in resource.content).join ''

      for deferred in resource.deferred
        deferred resource.content

    for content in resource.content
      do (content) ->
        chunks = []
        resourceRequest.get(
          content.href,
          req,
          (err, res) ->
            if err
              console.log "Unable to load combined resource #{content.href}", err
              content.data = ''
              content.complete = true
            else
              res.on 'data', (chunk) ->
                chunks.push chunk
              res.on 'end', ->
                content.data = chunks.join('') + (separator ? '')
                content.complete = true
                checkComplete()
        )

    resource.href = virtual.create
      content: (callback) ->
        resource.deferred.push callback
        checkComplete()
      contentType: contentType
      prefix: prefix

  # TODO Figure out a better way to map to the virtual middleware
  '/virtual/' + resource.href

Fix id generation within the resource combiner


# This script teaches Hubot to answer questions like "Who owns Collections Publisher?"
# and "Who owns publishing-api?"
module.exports = (robot) ->
  robot.hear /who owns (.*)\?/i, (res) ->
    # Best guess of the intended application.
    application = res.match[1].replace(/\s/g, '-').toLowerCase()
    console.log("Fetching owner for #{application}")

    robot.http("https://docs.publishing.service.gov.uk/apps/#{application}.json")
      .get() (err, response, body) ->
        if response.statusCode == 200
          data = JSON.parse(body)
          res.reply "#{data.app_name} is owned by #{data.team}"

# This script teaches Hubot to answer questions like "Who owns Collections Publisher?"
# and "Who owns publishing-api?"
module.exports = (robot) ->
  robot.hear /who owns (.*)\?/i, (res) ->
    # Best guess of the intended application.
    application = res.match[1].replace(/\s/g, '-').toLowerCase()
    console.log("Fetching owner for #{application}")

    robot.http("https://docs.publishing.service.gov.uk/apps/#{application}.json")
      .get() (err, response, body) ->
        if response.statusCode == 200
          data = JSON.parse(body)
          res.reply "#{data.app_name} is owned by #{data.product_manager} (#{data.team})"

Update product ownership to refer to Product Manager

The current state of affairs is that products are owned by product owners, and products follow them when they change team.
The team information still seems useful so we leave it in.


exports.app    = app = require('../config/app').app

exports.vows   = require 'vows'
exports.assert = require 'assert'
exports.should = require 'should'

exports.zombie         = require 'zombie'
exports.zombie.browser = new exports.zombie.Browser
  debug:      false
  runScripts: false

exports.tobi                = require 'tobi'

exports.tobi.get = (path, callback) ->
  browser = exports.tobi.createBrowser(app)
  browser.get(path, { headers: exports.headers.jsonHeaders }, callback)
  return

exports.headers             = {}
exports.headers.jsonHeaders = { 'Content-Type': 'application/json' }

Repository = require('../models/repository').Repository

exports.factories                   = {}
exports.factories.Repository        = {}
exports.factories.Repository.create = (attributes, callback) ->
  repositories = new Repository attributes
  repositories.save(callback)
  return

exports.app    = app = require('../config/app').app

exports.vows   = require 'vows'
exports.assert = require 'assert'
exports.should = require 'should'

exports.zombie         = require 'zombie'
exports.zombie.browser = new exports.zombie.Browser
  debug:      false
  runScripts: false

exports.tobi                = require 'tobi'

exports.tobi.get = (path, callback) ->
  browser = exports.tobi.createBrowser(app)
  browser.get path, { headers: exports.headers.jsonHeaders }, callback
  return

exports.headers             = {}
exports.headers.jsonHeaders = { 'Content-Type': 'application/json' }

Repository = require('../models/repository').Repository

exports.factories                   = {}
exports.factories.Repository        = {}
exports.factories.Repository.create = (attributes, callback) ->
  repositories = new Repository attributes
  repositories.save(callback)
  return

Make syntax more coffee like.

Make syntax more coffee like.


KeybindingResolverView = require '../lib/keybinding-resolver-view'
{$, RootView} = require 'atom'

describe "KeybindingResolverView", ->
  beforeEach ->
    atom.rootView = new RootView
    atom.packages.activatePackage('keybinding-resolver', immediate: true)

  describe "when the keybinding-resolver:toggle event is triggered", ->
    it "attaches and then detaches the view", ->
      expect(atom.rootView.find('.keybinding-resolver')).not.toExist()
      atom.rootView.trigger 'keybinding-resolver:toggle'
      expect(atom.rootView.find('.keybinding-resolver')).toExist()
      atom.rootView.trigger 'keybinding-resolver:toggle'
      expect(atom.rootView.find('.keybinding-resolver')).not.toExist()

  describe "when a keydown event occurs", ->
    it "displays all commands for the event", ->
      atom.keymap.bindKeys 'name', 'body', 'x': 'match-1'
      atom.keymap.bindKeys 'name', 'body', 'x': 'match-2'
      atom.keymap.bindKeys 'name', '.never-again', 'x': 'unmatch-2'

      atom.rootView.trigger 'keybinding-resolver:toggle'
      $(document).trigger keydownEvent('x', target: rootView)
      expect(atom.rootView.find('.keybinding-resolver .matched')).toHaveLength 2
      expect(atom.rootView.find('.keybinding-resolver .unmatched')).toHaveLength 1

KeybindingResolverView = require '../lib/keybinding-resolver-view'
{$, RootView} = require 'atom'

describe "KeybindingResolverView", ->
  beforeEach ->
    atom.rootView = new RootView
    atom.packages.activatePackage('keybinding-resolver', immediate: true)

  describe "when the keybinding-resolver:toggle event is triggered", ->
    it "attaches and then detaches the view", ->
      expect(atom.rootView.find('.keybinding-resolver')).not.toExist()
      atom.rootView.trigger 'keybinding-resolver:toggle'
      expect(atom.rootView.find('.keybinding-resolver')).toExist()
      atom.rootView.trigger 'keybinding-resolver:toggle'
      expect(atom.rootView.find('.keybinding-resolver')).not.toExist()

  describe "when a keydown event occurs", ->
    it "displays all commands for the event", ->
      atom.keymap.bindKeys 'name', 'body', 'x': 'match-1'
      atom.keymap.bindKeys 'name', 'body', 'x': 'match-2'
      atom.keymap.bindKeys 'name', '.never-again', 'x': 'unmatch-2'

      atom.rootView.trigger 'keybinding-resolver:toggle'
      $(document).trigger keydownEvent('x', target: atom.rootView)
      expect(atom.rootView.find('.keybinding-resolver .matched')).toHaveLength 2
      expect(atom.rootView.find('.keybinding-resolver .unmatched')).toHaveLength 1

Use atom.rootView as target value

Use atom.rootView as target value


{View} = require './space-pen-extensions'
Pane = null

### Internal ###
module.exports =
class PaneAxis extends View
  initialize: (@model) ->
    @onChildAdded(child) for child in @model.children
    @subscribe @model.children, 'changed', @onChildrenChanged

  viewForModel: (model) ->
    viewClass = model.getViewClass()
    model._view ?= new viewClass(model)

  addChild: (child, index) ->
    @model.addChild(child.model, index)

  removeChild: (child) ->
    @model.removeChild(child.model)

  onChildrenChanged:  ({index, removedValues, insertedValues}) =>
    focusedElement = document.activeElement if @hasFocus()
    @onChildRemoved(child, index) for child in removedValues
    @onChildAdded(child, index + i) for child, i in insertedValues
    focusedElement?.focus() if document.activeElement is document.body

  onChildAdded: (child, index) =>
    view = @viewForModel(child)
    @insertAt(index, view)

  onChildRemoved: (child) =>
    view = @viewForModel(child)
    view.detach()
    Pane ?= require './pane'

    if view instanceof Pane and view.model.isDestroyed()
      @getContainer()?.trigger 'pane:removed', [view]

  getContainer: ->
    @closest('.panes').view()

  getActivePaneItem: ->
    @getActivePane()?.activeItem

  getActivePane: ->
    @find('.pane.active').view() ? @find('.pane:first').view()

  insertChildBefore: (currentChild, newChild) ->
    @model.insertChildBefore(currentChild, newChild)

  insertChildAfter: (currentChild, newChild) ->
    @model.insertChildAfter(currentChild, newChild)

{View} = require './space-pen-extensions'
Pane = null

### Internal ###
module.exports =
class PaneAxis extends View
  initialize: (@model) ->
    @onChildAdded(child) for child in @model.children
    @subscribe @model.children, 'changed', @onChildrenChanged

  viewForModel: (model) ->
    viewClass = model.getViewClass()
    model._view ?= new viewClass(model)

  onChildrenChanged:  ({index, removedValues, insertedValues}) =>
    focusedElement = document.activeElement if @hasFocus()
    @onChildRemoved(child, index) for child in removedValues
    @onChildAdded(child, index + i) for child, i in insertedValues
    focusedElement?.focus() if document.activeElement is document.body

  onChildAdded: (child, index) =>
    view = @viewForModel(child)
    @insertAt(index, view)

  onChildRemoved: (child) =>
    view = @viewForModel(child)
    view.detach()
    Pane ?= require './pane'

    if view instanceof Pane and view.model.isDestroyed()
      @getContainer()?.trigger 'pane:removed', [view]

  getContainer: ->
    @closest('.panes').view()

Remove unused methods from PaneAxis

Remove unused methods from PaneAxis


# The global namespace, the `Batman` function will also create also create a new
# instance of Batman.Object and mixin all arguments to it.
Batman = (mixins...) ->
  new Batman.Object(mixins...)

Batman.version = '0.14.1'

Batman.config =
  pathToApp: '/'
  usePushState: true

  pathToHTML: 'html'
  fetchRemoteHTML: true
  cacheViews: false

  minificationErrors: true
  protectFromCSRF: false

(Batman.container = do -> this).Batman = Batman  # I am so, so sorry.

# Support AMD loaders
if typeof define is 'function'
  define 'batman', [], -> Batman

Batman.exportHelpers = (onto) ->
  for k in ['mixin', 'extend', 'unmixin', 'redirect', 'typeOf', 'redirect', 'setImmediate', 'clearImmediate']
    onto["$#{k}"] = Batman[k]
  onto

Batman.exportGlobals = -> Batman.exportHelpers(Batman.container)

# The global namespace, the `Batman` function will also create a new
# instance of Batman.Object and mixin all arguments to it.
Batman = (mixins...) ->
  new Batman.Object(mixins...)

Batman.version = '0.14.1'

Batman.config =
  pathToApp: '/'
  usePushState: true

  pathToHTML: 'html'
  fetchRemoteHTML: true
  cacheViews: false

  minificationErrors: true
  protectFromCSRF: false

(Batman.container = do -> this).Batman = Batman  # I am so, so sorry.

# Support AMD loaders
if typeof define is 'function'
  define 'batman', [], -> Batman

Batman.exportHelpers = (onto) ->
  for k in ['mixin', 'extend', 'unmixin', 'redirect', 'typeOf', 'redirect', 'setImmediate', 'clearImmediate']
    onto["$#{k}"] = Batman[k]
  onto

Batman.exportGlobals = -> Batman.exportHelpers(Batman.container)

Fix a typo the file description

Fix a typo the file description


React = require 'react'

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

  getInitialState: ->
    subject_sets: []

  componentWillMount: ->
    @props.workflow?.get 'subject_sets', page_size: 40
      .then (subject_sets) =>
        subject_sets.sort (a, b) ->
          return 1 if a.metadata.BOROUGH > b.metadata.BOROUGH
          return -1 if a.metadata.BOROUGH  b.metadata['File prefix']
          return -1 if a.metadata['File prefix']

Choose a report to work on

        {
{subject_set.display_name}
{subject_set.metadata.BOROUGH.replace(' (London, England)', '')}
{subject_set.metadata.Date}
 for subject_set in @state.subject_sets}

  update: (e) ->
    subject_set_id = e.currentTarget.href.split('/').pop()
    @props.onChange subject_set_id

React = require 'react'

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

  getInitialState: ->
    subject_sets: []

  componentWillMount: ->
    @props.workflow?.get 'subject_sets', page_size: 40
      .then (subject_sets) =>
        subject_sets.sort (a, b) ->
          return 1 if a.metadata.BOROUGH > b.metadata.BOROUGH
          return -1 if a.metadata.BOROUGH  b.metadata['File prefix']
          return -1 if a.metadata['File prefix']

Choose a report to work on

        {
{subject_set.metadata.BOROUGH.replace(' (London, England)', '')}
{subject_set.metadata.Date}
 for subject_set in @state.subject_sets}

  update: (e) ->
    subject_set_id = e.currentTarget.href.split('/').pop()
    @props.onChange subject_set_id

Remove the report ID from the Reports page


kd = require 'kd'
KDCustomHTMLView = kd.CustomHTMLView
KodingSwitch = require 'app/commonviews/kodingswitch'
CustomLinkView = require 'app/customlinkview'
module.exports = class IDEChatHeadWatchItemView extends KDCustomHTMLView

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

    options.partial = 'Watch'

    super options, data

    { isWatching, nickname } = @getOptions()

    @addSubView @toggle = new KodingSwitch
      cssClass     : 'tiny'
      defaultValue : isWatching
      callback     : (state) =>
        @getDelegate().setWatchState state, nickname

    @addSubView @info = new CustomLinkView
      title        : ''
      cssClass     : 'info'
      href         : 'http://learn.koding.com/guides/collaboration/#what-does-quot-watch-quot-mode-mean-'
      target       : '_blank'

kd = require 'kd'
KDCustomHTMLView = kd.CustomHTMLView
KodingSwitch = require 'app/commonviews/kodingswitch'
CustomLinkView = require 'app/customlinkview'
module.exports = class IDEChatHeadWatchItemView extends KDCustomHTMLView

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

    options.partial ?= 'Watch'

    super options, data

    { isWatching, nickname } = @getOptions()

    @addSubView @toggle = new KodingSwitch
      cssClass     : 'tiny'
      defaultValue : isWatching
      callback     : (state) =>
        @getDelegate().setWatchState state, nickname

    @addSubView @info = new CustomLinkView
      title        : ''
      cssClass     : 'info'
      href         : 'http://learn.koding.com/guides/collaboration/#what-does-quot-watch-quot-mode-mean-'
      target       : '_blank'

Set partial option as default value

Set partial option as default value


{DOMParser} = require 'xmldom'
xpath = require 'xpath'
request = require 'request'

module.exports =
  getTransactions: (cik, type, callback) ->
    baseUrl = process.env.SEC_EDGAR_URL ? 'http://www.sec.gov/cgi-bin/browse-edgar'
    url = "#{baseUrl}?action=getcompany&output=atom&start=0&count=1000&CIK=#{cik}&type=#{type}"

    request url, (error, response, body) ->
      if error?
        callback(error)
      else
        dom = new DOMParser().parseFromString(body)
        cik = parseInt(xpath.select('/feed/company-info/cik/text()', dom))
        transactions = []
        for transaction in xpath.select('//entry/content', dom)
          filingDate = xpath.select('filing-date/text()', transaction).toString()
          date = new Date("#{filingDate} 14:30:00 GMT")
          accessionNumber = xpath.select('accession-nunber/text()', transaction).toString()
          id = accessionNumber.replace(/-/g, '')
          type = xpath.select('filing-type/text()', transaction).toString()
          transactions.push({date, cik, id, type})
        callback(null, transactions)

{DOMParser} = require 'xmldom'
xpath = require 'xpath'
request = require 'request'

module.exports =
  getTransactions: (cik, type, callback) ->
    baseUrl = process.env.SEC_EDGAR_URL ? 'http://www.sec.gov/cgi-bin/browse-edgar'
    url = "#{baseUrl}?action=getcompany&output=atom&start=0&count=1000&CIK=#{cik}&type=#{type}"
    url = "#{url}&owner=only"if parseInt(type) is 4

    request url, (error, response, body) ->
      if error?
        callback(error)
      else
        dom = new DOMParser().parseFromString(body)
        cik = parseInt(xpath.select('/feed/company-info/cik/text()', dom))
        transactions = []
        for transaction in xpath.select('//entry/content', dom)
          filingDate = xpath.select('filing-date/text()', transaction).toString()
          date = new Date("#{filingDate} 14:30:00 GMT")
          accessionNumber = xpath.select('accession-nunber/text()', transaction).toString()
          id = accessionNumber.replace(/-/g, '')
          type = xpath.select('filing-type/text()', transaction).toString()
          transactions.push({date, cik, id, type})
        callback(null, transactions)

Set owner=only on form 4 requests

Set owner=only on form 4 requests


noflo = require "noflo"

class Output extends noflo.Component

    description: "This component receives input on a single inport, and sends the data items directly to console.log"

    constructor: ->
        @inPorts =
            in: new noflo.ArrayPort()

        @outPorts = {}

        @inPorts.in.on "data", (data) ->
            console.log data

exports.getComponent = ->
    new Output()

noflo = require "noflo"
util = require "util"

class Output extends noflo.Component

    description: "This component receives input on a single inport, and sends the data items directly to console.log"

    constructor: ->
        @options =
            showHidden: false
            depth: 2
            colors: false

        @inPorts =
            in: new noflo.ArrayPort()
            options: new noflo.Port()

        @outPorts = {}

        @inPorts.in.on "data", (data) =>
            @log data

        @inPorts.options.on "data", (data) =>
            @setOptions data

    setOptions: (options) ->
        throw "Options is not an object" unless typeof options is "object"
        for own key, value of options
            @options[key] = value

    log: (data) ->
        console.log util.inspect data,
            @options.showHidden, @options.depth, @options.colors

exports.getComponent = ->
    new Output()

Add ability to set output display options.


# See https://atom.io/docs/latest/creating-a-package#menus for more details
'context-menu':
  '.conflicted':
    'Resolve: Current': 'merge-conflicts:accept-current'
    'Resolve: Ours': 'merge-conflicts:accept-ours'
    'Resolve: Theirs': 'merge-conflicts:accept-theirs'
    'Resolve: Ours Then Theirs': 'merge-conflicts:ours-then-theirs'
    'Resolve: Theirs Then Ours': 'merge-conflicts:theirs-then-ours'
  '.merge-conflicts .navigate':
    'Resolve: Entire File Ours': 'merge-conflicts:entire-file-ours'
    'Resolve: Entire File Theirs': 'merge-conflicts:entire-file-theirs'

'menu': [
  {
    'label': 'Packages'
    'submenu': [
      'label': 'Merge Conflicts'
      'submenu': [
        { 'label': 'Detect', 'command': 'merge-conflicts:detect' }
      ]
    ]
  }
]

# See https://atom.io/docs/latest/creating-a-package#menus for more details
'context-menu':
  'atom-text-editor': [{
    label: 'Resolve Conflict',
    submenu: [
      { label: 'Current', command: 'merge-conflicts:accept-current' }
      { label: 'Ours', command: 'merge-conflicts:accept-ours' }
      { label: 'Theirs', command: 'merge-conflicts:accept-theirs' }
      { label: 'Ours Then Theirs', command: 'merge-conflicts:ours-then-theirs' }
      { label: 'Theirs Then Ours', command: 'merge-conflicts:theirs-then-ours' }
    ]
  }]
  '.merge-conflicts .navigate': [
    { label: 'Resolve Entire File Ours', command: 'merge-conflicts:entire-file-ours' }
    { label: 'Resolve Entire File Theirs', command: 'merge-conflicts:entire-file-theirs' }
  ]

'menu': [{
  label: 'Packages'
  submenu: [
    label: 'Merge Conflicts',
    submenu: [{ label: 'Detect', command: 'merge-conflicts:detect' }]
  ]
}]

Rework the context menu entries.

Rework the context menu entries.


Steam.FrameListView = (_) ->
  _items = do nodes$

  activate = (item) ->
    for other in _items()
      if other is item
        other.isActive yes
      else
        other.isActive no

    _.displayFrame item.data

    return

  createItem = (frame) ->
    #TODO replace with type checking
    console.assert isArray frame.column_names
    self =
      data: frame
      title: frame.key
      caption: describeCount frame.column_names.length, 'column'
      cutline: join frame.column_names, ', '
      display: -> activate self
      isActive: node$ no

  displayFrames = (frames) ->
    _items items = map frames, createItem
    activate head items unless isEmpty items

  loadFrames = (opts) ->
    console.assert isDefined opts
    switch opts.type
      when 'all'
        _.requestFrames (error, data) ->
          if error
            #TODO handle errors
          else
            displayFrames data.frames
      when 'compatibleWithModel'
        _.requestModelAndCompatibleFrames opts.modelKey, (error, data) ->
          if error
            #TODO handle errors
          else
            displayFrames (head data.models).compatible_frames
    return

  link$ _.loadFrames, loadFrames

  items: _items
  template: 'frame-list-view'

Steam.FrameListView = (_) ->
  _predicate = node$ type: 'all'
  _items = do nodes$
  _hasItems = lift$ _items, (items) -> items.length > 0

  _canClearPredicate = lift$ _predicate, (predicate) -> predicate.type isnt 'all'
  _predicateCaption = lift$ _predicate, (predicate) ->
    switch predicate.type
      when 'all'
        'Showing\nall frames'
      when 'compatibleWithModel'
        "Showing frames compatible with\n#{predicate.modelKey}"
      else
        ''

  activateItem = (item) ->
    for other in _items()
      if other is item
        other.isActive yes
      else
        other.isActive no

    _.displayFrame item.data

    return

  createItem = (frame) ->
    #TODO replace with type checking
    console.assert isArray frame.column_names
    self =
      data: frame
      title: frame.key
      caption: describeCount frame.column_names.length, 'column'
      cutline: join frame.column_names, ', '
      display: -> activateItem self
      isActive: node$ no

  displayFrames = (frames) ->
    _items items = map frames, createItem
    if isEmpty items
      _.displayEmpty()
    else
      activateItem head items

  apply$ _predicate, (predicate) ->
    console.assert isDefined predicate
    switch predicate.type
      when 'all'
        _.requestFrames (error, data) ->
          if error
            #TODO handle errors
          else
            displayFrames data.frames

      when 'compatibleWithModel'
        _.requestModelAndCompatibleFrames predicate.modelKey, (error, data) ->
          if error
            #TODO handle errors
          else
            displayFrames (head data.models).compatible_frames
    return

  clearPredicate = -> _predicate type: 'all'

  link$ _.loadFrames, _predicate

  items: _items
  predicateCaption: _predicateCaption
  clearPredicate: clearPredicate
  canClearPredicate: _canClearPredicate
  hasItems: _hasItems
  template: 'frame-list-view'

Add filter widget to frames list. PP-8

Add filter widget to frames list.
PP-8


if Meteor.isServer
  CuratorSources = require './curatorSources.coffee'

  PromedPosts = null
  try
    # spaDb = new MongoInternals.RemoteCollectionDriver(process.env.SPA_MONGO_URL)
    # PromedPosts = new Meteor.Collection("posts", { _driver: spaDb })
    PromedPosts = new Meteor.Collection("posts")
  catch e
    console.warn 'Unable to connect to remote SPA mongodb.'

  module.exports = PromedPosts

if Meteor.isServer
  CuratorSources = require './curatorSources.coffee'

  PromedPosts = null
  try
    spaDb = new MongoInternals.RemoteCollectionDriver(process.env.SPA_MONGO_URL)
    PromedPosts = new Meteor.Collection("posts", { _driver: spaDb })
  catch e
    console.warn 'Unable to connect to remote SPA mongodb.'

  module.exports = PromedPosts

Use SPA_MONGO_URL env. variable for remote mongodb.


position_footer = ->
  mFoo = $('#footer')
  if $(document.body).height() + mFoo.outerHeight() < $(window).height() and mFoo.css('position') == 'fixed' or $(document.body).height() < $(window).height() and mFoo.css('position') != 'fixed'
    mFoo.css
      position: 'fixed'
      bottom: '0'
  else
    mFoo.css position: 'relative'
  return

$(document).on 'ready page:load page:restore', ->
  $(window).scroll position_footer
  $(window).resize position_footer

  sleep 30
  $(window).resize()
  return

position_footer = ->
  mFoo = $('#footer')
  if $(document.body).height() + mFoo.outerHeight() < $(window).height() and mFoo.css('position') == 'fixed' or $(document.body).height() < $(window).height() and mFoo.css('position') != 'fixed'
    mFoo.css
      position: 'fixed'
      bottom: '0'
  else
    mFoo.css position: 'relative'
  return

$(document).on 'ready page:load page:restore', ->
  position_footer
  $(window).scroll position_footer
  $(window).resize position_footer
  return

Revert changes made in sticky_footer as it's not working


class window.FactBottomView extends Backbone.Marionette.ItemView
  className: 'fact-bottom'

  template: 'facts/fact_bottom'

  events:
    "click .js-add-to-channel": "showAddToChannel"
    "click .js-start-conversation": "showStartConversation"
    "click .js-open-proxy-link" : "openProxyLink"
    "click .js-arguments-link": "openDiscussionModal"

  templateHelpers: ->
    formatted_time: ->
      if @friendly_time
        # this is relevant in a channel, a fact is then 'posted'
        # or reposted <time> ago
        "Posted #{@friendly_time} ago"
      else
        @created_by_ago

    believe_percentage: @model.opinionPercentage('believe')
    disbelieve_percentage: @model.opinionPercentage('disbelieve')

  showAddToChannel: (e) ->
    e.preventDefault()
    e.stopPropagation()

    FactlinkApp.ModalWindowContainer.show new AddToChannelModalWindowView(model: @model)

  showStartConversation: (e) ->
    e.preventDefault()
    e.stopPropagation()

    FactlinkApp.ModalWindowContainer.show new StartConversationModalWindowView(model: @model)

    mp_track "Factlink: Open share modal"

  openProxyLink: (e) ->
    mp_track "Factlink: Open proxy link",
      site_url: @model.get("fact_url")

  openDiscussionModal: (e) ->
    e.preventDefault()

    FactlinkApp.DiscussionModalOnFrontend.openDiscussion @model.clone()

class window.FactBottomView extends Backbone.Marionette.ItemView
  className: 'fact-bottom'

  template: 'facts/fact_bottom'

  events:
    "click .js-add-to-channel": "showAddToChannel"
    "click .js-start-conversation": "showStartConversation"
    "click .js-open-proxy-link" : "openProxyLink"
    "click .js-arguments-link": "openDiscussionModal"

  templateHelpers: ->
    formatted_time: ->
      if @friendly_time
        # this is relevant in a channel, a fact is then 'posted'
        # or reposted <time> ago
        "Posted #{@friendly_time} ago"
      else
        @created_by_ago

    believe_percentage: @model.opinionPercentage('believe')
    disbelieve_percentage: @model.opinionPercentage('disbelieve')

  showAddToChannel: (e) ->
    e.preventDefault()
    e.stopPropagation()

    FactlinkApp.ModalWindowContainer.show new AddToChannelModalWindowView(model: @model)

  showStartConversation: (e) ->
    e.preventDefault()
    e.stopPropagation()

    FactlinkApp.ModalWindowContainer.show new StartConversationModalWindowView(model: @model)

    mp_track "Factlink: Open share modal"

  openProxyLink: (e) ->
    mp_track "Factlink: Open proxy link",
      site_url: @model.get("fact_url")

  openDiscussionModal: (e) ->
    e.preventDefault()

    if e.metaKey or e.ctrlKey or e.altKey
      window.open @model.get('url'), "_blank"
    else
      FactlinkApp.DiscussionModalOnFrontend.openDiscussion @model.clone()

Handle function keys when clicking on a factrelation


ETahi.NewAdHocTaskOverlayView = ETahi.OverlayView.extend
  templateName: 'overlays/new_adhoc_task_overlay'
  layoutName: 'layouts/new_task_overlay_layout'

ETahi.NewAdHocTaskOverlayView = ETahi.OverlayView.extend
  templateName: 'overlays/new_ad_hoc_task_overlay'
  layoutName: 'layouts/new_task_overlay_layout'

Fix ad hoc template name

Fix ad hoc template name


if Meteor.isServer
	storeType = 'GridFS'

	if Meteor.settings?.public?.avatarStore?.type?
		storeType = Meteor.settings.public.avatarStore.type

	RocketStore = RocketFile[storeType]

	if not RocketStore?
		throw new Error "Invalid RocketStore type [#{storeType}]"

	transformWrite = undefined
	if Meteor.settings?.public?.avatarStore?.size?.height?
		height = Meteor.settings.public.avatarStore.size.height
		width = Meteor.settings.public.avatarStore.size.width
		transformWrite = (file, readStream, writeStream) ->
			RocketFile.gm(readStream, file.fileName).background('#ffffff').resize(width, height).gravity('Center').extent(width, height).stream('jpeg').pipe(writeStream)

	path = "~/uploads"

	if Meteor.settings?.public?.avatarStore?.path?
		path = Meteor.settings.public.avatarStore.path

	@RocketFileAvatarInstance = new RocketFile.GridFS
		name: 'avatars'
		absolutePath: path
		transformWrite: transformWrite

	HTTP.methods
		'/avatar/:username':
			'stream': true
			'get': (data) ->
				this.params.username
				file = RocketFileAvatarInstance.getFileWithReadStream this.params.username

				this.setContentType 'image/jpeg'
				this.addHeader 'Content-Disposition', 'inline'
				this.addHeader 'Content-Length', file.length

				file.readStream.pipe this.createWriteStream()
				return

if Meteor.isServer
	storeType = 'GridFS'

	if Meteor.settings?.public?.avatarStore?.type?
		storeType = Meteor.settings.public.avatarStore.type

	RocketStore = RocketFile[storeType]

	if not RocketStore?
		throw new Error "Invalid RocketStore type [#{storeType}]"

	console.log "Using #{storeType} for Avatar storage".green

	transformWrite = undefined
	if Meteor.settings?.public?.avatarStore?.size?.height?
		height = Meteor.settings.public.avatarStore.size.height
		width = Meteor.settings.public.avatarStore.size.width
		transformWrite = (file, readStream, writeStream) ->
			RocketFile.gm(readStream, file.fileName).background('#ffffff').resize(width, height).gravity('Center').extent(width, height).stream('jpeg').pipe(writeStream)

	path = "~/uploads"

	if Meteor.settings?.public?.avatarStore?.path?
		path = Meteor.settings.public.avatarStore.path

	@RocketFileAvatarInstance = new RocketStore
		name: 'avatars'
		absolutePath: path
		transformWrite: transformWrite

	HTTP.methods
		'/avatar/:username':
			'stream': true
			'get': (data) ->
				this.params.username
				file = RocketFileAvatarInstance.getFileWithReadStream this.params.username

				this.setContentType 'image/jpeg'
				this.addHeader 'Content-Disposition', 'inline'
				this.addHeader 'Content-Length', file.length

				file.readStream.pipe this.createWriteStream()
				return

Fix store reference and log which store is using


OptimistParser = require './optimist_parser'

commands =
  run: "Runs all tests found in /test"
  watch: "Runs all tests in /test and watches for changes"
  help: "Shows this help page"

usage_text = """
                Usage: mycha <command> [options]

                where <command> is one of:
                #{("  #{command_name} - #{command_desc}" for command_name, command_desc of commands).join("\n")}
              """

argv = require('optimist')
  .usage(usage_text)
  .check (argv) ->
    command = argv._[0]
    throw "Missing command." unless command?
    throw "\"#{command}\" is not a valid command." unless commands[command]?
    throw "" if command is 'help'
  .argv

Mycha = require __dirname + '/mycha'
mycha = new Mycha process.cwd()
if argv._[0] is 'run'
  optimist_parser = new OptimistParser argv
  mycha.run optimist_parser.options(),
            optimist_parser.files(),
            (exit_code) -> process.exit exit_code

OptimistParser = require './optimist_parser'

commands =
  run: "Runs all tests found in /test"
  watch: "Runs all tests in /test and watches for changes"
  help: "Shows this help page"

usage_text = """
                Usage: mycha <command> [options]

                where <command> is one of:
                #{("  #{command_name} - #{command_desc}" for command_name, command_desc of commands).join("\n")}
              """

argv = require('optimist')
  .usage(usage_text)
  .check (argv) ->
    command = argv._[0]
    throw "Missing command." unless command?
    throw "\"#{command}\" is not a valid command." unless commands[command]?
    throw "" if command is 'help'
  .argv

Mycha = require __dirname + '/mycha'
mycha = new Mycha process.cwd()
optimist_parser = new OptimistParser argv
if optimist_parser.command() is 'run'
  mycha.run optimist_parser.options(),
            optimist_parser.files(),
            (exit_code) -> process.exit exit_code

Use OptimistParser to determine the command


# FIXME - Move this in a seperate file ~ GG

class VmListItem extends KDListItemView

  click: ->
    @getDelegate().emit 'VMSelected', @getData()

  viewAppended: ->
    JView::viewAppended.call this

  pistachio: ->
    """

        #{@getData()}

    """

class VMSelection extends KDModalView

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

    super
      width           : 300
      title           : "Select VM"
      overlay         : yes
      # cssClass        : KD.utils.curry 'vm-selection', options.cssClass
      draggable       : no
      cancellable     : yes
      appendToDomBody : yes
      delegate        : options.delegate
    , data

    @listController   = new KDListViewController
      view            : new KDListView
        type          : "vm"
        cssClass      : "vm-list"
        itemClass     : VmListItem

  viewAppended:->
    # @unsetClass 'kdmodal'
    @addSubView view = @listController.getView()

    @listController.getListView().on 'VMSelected', (vm)=>
      @emit "VMSelected", vm
      @destroy()

    KD.singleton("vmController").fetchGroupVMs no, (err, vms) =>
      return  if KD.showError err
      @listController.instantiateListItems vms

# FIXME - Move this in a seperate file ~ GG

class VmListItem extends KDListItemView

  click: ->
    @getDelegate().emit 'VMSelected', @getData()

  viewAppended: ->
    JView::viewAppended.call this

  pistachio: ->
    """

        #{@getData()}

    """

class VMSelection extends KDModalView

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

    super
      width           : 300
      title           : "Select VM"
      overlay         : yes
      # cssClass        : KD.utils.curry 'vm-selection', options.cssClass
      draggable       : no
      cancellable     : yes
      appendToDomBody : yes
      delegate        : options.delegate
    , data

    @listController   = new KDListViewController
      view            : new KDListView
        type          : "vm"
        cssClass      : "vm-list"
        itemClass     : VmListItem

  viewAppended:->
    # @unsetClass 'kdmodal'
    @addSubView view = @listController.getView()

    @listController.getListView().on 'VMSelected', (vm)=>
      @emit "VMSelected", vm
      @destroy()

    @listController.instantiateListItems KD.getSingleton('vmController').vms

Revert "terminal: fetch account's group associated VMs in "VM selection" modal"

This reverts commit e0d8381d3efe193093c86cf6f23ee399c2e109f2.


'use strict'

app.factory('uploadService', [
  'routerService',
  (router) ->
    removedAsset: undefined

    # callbacks for ng-flow:

    fileSuccess: (file) ->
      file.store.progress = 1
      file.store.save()
      return

    fileError: (file, message, flow) ->
      file.abort()
      alert("There was an error uploading the file")

    fileProgress: (file) ->
      file.store.progress = file.progress()
      return

    flowOptions:
      target: router.controllers.AssetApi.uploadChunk().url
      method: 'octet'
      chunkSize: 4194304
      simultaneousUploads: 3
      testChunks: false
      chunkRetryInterval: 5000
      permanentErrors: [400, 403, 404, 409, 415, 500, 501]
      successStatuses: [200, 201, 202, 204],
      progressCallbacksInterval: 500
      prioritizeFirstAndLastChunk: true
])

'use strict'

app.factory('uploadService', [
  'routerService', 'messageService'
  (router, messages) ->
    removedAsset: undefined

    # callbacks for ng-flow:

    fileSuccess: (file) ->
      file.store.progress = 1
      file.store.save()
      return

    fileError: (file, message, flow) ->
      file.abort()
      messages.addError
        type: 'red'
        body: message
        owner: this

    fileProgress: (file) ->
      file.store.progress = file.progress()
      return

    flowOptions:
      target: router.controllers.AssetApi.uploadChunk().url
      method: 'octet'
      chunkSize: 4194304
      simultaneousUploads: 3
      testChunks: false
      chunkRetryInterval: 5000
      permanentErrors: [400, 403, 404, 409, 415, 500, 501]
      successStatuses: [200, 201, 202, 204],
      progressCallbacksInterval: 500
      prioritizeFirstAndLastChunk: true
])

Make error use the standard addError function.


React = require 'react'
BS = require 'react-bootstrap'

TeacherContentToggle = React.createClass

  getInitialState: ->
    isShowing: false

  propTypes:
    onChange: React.PropTypes.func.isRequired

  onClick: ->
    isShowing = not @state.isShowing
    @setState({isShowing})
    @props.onChange(isShowing)

  render: ->
    teacherLinkText = if @state.isShowing
      'Hide Teacher Edition'
    else
      'Show Teacher Edition'

    <BS.Button className='btn-sm teacher-edition' onClick={@onClick}>
      {teacherLinkText}
    </BS.Button>

module.exports = TeacherContentToggle

React = require 'react'
BS = require 'react-bootstrap'

TeacherContentToggle = React.createClass

  propTypes:
    onChange: React.PropTypes.func.isRequired
    isShowing: React.PropTypes.bool.isRequired

  onClick: ->
    @props.onChange(not @props.isShowing)

  render: ->
    teacherLinkText = if @props.isShowing
      'Hide Teacher Edition'
    else
      'Show Teacher Edition'

    <BS.NavItem className='teacher-edition' onClick={@onClick}>
      {teacherLinkText}
    </BS.NavItem>

module.exports = TeacherContentToggle

Use property vs state to determine toggled status


d = ->
  console.log arguments...

base = global.wolisconfig.test_url

casper.start base, ->
  this.test.assertHttpStatus 200

casper.run ->
  @test.done()

d = ->
  console.log arguments...

base = global.wolisconfig.test_url

casper.start base, ->
  @test.assertHttpStatus 200

  # check that the index page has expected text
  @test.assertTextExists 'Your first forum'

casper.run ->
  @test.done()

Check that index page is legitimate

Check that index page is legitimate


`import BaseRoute from './_base'`

Route = BaseRoute.extend
  model: ->
    # return the current user as the model if authenticated, otherwise a blank object
    new Ember.RSVP.Promise (resolve, reject) =>
      promise = $.ajax
        url: '/api/me'
        dataType: 'json'
      promise.done (result) =>
        user = Ember.Object.create(result)
        if result.isAnon
          @set 'session.isAnon', true
          @set 'session.currentUser', null
        else
          @set 'session.isAnon', false
          @set 'session.currentUser', user
        resolve(user)
      promise.fail (result) ->
        reject()
  clearSession: ->
    @set 'session.currentUser', undefined
    @set 'session.isAnon', true
    @set 'settings.editMode', false
  actions:
    toggleEditMode: ->
      @toggleProperty 'settings.editMode'
      @send 'flushUpdateQueue'
    flushUpdateQueue: ->
      return unless @get('session.currentUser.isAdmin')
      updateQueue = @get('updateQueue')
      while updateQueue.get('length')
        section = updateQueue.popObject()
        section.save()
    error: (result, transition) ->
      if result.status is 403
        @clearSession()
        @transitionTo 'signout'
`export default Route`

`import BaseRoute from './_base'`
`import ajax from 'ic-ajax'`

Route = BaseRoute.extend
  model: ->
    mePath = '/api/me'

    # return the current user as the model if authenticated, otherwise a blank object
    new Ember.RSVP.Promise (resolve, reject) =>
      ajax
          url: mePath
          dataType: 'json'
        .then (result) =>
            user = Ember.Object.create(result)
            if result.isAnon
              @set 'session.isAnon', true
              @set 'session.currentUser', null
            else
              @set 'session.isAnon', false
              @set 'session.currentUser', user
            resolve(user)
            ajax
              url: mePath
              method: 'post'
          ,
            (result) ->
              reject()
  clearSession: ->
    @set 'session.currentUser', undefined
    @set 'session.isAnon', true
    @set 'settings.editMode', false
  actions:
    toggleEditMode: ->
      @toggleProperty 'settings.editMode'
      @send 'flushUpdateQueue'
    flushUpdateQueue: ->
      return unless @get('session.currentUser.isAdmin')
      updateQueue = @get('updateQueue')
      while updateQueue.get('length')
        section = updateQueue.popObject()
        section.save()
    error: (result, transition) ->
      if result.status is 403
        @clearSession()
        @transitionTo 'signout'
`export default Route`

Sort out ES6 import confusion; add POST to /api/me to record user visit


path = require 'path'

ROOT = process.env.APP_ROOT

express = require 'express'
handlebars = require 'express-handlebars'
bodyParser = require 'body-parser'
morgan = require 'morgan'
favicon = require 'serve-favicon'
mincer = require 'mincer'

## Set up the app
module.exports = app = express()

# Logging
app.use morgan('short')

# Set up handlebars
app.set 'view engine', 'handlebars'
app.engine 'handlebars', handlebars
  defaultLayout: 'main'
  helpers:
    comicUrl: (at) -> "/at/#{at}/"
    xmlDate: (at) -> (new Date(at)).toString()

# View, static, and LESS paths on disk
app.set 'views', path.resolve(ROOT, 'views')
app.use '/static/', express.static(path.resolve(ROOT, 'public'))

# The most important thing of all.The /favicon.ico handler
app.use favicon(path.resolve(ROOT, 'public', 'favicon.ico'))

# Handle assets with mincer.
mincerEnv = new mincer.Environment();
mincerEnv.appendPath path.resolve(ROOT, 'assets')
app.use '/assets', mincer.createServer(mincerEnv)

# Parse JSON
app.use(bodyParser.json(type: '*/json'))

path = require 'path'

ROOT = process.env.APP_ROOT

express = require 'express'
handlebars = require 'express-handlebars'
bodyParser = require 'body-parser'
morgan = require 'morgan'
favicon = require 'serve-favicon'
mincer = require 'mincer'

## Set up the app
module.exports = app = express()

# Logging
app.use morgan('short')

# Set up handlebars
app.set 'view engine', 'handlebars'
app.engine 'handlebars', handlebars
  defaultLayout: 'main'
  helpers:
    comicUrl: (at) -> "/at/#{at}/"
    baseUrl: -> "http://ayp.wtf.cat" # TODO: Switch on development mode
    xmlDate: (at) -> (new Date(parseInt(at))).toString()

# View, static, and LESS paths on disk
app.set 'views', path.resolve(ROOT, 'views')
app.use '/static/', express.static(path.resolve(ROOT, 'public'))

# The most important thing of all.The /favicon.ico handler
app.use favicon(path.resolve(ROOT, 'public', 'favicon.ico'))

# Handle assets with mincer.
mincerEnv = new mincer.Environment();
mincerEnv.appendPath path.resolve(ROOT, 'assets')
app.use '/assets', mincer.createServer(mincerEnv)

# Parse JSON
app.use(bodyParser.json(type: '*/json'))

Fix xmlDate and add baseURL

Fix xmlDate and add baseURL


scrollbarWidth = 0
FactlinkJailRoot.loaded_promise.then ->
  # Create the measurement nod; see http://davidwalsh.name/detect-scrollbar-width
  scrollDiv = document.createElement("div");
  $(scrollDiv).css(
    width: "100px"
    height: "100px"
    overflow: "scroll"
    position: "absolute"
    top: "-9999px"
  )
  document.body.appendChild(scrollDiv)

  scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth

  document.body.removeChild(scrollDiv)
  console.log 'scrollbar width: ', scrollbarWidth

FactlinkJailRoot.on 'modalOpened', ->
  document.documentElement.setAttribute('data-factlink-suppress-scrolling', '')
FactlinkJailRoot.on 'modalClosed', -> document.documentElement.removeAttribute('data-factlink-suppress-scrolling')

scrollbarWidth = 0
FactlinkJailRoot.loaded_promise.then ->
  # Create the measurement nod; see http://davidwalsh.name/detect-scrollbar-width
  scrollDiv = document.createElement("div");
  $(scrollDiv).css(
    width: "100px"
    height: "100px"
    overflow: "scroll"
    position: "absolute"
    top: "-9999px"
  )
  document.body.appendChild(scrollDiv)

  scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth

  document.body.removeChild(scrollDiv)
  console.log 'scrollbar width: ', scrollbarWidth

# To check for scrollbars on the window,  use the slightly unusual window.innerHeight
# rather than  document.documentElement.clientHeight so it works in css compat mode.
window_has_scrollbar = -> window.innerHeight < document.documentElement.scrollHeight
FactlinkJailRoot.on 'modalOpened', ->
  document.documentElement.setAttribute('data-factlink-suppress-scrolling', '')
FactlinkJailRoot.on 'modalClosed', -> document.documentElement.removeAttribute('data-factlink-suppress-scrolling')

Check whether there are currently any scrollbars


Transport = require "../src/redis-transport"
RemoteChannel = require "../src/remote-channel"
helpers = require "./helpers"
{testify,assert,events} = helpers

make = ->

  transport = new Transport
    host: "localhost"
    port: 6379

  transport.events.forward events

  channel = new RemoteChannel
    name: "greeting"
    transport: transport

  channel

testify.test "A remote channel", (context) ->

  context.test "can send and receive events", (context) ->

    sender = make()
    receiver = make()

    listen = receiver.listen()

    listen.on "success", ->

      receiver.receive (message) ->

        context.test "using an 'on' handler", ->
          assert.ok message.content is "Dan"
          receiver.end()

      sender.send content: "Dan"

Transport = require "../src/redis-transport"
RemoteChannel = require "../src/remote-channel"
helpers = require "./helpers"
{testify,assert,events} = helpers

make = ->

  transport = new Transport
    host: "localhost"
    port: 6379

  transport.events.forward events

  channel = new RemoteChannel
    name: "greeting"
    transport: transport

  channel

testify.test "A remote channel", (context) ->

  context.test "can send and receive messages", (context) ->

    sender = make()
    receiver = make()

    listen = receiver.listen()

    listen.on "success", ->

      receiver.receive (message) ->

        context.test "using a recieve handler", ->
          assert.ok message.content is "Dan"
          receiver.end()

      sender.send content: "Dan"

Update the test descriptions to match the tests