_ = require('underscore')

class DeploymentMonitor

  timeout = 10000

  constructor: ()->
    @_deployment = null
    @_chat = null
    @_currentTimeout = 0
    @_eventListeners = {
      'change': ()=>
        @_resetTimeout()
        @_monitorTimeout()
      'close': ()=>
        @.removeDeployment()
      'error': ()=>
        @.removeDeployment()
    }

  setDeployment: (deployment, chat)->
    @removeDeployment() if @hasDeployment()
    @_deployment = deployment
    @_chat = chat
    _.each(@_eventListeners, (listener, event)=>
      @_deployment.on(event, listener)
    )

  hasDeployment: ()->
    return null != @_deployment

  getDeployment: ()->
    return @_deployment

  removeDeployment: ()->
    _.each(@_eventListeners, (listener, event)=>
      @_deployment.removeListener(event, listener)
    )
    @_deployment = null
    @_chat = null

  _monitorTimeout: _.debounce(()->
    if !@.hasDeployment()
      return
    @_currentTimeout += timeout
    @_chat.send "Running #{@_currentTimeout}ms: #{getLastText(@_deployment.data.output)}"
    @_monitorTimeout()
  , timeout)

  _resetTimeout: ()->
    if @_currentTimeout > 0
      @_chat.send "Continuing..."
    @_currentTimeout = 0

  getLastText = (text)->
    textLines = text.split(/\r?\n/)
    n = textLines.length - 1
    while(!textLines[n].trim() && n > 0)
      n--
    return textLines[n]

module.exports = DeploymentMonitor

_ = require('underscore')

class DeploymentMonitor

  timeout = 10000

  constructor: ()->
    @_deployment = null
    @_chat = null
    @_currentTimeout = 0
    @_eventListeners = {
      change: ()=>
        @_resetTimeout()
        @_monitorTimeout()
      close: ()=>
        @.removeDeployment()
      error: ()=>
        @.removeDeployment()
    }

  setDeployment: (deployment, chat)->
    @removeDeployment() if @hasDeployment()
    @_deployment = deployment
    @_chat = chat
    _.each(@_eventListeners, (listener, event)=>
      @_deployment.on(event, listener)
    )

  hasDeployment: ()->
    return null != @_deployment

  getDeployment: ()->
    return @_deployment

  removeDeployment: ()->
    _.each(@_eventListeners, (listener, event)=>
      @_deployment.removeListener(event, listener)
    )
    @_deployment = null
    @_chat = null

  _monitorTimeout: _.debounce(()->
    if !@.hasDeployment()
      return
    @_currentTimeout += timeout
    @_chat.send "Running #{@_currentTimeout}ms: #{getLastText(@_deployment.data.output)}"
    @_monitorTimeout()
  , timeout)

  _resetTimeout: ()->
    if @_currentTimeout > 0
      @_chat.send "Continuing..."
    @_currentTimeout = 0

  getLastText = (text)->
    textLines = text.split(/\r?\n/)
    n = textLines.length - 1
    while(!textLines[n].trim() && n > 0)
      n--
    return textLines[n]

module.exports = DeploymentMonitor

Remove unnecessary quotes of eventListeners.


App.CwbResultTableView = Em.View.extend

  didInsertElement: ->
    @$('[data-ot]').each (index, token) ->
      new Opentip token, $(token).data('ot'),
        style: 'dark'
        fixed: true

App.CwbResultTableView = Em.View.extend

  contentBinding: 'controller.arrangedContent'

  # We need to create tooltips with grammatical info for tokens after both of these have happened:
  # - the controller's content property has been filled with a page of search results
  # - the view has been rendered
  contentDidChange: (->
    return unless @get('content.length')

    # Use the afterRender queue since the table rows may not be rendered yet even though the
    # content property has been filled.
    Ember.run.scheduleOnce 'afterRender', @, ->
      @$('[data-ot]').each (index, token) ->
        new Opentip token, $(token).data('ot'),
          style: 'dark'
          fixed: true
  ).observes('content.length')

Fix creation of tooltips with grammatical token info


# Monkey patch Turbolinks to render 401
# See https://github.com/turbolinks/turbolinks/issues/179
#     https://github.com/projecthydra-labs/hyrax/issues/617
Turbolinks.HttpRequest.prototype.requestLoaded = ->
  @endRequest =>
    if 200 <= @xhr.status < 300 or @xhr.status == 401
      @delegate.requestCompletedWithResponse(@xhr.responseText, @xhr.getResponseHeader("Turbolinks-Location"))
    else
      @failed = true
      @delegate.requestFailedWithStatusCode(@xhr.status, @xhr.responseText)

# Monkey patch Turbolinks to render 401
# See https://github.com/turbolinks/turbolinks/issues/179
#     https://github.com/projecthydra-labs/hyrax/issues/617
if Turbolinks?
  Turbolinks.HttpRequest.prototype.requestLoaded = ->
    @endRequest =>
      if 200 <= @xhr.status < 300 or @xhr.status == 401
        @delegate.requestCompletedWithResponse(@xhr.responseText, @xhr.getResponseHeader("Turbolinks-Location"))
      else
        @failed = true
        @delegate.requestFailedWithStatusCode(@xhr.status, @xhr.responseText)

Make sure Turbolinks is defined before patching.


#= require almond

window.ttm ||= {}

ttm.ClassMixer = (klass)->
  klass.build = ->
    it = new klass
    it.initialize && it.initialize.apply(it, arguments)
    it
  klass.prototype.klass = klass
  klass

define "lib/class_mixer", ->
  return ttm.ClassMixer

#= require almond

window.ttm ||= {}
window.decorators ||= {}

ttm.ClassMixer = (klass)->
  klass.build = ->
    it = new klass
    it.initialize && it.initialize.apply(it, arguments)
    it
  klass.prototype.klass = klass
  klass

define "lib/class_mixer", ->
  return ttm.ClassMixer

WIP: Implement decorator pattern for student dashboard.


ETahi.AddAuthorFormComponent = Ember.Component.extend
  tagName: 'div'

  setNewAuthor: ( ->
    unless @get('newAuthor')
      @set('newAuthor', {})
  ).on('init')

  actions:
    toggleAuthorForm: ->
      @sendAction('hideAuthorForm')

    saveNewAuthor: ->
      @sendAction('saveAuthor', @get('newAuthor'))

ETahi.AddAuthorFormComponent = Ember.Component.extend
  tagName: 'div'

  setNewAuthor: ( ->
    unless @get('newAuthor')
      @set('newAuthor', {})
  ).on('init')

  actions:
    toggleAuthorForm: ->
      @sendAction('hideAuthorForm')

    saveNewAuthor: ->
      author = @get('newAuthor')
      @sendAction('saveAuthor', author)
      if Ember.typeOf(author) == 'object'
        @set('newAuthor', {})

Fix new author form holding on to new author.


Lanes.React.Mixins.Screen = {
    childContextTypes:
        screen: React.PropTypes.object

    listenNetworkEvents: true

    loadOrCreateModel: (options) ->
        if options.prop and @props[options.prop]
            @props[options.prop]
        else
            model = new options.klass
            if options.attribute and @props.args?.length
                model.fetch(_.extend( {}, options.syncOptions, {
                    query: {"#{options.attribute}": @props.args[0]}
                }))
            model

}

Lanes.React.Mixins.Screen = {

    childContextTypes:
        screen: React.PropTypes.object

    listenNetworkEvents: true

    loadOrCreateModel: (options) ->
        if options.prop and @props[options.prop]
            @props[options.prop]
        else
            model = new options.klass
            if options.attribute and @props.args?.length
                model.fetch(_.extend( {}, options.syncOptions, {
                    query: {"#{options.attribute}": @props.args[0]}
                })).then => @state?.commands?.setModel(model)
            model

}

Call setModel after fetching model

Call setModel after fetching model

Needed so the commands can notify observers


module.exports =
  configDefaults:
    clangCommand: 'clang'
    clangPlusPlusCommand: 'clang++'
    clangExecutablePath: null
    clangIncludePaths: '.'
    clangSuppressWarnings: false
    clangDefaultCFlags: '-Wall'
    clangDefaultCppFlags: '-Wall'

  activate: ->
    console.log 'activate linter-clang'

module.exports =
  configDefaults:
    clangCommand: 'clang'
    clangExecutablePath: null
    clangIncludePaths: '.'
    clangSuppressWarnings: false
    clangDefaultCFlags: '-Wall'
    clangDefaultCppFlags: '-Wall'

  activate: ->
    console.log 'activate linter-clang'

Remove Clang Plus Plus Command Variable.


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

# Handles requests with 'atom' protocol.
#
# It's created by {AtomApplication} upon instantiation and is used to create a
# custom resource loader for 'atom://' URLs.
#
# The following directories are searched in order:
#   * ~/.atom/assets
#   * ~/.atom/dev/packages (unless in safe mode)
#   * ~/.atom/packages
#   * RESOURCE_PATH/node_modules
#
module.exports =
class AtomProtocolHandler
  constructor: (@resourcePath, safeMode) ->
    @loadPaths = []

    unless safeMode
      @loadPaths.push(path.join(app.getHomeDir(), '.atom', 'dev', 'packages'))

    @loadPaths.push(path.join(app.getHomeDir(), '.atom', 'packages'))
    @loadPaths.push(path.join(@resourcePath, 'node_modules'))

    @registerAtomProtocol()

  # Creates the 'atom' custom protocol handler.
  registerAtomProtocol: ->
    protocol.registerProtocol 'atom', (request) =>
      relativePath = path.normalize(request.url.substr(7))

      if relativePath.indexOf('assets/') is 0
        assetsPath = path.join(app.getHomeDir(), '.atom', relativePath)
        filePath = assetsPath if fs.statSyncNoException(assetsPath).isFile?()

      unless filePath
        for loadPath in @loadPaths
          filePath = path.join(loadPath, relativePath)
          break if fs.statSyncNoException(filePath).isFile?()

      new protocol.RequestFileJob(filePath)

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

# Handles requests with 'atom' protocol.
#
# It's created by {AtomApplication} upon instantiation and is used to create a
# custom resource loader for 'atom://' URLs.
#
# The following directories are searched in order:
#   * ~/.atom/assets
#   * ~/.atom/dev/packages (unless in safe mode)
#   * ~/.atom/packages
#   * RESOURCE_PATH/node_modules
#
module.exports =
class AtomProtocolHandler
  constructor: (@resourcePath, safeMode) ->
    @loadPaths = []
    @dotAtomDirectory = path.join(app.getHomeDir(), '.atom')

    unless safeMode
      @loadPaths.push(path.join(@dotAtomDirectory, 'dev', 'packages'))

    @loadPaths.push(path.join(@dotAtomDirectory, 'packages'))
    @loadPaths.push(path.join(@resourcePath, 'node_modules'))

    @registerAtomProtocol()

  # Creates the 'atom' custom protocol handler.
  registerAtomProtocol: ->
    protocol.registerProtocol 'atom', (request) =>
      relativePath = path.normalize(request.url.substr(7))

      if relativePath.indexOf('assets/') is 0
        assetsPath = path.join(@dotAtomDirectory, relativePath)
        filePath = assetsPath if fs.statSyncNoException(assetsPath).isFile?()

      unless filePath
        for loadPath in @loadPaths
          filePath = path.join(loadPath, relativePath)
          break if fs.statSyncNoException(filePath).isFile?()

      new protocol.RequestFileJob(filePath)

Add dot atom directory ivar

Add dot atom directory ivar


Gather.Views.Work.JobFormView = Backbone.View.extend

  initialize: (options) ->

  events:
    'cocoon:after-insert': 'shiftInserted'

  shiftInserted: (event, inserted) ->
    @initDatePickers(inserted)

  initDatePickers: (inserted) ->
    $(inserted).find(".datetimepicker").datetimepicker()

Gather.Views.Work.JobFormView = Backbone.View.extend

  initialize: (options) ->
    @formatFields()

  events:
    'cocoon:after-insert': 'shiftInserted'
    'change #work_job_times': 'formatFields'

  formatFields: ->
    dateFormat = I18n.t('datepicker.pformat')
    timeFormat = I18n.t('timepicker.pformat')
    times = @$('#work_job_times').val()
    switch times
      when 'date_time' then @setPickerFormat("#{dateFormat} #{timeFormat}")
      when 'date_only', 'full_period' then @setPickerFormat(dateFormat)

  shiftInserted: (event, inserted) ->
    @initDatePickers(inserted)

  initDatePickers: (inserted) ->
    @$(inserted).find('.datetimepicker').datetimepicker()

  setPickerFormat: (format) ->
    @shiftDatePickers().map (picker) ->
      $(this).data('DateTimePicker').format(format)

  shiftDatePickers: ->
    @$('#shift-table .datetimepicker')

Set picker format based on times

7836: Set picker format based on times


class window.FavouriteTopicButtonView extends ActionButtonView
  mini: true

  onRender: -> @updateButton()

  initialize: ->
    @user = currentUser
    @bindTo @user.favourite_topics, 'add remove change reset', @updateButton, @

  templateHelpers: =>
    disabled_label: Factlink.Global.t.follow_topic.capitalize()
    disable_label:  Factlink.Global.t.unfavourite.capitalize()
    enabled_label:  Factlink.Global.t.favourited.capitalize()

  buttonEnabled: ->
    @model.get('slug_title') in @user.favourite_topics.pluck('slug_title')

  primaryAction: (e) ->
    @model.favourite()

  secondaryAction: (e) ->
    @model.unfavourite()

class window.FavouriteTopicButtonView extends ActionButtonView
  mini: true

  onRender: -> @updateButton()

  initialize: ->
    @user = currentUser
    @bindTo @user.favourite_topics, 'add remove change reset', @updateButton, @

  templateHelpers: =>
    disabled_label: Factlink.Global.t.follow_topic.capitalize()
    disable_label:  Factlink.Global.t.unfollow.capitalize()
    enabled_label:  Factlink.Global.t.followed.capitalize()

  buttonEnabled: ->
    @model.get('slug_title') in @user.favourite_topics.pluck('slug_title')

  primaryAction: (e) ->
    @model.favourite()

  secondaryAction: (e) ->
    @model.unfavourite()

Use unfollow labels for 'Follow topic' button


#!/usr/bin/env coffee

argv = require('yargs')
  .usage('Usage: $0 --teams [num] --time [num] --rest [num] --areas [num]')
  .demand(['teams'])
  .default('time', 33)
  .default('rest', 7)
  .default('areas', 1)
  .argv

{teams, time, rest, areas} = argv
{timeNeededMinutes, tourneySchedule, playoffGames} = require('../lib/tourney-time')(argv)

hours = Math.floor(timeNeededMinutes / 60)
minutes = timeNeededMinutes % 60

time = ""
time += "#{hours} hours" if hours
time += ', ' if hours and minutes
time += "#{minutes} minutes" if minutes

console.log """For #{teams} teams
               Playing #{time} minute games
               with #{rest} minute breaks in between games
               on #{areas} playing area(s)
               you'll play a #{tourneySchedule.type} tournament with #{tourneySchedule.games + playoffGames} total games
               #{tourneySchedule.games} tourney games and #{playoffGames} playoff games
               which will take #{time} """

console.log tourneySchedule

#!/usr/bin/env coffee

argv = require('yargs')
  .usage('Usage: $0 --teams [num] --time [num] --rest [num] --areas [num]')
  .demand(['teams'])
  .default('time', 33)
  .default('rest', 7)
  .default('areas', 1)
  .argv

{teams, time, rest, areas} = argv
{timeNeededMinutes, tourneySchedule, playoffGames, schedule} = require('../lib/tourney-time')(argv)

hours = Math.floor(timeNeededMinutes / 60)
minutes = timeNeededMinutes % 60

totalTime = ""
totalTime += "#{hours} hours" if hours
totalTime += ', ' if hours and minutes
totalTime += "#{minutes} minutes" if minutes

console.log """For #{teams} teams
               Playing #{time} minute games
               with #{rest} minute breaks in between games
               on #{areas} playing area(s)
               you'll play a #{tourneySchedule.type} tournament with #{tourneySchedule.games + playoffGames} total games
               #{tourneySchedule.games} tourney games and #{playoffGames} playoff games
               which will take #{totalTime} """

console.log schedule

Fix for bad game time output

bug: Fix for bad game time output


class DevToolsController extends AppController

  name    = "DevTools"
  version = "0.1"
  route   = "/:name?/#{name}"

  KD.registerAppClass this, {
    name, version, behavior: "application", route,
    menu          : [
      { title     : "Create a new App",    eventName : "create" }
      { type      : "separator" }
      { title     : "Save",                eventName : "save" }
      { title     : "Save All",            eventName : "saveAll" }
      { type      : "separator" }
      { title     : "Publish to AppStore", eventName : "publish" }
      { title     : "customViewToggleLiveReload" }
      { type      : "separator" }
      { title     : "customViewToggleFullscreen" }
      { type      : "separator" }
      { title     : "Exit",                eventName : "exit" }
    ]
  }

  constructor:(options = {}, data)->
    options.view    = new DevToolsMainView
    options.appInfo =
      name     : "DevTools"
      type     : "application"

    super options, data

  # FIXME facet, to make it work I had to call notifyWindowResizeListeners here
  handleQuery:->
    {workspace} = @getView()
    workspace.ready ->
      KD.getSingleton("windowController").notifyWindowResizeListeners()

class DevToolsController extends AppController

  name    = "DevTools"
  version = "0.1"
  route   = "/:name?/#{name}"

  KD.registerAppClass this, {
    name, version, behavior: "application", route,
    menu          : [
      { title     : "Create a new App",    eventName : "create" }
      { type      : "separator" }
      { title     : "Save",                eventName : "save" }
      { title     : "Save All",            eventName : "saveAll" }
      { type      : "separator" }
      { title     : "Publish to AppStore", eventName : "publish" }
      { title     : "customViewToggleLiveReload" }
      { type      : "separator" }
      { title     : "customViewToggleFullscreen" }
      { type      : "separator" }
      { title     : "Exit",                eventName : "exit" }
    ]
  }

  constructor:(options = {}, data)->
    options.view    = new DevToolsMainView
    options.appInfo =
      name     : "DevTools"
      type     : "application"

    super options, data

  # FIXME facet, to make it work I had to call notifyWindowResizeListeners here
  handleQuery:->
    {workspace} = @getView()
    workspace.ready ->
      wc = KD.getSingleton("windowController")
      wc.notifyWindowResizeListeners()
      wc.notifyWindowResizeListeners()

DevTools: Fix the resizeHandler, dunno why it needs to be called two times ~ FIXME later


unless Offline
  throw new Error("Offline simulate brought in without offline.js")

for state in ['up', 'down']
  if document.querySelector("script[data-simulate='#{ state }']") or localStorage.OFFLINE_SIMULATE is state
    Offline.options ?= {}
    Offline.options.checks ?= {}
    Offline.options.checks.active = state

unless Offline
  throw new Error("Offline simulate brought in without offline.js")

for state in ['up', 'down']
  if document.querySelector("script[data-simulate='#{ state }']") or localStorage?.OFFLINE_SIMULATE is state
    Offline.options ?= {}
    Offline.options.checks ?= {}
    Offline.options.checks.active = state

Fix missing availability check for window.localStorage (e.g. it is disabled by default in android webviews)


try
  ReactDOM = require 'react-dom'
catch
  ReactDOM = window.ReactDOM
unless ReactDOM?
  throw "Can't find ReactDOM"
module.exports = ReactDOM

try
  ReactDOM = require 'react-dom'
catch
  ReactDOM = window.ReactDOM

# can fall back to normal React until 0.15
unless ReactDOM?
  try
    ReactDOM = require 'react'
  catch
    ReactDOM = window.React

unless ReactDOM?
  throw "Can't find ReactDOM"

module.exports = ReactDOM

Allow ReactDOM -> React fallback

Allow ReactDOM -> React fallback


require 'atom'
{runSpecSuite} = require '../spec/jasmine-helper'

atom.openDevTools()

document.title = "Benchmark Suite"
benchmarkSuite = require.resolve('./benchmark-suite')
runSpecSuite(benchmarkSuite, true)

require '../src/atom'
{runSpecSuite} = require '../spec/jasmine-helper'

atom.openDevTools()

document.title = "Benchmark Suite"
benchmarkSuite = require.resolve('./benchmark-suite')
runSpecSuite(benchmarkSuite, true)

Use relative path to atom require

Use relative path to atom require


Template.newTaskForm.events
  'submit #new-task, click #addTaskButton': (e) ->
    e.preventDefault()
    body = $('#new-task-text').val()
    $('#new-task-text').val("")
    now = new Date()
    priority = 'low'
    list = 'Adel'
    Tasks.insert
      body: body
      dateDue: moment(now).add('w', 1).toDate()
      dateCreated: now
      dateCompleted: false
      modified: now
      list: list
      priority: priority
      completed: false
      repeating: false
    list = Lists.findOne
      name: list
    Lists.update list._id,
      $inc:
        numTodos: 1

Template.newTaskForm.events
  'submit #new-task, click #addTaskButton': (e) ->
    e.preventDefault()
    body = $('#new-task-text').val()
    $('#new-task-text').val("")
    now = new Date()
    priority = 'low'
    list = 'Home'
    Tasks.insert
      body: body
      dateDue: moment(now).add('w', 1).toDate()
      dateCreated: now
      dateCompleted: false
      modified: now
      list: list
      priority: priority
      completed: false
      repeating: false
    list = Lists.findOne
      name: list
    Lists.update list._id,
      $inc:
        numTodos: 1

Add tasks to 'Home' list by default

Add tasks to 'Home' list by default


{View} = require 'space-pen'

module.exports =
class Tab extends View
  @content: (editSession) ->
    @div class: 'tab', =>
      @span class: 'file-name', outlet: 'fileName'
      @span class: 'close-icon'

  initialize: (@editSession) ->
    @buffer = @editSession.buffer
    @subscribe @buffer, 'path-change', => @updateFileName()
    @subscribe @buffer, 'contents-modified', => @updateModifiedStatus()
    @subscribe @buffer, 'after-save', => @updateModifiedStatus()
    @subscribe @buffer, 'git-status-change', => @updateModifiedStatus()
    @updateFileName()
    @updateModifiedStatus()

  updateModifiedStatus: ->
    if @buffer.isModified()
      @toggleClass('file-modified') unless @isModified
      @isModified = true
    else
      @removeClass('file-modified') if @isModified
      @isModified = false

  updateFileName: ->
    @fileName.text(@editSession.buffer.getBaseName() ? 'untitled')

{View} = require 'space-pen'

module.exports =
class Tab extends View
  @content: (editSession) ->
    @div class: 'tab', =>
      @span class: 'file-name', outlet: 'fileName'
      @span class: 'close-icon'

  initialize: (@editSession) ->
    @buffer = @editSession.buffer
    @subscribe @buffer, 'path-changed', => @updateFileName()
    @subscribe @buffer, 'contents-modified', => @updateModifiedStatus()
    @subscribe @buffer, 'saved', => @updateModifiedStatus()
    @subscribe @buffer, 'git-status-changed', => @updateModifiedStatus()
    @updateFileName()
    @updateModifiedStatus()

  updateModifiedStatus: ->
    if @buffer.isModified()
      @toggleClass('file-modified') unless @isModified
      @isModified = true
    else
      @removeClass('file-modified') if @isModified
      @isModified = false

  updateFileName: ->
    @fileName.text(@editSession.buffer.getBaseName() ? 'untitled')

Rename `Anchor` and `Buffer` events to passive-voice scheme


class OpinionatorsAvatarView extends Backbone.Marionette.Layout
  tagName: 'span'
  className: 'opinionators-avatar'
  template: 'opinionators/avatar'

  onRender: ->
    UserPopoverContentView.makeTooltip @, @model

class window.OpinionatorsAvatarsView extends Backbone.Marionette.CompositeView
  tagName: 'span'
  className: 'opinionators-avatars'
  template: "opinionators/avatars"
  itemView: OpinionatorsAvatarView

  itemViewContainer: ".js-opinionators-avatars-collection"

  events:
    'click .js-show-all' : 'show_all'

  number_of_items: 7

  initialize: (options) ->
    @listenTo @collection, 'add remove reset sync', @render

  _initialEvents: -> # don't use default bindings to collection

  appendHtml: (collectionView, itemView, index) ->
    super if index < @truncatedListSizes().numberToShow

  templateHelpers: =>
    numberOfOthers: @truncatedListSizes().numberOfOthers

  truncatedListSizes: ->
    truncatedListSizes @collection.totalRecords, @number_of_items

  show_all: (e) ->
    e.stopPropagation()
    e.preventDefault()
    @number_of_items = Infinity
    @render()

class OpinionatorsAvatarView extends Backbone.Marionette.Layout
  tagName: 'span'
  className: 'opinionators-avatar'
  template: 'opinionators/avatar'

  onRender: ->
    UserPopoverContentView.makeTooltip @, @model

class window.OpinionatorsAvatarsView extends Backbone.Marionette.CompositeView
  tagName: 'span'
  className: 'opinionators-avatars'
  template: "opinionators/avatars"
  itemView: OpinionatorsAvatarView

  itemViewContainer: ".js-opinionators-avatars-collection"

  events:
    'click .js-show-all' : 'show_all'

  number_of_items: 4

  initialize: (options) ->
    @listenTo @collection, 'add remove reset sync', @render

  _initialEvents: -> # don't use default bindings to collection

  appendHtml: (collectionView, itemView, index) ->
    super if index < @truncatedListSizes().numberToShow

  templateHelpers: =>
    numberOfOthers: @truncatedListSizes().numberOfOthers

  truncatedListSizes: ->
    truncatedListSizes @collection.totalRecords, @number_of_items

  show_all: (e) ->
    e.stopPropagation()
    e.preventDefault()
    @number_of_items = Infinity
    @render()

Truncate to 4 images by default

Truncate to 4 images by default


settings = require "settings-sharelatex"
logger = require "logger-sharelatex"
_ = require "underscore"
request = require "request"

makeRequest: (opts, callback)->
	if settings.apis?.analytics?.url?
		request opts, callback
	else
		callback()

module.exports =

	recordEvent: (user_id, event, segmentation = {}, callback = (error) ->) ->
		if user_id == settings.smokeTest?.userId
			return callback()
		opts =
			body:
				event:event
				segmentation:segmentation
			json:true
			method:"POST"
			timeout:1000
			url: "#{settings.apis.analytics.url}/user/#{user_id}/event"
		makeRequest opts, callback

	getLastOccurance: (user_id, event, callback = (error) ->) ->
		opts =
			body:
				event:event
			json:true
			method:"POST"
			timeout:1000
			url: "#{settings.apis.analytics.url}/user/#{user_id}/event/last_occurnace"
		makeRequest opts, (err, response, body)->
			if err?
				console.log response, opts
				logger.err {user_id, err}, "error getting last occurance of event"
				return callback err
			else
				return callback null, body

settings = require "settings-sharelatex"
logger = require "logger-sharelatex"
_ = require "underscore"
request = require "request"

makeRequest = (opts, callback)->
	if settings.apis?.analytics?.url?
		urlPath = opts.url
		opts.url = "#{settings.apis.analytics.url}#{urlPath}"
		request opts, callback
	else
		callback()

module.exports =

	recordEvent: (user_id, event, segmentation = {}, callback = (error) ->) ->
		if user_id == settings.smokeTest?.userId
			return callback()
		opts =
			body:
				event:event
				segmentation:segmentation
			json:true
			method:"POST"
			timeout:1000
			url: "/user/#{user_id}/event"
		makeRequest opts, callback

	getLastOccurance: (user_id, event, callback = (error) ->) ->
		opts =
			body:
				event:event
			json:true
			method:"POST"
			timeout:1000
			url: "/user/#{user_id}/event/last_occurnace"
		makeRequest opts, (err, response, body)->
			if err?
				console.log response, opts
				logger.err {user_id, err}, "error getting last occurance of event"
				return callback err
			else
				return callback null, body

Fix up makeRequest, so it copes with `analytics.url` being un-configured.


CI.outer.Error = class Error extends CI.outer.Page
  constructor: ->
    @name = "error"
    @status = renderContext.status or 404
    @url = renderContext.githubPrivateAuthURL

  title: =>
    titles =
      401: "Login required"
      404: "Page not found"
      500: "Internal server error"

    titles[@status] or "Something unexpected happened"

  message: =>
    messages =
      401: "Login here to view this page"
      404: "We're sorry, but that page doesn't exist"
      500: "We're sorry, but something broke"
    messages[@status] or "Something completely unexpected happened"

  viewContext: =>
    title: @title()
    error: @status
    message: @message()

CI.outer.Error = class Error extends CI.outer.Page
  constructor: ->
    super
    @name = "error"
    @status = renderContext.status or 404
    @url = renderContext.githubPrivateAuthURL

  title: =>
    titles =
      401: "Login required"
      404: "Page not found"
      500: "Internal server error"

    titles[@status] or "Something unexpected happened"

  message: =>
    messages =
      401: "Login here to view this page"
      404: "We're sorry, but that page doesn't exist"
      500: "We're sorry, but something broke"
    messages[@status] or "Something completely unexpected happened"

  viewContext: =>
    title: @title()
    error: @status
    message: @message()

Fix the 401 page looking incorrect.

Fix the 401 page looking incorrect.

CI.outer.Error inherited from outer.Page, but wasn't calling super, so variables that Page expected, @opts, wasn't created

Fixes #1740


describe 'HeaderCells StringCell', ->
  HeaderCell = require '../../lib/header-cells/string-cell'

  initView = ({ model } = {}) ->
    model ?= new Backbone.Model label: 'header cell label'
    new HeaderCell { model }

  showView = ->
    initView(arguments...).render()

  it 'renders the label', ->
    expect(showView().$el).toHaveText 'header cell label'

  it 'has a className', ->
    expect(showView().$el).toHaveClass 'string-cell'

Improve string cell spec coverage

Improve string cell spec coverage


module.exports = (grunt) ->
  documentation = 'doc'
  grunt.initConfig
    pkg: grunt.file.readJSON 'package.json'
    clean: [
      'bin'
      '.grunt'
      documentation
    ]
    jsdoc:
      dist:
        src: ['topicmap.js', 'README.md']
        options:
          destination: documentation
          template: 'node_modules/ink-docstrap/template'
          configure: 'jsdoc.conf.json'
    'gh-pages':
      'default':
        src: '**/*'
        options:
          base: 'doc'
          message: 'Update documentation'
    travis:
      src: '**/*'
      options:
        base: 'doc'
        message: 'Update documentation'
        repo: 'https://' + process.env.GH_TOKEN + '@github.com/' + process.env.TRAVIS_REPO_SLUG
        user:
          name: 'Travis CI Server'
          email: 'will.byrne@hpe.com'
    watch:
      doc:
        files: [
          'src/**/*.js'
          'README.md'
        ]
        tasks: ['doc']

  grunt.loadNpmTasks 'grunt-gh-pages'
  grunt.loadNpmTasks 'grunt-jsdoc'

  grunt.registerTask 'doc', ['jsdoc']
  grunt.registerTask 'push-doc', ['doc', 'gh-pages:default']
  grunt.registerTask 'push-doc-travis', ['doc', 'gh-pages:travis']
  grunt.registerTask 'watch-doc', ['watch:doc']

[BIFHI-38] Set up travis for Topic Map [rev: Alex Scown]


#
# User icon via gravatar email address or user initials
#
angular.module('doubtfire.common.user-icon', [])
.directive('userIcon', ->
  restrict: 'E'
  replace: true
  scope:
    user: '=?'
    size: '=?'
    email: '=?'
  templateUrl: 'common/user-icon/user-icon.tpl.html'
  controller: ($scope, $http, currentUser, md5) ->
    $scope.user ?= currentUser.profile
    $scope.size ?= 100
    $scope.email ?= $scope.user.email

    $scope.userBackgroundStyle = (email) ->
      # Gravatar hash
      hash = if (email) then md5.createHash(email.trim().toLowerCase()) else md5.createHash('')
      backgroundUrl = "https://www.gravatar.com/avatar/#{hash}.png?default=blank&size=#{$scope.size}"
      "background-image: url('#{backgroundUrl}')"

    $scope.initials = (->
      initials = if ($scope.user && $scope.user.name) then $scope.user.name.split(" ") else "  "
      ("#{initials[0][0]}#{initials[1][0]}").toUpperCase()
    )()
)

#
# User icon via gravatar email address or user initials
#
angular.module('doubtfire.common.user-icon', [])
.directive('userIcon', ->
  restrict: 'E'
  replace: true
  scope:
    user: '=?'
    size: '=?'
    email: '=?'
  templateUrl: 'common/user-icon/user-icon.tpl.html'
  controller: ($scope, $http, currentUser, md5) ->
    $scope.user ?= currentUser.profile
    $scope.size ?= 100
    $scope.email ?= $scope.user.email

    $scope.userBackgroundStyle = (email) ->
      # Gravatar hash
      hash = if (email) then md5.createHash(email.trim().toLowerCase()) else md5.createHash('')
      backgroundUrl = "https://www.gravatar.com/avatar/#{hash}.png?default=blank&size=#{$scope.size}"
      "background-image: url('#{backgroundUrl}')"

    $scope.initials = (->
      initials = if ($scope.user && $scope.user.name) then $scope.user.name.split(" ") else "  "
      if initials.length > 1 then ("#{initials[0][0]}#{initials[1][0]}").toUpperCase() else "  "
    )()
)

Test length of name array in initials

FIX: Test length of name array in initials


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

ready = ->
  engine = new Bloodhound(
    datumTokenizer: (d) ->
      console.log d
      Bloodhound.tokenizers.whitespace d.title
    queryTokenizer: Bloodhound.tokenizers.whitespace
    remote: url: '../projects/typeahead/%QUERY')
  promise = engine.initialize()
  $('.typeahead').typeahead null,
    name: 'engine'
    displayKey: 'email'
    source: engine.ttAdapter()
  return

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

ready = ->
  $('#SignOutMyself').click ->
    if !@checked
      url = document.URL

      alert 'You won\'t be able to perform any more actions on the project after you\'re unenrolled!'
    return
  return

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

ready = ->
  $('#setInactiveButton').click ->
    alert 'You\'re going to set the project status to inactive'
  return

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

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

ready = ->
  engine = new Bloodhound(
    datumTokenizer: (d) ->
      console.log d
      Bloodhound.tokenizers.whitespace d.title
    queryTokenizer: Bloodhound.tokenizers.whitespace
    remote: url: '../projects/typeahead/%QUERY')
  promise = engine.initialize()
  $('.typeahead').typeahead null,
    name: 'engine'
    displayKey: 'email'
    source: engine.ttAdapter()
  return

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

ready = ->
  $('#SignOutMyself').click ->
    if !@checked
      url = document.URL
      locale = url.split('?')[1].split('=')[1]
      if locale == 'de'
        alert 'Sie sind nicht mehr befugt, weitere Maßnahmen für das Projekt durchzuführen, nachdem Sie sich aus dem Projekt ausgetragen haben!'
      else
        alert 'You won\'t be able to perform any more actions on the project after you\'re unenrolled!'
      return
    return
  return

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

ready = ->
  $('#setInactiveButton').click ->
    url = document.URL
    locale = url.split('?')[1].split('=')[1]
    if locale == 'de'
      alert 'Das Projekt wird nun inaktiv geschalten!'
    else
      alert 'You\'re going to set the project status to inactive'
    return
  return

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

Add translation for pop up messages

Add translation for pop up messages


{BufferedProcess, CompositeDisposable} = require 'atom'
lsc = require 'atom-livescript'

module.exports =
  provideLinter: ->
    grammarScopes: ['source.livescript']
    scope: 'file'
    lintOnFly: true
    lint: (textEditor)=>
      new Promise (resolve, reject)=>
        filePath = textEditor.getPath()
        fileText = textEditor.getText()
        try
          lsc.compile fileText
          resolve []
        catch err
          result = /Parse error on line (\d+): (.*)/.exec err.message
          messages = [
            type: 'Error'
            text: result[2]
            filePath: filePath
            range: [
              [parseInt(result[1])-1, 0]
              [parseInt(result[1]), 0]
            ]
          ]
          resolve messages

        process.onWillThrowError ({error, handle})=>
          atom.notifications.addError "Failed to run lsc",
            detail: error.message
            dismissable: true
          handle()
          resolve []

{BufferedProcess, CompositeDisposable} = require 'atom'
lsc = require 'atom-livescript'

module.exports =
  provideLinter: ->
    grammarScopes: ['source.livescript']
    scope: 'file'
    lintOnFly: true
    lint: (textEditor)=>
      new Promise (resolve, reject)=>
        filePath = textEditor.getPath()
        fileText = textEditor.getText()
        try
          lsc.compile fileText
          resolve []
        catch err
          result = switch
            when err instanceof SyntaxError
              r = /(.*) on line (\d+)$/.exec err.message
              line: r[2]
              text: r[1]
            else
              r = /Parse error on line (\d+): (.*)/.exec err.message
              line: r[1]
              text: r[2]
          messages = [
            type: 'Error'
            text: result.text
            filePath: filePath
            range: [
              [parseInt(result.line)-1, 0]
              [parseInt(result.line), 0]
            ]
          ]
          resolve messages

        process.onWillThrowError ({error, handle})=>
          atom.notifications.addError "Failed to run lsc",
            detail: error.message
            dismissable: true
          handle()
          resolve []

Fix for another parse error

Fix for another parse error


grammarListView = null
grammarStatusView = null

module.exports =
  config:
    showOnRightSideOfStatusBar:
      type: 'boolean'
      default: true

  activate: ->
    @commandDisposable = atom.commands.add('atom-text-editor', 'grammar-selector:show', createGrammarListView)
    atom.packages.onDidActivateAll(createGrammarStatusView)

  deactivate: ->
    @commandDisposable.dispose()
    grammarStatusView?.destroy()
    grammarStatusView = null
    grammarListView?.destroy()
    grammarListView = null

createGrammarListView = ->
  editor = atom.workspace.getActiveTextEditor()
  if editor?
    GrammarListView = require './grammar-list-view'
    grammarListView ?= new GrammarListView(editor)
  grammarListView.attach()

createGrammarStatusView = ->
  statusBar = atom.views.getView(atom.workspace).querySelector("status-bar")
  if statusBar?
    GrammarStatusView = require './grammar-status-view'
    grammarStatusView = new GrammarStatusView().initialize(statusBar)
    grammarStatusView.attach()

commandDisposable = null
grammarListView = null
grammarStatusView = null

module.exports =
  config:
    showOnRightSideOfStatusBar:
      type: 'boolean'
      default: true

  activate: ->
    commandDisposable = atom.commands.add('atom-text-editor', 'grammar-selector:show', createGrammarListView)
    atom.packages.onDidActivateAll(createGrammarStatusView)

  deactivate: ->
    commandDisposable?.dispose()
    commandDisposable = null

    grammarStatusView?.destroy()
    grammarStatusView = null

    grammarListView?.destroy()
    grammarListView = null

createGrammarListView = ->
  editor = atom.workspace.getActiveTextEditor()
  if editor?
    GrammarListView = require './grammar-list-view'
    grammarListView ?= new GrammarListView(editor)
  grammarListView.toggle()

createGrammarStatusView = ->
  statusBar = atom.views.getView(atom.workspace).querySelector("status-bar")
  if statusBar?
    GrammarStatusView = require './grammar-status-view'
    grammarStatusView = new GrammarStatusView().initialize(statusBar)
    grammarStatusView.attach()

Call toggle each time command is dispatched


angular.module('impac.filters.moment-date', []).filter('momentDate', ($translate, ImpacTheming) ->
  (date, component) ->

    moment.locale($translate.use().toLowerCase())

    validPeriods = ['daily', 'weekly', 'monthly', 'quarterly', 'yearly', 'default']
    if !_.isEmpty(component) && validPeriods.includes(component.toLowerCase())
      component = 'period-' + component.toLowerCase()

    settings = ImpacTheming.get()
    format = settings.dateFormatterSettings.default
    if settings.dateFormatterSettings.formats && settings.dateFormatterSettings.formats[component]
      format = settings.dateFormatterSettings.formats[component]

    return moment(date).format(format)
)

angular.module('impac.filters.moment-date', []).filter('momentDate', ($translate, ImpacTheming) ->
  (date, component) ->

    moment.locale($translate.use().toLowerCase())

    validPeriods = ['daily', 'weekly', 'monthly', 'quarterly', 'yearly', 'default']
    if !_.isEmpty(component) && _.includes(validPeriods, component.toLowerCase())
      component = 'period-' + component.toLowerCase()

    settings = ImpacTheming.get()
    format = settings.dateFormatterSettings.default
    if settings.dateFormatterSettings.formats && settings.dateFormatterSettings.formats[component]
      format = settings.dateFormatterSettings.formats[component]

    return moment(date).format(format)
)

Refactor using lodash for ie11

Refactor using lodash for ie11


Sprangular.directive 'addressSelection', ->
  restrict: 'E'
  templateUrl: 'addresses/selection.html'
  scope:
    address: '='
    addresses: '='
    countries: '='
    disabled: '=disabledFields'
    submitted: '='
    existingAddress: '='
  controller: ($scope) ->
    $scope.$watch 'addresses', (addresses) ->
      return unless addresses && addresses.length > 0

      found = _.find addresses, (existing) ->
        existing.same($scope.address)

      $scope.toggleExistingAddress() if found

    $scope.toggleExistingAddress = ->
      $scope.existingAddress = !$scope.existingAddress

      if $scope.existingAddress
        $scope.address = $scope.addresses[0]
      else
        $scope.address = new Sprangular.Address()

  link: (element, attrs) ->
    attrs.disabled = false unless attrs.disabled?
    attrs.existingAddress = false unless attrs.existingAddress?

Sprangular.directive 'addressSelection', ->
  restrict: 'E'
  templateUrl: 'addresses/selection.html'
  scope:
    address: '='
    addresses: '='
    countries: '='
    disabled: '=disabledFields'
    submitted: '='
    existingAddress: '='
  controller: ($scope) ->
    $scope.$watch 'addresses', (addresses) ->
      return unless addresses && addresses.length > 0

      found = _.find addresses, (existing) ->
        existing.same($scope.address)

      $scope.toggleExistingAddress() if found

    $scope.toggleExistingAddress = ->
      $scope.existingAddress = !$scope.existingAddress

      if $scope.existingAddress
        $scope.address = $scope.addresses[0]
      else
        $scope.address = new Sprangular.Address()

  compile: (element, attrs) ->
    attrs.existingAddress = 'false' if !attrs.exisitingAddress?

  link: (scope, element, attrs) ->
    attrs.disabled = false unless attrs.disabled?

Add default value for existingAddress attribute in compile stage


# Implements global `ajaxBeforeSend` event.
#
# jQuery already provides a handful of global AJAX events. However,
# there is no global version of `beforeSend`, so `ajaxBeforeSend` is
# added to complement it.
#
# Reference: http://docs.jquery.com/Ajax_Events

# Skip for Zepto which doesn't have ajaxSetup but does already support
# `ajaxBeforeSend`. It'd be better to feature test for the event and
# see if we need to install it.
return unless $.ajaxSetup

# One caveat about using `$.ajaxSetup` is that its easily clobbered.
# If anything else tries to register another global `beforeSend`
# handler, ours will be overriden.
#
# To work around this, register your global `beforeSend` handler with:
#
#     $(document).bind('ajaxBeforeSend', function() {})
#
$.ajaxSetup
  beforeSend: (xhr, settings) ->
    # Skip if global events are disabled
    return unless settings.global

    # Default to document if context isn't set
    element = settings.context || document

    # Provide a global version of the `beforeSend` callback
    event = $.Event 'ajaxBeforeSend'
    $(element).trigger event, [xhr, settings]
    event.result

# Implements global `ajaxBeforeSend` event.
#
# jQuery already provides a handful of global AJAX events. However,
# there is no global version of `beforeSend`, so `ajaxBeforeSend` is
# added to complement it.
#
# Reference: http://docs.jquery.com/Ajax_Events

# Skip for Zepto which doesn't have ajaxSetup but does already support
# `ajaxBeforeSend`. It'd be better to feature test for the event and
# see if we need to install it.
if $.ajaxSetup

  # One caveat about using `$.ajaxSetup` is that its easily clobbered.
  # If anything else tries to register another global `beforeSend`
  # handler, ours will be overriden.
  #
  # To work around this, register your global `beforeSend` handler with:
  #
  #     $(document).bind('ajaxBeforeSend', function() {})
  #
  $.ajaxSetup
    beforeSend: (xhr, settings) ->
      # Skip if global events are disabled
      return unless settings.global

      # Default to document if context isn't set
      element = settings.context || document

      # Provide a global version of the `beforeSend` callback
      event = $.Event 'ajaxBeforeSend'
      $(element).trigger event, [xhr, settings]
      event.result

Make compatible with coffee 1.1.3 bug

Make compatible with coffee 1.1.3 bug


'menu': [
  'label': 'Find'
  'submenu': [
    { 'label': 'Find in Buffer', 'command': 'find-and-replace:show'}
    { 'label': 'Replace in Buffer', 'command': 'find-and-replace:show-replace'}
    { 'label': 'Select Next', 'command': 'find-and-replace:select-next'}
    { 'label': 'Select All', 'command': 'find-and-replace:select-all'}
    { 'type': 'separator' }
    { 'label': 'Find in Project', 'command': 'project-find:show'}
    { 'type': 'separator' }
    { 'label': 'Find Next', 'command': 'find-and-replace:find-next'}
    { 'label': 'Find Previous', 'command': 'find-and-replace:find-previous'}
    { 'label': 'Replace Next', 'command': 'find-and-replace:replace-next'}
    { 'label': 'Replace All', 'command': 'find-and-replace:replace-all'}
    { 'type': 'separator' }
  ]
]

'context-menu':
  '.tree-view > li.directory':
    'Search in this directory': 'project-find:show-in-current-directory'

'menu': [
  'label': 'Find'
  'submenu': [
    { 'label': 'Find in Buffer', 'command': 'find-and-replace:show'}
    { 'label': 'Replace in Buffer', 'command': 'find-and-replace:show-replace'}
    { 'label': 'Select Next', 'command': 'find-and-replace:select-next'}
    { 'label': 'Select All', 'command': 'find-and-replace:select-all'}
    { 'label': 'Toggle Find in Buffer', 'command': 'find-and-replace:toggle'}
    { 'type': 'separator' }
    { 'label': 'Find in Project', 'command': 'project-find:show'}
    { 'label': 'Toggle Find in Project', 'command': 'project-find:toggle'}
    { 'type': 'separator' }
    { 'label': 'Find Next', 'command': 'find-and-replace:find-next'}
    { 'label': 'Find Previous', 'command': 'find-and-replace:find-previous'}
    { 'label': 'Replace Next', 'command': 'find-and-replace:replace-next'}
    { 'label': 'Replace All', 'command': 'find-and-replace:replace-all'}
    { 'type': 'separator' }
  ]
]

'context-menu':
  '.tree-view > li.directory':
    'Search in this directory': 'project-find:show-in-current-directory'

Add toggle menu item for panels

Add toggle menu item for panels

Refs #164


# Description:
#   When you get some bad news sometimes you got to let it out.
#
# Dependencies:
#   None
#
# Configuration:
#   None
#
# Commands:
#   sheeeit - Display an image or animated gif
#
# Author:
#   danishkhan

sheits = [
  "http://www.circlenoir.com/forums/attachment.php?attachmentid=478&stc=1#.jpg",
  "http://media.skateboard.com.au/forum/images/davis_sheeeit.jpg",
  "http://www.gifsoup.com/webroot/animatedgifs1/2019075_o.gif",
  "http://i417.photobucket.com/albums/pp258/reddreadrevolver/sheeeit.gif",
  "http://behance.vo.llnwd.net/profiles3/111050/projects/252777/1110501249152745.jpg"
]

module.exports = (robot) ->
  robot.hear /sh(e+)(i+)(t+)/, (msg) ->
  msg.send msg.random sheits

Indent msg.send so that we all can Clay. Sheeeeeeeeeit.


{CompositeDisposable} = require 'atom'

module.exports =
class MinimapPigmentsBinding
  constructor: ({@editor, @minimap, @colorBuffer}) ->
    @displayedMarkers = []
    @subscriptions = new CompositeDisposable

    @colorBuffer.initialize().then => @updateMarkers()

    @subscriptions.add @colorBuffer.onDidUpdateColorMarkers => @updateMarkers()

  updateMarkers: ->
    markers = @colorBuffer.findValidColorMarkers()

    for m in @displayedMarkers when m not in markers
      @minimap.removeAllDecorationsForMarker(m.marker)

    for m in markers when m.color?.isValid() and m not in @displayedMarkers
      @minimap.decorateMarker(m.marker, type: 'highlight', color: m.color.toCSS())

    @displayedMarkers = markers

  destroy: ->
    @subscriptions.dispose()

{CompositeDisposable} = require 'atom'

module.exports =
class MinimapPigmentsBinding
  constructor: ({@editor, @minimap, @colorBuffer}) ->
    @displayedMarkers = []
    @subscriptions = new CompositeDisposable

    @colorBuffer.initialize().then => @updateMarkers()

    @subscriptions.add @colorBuffer.editor.displayBuffer.onDidTokenize =>
      @updateMarkers()
    @subscriptions.add @colorBuffer.onDidUpdateColorMarkers =>
      @updateMarkers()

  updateMarkers: ->
    markers = @colorBuffer.findValidColorMarkers()

    for m in @displayedMarkers when m not in markers
      @minimap.removeAllDecorationsForMarker(m.marker)

    for m in markers when m.color?.isValid() and m not in @displayedMarkers
      @minimap.decorateMarker(m.marker, type: 'highlight', color: m.color.toCSS())

    @displayedMarkers = markers

  destroy: ->
    @subscriptions.dispose()

:bug: Fix markers persisting after tokenisation when they should have been hidden


webpackConfig = require('./webpack.config.coffee')
webpackConfig.entry = {}

module.exports = (config)  ->
  config.set
    frameworks: ['jasmine']
    browsers: ['Chrome']

    files: [
      './spec/**/*.coffee'
    ]

    preprocessors:
      './spec/**/*spec.coffee': ['webpack']

    webpack: webpackConfig

    webpackMiddleware:
      noInfo: true

webpackConfig = require('./webpack.config.coffee')
webpackConfig.entry = {}

module.exports = (config)  ->
  config.set
    frameworks: ['jasmine']
    browsers: ['Chrome']

    files: [
      './spec/**/*.coffee'
    ]

    preprocessors:
      './spec/**/*': ['webpack']

    webpack: webpackConfig

    webpackMiddleware:
      noInfo: true

Simplify spec files regex since webpack should be doing all the heavy lifting


Router.configure
	layoutTemplate: 'Layout'

Router.route '/', ->
	return @redirect '/course/si'

Router.route '/si', ->
	return @redirect '/course/si'

Router.route '/tsi', ->
	return @redirect '/course/tsi'

Router.route '/course/:course',
	name: 'course'

	waitOn: ->
		return [
			Meteor.subscribe 'Grade'
			Meteor.subscribe 'userGradeInfo'
		]

	action: ->
		course = @params.course.toLowerCase()
		if course not in ['si', 'tsi']
			return @redirect '/course/si'

		Session.set 'grade', course

		Session.set 'grade-filter-status', @params.query.status

		@render 'Grade'

	fastRender: true

Router.route '/my/:course/:email',
	name: 'my'

	waitOn: ->
		return [
			Meteor.subscribe 'Grade'
			Meteor.subscribe 'userGradeInfo'
		]

	action: ->
		course = @params.course.toLowerCase()
		if course not in ['si', 'tsi']
			return @redirect "/course/si/#{params.email}"

		Session.set 'grade', course

		Session.set 'grade-filter-status', @params.query.status

		@render 'Grade',
		data:
			email: @params.email

	fastRender: true

Router.configure
	layoutTemplate: 'Layout'

Router.route '/', ->
	return @redirect '/course/si'

Router.route '/si', ->
	return @redirect '/course/si'

Router.route '/tsi', ->
	return @redirect '/course/tsi'

Router.route '/course/:course',
	name: 'course'

	waitOn: ->
		return [
			Meteor.subscribe 'Grade'
			Meteor.subscribe 'userGradeInfo'
		]

	action: ->
		course = @params.course.toLowerCase()
		if course not in ['si', 'tsi']
			return @redirect '/course/si'

		Session.set 'grade', course

		Session.set 'grade-filter-status', @params.query.status

		@render 'Grade'

	fastRender: true

Router.route '/my/:course/:email',
	name: 'my'

	waitOn: ->
		return [
			Meteor.subscribe 'Grade'
			Meteor.subscribe 'userGradeInfo', @params.email
		]

	action: ->
		course = @params.course.toLowerCase()
		if course not in ['si', 'tsi']
			return @redirect "/course/si/#{@params.email}"

		Session.set 'grade', course

		Session.set 'grade-filter-status', @params.query.status

		@render 'Grade',
			data:
				email: @params.email

	fastRender: true

Fix bugs with shared url

Fix bugs with shared url


define (require) ->

  Piece = require 'Piece'
  ranks = require 'ranks'

  describe 'Piece', ->

    for rank of ranks
      do (rank) ->
        it "should not throw if rank #{rank}", ->
          expect(->
            new Piece
              rank: rank
              side: 0
          ).to.not.throw()

    it 'should throw if rank is not valid', ->
      expect(->
        new Piece
          rank: 'abc'
          side: 0
      ).to.throw()

    it 'should not throw if side 0 or 1', ->
      expect(->
        new Piece
          rank: '1'
          side: 0
      ).to.not.throw()

      expect(->
        new Piece
          rank: '1'
          side: 1
      ).to.not.throw()

    it 'should throw if side not valid', ->
      expect(->
        new Piece
          rank: '1'
          side: 2
      ).to.throw()

      expect(->
        new Piece
          rank: '1'
          side: -1
      ).to.throw()

define (require) ->

  Piece = require 'Piece'
  ranks = require 'ranks'

  describe 'Piece', ->

    it 'should not throw if rank is valid', ->
      for rank of ranks
        expect(->
          new Piece
            rank: rank
            side: 0
        ).to.not.throw()

    it 'should throw if rank is not valid', ->
      expect(->
        new Piece
          rank: 'abc'
          side: 0
      ).to.throw()

    it 'should not throw if side 0 or 1', ->
      expect(->
        new Piece
          rank: '1'
          side: 0
      ).to.not.throw()

      expect(->
        new Piece
          rank: '1'
          side: 1
      ).to.not.throw()

    it 'should throw if side not valid', ->
      expect(->
        new Piece
          rank: '1'
          side: 2
      ).to.throw()

      expect(->
        new Piece
          rank: '1'
          side: -1
      ).to.throw()

Break tests down into one

[master] Break tests down into one


require 'window'

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

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

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

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

  # Run bundled package specs
  for packagePath in fsUtils.listTreeSync(config.nodeModulesDirPath) when atom.isInternalPackage(packagePath)
    requireSpecs(packagePath, 'bundled')
  setSpecType('bundled')

  # Run user package specs
  for packagePath in fsUtils.listTreeSync(config.userPackagesDirPath)
    requireSpecs(packagePath, 'user')
  setSpecType('user')

fs = require 'fs'

require 'window'

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

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

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

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

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

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

Use fs.readdirSync() for listing package directories

Previously fsUtils.listTreeSync() was used which returns every path
in the tree, not just paths directly underneath the root path.

This speeds up the spec suite require time by not stat'ing the entire
node_modules directory.


Meteor.methods
	setAvatarFromService: (image, service) ->
		if not Meteor.userId()
			throw new Meteor.Error 203, '[methods] typingStatus -> Usuário não logado'

		user = Meteor.user()

		file = new FS.File image
		file.attachData image, ->
			file.name user.username

			Avatars.insert file, (err, fileObj) ->
				Meteor.users.update {_id: user._id}, {$set: {avatarOrigin: service}}

	resetAvatar: (image, service) ->
		if not Meteor.userId()
			throw new Meteor.Error 203, '[methods] typingStatus -> Usuário não logado'

		user = Meteor.user()

		Avatars.remove {'copies.avatars.name': user.username}

		Meteor.users.update user._id, {$unset: {avatarOrigin: 1}}

Add method to reset avatar

Add method to reset avatar


angular.module("hyperadmin")
  .controller "FormCtrl", ($scope, Restangular) ->
    @submit = =>
      target = @meta.target
      method = @meta.method

      onError = (response) =>
        errors = response.data

      onSuccess = (resource) =>
        # Do nothing yet

      if method == "post"
        Restangular.all(target).post(@resource).then onSuccess, onError
      else if method == "patch"
        Restangular.one(target).patch(@resource).then onSuccess, onError

    this

angular.module("hyperadmin")
  .controller "FormCtrl", ($scope, Restangular) ->
    @submit = =>
      target = @meta.target
      method = @meta.method

      onError = (response) =>
        @errors = response.data

      onSuccess = (resource) =>
        # Do nothing yet

      if method == "post"
        Restangular.all(target).post(@resource).then onSuccess, onError
      else if method == "patch"
        Restangular.one(target).patch(@resource).then onSuccess, onError

    this

Fix displaying error messages from forms


_ = require 'underscore'
{ isTouchDevice } = require '../util/device.coffee'

module.exports =
  firstVisibleInput: ($el) ->
    $el.find('input:visible, textarea:visible').first()

  moveCursorToEnd: ($input) ->
    val = $input.val()
    $input.val('').val val

  autofocus: ($input, defer = false) ->
    return if isTouchDevice()

    focus = =>
      $input.focus()
      @moveCursorToEnd $input

    if defer then _.defer(focus) else focus()

_ = require 'underscore'
{ isTouchDevice } = require '../util/device.coffee'

module.exports =
  firstVisibleInput: ($el) ->
    $el.find('input:visible, textarea:visible').first()

  moveCursorToEnd: ($input) ->
    val = $input.val()
    $input.val('').val val

  isFocusable: ($el) ->
    $el.is('input') or $el.is('textarea')

  autofocus: ($el, defer = false) ->
    return if isTouchDevice()

    focus = =>
      if not @isFocusable $el
        $el = @firstVisibleInput $el

      $el.focus()
      @moveCursorToEnd $el

    if defer then _.defer(focus) else focus()

Allow autofocus to accept a container element


noflo = require("noflo")
uuid = require("node-uuid")

class RandomUuid extends noflo.Component

  description: "Generate a random UUID token"

  constructor: ->
    @inPorts =
      in: new noflo.Port
    @outPorts =
      out: new noflo.Port

    @inPorts.in.on "disconnect", =>
      token = uuid.v4()
      @outPorts.out.send(token)
      @outPorts.out.disconnect()

exports.getComponent = -> new RandomUuid

noflo = require('noflo')
uuid = require('node-uuid')

class RandomUuid extends noflo.Component

  description: 'Generate a random UUID token'

  constructor: ->
    @inPorts =
      in: new noflo.Port
    @outPorts =
      out: new noflo.Port

    @inPorts.in.on 'begingroup', (group) =>
      @outPorts.out.beginGroup group
    @inPorts.in.on 'endgroup', =>
      @outPorts.out.endGroup()
    @inPorts.in.on 'disconnect', =>
      @outPorts.out.disconnect()

    @inPorts.in.on 'data', =>
      @outPorts.out.send uuid.v4()

exports.getComponent = -> new RandomUuid

Send UUID on 'data' rather than 'disconnect'


require 'js-yaml'
socket = require 'socket.io'

solid = require 'solid'

{Engine} = require './engine'
engine = new Engine
  moves: require './data/bw/moves.yml'

io = socket.listen solid (app) ->
  app.get '/', @render ->
    @doctype 5
    @html ->
      @head ->
        @js '/socket.io/socket.io.js'
        @script @html_safe '''
          var socket = io.connect('http://localhost');
          socket.on('newuser', function (data) {
            console.log(data);
            document.write(data);
          });
        '''

io.sockets.on 'connection', (socket) ->
  socket.emit 'newuser', 'you joined!'

require 'js-yaml'
socket = require 'socket.io'

solid = require 'solid'

{Engine} = require './engine'
engine = new Engine
  moves: require './data/bw/moves.yml'

io = socket.listen solid (app) ->
  app.get '/jquery.js', @jquery
  app.get '/', @render ->
    @doctype 5
    @html ->
      @head ->
        @js '/jquery.js'
        @js '/socket.io/socket.io.js'
        @script @html_safe '''
          var socket = io.connect('http://localhost');
          socket.on('newuser', function (data) {
            console.log(data);
            $("#messages").append(data);
          });
        '''
      @body ->
        @p JSON.stringify(engine.moves)
        @p '#messages'

io.sockets.on 'connection', (socket) ->
  socket.emit 'newuser', 'you joined!'

Add jQuery and show move data.

Add jQuery and show move data.


'fileTypes': [
  'gitattributes'
  'gitignore'
  'conf'
  'ctags'
  'hgignore'
  'mailmap'
  'stylelintignore'
  'cfg'
]
'name': 'Generic Configuration'
'patterns': [
  {
    'include': '#comment'
  }
]
'repository':
  'comment':
    'captures':
      '1':
        'name': 'comment.line.number-sign.generic-config'
      '2':
        'name': 'punctuation.definition.comment.generic-config'
      '3':
        'name': 'comment.line.semi-colon.generic-config'
      '4':
        'name': 'punctuation.definition.comment.generic-config'
    'match': '((#).*$\\n?)|((;).*$\\n?)'
'scopeName': 'text.generic-config'

'fileTypes': [
  'gitattributes'
  '.config/git/ignore'
  '.git/info/exclude'
  'gitignore'
  'conf'
  'ctags'
  'hgignore'
  'mailmap'
  'stylelintignore'
  'cfg'
]
'name': 'Generic Configuration'
'patterns': [
  {
    'include': '#comment'
  }
]
'repository':
  'comment':
    'captures':
      '1':
        'name': 'comment.line.number-sign.generic-config'
      '2':
        'name': 'punctuation.definition.comment.generic-config'
      '3':
        'name': 'comment.line.semi-colon.generic-config'
      '4':
        'name': 'punctuation.definition.comment.generic-config'
    'match': '((#).*$\\n?)|((;).*$\\n?)'
'scopeName': 'text.generic-config'

Add support for gitignores other than .gitignore


client  = require 'nack/client'
process = require 'nack/process'

config = __dirname + "/fixtures/echo.ru"

exports.testClientRequest = (test) ->
  test.expect 8

  p = process.createProcess config
  p.on 'ready', () ->
    c = client.createConnection p.sockPath
    test.ok c

    request = c.request 'GET', '/foo', {}
    test.ok request
    test.same "GET", request.method
    test.same "/foo", request.path
    test.same "/foo", request.headers['PATH_INFO']

    request.end()

    request.on "response", (response) ->
      test.ok response
      test.same 200, response.statusCode

      p.quit()
      p.on 'exit', () ->
        test.ok true
        test.done()

client  = require 'nack/client'
process = require 'nack/process'

config = __dirname + "/fixtures/echo.ru"

exports.testClientRequest = (test) ->
  test.expect 10

  p = process.createProcess config
  p.on 'ready', () ->
    c = client.createConnection p.sockPath
    test.ok c

    request = c.request 'GET', '/foo', {}
    test.ok request
    test.same "GET", request.method
    test.same "/foo", request.path
    test.same "/foo", request.headers['PATH_INFO']

    test.ok request.writeable

    request.end()

    request.on 'close', () ->
      test.ok true

    request.on 'response', (response) ->
      test.ok response
      test.same 200, response.statusCode

      p.quit()
      p.on 'exit', () ->
        test.ok true
        test.done()

Test client request is a writable stream


DefaultFileIcons = require '../lib/default-file-icons'
FileIcons = require '../lib/file-icons'

describe 'FileIcons', ->
  afterEach ->
    FileIcons.setService(new DefaultFileIcons)

  it 'provides a default', ->
    expect(FileIcons.getService()).toBeDefined()
    expect(FileIcons.getService()).not.toBeNull()

  it 'allows the default to be overridden', ->
    service = new Object
    FileIcons.setService(service)

    expect(FileIcons.getService()).toBe(service)

  it 'allows the service to be reset to the default easily', ->
    service = new Object
    FileIcons.setService(service)
    FileIcons.resetService()

    expect(FileIcons.getService()).not.toBe(service)

  describe 'Class handling', ->
    workspaceElement = null

    beforeEach ->
      workspaceElement = atom.views.getView(atom.workspace)

      waitsForPromise ->
        atom.workspace.open('sample.js')

      waitsForPromise ->
        atom.packages.activatePackage('tabs')

    it 'allows multiple classes to be passed', ->
      service =
        iconClassForPath: (path) -> 'first second'

      FileIcons.setService(service)
      tab = workspaceElement.querySelector('.tab')
      tab.updateIcon()
      expect(tab.itemTitle.className).toBe('title icon first second')

    it 'allows an array of classes to be passed', ->
      service =
        iconClassForPath: (path) -> ['first', 'second']

      FileIcons.setService(service)
      tab = workspaceElement.querySelector('.tab')
      tab.updateIcon()
      expect(tab.itemTitle.className).toBe('title icon first second')

Add a spec for class-list enhancements

Add a spec for class-list enhancements


# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

OUT.registerCreatedHandler "todo_list", (selector) ->
  $(selector).find("a.new").click()

$ ->
  # Remove links to todo-list from todo-lists in content area
  $('.content h2 a[rel="todo-list"]').each ->
    $(this).parent().html $(this).html()
    # TODO: doesnot work with live added data

  OUT.contentItems.highlightQueryIn ".content-items .content-item-todo-list h2, .content-items .todo-title", (chain) ->
    matched_lists = $(".content-items .content-item-todo-list h2 span.highlight").parents('.content-item-todo-list')
    matched_lists.find('.content-item').show()

$(window).load ->
  $('.content-todo-list.sortable').sortable
    connectWith: ".content-todo-list.sortable"

# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

OUT.registerCreatedHandler "todo_list", (selector) ->
  $(selector).find("a.new").click()

$ ->
  # Remove links to todo-list from todo-lists in content area
  $('.content h2 a[rel="todo-list"]').each ->
    $(this).replaceWith $(this).html()
    # TODO: doesnot work with live added data

  OUT.contentItems.highlightQueryIn ".content-items .content-item-todo-list h2, .content-items .todo-title", (chain) ->
    matched_lists = $(".content-items .content-item-todo-list h2 span.highlight").parents('.content-item-todo-list')
    matched_lists.find('.content-item').show()

$(window).load ->
  $('.content-todo-list.sortable').sortable
    connectWith: ".content-todo-list.sortable"

Fix bug in transforming linked todo_list titles into plain text


angular.module('loomioApp').factory 'StanceModel', (DraftableModel, AppConfig, MentionLinkService) ->
  class StanceModel extends DraftableModel
    @singular: 'stance'
    @plural: 'stances'
    @indices: ['pollId', 'authorId']
    @serializableAttributes: AppConfig.permittedParams.stance
    @draftParent: 'poll'

    defaultValues: ->
      reason: ''
      stanceChoicesAttributes: []
      visitorAttributes: {}

    relationships: ->
      @belongsTo 'poll'
      @hasMany 'stanceChoices'

    participant: ->
      @recordStore.users.find(@userId) or @recordStore.visitors.find(@visitorId)

    stanceChoice: ->
      _.first @stanceChoices()

    pollOption: ->
      @stanceChoice().pollOption() if @stanceChoice()

    pollOptionId: ->
      (@pollOption() or {}).id

    pollOptions: ->
      @recordStore.pollOptions.find(@pollOptionIds())

    stanceChoiceNames: ->
      _.pluck(@pollOptions(), 'name')

    pollOptionIds: ->
      _.pluck @stanceChoices(), 'pollOptionId'

    choose: (optionIds) ->
      _.each @recordStore.stanceChoices.find(stanceId: @id), (stanceChoice) ->
        stanceChoice.remove()

      _.each _.flatten([optionIds]), (optionId) =>
        @recordStore.stanceChoices.create(pollOptionId: parseInt(optionId), stanceId: @id)
      @

    # prepareForForm: ->
    #   @stanceChoicesAttributes = _.map(@pollOptionIds(), (id) -> { pollOptionId: id })

angular.module('loomioApp').factory 'StanceModel', (DraftableModel, AppConfig, MentionLinkService) ->
  class StanceModel extends DraftableModel
    @singular: 'stance'
    @plural: 'stances'
    @indices: ['pollId', 'authorId']
    @serializableAttributes: AppConfig.permittedParams.stance
    @draftParent: 'poll'

    afterConstruction: ->
      @visitorAttributes =
        name:  @participant().name
        email: @participant().email

    defaultValues: ->
      reason: ''
      stanceChoicesAttributes: []
      visitorAttributes: {}

    relationships: ->
      @belongsTo 'poll'
      @hasMany 'stanceChoices'

    participant: ->
      @recordStore.users.find(@userId) or @recordStore.visitors.find(@visitorId)

    stanceChoice: ->
      _.first @stanceChoices()

    pollOption: ->
      @stanceChoice().pollOption() if @stanceChoice()

    pollOptionId: ->
      (@pollOption() or {}).id

    pollOptions: ->
      @recordStore.pollOptions.find(@pollOptionIds())

    stanceChoiceNames: ->
      _.pluck(@pollOptions(), 'name')

    pollOptionIds: ->
      _.pluck @stanceChoices(), 'pollOptionId'

    choose: (optionIds) ->
      _.each @recordStore.stanceChoices.find(stanceId: @id), (stanceChoice) ->
        stanceChoice.remove()

      _.each _.flatten([optionIds]), (optionId) =>
        @recordStore.stanceChoices.create(pollOptionId: parseInt(optionId), stanceId: @id)
      @

    # prepareForForm: ->
    #   @stanceChoicesAttributes = _.map(@pollOptionIds(), (id) -> { pollOptionId: id })

Set visitor attributes on stance construction


# This file contains utilities to deal with prototype extensions.

#### def

# Defines a non-enumerable property on the specified constructor's `prototype`.
#
#     def Object, merge: (o) ->
#       # merge implementation
#
#     {foo: 10}.merge bar: 20 # {foo: 10, bar: 20}
def = (ctor, o) ->
  for name, value of o
    unless ctor.prototype[name]?
      Object.defineProperty? ctor.prototype,
                             name,
                             enumerable: false, value: value

module.exports = {def}

# This file contains utilities to deal with prototype extensions.

#### def

# Defines a non-enumerable property on the specified constructor's `prototype`.
#
#     def Object, merge: (o) ->
#       # merge implementation
#
#     {foo: 10}.merge bar: 20 # {foo: 10, bar: 20}
def = (ctor, o) ->
  for name, value of o
    Object.defineProperty? ctor.prototype,
                           name,
                           enumerable: false, value: value, writable: true

module.exports = {def}

Remove existence check in def and set defined properties as writable.


define [
  'jquery'
  'underscore'
  'backbone'
  'marionette'
  'cs!collections/media-types'
  'cs!views/workspace/menu/auth'
  'cs!views/workspace/menu/add'
  'cs!views/workspace/menu/toolbar-search'
  'hbs!templates/layouts/workspace/menu'
], ($, _, Backbone, Marionette, mediaTypes, AuthView, AddView, toolbarView, menuTemplate) ->

  _toolbar = null

  return new class MenuLayout extends Marionette.Layout
    template: menuTemplate

    regions:
      add: '#workspace-menu-add'
      auth: '#workspace-menu-auth'
      toolbar: '#workspace-menu-toolbar'

    onRender: () ->
      @showView(_toolbar)

    showView: (view) ->
      _toolbar = view or toolbarView

      @add.show(new AddView {collection:mediaTypes})
      @auth.show(new AuthView())
      @toolbar.show(_toolbar)

    showToolbar: (view) ->
      @showView(view or toolbarView)

define [
  'jquery'
  'underscore'
  'backbone'
  'marionette'
  'cs!collections/media-types'
  'cs!session'
  'cs!views/workspace/menu/auth'
  'cs!views/workspace/menu/add'
  'cs!views/workspace/menu/toolbar-search'
  'hbs!templates/layouts/workspace/menu'
], ($, _, Backbone, Marionette, mediaTypes, session, AuthView, AddView, toolbarView, menuTemplate) ->

  _toolbar = null

  return new class MenuLayout extends Marionette.Layout
    template: menuTemplate

    regions:
      add: '#workspace-menu-add'
      auth: '#workspace-menu-auth'
      toolbar: '#workspace-menu-toolbar'

    onRender: () ->
      @showView(_toolbar)

    showView: (view) ->
      _toolbar = view or toolbarView

      @add.show(new AddView {collection:mediaTypes})
      @auth.show(new AuthView {model: session})
      @toolbar.show(_toolbar)

    showToolbar: (view) ->
      @showView(view or toolbarView)

Include session in AuthView constructor

Include session in AuthView constructor


Meteor.methods
	updateRoom: (rid, name, users, accessPermissions) ->

		if not Meteor.userId()
			throw new Meteor.Error('invalid-user', "[methods] updateRoom -> Invalid user")

		room = ChatRoom.findOne rid

		if room.t not in ['d'] and name? and name isnt room.name
			Meteor.call 'saveRoomName', rid, name
		Meteor.call 'relabelRoom', rid, accessPermissions
		if users?
			# remove the current users
			users = _.without.apply _,room.usernames.concat users
			for user in users
				Meteor.call 'addUserToRoom', rid, user
		return true

Meteor.methods
	updateRoom: (rid, name, usernames, accessPermissions) ->

		console.log '[method] updateRoom -> arguments', arguments

		if not Meteor.userId()
			throw new Meteor.Error('invalid-user', "[methods] updateRoom -> Requires authenticated user")

		# validate params exist
		unless rid and name and usernames and accessPermissions
			throw new Meteor.Error 'invalid-argument', 'Missing required values'

		room = ChatRoom.findOne rid

		# don't allow the room creator to be removed
		unless room.u.username in usernames
			throw new Meteor.Error 'invalid-argument', 'You cannot remove the room creator'

		if room.t not in ['d'] and name? and name isnt room.name
			Meteor.call 'saveRoomName', rid, name

		# relabel room if permissions changed
		if _.difference( room.accessPermissions, accessPermissions ).length > 0
			Meteor.call 'relabelRoom', rid, accessPermissions

		# add users to the room that weren't previously in the room
		usersToAdd = _.difference( usernames, room.usernames)
		for username in usersToAdd
			Meteor.call 'addUserToRoom', {rid: rid, username: username}

		# remove users from the room that were previously in the room
		usersToRemove = _.difference( room.usernames, usernames)
		for username in usersToRemove
			Meteor.call 'removeUserFromRoom', {rid: rid, username: username}

		return {rid:rid}

Validate parameters, and: 1. check that room creator is not removed 2. relabel room if access permissions changed 3. add new members that we're previously in the room 4. remove existing members that should no longer be in the room


class Trix.ImageAttachmentView
  constructor: (@attachment) ->
    @attachment.delegate = this
    @image = document.createElement("img")

  render: ->
    @loadPreview() if @attachment.isPending()
    @updateImageAttributes()
    @image

  loadPreview: ->
    reader = new FileReader
    reader.onload = (event) =>
      @image.setAttribute("src", event.target.result)
    reader.readAsDataURL(@attachment.file)

  attributeNames = "src width height class".split(" ")

  updateImageAttributes: ->
    attributes = {}
    attributes[key] = value for key, value of @attachment.attributes
    attributes.src = attributes.url
    attributes.class = "pending-attachment" if @attachment.isPending()

    for key in attributeNames
      if value = attributes[key]
        @image.setAttribute(key, value)
      else
        @image.removeAttribute(key)

  # Attachment delegate

  attachmentDidUpdate: (@attachment) ->
    @updateImageAttributes()

class Trix.ImageAttachmentView
  constructor: (@attachment) ->
    @attachment.delegate = this
    @image = document.createElement("img")

  render: ->
    @loadFile() if @attachment.isPending()
    @updateImageAttributes()
    @image

  loadFile: ->
    reader = new FileReader
    reader.onload = (event) =>
      if @attachment.isPending()
        @image.setAttribute("src", event.target.result)
        @attachment.update(width: @image.offsetWidth, height: @image.offsetHeight)
    reader.readAsDataURL(@attachment.file)

  attributeNames = "url width height class".split(" ")

  updateImageAttributes: ->
    attributes = {}

    for key in attributeNames
      attributes[key] = @attachment.attributes[key]

    if attributes.url
      attributes.src = attributes.url
      delete attributes.url

    if @attachment.isPending()
      attributes.class = "pending-attachment"

    for key, value of attributes
      if value?
        @image.setAttribute(key, value)
      else
        @image.removeAttribute(key)

  # Attachment delegate

  attachmentDidUpdate: ->
    @updateImageAttributes()

Set image dimensions after loading the File


'menu': [
  {
    'label': 'Packages'
    'submenu': [
      'label': 'Bracket Matcher'
      'submenu': [
        { 'label': 'Go To Matching Bracket', 'command': 'bracket-matcher:go-to-matching-bracket' }
        { 'label': 'Select Inside Brackets', 'command': 'bracket-matcher:select-inside-brackets' }
        { 'label': 'Remove Brackets From Selection', 'command': 'bracket-matcher:remove-brackets-from-selection' }
        { 'label': 'Close Current Tag', 'command': 'bracket-matcher:close-tag'}
        { 'label': 'Remove Matching Brackets', 'command': 'bracket-matcher:remove-matching-brackets'}
      ]
    ]
  }
]

'menu': [
  {
    'label': 'Packages'
    'submenu': [
      'label': 'Bracket Matcher'
      'submenu': [
        { 'label': 'Go To Matching Bracket', 'command': 'bracket-matcher:go-to-matching-bracket' }
        { 'label': 'Select Inside Brackets', 'command': 'bracket-matcher:select-inside-brackets' }
        { 'label': 'Remove Brackets From Selection', 'command': 'bracket-matcher:remove-brackets-from-selection' }
        { 'label': 'Close Current Tag', 'command': 'bracket-matcher:close-tag' }
        { 'label': 'Remove Matching Brackets', 'command': 'bracket-matcher:remove-matching-brackets' }
      ]
    ]
  },
  {
    'label': 'Selection'
    'submenu': [
        { 'label': 'Select Inside Brackets', 'command': 'bracket-matcher:select-inside-brackets' }
    ]
  }
]

Add Select Inside Brackets to Selection menu


module.exports = (robot) ->
  robot.hear /the more you know/, (msg) ->
    msg.reply "http://www.youtube.com/watch?v=v3rhQc666Sg"

module.exports = (robot) ->
  robot.hear /the more you know/, (msg) ->
    msg.send "http://www.youtube.com/watch?v=v3rhQc666Sg"

Change to post in channel not reply.

Change to post in channel not reply.


define ['lib/amd/Backbone','neo4j/webadmin/modules/databrowser/views/PropertyContainerView'], (Backbone, PropertyContainerView) ->

  # TODO: Refactor out the "shouldBeConvertedToString" into it's own class
  describe "PropertyContainerView", ->
    pcv = new PropertyContainerView(template:null)

    it "recognizes ascii characters as strings", ->
      expect(pcv.shouldBeConvertedToString "a").toBe(true)
      expect(pcv.shouldBeConvertedToString "abcd123 ").toBe(true)

    it "recognizes swedish characters as strings", ->
      expect(pcv.shouldBeConvertedToString "åäö").toBe(true)
      expect(pcv.shouldBeConvertedToString "åäö #$ asd  ").toBe(true)

    it "recognizes strings containing odd characters as strings", ->
      expect(pcv.shouldBeConvertedToString ";åäö #$ asd  ").toBe(true)

    it "recognizes valid JSON values as not being strings", ->
      expect(pcv.shouldBeConvertedToString "1").toBe(false)
      expect(pcv.shouldBeConvertedToString "12").toBe(false)
      expect(pcv.shouldBeConvertedToString "12.523").toBe(false)

      expect(pcv.shouldBeConvertedToString "['1','2','3']").toBe(false)
      expect(pcv.shouldBeConvertedToString "[1,2,3]").toBe(false)

      expect(pcv.shouldBeConvertedToString '"a quoted string"').toBe(false)

define ['lib/amd/Backbone','neo4j/webadmin/modules/databrowser/views/PropertyContainerView'], (Backbone, PropertyContainerView) ->

  # TODO: Refactor out the "shouldBeConvertedToString" into it's own class
  describe "PropertyContainerView", ->
    pcv = new PropertyContainerView(template:null)

    it "recognizes ascii characters as strings", ->
      expect(pcv.shouldBeConvertedToString "a").toBe(true)
      expect(pcv.shouldBeConvertedToString "abcd123 ").toBe(true)

    it "recognizes strings containing odd characters as strings", ->
      expect(pcv.shouldBeConvertedToString ";åäö #$ asd  ").toBe(true)

    it "recognizes valid JSON values as not being strings", ->
      expect(pcv.shouldBeConvertedToString "1").toBe(false)
      expect(pcv.shouldBeConvertedToString "12").toBe(false)
      expect(pcv.shouldBeConvertedToString "12.523").toBe(false)

      expect(pcv.shouldBeConvertedToString "['1','2','3']").toBe(false)
      expect(pcv.shouldBeConvertedToString "[1,2,3]").toBe(false)

      expect(pcv.shouldBeConvertedToString '"a quoted string"').toBe(false)

Remove a test that fails in TeamCity.

Remove a test that fails in TeamCity.

There is no obvious reason why this test should fail, but since
Webadmin has been replaced by the Browser, there doesn't seem to be any
point trying to work out what is going on.


reqr              = if global.GENTLY then GENTLY.hijack(require) else require
stream            = reqr "stream"

class PaginationStream extends stream.Readable
  constructor: (fetchPage) ->
    super objectMode: true
    @_fetchPage = fetchPage
    @_pageno = 0
    @_items = []
    @_itemsRead = 0

  _read: ->
    if @_items.length > 0
      @_itemsRead++
      return process.nextTick => @push @_items.pop()

    if @_nitems? && @_itemsRead >= @_nitems
      return process.nextTick => @push null

    @_fetchPage ++@_pageno, (err, result) =>
      if err?
        return @emit "error", err

      @_nitems = result.count

      @_items = (result.items[i] for i in [result.items.length-1 .. 0])

      return @_read()

module.exports = PaginationStream

reqr              = if global.GENTLY then GENTLY.hijack(require) else require
TransloaditClient = reqr "./TransloaditClient"
stream            = reqr "stream"

class PaginationStream extends stream.Readable
  constructor: (@_fetchPage) ->
    super objectMode: true
    @_pageno = 0
    @_items = []
    @_itemsRead = 0

  _read: ->
    if @_items.length > 0
      @_itemsRead++
      return process.nextTick => @push @_items.pop()

    if @_nitems? && @_itemsRead >= @_nitems
      return process.nextTick => @push null

    @_fetchPage ++@_pageno, (err, result) =>
      if err?
        return @emit "error", err

      @_nitems = result.count

      @_items = (result.items[i] for i in [result.items.length-1 .. 0])

      return @_read()

module.exports = PaginationStream

Revert "Some nice warnings away"

Revert "Some nice warnings away"

This reverts commit 1e04dc556af840e0e4571e287135e510245524d7.


###
Use intersection observer to lazy load.  Not using vue-in-viewport-mixin since
needs are simpler and I want more control.
###
export default

	props:
		intersectionOptions:
			type: Object
			default: -> {}

	data: -> inViewport: false

	mounted: ->
		return unless @shouldObserve and IntersectionObserver?
		@observer = new IntersectionObserver @onInViewport,
			@makeIntersectionOptions()
		@observer.observe @$el

	computed:

		# Conditions where the viewport is watched
		shouldObserve: -> @lazyload or @autopause

		# Conditions where we observe only once
		shouldObserveOnce: -> not @autopause

	watch:

		# Trigger load when in viewport
		inViewport: (visible) -> @load() if visible

	methods:

		# Parse interesection options
		makeIntersectionOptions: ->
			options = @intersectionOptions
			if options.root and typeof options.root == 'string'
				options.root = document.querySelector options.root
			return options

		# Store when in viewport
		onInViewport: (entries) ->
			@inViewport = entries[0].isIntersecting
			@observer?.disconnect() if @inViewport and @shouldObserveOnce

###
Use intersection observer to lazy load.  Not using vue-in-viewport-mixin since
needs are simpler and I want more control.
###
export default

	props:
		intersectionOptions:
			type: Object
			default: -> {}

	data: -> inViewport: false

	# Start observing on init
	mounted: -> @startObserving()

	computed:

		# Conditions where the viewport is watched
		shouldObserve: -> @lazyload or @autopause

		# Conditions where we observe only once
		shouldObserveOnce: -> not @autopause

	watch:

		# Trigger load when in viewport
		inViewport: (visible) -> @load() if visible

	methods:

		# Start observing if appropriate
		startObserving: ->
			return unless @shouldObserve and IntersectionObserver?
			return if @observer # Don't make multiple observers
			@observer = new IntersectionObserver @onInViewport,
				@makeIntersectionOptions()
			@observer.observe @$el

		# Parse interesection options
		makeIntersectionOptions: ->
			options = @intersectionOptions
			if options.root and typeof options.root == 'string'
				options.root = document.querySelector options.root
			return options

		# Store when in viewport
		onInViewport: (entries) ->
			@inViewport = entries[0].isIntersecting
			if @inViewport and @shouldObserveOnce
				@observer?.disconnect()
				delete @observer

Move initializing of observing into a method


{Testing, expect, sinon, _} = require 'openstax-react-components/test/helpers'

User = require 'user/model'

describe 'User', ->

  it 'defaults to not logged in', ->
    expect(User.isLoggedIn()).to.be.false

  it 'resets courses when destroy is called, but still emits signals', ->
    fakeCourse =
      destroy: -> true
    sinon.stub(fakeCourse, 'destroy')
    User.courses = [fakeCourse]
    logoutSpy = sinon.spy()
    User.channel.on 'logout.received', logoutSpy
    User.destroy()
    expect(User.courses).to.be.empty
    expect(fakeCourse.destroy).to.have.been.called
    expect(logoutSpy).not.to.have.been.called

    User._signalLogoutCompleted()
    expect(logoutSpy).to.have.been.called

Test logout signal is emitted after destroy


module.exports =
  observeConfig: (keyPath, args...) ->
    @configSubscriptions ?= {}
    @configSubscriptions[keyPath] = config.observe(keyPath, args...)

  unobserveConfig: ->
    for keyPath, subscription of @configSubscriptions ? {}
      subscription.cancel()

module.exports =
  observeConfig: (keyPath, args...) ->
    @configSubscriptions ?= {}
    @configSubscriptions[keyPath] = config.observe(keyPath, args...)

  unobserveConfig: ->
    if @configSubscriptions?
      subscription.cancel() for keyPath, subscription of @configSubscriptions
      @configSubscriptions = null

:non-potable_water: Clear config subscriptions when unobserving


class window.DatabaseItemView extends Backbone.View
  tagName: 'li'
  className: 'databaseItem'

  events:
    'click .databaseItem span': 'gotoDatabase',
    'click .databaseItem a': 'toggleCollections'

  initialize: ->
    @template = _.template $('#databaseItemTemplate').html()

  render: ->
    id = @model.get '_id'
    $(@el).html @template({ id: id, name: @model.get('name'), url: "databases/#{id}" })
    this

  toggleCollections: (event) =>
    console.log 'toggleCollections'
    @collectionsView = new CollectionsView(this, @model) unless @collectionsView
    icon = $('a.treeIcon', @el)
    if @collectionsView.isVisible
      icon.removeClass 'expanded'
      @collectionsView.hide()
    else
      icon.addClass 'expanded'
      @collectionsView.show()

  gotoDatabase: (event) =>
    console.log 'gotoDatabase'

Remove function to open collections when clicking on the database label in the tree


# USE
# If the element is not a component, add full-height-header attribute to the tag.
# With a component, add 'full_height_header: true' to the persisted options
class @MC.Utils.FullHeightHeader
  @autoInit: ->
    $('[full-height-header]').each (i, el) => new @($(el))

  constructor: (@el) ->
    @component = @el.data('component')

    if @component
      @inner = @component.inner
    else
      @inner = @el

    @lastWidth = null
    @offset = @inner.offset().top

    @compute()
    $(window).on 'resize', @compute

  compute: =>
    return false if @lastWidth == $(window).width()
    @inner.css('height', $(window).height() - @offset)
    @lastWidth = $(window).width()
    @el.trigger 'compute.fullHeightHeader.MC'

# USE
# If the element is not a component, add full-height-header attribute to the tag.
# With a component, add 'full_height_header: true' to the persisted options
class @MC.Utils.FullHeightHeader
  @autoInit: ->
    $('[full-height-header]').each (i, el) => new @($(el))

  constructor: (@el) ->
    @el.data('fullHeightHeader', @)

    @component = @el.data('component')

    if @component
      @inner = @component.inner
    else
      @inner = @el

    @lastWidth = null

    @compute()

    $(window).on 'resize', =>
      @compute() unless @lastWidth == $(window).width()
      @lastWidth = $(window).width()

  compute: =>
    @inner.css('height', $(window).height() - @inner.offset().top)
    @el.trigger 'compute.fullHeightHeader.MC'

Update Utils.FullHeightHeader. Put instance into @el so @el can access @compute()


$ ->
  GlobalHeader = React.createClass
    propTypes:
      imagePath: React.PropTypes.string.isRequired
      imageTitle: React.PropTypes.string.isRequired
    render: ->

  globalHeader = document.querySelector('.GlobalHeader')
  imagePath = globalHeader.dataset.headerImagePath
  imageTitle = globalHeader.dataset.headerImageTitle
  React.render(<GlobalHeader imagePath={imagePath} imageTitle={imageTitle} />, globalHeader)

Add link to global header

Add link to global header


#= require_self
#= require_tree .

window.Neighborly =
  Common:
    initPage: ->
      that = this
      unless window.Turbolinks is undefined
        $(document).bind "page:fetch", ->
          that.Loading.show()

        $(document).bind "page:restore", ->
          that.Loading.hide()

        $(document).bind "page:change", ->
          $(window).scrollTop(0)

          try
            FB.XFBML.parse()
          try
            twttr.widgets.load()

    init: ->
      $(document).foundation('reveal', {animation: 'fadeIn', animationSpeed: 100})
      $(document).foundation()

    finish: ->

    Loading:
      show: ->
        $('#loading #back-overlay, #loading #front-overlay').fadeIn(2)
      hide: ->
        $('#loading #back-overlay, #loading #front-overlay').fadeOut(2)

#= require_self
#= require_tree .

window.Neighborly =
  Common:
    initPage: ->
      that = this
      unless window.Turbolinks is undefined
        $(document).bind "page:fetch", ->
          that.Loading.show()

        $(document).bind "page:restore", ->
          that.Loading.hide()

        $(document).bind "page:change", ->
          $(window).scrollTop(0)

          try
            FB.XFBML.parse()
          try
            twttr.widgets.load()

    init: ->
      $(document).foundation('reveal', {animation: 'fadeIn', animationSpeed: 100})
      $(document).foundation()
      $('.search-button').click ->
        if $('.discover-form-input').val() != ''
          $('form.discover-form').submit()
        else
          $('.discover-form-input').toggleClass('show').focus()

        return false

    finish: ->

    Loading:
      show: ->
        $('#loading #back-overlay, #loading #front-overlay').fadeIn(2)
      hide: ->
        $('#loading #back-overlay, #loading #front-overlay').fadeOut(2)

Add javascript for search button

Add javascript for search button


describe "RequireJS", ->
  beforeEach ->
    @addMatchers
      requirejsTobeUndefined: ->
        typeof requirejs is "undefined"

      requireTobeUndefined: ->
        typeof require is "undefined"

      defineTobeUndefined: ->
        typeof define is "undefined"

  it "check that the RequireJS object is present in the global namespace", ->
    expect(RequireJS).toEqual jasmine.any(Object)
    expect(window.RequireJS).toEqual jasmine.any(Object)

  it "check that requirejs(), require(), and define() are not in the global namespace", ->
    expect({}).requirejsTobeUndefined()
    expect({}).requireTobeUndefined()
    expect({}).defineTobeUndefined()
    expect(window.requirejs).not.toBeDefined()
    expect(window.require).not.toBeDefined()
    expect(window.define).not.toBeDefined()

  it "check that the RequireJS has requirejs(), require(), and define() functions as its properties", ->
    expect(RequireJS.requirejs).toEqual jasmine.any(Function)
    expect(RequireJS.require).toEqual jasmine.any(Function)
    expect(RequireJS.define).toEqual jasmine.any(Function)

Work on RequireJS Jasmine test.

Work on RequireJS Jasmine test.


_ = require "underscore-plus"

module.exports =
class Builder
  constructor: ->
    @envPathKey = switch process.env.platform
      when "win32" then "Path"
      else "PATH"

  run: -> null
  constructArgs: -> null
  parseLogFile: (texFilePath) -> null

  constructChildProcessOptions: ->
    env = _.clone(process.env)
    path = @constructPath()
    env[@envPathKey] = path if path?.length
    options = env: env

  constructPath: ->
    texPath = atom.config.get("latex.texPath")
    texPath?.replace("$PATH", process.env[@envPathKey])

  resolveLogFilePath: (texFilePath) ->
    outputDirectory = atom.config.get("latex.outputDirectory") ? ""
    currentDirectory = path.dirname(texFilePath)
    fileName = path.basename(texFilePath).replace(/\.tex$/, ".log")
    logFilePath = path.join(currentDirectory, outputDirectory, fileName)

_ = require "underscore-plus"

module.exports =
class Builder
  constructor: ->
    @envPathKey = switch process.env.platform
      when "win32" then "Path"
      else "PATH"

  run: (args, callback) -> null
  constructArgs: (filePath) -> null
  parseLogFile: (texFilePath) -> null

  constructChildProcessOptions: ->
    env = _.clone(process.env)
    path = @constructPath()
    env[@envPathKey] = path if path?.length
    options = env: env

  constructPath: ->
    texPath = atom.config.get("latex.texPath")
    texPath?.replace("$PATH", process.env[@envPathKey])

  resolveLogFilePath: (texFilePath) ->
    outputDirectory = atom.config.get("latex.outputDirectory") ? ""
    currentDirectory = path.dirname(texFilePath)
    fileName = path.basename(texFilePath).replace(/\.tex$/, ".log")
    logFilePath = path.join(currentDirectory, outputDirectory, fileName)

Adjust `Builder` signature to match implementations


_ = require "underscore-plus"

module.exports =
class Builder
  constructor: ->
    @envPathKey = switch process.env.platform
      when "win32" then "Path"
      else "PATH"

  run: (args, callback) -> null
  constructArgs: (filePath) -> null
  parseLogFile: (texFilePath) -> null

  constructChildProcessOptions: ->
    env = _.clone(process.env)
    path = @constructPath()
    env[@envPathKey] = path if path?.length
    options = env: env

  constructPath: ->
    texPath = atom.config.get("latex.texPath")
    texPath?.replace("$PATH", process.env[@envPathKey])

  resolveLogFilePath: (texFilePath) ->
    outputDirectory = atom.config.get("latex.outputDirectory") ? ""
    currentDirectory = path.dirname(texFilePath)
    fileName = path.basename(texFilePath).replace(/\.tex$/, ".log")
    logFilePath = path.join(currentDirectory, outputDirectory, fileName)

_ = require "underscore-plus"
path = require "path"

module.exports =
class Builder
  constructor: ->
    @envPathKey = switch process.env.platform
      when "win32" then "Path"
      else "PATH"

  run: (args, callback) -> null
  constructArgs: (filePath) -> null
  parseLogFile: (texFilePath) -> null

  constructChildProcessOptions: ->
    env = _.clone(process.env)
    childPath = @constructPath()
    env[@envPathKey] = childPath if childPath?.length
    options = env: env

  constructPath: ->
    texPath = atom.config.get("latex.texPath")
    texPath?.replace("$PATH", process.env[@envPathKey])

  resolveLogFilePath: (texFilePath) ->
    outputDirectory = atom.config.get("latex.outputDirectory") ? ""
    currentDirectory = path.dirname(texFilePath)
    fileName = path.basename(texFilePath).replace(/\.tex$/, ".log")
    logFilePath = path.join(currentDirectory, outputDirectory, fileName)

Fix bug due to accidental use of `path` module

The function `Builder::resolveLogFilePath` uses `path` module rather than
the `path` variable. Changes variable name to `childPath` and requires
the `path` module.

Closes #28